mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Conflict resolution notes: - package.json/run-gates: both sides' new doc-sync gates kept (master's scoped-events/readme gates + this branch's website-api/website-yaml); js-yaml devDeps deduped (master added them independently). - pnpm-workspace/knip: website AND python/sdk-runtime entries kept. - doc-typecheck/verify-type-equiv: master's condensed headers kept, website glob retained in both scan scopes. - vendor/cordis/src/fiber.ts: master's lifecycle-hardening code taken; this branch's richer FiberState JSDoc reapplied on top. vendor/README.md logs both local modifications (hardening = 6, JSDoc enrichment = 7). - pnpm-lock: regenerated from master's side (pnpm install). Post-merge sync the gates forced (the system working as designed): - verify-website-yaml caught 4 stale plugin names from master's package reorg (dsh-stdio-agent -> dsh-stdio-demo, dsh-acp-agent -> dsh-acp-demo); 8 references fixed across guide/ and develop/. - gen-website-api picked up master's 6 new services automatically (ctx.approval/permission/sandbox/sessionQuery/skills/tasks -> 6 new pages + sidebar); api/index.md hub updated to list them. - AGENTS.md budget ceiling 1370 -> 1400: the website rows (layout line + two command lines) and master's own growth collided with the old ceiling; all three website rows are load-bearing (new top-level dir, new CI command).
418 lines
15 KiB
TypeScript
418 lines
15 KiB
TypeScript
import { defineProperty, Dict, isNullable } from 'cosmokit'
|
|
import { Context } from './context.ts'
|
|
import { getTraceable, symbols, withProps } from './utils.ts'
|
|
import { Fiber, FiberState } from './fiber.ts'
|
|
|
|
declare module './context.ts' {
|
|
interface Context {
|
|
/**
|
|
* Read a service from the store without the inject requirement.
|
|
*
|
|
* @param name — the service name.
|
|
* @param strict — when `true` (default), only return implementations
|
|
* whose providing fiber is currently active.
|
|
* @returns the service value, or `undefined` when not (yet) provided.
|
|
*/
|
|
get<K extends string & keyof this>(name: K, strict?: boolean): undefined | this[K]
|
|
/** Same as above for service names outside the typed `Context` surface. */
|
|
get(name: string, strict?: boolean): any
|
|
/**
|
|
* Overwrite a provided service's value.
|
|
*
|
|
* Only the fiber that provided the service may set it; setting an
|
|
* unprovided name throws.
|
|
*
|
|
* @param name — the service name.
|
|
* @param value — the new service value.
|
|
*/
|
|
set<K extends string & keyof this>(name: K, value: undefined | this[K]): void
|
|
/** Same as above for service names outside the typed `Context` surface. */
|
|
set(name: string, value: any): void
|
|
/**
|
|
* Register a service implementation owned by the current fiber.
|
|
*
|
|
* The service becomes visible to dependents in the same isolation scope
|
|
* once the fiber is active; it is unregistered (waking dependents) when
|
|
* the returned disposer runs or the fiber unloads. Throws if the name is
|
|
* already provided in this scope or declared as an accessor.
|
|
*
|
|
* @param name — the service name.
|
|
* @param value — the service value.
|
|
* @returns a disposer that unregisters the service.
|
|
*/
|
|
provide<K extends string & keyof this>(name: K, value: undefined | this[K]): () => void
|
|
/** Same as above for service names outside the typed `Context` surface. */
|
|
provide(name: string, value?: any): () => void
|
|
/**
|
|
* Define a computed context property backed by get/set hooks.
|
|
*
|
|
* The accessor is removed when the current fiber unloads. Throws if the
|
|
* name is already declared.
|
|
*
|
|
* @param name — the context property name.
|
|
* @param options — the `get` hook and optional `set` hook.
|
|
*/
|
|
accessor(name: string, options: Omit<Property.Accessor, 'type'>): void
|
|
/**
|
|
* Expose selected members of a service directly on `ctx`.
|
|
*
|
|
* Each mixed-in key becomes an accessor that forwards to the service
|
|
* (binding methods to it), so e.g. `ctx.on` forwards to `ctx.events.on`.
|
|
* Mixins are removed when the current fiber unloads.
|
|
*
|
|
* @param name — the context property holding the source service.
|
|
* @param mixins — keys to forward, or a source-key → ctx-key map.
|
|
*/
|
|
mixin<K extends string & keyof this>(name: K, mixins: (keyof this & keyof this[K])[] | Dict<string>): void
|
|
/** Same as above with a source object instead of a context property name. */
|
|
mixin<T extends {}>(source: T, mixins: (keyof this & keyof T)[] | Dict<string>): void
|
|
}
|
|
}
|
|
|
|
function enhanceError(error: Error) {
|
|
const lines = error.stack!.split('\n')
|
|
lines.splice(0, 2, `Error: ${error.message}`)
|
|
error.stack = lines.join('\n')
|
|
return error
|
|
}
|
|
|
|
const RESERVED_WORDS = ['prototype', 'then']
|
|
|
|
// - is a symbol
|
|
// - is a reserved word (prototype, then)
|
|
// - is a number string (0, 1, 2, ...)
|
|
// - starts with `_`
|
|
function isSpecialProperty(prop: string | symbol): prop is symbol {
|
|
return typeof prop === 'symbol'
|
|
|| RESERVED_WORDS.includes(prop)
|
|
|| parseInt(prop).toString() === prop
|
|
|| prop.startsWith('_')
|
|
}
|
|
|
|
/** Context property definition known by the reflection service. */
|
|
export type Property = Property.Service | Property.Accessor
|
|
|
|
/** Property definition variants understood by `ReflectService`. */
|
|
export namespace Property {
|
|
/** Service property backed by a provided implementation. */
|
|
export interface Service {
|
|
/** Discriminator. */
|
|
type: 'service'
|
|
}
|
|
|
|
/** Computed context property backed by custom get/set hooks. */
|
|
export interface Accessor {
|
|
/** Discriminator. */
|
|
type: 'accessor'
|
|
/** Compute the property value; `error` carries the caller stack for diagnostics. */
|
|
get: (this: Context, receiver: any, error: Error) => any
|
|
/** Optional setter; return `false` to reject the write. */
|
|
set?: (this: Context, value: any, receiver: any, error: Error) => boolean
|
|
}
|
|
}
|
|
|
|
/** Concrete service implementation record stored in the root reflect service. */
|
|
export interface Impl {
|
|
/** The service name. */
|
|
name: string
|
|
/** The fiber that provided the service (owns its lifetime). */
|
|
fiber: Fiber
|
|
/** The current service value. */
|
|
value?: any
|
|
/** Optional availability predicate consulted before dependents may load. */
|
|
check?: () => boolean
|
|
}
|
|
|
|
/**
|
|
* Reflection and service-resolution layer installed as `ctx.reflect`.
|
|
*
|
|
* This service powers the context proxy, service registration, accessors, and
|
|
* the mixins that expose core service methods directly on `ctx`.
|
|
*/
|
|
export class ReflectService {
|
|
/** Proxy traps implementing service resolution for every context object. */
|
|
static handler: ProxyHandler<Context> = {
|
|
get: (target, prop, ctx: Context) => {
|
|
if (isSpecialProperty(prop)) {
|
|
return Reflect.get(target, prop, ctx)
|
|
}
|
|
if (Reflect.has(target, prop)) {
|
|
return getTraceable(ctx, Reflect.get(target, prop, ctx))
|
|
}
|
|
|
|
const error = new Error(`cannot get property "${prop}" without inject`)
|
|
|
|
try {
|
|
const def = target.reflect.props[prop]
|
|
if (def?.type === 'accessor') {
|
|
return def.get.call(ctx, ctx[symbols.receiver], error)
|
|
}
|
|
|
|
if (!ctx.fiber.runtime) return ctx.reflect.get(prop, false)
|
|
return ctx.events.waterfall('internal/get', ctx, prop, error, () => {
|
|
const key = target[symbols.isolate][prop]
|
|
let fiber = (ctx[symbols.shadow] as Context ?? ctx).fiber
|
|
while (true) {
|
|
const impl = fiber.store?.[prop]
|
|
if (impl) return getTraceable(ctx, impl.value)
|
|
if (prop in fiber.inject) {
|
|
error.message = `cannot get required service "${prop}" in inactive context`
|
|
throw error
|
|
}
|
|
if (!fiber.runtime) throw error
|
|
if (fiber.parent[symbols.isolate][prop] !== key) throw error
|
|
fiber = fiber.parent.fiber
|
|
}
|
|
})
|
|
} catch (e: any) {
|
|
throw e === error ? enhanceError(e) : e
|
|
}
|
|
},
|
|
|
|
set: (target, prop, value, ctx: Context) => {
|
|
if (isSpecialProperty(prop)) {
|
|
return Reflect.set(target, prop, value, ctx)
|
|
}
|
|
|
|
const error = new Error(`cannot set property "${prop}" without provide`)
|
|
const def = target.reflect.props[prop]
|
|
if (!def) {
|
|
if (!ctx.fiber.runtime) return Reflect.set(target, prop, value, ctx)
|
|
throw enhanceError(error)
|
|
}
|
|
|
|
try {
|
|
if (def.type === 'accessor') {
|
|
if (!def.set) return false
|
|
return def.set.call(ctx, value, ctx[symbols.receiver], error)
|
|
}
|
|
|
|
return ctx.events.waterfall('internal/set', ctx, prop, value, error, () => {
|
|
return ctx.reflect.set(prop, value, error)
|
|
})
|
|
} catch (e: any) {
|
|
throw e === error ? enhanceError(e) : e
|
|
}
|
|
},
|
|
|
|
has: (target, prop) => {
|
|
if (isSpecialProperty(prop)) {
|
|
return Reflect.has(target, prop)
|
|
}
|
|
if (Reflect.has(target, prop)) return true
|
|
return !!target.reflect.props[prop]
|
|
},
|
|
}
|
|
|
|
/** Service implementations, keyed by isolation label. */
|
|
public store: Dict<Impl, symbol> = Object.create(null)
|
|
/** Declared context properties (services and accessors), by name. */
|
|
public props: Dict<Property> = Object.create(null)
|
|
|
|
constructor(public ctx: Context) {
|
|
defineProperty(this, symbols.tracker, {
|
|
property: 'ctx',
|
|
noShadow: true,
|
|
})
|
|
|
|
this.mixin('reflect', ['get', 'set', 'provide', 'accessor', 'mixin'])
|
|
this.mixin('fiber', ['runtime', 'effect'])
|
|
this.mixin('registry', ['inject', 'plugin'])
|
|
this.mixin('events', ['on', 'once', 'parallel', 'emit', 'serial', 'bail', 'waterfall'])
|
|
}
|
|
|
|
/**
|
|
* Read a service from the store without the inject requirement.
|
|
*
|
|
* @param name — the service name.
|
|
* @param strict — when `true`, only return implementations whose providing
|
|
* fiber is currently active.
|
|
* @returns the service value, or `undefined` when not (yet) provided.
|
|
*/
|
|
get(name: string, strict = true) {
|
|
return getTraceable(this.ctx, this._getImpl(name, strict)?.value)
|
|
}
|
|
|
|
_getImpl(name: string, strict = true) {
|
|
const key = this.ctx[symbols.isolate][name]
|
|
const impl = key && this.store[key]
|
|
if (!impl) return
|
|
if (strict && impl.fiber.state !== FiberState.ACTIVE) return
|
|
return impl
|
|
}
|
|
|
|
/**
|
|
* Overwrite a provided service's value.
|
|
*
|
|
* @param name — the service name.
|
|
* @param value — the new service value.
|
|
* @param error — carrier for the caller stack in diagnostics.
|
|
* @returns `true` on success.
|
|
* @throws when `name` was never provided, or was provided by another fiber.
|
|
*/
|
|
set(name: string, value: any, error?: Error) {
|
|
const key = this.ctx[symbols.isolate][name]
|
|
const impl = this.store[key]
|
|
if (!impl) {
|
|
throw new Error(`cannot set property "${name}" without provide`)
|
|
}
|
|
if (impl.fiber !== this.ctx.fiber) {
|
|
throw new Error(`cannot set property "${name}" in multiple fibers`)
|
|
}
|
|
impl.value = value
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* Register a service implementation owned by the current fiber.
|
|
*
|
|
* See the `ctx.provide()` overload above for the full contract.
|
|
*
|
|
* @param name — the service name.
|
|
* @param value — the service value.
|
|
* @param check — optional availability predicate for dependents.
|
|
* @returns a disposer that unregisters the service.
|
|
*/
|
|
provide(name: string, value?: any, check?: () => boolean) {
|
|
return this.ctx.fiber.effect(() => {
|
|
if (!this.props[name]) {
|
|
this.props[name] ??= { type: 'service' }
|
|
} else if (this.props[name].type !== 'service') {
|
|
throw new Error(`property "${name}" is already declared as ${this.props[name].type}`)
|
|
}
|
|
this.props[name] = { type: 'service' }
|
|
|
|
this.ctx.root[symbols.isolate][name] ??= Symbol(name)
|
|
const key = this.ctx[symbols.isolate][name]
|
|
const impl: Impl = { name, value, fiber: this.ctx.fiber, check }
|
|
if (this.store[key]) {
|
|
throw new Error(`service "${name}" has been registered at <${this.store[key].fiber.name}>`)
|
|
}
|
|
this.store[key] = impl
|
|
this.ctx.fiber.store![name] = impl
|
|
if (this.ctx.fiber.state === FiberState.ACTIVE) {
|
|
this.notify([name])
|
|
}
|
|
return async () => {
|
|
delete this.store[key]
|
|
const fibers = this.notify([name])
|
|
await Promise.allSettled(fibers.map(fiber => fiber.await()))
|
|
// ensure self access before dependencies cleanup
|
|
delete this.ctx.fiber.store![name]
|
|
}
|
|
}, `ctx.provide(${JSON.stringify(name)})`)
|
|
}
|
|
|
|
/**
|
|
* Re-evaluate every fiber that requires one of the given services.
|
|
*
|
|
* @param names — the service names that changed.
|
|
* @param filter — restricts notification to matching isolation scopes.
|
|
* @returns the fibers whose dependency state was refreshed.
|
|
*/
|
|
notify(names: string[], filter = (ctx: Context, name: string) => ctx[symbols.isolate][name] === this.ctx[symbols.isolate][name]) {
|
|
const fibers: Fiber[] = []
|
|
for (const runtime of this.ctx.registry.values()) {
|
|
for (const fiber of runtime.fibers) {
|
|
let hasUpdate = false
|
|
for (const name of names) {
|
|
if (!(name in fiber.inject)) continue
|
|
if (!filter(fiber.ctx, name)) continue
|
|
hasUpdate = true
|
|
fiber._checkImpl(name)
|
|
}
|
|
if (!hasUpdate) continue
|
|
fiber._refresh()
|
|
fibers.push(fiber)
|
|
}
|
|
}
|
|
for (const name of names) {
|
|
const self: Context = Object.create(this.ctx)
|
|
self[symbols.filter] = (target: Context) => filter(target, name)
|
|
this.ctx.events.emit(self, 'internal/service', name, this._getImpl(name, false)?.value)
|
|
}
|
|
return fibers
|
|
}
|
|
|
|
/**
|
|
* Define a computed context property backed by get/set hooks.
|
|
*
|
|
* @param name — the context property name.
|
|
* @param options — the `get` hook and optional `set` hook.
|
|
* @returns a disposer that removes the accessor.
|
|
*/
|
|
accessor(name: string, options: Omit<Property.Accessor, 'type'>) {
|
|
return this.ctx.fiber.effect(() => {
|
|
if (name in this.props) {
|
|
throw new Error(`property "${name}" is already declared as ${this.props[name].type}`)
|
|
}
|
|
this.props[name] = { type: 'accessor', ...options }
|
|
return () => delete this.props[name]
|
|
}, `ctx.accessor(${JSON.stringify(name)})`)
|
|
}
|
|
|
|
/**
|
|
* Expose selected members of a service directly on `ctx`.
|
|
*
|
|
* See the `ctx.mixin()` overload above for the full contract.
|
|
*
|
|
* @param source — a context property name or a source object.
|
|
* @param mixins — keys to forward, or a source-key → ctx-key map.
|
|
* @returns a disposer that removes all created accessors.
|
|
*/
|
|
mixin(source: any, mixins: string[] | Dict<string>) {
|
|
const self = this
|
|
return this.ctx.fiber.effect(function* () {
|
|
const entries = Array.isArray(mixins) ? mixins.map(key => [key, key]) : Object.entries(mixins)
|
|
const getTarget = (ctx: Context, error: Error) => {
|
|
// TODO enhance error message
|
|
return ctx[source]
|
|
}
|
|
for (const [key, value] of entries) {
|
|
yield self.accessor(value, {
|
|
get(receiver, error) {
|
|
const service = getTarget(this, error)
|
|
if (isNullable(service)) return service
|
|
const mixin = receiver ? withProps(receiver, service) : service
|
|
const value = Reflect.get(service, key, mixin)
|
|
if (typeof value !== 'function') return value
|
|
return value.bind(mixin ?? service)
|
|
},
|
|
set(value, receiver, error) {
|
|
const service = getTarget(this, error)
|
|
const mixin = receiver ? withProps(receiver, service) : service
|
|
return Reflect.set(service, key, value, mixin)
|
|
},
|
|
})
|
|
}
|
|
}, `ctx.mixin(${JSON.stringify(source)})`)
|
|
}
|
|
|
|
/**
|
|
* Attach this context's tracing wrapper to a value.
|
|
*
|
|
* @param value — the value to wrap.
|
|
* @returns the traceable wrapper (or the value itself when not applicable).
|
|
*/
|
|
trace<T>(value: T) {
|
|
return getTraceable(this.ctx, value)
|
|
}
|
|
|
|
/**
|
|
* Wrap a callback so calls trace `this` and arguments to this context.
|
|
*
|
|
* @param callback — the function to wrap.
|
|
* @returns a proxy delegating to `callback` with traced values.
|
|
*/
|
|
bind<T extends Function>(callback: T) {
|
|
return new Proxy(callback, {
|
|
apply: (target, thisArg, args) => {
|
|
return Reflect.apply(target, this.trace(thisArg), args.map(arg => this.trace(arg)))
|
|
},
|
|
construct: (target, args, newTarget) => {
|
|
return Reflect.construct(target, args.map(arg => this.trace(arg)), newTarget)
|
|
},
|
|
})
|
|
}
|
|
}
|