@sema-agent/core 7.5.1 → 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 (132) hide show
  1. package/CHANGELOG.md +61 -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/protocol-table.d.ts +5 -0
  31. package/dist/core/protocol-table.js +1 -0
  32. package/dist/core/runner/abort-race.d.ts +41 -0
  33. package/dist/core/runner/abort-race.js +38 -0
  34. package/dist/core/runner/assemble-result.d.ts +32 -41
  35. package/dist/core/runner/assemble-result.js +55 -74
  36. package/dist/core/runner/checkpoint-scope.d.ts +15 -3
  37. package/dist/core/runner/checkpoint-scope.js +3 -0
  38. package/dist/core/runner/compaction-call-options.d.ts +1 -1
  39. package/dist/core/runner/content-ask-bindings.d.ts +27 -0
  40. package/dist/core/runner/content-ask-bindings.js +1 -0
  41. package/dist/core/runner/contracts.d.ts +144 -50
  42. package/dist/core/runner/denial-limit-arms.d.ts +24 -1
  43. package/dist/core/runner/denial-limit-arms.js +24 -3
  44. package/dist/core/runner/gate-exit.d.ts +74 -0
  45. package/dist/core/runner/gate-exit.js +55 -0
  46. package/dist/core/runner/inherited-ask-grants.d.ts +46 -0
  47. package/dist/core/runner/inherited-ask-grants.js +29 -0
  48. package/dist/core/runner/park-commit.d.ts +102 -0
  49. package/dist/core/runner/park-commit.js +31 -0
  50. package/dist/core/runner/{prepare-permission-rules.d.ts → permission-rule-lanes.d.ts} +109 -3
  51. package/dist/core/runner/{prepare-permission-rules.js → permission-rule-lanes.js} +47 -1
  52. package/dist/core/runner/prepare-ask-lane.d.ts +107 -0
  53. package/dist/core/runner/prepare-ask-lane.js +131 -0
  54. package/dist/core/runner/prepare-boundary-parks.d.ts +102 -0
  55. package/dist/core/runner/prepare-boundary-parks.js +169 -0
  56. package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
  57. package/dist/core/runner/prepare-context-lane.d.ts +119 -0
  58. package/dist/core/runner/prepare-context-lane.js +230 -0
  59. package/dist/core/runner/prepare-gate-stations.d.ts +174 -0
  60. package/dist/core/runner/prepare-gate-stations.js +265 -0
  61. package/dist/core/runner/prepare-hands-readface.d.ts +4 -4
  62. package/dist/core/runner/prepare-inherited-gate.d.ts +4 -4
  63. package/dist/core/runner/prepare-inherited-gate.js +1 -1
  64. package/dist/core/runner/prepare-memory-engine-session.d.ts +84 -0
  65. package/dist/core/runner/prepare-memory-engine-session.js +233 -0
  66. package/dist/core/runner/prepare-memory.d.ts +44 -26
  67. package/dist/core/runner/prepare-park-ask.d.ts +162 -0
  68. package/dist/core/runner/prepare-park-ask.js +377 -0
  69. package/dist/core/runner/prepare-policy-chain.d.ts +208 -0
  70. package/dist/core/runner/prepare-policy-chain.js +584 -0
  71. package/dist/core/runner/prepare-project-context.d.ts +1 -13
  72. package/dist/core/runner/prepare-project-context.js +1 -3
  73. package/dist/core/runner/prepare-prompt-assembly.d.ts +95 -0
  74. package/dist/core/runner/prepare-prompt-assembly.js +162 -0
  75. package/dist/core/runner/prepare-prompt-inputs.d.ts +1 -20
  76. package/dist/core/runner/prepare-protocol-tools.d.ts +3 -3
  77. package/dist/core/runner/prepare-protocol-tools.js +0 -3
  78. package/dist/core/runner/prepare-question-face.d.ts +3 -21
  79. package/dist/core/runner/prepare-question-face.js +2 -1
  80. package/dist/core/runner/prepare-safety-scan.d.ts +0 -5
  81. package/dist/core/runner/prepare-safety-scan.js +1 -2
  82. package/dist/core/runner/prepare-suspend-saga.d.ts +170 -0
  83. package/dist/core/runner/prepare-suspend-saga.js +308 -0
  84. package/dist/core/runner/prepare-task.d.ts +9 -136
  85. package/dist/core/runner/prepare-task.js +47 -2745
  86. package/dist/core/runner/prepare-turn-wiring.d.ts +154 -0
  87. package/dist/core/runner/prepare-turn-wiring.js +201 -0
  88. package/dist/core/runner/prepare-wiring-manifest.d.ts +17 -17
  89. package/dist/core/runner/prepare-wiring-manifest.js +16 -10
  90. package/dist/core/runner/prepare-workspace-restore.d.ts +2 -29
  91. package/dist/core/runner/prepare-workspace-restore.js +3 -16
  92. package/dist/core/runner/prompt-hash-salt.d.ts +1 -0
  93. package/dist/core/runner/prompt-hash-salt.js +2 -0
  94. package/dist/core/runner/remote-env-retry.d.ts +29 -0
  95. package/dist/core/runner/remote-env-retry.js +16 -0
  96. package/dist/core/runner/runtask.d.ts +16 -31
  97. package/dist/core/runner/runtask.js +110 -121
  98. package/dist/core/runner/terminal-projection.d.ts +22 -0
  99. package/dist/core/runner/terminal-projection.js +28 -0
  100. package/dist/core/session.d.ts +12 -0
  101. package/dist/core/session.js +3 -0
  102. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -1
  103. package/dist/core/store-contracts/checkpoint-store-contract.js +8 -2
  104. package/dist/core/terminal-cause.d.ts +137 -0
  105. package/dist/core/terminal-cause.js +9 -0
  106. package/dist/core/tool-policy.d.ts +43 -139
  107. package/dist/core/tool-policy.js +79 -112
  108. package/dist/core/types.d.ts +67 -164
  109. package/dist/core/wiring-manifest.d.ts +6 -3
  110. package/dist/core/workflow-journal-store.js +3 -4
  111. package/dist/engine/harness/agent-harness.d.ts +1 -1
  112. package/dist/index.d.ts +10 -7
  113. package/dist/index.js +8 -5
  114. package/dist/orchestration/builtin-workflows.d.ts +2 -2
  115. package/dist/orchestration/builtin-workflows.js +1 -1
  116. package/dist/orchestration/goal.js +8 -7
  117. package/dist/orchestration/run-spec.js +5 -3
  118. package/dist/orchestration/run-workflow-tool.d.ts +1 -1
  119. package/dist/orchestration/run-workflow-tool.js +4 -4
  120. package/dist/orchestration/workflow-governance.d.ts +4 -4
  121. package/dist/orchestration/workflow-governance.js +4 -2
  122. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  123. package/dist/orchestration/workflow-primitives.js +1 -1
  124. package/dist/orchestration/workflow.d.ts +11 -0
  125. package/dist/orchestration/workflow.js +64 -39
  126. package/dist/prompts/supervisor.d.ts +1 -1
  127. package/dist/prompts/supervisor.js +3 -3
  128. package/dist/scenarios/scenario-registry.js +1 -1
  129. package/package.json +3 -1
  130. package/test/export-surface.snapshot.json +74 -22
  131. /package/dist/core/runner/{prepare-announce-once.d.ts → announce-once-ledger.d.ts} +0 -0
  132. /package/dist/core/runner/{prepare-announce-once.js → announce-once-ledger.js} +0 -0
