English | 中文
Session-projection Service Definition and drive registry. It owns ctx.sessionProjections, the registry that drives every registered projection unit over committed session events and serves finished whole values to carriers, currently the api-proxy history tail page and session/projection push frame. A domain registers pure mathematics; the framework owns the drive. The session-projection RFC records the design rationale.
SessionProjectionRegistry (ctx key: sessionProjections)ctx.sessionProjections.register(definition): () => void Register one domain's unit. Duplicate keys and invalid stateVersion throw; the registration is an effect on the calling fiber, so an unloaded domain plugin's key (with its cached cells) disappears from subsequent drives and snapshots — clients read that as capability absence.ctx.sessionProjections.onChanged(listener): () => void Subscribe to the change feed: one call per client-visible unit whose state reference changed, per committed event, carrying the schema-validated view and the causing seq. Effect-tied like register.ctx.sessionProjections.stateOf(session, key) Read one registered unit's current host state without computing unrelated views. The returned value is a live read-only reference; callers must not mutate it.ctx.sessionProjections.snapshot(session): ProjectionSnapshot One consistent synchronous cut over every registered client-visible unit — { asOfSeq, values } with asOfSeq = the seq of the last event every value reflects (-1 for an empty log). Host-only state is available only through stateOf.SessionProjectionMap — the merge-extensible client-view table shared by wire blocks and client hooks. Values are wire-JSON whole values; rendering belongs to the slot system, never this layer.SessionProjectionStateMap — the merge-extensible host fold-state table. Every client-visible key appears in both tables; host-only keys appear only here.ProjectionDefinition<K, S> — { key, stateSchema, init(), apply(state, event), wire?, stateVersion }: a synchronous state-driven computation unit. wire supplies viewSchema and view; omitting it makes the unit host-only.session/event once; every committed event passes every unit's apply eagerly. Domains hold no subscriptions. Cells ({state, observedSeq} per unit per session, WeakMap-keyed) build lazily — a unit registered after events flowed, or a read of a session predating the registration, folds init over the in-memory log on first touch.apply MUST return the same state reference for events that do not concern the unit; the drive gates the change feed on Object.is, so non-matching events cost one call and nothing downstream.init/apply/wire.view MUST be synchronous; carriers read snapshot() in the same tick as their page slice, which is what makes asOfSeq one consistent cut. An accidentally async view returns a Promise, which fails wire.viewSchema.parse.stateVersion is its invalidation anchor. The persisted projection cache stores (sessionId, key, ver, seq, val) rows and validates val with stateSchema before use; bump stateVersion whenever the state fields or fold semantics change. Every unit's state is checkpointed — client-visible and host-only alike.session/projection) and blocks from them.ctx.inject(['sessionProjections'], …) so headless assemblies without the registry stay unaffected; carriers use ctx.get('sessionProjections') and omit their block/frames entirely when the registry is absent.This package owns the Service Definition and drive roles of the capability seam: domain host plugins (e.g. dsh-tool-todo) contribute units, carriers (dsh-host-apiproxy) consume the snapshot and change feed, and neither knows the other.
None, as the registry only computes client-facing read models of already-logged session state and touches no prompt, message, schema, stream, or tool result.
None; projections never assemble or send provider requests.
plan.active, an empty todo list) rather than treat an absent key as absence of the feature; a unit whose empty value is indistinguishable from a real one belongs on the host plane instead, which is why dsh-token-meter sits there.dsh-session-projection-cache seed that fold from persisted rows instead.wire.viewSchema.parse rejects a Promise-returning view, but an apply that blocks or reads torn non-session state is a review concern; the invariant companion documents why no runtime check exists.