@sema-agent/core 7.5.2 → 7.6.0

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 (85) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/agents/cascade.d.ts +2 -2
  3. package/dist/agents/cascade.js +12 -10
  4. package/dist/agents/repair-loop.d.ts +5 -3
  5. package/dist/agents/repair-loop.js +13 -15
  6. package/dist/agents/subagent.d.ts +24 -42
  7. package/dist/agents/subagent.js +119 -105
  8. package/dist/agents/suspend-guard.d.ts +31 -19
  9. package/dist/agents/suspend-guard.js +14 -8
  10. package/dist/agents/teacher.js +9 -9
  11. package/dist/agents/team.d.ts +4 -3
  12. package/dist/agents/team.js +10 -8
  13. package/dist/agents/verify.d.ts +3 -3
  14. package/dist/agents/verify.js +17 -17
  15. package/dist/core/a2a.js +2 -1
  16. package/dist/core/ask-origin.d.ts +60 -7
  17. package/dist/core/ask-origin.js +26 -1
  18. package/dist/core/checkpoint-store.d.ts +78 -76
  19. package/dist/core/checkpoint-store.js +17 -1
  20. package/dist/core/gate-outcome.d.ts +189 -0
  21. package/dist/core/gate-outcome.js +70 -0
  22. package/dist/core/hooks.d.ts +18 -92
  23. package/dist/core/hooks.js +88 -85
  24. package/dist/core/mcp-failure.d.ts +104 -0
  25. package/dist/core/mcp-failure.js +128 -0
  26. package/dist/core/mcp.d.ts +21 -77
  27. package/dist/core/mcp.js +76 -150
  28. package/dist/core/pause-registry.d.ts +131 -0
  29. package/dist/core/pause-registry.js +27 -0
  30. package/dist/core/runner/assemble-result.d.ts +32 -41
  31. package/dist/core/runner/assemble-result.js +55 -74
  32. package/dist/core/runner/contracts.d.ts +46 -64
  33. package/dist/core/runner/denial-limit-arms.d.ts +1 -1
  34. package/dist/core/runner/denial-limit-arms.js +3 -3
  35. package/dist/core/runner/gate-exit.d.ts +74 -0
  36. package/dist/core/runner/gate-exit.js +55 -0
  37. package/dist/core/runner/park-commit.d.ts +17 -23
  38. package/dist/core/runner/park-commit.js +14 -15
  39. package/dist/core/runner/prepare-ask-lane.d.ts +0 -3
  40. package/dist/core/runner/prepare-ask-lane.js +3 -5
  41. package/dist/core/runner/prepare-boundary-parks.d.ts +3 -6
  42. package/dist/core/runner/prepare-boundary-parks.js +3 -3
  43. package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
  44. package/dist/core/runner/prepare-gate-stations.d.ts +4 -7
  45. package/dist/core/runner/prepare-gate-stations.js +29 -54
  46. package/dist/core/runner/prepare-inherited-gate.js +1 -1
  47. package/dist/core/runner/prepare-memory.d.ts +44 -26
  48. package/dist/core/runner/prepare-park-ask.d.ts +2 -4
  49. package/dist/core/runner/prepare-park-ask.js +5 -5
  50. package/dist/core/runner/prepare-task.js +8 -9
  51. package/dist/core/runner/prepare-wiring-manifest.d.ts +7 -15
  52. package/dist/core/runner/prepare-wiring-manifest.js +9 -10
  53. package/dist/core/runner/runtask.d.ts +16 -31
  54. package/dist/core/runner/runtask.js +109 -120
  55. package/dist/core/runner/terminal-projection.d.ts +22 -0
  56. package/dist/core/runner/terminal-projection.js +28 -0
  57. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -1
  58. package/dist/core/store-contracts/checkpoint-store-contract.js +8 -2
  59. package/dist/core/terminal-cause.d.ts +137 -0
  60. package/dist/core/terminal-cause.js +9 -0
  61. package/dist/core/tool-policy.d.ts +43 -139
  62. package/dist/core/tool-policy.js +79 -112
  63. package/dist/core/types.d.ts +67 -164
  64. package/dist/core/wiring-manifest.d.ts +6 -3
  65. package/dist/core/workflow-journal-store.js +3 -4
  66. package/dist/engine/harness/agent-harness.d.ts +1 -1
  67. package/dist/index.d.ts +10 -7
  68. package/dist/index.js +8 -5
  69. package/dist/orchestration/builtin-workflows.d.ts +2 -2
  70. package/dist/orchestration/builtin-workflows.js +1 -1
  71. package/dist/orchestration/goal.js +8 -7
  72. package/dist/orchestration/run-spec.js +5 -3
  73. package/dist/orchestration/run-workflow-tool.d.ts +1 -1
  74. package/dist/orchestration/run-workflow-tool.js +4 -4
  75. package/dist/orchestration/workflow-governance.d.ts +4 -4
  76. package/dist/orchestration/workflow-governance.js +4 -2
  77. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  78. package/dist/orchestration/workflow-primitives.js +1 -1
  79. package/dist/orchestration/workflow.d.ts +11 -0
  80. package/dist/orchestration/workflow.js +64 -39
  81. package/dist/prompts/supervisor.d.ts +1 -1
  82. package/dist/prompts/supervisor.js +3 -3
  83. package/dist/scenarios/scenario-registry.js +1 -1
  84. package/package.json +3 -1
  85. package/test/export-surface.snapshot.json +74 -22