@@ -0,0 +1,154 @@
1
+ import type { AgentHarness, AgentTool, ExecutionEnv, ThinkingLevel } from "../../internal/harness.js";
2
+ import type { Model } from "../../internal/llm.js";
3
+ import { type AssembledPrompt, type AssembleInputs } from "../../prompt-assembly/assemble.js";
4
+ import { type PromptEpochArtifact } from "../../prompt-assembly/artifact.js";
5
+ import type { EpochDeclaredSections } from "../../prompt-assembly/epoch.js";
6
+ import { type TurnPromptSnapshot } from "../../prompt-assembly/turn-snapshot.js";
7
+ import type { PromptTextDeclaration } from "../../prompts/default.js";
8
+ import { CacheBreakDetector, type ToolFingerprintInput } from "../cache-break-detector.js";
9
+ import { type ToolPolicy } from "../tool-policy.js";
10
+ import type { RunnerDeps, TaskSpec } from "../types.js";
11
+ import type { CwdRef } from "../../tools/fs/fs-shared.js";
12
+ import { type ReadFace } from "../../tools/fs/index.js";
13
+ import type { ReadDenyMatcher } from "../../tools/fs/read-deny.js";
14
+ import { type ReadFileState } from "../../tools/fs/safety.js";
15
+ import type { Prepared } from "./contracts.js";
16
+ export interface PrepareTurnWiringInput {
17
+ /** borrowed-readonly — the deployment seats: `cacheBreakDetection` (the detector knob), `onError` (the attachment-skip
18
+ * degraded line), `promptSource` (the center candidate's state and current artifact). Receiver preserved. */
19
+ deps: Pick<RunnerDeps, "cacheBreakDetection" | "onError" | "promptSource">;
20
+ /** borrowed-mutable — the LIVE system prompt (the prompt-assembly phase's accessor cell). Read: the fingerprint, the
21
+ * snapshot's stable text, the overhead term. Writer here: the center candidate's `apply` (the boundary swap). */
22
+ systemPromptSeat: {
23
+ current: string;
24
+ };
25
+ /** borrowed-readonly — the FINAL wire tool list (the fingerprint's and the snapshot's tool wire, the overhead sum). */
26
+ harnessTools: ReadonlyArray<AgentTool>;
27
+ /** borrowed-mutable — the fingerprint ref the tool-disclosure phase minted. Writers: THIS phase seeds `current`; the
28
+ * candidate's `apply` rewrites `current.systemPrompt`; the disclosure phase's rebuild seam refreshes `current.tools`. */
29
+ fpRef: {
30
+ current?: {
31
+ systemPrompt: string;
32
+ tools: ToolFingerprintInput[];
33
+ };
34
+ };
35
+ /** borrowed-readonly — the task model: `provider` / `id` / `maxTokens` / `api` (the snapshot's identity elements). */
36
+ model: Pick<Model, "provider" | "id" | "maxTokens" | "api">;
37
+ /** borrowed-readonly — the resolved epoch's artifact digest (the snapshot's identity element). */
38
+ epochArtifactDigestForSnapshot: string;
39
+ /** borrowed-readonly — the resolved prompt profile (a snapshot element). */
40
+ promptProfile: "simple" | "classic";
41
+ /** borrowed-readonly — the fable mitigations switch (a snapshot element). */
42
+ fableMitigations: boolean;
43
+ /** borrowed-readonly — the assembled prompt's physical block face, or undefined (the snapshot's layout groups). */
44
+ systemBlocks: AssembledPrompt["systemBlocks"];
45
+ /** borrowed-readonly — the resolved thinking level, or undefined (a snapshot element). */
46
+ thinking: ThinkingLevel | undefined;
47
+ /** borrowed-readonly — the REBOUND spec; only `outputSchema` (presence) and `taskId` (the snapshot's identity). */
48
+ spec: Pick<TaskSpec, "outputSchema" | "taskId">;
49
+ /** borrowed-readonly — the acquired session id (the snapshot; the attachment-skip attribution). */
50
+ sessionId: string;
51
+ /** borrowed-mutable — the snapshot ref the tool-disclosure phase minted. Writers: THIS phase seeds `current`; the candidate's
52
+ * `apply` calls `refreshStableSystem`; the disclosure phase's rebuild seam calls `refreshTools`; the run loop assigns
53
+ * `current.onChange` (the snapshot-changed trace emitter) once Prepared is built. */
54
+ turnSnapshotRef: {
55
+ current?: TurnPromptSnapshot;
56
+ };
57
+ /** borrowed-mutable — the composition manifest (`Prepared.promptManifest`). Writer here: the identity/lowering BACKFILL
58
+ * (the snapshot needs the final wire list, which exists only now). */
59
+ promptManifest: Prepared["promptManifest"];
60
+ /** borrowed-readonly — the model's structural coefficient (the context lane's), the overhead term's coordinate. */
61
+ charsPerToken: number;
62
+ /** borrowed-readonly — whether an execution env is wired (every reader exists only then). */
63
+ handsEnabled: boolean;
64
+ /** borrowed-readonly — the run's resolved env (the readers read and stat through it). */
65
+ executionEnv: ExecutionEnv;
66
+ /** borrowed-readonly — the canonical attachment root, or undefined (the readers' containment fence). */
67
+ attachmentRootCanonical: string | undefined;
68
+ /** borrowed-readonly — the canonical extra write roots (the fence's root set). */
69
+ additionalRootsCanonical: ReadonlyArray<string>;
70
+ /** borrowed-readonly — the canonical extra read roots (the fence's root set). */
71
+ additionalReadRootsCanonical: ReadonlyArray<string>;
72
+ /** borrowed-readonly — the compiled read-deny judge, or undefined (the reader's withheld arm). */
73
+ readDenyMatcher: ReadDenyMatcher | undefined;
74
+ /** borrowed-readonly — the resolved read face, or undefined (the fence's ninth argument). */
75
+ resolvedReadFace: ReadFace | undefined;
76
+ /** borrowed-readonly — the tracked-cwd seat, read per call by the kept-tail normalizer (never written here). */
77
+ handsCwdRef: CwdRef | undefined;
78
+ /** borrowed-mutable — the live read-file state, or undefined. Writers here, at RUN time: `detectExternalChanges` evicts a
79
+ * not-found entry; `onCompactionApplied` applies a landed compaction to it. The hands phase mints it. */
80
+ readFileStateForCheckpoint: ReadFileState | undefined;
81
+ /** borrowed-readonly — the deny-narrowing fold, or undefined (the changed-files deny door's authority). */
82
+ denyNarrowingPolicy: ToolPolicy | undefined;
83
+ /** borrowed-readonly — the run's abort controller; only `signal` (the deny door's and the stat's signal). */
84
+ abortController: Pick<AbortController, "signal">;
85
+ /** borrowed-readonly — the per-task identity (the background listing's owner). */
86
+ hostTaskId: string;
87
+ /** borrowed-readonly — the registry scope the background listing reads under. */
88
+ taskScope: string;
89
+ /** borrowed-readonly — the prompt-assembly phase's re-invokable assemble-inputs builder (the candidate re-assembles). */
90
+ buildAssembleInputs: (centerDecls?: PromptTextDeclaration[]) => AssembleInputs;
91
+ /** borrowed-mutable — the LIVE center adoption (the prompt-assembly phase's accessor cell). Read: the candidate's arming and
92
+ * same-artifact test. Writer here: the candidate's `apply` (the boundary swap / the explicit-disable rollback). */
93
+ centerAdoptionRef: {
94
+ current: {
95
+ artifact: PromptEpochArtifact;
96
+ sourceRevision?: string;
97
+ } | undefined;
98
+ };
99
+ /** borrowed-readonly — the harness ref (the candidate's `apply` calls `setSystemPrompt` on the current harness). */
100
+ harnessRef: {
101
+ current?: AgentHarness;
102
+ };
103
+ /** borrowed-readonly — the provider-declared sections in the epoch shape (the candidate's declared-sections TYPE). */
104
+ epochDeclaredSections: EpochDeclaredSections;
105
+ /** borrowed-readonly — the ONE carrier-face expression both delegation mounts consume (the observation seat). */
106
+ carrierReadFace: () => ReadFace | undefined;
107
+ /** borrowed-readonly — the normalized deny additions (the observation seat copies them). */
108
+ readDenyAdditionsNormalized: ReadonlyArray<{
109
+ pattern: string;
110
+ caseSensitive: boolean;
111
+ }>;
112
+ /** borrowed-mutable — the holder of the built Prepared. Writer: the DRIVER fills `current` right after `buildPrepared()`;
113
+ * the candidate's `apply` then writes `current.epochDeclaredSections` and `current.promptOverheadTokens` at a boundary. */
114
+ preparedHolder: {
115
+ current?: Prepared;
116
+ };
117
+ }
118
+ export interface PrepareTurnWiringResult {
119
+ /** owned — the two-phase cache-break detector (`Prepared.cacheBreakDetector`), or undefined when the knob is off. */
120
+ cacheBreakDetector: CacheBreakDetector | undefined;
121
+ /** borrowed-mutable — the prefix fingerprint (`Prepared.cacheFingerprint`; the same object `fpRef.current` holds), or
122
+ * undefined. Writers after this phase: the candidate's `apply` (systemPrompt), the disclosure rebuild seam (tools). */
123
+ cacheFingerprint: {
124
+ systemPrompt: string;
125
+ tools: ToolFingerprintInput[];
126
+ } | undefined;
127
+ /** owned — the fixed prompt-overhead term in the compaction trigger's coordinate (`Prepared.promptOverheadTokens`). */
128
+ promptOverheadTokens: number;
129
+ /** owned — the fenced working-file reader, or undefined without hands (`Prepared.readTaskFile`). */
130
+ readTaskFile: Prepared["readTaskFile"];
131
+ /** owned — the kept-tail path normalizer, or undefined without hands. */
132
+ normalizeAttachmentPath: Prepared["normalizeAttachmentPath"];
133
+ /** owned — the Read tool's dedup-stub predicate, or undefined without hands. */
134
+ isDedupStubResult: Prepared["isDedupStubResult"];
135
+ /** owned — the most-recently-read files projection, or undefined without read state. */
136
+ recentlyReadFiles: Prepared["recentlyReadFiles"];
137
+ /** owned — the landed-compaction read-state hook, or undefined without read state. */
138
+ onCompactionApplied: Prepared["onCompactionApplied"];
139
+ /** owned — the boundary-time changed-files scan, or undefined without read state. */
140
+ detectExternalChanges: Prepared["detectExternalChanges"];
141
+ /** owned — the post-compact background-task listing (`Prepared.listBackgroundTasks`). */
142
+ listBackgroundTasks: Prepared["listBackgroundTasks"];
143
+ /** owned — the compaction-boundary center adoption candidate, or undefined (no source, or no adoption). */
144
+ centerCompactionCandidate: Prepared["centerCompactionCandidate"];
145
+ /** owned — the effective read face observed for the result seats (`Prepared.effectiveReadFace`), or undefined. */
146
+ effectiveReadFaceObserved: ReadFace | undefined;
147
+ /** owned — the effective read-deny patterns observed (fresh copies), or undefined when none. */
148
+ effectiveReadDenyObserved: Array<{
149
+ pattern: string;
150
+ caseSensitive: boolean;
151
+ }> | undefined;
152
+ }
153
+ /** The M22 phase body — prepareTask's turn-wiring stretch, verbatim (see the module header). */
154
+ export declare function prepareTurnWiring(input: PrepareTurnWiringInput): PrepareTurnWiringResult;
@@ -0,0 +1,201 @@
1
+ import { createHash } from "node:crypto";
2
+ import { assemblePrompt } from "../../prompt-assembly/assemble.js";
3
+ import { artifactDeclarations } from "../../prompt-assembly/artifact.js";
4
+ import { buildTurnPromptSnapshot } from "../../prompt-assembly/turn-snapshot.js";
5
+ import { CacheBreakDetector, toolsToFingerprintInputs } from "../cache-break-detector.js";
6
+ import { defaultTaskRegistry } from "../task-registry.js";
7
+ import { refuseOutOfContractDecision } from "../tool-policy.js";
8
+ import { decodeTextBytes } from "../../tools/fs/encoding.js";
9
+ import { applyCompactionToReadFileState, isReadDedupStubResult } from "../../tools/fs/index.js";
10
+ import { resolveKey } from "../../tools/fs/safety.js";
11
+ import { PROMPT_HASH_SALT } from "./prompt-hash-salt.js";
12
+ import { CHANGED_FILES_MTIME_EPS_MS } from "./turn-attachments.js";
13
+ export function prepareTurnWiring(input) {
14
+ const { deps, systemPromptSeat, harnessTools, fpRef, model, epochArtifactDigestForSnapshot, promptProfile, fableMitigations, systemBlocks, thinking, spec, sessionId, turnSnapshotRef, promptManifest, charsPerToken, handsEnabled, executionEnv, attachmentRootCanonical, additionalRootsCanonical, additionalReadRootsCanonical, readDenyMatcher, resolvedReadFace, handsCwdRef, readFileStateForCheckpoint, denyNarrowingPolicy, abortController, hostTaskId, taskScope, buildAssembleInputs, centerAdoptionRef, harnessRef, epochDeclaredSections, carrierReadFace, readDenyAdditionsNormalized, preparedHolder } = input;
15
+ const cacheBreakDetector = deps.cacheBreakDetection === false ? undefined : new CacheBreakDetector();
16
+ const cacheFingerprint = cacheBreakDetector
17
+ ? { systemPrompt: systemPromptSeat.current, tools: toolsToFingerprintInputs(harnessTools) }
18
+ : undefined;
19
+ fpRef.current = cacheFingerprint;
20
+ const turnSnapshot = buildTurnPromptSnapshot({
21
+ provider: model.provider,
22
+ modelId: model.id,
23
+ artifactDigest: epochArtifactDigestForSnapshot,
24
+ promptProfile,
25
+ fableMitigations,
26
+ layoutGroups: (systemBlocks ?? [{ groupId: "body", cacheControlBoundary: true }]).map((b) => ({ groupId: b.groupId, cacheControlBoundary: b.cacheControlBoundary })),
27
+ stableSystemText: systemPromptSeat.current,
28
+ toolWire: toolsToFingerprintInputs(harnessTools),
29
+ thinkingLevel: (thinking ?? "off"),
30
+ maxTokens: model.maxTokens,
31
+ hasOutputSchema: spec.outputSchema !== undefined,
32
+ sessionId,
33
+ taskId: spec.taskId,
34
+ }, model.api, (text) => `salted:${createHash("sha256").update(PROMPT_HASH_SALT).update(text).digest("hex")}`);
35
+ turnSnapshotRef.current = turnSnapshot;
36
+ promptManifest.snapshot = { cacheIdentity: turnSnapshot.cacheIdentity, elements: { ...turnSnapshot.elements } };
37
+ promptManifest.lowering = turnSnapshot.lowering;
38
+ const promptOverheadTokens = Math.ceil((systemPromptSeat.current.length +
39
+ harnessTools.reduce((a, t) => a + t.name.length + (t.description?.length ?? 0) + JSON.stringify(t.parameters ?? {}).length, 0)) / charsPerToken);
40
+ const announceAttachmentSkip = (path, reason) => {
41
+ deps.onError?.(new Error(`working-file attachment skipped — ${path}: ${reason}`), {
42
+ phase: "degraded",
43
+ sessionId,
44
+ classification: "attachment-read",
45
+ });
46
+ };
47
+ const readTaskFile = handsEnabled
48
+ ? async (path) => {
49
+ try {
50
+ let key = path;
51
+ if (attachmentRootCanonical !== undefined) {
52
+ const rk = await resolveKey(executionEnv, attachmentRootCanonical, path, undefined, undefined, [...additionalRootsCanonical, ...additionalReadRootsCanonical], undefined, readDenyMatcher, resolvedReadFace);
53
+ if (!rk.ok) {
54
+ announceAttachmentSkip(path, rk.violation.message);
55
+ if (rk.violation.code === "read_path_denied" && rk.violation.pattern !== undefined) {
56
+ return { withheld: { pattern: rk.violation.pattern } };
57
+ }
58
+ return null;
59
+ }
60
+ key = rk.key;
61
+ }
62
+ const r = await executionEnv.readBinaryFile(key);
63
+ if (!r.ok) {
64
+ announceAttachmentSkip(path, r.error.message);
65
+ return null;
66
+ }
67
+ const d = decodeTextBytes(r.value);
68
+ if (d.malformed) {
69
+ announceAttachmentSkip(path, "malformed text encoding (truncated UTF-16 body)");
70
+ return null;
71
+ }
72
+ return d.text;
73
+ }
74
+ catch (err) {
75
+ announceAttachmentSkip(path, err instanceof Error ? err.message : String(err));
76
+ return null;
77
+ }
78
+ }
79
+ : undefined;
80
+ const normalizeAttachmentPath = handsEnabled
81
+ ? async (path) => {
82
+ if (attachmentRootCanonical === undefined)
83
+ return path;
84
+ try {
85
+ const rk = await resolveKey(executionEnv, attachmentRootCanonical, path, undefined, handsCwdRef?.current, [...additionalRootsCanonical, ...additionalReadRootsCanonical], undefined, undefined, resolvedReadFace);
86
+ return rk.ok ? rk.key : path;
87
+ }
88
+ catch {
89
+ return path;
90
+ }
91
+ }
92
+ : undefined;
93
+ const isDedupStubResult = handsEnabled ? isReadDedupStubResult : undefined;
94
+ const recentlyReadFiles = handsEnabled && readFileStateForCheckpoint
95
+ ? () => [...readFileStateForCheckpoint.entries()]
96
+ .filter(([, v]) => v.seededFromContext !== true)
97
+ .sort((a, b) => (b[1].lastReadAt ?? 0) - (a[1].lastReadAt ?? 0))
98
+ .map(([path]) => path)
99
+ : undefined;
100
+ const onCompactionApplied = handsEnabled && readFileStateForCheckpoint
101
+ ? (attachedComplete, preserveReadState) => applyCompactionToReadFileState(readFileStateForCheckpoint, attachedComplete, preserveReadState)
102
+ : undefined;
103
+ const changedFilesReadDenied = denyNarrowingPolicy === undefined
104
+ ? undefined
105
+ : async (path) => {
106
+ try {
107
+ const d = refuseOutOfContractDecision(await denyNarrowingPolicy.check({ toolName: "Read", args: { file_path: path }, toolCallId: "changed-files-visibility-probe" }, abortController.signal));
108
+ return d.action === "deny";
109
+ }
110
+ catch {
111
+ return false;
112
+ }
113
+ };
114
+ const detectExternalChanges = handsEnabled && readFileStateForCheckpoint
115
+ ? async (maxFiles) => {
116
+ const candidates = [...readFileStateForCheckpoint.entries()]
117
+ .filter(([, e]) => e.lastReadAt !== undefined)
118
+ .filter(([, e]) => e.truncated !== true)
119
+ .sort((a, b) => (b[1].lastReadAt ?? 0) - (a[1].lastReadAt ?? 0))
120
+ .slice(0, Math.max(0, maxFiles));
121
+ const changed = [];
122
+ const evicted = [];
123
+ for (const [path, entry] of candidates) {
124
+ if (changedFilesReadDenied !== undefined && (await changedFilesReadDenied(path)))
125
+ continue;
126
+ try {
127
+ const info = await executionEnv.fileInfo(path, abortController.signal);
128
+ if (!info.ok) {
129
+ if (info.error.code === "not_found") {
130
+ readFileStateForCheckpoint.delete(path);
131
+ evicted.push(path);
132
+ }
133
+ continue;
134
+ }
135
+ if (info.value.mtimeMs > (entry.lastReadAt ?? 0) + CHANGED_FILES_MTIME_EPS_MS) {
136
+ changed.push({ path, mtimeMs: info.value.mtimeMs });
137
+ }
138
+ }
139
+ catch {
140
+ }
141
+ }
142
+ return { changed, evicted };
143
+ }
144
+ : undefined;
145
+ const listBackgroundTasks = () => defaultTaskRegistry
146
+ .list({ owner: hostTaskId, scope: taskScope, sessionId })
147
+ .filter((t) => t.status === "pending" || t.status === "running" || t.status === "parked")
148
+ .map((t) => ({ id: t.task_id, ...(t.description !== undefined ? { description: t.description } : {}), status: t.status }));
149
+ const overheadState = { promptChars: 0 };
150
+ const centerCompactionCandidate = deps.promptSource !== undefined && centerAdoptionRef.current !== undefined
151
+ ? () => {
152
+ const state = deps.promptSource.sourceState?.() ?? "unavailable";
153
+ const cand = deps.promptSource.current();
154
+ const toBundled = state === "disabled";
155
+ if (!toBundled && (cand === undefined || cand.artifact.artifactDigest === centerAdoptionRef.current.artifact.artifactDigest))
156
+ return undefined;
157
+ let a2;
158
+ try {
159
+ a2 = assemblePrompt(buildAssembleInputs(toBundled ? undefined : artifactDeclarations(cand.artifact)));
160
+ }
161
+ catch {
162
+ return undefined;
163
+ }
164
+ if (!toBundled && a2.centerMounted !== true)
165
+ return undefined;
166
+ const declared2 = (a2.sections ?? [])
167
+ .filter((sec) => sec.origin === "provider")
168
+ .map((sec) => ({ id: sec.id, slot: sec.slot, chars: sec.text.length, ...(sec.declaredContentHash !== undefined ? { contentHash: sec.declaredContentHash } : {}) }));
169
+ const overheadTokensAfter = preparedHolder.current.promptOverheadTokens -
170
+ Math.ceil(overheadState.promptChars / charsPerToken) +
171
+ Math.ceil(a2.systemPrompt.length / charsPerToken);
172
+ return {
173
+ ...(toBundled
174
+ ? { clear: true }
175
+ : {
176
+ centerArtifactDigest: cand.artifact.artifactDigest,
177
+ ...(cand.sourceRevision !== undefined ? { sourceRevision: cand.sourceRevision } : {}),
178
+ }),
179
+ declaredSections: declared2,
180
+ overheadTokensAfter,
181
+ apply: (committedArtifactDigest) => {
182
+ systemPromptSeat.current = a2.systemPrompt;
183
+ harnessRef.current?.setSystemPrompt(a2.systemPrompt, a2.systemBlocks);
184
+ if (fpRef.current)
185
+ fpRef.current.systemPrompt = a2.systemPrompt;
186
+ turnSnapshotRef.current?.refreshStableSystem(a2.systemPrompt, committedArtifactDigest);
187
+ preparedHolder.current.epochDeclaredSections = declared2;
188
+ preparedHolder.current.promptOverheadTokens = overheadTokensAfter;
189
+ overheadState.promptChars = a2.systemPrompt.length;
190
+ centerAdoptionRef.current = toBundled
191
+ ? undefined
192
+ : { artifact: cand.artifact, ...(cand.sourceRevision !== undefined ? { sourceRevision: cand.sourceRevision } : {}) };
193
+ },
194
+ };
195
+ }
196
+ : undefined;
197
+ overheadState.promptChars = systemPromptSeat.current.length;
198
+ const effectiveReadFaceObserved = carrierReadFace();
199
+ const effectiveReadDenyObserved = readDenyAdditionsNormalized.length > 0 ? readDenyAdditionsNormalized.map((e) => ({ ...e })) : undefined;
200
+ return { cacheBreakDetector, cacheFingerprint, promptOverheadTokens, readTaskFile, normalizeAttachmentPath, isDedupStubResult, recentlyReadFiles, onCompactionApplied, detectExternalChanges, listBackgroundTasks, centerCompactionCandidate, effectiveReadFaceObserved, effectiveReadDenyObserved };
201
+ }
@@ -25,7 +25,8 @@ import type { LockedPreflight } from "../locked-config.js";
25
25
  import type { MaterializedMcp } from "../mcp.js";
