@sema-agent/core 7.6.0 → 7.6.2

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 (78) hide show
  1. package/CHANGELOG.md +37 -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/checkpoint-store.d.ts +8 -4
  15. package/dist/core/delegation-frames.d.ts +298 -0
  16. package/dist/core/delegation-frames.js +21 -0
  17. package/dist/core/engine-notice.d.ts +555 -0
  18. package/dist/core/engine-notice.js +55 -0
  19. package/dist/core/gate-fold.d.ts +12 -0
  20. package/dist/core/gate-fold.js +158 -0
  21. package/dist/core/gate-lanes.d.ts +93 -0
  22. package/dist/core/gate-lanes.js +626 -0
  23. package/dist/core/hands-band.d.ts +134 -0
  24. package/dist/core/hands-band.js +1 -0
  25. package/dist/core/hooks.d.ts +20 -101
  26. package/dist/core/hooks.js +53 -854
  27. package/dist/core/mcp-failure.d.ts +43 -5
  28. package/dist/core/mcp-failure.js +31 -14
  29. package/dist/core/mcp-server-spec.d.ts +217 -0
  30. package/dist/core/mcp-server-spec.js +1 -0
  31. package/dist/core/model-seat.d.ts +99 -0
  32. package/dist/core/model-seat.js +1 -0
  33. package/dist/core/reminder-mint.d.ts +10 -0
  34. package/dist/core/reminder-mint.js +3 -0
  35. package/dist/core/runner/contracts.d.ts +382 -6
  36. package/dist/core/runner/gate-exit.d.ts +177 -9
  37. package/dist/core/runner/gate-exit.js +70 -1
  38. package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -7
  39. package/dist/core/runner/prepare-delegation-surface.d.ts +2 -7
  40. package/dist/core/runner/prepare-run-refs.d.ts +12 -0
  41. package/dist/core/runner/prepare-run-refs.js +5 -0
  42. package/dist/core/runner/prepare-task.d.ts +2 -2
  43. package/dist/core/runner/runtask.d.ts +4 -71
  44. package/dist/core/runner/runtask.js +18 -6
  45. package/dist/core/runner-deps.d.ts +1416 -0
  46. package/dist/core/runner-deps.js +1 -0
  47. package/dist/core/runtime-caps.d.ts +164 -0
  48. package/dist/core/runtime-caps.js +1 -0
  49. package/dist/core/task-event.d.ts +910 -0
  50. package/dist/core/task-event.js +1 -0
  51. package/dist/core/task-limits.d.ts +110 -0
  52. package/dist/core/task-limits.js +1 -0
  53. package/dist/core/task-result.d.ts +809 -0
  54. package/dist/core/task-result.js +1 -0
  55. package/dist/core/task-spec.d.ts +1370 -0
  56. package/dist/core/task-spec.js +1 -0
  57. package/dist/core/task-stream.d.ts +382 -0
  58. package/dist/core/task-stream.js +1 -0
  59. package/dist/core/tool-spec.d.ts +1174 -0
  60. package/dist/core/tool-spec.js +1 -0
  61. package/dist/core/types.d.ts +26 -7691
  62. package/dist/core/types.js +2 -76
  63. package/dist/core/warm-resume.d.ts +2 -2
  64. package/dist/index.d.ts +2 -1
  65. package/dist/index.js +1 -1
  66. package/dist/orchestration/goal.d.ts +2 -2
  67. package/dist/orchestration/run-spec.d.ts +2 -2
  68. package/dist/orchestration/run-workflow-tool.d.ts +3 -3
  69. package/dist/orchestration/workflow.d.ts +4 -4
  70. package/dist/scenarios/scenario-registry.d.ts +3 -3
  71. package/dist/scenarios/teacher-quickstart.d.ts +2 -2
  72. package/dist/server/http.d.ts +2 -2
  73. package/dist/stores/file/fs-atomic.d.ts +88 -12
  74. package/dist/stores/file/fs-atomic.js +184 -55
  75. package/dist/stores/file/index.d.ts +1 -0
  76. package/dist/stores/file/index.js +1 -0
  77. package/package.json +1 -1
  78. package/test/export-surface.snapshot.json +9 -1
@@ -1,5 +1,7 @@
1
1
  import { screenGateOutcome, SETTLEMENT_IS_REFUSAL } from "../gate-outcome.js";
