@sema-agent/core 7.5.2 → 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.
- package/CHANGELOG.md +67 -0
- package/dist/agents/agent-transcript-tool.d.ts +2 -2
- package/dist/agents/cascade.d.ts +4 -5
- package/dist/agents/cascade.js +12 -10
- package/dist/agents/repair-loop.d.ts +7 -5
- package/dist/agents/repair-loop.js +13 -15
- package/dist/agents/retain-ledger.d.ts +2 -3
- package/dist/agents/send-message-tool.d.ts +2 -2
- package/dist/agents/session-util.d.ts +2 -2
- package/dist/agents/subagent.d.ts +27 -46
- package/dist/agents/subagent.js +119 -105
- package/dist/agents/suspend-guard.d.ts +31 -19
- package/dist/agents/suspend-guard.js +14 -8
- package/dist/agents/teacher.d.ts +2 -2
- package/dist/agents/teacher.js +9 -9
- package/dist/agents/team.d.ts +6 -5
- package/dist/agents/team.js +10 -8
- package/dist/agents/verify.d.ts +8 -9
- package/dist/agents/verify.js +17 -17
- package/dist/core/a2a.js +2 -1
- package/dist/core/agent-definition.d.ts +172 -0
- package/dist/core/agent-definition.js +1 -0
- package/dist/core/ask-origin.d.ts +60 -7
- package/dist/core/ask-origin.js +26 -1
- package/dist/core/checkpoint-store.d.ts +78 -76
- package/dist/core/checkpoint-store.js +17 -1
- package/dist/core/delegation-frames.d.ts +298 -0
- package/dist/core/delegation-frames.js +21 -0
- package/dist/core/engine-notice.d.ts +555 -0
- package/dist/core/engine-notice.js +55 -0
- package/dist/core/gate-fold.d.ts +12 -0
- package/dist/core/gate-fold.js +158 -0
- package/dist/core/gate-lanes.d.ts +93 -0
- package/dist/core/gate-lanes.js +626 -0
- package/dist/core/gate-outcome.d.ts +189 -0
- package/dist/core/gate-outcome.js +70 -0
- package/dist/core/hands-band.d.ts +134 -0
- package/dist/core/hands-band.js +1 -0
- package/dist/core/hooks.d.ts +22 -177
- package/dist/core/hooks.js +53 -851
- package/dist/core/mcp-failure.d.ts +142 -0
- package/dist/core/mcp-failure.js +145 -0
- package/dist/core/mcp-server-spec.d.ts +217 -0
- package/dist/core/mcp-server-spec.js +1 -0
- package/dist/core/mcp.d.ts +21 -77
- package/dist/core/mcp.js +76 -150
- package/dist/core/model-seat.d.ts +99 -0
- package/dist/core/model-seat.js +1 -0
- package/dist/core/pause-registry.d.ts +131 -0
- package/dist/core/pause-registry.js +27 -0
- package/dist/core/reminder-mint.d.ts +10 -0
- package/dist/core/reminder-mint.js +3 -0
- package/dist/core/runner/assemble-result.d.ts +32 -41
- package/dist/core/runner/assemble-result.js +55 -74
- package/dist/core/runner/contracts.d.ts +427 -69
- package/dist/core/runner/denial-limit-arms.d.ts +1 -1
- package/dist/core/runner/denial-limit-arms.js +3 -3
- package/dist/core/runner/gate-exit.d.ts +242 -0
- package/dist/core/runner/gate-exit.js +124 -0
- package/dist/core/runner/park-commit.d.ts +17 -23
- package/dist/core/runner/park-commit.js +14 -15
- package/dist/core/runner/prepare-ask-lane.d.ts +0 -3
- package/dist/core/runner/prepare-ask-lane.js +3 -5
- package/dist/core/runner/prepare-boundary-parks.d.ts +3 -6
- package/dist/core/runner/prepare-boundary-parks.js +3 -3
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -7
- package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
- package/dist/core/runner/prepare-delegation-surface.d.ts +2 -7
- package/dist/core/runner/prepare-gate-stations.d.ts +4 -7
- package/dist/core/runner/prepare-gate-stations.js +29 -54
- package/dist/core/runner/prepare-inherited-gate.js +1 -1
- package/dist/core/runner/prepare-memory.d.ts +44 -26
- package/dist/core/runner/prepare-park-ask.d.ts +2 -4
- package/dist/core/runner/prepare-park-ask.js +5 -5
- package/dist/core/runner/prepare-task.d.ts +2 -2
- package/dist/core/runner/prepare-task.js +8 -9
- package/dist/core/runner/prepare-wiring-manifest.d.ts +7 -15
- package/dist/core/runner/prepare-wiring-manifest.js +9 -10
- package/dist/core/runner/runtask.d.ts +20 -102
- package/dist/core/runner/runtask.js +119 -121
- package/dist/core/runner/terminal-projection.d.ts +22 -0
- package/dist/core/runner/terminal-projection.js +28 -0
- package/dist/core/runner-deps.d.ts +1416 -0
- package/dist/core/runner-deps.js +1 -0
- package/dist/core/runtime-caps.d.ts +164 -0
- package/dist/core/runtime-caps.js +1 -0
- package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +8 -2
- package/dist/core/task-event.d.ts +910 -0
- package/dist/core/task-event.js +1 -0
- package/dist/core/task-limits.d.ts +110 -0
- package/dist/core/task-limits.js +1 -0
- package/dist/core/task-result.d.ts +809 -0
- package/dist/core/task-result.js +1 -0
- package/dist/core/task-spec.d.ts +1370 -0
- package/dist/core/task-spec.js +1 -0
- package/dist/core/task-stream.d.ts +382 -0
- package/dist/core/task-stream.js +1 -0
- package/dist/core/terminal-cause.d.ts +137 -0
- package/dist/core/terminal-cause.js +9 -0
- package/dist/core/tool-policy.d.ts +43 -139
- package/dist/core/tool-policy.js +79 -112
- package/dist/core/tool-spec.d.ts +1174 -0
- package/dist/core/tool-spec.js +1 -0
- package/dist/core/types.d.ts +27 -7789
- package/dist/core/types.js +2 -76
- package/dist/core/warm-resume.d.ts +2 -2
- package/dist/core/wiring-manifest.d.ts +6 -3
- package/dist/core/workflow-journal-store.js +3 -4
- package/dist/engine/harness/agent-harness.d.ts +1 -1
- package/dist/index.d.ts +12 -8
- package/dist/index.js +9 -6
- package/dist/orchestration/builtin-workflows.d.ts +2 -2
- package/dist/orchestration/builtin-workflows.js +1 -1
- package/dist/orchestration/goal.d.ts +2 -2
- package/dist/orchestration/goal.js +8 -7
- package/dist/orchestration/run-spec.d.ts +2 -2
- package/dist/orchestration/run-spec.js +5 -3
- package/dist/orchestration/run-workflow-tool.d.ts +4 -4
- package/dist/orchestration/run-workflow-tool.js +4 -4
- package/dist/orchestration/workflow-governance.d.ts +4 -4
- package/dist/orchestration/workflow-governance.js +4 -2
- package/dist/orchestration/workflow-primitives.d.ts +1 -1
- package/dist/orchestration/workflow-primitives.js +1 -1
- package/dist/orchestration/workflow.d.ts +15 -4
- package/dist/orchestration/workflow.js +64 -39
- package/dist/prompts/supervisor.d.ts +1 -1
- package/dist/prompts/supervisor.js +3 -3
- package/dist/scenarios/scenario-registry.d.ts +3 -3
- package/dist/scenarios/scenario-registry.js +1 -1
- package/dist/scenarios/teacher-quickstart.d.ts +2 -2
- package/dist/server/http.d.ts +2 -2
- package/dist/stores/file/fs-atomic.d.ts +88 -12
- package/dist/stores/file/fs-atomic.js +184 -55
- package/dist/stores/file/index.d.ts +1 -0
- package/dist/stores/file/index.js +1 -0
- package/package.json +3 -1
- package/test/export-surface.snapshot.json +82 -22
|
@@ -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 {};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The terminal cause — the ONE record of how a run ended (`TaskResult.terminal`), its closed kind set,
|
|
3
|
+
* the one disposition table over that set (which cause a resume journal may replay) and the admission
|
|
4
|
+
* read a journal entry's cause passes through. Layer 0 vocabulary, beside `gate-outcome.ts` (the record
|
|
5
|
+
* of one gate pass) and `pause-registry.ts` (the pause gate × pending-action pairing a paused cause
|
|
6
|
+
* points at): no runtime import above this layer, so any module may read the words.
|
|
7
|
+
*
|
|
8
|
+
* The plane-word projection a face that still speaks `status` needs lives in
|
|
9
|
+
* `runner/terminal-projection.ts` — the one cause→plane derivation.
|
|
10
|
+
*/
|
|
11
|
+
import type { AssertAllKeysHandled } from "./ask-origin.js";
|
|
12
|
+
/**
|
|
13
|
+
* WHY a run ended — ONE tagged cause, and the ONLY terminal record a {@link TaskResult} carries. The run
|
|
14
|
+
* loop's terminal arbitration used to decide a `status` word plus seven optional plane fields one by one
|
|
15
|
+
* across an if/else-if ladder over competing optional flags, and the legal combinations
|
|
16
|
+
* ("`status:"suspended"` ⇔ a token is present", "a review pause and an approval pause are never both set")
|
|
17
|
+
* lived only in that ladder's order. Here they are the SHAPE: a paused run carries its token; a completed
|
|
18
|
+
* run cannot. Branch on `kind`; the five-word {@link TaskStatus} a cause reads as (a `paused` cause reads
|
|
19
|
+
* its gate kind's registry row) is derived by {@link import("./runner/terminal-projection.js").terminalProjection},
|
|
20
|
+
* the one cause→plane derivation, for the faces that still speak the word.
|
|
21
|
+
* - `completed` — the run finished (a person's clean halt included — see `TaskResult.haltedByUser`).
|
|
22
|
+
* - `failed` — a limit, a provider failure, an abort, an invalid output…
|
|
23
|
+
* - `blocked` — the agent could not finish and said why.
|
|
24
|
+
* - `paused` — a durable pause committed a checkpoint and the run is resumable.
|
|
25
|
+
* A `GateOutcome` is the record of ONE gate pass (per call); a `TerminalCause` is the record of ONE run
|
|
26
|
+
* (per leg); a paused run's `gate` and the park row's `gate.kind` are the same word, joined by the token.
|
|
27
|
+
*/
|
|
28
|
+
export type TerminalCause = {
|
|
29
|
+
kind: "completed";
|
|
30
|
+
} | {
|
|
31
|
+
kind: "failed";
|
|
32
|
+
/**
|
|
33
|
+
* Machine-readable failure code when the failure carried one — e.g. a `SessionError.code` such as
|
|
34
|
+
* `"conflict"` (a cross-instance write lost the optimistic lock), or a Node error code. Lets a
|
|
35
|
+
* caller branch programmatically (e.g. evict a stale cache + retry on `"conflict"`) instead of
|
|
36
|
+
* matching `message` strings. Absent only for the failures that never had one (an abort with no
|
|
37
|
+
* limit axis, no assistant message produced).
|
|
38
|
+
*
|
|
39
|
+
* Terminal codes use a **dotted namespace** so a caller can prefix-match a whole class:
|
|
40
|
+
* `"limits.max_tokens_exceeded"` / `"limits.max_cost_exceeded"` / `"limits.max_turns_exceeded"` /
|
|
41
|
+
* `"limits.max_walltime_exceeded"` / `"config.limit_invalid"` / `"config.limit_unknown_key"` /
|
|
42
|
+
* `"config.attachment_invalid"` (a mode-valued `TaskSpec.attachments` member outside its closed
|
|
43
|
+
* set, refused at the same door as the limits) (e.g. `code.startsWith("limits.")`). Brain codes
|
|
44
|
+
* (`auth`/`network`/`rate_limit`/…) and `"conflict"` remain flat.
|
|
45
|
+
*
|
|
46
|
+
* Two codes name the EXTERNAL stop causes — neither is a `limits.` code, because neither is an
|
|
47
|
+
* allowance the task chose, and a caller that retries on `limits.*` should NOT treat these the same:
|
|
48
|
+
* - `"env.lifetime_expired"` — the execution environment's platform lifetime ran out and the run
|
|
49
|
+
* could not be suspended durably. Retrying needs a NEW environment, not a smaller budget.
|
|
50
|
+
* - `"usage.window_exhausted"` — a deployment usage-governance window is full (see
|
|
51
|
+
* `RunnerDeps.usageWindows`). Reported at task ENTRY (nothing ran) or after a suspend was
|
|
52
|
+
* impossible; the wait hint rides the thrown error's `retryAfterMs` (delivered through
|
|
53
|
+
* `RunnerDeps.onError`) and the message text. Retrying before the window frees is refused again.
|
|
54
|
+
* Their config-time siblings are `"config.env_lifetime_invalid"` / `"config.usage_window_invalid"`.
|
|
55
|
+
* A governance window with a MONEY ceiling adds two more, both of which say "the ceiling could not
|
|
56
|
+
* be evaluated" rather than "the ceiling was reached" — neither is retryable without a config change:
|
|
57
|
+
* - `"config.usage_window_unpriced"` — a `UsageWindow.maxCostUsd` over a run with no cost figure
|
|
58
|
+
* (no `RunnerDeps.pricing` entry and no `Model.cost`).
|
|
59
|
+
* - `"usage_window.store_cost_unanswered"` — the wired ledger does not carry the money arm.
|
|
60
|
+
* A NESTED orchestrator that met a durable pause it cannot drive stamps `"unexpected.suspended"` /
|
|
61
|
+
* `"unexpected.needs_review"` and carries the pause on {@link nestedPause}.
|
|
62
|
+
*/
|
|
63
|
+
code?: string;
|
|
64
|
+
/** Human-readable failure text. */
|
|
65
|
+
message?: string;
|
|
66
|
+
/**
|
|
67
|
+
* The durable pause this failure STANDS IN FOR — set only by the nested hard boundary
|
|
68
|
+
* (`mapNestedSuspend`): an orchestrator's nested leg paused on a durable gate, the orchestrator cannot
|
|
69
|
+
* drive a resume from the inside, so at ITS boundary the pause is a failure — but the pause's own
|
|
70
|
+
* cause travels here verbatim so the top-level caller keeps the recovery capability
|
|
71
|
+
* (`nestedPause.token` → `runner.resume`). Absent on every failure the engine itself assembles.
|
|
72
|
+
*/
|
|
73
|
+
nestedPause?: PausedCause;
|
|
74
|
+
} | {
|
|
75
|
+
kind: "blocked";
|
|
76
|
+
/** Why the agent could not finish. */
|
|
77
|
+
reason: string;
|
|
78
|
+
} | {
|
|
79
|
+
kind: "paused";
|
|
80
|
+
/**
|
|
81
|
+
* WHICH pause — who/what must resume (e.g. `{kind:"human", reason, toolName}`), so the caller knows
|
|
82
|
+
* what decision the checkpoint awaits and which resume outcome it takes (a `policy_ask` for an
|
|
83
|
+
* approval gate; a `dry_run_review` on a `needs_review` gate; a `plan_review` on a `plan_review`
|
|
84
|
+
* gate). Its kind's {@link import("./pause-registry.js").PAUSE_REGISTRY} row is what
|
|
85
|
+
* {@link import("./runner/terminal-projection.js").terminalProjection} reads the status word from
|
|
86
|
+
* (`"suspended"` for an approval / a resource slice / a platform pause, `"needs_review"` for a review).
|
|
87
|
+
*/
|
|
88
|
+
gate: import("./checkpoint-store.js").CheckpointGate;
|
|
89
|
+
/**
|
|
90
|
+
* The durable-checkpoint token to resume this task with via `runner.resume(token, outcome)`.
|
|
91
|
+
* Branded `CheckpointToken`; **never log it or put it in a URL** (it is the resume capability, §6).
|
|
92
|
+
*/
|
|
93
|
+
token: import("./checkpoint-store.js").CheckpointToken;
|
|
94
|
+
/**
|
|
95
|
+
* The pause's NON-SECRET stable identity ({@link import("./checkpoint-store.js").Checkpoint.checkpointId})
|
|
96
|
+
* — the display/correlation key a consumer may log or render where the token must not travel.
|
|
97
|
+
* Absent on pre-identity checkpoints.
|
|
98
|
+
*/
|
|
99
|
+
checkpointId?: string;
|
|
100
|
+
/**
|
|
101
|
+
* HOW the paused task's remote workspace will come back, present whenever the pause captured a
|
|
102
|
+
* remote workspace. The two modes bill and behave very differently:
|
|
103
|
+
* - `"snapshot"` — the workspace VM was `suspendVM`-paused into a snapshot. Provider billing
|
|
104
|
+
* typically stops; in-memory process state is captured; resume restores it.
|
|
105
|
+
* - `"park_only"` — the env declared itself non-suspendable (an SSH host / an ADB device), so
|
|
106
|
+
* NOTHING was paused: the machine keeps running (and keeps costing), any in-memory process state
|
|
107
|
+
* is at the target's mercy, and resume simply reconnects to the still-present workspace.
|
|
108
|
+
* Deliberate and honest (core does not fabricate a snapshot) — but a scheduler that assumes
|
|
109
|
+
* "paused ⇒ idle and free" must be able to see it.
|
|
110
|
+
* ABSENT for a process-local pause (a static caller-owned env: no remote workspace was captured).
|
|
111
|
+
* The same discriminant rides {@link import("./checkpoint-store.js").CheckpointSummary} for the
|
|
112
|
+
* inbox/`listByScope` face.
|
|
113
|
+
*/
|
|
114
|
+
restoreMode?: "snapshot" | "park_only";
|
|
115
|
+
};
|
|
116
|
+
/** The paused arm alone — the shape the run's committed-pause holder carries. */
|
|
117
|
+
/**
|
|
118
|
+
* The one disposition table over `TerminalCause["kind"]`: which causes a resume journal may REPLAY
|
|
119
|
+
* instead of re-running. Only a completed cause is a finished leg; every other cause means the leg has
|
|
120
|
+
* work left (or failed) and a resume runs it live. The table is also the admission list for a journal
|
|
121
|
+
* entry's cause at load: a kind absent here (a journal written before `TaskResult.terminal`, a corrupted
|
|
122
|
+
* or version-skewed entry) is refused before anything is dispatched — it is never read as "not
|
|
123
|
+
* completed, so run it again", which would replay the side effects of finished work.
|
|
124
|
+
*/
|
|
125
|
+
export declare const TERMINAL_CAUSE_IS_REPLAYABLE: {
|
|
126
|
+
readonly completed: true;
|
|
127
|
+
readonly failed: false;
|
|
128
|
+
readonly blocked: false;
|
|
129
|
+
readonly paused: false;
|
|
130
|
+
};
|
|
131
|
+
/** Compile-time fence: every `TerminalCause` kind has a row in the replay table (and, via `satisfies`, only those). */
|
|
132
|
+
export type ReplayTableCoversEveryTerminalCause = AssertAllKeysHandled<Exclude<TerminalCause["kind"], keyof typeof TERMINAL_CAUSE_IS_REPLAYABLE>>;
|
|
133
|
+
/** The admission read for a journaled cause: a closed-set member, by table row. */
|
|
134
|
+
export declare function isTerminalCauseKind(kind: unknown): kind is TerminalCause["kind"];
|
|
135
|
+
export type PausedCause = Extract<TerminalCause, {
|
|
136
|
+
kind: "paused";
|
|
137
|
+
}>;
|