26
26
  import type { SessionStore } from "../session.js";
27
27
  import { type ToolResultStore } from "../tool-result-store.js";
28
- import { type ApprovalSettledBy, type OnAsk } from "../tool-policy.js";
28
+ import { type OnAsk, type ToolPolicy } from "../tool-policy.js";
29
+ import type { GateOutcome } from "../gate-outcome.js";
29
30
  import type { RunnerDeps, RuntimeCaps, TaskSpec, ToolEffect, ToolExecuteContext } from "../types.js";
30
31
  import { type UsageWindow } from "../usage-window-store.js";
31
32
  import { type AutoModeArmReason, type WiringManifest, type WiringManifestMcpEntry } from "../wiring-manifest.js";
@@ -33,8 +34,9 @@ import type { AnnounceOnceSink, Prepared, PrepareResume, RunInternals, UsageGove
33
34
  export interface PrepareWiringManifestInput {
34
35
  /** borrowed-readonly — the REBOUND spec. Read: `onAsk` (the own seat), `onQuestion` (the question seam), `handsReadOnly` / `tools` (the ungated scan),
35
36
  * `resourceSuspend` (the task-axis lane), `durableApproval` / `principal` (the synthesis and the notice), `interactiveTools` /
36
- * `mcp` / `checkpointStore` (manifest cells and the notice's cause). Never mutated. */
37
- spec: Pick<TaskSpec, "onAsk" | "onQuestion" | "handsReadOnly" | "tools" | "resourceSuspend" | "durableApproval" | "principal" | "interactiveTools" | "mcp" | "checkpointStore">;
37
+ * `mcp` / `checkpointStore` (manifest cells and the notice's cause), `enablePlanMode` (the gate-machinery predicate's
38
+ * plan-mode term). Never mutated. */
39
+ spec: Pick<TaskSpec, "onAsk" | "onQuestion" | "handsReadOnly" | "tools" | "resourceSuspend" | "durableApproval" | "principal" | "interactiveTools" | "mcp" | "checkpointStore" | "enablePlanMode">;
38
40
  /** borrowed-readonly — the deployment seats, whole: `onAsk` (the seat's deps half), `onError` (the warning), `executionEnv`
39
41
  * (the hook-env predicate), `checkpointStore` (durable infra), the manifest's dozen wiring facts (`backgroundAgentStore`,
40
42
  * `permissionRuleStore`, `permissionRuleSyncWired`, `onBackgroundChildEvent`, `lockedConfig`, `compliancePostureResolver`,
@@ -61,6 +63,9 @@ export interface PrepareWiringManifestInput {
61
63
  preToolUseObservational: boolean;
62
64
  /** borrowed-readonly — the caller-facing policy layers; only their COUNT is read (an effect-aware gate exists). */
63
65
  policyLayers: ReadonlyArray<unknown>;
66
+ /** borrowed-readonly — the policy chain's effective policy, or undefined; only its PRESENCE is read (the first term of the
67
+ * gate-machinery predicate below). Never called here. */
68
+ effectivePolicy: ToolPolicy | undefined;
64
69
  /** borrowed-readonly — whether an execution env is wired (the hand band exists at all). */
65
70
  handsEnabled: boolean;
66
71
  /** borrowed-readonly — the materialized MCP handle: `tools` (the destructive-ungated scan) and `statuses` (the manifest face). */
@@ -109,9 +114,7 @@ export interface PrepareWiringManifestInput {
109
114
  /** borrowed-readonly — the built harness the peer drain binds to. */
110
115
  harness: AgentHarness;
111
116
  /** borrowed-readonly — the durable-suspend holder; the drain's parked predicate reads `token` at run time. Not written here. */
112
- suspendRef: Prepared["suspendRef"];
113
- /** borrowed-readonly — the review holder; same read. Not written here. */
114
- reviewRef: Prepared["reviewRef"];
117
+ pausedRef: Prepared["pausedRef"];
115
118
  /** borrowed-readonly — the FROZEN approver seat (the notice's liveApprover fact). */
116
119
  frozenOnAsk: OnAsk | undefined;
117
120
  /** borrowed-readonly — the LIVE half of the inherited chain (the notice reads its content mandates). */
@@ -130,16 +133,9 @@ export interface PrepareWiringManifestResult {
130
133
  /** borrowed-mutable — the call ids the gate blocked or suspended (`Prepared.blockedToolCalls`). Writers: the gate; readers
131
134
  * delete one-shot. */
132
135
  blockedToolCalls: Set<string>;
133
- /** borrowed-mutable — the settlement sideband (`Prepared.approvalSettlement`). Writer: the gate; the frame mint reads and
136
+ /** borrowed-mutable — the gate-outcome sideband (`Prepared.gateOutcomes`). Writer: the gate station; the frame mint reads and
134
137
  * deletes. Nothing else may write it. */
135
- approvalSettlement: Map<string, {
136
- settledBy?: ApprovalSettledBy;
137
- approver?: string;
138
- resolution?: import("../tool-policy.js").AskDenyResolution;
139
- autoDenied?: true;
140
- }>;
141
- /** borrowed-mutable — the bare-human-rejection sideband. Writer: resolveAskBound; the gate handler consumes one-shot. */
142
- humanBareRejections: Set<string>;
138
+ gateOutcomes: Map<string, GateOutcome>;
143
139
  /** borrowed-mutable — the turn's halt fact (`Prepared.batchHaltRef`). Writer: the judgment site; cleared at the next request. */
144
140
  batchHaltRef: Prepared["batchHaltRef"];
145
141
  /** owned — whether any consumer of the blocked set is wired (populate only then). */
@@ -160,6 +156,10 @@ export interface PrepareWiringManifestResult {
160
156
  wiringManifest: WiringManifest;
161
157
  /** owned — THE park predicate as one named const (`parkLane.effective === true`). */
162
158
  parkLaneArmed: boolean;
159
+ /** owned — THE gate-machinery activation predicate as one named const: a policy, a screening hook, an egress or
160
+ * irreversible tool, an eligible resource lane, an armed platform lane, or plan mode. The ask lane, the suspend saga,
161
+ * the boundary parks, the park closure and the gate station are built exactly when it holds. */
162
+ gateMachineryActive: boolean;
163
163
  /** owned — THE identity mint of this leg (`Prepared.hookIdentity`), one frozen envelope every hook station reuses. */
164
164
  hookIdentity: HookInvocationIdentity;
165
165
  /** owned — the read-only env capability face every hook context carries, or undefined (no hook consumer, or no env wired). */
@@ -170,8 +170,8 @@ export interface PrepareWiringManifestResult {
170
170
  * server the leg's FROZEN entry snapshot declared, in declaration order, joined by name to this leg's
171
171
  * materialization statuses. Pure projection — no connection logic, no second look at any transport:
172
172
  * · a status `connected` ⇒ `connected` with the mounted roster size (the intake's post-filter count);
173
- * · a status `failed` ⇒ `failed` with the class the materialization stamped ({@link McpFailureCode})
174
- * and the remote text SECRET-REDACTED (an HTTP endpoint's error body rides the SDK's message
173
+ * · a status `failed` ⇒ `failed` with the failure record the materialization stamped (`errorCode`/`delivered`/
174
+ * `httpStatus` = the MCP vocabulary's `McpFailure` spread flat) and the remote text SECRET-REDACTED (an HTTP endpoint's error body rides the SDK's message
175
175
  * verbatim, and a body that echoes the bearer it rejected must not reach a stream consumer),
176
176
  * then neutralized + bounded to the reminder's own 240 (the same bound the once-per-session
177
177
  * ledger applies to that text — one face, one width);
@@ -22,7 +22,7 @@ export function mcpManifestEntries(entries, statuses) {
22
22
  name: entry.name,
23
23
  ...source,
24
24
  status: "failed",
25
- ...(status.errorCode !== undefined ? { errorCode: status.errorCode } : {}),
25
+ ...(status.errorCode !== undefined ? { errorCode: status.errorCode, delivered: status.delivered, ...(status.httpStatus !== undefined ? { httpStatus: status.httpStatus } : {}) } : {}),
26
26
  ...(status.error !== undefined ? { error: inlineUntrusted(status.error, 240) } : {}),
27
27
  toolCount: 0,
28
28
  };
@@ -87,17 +87,17 @@ const ungatedWarnedShapes = new WeakMap();
87
87
  function announceDurableGateUnavailable(args) {
88
88
  if (!args.forceDurableGate || args.storeWired)
89
89
  return;
90
- const cause = args.taskStoreNull ? "task_store_null" : "no_deployment_store";
90
+ const cause = args.taskStoreDisabled ? "task_store_disabled" : "no_deployment_store";
91
91
  const inheritedContentMandate = (args.parentConstraints ?? []).some((pc) => pc.contentMandate === true);
92
92
  const liveQuestionFace = args.liveQuestionFace && !inheritedContentMandate;
93
93
  deliverEngineNotice(args.onNotice, {
94
94
  code: "config.durable_gate_unavailable",
95
95
  message: `Durable approval gate mandated but unavailable: the runtime entitlement forceDurableGate is in force for this ` +
96
- `principal, but ${cause === "task_store_null" ? "this task disabled the checkpoint store (checkpointStore: null)" : "this deployment wired no checkpoint store"}, ` +
96
+ `principal, but ${cause === "task_store_disabled" ? 'this task disabled the checkpoint store (checkpointStore: "disabled")' : "this deployment wired no checkpoint store"}, ` +
97
97
  `so no interactive ask of this task can be parked for a durable answer. Asks resolve on the live chain instead ` +
98
98
  `(${args.liveApprover ? "a live approver answers permission asks in-stream" : "permission asks are denied fail-closed — no live approver is wired"}; ` +
99
99
  `${liveQuestionFace ? "a live question face answers AskUserQuestion in-stream" : args.liveQuestionFace ? "AskUserQuestion is withheld from the live face by an inherited durable mandate and refused" : "AskUserQuestion has no live face and is refused"}) ` +
100
- `and NO durable approval record is written. ${cause === "task_store_null" ? "Run this task with its store, or withdraw the entitlement for principals whose runs are machine-started." : "Wire a checkpoint store on this deployment, or withdraw the entitlement for principals served here."}`,
100
+ `and NO durable approval record is written. ${cause === "task_store_disabled" ? "Run this task with its store, or withdraw the entitlement for principals whose runs are machine-started." : "Wire a checkpoint store on this deployment, or withdraw the entitlement for principals served here."}`,
101
101
  detail: {
102
102
  sessionId: args.sessionId,
103
103
  runId: args.runId,
@@ -109,7 +109,7 @@ function announceDurableGateUnavailable(args) {
109
109
  });
110
110
  }
111
111
  export function prepareWiringManifest(input) {
112
- const { spec, deps, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, handsEnabled, mcp, irreversibleTools, egressTools, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, lockedPreflight, peerLaneRefs, peerLaneActive, harness, suspendRef, reviewRef, frozenOnAsk, liveInheritedGate, delegation } = input;
112
+ const { spec, deps, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, effectivePolicy, handsEnabled, mcp, irreversibleTools, egressTools, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, lockedPreflight, peerLaneRefs, peerLaneActive, harness, pausedRef, frozenOnAsk, liveInheritedGate, delegation } = input;
113
113
  const onAsk = spec.onAsk ?? deps.onAsk;
114
114
  const handWriteTools = handsEnabled && spec.handsReadOnly !== true
115
115
  ? Object.keys(HAND_TOOL_EFFECTS).filter((name) => {
@@ -144,8 +144,7 @@ export function prepareWiringManifest(input) {
144
144
  }
145
145
  const preToolContexts = new Map();
146
146
  const blockedToolCalls = new Set();
147
- const approvalSettlement = new Map();
148
- const humanBareRejections = new Set();
147
+ const gateOutcomes = new Map();
149
148
  const batchHaltRef = {};
150
149
  const blockedTracked = Boolean(hooks?.postToolUse || hooks?.preToolUse || hooks?.postToolUseFailure || hooks?.postToolBatch);
151
150
  const restoreSurfaceGap = ownedEnv !== undefined && isRemoteExecutionEnv(ownedEnv) && ownedEnv.capabilities.suspendable ? missingRestoreSurface(ownedEnv) : [];
@@ -220,11 +219,18 @@ export function prepareWiringManifest(input) {
220
219
  ownTokens: () => internals?.peerSelfRef?.current.ownTokens ?? [],
221
220
  refs: peerLaneRefs,
222
221
  selfName: () => internals?.explicitAgentName,
223
- parked: () => suspendRef.token !== undefined || reviewRef.token !== undefined,
222
+ parked: () => pausedRef.current !== undefined,
224
223
  });
225
224
  }
226
225
  const parkLaneArmed = wiringManifest.parkLane.effective === true;
227
- announceDurableGateUnavailable({ onNotice: deps.onNotice, forceDurableGate: runtimeCaps?.forceDurableGate === true, storeWired: checkpointStore !== undefined, taskStoreNull: spec.checkpointStore === null, liveApprover: isLiveApproverSeat(frozenOnAsk), liveQuestionFace: liveQuestionFace !== undefined, parentConstraints: liveInheritedGate?.parentConstraints, sessionId, runId, principal: spec.principal });
226
+ const gateMachineryActive = effectivePolicy !== undefined ||
227
+ hooks?.preToolUse !== undefined ||
228
+ egressTools.size > 0 ||
229
+ irreversibleTools.size > 0 ||
230
+ resourceSuspendEligible ||
231
+ platformSuspendArmed ||
232
+ spec.enablePlanMode === true;
233
+ announceDurableGateUnavailable({ onNotice: deps.onNotice, forceDurableGate: runtimeCaps?.forceDurableGate === true, storeWired: checkpointStore !== undefined, taskStoreDisabled: spec.checkpointStore === "disabled", liveApprover: isLiveApproverSeat(frozenOnAsk), liveQuestionFace: liveQuestionFace !== undefined, parentConstraints: liveInheritedGate?.parentConstraints, sessionId, runId, principal: spec.principal });
228
234
  const hookIdentity = mintHookInvocationIdentity({
229
235
  sessionId,
230
236
  taskId: hostTaskId,
@@ -236,5 +242,5 @@ export function prepareWiringManifest(input) {
236
242
  });
237
243
  const hookContextConsumerWired = hooks?.preToolUse !== undefined || hooks?.postToolUse !== undefined || hooks?.postToolUseFailure !== undefined;
238
244
  const hookEnvFace = hookContextConsumerWired && (ownedEnv ?? deps.executionEnv) != null ? createHookEnvCapabilities(executionEnv) : undefined;
239
- return { onAsk, preToolContexts, blockedToolCalls, approvalSettlement, humanBareRejections, batchHaltRef, blockedTracked, incompleteSuspendAdapter, durableSuspendInfraReady, resourceSuspendEligible, usageGovernance, platformSuspendArmed, durableApproval, wiringManifest, parkLaneArmed, hookIdentity, hookEnvFace };
245
+ return { onAsk, preToolContexts, blockedToolCalls, gateOutcomes, batchHaltRef, blockedTracked, incompleteSuspendAdapter, durableSuspendInfraReady, resourceSuspendEligible, usageGovernance, platformSuspendArmed, durableApproval, wiringManifest, parkLaneArmed, gateMachineryActive, hookIdentity, hookEnvFace };
240
246
  }
@@ -1,34 +1,7 @@
1
- import type { RemoteExecutionEnv, RemoteExecutionError, SnapshotId, VmLifecycleOptions } from "../remote-env.js";
2
1
  import type { ExecutionEnv } from "../../internal/harness.js";
3
- import type { RemoteEnvFailureNote, RunnerDeps, TaskSpec } from "../types.js";
2
+ import type { RunnerDeps, TaskSpec } from "../types.js";
4
3
  import type { PrepareResume, RunInternals } from "./contracts.js";
5
- /**
6
- * RB-439-a — project one failed remote lifecycle call onto the caller-facing note. ONE builder for all
7
- * three ops so the `retryable` classification can never drift between the lane that reports a `suspendVM`
8
- * refusal and the lane that reports a failed restore.
9
- */
10
- export declare function remoteEnvFailureNote(op: RemoteEnvFailureNote["op"], error: RemoteExecutionError, attempts: number): RemoteEnvFailureNote;
11
- /**
12
- * RB-439-a — `resumeVM` with a bounded retry on the TRANSIENT codes, plus the attempt count for the
13
- * caller-facing note.
14
- *
15
- * Why this op and no other: `resumeVM` is the one lifecycle call the seam documents as "idempotent +
16
- * re-entrant" (a replica that crashed mid-suspend can be superseded by another resuming the SAME
17
- * snapshot), which is exactly `withRetry`'s red line — retry idempotent establishment, never a
18
- * side-effecting op. `suspendVM` TAKES a snapshot and `postResumeInit` re-injects secrets under an
19
- * ordering red line; neither is retried here, they are disclosed with `retryable` instead so the caller
20
- * decides. A code outside the retryable family (`unsupported`, `auth_failed`) returns on the first
21
- * attempt — `withRetry` will not spend a second call on a permanent refusal.
22
- */
23
- export declare function restoreWorkspaceWithRetry(env: RemoteExecutionEnv, snapshotId: SnapshotId, options: VmLifecycleOptions,
24
- /** design/384 slice 2 (additive): observe each attempt AS IT STARTS. The returned `attempts` is
25
- * only readable after settlement, and a caller whose wait on this promise is BOUNDED (the park
26
- * compensation) must report the live attempt count when its bound fires mid-call — without this
27
- * seat a deaf second attempt was reported as `attempts: 1`. */
28
- onAttempt?: (attempt: number) => void): Promise<{
29
- outcome: Awaited<ReturnType<RemoteExecutionEnv["resumeVM"]>>;
30
- attempts: number;
31
- }>;
4
+ export { remoteEnvFailureNote, restoreWorkspaceWithRetry } from "./remote-env-retry.js";
32
5
  export interface PrepareWorkspaceRestoreInput {
33
6
  /** borrowed-readonly (the ownership-ref PROTOCOL stays with the driver — D-8 案①): the env this
34
7
  * task owns, when the factory minted one. This phase reads and drives it (`resumeVM`,
@@ -1,20 +1,7 @@
1
- import { isRemoteExecutionEnv, isRetryableRemoteErrorCode, missingRestoreSurface, RETRYABLE_REMOTE_ERROR_CODES } from "../remote-env.js";
2
- import { withRetry } from "../with-retry.js";
1
+ import { isRemoteExecutionEnv, missingRestoreSurface } from "../remote-env.js";
2
+ import { remoteEnvFailureNote, restoreWorkspaceWithRetry } from "./remote-env-retry.js";
3
3
  import { rebaseWorkspacePathAcross } from "./workspace-path.js";
4
- export function remoteEnvFailureNote(op, error, attempts) {
5
- return { op, code: error.code, retryable: isRetryableRemoteErrorCode(error.code), attempts, message: error.message };
6
- }
7
- const REMOTE_RESTORE_MAX_ATTEMPTS = 2;
8
- const REMOTE_RESTORE_BACKOFF_MS = 200;
9
- export async function restoreWorkspaceWithRetry(env, snapshotId, options, onAttempt) {
10
- let attempts = 0;
11
- const outcome = await withRetry(async (attempt) => {
12
- attempts = attempt;
13
- onAttempt?.(attempt);
14
- return env.resumeVM(snapshotId, options);
15
- }, { retryableCodes: RETRYABLE_REMOTE_ERROR_CODES, maxAttempts: REMOTE_RESTORE_MAX_ATTEMPTS, backoffMs: () => REMOTE_RESTORE_BACKOFF_MS }, { ...(options.abortSignal !== undefined ? { signal: options.abortSignal } : {}) });
16
- return { outcome, attempts };
17
- }
4
+ export { remoteEnvFailureNote, restoreWorkspaceWithRetry } from "./remote-env-retry.js";
18
5
  export async function prepareWorkspaceRestore(input) {
19
6
  const { ownedEnv, deps, spec, internals, resume, abortController, sessionId } = input;
20
7
  let taskRootPath = input.taskRootInitial;
@@ -0,0 +1 @@
1
+ export declare const PROMPT_HASH_SALT: Buffer<ArrayBufferLike>;
@@ -0,0 +1,2 @@
1
+ import { randomBytes } from "node:crypto";
2
+ export const PROMPT_HASH_SALT = randomBytes(16);
@@ -0,0 +1,29 @@
1
+ import type { RemoteExecutionEnv, RemoteExecutionError, SnapshotId, VmLifecycleOptions } from "../remote-env.js";
2
+ import type { RemoteEnvFailureNote } from "../types.js";
3
+ /**
4
+ * RB-439-a — project one failed remote lifecycle call onto the caller-facing note. ONE builder for all
5
+ * three ops so the `retryable` classification can never drift between the lane that reports a `suspendVM`
6
+ * refusal and the lane that reports a failed restore.
7
+ */
8
+ export declare function remoteEnvFailureNote(op: RemoteEnvFailureNote["op"], error: RemoteExecutionError, attempts: number): RemoteEnvFailureNote;
9
+ /**
10
+ * RB-439-a — `resumeVM` with a bounded retry on the TRANSIENT codes, plus the attempt count for the
11
+ * caller-facing note.
12
+ *
13
+ * Why this op and no other: `resumeVM` is the one lifecycle call the seam documents as "idempotent +
14
+ * re-entrant" (a replica that crashed mid-suspend can be superseded by another resuming the SAME
15
+ * snapshot), which is exactly `withRetry`'s red line — retry idempotent establishment, never a
16
+ * side-effecting op. `suspendVM` TAKES a snapshot and `postResumeInit` re-injects secrets under an
17
+ * ordering red line; neither is retried here, they are disclosed with `retryable` instead so the caller
18
+ * decides. A code outside the retryable family (`unsupported`, `auth_failed`) returns on the first
19
+ * attempt — `withRetry` will not spend a second call on a permanent refusal.
20
+ */
21
+ export declare function restoreWorkspaceWithRetry(env: RemoteExecutionEnv, snapshotId: SnapshotId, options: VmLifecycleOptions,
22
+ /** design/384 slice 2 (additive): observe each attempt AS IT STARTS. The returned `attempts` is
23
+ * only readable after settlement, and a caller whose wait on this promise is BOUNDED (the park
24
+ * compensation) must report the live attempt count when its bound fires mid-call — without this
25
+ * seat a deaf second attempt was reported as `attempts: 1`. */
26
+ onAttempt?: (attempt: number) => void): Promise<{
27
+ outcome: Awaited<ReturnType<RemoteExecutionEnv["resumeVM"]>>;
28
+ attempts: number;
29
+ }>;