package/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
1
1
  # Changelog
2
2
 
3
+ ## 7.6.0 — 2026-09-07
4
+
5
+ **BREAKING.** design/390 S6 (three slices, merged): the tool gate's settlement/origin words become ONE `GateOutcome` record, `TaskResult` becomes ONE terminal record, MCP failures become ONE structural record with a delivery verdict, and the durable off switch becomes a word. Every consumer that read the retired keys must migrate (server 7.64.0, cli/client-core, web-admin, desktop — the criteria post lists the frames old→new).
6
+
7
+ ### BREAKING — exports removed (10)
8
+ - `APPROVAL_SETTLED_BY_VALUES`, `ASK_DENY_RESOLUTION_VALUES`, `isApprovalSettledBy`, `isAskDenyResolution`, `type ApprovalSettledBy`, `type AskDenyResolution`, `type PermissionDeniedSource` (S6-A — replaced by `GateOutcome` / `Settlement` / `DeniedBy`).
9
+ - `MCP_FAILURE_CODES`, `mcpFailureCodeOf`, `type McpFailureCode` (S6-B — replaced by `McpFailure` / `MCP_FAILURE_KINDS` / `classifyMcpFailure`).
10
+
11
+ ### BREAKING — same-name shape changes
12
+ - `TaskResult`: the eight plane fields `status` / `errorCode` / `errorMessage` / `blockedReason` / `checkpointToken` / `checkpointId` / `checkpointGate` / `workspaceRestoreMode` are deleted; the run's end is ONE required `terminal: TerminalCause` (`completed` | `failed{code?, message?, nestedPause?}` | `blocked{reason}` | `paused{gate, token, checkpointId?, restoreMode?}`). `terminalProjection(terminal)` is the explicit projection for a face that keeps the old shape. The `done` frame, the HTTP task body and SSE `done.result` are the result itself, so they lose the eight keys and carry `terminal`; a nested boundary's pause is `failed` + `nestedPause` (its token no longer rides `checkpointToken`).
13
+ - `TaskSpec.checkpointStore`: the per-run durable off switch is the word `"disabled"`; the retired `null` is refused at the first prepare with `config.invalid_checkpoint_store` (also through a worktree baseline overlay). `engine_notice` cause `task_store_null` → `task_store_disabled`.
14
+ - `Checkpoint` is a type (`CheckpointRow & CheckpointPause`) whose gate × pendingAction pairing is fixed by `PAUSE_REGISTRY`; hand-built rows go through `checkpointFrom` / `pauseOf`. A dry-run `needs_review` row carries its own `pendingAction.kind: "review"`.
15
+ - `ResumeOutcome.policy_ask.hostDecision` is required: a host resolving a park supplies decision FACTS (`{decision, decidedBy: "person" | "sla_timeout", …}`) and core mints every settlement word — a host never spells a kind.
16
+ - `RealApprovalGateBit.origin` is the full `AskOrigin` set (was a five-word subset) and gains the org FACT member `org?: "rule" | "unavailable"`; read governance through `realApprovalOrgFact(bit)`, never through the routing word. `AskOrigin` gains `safety_tighten` and `shell_gate_tighten`.
17
+ - `TeacherRunResult` and `VerificationResult` (they extend `TaskResult`) lose the same eight plane keys; `ResumeOutcome` loses `settledBy` / `approver` (the host supplies `hostDecision` facts instead).
18
+ - `ResolvedAsk` loses its four settlement keys; `RepairResult.terminal` → `repairTerminal`; `McpErrorMarks.errorKind` is replaced by `McpFailure{kind, delivered, httpStatus?}`; `SubagentErrorKind` drops `overloaded` and gains `unknown`.
19
+
20
+ ### BREAKING — wire frames
21
+ - `tool_end`: `settledBy` / `resolution` / `autoDenied` / `approver` deleted → `gate?: GateOutcome` (`disposition: {kind:"allowed"} | {kind:"denied", deniedBy}`, `settlement?: Settlement` (12 kinds, `who` fixed by `kind`), `origin?: AskOrigin`); `errorCode` speaks `McpFailureKind` (`connection_failed` / `connection_closed` / `timeout` / `protocol` / `http_status` / …; `network` and `http_<n>` retired) or a result code (`mcp.task_required`, `mcp.tool_error`, `mcp.server_revoked`); new `delivered?: "yes" | "no" | "unknown"`.
22
+ - `permissionDenied` observer payload: `source` / `resolution` / `autoDenied` deleted → `gate: GateOutcome` (frozen). `DeniedBy` = policy / hook / org / classifier / plan_mode / compliance / write_protection / ask_resolution.
23
+ - `wiring_manifest.mcp[]`: `errorCode` in the new words, plus `delivered` and `httpStatus`.
24
+ - Checkpoint rows: `realApproval.origin` full set + `org`; resolved rows record `resolvedOutcome.gateOutcome`; a `review` pending kind exists.
25
+ - Agent card `error_kind`: `overloaded` → gone, `unknown` added (no structured code ⇒ `unknown`, not retryable).
26
+
27
+ ### Changed (narrowings, named)
28
+ - An approval-factory approver's bare refusal on the parent thread halts the batch like a live approver's; a person's yes vetoed by a later layer is recorded as `human_allowed` beside the vetoing `deniedBy`; the post-CAS belt's refusal carries the veto record.
29
+ - The HTTP MCP transport no longer follows redirects (3xx ⇒ `http_status`, delivered `unknown`); a listing requiring task support is refused before any send (`mcp.task_required`, delivered `no`); a server's `isError:true` reply is `mcp.tool_error` with delivered `yes`; same-raw-name listings mount from one accepted view; a bare `TypeError` classifies as `unknown`, never `connection_failed`.
30
+ - A resume journal entry whose result carries no closed-set terminal cause (a journal written before `TaskResult.terminal`, or a malformed entry) refuses the resume with `WorkflowJournalIncompatibleError` (`workflow.journal_incompatible`) before any agent is dispatched — it no longer diverges into a live re-run of completed work. `TERMINAL_CAUSE_IS_REPLAYABLE` is the one table behind admission and replay.
31
+ - A background child's "durable pause without a token" arm is retired (unconstructible).
32
+
33
+ ### Added
34
+ - `GateOutcome` / `Settlement` / `DeniedBy` (+ `SETTLEMENT_KINDS`, `DENIED_BY_VALUES`, `SETTLEMENT_IS_REFUSAL`, `DENIED_BY_MAY_VETO`, `isSettlementKind`, `isDeniedBy`, `screenGateOutcome`), minted once at the gate exit (`mintGateOutcome`) and projected onto three faces; invariants I1–I4 screened at the wire entry; `ORIGIN_IMPLIES_REAL_APPROVAL`.
35
+ - `PAUSE_REGISTRY` (+ `GateKind`, `PendingKind`, `ResumeGate`, `isGateKind`, `resumeGateMatches`, `checkpointFrom`, `pauseOf`, `CheckpointFields`, `CheckpointPause`, `CheckpointRow`).
36
+ - `TerminalCause`, `PausedCause`, `terminalProjection`, `realApprovalOrgFact`, `resolveCheckpointStore`.
37
+ - `McpFailure`, `MCP_FAILURE_KINDS`, `MCP_DELIVERY_VERDICTS`, `McpDelivered`, `classifyMcpFailure` (ordered structural classifier, no text regex).
38
+ - `WorkflowJournalIncompatibleError`.
39
+ - Gates: `gate:closed-sets` (`docs/CLOSED-SETS.md` registry + `AssertAllKeysHandled` fences + switch exhaustiveness) and `gate:contract-sentences` (`@contract` ids, `docs/CONTRACT-SENTENCES.md`, duplicate-sentence baseline); invariant pins `test/gate-outcome-invariants.test.ts`; `test/design390-d8-terminal-only.test.ts`.
40
+
41
+ ### Unchanged and pinned
42
+ - The suspendAsk 13-parameter `AskCarry`; the 23 gate stations (words, order); delegation lifecycle frames, `task.end` trace, agent cards, reports and workflow records keep their shape through the explicit projection; `engine_notice` codes other than the renamed cause; `wiring_manifest` keys other than `mcp[]`.
43
+
3
44
  ## 7.5.2 — 2026-09-06
