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

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