Skip to content
源码版本47f9438 (dsh@0.1)

Cordis Context

Context is Cordis's root container. It is not a plain object but a Proxy proxied by ReflectService.handler — reading ordinary properties goes through the service resolver, while extend() / isolate() / intercept() create child contexts via the prototype chain without modifying the parent. Context both carries built-in services and, via TypeScript's declare module interface merging, lets third-party plugins declare new ctx.<key>s.

The Public Shape of Context

vendor/cordis/src/context.ts:16-33 defines interface Context:
  • Four built-in services: events / logger / reflect / registry
  • The root and baseUrl pointers
  • Two symbol-based internal maps: [symbols.isolate] and [symbols.intercept]

These fields are not ordinary properties; they are forwarded through Context's Proxy to the service store.

Context Is a Proxy

The most direct evidence is in vendor/cordis/src/context.ts:42-84: inside the class Context constructor:

ts
new Proxy<this>(this, ReflectService.handler)

The constructor creates the root context and mounts the built-in services, with the root fiber using an empty runtime. This is the root cause of "Context is a proxy" — all property reads and writes are intercepted by ReflectService.

Cross-Copy Identification

vendor/cordis/src/context.ts:61-68 implements identification across realms / across Cordis copies:
ts
static is(value) { ... }
static { Context.is[Symbol.toPrimitive] = () => Symbol.for('cordis.is') }

It uses a global symbol rather than instanceof to identify Context. This matters: dsh makes heavy use of vm sandboxes (for dynamic plugins), where instanceof on a Proxy fails, so Cordis routes around it with a global symbol.

Layering: extend and the Prototype Chain

extend(meta) at vendor/cordis/src/context.ts:99-107:

ts
extend(meta) {
  return Object.create(getTraceable(this, this))  // prototype-chain child context
}

extend returns an Object.create(this) object; own keys of meta override inherited values, and the parent context is not modified. This means Context's "layering" relies on the JS prototype chain rather than immutable values — reads walk the prototype, writes only write to self.

Isolation: isolate

isolate(name, label?) at vendor/cordis/src/context.ts:121-125:

ts
isolate(name, label?) {
  // copy the isolate map and give name a new symbol scope
}

The same service name (e.g. shell) can register different implementations in two isolate('shell') subtrees, without interfering with each other. This is critical for multi-agent / multi-session scenarios — every session can have its own shell instance.

Interception: intercept

intercept(name, config) at vendor/cordis/src/context.ts:139-145:

ts
intercept(name, config) {
  // attach the intercept config onto the child context
}

When a service starts, [symbols.resolveConfig] merges along the prototype chain. This is Cordis DI's equivalent of "parameter injection" — ctx.intercept('llm', { provider: 'x' }) lets every plugin in the subtree see an llm config that merges in this override, while the root context is unaffected.

Service Store and Mixin

The official docs docs/cordis-api/context.md:236-339 list the "Service store and mixins" APIs: ctx.get / ctx.set / ctx.provide / ctx.accessor / ctx.mixin. Their actual definitions live in reflect.ts but are exposed through the Context proxy:

  • Writing ctx.foo triggers the ReflectService handler, which puts the value into the service store.
  • Reading ctx.foo resolves the impl in the current scope from the store.
  • ctx.onctx.events.on is a mixin-forwarding accessor, which lets a service instance be replaced while ctx.on always points at the currently active impl.

Why Symbols Instead of Strings

Context.effect / Context.filter / Context.isolate / Context.intercept on Context are all unique symbol static fields. Plugin authors access metadata via ctx[Context.effect] etc., avoiding collisions with string keys — because ctx is a proxy, any string-keyed property access is intercepted by the service resolver; only symbols can reach the internal maps directly.

Unofficial community learning site. Content based on the MIT-licensed deepseek-ai/deepseek-harness source. · Privacy · Terms · About