4
45
 
5
46
  Maintenance release: design/390 slices S3–S4 complete the `prepareTask` decomposition (4 756 → 1 666 driver lines, 34 phase modules), one settlement-path narrowing (#587), and the wire-manifest tooling that the 7.5.1 review asked for (#588 / #589). **No BREAKING change**: the export surface is byte-identical to 7.5.1; the only behaviour delta is that a reserved-tool-name collision now disposes an acquired protocol handle once instead of twice (below).
@@ -1,6 +1,6 @@
1
1
  import type { Runner } from "../core/runner/runtask.js";
2
2
  import type { RunInternals } from "../core/runner/prepare-task.js";
3
- import type { ModelRef, TaskResult, TaskSpec } from "../core/types.js";
3
+ import type { ModelRef, TaskResult, TaskSpec, TaskStatus } from "../core/types.js";
4
4
  /**
5
5
  * Quality-gate cascade (design/27). Run the SAME task across a ladder of models (cheapest → strongest);
6
6
  * after each rung a caller-supplied **decidable gate** judges "good enough" — pass = keep it, fail =
@@ -109,7 +109,7 @@ export interface CascadeAttempt {
109
109
  * F4) — a fabricated 0 here both misreported the attempt and let escalation ride under a finite
110
110
  * ceiling the engine could not actually enforce. */
111
111
  costMicroUsd?: number;
112
- status: TaskResult["status"];
112
+ status: TaskStatus;
113
113
  /** The rung's own failure code (e.g. "auth") if its `runTask` failed — distinct from "gate rejected it". */
114
114
  errorCode?: string;
115
115
  /** Set when the gate THREW (vs returned false) — surfaces a broken verifier-gate (expired key, etc.). */
@@ -1,3 +1,4 @@
1
+ import { terminalProjection } from "../core/runner/terminal-projection.js";
1
2
  import { releaseSession } from "./session-util.js";
2
3
  import { mapNestedSuspend, isDurablePause } from "./suspend-guard.js";
3
4
  import { buildCumulativeStats } from "./cumulative-stats.js";
@@ -10,9 +11,13 @@ function cascadeConfigError(message) {
10
11
  e.code = CASCADE_CONFIG_ERROR_CODE;
11
12
  return e;
12
13
  }
14
+ function attemptTerminal(result) {
15
+ const face = terminalProjection(result.terminal);
16
+ return { status: face.status, errorCode: face.errorCode };
17
+ }
13
18
  function createDefaultGate(spec) {
14
19
  const requiresStructured = spec.outputSchema != null;
15
- return (result) => result.status === "completed" && (!requiresStructured || result.structuredOutput !== undefined);
20
+ return (result) => result.terminal.kind === "completed" && (!requiresStructured || result.structuredOutput !== undefined);
16
21
  }
17
22
  export async function runCascade(runner, spec, config, internals) {
18
23
  const ladder = config.ladder;
@@ -126,9 +131,9 @@ export async function runCascade(runner, spec, config, internals) {
126
131
  nestedTasks += s.nested.tasks;
127
132
  nestedCostMicro += s.nested.costMicroUsd ?? 0;
128
133
  }
129
- if (isDurablePause(result.status)) {
134
+ if (isDurablePause(result.terminal)) {
130
135
  const mapped = mapNestedSuspend(result);
131
- attempts.push({ index: i, model: rung.model, passed: false, costMicroUsd: rungCost, status: result.status, errorCode: mapped.errorCode });
136
+ attempts.push({ index: i, model: rung.model, passed: false, costMicroUsd: rungCost, status: attemptTerminal(result).status, errorCode: attemptTerminal(mapped).errorCode });
132
137
  return {
133
138
  ...mapped,
134
139
  stats: cumulativeStats(result.stats),
@@ -145,8 +150,7 @@ export async function runCascade(runner, spec, config, internals) {
145
150
  model: rung.model,
146
151
  passed: false,
147
152
  costMicroUsd: rungCost,
148
- status: result.status,
149
- errorCode: result.errorCode,
153
+ ...attemptTerminal(result),
150
154
  ...(deadlineFired ? { timedOut: true } : { cancelled: true }),
151
155
  });
152
156
  notifier.notify(() => config.onRung?.({ index: i, model: rung.model, passed: false, result }), CASCADE_ON_RUNG_SITE);
@@ -170,7 +174,7 @@ export async function runCascade(runner, spec, config, internals) {
170
174
  passed = false;
171
175
  gateError = e instanceof Error ? e.message : String(e);
172
176
  }
173
- const statusRejected = passed && config.acceptPartial !== true && result.status !== "completed";
177
+ const statusRejected = passed && config.acceptPartial !== true && result.terminal.kind !== "completed";
174
178
  if (statusRejected)
175
179
  passed = false;
176
180
  attempts.push({
@@ -178,8 +182,7 @@ export async function runCascade(runner, spec, config, internals) {
178
182
  model: rung.model,
179
183
  passed,
180
184
  costMicroUsd: rungCost,
181
- status: result.status,
182
- errorCode: result.errorCode,
185
+ ...attemptTerminal(result),
183
186
  gateError,
184
187
  diagnostics,
185
188
  ...(statusRejected ? { statusRejected: true } : {}),
@@ -198,9 +201,8 @@ export async function runCascade(runner, spec, config, internals) {
198
201
  return {
199
202
  taskId: spec.taskId ?? "",
200
203
  sessionId: "",
201
- status: "failed",
202
204
  result: `runCascade: the overall wall-clock budget (totalTimeoutMs ${config.totalTimeoutMs}ms) was already spent when the ladder was entered — no rung dispatched`,
203
- errorCode: CASCADE_NO_DISPATCH_ERROR_CODE,
205
+ terminal: { kind: "failed", code: CASCADE_NO_DISPATCH_ERROR_CODE },
204
206
  stats: { tokens: 0, turns: 0, costMicroUsd: 0 },
205
207
  cascadeOutcome: "exhausted",
206
208
  escalated: false,
@@ -179,7 +179,7 @@ export interface RepairLoopConfig {
179
179
  /** Per-attempt callback (observability). */
180
180
  onAttempt?: (info: {
181
181
  attempt: number;
182
- terminal?: RepairTerminal;
182
+ repairTerminal?: RepairTerminal;
183
183
  oracle: OracleResult;
184
184
  }) => void;
185
185
  /**
@@ -191,8 +191,10 @@ export interface RepairLoopConfig {
191
191
  onWarn?: (warning: Error) => void;
192
192
  }
193
193
  export interface RepairResult extends VerificationResult {
194
- /** The projected terminal (§2). SAFE-tier never returns `fixed`. */
195
- terminal: RepairTerminal;
194
+ /** The projected REPAIR terminal (§2) the loop's own word (fixed / candidate_only / gave_up / …), distinct
195
+ * from the inherited run terminal `terminal` (a {@link import("../core/types.js").TerminalCause}, WHY the
196
+ * worker's last leg ended). SAFE-tier never returns `fixed`. */
197
+ repairTerminal: RepairTerminal;
196
198
  /** The final repair state (in-memory unless an orthogonal suspend serialized it — §4). */
197
199
  bundle: RepairBundle;
198
200
  /**
@@ -1,4 +1,4 @@
1
- import { isDurablePause } from "./suspend-guard.js";
1
+ import { isDurablePause, pauseFamily } from "./suspend-guard.js";
2
2
  import { delimitUntrusted, sanitizeUntrustedText } from "../core/untrusted-text.js";
3
3
  import { assertOracleIsolation } from "../core/oracle-isolation.js";
4
4
  import { buildCumulativeStats } from "./cumulative-stats.js";
@@ -48,12 +48,11 @@ export async function runRepairLoop(runner, implSpec, config) {
48
48
  const oracle = { tier: "none", passed: false, flaky: false, retries: 0 };
49
49
  return {
50
50
  taskId: implSpec.taskId ?? "",
51
- status: "failed",
52
51
  result: `oracle isolation check failed: ${reason}`,
53
- errorCode: "oracle.unprotected",
52
+ terminal: { kind: "failed", code: "oracle.unprotected", message: `oracle isolation check failed: ${reason}` },
54
53
  sessionId: "",
55
54
  stats: { tokens: 0, turns: 0, costMicroUsd: 0 },
56
- terminal: "oracle.unprotected",
55
+ repairTerminal: "oracle.unprotected",
57
56
  ...(isolationClass ? { isolationClass } : {}),
58
57
  bundle: { ...freshBundle(), attemptCount: config.resumeBundle?.attemptCount ?? 0 },
59
58
  oracleCostMicroUsd: 0,
@@ -157,12 +156,11 @@ export async function runRepairLoop(runner, implSpec, config) {
157
156
  };
158
157
  const exhaustedBeforeDispatch = (message, errorCode) => ({
159
158
  taskId: implSpec.taskId ?? "",
160
- status: "failed",
161
159
  result: message ?? `repair loop: attempt budget already spent (attemptCount ${bundle.attemptCount} >= maxAttempts ${maxAttempts}) — no attempt dispatched`,
162
- errorCode: errorCode ?? "repair.attempts_exhausted",
160
+ terminal: { kind: "failed", code: errorCode ?? "repair.attempts_exhausted", message: message ?? `repair loop: attempt budget already spent (attemptCount ${bundle.attemptCount} >= maxAttempts ${maxAttempts}) — no attempt dispatched` },
163
161
  sessionId: implSpec.sessionId ?? "",
164
162
  stats: { tokens: 0, turns: 0, costMicroUsd: 0 },
165
- terminal: "gave_up",
163
+ repairTerminal: "gave_up",
166
164
  bundle,
167
165
  ...(isolationClass ? { isolationClass } : {}),
168
166
  oracleCostMicroUsd,
@@ -208,22 +206,22 @@ export async function runRepairLoop(runner, implSpec, config) {
208
206
  accumulate(result.stats);
209
207
  lastResult = result;
210
208
  bundle.attemptCount += 1;
211
- if (isDurablePause(result.status)) {
209
+ if (isDurablePause(result.terminal)) {
212
210
  return {
213
211
  ...result,
214
212
  stats: cumulativeStats(result.stats),
215
- terminal: "gave_up",
213
+ repairTerminal: "gave_up",
216
214
  bundle,
217
215
  ...(isolationClass ? { isolationClass } : {}),
218
216
  oracleCostMicroUsd,
219
- verification: { verdict: "unverified", unverifiedReason: result.status === "needs_review" ? "needs_review" : "suspended", rounds: bundle.attemptCount, findings: [] },
217
+ verification: { verdict: "unverified", unverifiedReason: pauseFamily(result.terminal), rounds: bundle.attemptCount, findings: [] },
220
218
  };
221
219
  }
222
- if (result.status !== "completed") {
220
+ if (result.terminal.kind !== "completed") {
223
221
  return {
224
222
  ...result,
225
223
  stats: cumulativeStats(result.stats),
226
- terminal: "gave_up",
224
+ repairTerminal: "gave_up",
227
225
  bundle,
228
226
  ...(isolationClass ? { isolationClass } : {}),
229
227
  oracleCostMicroUsd,
@@ -237,12 +235,12 @@ export async function runRepairLoop(runner, implSpec, config) {
237
235
  lastOracle = oracle;
238
236
  bundle.oracleTier = oracle.tier;
239
237
  const projected = terminalForTier(oracle);
240
- config.onAttempt?.({ attempt: bundle.attemptCount, terminal: projected, oracle });
238
+ config.onAttempt?.({ attempt: bundle.attemptCount, repairTerminal: projected, oracle });
241
239
  if (projected !== undefined) {
242
240
  return {
243
241
  ...result,
244
242
  stats: cumulativeStats(result.stats),
245
- terminal: projected,
243
+ repairTerminal: projected,
246
244
  bundle,
247
245
  ...(isolationClass ? { isolationClass } : {}),
248
246
  oracleCostMicroUsd,
@@ -266,7 +264,7 @@ export async function runRepairLoop(runner, implSpec, config) {
266
264
  return {
267
265
  ...lastResult,
268
266
  stats: cumulativeStats(lastResult.stats),
269
- terminal,
267
+ repairTerminal: terminal,
270
268
  bundle,
271
269
  ...(isolationClass ? { isolationClass } : {}),
272
270
  oracleCostMicroUsd,
@@ -1,5 +1,5 @@
1
1
  import type { Runner } from "../core/runner/runtask.js";
2
- import type { AgentDefinition, Model, ModelRef, TaskLimits, ToolSpec } from "../core/types.js";
2
+ import type { AgentDefinition, Model, ModelRef, TaskLimits, TerminalCause, ToolSpec } from "../core/types.js";
3
3
  import { type ExecutionEnv } from "../internal/harness.js";
4
4
  import type { RunInternals } from "../core/runner/prepare-task.js";
5
5
  import type { TaskNotificationPayload } from "../core/task-notification.js";
@@ -215,45 +215,28 @@ export declare function createSubagentWorktreeHelper(baseEnv: ExecutionEnv, repo
215
215
  /** Cap for the inline-defused worker-controlled report metadata lines (blocked_reason / error). Matches the
216
216
  * ask-question.ts inline-value convention (300) — a one-line operator-facing reason, not a bulk payload. */
217
217
  export declare const REPORT_FIELD_MAX = 300;
218
- /** The machine-readable failure class stamped on a FAILED sub-agent report (sema form — see below). */
219
- export type SubagentErrorKind = "rate_limit" | "overloaded" | "timeout" | "network" | "logic" | "governance";
220
218
  /**
221
- * RB-302 ATTRIBUTION CORRECTED (taxonomy and behavior unchanged; this is a SEMA form, not CC
222
- * parity). The old header called this "CC 2.1.201 parity (追平批 α 项5)". Re-verified against the CC
223
- * 2.1.220 corpus, three things are true and none of them support that claim:
224
- * CC's `errorKind` lives on an ERROR CLASS (`AgentApiErrorTerminationError` @409103-409110), not
225
- * on the delegation's report card the Agent result schema (@409040-409090: usage / toolStats /
226
- * modelsUsed / resolvedModel) has no `error_kind` field at all;
227
- * its DOMAIN is three values, `{rate_limit, overloaded, server_error}` (`G2_` @409113) — no
228
- * `timeout`, no `network`, no `logic`;
229
- * its USE is not a retry signal. It gates PARTIAL-OUTPUT HANDBACK (`V2_` @408558-408570): when the
230
- * kind is in that set and the child produced text before dying, CC returns the salvaged history
231
- * with the cutoff note "Everything below is PARTIAL output recovered from the agent before it was
232
- * cut off. The agent did NOT finish its task — treat these results as incomplete."
233
- * sema's pair is a different mechanism answering a different question (re-delegate or not), derived
234
- * from sema's OWN errorCode taxonomy. It is kept as-is: it serves a real parent-side decision, and
235
- * changing the domain now would break every consumer for a parity that was never there.
236
- *
237
- * Residual (registered, not done here): CC's partial-output handback has no sema equivalent on this
238
- * lane sema's `salvagedOutput` covers two other terminals only. And `authentication_failed`-style
239
- * auth faults still fold into `logic`, which is correct for "don't retry" but coarse for a caller
240
- * that wants to re-auth and retry.
241
- *
242
- * Classification (unchanged) — a FAILED child run into a coarse `error_kind` +
243
- * `retryable` pair so the PARENT model (and an orchestrating deployment) can react correctly without
244
- * string-matching the report: a `rate_limit`/`overloaded`/`timeout`/`network` failure is transient
245
- * (re-delegating the same subtask may succeed); a `logic` failure is not (auth/invalid_request/budget/
246
- * output.* — re-delegating unchanged just burns tokens). Derived from the EXISTING taxonomy: the
247
- * child's `TaskResult.errorCode` (assemble-result's priority chain — brain `[code]` prefixes lifted
248
- * via `extractErrorCode`, `limits.max_walltime_exceeded`, `budget.*`, …) plus a message sniff for the two classes our
249
- * BrainError codes fold together ("server" covers overloaded_error/529 AND plain 5xx; "network"
250
- * covers both timeouts and resets). Returns undefined for a non-failed child (no error to classify).
219
+ * The machine-readable failure class stamped on a FAILED sub-agent report (the Agent card's `error_kind`
220
+ * beside `retryable`). A sema form, not a CC one: CC's kind lives on an error class and gates
221
+ * partial-output handback, not retry; sema's pair answers the parent's one question re-delegate or not.
222
+ * · `rate_limit` / `network` / `timeout` transient (re-delegating the same subtask may succeed);
223
+ * · `governance` a registered policy-plane refusal; retryability is the
224
+ * governance table's transient/terminal split;
225
+ * · `logic` — a coded failure re-delegating unchanged cannot fix
226
+ * (auth / invalid_request / http / output.* / budget axes);
227
+ * · `unknown` — the child reported NO code: nothing structural to classify,
228
+ * and the class is not guessed from the message (not retried).
229
+ */
230
+ export declare const SUBAGENT_ERROR_KINDS: readonly ["rate_limit", "timeout", "network", "logic", "governance", "unknown"];
231
+ export type SubagentErrorKind = (typeof SUBAGENT_ERROR_KINDS)[number];
232
+ /**
233
+ * Classify a FAILED child run into its `error_kind` + `retryable` pair from the child's STRUCTURED
234
+ * terminal code alone (the `TerminalCause.failed.code` taxonomy: brain codes, `limits.*`, `budget.*`,
235
+ * governance codes). The message is never read: a class read out of prose moved when the prose moved,
236
+ * and a remote's own error text could steer it. A child with no code is `unknown`. Returns undefined
237
+ * for a non-failed child (no error to classify).
251
238
  */
252
- export declare function classifySubagentError(child: {
253
- status: string;
254
- errorCode?: string;
255
- errorMessage?: string;
256
- }): {
239
+ export declare function classifySubagentError(terminal: TerminalCause): {
257
240
  errorKind: SubagentErrorKind;
258
241
  retryable: boolean;
259
242
  } | undefined;
@@ -315,13 +298,12 @@ export declare function classifySubagentError(child: {
315
298
  export declare function completedAgentCard(child: {
316
299
  taskId: string;
317
300
  sessionId: string;
318
- status: string;
301
+ /** The child's terminal cause. The card speaks its projection (`status` + the failure-attribution keys), never the
302
+ * cause's own members: a paused token, or the pause a nested boundary wrapped on `nestedPause`, is the resume CAPABILITY. */
303
+ terminal: TerminalCause;
319
304
  model?: string;
320
305
  result: string;
321
306
  salvagedOutput?: string;
322
- blockedReason?: string;
323
- errorMessage?: string;
324
- errorCode?: string;
325
307
  /** The child run's provider-boundary fault assertion, verbatim from its `TaskResult.apiFailure`.
326
308
  * PRESENCE is the claim ("this child's terminal failure came from the transport/provider, not from
327
309
  * this deployment refusing to send, a limit, or unusable model output"); the members are whatever