@sema-agent/core 7.6.0 → 7.6.1

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.
Files changed (75) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/agents/agent-transcript-tool.d.ts +2 -2
  3. package/dist/agents/cascade.d.ts +2 -3
  4. package/dist/agents/repair-loop.d.ts +2 -2
  5. package/dist/agents/retain-ledger.d.ts +2 -3
  6. package/dist/agents/send-message-tool.d.ts +2 -2
  7. package/dist/agents/session-util.d.ts +2 -2
  8. package/dist/agents/subagent.d.ts +3 -4
  9. package/dist/agents/teacher.d.ts +2 -2
  10. package/dist/agents/team.d.ts +2 -2
  11. package/dist/agents/verify.d.ts +5 -6
  12. package/dist/core/agent-definition.d.ts +172 -0
  13. package/dist/core/agent-definition.js +1 -0
  14. package/dist/core/delegation-frames.d.ts +298 -0
  15. package/dist/core/delegation-frames.js +21 -0
  16. package/dist/core/engine-notice.d.ts +555 -0
  17. package/dist/core/engine-notice.js +55 -0
  18. package/dist/core/gate-fold.d.ts +12 -0
  19. package/dist/core/gate-fold.js +158 -0
  20. package/dist/core/gate-lanes.d.ts +93 -0
  21. package/dist/core/gate-lanes.js +626 -0
  22. package/dist/core/hands-band.d.ts +134 -0
  23. package/dist/core/hands-band.js +1 -0
  24. package/dist/core/hooks.d.ts +20 -101
  25. package/dist/core/hooks.js +53 -854
  26. package/dist/core/mcp-failure.d.ts +43 -5
  27. package/dist/core/mcp-failure.js +31 -14
  28. package/dist/core/mcp-server-spec.d.ts +217 -0
  29. package/dist/core/mcp-server-spec.js +1 -0
  30. package/dist/core/model-seat.d.ts +99 -0
  31. package/dist/core/model-seat.js +1 -0
  32. package/dist/core/reminder-mint.d.ts +10 -0
  33. package/dist/core/reminder-mint.js +3 -0
  34. package/dist/core/runner/contracts.d.ts +382 -6
  35. package/dist/core/runner/gate-exit.d.ts +177 -9
  36. package/dist/core/runner/gate-exit.js +70 -1
  37. package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -7
  38. package/dist/core/runner/prepare-delegation-surface.d.ts +2 -7
  39. package/dist/core/runner/prepare-task.d.ts +2 -2
  40. package/dist/core/runner/runtask.d.ts +4 -71
  41. package/dist/core/runner/runtask.js +14 -5
  42. package/dist/core/runner-deps.d.ts +1416 -0
  43. package/dist/core/runner-deps.js +1 -0
  44. package/dist/core/runtime-caps.d.ts +164 -0
  45. package/dist/core/runtime-caps.js +1 -0
  46. package/dist/core/task-event.d.ts +910 -0
  47. package/dist/core/task-event.js +1 -0
  48. package/dist/core/task-limits.d.ts +110 -0
  49. package/dist/core/task-limits.js +1 -0
  50. package/dist/core/task-result.d.ts +809 -0
  51. package/dist/core/task-result.js +1 -0
  52. package/dist/core/task-spec.d.ts +1370 -0
  53. package/dist/core/task-spec.js +1 -0
  54. package/dist/core/task-stream.d.ts +382 -0
  55. package/dist/core/task-stream.js +1 -0
  56. package/dist/core/tool-spec.d.ts +1174 -0
  57. package/dist/core/tool-spec.js +1 -0
  58. package/dist/core/types.d.ts +26 -7691
  59. package/dist/core/types.js +2 -76
  60. package/dist/core/warm-resume.d.ts +2 -2
  61. package/dist/index.d.ts +2 -1
  62. package/dist/index.js +1 -1
  63. package/dist/orchestration/goal.d.ts +2 -2
  64. package/dist/orchestration/run-spec.d.ts +2 -2
  65. package/dist/orchestration/run-workflow-tool.d.ts +3 -3
  66. package/dist/orchestration/workflow.d.ts +4 -4
  67. package/dist/scenarios/scenario-registry.d.ts +3 -3
  68. package/dist/scenarios/teacher-quickstart.d.ts +2 -2
  69. package/dist/server/http.d.ts +2 -2
  70. package/dist/stores/file/fs-atomic.d.ts +88 -12
  71. package/dist/stores/file/fs-atomic.js +184 -55
  72. package/dist/stores/file/index.d.ts +1 -0
  73. package/dist/stores/file/index.js +1 -0
  74. package/package.json +1 -1
  75. package/test/export-surface.snapshot.json +9 -1
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,382 @@
1
+ /**
2
+ * The run's CONTROL surface: `TaskStream` — the async-iterable handle `runTaskStream` returns, with
3
+ * the steer / halt / approve / compact methods a caller drives a live run through. Separate from the
4
+ * frames it yields (`task-event.ts`): a change to the handle's methods is not a change to the wire.
5
+ * Layer 0 vocabulary; `types.ts` re-exports the name, so no consumer's import changes.
6
+ */
7
+ import type { ActorAssertion } from "../internal/llm.js";
8
+ import type { CompactOutcome, TaskEvent } from "./task-event.js";
9
+ import type { TaskResult } from "./task-result.js";
10
+ /** A running task you can iterate for live events and await for the final result. */
11
+ export interface TaskStream extends AsyncIterable<TaskEvent> {
12
+ result(): Promise<TaskResult>;
13
+ /**
14
+ * design/100 §E12 — resolves to the post-completion prompt **suggestions** (the shell's follow-up prompts),
15
+ * or `[]` when `suggestNextPrompts` was off / the task did not complete / the pass produced nothing / it
16
+ * failed. The pass is **fire-and-forget**: `result()` never waits for it, so a
17
+ * shell does `await stream.result()` THEN `await stream.suggestions()`. **Never rejects.** The strings are
18
+ * UNTRUSTED model output for display ONLY — never re-feed them to a model.
19
+ */
20
+ suggestions(): Promise<string[]>;
21
+ /**
22
+ * Inject a mid-task **steering** message (design/47) that the running task sees at the start of its
23
+ * next turn (delivered via the harness steering queue). Resolves once queued; **throws** (code
24
+ * `steering.not_running`) once the task has finished (teardown included) — never silently dropped. A
25
+ * steer issued BEFORE the run has ISSUED ITS FIRST PROMPT is not refused: it is HELD for the birth
26
+ * window and enters the queue as soon as the loop goes live, so the model sees it at the next turn
27
+ * boundary like any other steer — it is refused if the loop ends first, or if that BOUNDED wait runs
28
+ * out while the run still has not started (ruled 2026-08-05; a retry under the same `inputId` is then
29
+ * clean — a refusal reserves nothing). By default the text enters as a normal **user** message (in an orchestration the caller IS
30
+ * the task's user). Pass `trusted: true` ONLY for operator/system-level guidance: it is wrapped as a
31
+ * `<system-reminder>` (elevated authority) — do NOT use it for caller/third-party content that could
32
+ * carry a prompt injection. The seam gives the channel; the caller owns the judgement (design/43 §6).
33
+ *
34
+ * design/171 §5.2 — `actor` attributes WHO steered (a shared session's second voice): the text gets
35
+ * the speaker envelope from the single projection point and the queued message carries the
36
+ * metadata seat. Attribution only, never authority; absent = anonymous (bytes unchanged).
37
+ *
38
+ * design/171 §6.3 (additive) — `inputId` is the caller's correlation/idempotency key, the SAME key
39
+ * space as the parked queue's `PendingSteerEntry.inputId` and the `human_input` event's `inputId`
40
+ * (an ingress that already minted a message id passes it here, and the emitted frame carries it
41
+ * VERBATIM instead of a fresh uuidv7). It exists because the two legs of one steering ingress must be
42
+ * equally replay-safe: the parked leg has taken this key since the queue landed, so a retried request
43
+ * that arrives while the task is LIVE was the only one that injected twice.
44
+ * - **Absent ⇒ nothing changes**: a uuidv7 is minted for the event, nothing is recorded for the call,
45
+ * and the delivered bytes are what they always were. Every pre-existing caller is on this arm.
46
+ * - **Replay ⇒ idempotent no-op**: re-steering an id this stream already ACCEPTED, with an identical
47
+ * payload (same text, same `trusted`, same `actor`), injects nothing and emits no second
48
+ * `human_input` frame. What is remembered is exactly what QUEUED: a delivery that was refused
49
+ * (not running) can be retried under its own id, and a call whose DELIVERED payload is
50
+ * whitespace-only — which the harness discards without minting a frame; note a `trusted` steer is
51
+ * wrapped first, so it queues even for blank text — reserves nothing, leaving that id usable.
52
+ * - **Same id, DIFFERENT instruction ⇒ typed throw** `steering.duplicate_input_id`: a key is not
53
+ * evidence of a replay, and two callers colliding on one id must not silently lose the second
54
+ * (the parked leg's `appendPendingSteer` refuses it identically). Re-issue under a fresh id.
55
+ * - **Bad value ⇒ typed throw** `steering.invalid_content`, never a silent fallback to "no id": the
56
+ * value domain is the parked leg's (a non-empty string of at most `MAX_STEER_INPUT_ID_CHARS`
57
+ * characters, and never the reserved `LEGACY_PENDING_STEER_INPUT_ID`), so one key is accepted or
58
+ * refused the same way on both legs. Validated before the liveness check, like the parked leg's.
59
+ * - **Liveness outranks the key**: once the run's loop-liveness latch has flipped (the same signal
60
+ * the injection path stops polling on) a replay is refused `steering.not_running` like any other
61
+ * steer, never answered "already accepted" — the parked leg's row CAS answers `false` for a
62
+ * resolved checkpoint on a replayed id for the same reason. The one asymmetry, stated rather than
63
+ * papered over: in the sub-window where the harness has gone idle but the latch has not yet
64
+ * flipped, a FRESH steer polls (and is refused when the latch flips) while a replay answers
65
+ * immediately with the SAME outcome its original call reported — a key whose answer depended on
66
+ * microsecond timing would defeat its own purpose. Note what that outcome has always meant on this
67
+ * verb: ACCEPTED INTO THE QUEUE, not consumed by the model. A steer accepted in the last moments of
68
+ * a run can be stranded by the run ending before the next boundary drains it (true of every steer,
69
+ * keyed or not); a caller that needs delivery evidence reads the run's own events, not this receipt.
70
+ * - **The receipt does NOT distinguish the two**: a fresh accept and a replay both resolve `void`,
71
+ * exactly as `setPendingSteer` answers `true` for both. The observable difference is on the event
72
+ * stream (a fresh accept emits the `human_input` frame; a replay emits none), which is also where
73
+ * the parked leg's difference shows (a replay adds no queue entry, so the resume drains one frame).
74
+ * - **Honest window** (weaker than the parked leg's, deliberately stated): the live dedup domain is
75
+ * THIS stream object — one run leg, in this process. It is not persisted, so it does not span a
76
+ * restart, a replica, or a second `runTaskStream`/`resumeStream` call on the same session; and it
77
+ * holds only what the LIVE verb accepted, so an id already delivered by the parked leg (drained
78
+ * into this run's resume prompt) is NOT in it and WOULD inject again. A deployment that needs
79
+ * cross-leg or cross-process idempotency owns that half (its own key ledger), the same division
80
+ * of labor `notify`'s park-window dedup states.
81
+ *
82
+ * design/373 (additive) — `priority` is the steer face's injection tier, default `"next"`
83
+ * (= the pre-373 behavior, byte-identical):
84
+ * - `"next"` — the running turn's next boundary (unchanged);
85
+ * - `"later"` — never folded into work in progress: delivered at the run's natural
86
+ * would-otherwise-stop seat (its own closing turn); a run that ends before that seat re-parks
87
+ * the input via the existing durable/undrained lanes (receipt semantics unchanged — "queued"
88
+ * still means accepted-at-this-moment, never "consumed"); a `"later"` steer rides the followUp
89
+ * lane, so its stranded account is `task.user_followup_undrained` (the accounts are LANE-keyed
90
+ * — same two-key split the settled frame uses);
91
+ * - `"now"` — `"next"`'s delivery guarantee PLUS the boundary is manufactured early: the frame
92
+ * takes the queue's class head and, when a turn is in flight, the engine CUTS that turn at a
93
+ * reconciled boundary (finished tool calls keep their real results; never-started ones settle
94
+ * as paired interrupted results; the CC-verbatim interruption marker lands) and the run
95
+ * CONTINUES with this input — never the run-level `interrupt()`. Best-effort accelerator:
96
+ * with nothing in flight (idle tail, between turns) delivery is exactly `"next"`'s. Every
97
+ * real cut is announced (`task.turn_interrupted` notice) and counts toward `maxTurns` —
98
+ * deliberately (the hard cap IS the interrupt storm's bound), which means a `now` issued at
99
+ * the run's FINAL allowed turn spends the remaining budget on the cut and the run then ends
100
+ * at the limit before the steer's own turn: the input takes the terminal lanes like any
101
+ * accepted-but-undelivered steer (durable park when a resource-suspend seat is eligible —
102
+ * redeemed on resume; otherwise the loud `task.user_steer_undrained` account). Same for the
103
+ * other terminal gates (budget/walltime) firing at the cut's own `turn_end`. The receipt
104
+ * never promised consumption; no budget is reserved for the interruptor.
105
+ * ⚠️ POWER FACE (§5-4b): passing `priority` through to a third-party caller GRANTS it the power
106
+ * to cut this run's in-flight turns (cancelling that turn's in-flight tool work). A deployment
107
+ * relaying steer must gate this option at its own face — relay IS authorization.
108
+ * An unknown value throws typed `steering.invalid_content` (bad-value loudness). The replay
109
+ * identity (`inputId` idempotency) includes the NORMALIZED tier: the same text replayed under
110
+ * the same id at a different tier refuses `steering.duplicate_input_id` — an "idempotent
111
+ * success" that silently skipped the interrupt would be a disposition lie.
112
+ *
113
+ * design/373 §4.3 (D2, ruling of 2026-08-24) — a deployment `userPromptSubmit` hook SCREENS this verb:
114
+ * the steer face is a SERVICE entrance (third-party callers reach a running run through it), so
115
+ * the deployment's prompt filter sits at the entrance, `ctx.source:"steer"` + `ctx.inputId` +
116
+ * `ctx.actor` discriminated. Chain position: domain validation → liveness → `inputId` replay
117
+ * short-circuit → screen → accept/enqueue → (now) interrupt — an idempotent replay of an ACCEPTED
118
+ * id answers success WITHOUT re-running the hook (a re-run could answer differently and
119
+ * retro-falsify the standing receipt). Hook `block` ⇒ **typed throw `steering.blocked_by_hook`**
120
+ * (message carries the hook's own bounded reason); a hook timeout / cancellation / crash refuses
121
+ * with the SAME code, fail-closed (a screen that did not answer has not cleared the input —
122
+ * message discriminates the cause; the crash also reaches `onError` phase:"hook"). A blocked call
123
+ * was never accepted: no `human_input` frame, no undrained account, and the `inputId` stays
124
+ * UNBOOKED (retry freely, changed content included). `additionalContext` ⇒ prepended to the
125
+ * delivered frame as the engine's own reminder (never inside the untrusted mid-turn frame). The
126
+ * replay identity is over the CALLER's bytes — hook output never enters it. Engine-authored
127
+ * frames (task notifications, diagnostics) and external `notify()` text are OUT of the screen's
128
+ * domain (notifications are sanitized DATA, not prompts — the notify contract's ruling).
129
+ */
130
+ steer(text: string, options?: {
131
+ trusted?: boolean;
132
+ actor?: ActorAssertion;
133
+ inputId?: string;
134
+ priority?: import("./task-notification.js").SystemInjectionPriority;
135
+ }): Promise<void>;
136
+ /**
137
+ * design/144 §2 — inject an EXTERNAL structured event into this run's task-notification lane, as a
138
+ * `<task-notification type="external">` frame the model sees like any background completion. Core MINTS
139
+ * the payload (`task_type` is cast to `"external"` — a caller can never impersonate an internal lane)
140
+ * and every field is sanitized as UNTRUSTED data on the model face. Delivery rides the EXISTING
141
+ * notification machinery, so the semantics are identical to internal producers:
142
+ * - design/373 — the injection LADDER is live on this lane (BREAKING vs the flat era, named):
143
+ * `"next"` = the running turn's next boundary (mid-work included — the previous behavior of
144
+ * every frame, now this tier's); `"later"` = never folded into work in progress — delivered at
145
+ * the run's natural would-otherwise-stop seat (its own closing turn), and a run that ends
146
+ * before that seat parks the frame per session for the NEXT run's turn-open (delivery then
147
+ * depends on a next run happening — a recorded structural honesty note, this engine owns no
148
+ * idle process); `"now"` is REFUSED typed (`notify.invalid_priority`) — turn-interrupt
149
+ * authority belongs to the caller-provenance steer face, never to the notification lane
150
+ * (callers wanting earliest-boundary delivery say `"next"`). ⚠️ The parameterless DEFAULT is
151
+ * `"later"` (the CC pending-notification default): an omitted priority moved from
152
+ * next-boundary to the closing seat with this design — callers that need boundary delivery
153
+ * must say `"next"` explicitly. NOTE for the flat era's callers: `"later"` was silently inert
154
+ * then (delivered at the boundary with no runtime signal saying otherwise) — the version is
155
+ * the ONLY discriminator of which behavior a build has. Delivery order within a tier is
156
+ * ARRIVAL order; consecutive buffered notifications drain as one boundary batch. `priority`
157
+ * rides the park path (pend records carry it; the turn-open batch is priority-major) and the
158
+ * uplink path;
159
+ * - duplicate events dedup on `task_id:status[:seq]` — pass a fresh `seq` per repeat event. The
160
+ * external lane keys in its OWN dedup domain (design/144 X1): an external event can never collide
161
+ * with (or pre-occupy) an internal lane's key, and vice versa. The key deliberately excludes
162
+ * `priority` (tier = delivery metadata, not event identity): a repeat under an in-flight key
163
+ * folds WHATEVER tier it names — the standing entry keeps the FIRST accept's tier — so
164
+ * re-tiering requires a fresh `seq` (a new occurrence), never a same-key resend;
165
+ * - a notify landing AFTER the run ended PARKS per session and the session's NEXT run delivers it
166
+ * (unlike {@link steer}, a finished task does not reject — the park lane is the contract). The park
167
+ * leg dedups on the same key (design/144 X4) with an HONEST WINDOW LIMIT: the dedup set spans ONE
168
+ * parked batch — it does not reach back across the live-queue/park boundary, and it resets when the
169
+ * next run drains the batch (a repeat sent after that drain is deliverable again);
170
+ * Throws typed `notify.invalid_payload` on a malformed input, `notify.invalid_priority` on
171
+ * `priority: "now"` (see above — a KNOWN value this lane refuses, distinct from an unknown one),
172
+ * and `notify.not_running` when the run
173
+ * never built its notification lane (prepare failed). Everything upstream of the event reaching this
174
+ * process — transport, webhook/daemon wiring, inbound authn, durable parking for idle sessions — is
175
+ * the DEPLOYMENT half (design/43: zero daemon semantics in core).
176
+ */
177
+ notify(payload: import("./task-notification.js").ExternalNotificationInput, opts?: {
178
+ priority?: import("./task-notification.js").SystemInjectionPriority;
179
+ }): Promise<void>;
180
+ /**
181
+ * Request a manual context **compaction** (design/99 MF-18 — the shell `/compact` button). Forces a
182
+ * compaction at the NEXT safe turn boundary regardless of the auto-threshold (the only safe point — never
183
+ * mid-turn, which would rewrite the session under an in-flight request); a `compacted` event with
184
+ * `trigger:"manual"` is emitted when it actually compacts. Resolves once the request has been PROCESSED at
185
+ * a boundary, or when the task ends first (whichever comes first — it never hangs). **Throws**
186
+ * (`steering.not_running`) if the task hasn't started or has already finished. Idempotent-ish: concurrent
187
+ * calls coalesce onto the next boundary (all waiters get the same outcome).
188
+ *
189
+ * ⚠️ BREAKING (MF-18: "processed" without "compacted"): resolves with a {@link CompactOutcome}
190
+ * instead of `void` — "processed" alone told a consumer nothing when the attempt failed or was mooted
191
+ * (the 202-then-silence fingerprint). Every outcome also has a stream/trace counterpart:
192
+ * `"compacted"` → the `compacted` event; `"failed"` → `compaction.failed` trace (+ `onError(phase:
193
+ * "compaction")` when an attempt was burned); `"blocked"` → `compaction.blocked` trace; `"noop"` →
194
+ * `compaction.noop` trace; `"disabled"` → `compaction.disabled` trace; `"mooted"` →
195
+ * `compaction.mooted` trace — or the task simply settled before any boundary processed the request
196
+ * (the run-end backstop resolves "mooted" without a frame; the task's terminal events are the signal).
197
+ *
198
+ * design/145 §4 (additive): `opts.instructions` — the CC `/compact <instructions>` parity face.
199
+ * Injected into the summarization prompt in the CC-verbatim shape (`\n\nAdditional Instructions:`),
200
+ * REPLACING the spec-level `compaction.instructions` / default instructions for THIS pass (a
201
+ * one-shot directive should out-rank standing configuration — sema judgment inside the superset;
202
+ * CC has no spec level). A `preCompact` hook's `additionalInstructions` still APPENDS (CC 207
203
+ * :348007 merge shape). Sanitized like every instructions channel (break-out tags defused, fence
204
+ * sentinels capped, ≤2048 code points). Concurrent `compact()` calls coalesce onto one boundary:
205
+ * the LAST caller's instructions win that pass (all waiters share its outcome — same coalescing
206
+ * contract as before).
207
+ *
208
+ * `opts.signal` (additive) — CANCEL this compact request. Aborting resolves the caller's promise
209
+ * `"mooted"` (honest: the request was withdrawn by the caller, no boundary served it):
210
+ * - **before the pass starts** (still parked for a boundary): the request is simply un-parked — no
211
+ * summary call is made; when this was the only pending caller the whole manual request (and its
212
+ * pending instructions override) is withdrawn.
213
+ * - **while the summary call is in flight**: the signal is composed into that pass's summary-call
214
+ * abort chain, so the in-flight model call is aborted. The task itself is unaffected (compaction
215
+ * is best-effort; the run continues on the un-compacted session). A caller-cancel abort is an
216
+ * EXTERNAL cause, not summarizer evidence — it is never counted toward the §17.4 consecutive-
217
+ * failure breaker and never fired through `onError` (same discrimination posture as the walltime
218
+ * soft-deadline abort; the trace counterpart is `compaction.mooted` with `reason:"cancelled"`).
219
+ * Coalescing caveat: coalesced callers share ONE pass, so one caller's cancel aborts the shared
220
+ * pass — every waiter of that pass then resolves `"mooted"`.
221
+ * An already-aborted signal resolves `"mooted"` immediately without arming a request.
222
+ */
223
+ compact(opts?: {
224
+ instructions?: string;
225
+ signal?: AbortSignal;
226
+ }): Promise<CompactOutcome>;
227
+ /**
228
+ * design/116 detach(CC mid-flight ctrl+b)— move a RUNNING tool call to the background.
229
+ * Today only a detach-capable Bash honors it (the env adopts the child as a background shell; the tool
230
+ * settles early with "moved to background; task_id=b*", and the G2b completion notification takes over).
231
+ * Fire-and-forget and race-safe: a request landing before the tool reads its signal still detaches; one
232
+ * landing after the tool finished is a no-op. Unknown toolCallIds are a no-op (nothing to detach).
233
+ */
234
+ detach(toolCallId: string): void;
235
+ /**
236
+ * design/383 §2.1 — the MID-SESSION memory-capture opt-out verb ("聊着聊着发现不该记" — the
237
+ * design's primary scenario): from this call on, the running session commits NOTHING to the
238
+ * long-term memory store, its already-committed contributions leave the consolidation candidate
239
+ * set (the retroactive A2 arm), and the session window's residue on the writable memory root is
240
+ * boundary-swept into control-plane quarantine (per-path named; a sweep failure REFUSES the call
241
+ * loudly — the one-way record stands either way, so the failure mode is "opt-out on, residue
242
+ * named", never a silent partial).
243
+ *
244
+ * ONE-WAY: no reverse verb exists at any layer; a repeat call is an idempotent `"existed"`. The
245
+ * way back to capture is a NEW session (a deliberate product cost — "which turns count" has no
246
+ * honest answer for a retroactive re-enable).
247
+ *
248
+ * AUTHORITY (steer-lane law): core does not authenticate — holding this stream IS the capability
249
+ * handle, exactly as with {@link steer}; a service exposing this verb over the wire owns the
250
+ * verified-principal / session-ownership check at its own face (a bare sessionId relay would let
251
+ * anyone switch off anyone's memory: an availability attack). The call re-adjudicates the
252
+ * per-principal entitlement AT FLIP TIME (`RuntimeCaps.allowMemoryOptOut` under
253
+ * {@link RunnerDeps.memoryCapturePolicy}, fresh resolve — a denied flip refuses typed
254
+ * `memory.capture_optout_denied` and leaves ZERO record residue). MODEL-UNREACHABLE by
255
+ * construction (§2.6): no tool face, no MCP face, and governed workflow specs cannot spell it —
256
+ * a model that could declare an opt-out could silently disable a user's memory; one that could
257
+ * revoke it would pierce the privacy control.
258
+ *
259
+ * Typed refusals (the closed set, one per arm — rescan post-6.0.0-RC completed the roster):
260
+ * `memory.capture_optout_denied` (entitlement), `memory.capture_optout_unpersisted` (the record
261
+ * could not be durably written — a store-mark failure OR a failing §2.7 consolidation-epoch
262
+ * bump, both refused rather than held in-process, which the first resume would silently break),
263
+ * `memory.capture_optout_unavailable` (this run mounted no memory session — nothing to opt out
264
+ * of), `memory.capture_optout_sweep_failed` (the record STANDS but the §2.3 boundary sweep could
265
+ * not contain named residue paths — a repeat call is the retry lane),
266
+ * `config.memory_capture_unsupported` (remote/per-run deployment shape with no
267
+ * {@link RunnerDeps.memoryCaptureRecordStore} — the record would not survive to a resume
268
+ * replica), `steering.invalid_content` (a non-string `options.reason`), plus the steer-family
269
+ * `steering.not_running` once the task has finished. Resolves `{ outcome }`: `"created"` = this
270
+ * call made the crossing (the `memory.capture_opted_out` notice minted); `"existed"` = already
271
+ * recorded (idempotent; no second notice).
272
+ */
273
+ optOutMemoryCapture(options?: {
274
+ reason?: string;
275
+ }): Promise<{
276
+ outcome: "created" | "existed";
277
+ }>;
278
+ /**
279
+ * Hard-**interrupt** the running task — a real abort (not a best-effort hint), equivalent to firing the
280
+ * task's `signal` (design/47). No-op if the task already finished. For a SOFT redirect that lets the
281
+ * task keep running and adjust, use {@link steer} instead. For the CC-Esc "stop and wait for my
282
+ * input" form — cut the turn, keep the ending clean and resumable — use {@link halt}.
283
+ */
284
+ interrupt(): Promise<void>;
285
+ /**
286
+ * design/373 (#504) — the BARE user interrupt, the CC Esc true form ("cut + stop"): CUT the
287
+ * in-flight turn and STOP the run at that manufactured boundary, collecting to a clean,
288
+ * resumable ending. The run-model equivalent of CC's session staying alive waiting for the
289
+ * user's next input. Three verbs, three powers: {@link interrupt} hard-aborts the run (the
290
+ * orphan-reconcile ending), {@link steer} with `priority:"now"` cuts the turn AND CONTINUES the
291
+ * run with the steer text, `halt` cuts the turn and lets the run END awaiting the user.
292
+ *
293
+ * The CUT half is the design/373 S1 turn-scoped settle, verbatim: finished tool calls keep their
294
+ * REAL results; never-started ones settle as paired interrupted results
295
+ * (`errorKind:"interrupted_never_started"`); the CC-verbatim interruption marker lands in the
296
+ * session as an engine-note user frame. Every REAL cut is announced (`task.turn_interrupted`
297
+ * notice, `detail.cause:"user_halt"` — the steer-now landing keeps its own message and no
298
+ * `cause` key). The STOP half: from acceptance on, NO new model turn starts — the loop ends at
299
+ * whichever boundary comes first (both boundary faces consult the latch, the pre-turn one
300
+ * included, so a halt landing between turns cannot buy one more turn). With nothing in flight
301
+ * the cut half is a no-op (`turnCut:false`) and the stop half alone answers — no false marker.
302
+ *
303
+ * **Terminal form (#504 detail ①, ruled completed-with-marker over a new status/stop-reason
304
+ * member):** the run ends `status:"completed"` with the additive result seat
305
+ * {@link TaskResult.haltedByUser} `: true` as the discriminator. CC anchor: 250 has NO typed
306
+ * "interrupted" terminal — Esc leaves a clean continuable session with the transcript marker as
307
+ * the record, and the SDK result taxonomy (`error_during_execution`/`error_max_turns`/…) gains
308
+ * no interrupt member; a new closed-set status/stop-reason row would therefore be a non-anchored
309
+ * invention, while completed-plus-marker is also this repo's own precedent seat
310
+ * ({@link TaskResult.haltedOnUserRejection} — "the model did not finish; the person stopped it
311
+ * and the run awaits their direction"). Consumers discriminating "user stopped it" vs "ran to
312
+ * completion" (billing, continuation copy) read the one boolean.
313
+ *
314
+ * **Queued-but-undelivered frames (#504 detail ②, ruled keep-don't-clear):** a halt clears
315
+ * NOTHING. CC anchor: 250's `control_request:"interrupt"` handler keeps the command queue by
316
+ * default and answers `still_queued`; clearing is the explicit `cancel_queued:true` opt-in
317
+ * (`cleared_on_cancel`) — destruction is never the interrupt's default meaning. The run-model
318
+ * translation: queued steer/follow-up frames stay queued through the cut, are NOT drained into a
319
+ * turn that will never start, and settle by the run's EXISTING terminal contract for
320
+ * accepted-but-undelivered input — durable park when a suspend seat exists, else the loud
321
+ * per-run accounts (`task.user_steer_undrained` / `task.user_followup_undrained`). The receipt
322
+ * semantics were always "queued = accepted, never consumed": the caller holds the bytes, the
323
+ * account tells it what to re-send, and `inputId` idempotency makes host-side re-delivery on the
324
+ * next run replay-safe. (A per-session durable park for non-suspend endings would be new storage
325
+ * machinery — out of this slice, recorded.)
326
+ *
327
+ * **Resume hand-off (#504 detail ③, ruled the ordinary user-lane front door):** the next input
328
+ * enters as the NEXT RUN's objective on the same session (`runTask`/`runTaskStream` with this
329
+ * result's `sessionId`) — no special lane, no checkpoint. The halted transcript ends with the
330
+ * interruption marker exactly where CC's session shows it (marker, then the user's next
331
+ * message), so the resumed model reads the same shape a CC session shows after Esc. A
332
+ * checkpoint-resume is neither minted nor needed: `"completed"` + `sessionId` IS the
333
+ * continuation contract, and a marker-less halted transcript (nothing was in flight to cut) is
334
+ * an ordinary clean boundary.
335
+ *
336
+ * Receipt: resolves `{ turnCut }` — `true` iff THIS call cut a live turn (the completion-race
337
+ * arm is stated, not hidden: a turn whose work finished as the abort landed ends normally and
338
+ * the run still stops at its boundary). Repeat calls are accepted and answer honestly
339
+ * (`turnCut:false` once the stop is already latched); the notice is per-cut, the result seat
340
+ * per-run. AUTHORITY (steer-lane law): holding this stream IS the capability — a deployment
341
+ * relaying this verb owns its own gate (relay IS authorization; it cancels in-flight tool work
342
+ * and ends the run's forward progress). No text ⇒ the `userPromptSubmit` screen has no domain
343
+ * here (nothing enters the model). Honest window, stated: a halt accepted in the run's last
344
+ * moments (past the loop's final commit point) finds the run completing naturally — the result
345
+ * then reports the natural completion WITH the `haltedByUser` seat (the person did press stop
346
+ * while it finished; the transcript says how far the model got). Typed refusals: the
347
+ * steer-family `steering.not_running` once the task has finished (teardown included); a halt
348
+ * issued BEFORE the run's first prompt polls the same bounded birth window as {@link steer} and
349
+ * then stops the run before its first model turn (an empty, cleanly-halted completed run).
350
+ *
351
+ * **Receipt tension, stated (design/384 slice 2):** `{turnCut:true}` and the
352
+ * `task.turn_interrupted` notice assert facts about the CUT — a seat was cut, no new model turn
353
+ * starts — and both stay true even when the gate's durable leg still collects to `suspended`:
354
+ * a park whose store commit was already in flight (or committed) when the cut landed WINS the
355
+ * fence race, the row is redeemable, and the result then reads `status:"suspended"` WITHOUT
356
+ * {@link TaskResult.haltedByUser} (the transitional narrowing documented on that seat). A cut
357
+ * observed BEFORE the commit makes the park concede instead — no row, no card, and the ordinary
358
+ * halted ending.
359
+ */
360
+ halt(): Promise<{
361
+ turnCut: boolean;
362
+ }>;
363
+ /**
364
+ * Reap this task's resources — **callable after it finished**, idempotent, never throws (design/50 §3,
365
+ * design/51). The fan-out (v2) cancel primitive: an orchestrator's `cancelAll` is the shared
366
+ * `AbortController.abort()` then `Promise.all(streams.map((s) => s.destroy()))`.
367
+ *
368
+ * - **still running** → hard-interrupts it; the run's own `finish()` tears down its execution env.
369
+ * - **suspended** (`status:"suspended"`) → `finish()` SKIPPED env teardown (the remote env was
370
+ * `suspendVM`-paused for a durable resume) and the checkpoint is committed → `destroy()` reaps BOTH:
371
+ * it CAS-**expires** the checkpoint (`CheckpointStore.expire`, fencing any concurrent resume) and —
372
+ * **only if it won that CAS** — destroys the paused env. So a cancelled fan-out worker never leaks a
373
+ * paused container + orphan checkpoint, and is never resumed onto an env `destroy()` already tore down
374
+ * (the expire/resolve CAS on the one `pending` row is mutually exclusive: exactly one of reap/resume
375
+ * wins). If `expire` itself fails, the env is left intact (checkpoint + paused env stay a consistent,
376
+ * still-resumable pair; the failure surfaces via `RunnerDeps.onError`, not a throw).
377
+ * - **already completed / failed / already destroyed** → no-op.
378
+ *
379
+ * Idempotent at the promise level (repeat/concurrent calls await the same settled work).
380
+ */
381
+ destroy(): Promise<void>;
382
+ }
@@ -0,0 +1 @@
1
+ export {};