2
- import { engineSettlementOf } from "../tool-policy.js";
2
+ import { decisionText, engineSettlementOf } from "../tool-policy.js";
3
+ import { inlineUntrusted } from "../untrusted-text.js";
4
+ import { formatHookFeedback } from "../reminder-mint.js";
3
5
  export function mintGateOutcome(facts) {
4
6
  const settlement = facts.settled !== undefined ? Object.freeze({ ...facts.settled.settlement, who: Object.freeze({ ...facts.settled.settlement.who }) }) : undefined;
5
7
  const outcome = Object.freeze({
@@ -53,3 +55,70 @@ export function createSettlementLedger(call) {
53
55
  },
54
56
  };
55
57
  }
58
+ export function cloneObserverInput(input) {
59
+ try {
60
+ return structuredClone(input);
61
+ }
62
+ catch {
63
+ if (Array.isArray(input))
64
+ return [...input];
65
+ if (input !== null && typeof input === "object")
66
+ return { ...input };
67
+ return input;
68
+ }
69
+ }
70
+ export function traceHookCrash(input, err, notifier) {
71
+ notifier.notify(() => input.onHookError?.(err), "toolGate.onHookError");
72
+ }
73
+ const PARK_FAILURE_CAUSE_MAX = 600;
74
+ function withParkFailureCause(reason, parkFailed) {
75
+ if (parkFailed === undefined)
76
+ return reason;
77
+ return (`${reason} — note: a durable approval park was attempted for this call FIRST and could not be minted ` +
78
+ `(${inlineUntrusted(parkFailed, PARK_FAILURE_CAUSE_MAX)}), so the refusal above is what the fallback had ` +
79
+ `left to say, not the reason the call stopped.`);
80
+ }
81
+ export async function engineFailClosedExit(pass, reason) {
82
+ const { input, toolName, toolCallId, notifyPermissionDenied } = pass;
83
+ const gate = directDeny("hook");
84
+ await notifyPermissionDenied({ toolName, input: cloneObserverInput(pass.currentInput), toolCallId, reason, gate, ...(input.identity !== undefined ? { identity: input.identity } : {}) });
85
+ return { block: true, reason: formatHookFeedback(reason, input.reminderMark), gate, preToolContext: pass.preToolContext };
86
+ }
87
+ export function hookDenyExit(pass, r) {
88
+ const { input, toolName } = pass;
89
+ return {
90
+ block: true,
91
+ reason: formatHookFeedback(decisionText(r) ?? `tool "${toolName}" blocked by a PreToolUse hook`, input.reminderMark),
92
+ gate: directDeny("hook"),
93
+ preToolContext: pass.preToolContext,
94
+ };
95
+ }
96
+ export async function exitGate(pass) {
97
+ const { input, event, toolName, toolCallId, ledger, notifyPermissionDenied } = pass;
98
+ if (pass.decision.action === "deny") {
99
+ const denyReason = withParkFailureCause(decisionText(pass.decision) ?? `tool "${toolName}" denied by policy`, pass.parkFailed);
100
+ if (pass.decision.updatedInput !== undefined) {
101
+ pass.currentInput = pass.decision.updatedInput;
102
+ }
103
+ if (pass.deniedBy === undefined)
104
+ throw new Error(`the tool gate refused "${toolName}" without a refusing layer — every deny site attributes itself`);
105
+ const gate = mintGateOutcome({ deniedBy: pass.deniedBy, ...(ledger.settled !== undefined ? { settled: ledger.settled } : {}) });
106
+ await notifyPermissionDenied({ toolName, input: cloneObserverInput(pass.currentInput), toolCallId, reason: denyReason, gate, ...(input.identity !== undefined ? { identity: input.identity } : {}) });
107
+ return {
108
+ block: true,
109
+ reason: formatHookFeedback(denyReason, input.reminderMark),
110
+ gate,
111
+ preToolContext: pass.preToolContext,
112
+ };
113
+ }
114
+ if (pass.decision.action === "allow") {
115
+ const rw = pass.decision.updatedInput !== undefined ? pass.decision.updatedInput : pass.policyRewrite;
116
+ if (rw !== undefined)
117
+ pass.currentInput = rw;
118
+ }
119
+ return {
120
+ updatedInput: pass.currentInput === event.input ? undefined : pass.currentInput,
121
+ gate: mintGateOutcome(ledger.settled !== undefined ? { settled: ledger.settled } : {}),
122
+ preToolContext: pass.preToolContext,
123
+ };
124
+ }
@@ -22,7 +22,6 @@ import type { AgentHarness, AgentTool, ThinkingLevel } from "../../internal/harn
22
22
  import type { Model } from "../../internal/llm.js";
23
23
  import { forkGovernanceDenial } from "../../agents/subagent.js";
24
24
  import type { PromptEpochArtifact } from "../../prompt-assembly/artifact.js";
25
- import { createRunWorkflowTool } from "../../orchestration/run-workflow-tool.js";
26
25
  import { type WorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
27
26
  import { type AutoModeArmingRecipe } from "../auto-mode-arming.js";
28
27
  import { type AutoModeDecider, type AutoModeDenialTracker } from "../auto-mode.js";
@@ -33,11 +32,8 @@ import type { OnAsk } from "../tool-policy.js";
33
32
  import type { RunnerDeps, RuntimeCaps, TaskEvent, TaskSpec, ToolExecuteContext } from "../types.js";
34
33
  import type { AutoModeArmReason } from "../wiring-manifest.js";
35
34
  import type { ReadFace } from "../../tools/fs/index.js";
36
- import type { InheritedGate, Prepared, RunInternals, ToolFaceSnapshot } from "./contracts.js";
35
+ import type { InheritedGate, Prepared, RunInternals, RunnerSelfSeat, ToolFaceSnapshot } from "./contracts.js";
37
36
  import { type BlockedRef } from "./synthetic-tools.js";
38
- /** The runner the Workflow mount accepts — spelled as the factory's own option type so this phase names no runner
39
- * module of its own (the narrow `RunnerSelfSeat` contract is a design decision still open; see the layering registry). */
40
- type WorkflowMountRunner = Parameters<typeof createRunWorkflowTool>[0]["runner"];
41
37
  export interface PrepareCapsAndWorkflowInput {
42
38
  /** borrowed-readonly — the REBOUND spec, whole: the roster map reads `tools`; the mounts read `enableBlockedReport`,
43
39
  * `enablePlanMode`, `interactiveTools`, `checkpointStore`; the caps/compliance stations read `principal`, `selfOrchestration`,
@@ -113,7 +109,7 @@ export interface PrepareCapsAndWorkflowInput {
113
109
  peerSendMessageBuiltIn: boolean;
114
110
  /** borrowed-readonly — the trusted Runner self-reference the Workflow mount executes children through; undefined when
115
111
  * prepareTask runs standalone (then Workflow is simply not mounted). */
116
- runnerSelf: WorkflowMountRunner | undefined;
112
+ runnerSelf: RunnerSelfSeat | undefined;
117
113
  /** borrowed-readonly — LATE-BOUND: the memory-engine session; the Workflow mount's capture-floor getter reads it at ITS call time. */
118
114
  memoryEngineSession: () => Prepared["memoryEngineSession"];
119
115
  /** borrowed-readonly — LATE-BOUND: the adopted center artifact; the Workflow mount's two center getters read it at THEIR call time. */
@@ -167,4 +163,3 @@ export interface PrepareCapsAndWorkflowResult {
167
163
  }
168
164
  /** The M4 phase body — prepareTask's caps-and-workflow stretch, verbatim (see the module header). */
169
165
  export declare function prepareCapsAndWorkflow(input: PrepareCapsAndWorkflowInput): Promise<PrepareCapsAndWorkflowResult>;
170
- export {};
@@ -1,15 +1,11 @@
1
1
  import type { AgentTool, ExecutionEnv, WorkspaceState } from "../../internal/harness.js";
2
- import { createSendMessageTool } from "../../agents/send-message-tool.js";
3
2
  import { type PeerLaneRefs } from "../../agents/peer-session-drain.js";
4
3
  import type { SubagentRetainLedger } from "../../agents/retain-ledger.js";
5
4
  import { type WorktreeSessionRef } from "../../tools/worktree.js";
6
5
  import type { CwdRef } from "../../tools/fs/index.js";
7
6
  import { type ToolResultStore } from "../tool-result-store.js";
8
7
  import type { RunnerDeps, TaskSpec, ToolEffect, ToolExecuteContext } from "../types.js";
9
- import type { PrepareResume, RunInternals, ToolFaceSnapshot } from "./contracts.js";
10
- /** The runner the SendMessage / AgentTranscript mounts accept — spelled as the factory's own option type so this phase
11
- * names no runner module of its own (the narrow `RunnerSelfSeat` contract is a design decision still open). */
12
- type DelegationMountRunner = Parameters<typeof createSendMessageTool>[0]["runner"];
8
+ import type { PrepareResume, RunInternals, RunnerSelfSeat, ToolFaceSnapshot } from "./contracts.js";
13
9
  export interface PrepareDelegationSurfaceInput {
14
10
  /** borrowed-readonly — the REBOUND spec. Read: `tools` (the shadow checks and the revival spawner's delegation-tool
15
11
  * lookup), `oneShot` (the read/kill faces' completion promises), `retainBackgroundProcesses` (the Monitor receipt),
@@ -36,7 +32,7 @@ export interface PrepareDelegationSurfaceInput {
36
32
  executionEnv: ExecutionEnv;
37
33
  /** borrowed-readonly — the trusted Runner self-reference the SendMessage / AgentTranscript mounts execute through; undefined
38
34
  * when prepareTask runs standalone (then neither mounts). */
39
- runnerSelf: DelegationMountRunner | undefined;
35
+ runnerSelf: RunnerSelfSeat | undefined;
40
36
  /** borrowed-readonly — the background/workflow door's first half: the hands mount decided a real shell with write hands. */
41
37
  backgroundTaskToolsActive: boolean;
42
38
  /** borrowed-readonly — the door's second half: the Workflow tool is on the roster. */
@@ -101,4 +97,3 @@ export interface PrepareDelegationSurfaceResult {
101
97
  }
102
98
  /** The M6 phase body — prepareTask's delegation-surface stretch, verbatim (see the module header). */
103
99
  export declare function prepareDelegationSurface(input: PrepareDelegationSurfaceInput): PrepareDelegationSurfaceResult;
104
- export {};
@@ -86,4 +86,16 @@ export interface PrepareRunRefsResult {
86
86
  worktreeIsolation: SubagentWorktreeIsolation | undefined;
87
87
  }
88
88
  /** The M3a phase body — prepareTask's run-refs stretch, verbatim (see the module header). */
89
+ /**
90
+ * A run that opted into `forwardSubagentEvents` may have BACKGROUND children whose frames are forwarded to the
91
+ * deployment's sink from the child's own run. Those forwards are scheduled on later microtasks than the parent's
92
+ * turn, so "the parent stream ended" and "the child's already-emitted frames reached the sink" are two different
93
+ * moments — the gap is one microtask ladder deep and moves whenever the gate's station structure changes (design/393
94
+ * / #594: the three-layer gate added three async frames to a child's gated call, and a consumer that read the parent
95
+ * to `done` then inspected the sink lost the child's `tool_end`). The rule is not a tick count: **before the parent
96
+ * says `done`, every frame a child had already handed to the forward channel is delivered.** One macrotask turn is
97
+ * the smallest boundary that strictly orders after all pending microtasks; it is taken only on the opted-in run,
98
+ * only once, right before `done`.
99
+ */
100
+ export declare function drainForwardedFramesBeforeDone(spec: Pick<TaskSpec, "forwardSubagentEvents">): Promise<void>;
89
101
  export declare function prepareRunRefs(input: PrepareRunRefsInput): PrepareRunRefsResult;
@@ -2,6 +2,11 @@ import { createSubagentWorktreeHelper } from "../../agents/subagent.js";
2
2
  import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
3
3
  import { ActiveSkillScope } from "./active-skill-scope.js";
4
4
  import { createEditedFilesLedger } from "./edited-files-ledger.js";
5
+ export async function drainForwardedFramesBeforeDone(spec) {
6
+ if (spec.forwardSubagentEvents !== true)
7
+ return;
8
+ await new Promise((resolve) => setImmediate(resolve));
9
+ }
5
10
  export function prepareRunRefs(input) {
6
11
  const { spec, internals, executionEnv, taskRootFinal } = input;
7
12
  const { note: noteFileEdited, snapshot: editedFilesSnapshot } = createEditedFilesLedger();
@@ -7,7 +7,7 @@ export { DEFAULT_IRREVERSIBLE_SCOPE, checkpointScopeOf, placementValueOrAbsent }
7
7
  export { mcpManifestEntries } from "./prepare-wiring-manifest.js";
8
8
  export { __resetMaterializeEnvAnnouncements } from "./prepare-tool-disclosure-mount.js";
9
9
  export { fileHistoryFilesystemIdentity, resolveFileHistoryScope } from "./prepare-file-history.js";
10
- import type { Runner } from "./runtask.js";
10
+ import type { RunnerSelfSeat } from "./contracts.js";
11
11
  import type { Prepared, PrepareResume, RunInternals } from "./contracts.js";
12
12
  export type { FileHistoryBoundarySeat, InheritedGate, Prepared, PreparedMicroCompact, PrepareResume, ResolvedWorkspace, RunInternals, UsageGovernance } from "./contracts.js";
13
13
  import type { ExecutionEnv } from "../../internal/harness.js";
@@ -107,7 +107,7 @@ export declare function prepareTask(spec: TaskSpec, deps: RunnerDeps, sessions:
107
107
  /** design/98 §3.1 (S8c): a TRUSTED self-reference to the Runner, passed by the Runner itself (never a
108
108
  * TaskSpec field) so the `run_workflow` tool can execute child tasks via `runner.runTask`. Undefined when
109
109
  * prepareTask is exercised standalone (then run_workflow is simply not mounted). */
110
- runnerSelf?: Runner,
110
+ runnerSelf?: RunnerSelfSeat,
111
111
  /** #499 — carrier for the id minted below, so a prepare that THROWS still lets its caller name the
112
112
  * run (prepare emits run-scoped notices of its own; each needs a terminal that names it). Same seat
113
113
  * shape the Runner uses for the generated taskId/sessionId. */
@@ -1,6 +1,5 @@
1
1
  import { type Model } from "../../internal/llm.js";
2
- import { type Checkpoint, type CheckpointToken, type PendingSteerEntry, type ReopenReason, type ResumeOutcome } from "../checkpoint-store.js";
3
- import type { RunInternals } from "./prepare-task.js";
2
+ import { type CheckpointToken, type ResumeOutcome } from "../checkpoint-store.js";
4
3
  import { type TaskOutcome } from "../task-outcome.js";
5
4
  import { type SideQuerySpec, type SideQueryResult } from "../side-query.js";
6
5
  import type { SessionStore } from "../session.js";
@@ -8,74 +7,8 @@ import { type RecoveredOrphan } from "../session-reconcile.js";
8
7
  import type { GateOutcome } from "../gate-outcome.js";
9
8
  import { type McpDelivered } from "../mcp-failure.js";
10
9
  import type { AgentDefinition, ModelRef, RunnerDeps, TaskEvent, TaskResult, TaskSpec, TaskStream } from "../types.js";
11
- /**
12
- * Config re-supplied to {@link Runner.resume} (design/45). A suspended task's tools / model / policy /
13
- * hooks cannot be reconstructed from a checkpoint token (the session stores neither tool implementations
14
- * nor the hand band), so the caller's trusted control plane re-supplies the same {@link TaskSpec} it ran
15
- * with — minus the conversation bits: `sessionId` comes from the checkpoint and `objective` is replaced by
16
- * an internally-generated continuation, so both are omitted.
17
- */
18
- export type ResumeTaskConfig = Omit<TaskSpec, "objective" | "sessionId">;
19
- /** design/45 resume plan threaded from {@link Runner.resume} into the shared run loop. */
20
- interface ResumeRun {
21
- cp: Checkpoint;
22
- /**
23
- * RB-152 (2026-07-25, 按面收口): did the APPROVED ACTION actually begin executing?
24
- *
25
- * The reopen compensation used to key on an error-code whitelist (`resume.env_failed` /
26
- * `resume.session_not_found` / `resume.tool_unavailable`). That is a proxy for the thing that actually
27
- * matters, and every time a NEW way to exit without running the action appeared, the whitelist did not
28
- * know about it: a caller's abort landing anywhere between the CAS and `tool.execute` — across
29
- * `SessionStore.acquire` (pluggable, cross-network on a durable backend), the MCP reconnect, the env
30
- * rebuild, `resumeVM`, tool materialization — produced a `failed` result with NO errorCode, no reopen,
31
- * and a human approval consumed for work that never happened. Two rounds of fixes (RB-77, RB-109) each
32
- * moved that window rather than closing it, because both extended the whitelist.
33
- *
34
- * This binds the compensation to the FACT instead: set the instant before `tool.execute` for the gated
35
- * call (the same `onExecuteStart` signal the orphan-reconcile split already trusts). Every terminal —
36
- * throw OR returned result — asks one question: was the checkpoint consumed while this stayed false?
37
- */
38
- pendingActionStarted?: boolean;
39
- /** Validated against `cp.gate.kind` at the resume entry: human/irreversible_ask→`policy_ask`,
40
- * resource_limit→`resource_limit` (design/74), needs_review→`dry_run_review` (design/76 §2.5),
41
- * plan_review→`plan_review` (design/80 D-B). The gate-match guard in `resumeStream` enforces the
42
- * correlation. design/144 §3: `wake` is the NON-GATE arm — only a checkpoint awaiting NO gate
43
- * decision passes the resume entry with it (gate purity, `wake.gate_pending`); the run loop skips
44
- * `applyResumeDecision` for it (no pending action to resolve) and re-enters via the continuation +
45
- * pendingSteer tail alone. */
46
- outcome: Extract<ResumeOutcome, {
47
- gate: "policy_ask" | "resource_limit" | "dry_run_review" | "plan_review" | "wake";
48
- }>;
49
- /** design/144 §3 (X5) — the wake's own operator message, validated (`validatePendingSteer`) at the
50
- * resume entry and carried SEPARATELY from the checkpoint's parked `pendingSteer`: a message-bearing
51
- * wake of a checkpoint that ALSO holds a parked steer must deliver BOTH (park order: parked first,
52
- * wake message second), each under its own trusted framing — the old merge-into-the-slot shape
53
- * silently DISPLACED the parked (undelivered) supervisor steer. Wake outcomes only. */
54
- wakeMessage?: Omit<PendingSteerEntry, "seq">;
55
- /** design/373 §4.3 (D2) — the userPromptSubmit screen's `additionalContext` for {@link wakeMessage},
56
- * captured at the resume ENTRY (the message is screened once, pre-CAS, on the resuming process's
57
- * hook) and delivered by the drain as the engine's own reminder AHEAD of the wake frame — carrying
58
- * it forward is what keeps the hook single-run (re-screening at the drain would be the double-run
59
- * §4.3-3 reserves for the cross-process parked leg). Present only when a wake message passed a
60
- * screen that supplied context. */
61
- wakeMessageHookContext?: string;
62
- /** Compensation hook (design/45/49): called iff the resumed run fails with `resume.env_failed` (post-CAS
63
- * workspace `resumeVM` failed) OR `resume.tool_unavailable` (P-7: the approved tool vanished) — in both
64
- * the CAS already consumed the checkpoint but the pending action never ran. `resumeStream` supplies a
65
- * closure that reopens the checkpoint (`resolved → pending`) so a retry re-resumes the SAME suspended work
66
- * instead of losing it to a forced "re-initiate". design/80 D-1 (reopen-by-reason): the `reason` is
67
- * recorded on the reopened row so the next re-resume validates per reason — an `env_failed` reopen must
68
- * replay the persisted winner (a system retry of the approved action), while a `tool_unavailable` reopen
69
- * lets a human re-decide with the tool present (a fresh decision is allowed — preserves P-7). */
70
- onEnvRestoreFailed?: (reason: ReopenReason) => Promise<void>;
71
- /** RB-471/FR-C1 — set in the run body right after `applyResumeDecision` completes: the negative-
72
- * decision twin of `pendingActionStarted`. A reject/deny consumes its gate BY BEING DELIVERED, and
73
- * this bit is the delivery fact — every throw-arm cause (prepare failure, `session_not_found`, a
74
- * pre-delivery abort) and the walltime-exhausted settle fire BEFORE it is set, so an undelivered
75
- * negative decision still reopens as `env_failed` (the retry replays the persisted decision; it
76
- * never re-asks — design/80 D-1), closing the RB-152/RB-70 loss class the first RB-471 cut reopened. */
77
- decisionDelivered?: boolean;
78
- }
10
+ import type { ResumeRun, ResumeTaskConfig, RunInternals, RunnerSelfSeat } from "./contracts.js";
11
+ export type { ResumeTaskConfig } from "./contracts.js";
79
12
  /** The `tool_end` body fields projected from a harness tool result — output/truncated/totalChars via
80
13
  * {@link toolOutputFrom} and the CC card via {@link structuredFrom}. Single construction point for BOTH
81
14
  * the live loop's frames and the resumed batch's frames (`resolvePendingCall` + the deferred-sibling
@@ -145,7 +78,7 @@ export declare function raceUntilDeadline<T>(p: Promise<T>, deadline: number): P
145
78
  * A stateless task runner. Holds shared deps (the external brain, model catalog) and an
146
79
  * in-memory session store so that passing a `sessionId` continues a prior conversation.
147
80
  */
148
- export declare class Runner {
81
+ export declare class Runner implements RunnerSelfSeat {
149
82
  private deps;
150
83
  readonly sessions: SessionStore;
151
84
  /** Per-sessionId serialization so two tasks never mutate one session concurrently. */
@@ -6,7 +6,7 @@ import { deliverDelegationLifecycle, deliverEngineNotice, undrainedUserInputNoti
6
6
  import { planRejectionClears, resolveTriggerWindow } from "../context-edit.js";
7
7
  import { AgentHarness, DEFAULT_COMPACTION_SETTINGS, isSyntheticApiErrorMessage, uuidv7 } from "../../internal/harness.js";
8
8
  import { snapshotActorAssertion } from "../../internal/llm.js";
9
- import { CheckpointError, BINDING_CHECKPOINT_VERSION, checkpointVersionOf, F012_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, FACE_CHECKPOINT_VERSION, remainingBudgetMicroUsd, readPendingSteerQueue, remainingTokens, LEGACY_PENDING_STEER_INPUT_ID, MAX_STEER_INPUT_ID_CHARS, validatePendingSteer, winnerFromOutcome, realApprovalOrgFact } from "../checkpoint-store.js";
9
+ import { CheckpointError, BINDING_CHECKPOINT_VERSION, checkpointVersionOf, F012_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, FACE_CHECKPOINT_VERSION, remainingBudgetMicroUsd, readPendingSteerQueue, remainingTokens, resolveCheckpointStore, LEGACY_PENDING_STEER_INPUT_ID, MAX_STEER_INPUT_ID_CHARS, validatePendingSteer, winnerFromOutcome, realApprovalOrgFact } from "../checkpoint-store.js";
10
10
  import { GIT_STATUS_ECHO_PREVIEW, branchCarriesVisiblePositiveGitFrame, newestEngineGitFrame, stripGitStatusUnits } from "./git-status-frame.js";
11
11
  import { gitFrameContextVisible, normalizeGitAnnouncement } from "../../internal/harness.js";
12
12
  import { engineVersion } from "../version.js";
@@ -38,7 +38,10 @@ import { assembleResult, errorCodeOf } from "./assemble-result.js";
38
38
  import { amendTerminal, terminalProjection } from "./terminal-projection.js";
39
39
  import { ATTACHMENT_BYTE_CAP, CHANGED_FILES_MAX, TOTAL_TOKENS_REMINDER_DEFAULT_MODE, AGENT_LISTING_REMOVED_HEADER, SKILLS_LISTING_DELTA_HEADER, SKILLS_LISTING_REMOVED_HEADER, advanceCadenceClock, agentListingDeltaHeader, attachmentEnvelopeTags, agentListingInitialHeader, replayAnnouncedListing, replayAnnouncedModels, clipToBytes, collectDateChange, collectDueAttachments, collectInstructionsChange, commitAgentListing, commitInstructionsChange, commitSkillsListing, createAttachmentState, rebaseCadenceWindows, reduceToolEnd, renderAgentListingDelta, renderMcpDroppedTools, renderMcpInstructionsDelta, renderOrphanedBackgroundTasks, selectMcpDroppedBatch, renderSkillsListingDelta, renderToolsDelta, stampWriteAnchor } from "./turn-attachments.js";
40
40
  import { buildWorkingFileAttachments, centerAdoptionOption, contextInstructionFilesOption, emitInputTruncated, forkContextOption } from "./compaction-call-options.js";
41
- import { effectiveDelegationFacts, gatedCallIdOf, placementValueOrAbsent, prepareTask, resolveCheckpointStore } from "./prepare-task.js";
41
+ import { effectiveDelegationFacts, prepareTask } from "./prepare-task.js";
42
+ import { drainForwardedFramesBeforeDone } from "./prepare-run-refs.js";
43
+ import { gatedCallIdOf } from "./park-commit.js";
44
+ import { placementValueOrAbsent } from "./checkpoint-scope.js";
42
45
  import { SessionReadFileStates } from "./prepare-hands-readface.js";
43
46
  import { settleTeardownLeg } from "./teardown-bounded.js";
44
47
  import { TOOL_SEARCH_NAME } from "./tool-disclosure.js";
@@ -1480,6 +1483,11 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
1480
1483
  };
1481
1484
  return { onMessageUpdate, onMessageEnd, onToolStart, onToolEnd, onTurnEnd, announceWorkspaceMove };
1482
1485
  }
1486
+ const REFUSED_DECIDE_KEYS = [
1487
+ ["gateOutcome", "the settlement record is minted by core from the host's decision facts and the row's origin, never supplied"],
1488
+ ["settledBy", 'a retired key — the host supplies decision FACTS as hostDecision{decidedBy: "person" | "sla_timeout", approver?} and core mints the settlement word from them'],
1489
+ ["approver", "a retired key — the attribution rides the facts it belongs to, as hostDecision.approver"],
1490
+ ];
1483
1491
  export class Runner {
1484
1492
  deps;
1485
1493
  sessions;
@@ -1830,6 +1838,7 @@ export class Runner {
1830
1838
  onError: (f) => console.warn(`[sema-core] ${f.site}: delegation-lifecycle observer threw (contained; further failures counted, not re-disclosed): ${f.error.message}`),
1831
1839
  }), "runtask.onDelegationLifecycle");
1832
1840
  }
1841
+ await drainForwardedFramesBeforeDone(spec);
1833
1842
  queue.push({ type: "done", result: resultValue });
1834
1843
  queue.close();
1835
1844
  });
@@ -4269,6 +4278,7 @@ export class Runner {
4269
4278
  drainManualCompact("mooted");
4270
4279
  }
4271
4280
  manualCompactRef.emitMooted = undefined;
4281
+ await drainForwardedFramesBeforeDone(spec);
4272
4282
  queue.push({ type: "done", result });
4273
4283
  queue.close();
4274
4284
  await prepared.fileHistoryBoundary?.settle();
@@ -4599,6 +4609,11 @@ export class Runner {
4599
4609
  if (decision !== "allow" && decision !== "deny") {
4600
4610
  throw new CheckpointError("checkpoint.invalid_outcome", `resume decision "${describeSuppliedValue(decision)}" is outside the policy_ask domain — a decide is exactly "allow" or "deny"; refusing pre-CAS, the checkpoint stays pending`);
4601
4611
  }
4612
+ for (const [key, why] of REFUSED_DECIDE_KEYS) {
4613
+ if (Reflect.get(decide, key) !== undefined) {
4614
+ throw new CheckpointError("checkpoint.invalid_outcome", `resume carries \`${key}\` — ${why}; refusing pre-CAS, the checkpoint stays pending`, { field: "hostDecision" });
4615
+ }
4616
+ }
4602
4617
  const hostDecisionRaw = decide.hostDecision;
4603
4618
  if (typeof hostDecisionRaw !== "object" || hostDecisionRaw === null) {
4604
4619
  throw new CheckpointError("checkpoint.invalid_outcome", `resume carries no hostDecision — a policy_ask decide names who decided it ({ decidedBy: "person" | "sla_timeout", approver? }); refusing pre-CAS, the checkpoint stays pending`, { field: "hostDecision" });
@@ -4610,9 +4625,6 @@ export class Runner {
4610
4625
  if (decidedBy === "sla_timeout" && decision === "allow") {
4611
4626
  throw new CheckpointError("checkpoint.invalid_outcome", `resume carries decision "allow" decided by "sla_timeout" — an elapsed SLA window cannot be what approved an action that EXECUTES; refusing pre-CAS, the checkpoint stays pending`, { field: "hostDecision" });
4612
4627
  }
4613
- if (decide.gateOutcome !== undefined) {
4614
- throw new CheckpointError("checkpoint.invalid_outcome", "resume carries a gateOutcome — the settlement record is minted by core from the host's decision facts and the row's origin, never supplied; refusing pre-CAS, the checkpoint stays pending", { field: "hostDecision" });
4615
- }
4616
4628
  const attribution = screenApproverAttribution(hostDecisionRaw.approver);
4617
4629
  if (attribution.defect !== undefined) {
4618
4630
  throw new CheckpointError("checkpoint.invalid_outcome", `resume carries an approver attribution this engine refuses: ${attribution.defect}; refusing pre-CAS, the checkpoint stays pending`, { field: "approver" });
@@ -4974,7 +4986,7 @@ export class Runner {
4974
4986
  if (plainPolicyOutcome !== undefined) {
4975
4987
  const parkedOrigin = cp.pendingAction.kind === "tool_approval" ? cp.pendingAction.origin : undefined;
4976
4988
  if (!isAskOrigin(parkedOrigin)) {
4977
- throw new CheckpointError("checkpoint.unsupported_version", "this checkpoint's pending approval carries no origin word (a row minted before the ask's origin was persisted on the park) — its settlement cannot be recorded; the checkpoint stays pending");
4989
+ throw new CheckpointError("checkpoint.unsupported_version", "this checkpoint's pending approval carries no origin word (a row minted before the ask's origin was persisted on the park) — its settlement cannot be recorded; the checkpoint stays pending", { reason: "origin_missing" });
4978
4990
  }
4979
4991
  const facts = plainPolicyOutcome.hostDecision;
4980
4992
  const approverCell = facts.approver !== undefined ? { approver: facts.approver } : {};