@fishwithcat/dsh-agent 0.0.0-stage → 0.2.2-alpha.2

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