@stackstackstack/dsh-agent 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,385 @@
1
+ /**
2
+ * Agent service: live registry, factory delegation, and process-local
3
+ * initiator scope. Concrete creation and driving belong to the loop.
4
+ *
5
+ * @module @stackstackstack/dsh-agent
6
+ */
7
+ import { Context, Service } from '@deepseek-ai/cordis';
8
+ import type { SessionEvent, SessionId } from '@stackstackstack/dsh-session';
9
+ import type { TypertContext, TypertLookup } from '@stackstackstack/dsh-typert-protocol';
10
+ import type { Agent, AgentOptions } from './runtime-types.ts';
11
+ export * from './runtime-types.ts';
12
+ export * from './types.ts';
13
+ export * from './inbox.ts';
14
+ export * from './consumed-work.ts';
15
+ export * from './model-selection.ts';
16
+ export { agentCarrier, agentEvents, assembleContextFor, emitAgentEvent } from './dispatch.ts';
17
+ export type { AgentEventDispatch, AgentSubjectEvent } from './dispatch.ts';
18
+ declare module '@stackstackstack/dsh-typert-protocol' {
19
+ interface TypertLookupMap {
20
+ agent: TypertLookup<Agent, SessionId>;
21
+ }
22
+ interface TypertContextMap {
23
+ agent: TypertContext<SessionId>;
24
+ }
25
+ }
26
+ declare module '@deepseek-ai/cordis' {
27
+ interface Context {
28
+ agents: AgentRegistry;
29
+ /**
30
+ * The agent association installed as an own property on `Agent.ctx`, or
31
+ * `undefined` on a plain context. Contexts derived from `Agent.ctx` inherit
32
+ * the association; a deliberately nested scope may carry a nearer
33
+ * `dsh-scope` tag while retaining it, so this field is DX context rather
34
+ * than the scope resolver. {@link AgentRegistry} registers a root accessor
35
+ * defaulting to `undefined`, and core packages below the agent layer use
36
+ * `scopeOf()` for layer selection instead of reading this field.
37
+ */
38
+ agent?: Agent;
39
+ }
40
+ }
41
+ /**
42
+ * Synchronous finalizer returned by unpublished Agent setup when its
43
+ * contributions need validation at the exact publication commit point.
44
+ */
45
+ export interface AgentSetupCommit {
46
+ /**
47
+ * Validate and commit the prepared setup immediately before publication.
48
+ * @throws when publication must roll the unpublished Agent back.
49
+ */
50
+ commit(): void;
51
+ }
52
+ /**
53
+ * Compose an unpublished Agent scope and optionally return its publication commit.
54
+ * @param agentCtx - unpublished Agent scope.
55
+ * @returns an optional synchronous commit invoked after setup awaits settle and immediately before publication.
56
+ */
57
+ export type AgentSetup = (agentCtx: Context) => AgentSetupCommit | Promise<AgentSetupCommit | void> | void;
58
+ /**
59
+ * Options for programmatically creating an agent through the registry factory
60
+ * ({@link AgentRegistry.create}). The caller supplies the single live
61
+ * `sessionId` shared by the agent registry and session log (e.g. an
62
+ * ACP-generated id), plus optional session metadata (the validated `cwd`, fork
63
+ * lineage); the factory creates the session and agent under that identity.
64
+ */
65
+ export interface CreateAgentOptions {
66
+ /** The live agent/session identity. */
67
+ readonly sessionId: SessionId;
68
+ /**
69
+ * Session creation metadata: validated absolute `cwd`, `parentSession`
70
+ * fork lineage, the `seedLength` seed boundary, the coarse `origin`
71
+ * classification, and the `delegationDepth` recursion budget. Mirrors the
72
+ * `cwd`/`parentSession`/`seedLength`/`origin`/`delegationDepth` fields of
73
+ * {@link CreateSessionOptions.meta} in dsh-session (the internal-only
74
+ * `createdAt`, used when reconstructing a persisted session, is deliberately
75
+ * excluded — a factory caller never sets it). This is durable session data,
76
+ * so the session boundary validates and snapshots it before asynchronous
77
+ * setup begins.
78
+ */
79
+ readonly meta?: {
80
+ readonly cwd?: string;
81
+ readonly parentSession?: SessionId;
82
+ readonly seedLength?: number;
83
+ readonly origin?: 'subagent';
84
+ readonly delegationDepth?: number;
85
+ readonly agentPreset?: string;
86
+ };
87
+ /**
88
+ * Initial replay/fork history. A fork supplies a balanced completed-turn
89
+ * prefix of the parent's log. The complete seed must be contiguous from seq
90
+ * 0, carry only lossless-JSON data, and contain no open turn/step or dangling
91
+ * tool call. The factory passes it to the session's durable
92
+ * validator/snapshot boundary before publication.
93
+ */
94
+ readonly seed?: readonly SessionEvent[];
95
+ /** Per-agent options (model, …). */
96
+ readonly agentOptions?: AgentOptions;
97
+ /** Optional creation-only cancellation signal; detached before the returned handle becomes visible. */
98
+ readonly signal?: AbortSignal;
99
+ /**
100
+ * Creation-time composition of the agent's scoped world. The factory awaits
101
+ * setup after minting `agentCtx` but BEFORE inserting or announcing either
102
+ * the session or agent, so observers can never see a partially configured
103
+ * world. Setup may return an {@link AgentSetupCommit}; the factory invokes its
104
+ * synchronous `commit()` after every setup await settles and immediately
105
+ * before registry publication. This lets mutable provisioning revalidate at
106
+ * the exact publication boundary. Everything registered through `agentCtx`
107
+ * (scoped tools, prompt sections/variables, `restrict()`, listeners, awaited
108
+ * child plugins) exists before `session/created`, `agent/created`,
109
+ * `agent/session-start`, and the first prompt assembly. A setup
110
+ * throw/rejection, commit throw, or owner disposal rolls the scope back
111
+ * without publishing either id.
112
+ *
113
+ * **Setup composes, it never drives**: the callback is trusted same-process
114
+ * code and receives the full scoped context, so this is a contract rather
115
+ * than a runtime restriction. Drive the agent only after creation resolves.
116
+ */
117
+ readonly setup?: AgentSetup;
118
+ }
119
+ /**
120
+ * Options for resuming an agent on a persisted session
121
+ * ({@link AgentRegistry.resume}).
122
+ */
123
+ export interface ResumeAgentOptions {
124
+ /** The persisted session id to load and use as the live agent/session identity. */
125
+ readonly resumeSessionId: SessionId;
126
+ /** Per-agent options (model, …). */
127
+ readonly agentOptions?: AgentOptions;
128
+ /** Optional creation-only cancellation signal for persistence load/setup; detached before return. */
129
+ readonly signal?: AbortSignal;
130
+ /**
131
+ * Resume-time composition of the agent's fresh scoped world. Persistence is
132
+ * loaded first; the factory then mints `agentCtx` and awaits setup while the
133
+ * reconstructed session and agent remain unpublished. The callback has the
134
+ * same trusted composition-only contract and optional synchronous
135
+ * publication commit as {@link CreateAgentOptions.setup}: all registrations
136
+ * exist before either creation announcement, and rejection, commit failure,
137
+ * or owner disposal rolls the transaction back without publishing either id.
138
+ */
139
+ readonly setup?: AgentSetup;
140
+ }
141
+ /**
142
+ * An owned agent plus its disposer, returned by {@link AgentRegistry.create} /
143
+ * {@link AgentRegistry.resume}. The disposer is a CAPABILITY: among consumers,
144
+ * only the holder can tear this agent down. The registered factory provider is
145
+ * also a structural owner because the scoped agent depends on that provider's
146
+ * service API; provider unload stops and drains every live handle it made.
147
+ * `dispose()` stops the loop, awaits its exit, unregisters the agent, removes
148
+ * its session from the store, and finally unwinds its scoped world.
149
+ *
150
+ * `ctx.agents.get(id)` still returns a bare {@link Agent} — the handle is
151
+ * exposed only to the consumer owner that created it; the structural provider
152
+ * reaches the same teardown internally. Config-created agents (the loop's own
153
+ * startup) are owned by the loop fiber and never need a handle.
154
+ */
155
+ export interface AgentHandle {
156
+ agent: Agent;
157
+ dispose(): Promise<void>;
158
+ }
159
+ /**
160
+ * The agent-creation factory the loop implementation provides to the registry
161
+ * via {@link AgentRegistry.setFactory}. Kept on the `dsh-agent` interface so
162
+ * consumers (e.g. the ACP bridge) program against `ctx.agents` without
163
+ * depending on the concrete `dsh-agent-loop` package.
164
+ */
165
+ export interface AgentFactory {
166
+ /**
167
+ * Create a new agent on a caller-supplied session id. Async because creation
168
+ * awaits unpublished setup, invokes its optional synchronous commit, inserts
169
+ * both session and agent, emits their creation notifications in order, emits
170
+ * `agent/session-start`, and only then starts the loop. The sequence is
171
+ * rollback-covered, but notifications delivered before a later listener
172
+ * failure remain observable; every agent or session creation announcement
173
+ * that began is paired by `agent/disposed` or `session/disposed` during
174
+ * rollback. The owner disposes the resolved handle to stop/drain,
175
+ * unregister, remove the session, and unwind the scope.
176
+ * The registry passes a context carrying the `create()` caller's fiber and
177
+ * scope as `ownerCtx`. The implementation attaches the unpublished
178
+ * transaction and resulting lifecycle to that owner; it must not infer
179
+ * ownership from the factory object's registration context.
180
+ * @param ownerCtx - caller-bound context that owns the transaction and live handle.
181
+ * @param options - agent/session identity, configuration, and optional setup.
182
+ * @returns the owned handle after setup, both announcements, and loop start complete.
183
+ */
184
+ createAgent(ownerCtx: Context, options: CreateAgentOptions): Promise<AgentHandle>;
185
+ /**
186
+ * Prepare a persisted session and resume an agent on it. Async because it awaits
187
+ * both `ctx.sessionPersistence.prepare` and the optional unpublished setup
188
+ * transaction; must be called after that service exists (consumers inject
189
+ * `sessionPersistence`). Publication follows the same setup-commit and
190
+ * ordered boundary as {@link createAgent}.
191
+ * @param ownerCtx - caller-bound context that owns load, setup, and the live handle.
192
+ * @param options - persisted identity, configuration, and optional setup.
193
+ * @returns the owned handle after setup, both announcements, and loop start complete.
194
+ */
195
+ resume(ownerCtx: Context, options: ResumeAgentOptions): Promise<AgentHandle>;
196
+ }
197
+ /**
198
+ * Agent service (`ctx.agents`): tracks live agents and carries the initiating
199
+ * Agent through one process-local asynchronous driver chain. Agent *creation*
200
+ * is provided by whichever plugin implements the {@link AgentFactory}
201
+ * (`@stackstackstack/dsh-agent-loop`), registered via {@link setFactory}.
202
+ *
203
+ * Initiator methods provide same-process causal attribution only. Ambient
204
+ * presence is neither liveness proof nor authorization; subjects and owners
205
+ * remain explicit, as does identity at worker, process, persistence, and wire
206
+ * boundaries. Returned Promise boundaries drain during teardown, except a
207
+ * nested lineage that starts an owning-fiber unload is excluded from its own drain.
208
+ */
209
+ export declare class AgentRegistry extends Service {
210
+ private store;
211
+ private factory;
212
+ private readonly initiators;
213
+ private readonly initiatorRuns;
214
+ private initiatorState;
215
+ private activeInitiatorRuns;
216
+ private initiatorDrain;
217
+ private initiatorDisposal;
218
+ constructor(ctx: Context);
219
+ /**
220
+ * Read the Agent that initiated the inherited asynchronous driver chain.
221
+ * Use this optional form for logging, tracing, metrics, or host attribution
222
+ * that also supports agentless calls. When a parent creates a child, setup
223
+ * reports the causal parent while `agentCtx.agent` identifies the child.
224
+ * @returns the inherited Agent, or `undefined` outside an initiator boundary
225
+ * and inside an explicit clearing boundary.
226
+ * @throws when this service instance has been disposed.
227
+ */
228
+ currentInitiator(): Agent | undefined;
229
+ /**
230
+ * Read the initiating Agent and fail when no initiator boundary is active.
231
+ * Use this for private helpers contractually below a driver, or for a
232
+ * deployment-owned outbound request whose contract forbids agentless calls.
233
+ * Generic or direct-call paths use optional lookup or explicit request fields.
234
+ * @returns the inherited Agent.
235
+ * @throws when no initiator is active or this service instance has been disposed.
236
+ */
237
+ requireInitiator(): Agent;
238
+ /**
239
+ * Run an operation with one exact Agent as its process-local initiator. The
240
+ * exact synchronous value or Promise returned by the operation is preserved.
241
+ * Custom drivers and test harnesses wrap their complete returned foreground
242
+ * lifetime.
243
+ * A queue or wire receiver may establish this boundary only after validating
244
+ * explicit identity and resolving the exact live Agent; this method does neither.
245
+ * Detached work remains owned by the subsystem that starts it.
246
+ * @param agent - initiating Agent to inherit; presence is neither liveness proof nor authorization.
247
+ * @param operation - synchronous or asynchronous operation to invoke.
248
+ * @returns the exact value returned by `operation`.
249
+ * @throws when the initiator scope is closing/disposed, or when `operation` throws.
250
+ */
251
+ withInitiator<T>(agent: Agent, operation: () => T): T;
252
+ /**
253
+ * Run an operation inside a boundary that hides any inherited initiating
254
+ * Agent. The exact synchronous value or Promise is preserved.
255
+ * Use this while creating lazy shared timers, queue pumps, pool maintenance,
256
+ * watchers, or exporters so they do not inherit the first Agent that happens
257
+ * to initialize them. It clears only initiator attribution, not explicit
258
+ * fields, and does not own or drain detached resources.
259
+ * @param operation - synchronous or asynchronous operation to invoke without an initiator.
260
+ * @returns the exact value returned by `operation`.
261
+ * @throws when the initiator scope is closing/disposed, or when `operation` throws.
262
+ */
263
+ withoutInitiator<T>(operation: () => T): T;
264
+ /**
265
+ * Register the agent-creation factory (the loop calls this on construction,
266
+ * effect-scoped). A traced Cordis service is canonicalized to its concrete
267
+ * target; each create/resume call is then traced through that caller's
268
+ * context so ownership follows the caller without stacking proxy layers.
269
+ * Throws if a factory is already registered. Returns the disposer; on
270
+ * dispose the factory slot is cleared.
271
+ * @param factory - the loop-owned factory {@link create}/{@link resume} delegate to.
272
+ * @returns the disposer that clears the factory slot. The exact
273
+ * Cordis effect disposer (single-shot): composite (generator) effects may
274
+ * yield it directly — exact identity nests the teardown in order.
275
+ */
276
+ setFactory(factory: AgentFactory): () => void;
277
+ /** Return the active creation factory. */
278
+ private requireFactory;
279
+ /**
280
+ * Create and publish a new agent through the registered factory.
281
+ * Distinct from {@link register} (which records an already-constructed
282
+ * agent): this constructs the agent and its session. Rejects if no factory is
283
+ * registered or creation/setup fails. The resolved {@link AgentHandle} lets
284
+ * the owner tear down exactly this agent.
285
+ * @param options - shared identity, session seed/metadata, and agent options.
286
+ * @returns the handle after setup, rollback-covered publication, and loop start complete.
287
+ */
288
+ create(options: CreateAgentOptions): Promise<AgentHandle>;
289
+ /**
290
+ * Load a persisted session and resume an agent on it through the registered
291
+ * factory. Rejects if no factory is registered; the factory rejects if
292
+ * session persistence is not configured or persistence/setup fails.
293
+ * @param options - persisted identity, configuration, and optional setup.
294
+ * @returns the handle after setup, rollback-covered publication, and loop start complete.
295
+ */
296
+ resume(options: ResumeAgentOptions): Promise<AgentHandle>;
297
+ /**
298
+ * Register a live agent. Throws if an agent with the same id is already
299
+ * registered. Emits `agent/created` on registration and `agent/disposed`
300
+ * when the calling fiber is disposed — both with the agent's scope carrier
301
+ * (`scopeTarget(agent, agent)`): the subject is the agent in hand, so the
302
+ * emits are scope-filtered regardless of which context invoked `register`
303
+ * (calling through `agent.ctx` scopes EFFECTS; dispatch scoping always
304
+ * requires passing the carrier). Returns the disposer.
305
+ * @param agent - the already-constructed agent to record in the store.
306
+ * @returns the EXACT Cordis effect disposer (single-shot; a repeat call
307
+ * returns undefined without awaiting an in-flight teardown). Exact
308
+ * identity is load-bearing: a composite (generator) effect that owns a
309
+ * teardown ORDER — the agent factory's lifecycle chain — must yield THIS
310
+ * function so Cordis nests the unregistration at that yield position;
311
+ * yielding a wrapper would leave it disposing as a concurrent sibling on
312
+ * owner unload, unregistering the agent (and emitting `agent/disposed`)
313
+ * while its final turn is still draining.
314
+ */
315
+ register(agent: Agent): () => void;
316
+ /**
317
+ * Insert an already-constructed agent without announcing it. This is the
318
+ * advanced ordered-lifecycle primitive used by the async agent factory: it
319
+ * first completes setup while the agent is unpublished, then assigns the
320
+ * returned detach closure into its pre-installed composite teardown before
321
+ * calling {@link announce}. Ordinary callers use {@link register}.
322
+ * @param agent - the prepared, unpublished agent.
323
+ * @param owner - live agent whose scoped context created this agent, or
324
+ * undefined for a top-level runtime root. This is runtime ownership, not
325
+ * the resumed session's durable parent lineage.
326
+ * @returns an idempotent closure that removes this exact entry and emits
327
+ * `agent/disposed` with listener failures contained. When called from a
328
+ * synchronous `agent/created` listener, removal and disposal wait until
329
+ * that creation dispatch unwinds.
330
+ */
331
+ enter(agent: Agent, owner: Agent | undefined): () => void;
332
+ /** Remove one exact entered agent and emit its paired disposal when announced. */
333
+ private detachEntered;
334
+ /** Emit the paired disposal edge through the entry's stable carrier. */
335
+ private emitDisposed;
336
+ /**
337
+ * Announce an agent previously inserted with {@link enter}.
338
+ * @param agent - the live inserted agent to announce.
339
+ * @throws if `agent` is not the exact live registry entry for its id, or its
340
+ * creation announcement already began (including a reentrant call from a
341
+ * creation listener).
342
+ */
343
+ announce(agent: Agent): void;
344
+ /**
345
+ * Look up a live agent.
346
+ * @param id - the shared agent/session id to look up.
347
+ * @returns the agent, or undefined when no live agent has that id.
348
+ */
349
+ get(id: SessionId): Agent | undefined;
350
+ /**
351
+ * Test whether a live agent was created through one exact parent agent's
352
+ * scoped context. Runtime ownership is independent of durable session
353
+ * lineage and remains unambiguous when unrelated providers reuse an id.
354
+ * @param id - the candidate child agent's shared agent/session id.
355
+ * @param owner - the expected runtime creator agent.
356
+ * @returns true only while the exact child entry is live under that owner.
357
+ */
358
+ isOwnedBy(id: SessionId, owner: Agent): boolean;
359
+ /**
360
+ * All live agents, in registration order.
361
+ * @returns a fresh array; mutating it does not affect the registry.
362
+ */
363
+ list(): Agent[];
364
+ /**
365
+ * All live top-level agents in registration order. A top-level agent was
366
+ * created without an owning agent context; durable session lineage does not
367
+ * affect this runtime relation, so a resumed fork may still be a root.
368
+ * @returns a fresh array; mutating it does not affect the registry.
369
+ */
370
+ roots(): Agent[];
371
+ /** Reject new initiator boundaries while inherited continuations drain. */
372
+ private closeInitiators;
373
+ /** Wait for returned-Promise boundaries, then invalidate retained references. */
374
+ private disposeInitiators;
375
+ /** Establish one tracked initiator or clearing boundary. */
376
+ private runWithInitiator;
377
+ /** Whether one unloading fiber owns this service's lifecycle. */
378
+ private hasLifecycleAncestor;
379
+ private assertInitiatorsReadable;
380
+ /** Exclude the boundary chain that initiated this teardown from its own drain. */
381
+ private releaseReentrantInitiatorRuns;
382
+ private releaseInitiatorRun;
383
+ }
384
+ export default AgentRegistry;
385
+ //# sourceMappingURL=index.d.ts.map