@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,324 @@
1
+ /**
2
+ * Public agent types and live-runtime events. Durable transcript facts and
3
+ * turn/step boundaries remain `@stackstackstack/dsh-session` events.
4
+ *
5
+ * @module @stackstackstack/dsh-agent
6
+ */
7
+ import type { Context } from '@deepseek-ai/cordis';
8
+ import type { Scoped } from '@stackstackstack/dsh-scope';
9
+ import type { LlmCallConfig, LlmFailure, ResolvedRetryPolicy } from '@stackstackstack/dsh-llm';
10
+ import type { AgentCancelCause, Session, SessionId, UserMessage } from '@stackstackstack/dsh-session';
11
+ export type { AgentCancelCause } from '@stackstackstack/dsh-session';
12
+ import type { Inbox } from './inbox.ts';
13
+ import type { InboxTarget } from './types.ts';
14
+ declare module '@stackstackstack/dsh-system-prompt' {
15
+ interface AssembleContext {
16
+ /** Agent for this assembly; absent on diagnostics. When present, `scope` must identify the same agent. */
17
+ agent?: Agent;
18
+ }
19
+ }
20
+ /** Merge-extensible agent creation options. Persona belongs to system-prompt sections. */
21
+ export interface AgentOptions {
22
+ /** Provider route (must have a registered adapter at call time). */
23
+ provider?: string;
24
+ /** Model id interpreted by the selected provider adapter. */
25
+ model?: string;
26
+ /** Maximum output tokens for each conversation-model request. */
27
+ maxTokens?: number;
28
+ }
29
+ /** Options for {@link Agent.cancel}. */
30
+ export interface CancelOptions {
31
+ /**
32
+ * Preserve queued and steering inbox items instead of discarding them. The
33
+ * active turn is still aborted, but un-started and pending work survives for a
34
+ * later turn and no canceled inbox splice is logged.
35
+ */
36
+ keepInbox?: boolean | undefined;
37
+ }
38
+ /**
39
+ * An agent's lifecycle state, emitted on every transition as `agent/status`:
40
+ * `idle` means no driver is active; `running` begins when waking input starts
41
+ * cancellable pre-step processing and lasts while the driver drains,
42
+ * closes, or checkpoints turns. Disposal removes the agent from its registry;
43
+ * it is not a third observable status.
44
+ */
45
+ export type AgentStatus = 'idle' | 'running';
46
+ /** Whether and with which messages the loop enters a proposed step. */
47
+ export type PreStepDecision = {
48
+ kind: 'reject';
49
+ } | {
50
+ kind: 'enter';
51
+ messages: UserMessage[];
52
+ };
53
+ /** Action returned by a listener that owns model-request recovery. */
54
+ export type RequestErrorAction = {
55
+ kind: 'retry';
56
+ } | undefined;
57
+ /** Why a session lifecycle began; seeded creates are `startup`, while persisted loads are `resume`. */
58
+ export type SessionStartSource = 'startup' | 'resume' | 'clear' | 'compact';
59
+ /** Public live-agent handle. */
60
+ export interface Agent {
61
+ /** The single identity shared with {@link session}. */
62
+ readonly id: SessionId;
63
+ /** The provider route and model this agent's requests use. */
64
+ readonly options: AgentOptions;
65
+ /** The live session this agent drives; its log is the durable source of truth. */
66
+ readonly session: Session;
67
+ /** The agent-owned projection of durable pending work. */
68
+ readonly inbox: Inbox;
69
+ /** The current lifecycle state, mirrored on every `agent/status` transition. */
70
+ readonly status: AgentStatus;
71
+ /** Agent-scoped context; its contributions are agent-local, unwind on disposal, and reject registration afterward. */
72
+ readonly ctx: Context;
73
+ /**
74
+ * Clear queued and steering work — unless `keepInbox` — and abort the active
75
+ * turn or between-turn task. The first cause wins for that activity. With no
76
+ * active activity, cancellation is a no-op and does not arm later work.
77
+ * @param cause - the stable caller intent carried by the active operation signal.
78
+ * @param options - cancellation options; `keepInbox` preserves pending work.
79
+ */
80
+ cancel(cause: AgentCancelCause, options?: CancelOptions): void;
81
+ /**
82
+ * Resolve after the current whole-agent activity reaches quiescence. This
83
+ * follows replacement work started before the observed driver retires,
84
+ * but does not identify the settlement of any particular message.
85
+ * @returns fulfillment after no active driver or maintenance task remains.
86
+ */
87
+ whenIdle(): Promise<void>;
88
+ /**
89
+ * Run one non-turn maintenance task from the true idle phase. The task starts
90
+ * synchronously after claiming that phase; later waking input remains in the
91
+ * inbox until the task settles, while public status stays `idle`.
92
+ * `whenIdle()` follows both the task and any waking work released behind it.
93
+ * @param task - operation whose fulfillment or rejection is preserved, with a signal aborted by {@link cancel}.
94
+ * @throws synchronously when turn-driving or another maintenance task already owns the agent.
95
+ * @returns the task promise.
96
+ */
97
+ runMaintenance<T>(task: (signal: AbortSignal) => Promise<T>): Promise<T>;
98
+ /**
99
+ * Route identified input to an inbox boundary and optionally wake the driver.
100
+ * Waking input submitted after active cancellation is queued for the next
101
+ * turn and runs when the aborted activity converges to idle; a `disposed`
102
+ * cancel leaves it parked. A wake submitted while already idle always opens
103
+ * its turn boundary, even when its message is cleared before the driver
104
+ * claims ([cancel-convergence wake latch](../../../../.agents/notes/implemented/bug-fix/2026-08-07-cancel-convergence-wake-latch.md)).
105
+ * @param message - identified content and the source that supplied it.
106
+ * @param target - the preferred next-turn or next-step inbox boundary.
107
+ * @param wakeup - whether delivery may wake the driver.
108
+ */
109
+ send(message: UserMessage, target: InboxTarget, wakeup: boolean): void;
110
+ /**
111
+ * Queue an ordinary follow-up turn and wake the driver. The item becomes the
112
+ * sole ordinary message of its own turn.
113
+ * @param message - identified prompt content and the source that supplied it.
114
+ */
115
+ followup(message: UserMessage): void;
116
+ /**
117
+ * Submit steering for the nearest step. An idle driver starts a turn;
118
+ * a running driver consumes it at its next step boundary.
119
+ * A rejected step leaves steering parked in the inbox until the next
120
+ * wake; cancellation or disposal may discard pending steering.
121
+ * @param message - identified steering content and the source that supplied it.
122
+ */
123
+ steer(message: UserMessage): void;
124
+ /**
125
+ * Queue model-facing context for the next pre-step without waking the
126
+ * driver. A running driver claims it at the nearest later step boundary;
127
+ * idle drivers leave it pending until follow-up or steering
128
+ * wakes them. It may miss a request whose pre-step already claimed its
129
+ * batch. Cancellation or disposal may discard pending context.
130
+ * @param message - identified injected context and the source that supplied it.
131
+ */
132
+ inject(message: UserMessage): void;
133
+ }
134
+ declare module '@deepseek-ai/cordis' {
135
+ interface Events {
136
+ /**
137
+ * A fully configured agent and live session were published. Setup is
138
+ * composition-only; `agent/session-start` is the first startup-driving extension point.
139
+ * Synchronous listener failure vetoes publication, while returned-promise
140
+ * rejection is reported. Detach requested during dispatch waits until every
141
+ * creation listener has observed the stable entry.
142
+ * @param payload.agent - the newly registered agent with its live session and completed setup.
143
+ * Scope-filtered dispatch (`@stackstackstack/dsh-scope`): agent-scoped listeners receive only that agent.
144
+ * @mode emit
145
+ */
146
+ 'agent/created'(this: Scoped<Agent>, payload: {
147
+ agent: Agent;
148
+ }): void;
149
+ /**
150
+ * An agent left the registry; AgentLoop emits this after driver quiescence
151
+ * and scoped-registration unwind, but before session detachment. Custom
152
+ * registry users own their driver-ordering contract.
153
+ * @param payload.agent - the exact agent removed from the registry.
154
+ * Scope-filtered dispatch (`@stackstackstack/dsh-scope`): agent-scoped listeners receive only that agent.
155
+ * @mode emit
156
+ */
157
+ 'agent/disposed'(this: Scoped<Agent>, payload: {
158
+ agent: Agent;
159
+ }): void;
160
+ /**
161
+ * Agent status changed (`idle` ⇄ `running`). A waking delivery enters
162
+ * `running` synchronously after reserving cancellation; `idle` means no
163
+ * driver remains scheduled or active.
164
+ * @param payload.agent - the agent whose status flipped.
165
+ * @param payload.status - the status just entered (the transition's destination).
166
+ * Scope-filtered dispatch (`@stackstackstack/dsh-scope`): agent-scoped listeners receive only that agent.
167
+ * @mode emit
168
+ */
169
+ 'agent/status'(this: Scoped<Agent>, payload: {
170
+ agent: Agent;
171
+ status: AgentStatus;
172
+ }): void;
173
+ /**
174
+ * One message entered the live inbox.
175
+ * @param payload.agent - the agent whose inbox changed.
176
+ * @param payload.message - the inserted message.
177
+ * Scope-filtered dispatch (`@stackstackstack/dsh-scope`): agent-scoped listeners receive only that agent.
178
+ * @mode emit
179
+ */
180
+ 'agent/inbox/inserted'(this: Scoped<Agent>, payload: {
181
+ agent: Agent;
182
+ message: UserMessage;
183
+ }): void;
184
+ /**
185
+ * One message left the inbox inside its open turn. If the proposed step
186
+ * is rejected, the claimed message ends here: it is neither discarded nor
187
+ * re-emitted as a user/message, and the turn closes without a step.
188
+ * @param payload.agent - the agent whose inbox changed.
189
+ * @param payload.message - the claimed message.
190
+ * @param payload.turn - the owning turn.
191
+ * Scope-filtered dispatch (`@stackstackstack/dsh-scope`): agent-scoped listeners receive only that agent.
192
+ * @mode emit
193
+ */
194
+ 'agent/inbox/claimed'(this: Scoped<Agent>, payload: {
195
+ agent: Agent;
196
+ message: UserMessage;
197
+ turn: number;
198
+ }): void;
199
+ /**
200
+ * One message was discarded from the live inbox.
201
+ * @param payload.agent - the agent whose inbox changed.
202
+ * @param payload.message - the discarded message.
203
+ * Scope-filtered dispatch (`@stackstackstack/dsh-scope`): agent-scoped listeners receive only that agent.
204
+ * @mode emit
205
+ */
206
+ 'agent/inbox/discarded'(this: Scoped<Agent>, payload: {
207
+ agent: Agent;
208
+ message: UserMessage;
209
+ }): void;
210
+ /**
211
+ * The session lifecycle began, once before the first turn. Use
212
+ * `agent.inject()` to seed model-facing context. This is a notification, not
213
+ * a veto; disposal requested by a lifecycle owner is rechecked before the
214
+ * driver starts.
215
+ * @param payload.agent - the agent whose session lifecycle began.
216
+ * @param payload.source - why the session started (fresh startup, resume, …).
217
+ * Scope-filtered dispatch (`@stackstackstack/dsh-scope`): agent-scoped listeners receive only that agent.
218
+ * @mode emit
219
+ */
220
+ 'agent/session-start'(this: Scoped<Agent>, payload: {
221
+ agent: Agent;
222
+ source: SessionStartSource;
223
+ }): void;
224
+ /**
225
+ * Reject a proposed step or replace the messages that enter it. Calling
226
+ * `next()` preserves the current messages.
227
+ * @param payload.agent - the agent proposing the step.
228
+ * @param payload.messages - messages removed from the inbox for this step.
229
+ * @param payload.turn - the turn that will own the step.
230
+ * @param payload.step - the step proposed by the loop.
231
+ * @param payload.signal - the current turn's cancellation signal.
232
+ * Scope-filtered dispatch (`@stackstackstack/dsh-scope`): agent-scoped listeners receive only that agent.
233
+ * @mode waterfall
234
+ */
235
+ 'agent/pre-step'(this: Scoped<Agent>, payload: {
236
+ agent: Agent;
237
+ messages: UserMessage[];
238
+ turn: number;
239
+ step: number;
240
+ signal: AbortSignal;
241
+ }, next: () => Promise<PreStepDecision>): Promise<PreStepDecision>;
242
+ /**
243
+ * Replace the frozen call configuration. `await next()` yields the config
244
+ * the machine would use (agent options on the first request, the logged
245
+ * header afterwards); return a replacement to switch. Model-visible
246
+ * content must use logged channels; this waterfall cannot mutate messages.
247
+ * @param payload.agent - the agent making the model call.
248
+ * @param payload.turn - the open turn number.
249
+ * @param payload.step - the step whose request this is.
250
+ * @param payload.signal - the current turn's explicit abort signal.
251
+ * Scope-filtered dispatch (`@stackstackstack/dsh-scope`): agent-scoped listeners receive only that agent.
252
+ * @mode waterfall
253
+ */
254
+ 'agent/request'(this: Scoped<Agent>, payload: {
255
+ agent: Agent;
256
+ turn: number;
257
+ step: number;
258
+ signal: AbortSignal;
259
+ }, next: () => Promise<LlmCallConfig>): Promise<LlmCallConfig>;
260
+ /**
261
+ * Handle one failed model-request attempt before the loop retries or closes
262
+ * its step. A listener returns `{ kind: 'retry' }` without calling `next()`
263
+ * when it owns recovery, or calls `next()` to delegate. The default
264
+ * `undefined` leaves the failure terminal.
265
+ * @param payload.agent - the agent whose request failed.
266
+ * @param payload.turn - the turn containing the failed request.
267
+ * @param payload.step - the step containing the failed request attempt.
268
+ * @param payload.provider - the provider selected for the failed request.
269
+ * @param payload.failure - serializable facts normalized at the final adapter boundary.
270
+ * @param payload.retryPolicy - the policy of the adapter registration that served the failed request.
271
+ * @param payload.signal - the turn abort signal.
272
+ * Scope-filtered dispatch (`@stackstackstack/dsh-scope`): agent-scoped listeners receive only that agent.
273
+ * @mode waterfall
274
+ */
275
+ 'agent/request-error'(this: Scoped<Agent>, payload: {
276
+ agent: Agent;
277
+ turn: number;
278
+ step: number;
279
+ provider: string;
280
+ failure: LlmFailure;
281
+ retryPolicy: ResolvedRetryPolicy | undefined;
282
+ signal: AbortSignal;
283
+ }, next: () => Promise<RequestErrorAction>): Promise<RequestErrorAction>;
284
+ /**
285
+ * The turn is about to close: the model owes no response (no live tool
286
+ * calls, no fresh steering). Awaited before the boundary commits — a
287
+ * listener that objects steers (`agent.steer(...)`) and the machine
288
+ * re-reads its inbox: fresh steering runs another step, none closes the
289
+ * turn. Data decides, so listener order cannot change the outcome. The
290
+ * inverse control (stop a tool loop early) is data too: a tool result
291
+ * carrying `concludesTurn` ends the turn at its step. The conclusion
292
+ * never short-circuits already-submitted next-step work: same-step
293
+ * `additionalContexts` or racing steering still runs, and the turn
294
+ * closes only when that inbox drains.
295
+ * @param payload.agent - the agent whose turn is at its stop boundary.
296
+ * @param payload.turn - the turn about to close.
297
+ * @param payload.signal - the current turn's explicit abort signal.
298
+ * Scope-filtered dispatch (`@stackstackstack/dsh-scope`): agent-scoped listeners receive only that agent.
299
+ * @mode serial
300
+ */
301
+ 'agent/turn-stopping'(this: Scoped<Agent>, payload: {
302
+ agent: Agent;
303
+ turn: number;
304
+ signal: AbortSignal;
305
+ }): Promise<void> | void;
306
+ /**
307
+ * A step or turn errored. The machine reports a failure here even when
308
+ * the error has no in-turn position for a durable record.
309
+ * @param payload.agent - the agent whose turn errored.
310
+ * @param payload.turn - the turn in which the failure surfaced.
311
+ * @param payload.step - the step at which the failure surfaced.
312
+ * @param payload.error - the failure, verbatim.
313
+ * Scope-filtered dispatch (`@stackstackstack/dsh-scope`): agent-scoped listeners receive only that agent.
314
+ * @mode emit
315
+ */
316
+ 'agent/error'(this: Scoped<Agent>, payload: {
317
+ agent: Agent;
318
+ turn: number;
319
+ step: number;
320
+ error: unknown;
321
+ }): void;
322
+ }
323
+ }
324
+ //# sourceMappingURL=runtime-types.d.ts.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Public agent types and live-runtime events. Durable transcript facts and
3
+ * turn/step boundaries remain `@stackstackstack/dsh-session` events.
4
+ *
5
+ * @module @stackstackstack/dsh-agent
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=runtime-types.js.map
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Durable agent session-event vocabulary shared with type-only consumers.
3
+ *
4
+ * @module @stackstackstack/dsh-agent/types
5
+ */
6
+ import type { UserMessage } from '@stackstackstack/dsh-llm/types';
7
+ /** One of the two ordered pending-message lists owned by an agent. */
8
+ export type InboxTarget = 'next-turn' | 'next-step';
9
+ declare module '@stackstackstack/dsh-session/types' {
10
+ interface SessionEventMap {
11
+ /**
12
+ * One normalized mutation of an agent's durable pending-message lists.
13
+ * Live dispatch precedes projection mutation, so synchronous observers may
14
+ * read the pre-splice inbox to recover the removed messages.
15
+ */
16
+ 'agent/inbox/spliced': {
17
+ target: InboxTarget;
18
+ start: number;
19
+ removedCount?: number;
20
+ inserted: UserMessage[];
21
+ outcome?: 'canceled';
22
+ };
23
+ }
24
+ }
25
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Durable agent session-event vocabulary shared with type-only consumers.
3
+ *
4
+ * @module @stackstackstack/dsh-agent/types
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=types.js.map
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@stackstackstack/dsh-agent",
3
+ "description": "Agent interface, registry, initiator scope, and event vocabulary for the DeepSeek Harness",
4
+ "version": "0.1.5",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
11
+ "directory": "packages/core/agent"
12
+ },
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "types": "lib/types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./lib/types/index.d.ts",
19
+ "default": "./lib/index.js"
20
+ },
21
+ "./invariant": {
22
+ "types": "./lib/types/invariant.d.ts",
23
+ "default": "./lib/invariant.js"
24
+ },
25
+ "./types": {
26
+ "types": "./lib/types/types.d.ts",
27
+ "default": "./lib/types/types.js"
28
+ },
29
+ "./src/*": "./src/*",
30
+ "./package.json": "./package.json"
31
+ },
32
+ "files": [
33
+ "lib/index.js",
34
+ "lib/invariant.js",
35
+ "lib/types/**/*.js",
36
+ "lib/types/**/*.d.ts"
37
+ ],
38
+ "license": "MIT",
39
+ "peerDependencies": {
40
+ "@stackstackstack/dsh-invariants": "^0.1.5",
41
+ "@stackstackstack/dsh-scope": "^0.1.5",
42
+ "@stackstackstack/dsh-system-prompt": "^0.1.5",
43
+ "@stackstackstack/dsh-typert-protocol": "^0.1.5",
44
+ "@stackstackstack/dsh-session": "^0.1.5",
45
+ "@deepseek-ai/cordis": "^4.0.1",
46
+ "@stackstackstack/dsh-llm": "^0.1.5"
47
+ },
48
+ "devDependencies": {
49
+ "@stackstackstack/dsh-llm": "^0.1.5",
50
+ "@stackstackstack/dsh-scope": "^0.1.5",
51
+ "@stackstackstack/dsh-invariants": "^0.1.5",
52
+ "@stackstackstack/dsh-session": "^0.1.5",
53
+ "@stackstackstack/dsh-system-prompt": "^0.1.5",
54
+ "@stackstackstack/dsh-typert-protocol": "^0.1.5",
55
+ "@stackstackstack/dsh-typert-registry": "^0.1.5",
56
+ "@deepseek-ai/cordis": "^4.0.1"
57
+ }
58
+ }