@sema-agent/core 7.22.0 → 7.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +210 -1
  2. package/dist/core/auto-compaction.d.ts +2 -2
  3. package/dist/core/checkpoint-store.d.ts +1 -5
  4. package/dist/core/context-edit.js +1 -1
  5. package/dist/core/engine-notice.d.ts +13 -0
  6. package/dist/core/gate-bounded-await.d.ts +14 -4
  7. package/dist/core/gate-bounded-await.js +1 -1
  8. package/dist/core/gate-crash-trace.d.ts +7 -0
  9. package/dist/core/gate-crash-trace.js +3 -0
  10. package/dist/core/gate-fold.js +5 -4
  11. package/dist/core/gate-lanes.js +2 -1
  12. package/dist/core/governance-codes.d.ts +1 -1
  13. package/dist/core/governance-codes.js +2 -0
  14. package/dist/core/hooks.js +2 -1
  15. package/dist/core/park-selfcheck.js +1 -1
  16. package/dist/core/path-identity.d.ts +11 -4
  17. package/dist/core/path-identity.js +5 -2
  18. package/dist/core/read-root-candidate.js +11 -1
  19. package/dist/core/runner/assemble-result.d.ts +2 -7
  20. package/dist/core/runner/assemble-result.js +25 -8
  21. package/dist/core/runner/contracts.d.ts +15 -15
  22. package/dist/core/runner/gate-exit.d.ts +0 -5
  23. package/dist/core/runner/gate-exit.js +0 -3
  24. package/dist/core/runner/inherited-ask-grants.d.ts +2 -7
  25. package/dist/core/runner/inherited-ask-grants.js +1 -0
  26. package/dist/core/runner/prepare-ask-lane.js +1 -0
  27. package/dist/core/runner/prepare-context-lane.d.ts +43 -1
  28. package/dist/core/runner/prepare-context-lane.js +25 -4
  29. package/dist/core/runner/prepare-task.js +2 -2
  30. package/dist/core/runner/prepare-turn-wiring.js +2 -2
  31. package/dist/core/runner/run-harness-handlers.js +4 -2
  32. package/dist/core/runner/run-recovery-lanes.js +6 -3
  33. package/dist/core/runner/run-telemetry-and-budget-seats.js +11 -1
  34. package/dist/core/runner/write-protection-station.d.ts +7 -25
  35. package/dist/core/runner/write-protection-station.js +18 -0
  36. package/dist/core/task-registry.d.ts +0 -6
  37. package/dist/core/task-result.d.ts +28 -10
  38. package/dist/core/types.d.ts +1 -1
  39. package/dist/core/write-protect.d.ts +64 -12
  40. package/dist/core/write-protect.js +87 -34
  41. package/dist/engine/execution-env/node-execution-env.d.ts +19 -2
  42. package/dist/engine/execution-env/node-execution-env.js +5 -1
  43. package/dist/index.d.ts +1 -1
  44. package/dist/tools/fs/bash-lexer.d.ts +12 -5
  45. package/dist/tools/fs/bash-lexer.js +5 -2
  46. package/dist/tools/fs/bash-program-position.d.ts +18 -0
  47. package/dist/tools/fs/bash-program-position.js +13 -1
  48. package/dist/tools/fs/bash-readonly-classifier.d.ts +10 -3
  49. package/dist/tools/fs/bash-readonly-classifier.js +70 -41
  50. package/dist/tools/fs/fs-bash.d.ts +22 -0
  51. package/dist/tools/fs/fs-bash.js +19 -12
  52. package/dist/tools/fs/fs-read.js +2 -1
  53. package/dist/tools/fs/fs-shared.d.ts +33 -7
  54. package/dist/tools/fs/fs-shared.js +12 -1
  55. package/dist/tools/fs/safety.d.ts +37 -0
  56. package/dist/tools/fs/safety.js +31 -3
  57. package/package.json +2 -2
  58. package/test/export-surface.snapshot.json +3 -1
  59. package/wire-changes/core@7.23.0.json +316 -0
  60. package/wire-changes/core@7.23.1.json +205 -0
@@ -1,4 +1,5 @@
1
1
  import type { AssistantMessage } from "../../internal/llm.js";
2
+ import type { HumanReviewGateRow } from "../task-result.js";
2
3
  import type { NestedUsage, PausedCause, TaskResult, TaskSpec, TerminalCause } from "../types.js";
3
4
  import type { HaltConsumption } from "../../internal/harness.js";
4
5
  /** Per-task usage/cost accumulator (filled across the run loop, surfaced as `TaskResult.stats`). */
@@ -40,13 +41,7 @@ export interface Stats {
40
41
  humanReview?: {
41
42
  count: number;
42
43
  totalWaitMs: number;
43
- gates: Array<{
44
- kind: string;
45
- waitMs: number;
46
- decision?: string;
47
- toolName?: string;
48
- toolArg?: string;
49
- }>;
44
+ gates: HumanReviewGateRow[];
50
45
  };
51
46
  /** TB telemetry B2: engine-mechanism engagement counters. Set by the run loop
52
47
  * only when ≥1 engaged; flows straight through to `TaskResult.stats.mechanisms` via the
@@ -2,6 +2,7 @@ import { isDegenerateCutMessage } from "../../brain/terminal-cause.js";
2
2
  import { extractErrorCode, stripErrorCodePrefix } from "../../brain/errors.js";
3
3
  import { terminalProjection } from "./terminal-projection.js";
4
4
  import { haltConsumed } from "./halt-attribution.js";
5
+ import { describeRejectedValue } from "../tool-search-setting.js";
5
6
  const SALVAGE_ELIGIBLE_TERMINALS = new Set([
6
7
  "output.degenerate",
7
8
  "limits.max_tokens_exceeded",
@@ -11,18 +12,34 @@ const SALVAGE_ELIGIBLE_TERMINALS = new Set([
11
12
  "env.lifetime_expired",
12
13
  "usage.window_exhausted",
13
14
  ]);
15
+ function thrownMessage(threw) {
16
+ try {
17
+ if (threw instanceof Error) {
18
+ const m = threw.message;
19
+ if (typeof m === "string")
20
+ return m;
21
+ }
22
+ }
23
+ catch {
24
+ }
25
+ return describeRejectedValue(threw);
26
+ }
14
27
  export function errorCodeOf(err) {
15
28
  let cur = err;
16
29
  let fallback;
17
- for (let depth = 0; cur && typeof cur === "object" && depth < 8; depth++) {
18
- const code = cur.code;
19
- if (typeof code === "string" && code) {
20
- if (code !== "unknown") {
21
- return code;
30
+ try {
31
+ for (let depth = 0; cur && typeof cur === "object" && depth < 8; depth++) {
32
+ const code = cur.code;
33
+ if (typeof code === "string" && code) {
34
+ if (code !== "unknown") {
35
+ return code;
36
+ }
37
+ fallback ??= code;
22
38
  }
23
- fallback ??= code;
39
+ cur = cur.cause;
24
40
  }
25
- cur = cur.cause;
41
+ }
42
+ catch {
26
43
  }
27
44
  return fallback;
28
45
  }
@@ -84,7 +101,7 @@ export function terminalCauseOf(final, stats, flags) {
84
101
  terminal = { kind: "failed", code: "suspend.loop", message };
85
102
  }
86
103
  else if (flags.threw) {
87
- const message = flags.threw instanceof Error ? flags.threw.message : String(flags.threw);
104
+ const message = thrownMessage(flags.threw);
88
105
  const code = flags.abortedForTimeout
89
106
  ? "limits.max_walltime_exceeded"
90
107
  : flags.abortedForTurns
@@ -13,6 +13,7 @@
13
13
  * without adding a runtime edge anywhere. The blocks below are verbatim moves, in their original
14
14
  * order, JSDoc included, so the move stays auditable as a move.
15
15
  */
16
+ import type { HumanReviewGateRow } from "../task-result.js";
16
17
  import type { RepairBundle } from "../../agents/repair-loop.js";
17
18
  import type { SubagentRetainLedger } from "../../agents/retain-ledger.js";
18
19
  import type { BrainCallGuardrailRef } from "../../brain/timeout.js";
@@ -788,16 +789,14 @@ export interface Prepared {
788
789
  * prior-leg burden seeded from the resumed checkpoint). The run loop ADDS the durable-resume latency
789
790
  * (`now() − cp.suspendedAt`) on a resume, then surfaces it as `stats.humanReview` at assembly (omitted when
790
791
  * empty). **Budget-EXCLUDED** — never folded into cost/the budget gate. */
792
+ /** borrowed-mutable — the hands' read-state map (the SAME `Map` object the toolkit and the context
793
+ * lane hold; absent without hands). Read by the provider-rejection recovery arm so its stale-result
794
+ * clears retire the entries those transmissions fed, through the same landing the frontier pass uses. */
795
+ readFileState?: import("../../tools/fs/safety.js").ReadFileState;
791
796
  humanReviewRef: {
792
797
  count: number;
793
798
  totalWaitMs: number;
794
- gates: Array<{
795
- kind: string;
796
- waitMs: number;
797
- decision?: string;
798
- toolName?: string;
799
- toolArg?: string;
800
- }>;
799
+ gates: HumanReviewGateRow[];
801
800
  };
802
801
  /** The injectable wall-clock the run loop uses for the durable-resume human-review latency
803
802
  * (`humanLatencyMs = now() − cp.suspendedAt`), so it reads the SAME clock as the suspend-side `suspendedAt`. */
@@ -913,7 +912,14 @@ export interface Prepared {
913
912
  * that were re-attached WHOLE. Present only with hands (same gate as readTaskFile).
914
913
  * `preserveReadState` carries the kept-tail-visible files that were
915
914
  * deliberately skipped for re-attachment — their existing entry must survive the clear too (same
916
- * reasoning as a seeded entry: the model's view of the file did not change). */
915
+ * reasoning as a seeded entry: the model's view of the file did not change).
916
+ *
917
+ * SCOPE — read "every compaction lane" as exactly that and no wider. The request-layer
918
+ * stale-result pass takes a Read body off the wire with no compaction at all, and retires its own
919
+ * entries at its own landing site (`prepare-context-lane.ts`'s `landStaleResultClears`) through the
920
+ * SAME function this seat calls, on its `tool-results-cleared` arm: ONE retirement mechanism, two
921
+ * ways for content to leave. Taken as a claim about every SHRINK mechanism this sentence was true
922
+ * of the lanes it named and silently false of the one it did not. */
917
923
  onCompactionApplied?: (attachedComplete: ReadonlyArray<{
918
924
  path: string;
919
925
  content: string;
@@ -1189,13 +1195,7 @@ export interface PrepareResume {
1189
1195
  priorHumanReview?: {
1190
1196
  count: number;
1191
1197
  totalWaitMs: number;
1192
- gates: Array<{
1193
- kind: string;
1194
- waitMs: number;
1195
- decision?: string;
1196
- toolName?: string;
1197
- toolArg?: string;
1198
- }>;
1198
+ gates: HumanReviewGateRow[];
1199
1199
  };
1200
1200
  /** When the suspend ran with a remote workspace, the {@link CheckpointState.workspaceHandle}
1201
1201
  * to restore — prepare rebuilds the per-task env via `deps.executionEnvFactory` then `resumeVM(snapshotId)`
@@ -112,11 +112,6 @@ export declare function createSettlementLedger(call: {
112
112
  * a non-object primitive passes through as-is (immutable anyway).
113
113
  */
114
114
  export declare function cloneObserverInput(input: unknown): unknown;
115
- /** Hand the crash itself to the deployment's trace lane (`onError(phase:"hook")`), never letting a
116
- * throwing sink alter the fail-closed outcome — the same swallow posture as every other observer here.
117
- * Shared by the gate's dispatch (the phase-1 crash arm, the seat-bound refusal), the reversibility probe's
118
- * two failure arms and the approved-edit re-screen. */
119
- export declare function traceHookCrash(input: ToolGateInput, err: unknown, notifier: SafeNotifier): void;
120
115
  /**
121
116
  * The PreToolUse screening face as the gate's stations see it — the hook machinery of `runToolGate` (the seat
122
117
  * runner, the per-call seat bound, the fresh per-screening context, the result screen, the two fail-closed
@@ -76,9 +76,6 @@ export function cloneObserverInput(input) {
76
76
  return input;
77
77
  }
78
78
  }
79
- export function traceHookCrash(input, err, notifier) {
80
- notifier.notify(() => input.onHookError?.(err), "toolGate.onHookError");
81
- }
82
79
  const PARK_FAILURE_CAUSE_MAX = 600;
83
80
  function withParkFailureCause(reason, parkFailed) {
84
81
  if (parkFailed === undefined)
@@ -1,3 +1,4 @@
1
+ import type { HumanReviewGateRow } from "../task-result.js";
1
2
  import { type OnAsk, type PermissionResult, type ResolvedAsk, type ToolCallRequest } from "../tool-policy.js";
2
3
  /** JSON.stringify throws on a cyclic value (a deployment
3
4
  * policy's rewrite object carries no acyclicity guarantee) — `undefined` here means "no comparable
@@ -37,11 +38,5 @@ export declare function consumeInheritedAskGrant(grants: Map<string, {
37
38
  }>, onAsk: OnAsk | undefined, humanReviewRef: {
38
39
  count: number;
39
40
  totalWaitMs: number;
40
- gates: Array<{
41
- kind: string;
42
- waitMs: number;
43
- decision?: string;
44
- toolName?: string;
45
- toolArg?: string;
46
- }>;
41
+ gates: HumanReviewGateRow[];
47
42
  }, decision: PermissionResult, req: ToolCallRequest): ResolvedAsk | undefined;
@@ -23,6 +23,7 @@ export function consumeInheritedAskGrant(grants, onAsk, humanReviewRef, decision
23
23
  waitMs: grant.waitMs,
24
24
  decision: "allow",
25
25
  toolName: req.toolName,
26
+ toolCallId: req.toolCallId,
26
27
  ...(toolArg !== undefined ? { toolArg } : {}),
27
28
  });
28
29
  const reused = { action: "allow", presentedInput: grant.presented };
@@ -115,6 +115,7 @@ export function prepareAskLane(input) {
115
115
  waitMs,
116
116
  decision: resolved.action === "allow" ? "allow" : "deny",
117
117
  toolName: req.toolName,
118
+ toolCallId: req.toolCallId,
118
119
  ...(toolArg !== undefined ? { toolArg } : {}),
119
120
  });
120
121
  }
@@ -17,11 +17,12 @@
17
17
  */
18
18
  import { type AgentHarness, type AgentMessage } from "../../internal/harness.js";
19
19
  import type { Model } from "../../internal/llm.js";
20
- import { type MicroCompactMachine, type OccurrenceIndex } from "../context-edit.js";
20
+ import { type ClearedProjectionLedger, type MicroCompactMachine, type OccurrenceFingerprint, type OccurrenceIndex } from "../context-edit.js";
21
21
  import { type ToolResultStore } from "../tool-result-store.js";
22
22
  import { type RunnerDeps } from "../types.js";
23
23
  import type { Prepared, PreparedMicroCompact } from "./contracts.js";
24
24
  import { type GitStatusLaneRef } from "./git-status-frame.js";
25
+ import type { ReadFileState } from "../../tools/fs/safety.js";
25
26
  export interface PrepareContextLaneInput {
26
27
  /** borrowed-readonly — the task model: the budgets, the trigger-window geometry and `charsPerToken` are read off it. */
27
28
  model: Model;
@@ -56,6 +57,12 @@ export interface PrepareContextLaneInput {
56
57
  requestLossyRef: {
57
58
  current: boolean;
58
59
  };
60
+ /** borrowed-mutable — the hands' read-state map, or undefined without hands. Writer here: a landed stale-result
61
+ * clear RETIRES the entries whose transmission it just replaced with a marker — the same retirement the compaction
62
+ * lanes run through the SAME function (`applyContextShrinkToReadFileState`), because a Read body taken off the wire
63
+ * is off the wire whichever mechanism took it. Without it a re-read of an unchanged file answers "content omitted"
64
+ * pointing at a cleared marker. */
65
+ readFileState?: ReadFileState;
59
66
  /** borrowed-mutable — the trim-pressure flag (`Prepared.trimPressureRef`). Writer here: the guard chain's trim arm sets
60
67
  * `droppedMessages`; the run loop's boundary reads and clears it. */
61
68
  trimPressureRef: {
@@ -72,6 +79,38 @@ export interface PrepareContextLaneResult {
72
79
  }
73
80
  /** The M21 phase body — prepareTask's context-lane stretch, verbatim (see the module header). */
74
81
  export declare function prepareContextLane(input: PrepareContextLaneInput): PrepareContextLaneResult;
82
+ /** Record a cc-machine pass's clears into the ledger + emit the G11
83
+ * frame (one frame per firing pass; the replay of these markers on later requests emits nothing —
84
+ * cardinality is per CLEAR, not per request). `trigger` names the arm: `"frontier"` = the
85
+ * proactive request-build pass, `"blocking"` = the slice-3 arm-A pre-guard pass (the machine's
86
+ * one shot when the frontier hook is off). Marker bytes are read back off the edited array
87
+ * (the machine composed them, ref/media notes included) so the ledger replays EXACTLY what went
88
+ * on the wire; an occurrence the index cannot key (pathological same-id-same-timestamp ambiguity)
89
+ * is simply not recorded — its clear stays request-local, the safe (full-text-returns) direction.
90
+ * `at.fp` is the occurrence's fingerprint in the PRE-CAP replayed view (the same bytes the next
91
+ * session rebuild presents) — the replay-side mis-paint guard's comparand. */
92
+ /**
93
+ * The ONE landing of a set of stale-result clears: the ledger entry that makes the marker survive every
94
+ * later rebuild, and the retirement of the read-state entries those transmissions carried. The pair is
95
+ * inseparable — a ledger entry WITHOUT the retirement leaves a dedup stub pointing at a marker — which
96
+ * is why it is a function rather than two statements repeated at each of the two clear sites (the
97
+ * frontier/blocking pass and the provider-rejection recovery arm, which used to write the ledger
98
+ * independently, so only one of them retired anything). Posture: a ledger entry means the marker is
99
+ * repainted on every later request build, so the occurrence is cleared for good even when the
100
+ * projection that produced it is discarded — which is why the retirement belongs here and not in the
101
+ * per-request observation buffer.
102
+ */
103
+ export declare function landStaleResultClears(args: {
104
+ ledger: ClearedProjectionLedger;
105
+ cleared: ReadonlyArray<{
106
+ key: string;
107
+ marker: string;
108
+ groupCount: number;
109
+ fp: OccurrenceFingerprint;
110
+ toolCallId?: string;
111
+ }>;
112
+ readFileState?: ReadFileState;
113
+ }): void;
75
114
  /** See {@link runGuardChain}. */
76
115
  export interface GuardChainArgs {
77
116
  /** The post-frontier view the chain starts from. */
@@ -94,6 +133,9 @@ export interface GuardChainArgs {
94
133
  /** The r7 per-request observation buffer — the chain DROPS it on its early returns (a discarded
95
134
  * provisional projection must not narrate itself). */
96
135
  pendingProjectionObservations: Array<() => void>;
136
+ /** The hands' read-state map, or undefined without hands — arm A's clear retires the entries its
137
+ * cleared transmissions carried (see `PrepareContextLaneInput.readFileState`). */
138
+ readFileState?: ReadFileState;
97
139
  }
98
140
  export type GuardChainOutcome = {
99
141
  earlyReturn: {
@@ -7,8 +7,9 @@ import { createOffloadPersist } from "../tool-result-store.js";
7
7
  import { emitTrace } from "../trace.js";
8
8
  import { deliverEngineNotice } from "../types.js";
9
9
  import { applyGitFrameGuard } from "./git-status-frame.js";
10
+ import { applyContextShrinkToReadFileState } from "../../tools/fs/index.js";
10
11
  export function prepareContextLane(input) {
11
- const { model, deps, sessionId, runId, hostTaskId, offloadStore, microCompactKnob, harness, batchHaltRef, gitStatusRef, requestLossyRef, trimPressureRef } = input;
12
+ const { model, deps, sessionId, runId, hostTaskId, offloadStore, microCompactKnob, harness, batchHaltRef, gitStatusRef, requestLossyRef, trimPressureRef, readFileState } = input;
12
13
  const editAt = editBudget(model);
13
14
  const guardAt = guardBudget(model);
14
15
  const microCompact = buildMicroCompactState(deps, model, sessionId, runId, offloadStore, microCompactKnob);
@@ -57,12 +58,13 @@ export function prepareContextLane(input) {
57
58
  : {}),
58
59
  });
59
60
  if (ccPass !== undefined) {
60
- recordFrontierClears({ pass: ccPass, index: replay.index, edited, ledger: microCompact.ledger, tracer: deps.tracer, taskId: hostTaskId, trigger: "frontier" });
61
+ recordFrontierClears({ pass: ccPass, index: replay.index, edited, ledger: microCompact.ledger, tracer: deps.tracer, taskId: hostTaskId, trigger: "frontier", ...(readFileState !== undefined ? { readFileState } : {}) });
61
62
  }
62
63
  const chain = await runGuardChain({
63
64
  edited, replayed, index: replay.index, microCompact, guardAt, charsPerToken, offloadStore,
64
65
  sessionId, taskId: hostTaskId, deps, trimPressureRef, recheck, signal,
65
66
  pendingProjectionObservations,
67
+ ...(readFileState !== undefined ? { readFileState } : {}),
66
68
  });
67
69
  if ("earlyReturn" in chain) {
68
70
  return chain.earlyReturn;
@@ -138,15 +140,34 @@ function buildMicroCompactState(deps, model, sessionId, runId, offloadStore, kno
138
140
  ...(offloadStore ? { offloadPersist: createOffloadPersist(offloadStore, sessionId, deps.onNotice) } : {}),
139
141
  };
140
142
  }
143
+ export function landStaleResultClears(args) {
144
+ const clearedToolCallIds = [];
145
+ for (const c of args.cleared) {
146
+ args.ledger.entries.set(c.key, { marker: c.marker, groupCount: c.groupCount, fp: c.fp });
147
+ if (c.toolCallId !== undefined)
148
+ clearedToolCallIds.push(c.toolCallId);
149
+ }
150
+ if (args.readFileState !== undefined) {
151
+ applyContextShrinkToReadFileState(args.readFileState, { kind: "tool-results-cleared", clearedToolCallIds });
152
+ }
153
+ }
141
154
  function recordFrontierClears(args) {
155
+ const landed = [];
142
156
  for (const { index } of args.pass.clears) {
143
157
  const at = args.index.keyAt(index);
144
158
  const clearedMsg = args.edited[index];
145
159
  const markerText = clearedMsg.content?.[0]?.text;
146
160
  if (at !== undefined && typeof markerText === "string") {
147
- args.ledger.entries.set(at.key, { marker: markerText, groupCount: at.groupCount, fp: at.fp });
161
+ landed.push({
162
+ key: at.key,
163
+ marker: markerText,
164
+ groupCount: at.groupCount,
165
+ fp: at.fp,
166
+ ...(typeof clearedMsg.toolCallId === "string" ? { toolCallId: clearedMsg.toolCallId } : {}),
167
+ });
148
168
  }
149
169
  }
170
+ landStaleResultClears({ ledger: args.ledger, cleared: landed, ...(args.readFileState !== undefined ? { readFileState: args.readFileState } : {}) });
150
171
  emitTrace(args.tracer, () => ({
151
172
  kind: "context.mc_clear",
152
173
  version: 1,
@@ -198,7 +219,7 @@ export async function runGuardChain(args) {
198
219
  ...(args.offloadStore ? { offload: { persist: createOffloadPersist(args.offloadStore, args.sessionId, deps.onNotice) } } : {}),
199
220
  });
200
221
  if (blockingPass !== undefined) {
201
- recordFrontierClears({ pass: blockingPass, index: args.index, edited: blockingEdited, ledger: microCompact.ledger, tracer: deps.tracer, taskId, trigger: "blocking" });
222
+ recordFrontierClears({ pass: blockingPass, index: args.index, edited: blockingEdited, ledger: microCompact.ledger, tracer: deps.tracer, taskId, trigger: "blocking", ...(args.readFileState !== undefined ? { readFileState: args.readFileState } : {}) });
202
223
  working = blockingEdited;
203
224
  anchoredWorking = estimateContextTokens(working, charsPerToken).tokens;
204
225
  }
@@ -702,11 +702,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
702
702
  const { suspendForResource, suspendForPlatformLimit, suspendForReview } = prepareBoundaryParks({ saga, spec, checkpointStore, abortController, harness, session, sessions, sessionId, deps, priorLedger, maxSlices, maxSuspends, resourceTotal, priorSuspendCount, suspendChainBase, humanReviewRef, liveSpendRef, now, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, pausedRef, remoteEnvFailures, resourceSuspendEligible, durableSuspendInfraReady, incompleteSuspendAdapter });
703
703
  const { parkAsk } = prepareParkAsk({ askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas: rosterSeat, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, advertisedWritableDirs, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, now, humanReviewRef, abortController, harness, pausedRef });
704
704
  prepareGateStations({ askLane, parkAsk, tools, toolRosterDeltas: rosterSeat, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, planModeRef, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, hookEnvFace, writeProtectionCheck, irreversibilityTier, reversibilityProbes, abortController, shellGatedTools, autoModeDecider, autoModeDenialTracking, stopForDenialLimit, permissionRuleLane, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash });
705
- const { microCompact, charsPerToken } = prepareContextLane({ model, deps, sessionId, runId, hostTaskId, offloadStore, microCompactKnob, harness, batchHaltRef, gitStatusRef, requestLossyRef, trimPressureRef });
705
+ const { microCompact, charsPerToken } = prepareContextLane({ model, deps, sessionId, runId, hostTaskId, offloadStore, microCompactKnob, harness, batchHaltRef, gitStatusRef, requestLossyRef, trimPressureRef, ...(readFileStateForCheckpoint !== undefined ? { readFileState: readFileStateForCheckpoint } : {}) });
706
706
  const preparedHolder = {};
707
707
  const { cacheBreakDetector, cacheFingerprint, promptOverheadTokens, readTaskFile, normalizeAttachmentPath, isDedupStubResult, recentlyReadFiles, onCompactionApplied, detectExternalChanges, listBackgroundTasks, centerCompactionCandidate, effectiveReadFaceObserved, effectiveWriteFaceObserved, effectiveReadDenyObserved } = prepareTurnWiring({ 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, carrierWriteFace, readDenyAdditionsNormalized, preparedHolder });
708
708
  await onceLedger.settle(session, announcedListingsRef);
709
- const buildPrepared = () => ({ harness, session, sessionId, runId, reminderMark, reminderDisclosureCounts, editedFilesSnapshot, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp, ...(mcpRedial !== undefined ? { mcpRedial } : {}), ...(a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, gateOutcomes, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved, effectiveWriteFace: effectiveWriteFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, toolProgressRef, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}), ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, ...(permissionRuleLane !== undefined ? { persistedRuleLane: permissionRuleLane } : {}), ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, pausedRef, undeliveredContinuationRef, suspendProgressRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, workCutRef, brainCallGuardrailRef, gateStopRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolRoster, toolRosterDeltas: rosterSeat, structuredProjector, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, stopRequestedRef, announcedSnapshotRecovered: onceLedger.recovered, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, frameReceipts: { pending: [], listingSnapshotOwed: false }, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}), ...(toolDisclosureSeat !== undefined ? { toolDisclosure: toolDisclosureSeat } : {}) });
709
+ const buildPrepared = () => ({ harness, session, sessionId, runId, reminderMark, reminderDisclosureCounts, editedFilesSnapshot, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp, ...(mcpRedial !== undefined ? { mcpRedial } : {}), ...(a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, gateOutcomes, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved, effectiveWriteFace: effectiveWriteFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, toolProgressRef, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}), ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, ...(permissionRuleLane !== undefined ? { persistedRuleLane: permissionRuleLane } : {}), ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, pausedRef, undeliveredContinuationRef, suspendProgressRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, workCutRef, brainCallGuardrailRef, gateStopRef, suspendForReview, ...(readFileStateForCheckpoint !== undefined ? { readFileState: readFileStateForCheckpoint } : {}), resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolRoster, toolRosterDeltas: rosterSeat, structuredProjector, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, stopRequestedRef, announcedSnapshotRecovered: onceLedger.recovered, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, frameReceipts: { pending: [], listingSnapshotOwed: false }, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}), ...(toolDisclosureSeat !== undefined ? { toolDisclosure: toolDisclosureSeat } : {}) });
710
710
  rollback.commit();
711
711
  const prepared = buildPrepared();
712
712
  preparedHolder.current = prepared;
@@ -6,7 +6,7 @@ import { CacheBreakDetector, toolsToFingerprintInputs } from "../cache-break-det
6
6
  import { defaultTaskRegistry } from "../task-registry.js";
7
7
  import { refuseOutOfContractDecision } from "../tool-policy.js";
8
8
  import { decodeTextBytes } from "../../tools/fs/encoding.js";
9
- import { applyCompactionToReadFileState, isReadDedupStubResult } from "../../tools/fs/index.js";
9
+ import { applyContextShrinkToReadFileState, isReadDedupStubResult } from "../../tools/fs/index.js";
10
10
  import { resolveKey } from "../../tools/fs/safety.js";
11
11
  import { PROMPT_HASH_SALT } from "./prompt-hash-salt.js";
12
12
  import { CHANGED_FILES_MTIME_EPS_MS } from "./turn-attachments.js";
@@ -98,7 +98,7 @@ export function prepareTurnWiring(input) {
98
98
  .map(([path]) => path)
99
99
  : undefined;
100
100
  const onCompactionApplied = handsEnabled && readFileStateForCheckpoint
101
- ? (attachedComplete, preserveReadState) => applyCompactionToReadFileState(readFileStateForCheckpoint, attachedComplete, preserveReadState)
101
+ ? (attachedComplete, preserveReadState) => applyContextShrinkToReadFileState(readFileStateForCheckpoint, { kind: "summarized", attachedComplete, ...(preserveReadState !== undefined ? { preserveKeys: preserveReadState } : {}) })
102
102
  : undefined;
103
103
  const changedFilesReadDenied = denyNarrowingPolicy === undefined
104
104
  ? undefined
@@ -2,6 +2,7 @@ import { createSafeNotifier, observeThenableRejection } from "../safe-notify.js"
2
2
  import { forwardSafely } from "./forward-sink.js";
3
3
  import { emitTrace } from "../trace.js";
4
4
  import { primaryActivityArg } from "../arg-summary.js";
5
+ import { redactSecrets } from "../untrusted-egress.js";
5
6
  import { readDegradation } from "../../brain/degrading.js";
6
7
  import { OUTPUT_TOOL_NAME } from "./synthetic-tools.js";
7
8
  import { usageCostMicroUsd } from "./usage-accounting.js";
@@ -157,8 +158,9 @@ export function createHarnessHandlers(input) {
157
158
  rs.counters.repetitionCuts += 1;
158
159
  else
159
160
  rs.counters.repetitionSpared += 1;
161
+ const segment = redactSecrets(e.segment);
160
162
  if (rs.counters.repetitionEvents.length < rs.counters.REPETITION_EVENTS_CAP) {
161
- rs.counters.repetitionEvents.push({ turn: stats.turns + 1, action, rule: e.rule, period: e.period, reps: e.reps, segment: e.segment });
163
+ rs.counters.repetitionEvents.push({ turn: stats.turns + 1, action, rule: e.rule, period: e.period, reps: e.reps, segment });
162
164
  }
163
165
  emitTrace(rs.telemetry.tracer, () => ({
164
166
  kind: "repetition.detected",
@@ -169,7 +171,7 @@ export function createHarnessHandlers(input) {
169
171
  rule: e.rule,
170
172
  period: e.period,
171
173
  reps: e.reps,
172
- segment: e.segment,
174
+ segment,
173
175
  ts: Date.now(),
174
176
  }));
175
177
  }
@@ -1,5 +1,6 @@
1
1
  import { DEFAULT_COMPACTION_SETTINGS } from "../../internal/harness.js";
2
2
  import { planRejectionClears } from "../context-edit.js";
3
+ import { landStaleResultClears } from "./prepare-context-lane.js";
3
4
  import { DEFAULT_COMPACTION_INSTRUCTIONS, maybeCompact } from "../auto-compaction.js";
4
5
  import { emitTrace } from "../trace.js";
5
6
  import { isDegenerateCutMessage } from "../../brain/terminal-cause.js";
@@ -145,9 +146,11 @@ export function runRecoveryLanes(input) {
145
146
  }));
146
147
  }
147
148
  else {
148
- for (const e of plan.cleared) {
149
- prepared.microCompact.ledger.entries.set(e.key, { marker: e.marker, groupCount: e.groupCount, fp: e.fp });
150
- }
149
+ landStaleResultClears({
150
+ ledger: prepared.microCompact.ledger,
151
+ cleared: plan.cleared,
152
+ ...(prepared.readFileState !== undefined ? { readFileState: prepared.readFileState } : {}),
153
+ });
151
154
  emitTrace(rs.telemetry.tracer, () => ({
152
155
  kind: "context.mc_clear",
153
156
  version: 1,
@@ -1,3 +1,4 @@
1
+ import { primaryActivityArg } from "../arg-summary.js";
1
2
  import { remainingBudgetMicroUsd, remainingTokens } from "../checkpoint-store.js";
2
3
  import { computeCostMicroUsd, isModelPriced, malformedPricingField, modelCostToPricing } from "../pricing.js";
3
4
  import { resolveModel } from "../roles.js";
@@ -19,7 +20,16 @@ export function runTelemetryAndBudgetSeats(input) {
19
20
  prepared.humanReviewRef.count += 1;
20
21
  prepared.humanReviewRef.totalWaitMs += waitMs;
21
22
  const gateToolName = "toolName" in resume.cp.gate ? resume.cp.gate.toolName : undefined;
22
- prepared.humanReviewRef.gates.push({ kind: resume.cp.gate.kind, waitMs, ...(decision !== undefined ? { decision } : {}), ...(gateToolName !== undefined ? { toolName: gateToolName } : {}) });
23
+ const gatedCall = resume.cp.pendingAction.kind === "tool_approval" ? resume.cp.pendingAction : undefined;
24
+ const gateToolArg = gatedCall === undefined ? undefined : primaryActivityArg(gatedCall.args);
25
+ prepared.humanReviewRef.gates.push({
26
+ kind: resume.cp.gate.kind,
27
+ waitMs,
28
+ ...(decision !== undefined ? { decision } : {}),
29
+ ...(gateToolName !== undefined ? { toolName: gateToolName } : {}),
30
+ ...(gatedCall !== undefined ? { toolCallId: gatedCall.toolCallId } : {}),
31
+ ...(gateToolArg !== undefined ? { toolArg: gateToolArg } : {}),
32
+ });
23
33
  }
24
34
  if (resume !== undefined && resume.outcome.gate === "plan_review" && resume.outcome.decision === "reject") {
25
35
  prepared.planModeRef.active = true;
@@ -12,36 +12,18 @@
12
12
  *
13
13
  * Two readers, one compile: the gate station mounts `check`, and the leg's wiring manifest reports
14
14
  * `targetView`. Compiling twice would be two judges free to disagree about the seats they were handed.
15
+ *
16
+ * The SECOND absence this seat carries is the per-judgment one: a judge that HAS its target view but
17
+ * whose environment could not answer a particular target drops that view and judges by the spelling alone.
18
+ * That is a run-time fact, so it cannot be announced at compile like the one above — but it is a fact about
19
+ * the ENVIRONMENT rather than about any one write, so it is announced from here, once per leg per reason,
20
+ * and carries no call identity. This is the leg-scoped home the dedupe needs: the judge is compiled once
21
+ * per leg and every judgment of the leg goes through the check minted here.
15
22
  */
16
23
  import type { ExecutionEnv } from "../../internal/harness.js";
17
24
  import type { RunnerDeps } from "../runner-deps.js";
18
25
  import type { ToolRoster } from "../tool-roster.js";
19
26
  import { type WriteProtectionMount } from "../write-protect.js";
20
- /**
21
- * Compile the leg's write-protection judge and announce a missing target view.
22
- *
23
- * Returns the mount, or `undefined` when the deployment emptied the table (`[]`): no judge, no tighten
24
- * mounted at all, and nothing announced — an explicit opt-out is not a degrade. A garbage table refuses
25
- * prepare loudly from here, as it did from the gate station before, so an unusable table cannot lie
26
- * dormant on a write-tool-less task.
27
- *
28
- * The notice fires exactly once per prepared leg and only when a judge with NO target view sits beside at
29
- * least one tool whose writes it covers. Coverage runs the judge's OWN per-call test ({@link
30
- * protectivePathTargetOf}) rather than a copy of it, over every name a row DISPATCHES under — its own and
31
- * its aliases, because the gate hands the judge the name the call was made with. A row that declares a
32
- * path target is covered by that declaration under every name; a row that declares none is covered under
33
- * whichever of its names the catalog gives a non-read slot to. Both halves are load-bearing: a tool named
34
- * `Write` that declares nothing, and a tool aliased to `Write` that declares nothing, are each judged by
35
- * spelling alone on a hands-less leg, and each would otherwise go unnamed by the very line that exists to
36
- * name them. Reported by the ROW's own name — what the deployment mounted.
37
- *
38
- * SNAPSHOT BOUNDARY, deliberate and disclosed: the roster read here is the LEG-START one, the same one
39
- * the wiring manifest reports and for the same reason — this is one announcement per prepared leg, not a
40
- * subscription. A run that gains its FIRST covered write tool after prepare (a mid-run roster delta) is
41
- * judged by the same spelling-only judge and is not announced; the leg's `writeProtection.targetView`
42
- * still reports the judge's reading correctly, because the judge never gains an environment mid-run —
43
- * only the covered SET can widen.
44
- */
45
27
  export declare function mountWriteProtection(input: {
46
28
  /** borrowed-readonly — the deployment seats: the table (`writeProtectedPaths`; absent ⇒ the default
47
29
  * table, `[]` ⇒ opted out), the data root the memory library is materialized under
@@ -1,11 +1,29 @@
1
1
  import { deliverEngineNotice } from "../engine-notice.js";
2
2
  import { protectivePathTargetOf } from "../tool-registry.js";
3
3
  import { createWriteProtectionCheck } from "../write-protect.js";
4
+ const ABSENCE_REASON = { symlink: "unresolved-symlink", target: "unresolvable-target" };
4
5
  export function mountWriteProtection(input) {
6
+ const announced = new Set();
5
7
  const mount = createWriteProtectionCheck(input.deps.writeProtectedPaths, {
6
8
  ...(input.deps.memoryEngineDir !== undefined ? { dataRoot: input.deps.memoryEngineDir } : {}),
7
9
  ...(input.env !== undefined ? { env: input.env } : {}),
8
10
  rootPath: input.rootPath,
11
+ onViewAbsent: (absence) => {
12
+ const reason = absence.unresolvedSymlink ? ABSENCE_REASON.symlink : ABSENCE_REASON.target;
13
+ if (announced.has(reason))
14
+ return;
15
+ announced.add(reason);
16
+ deliverEngineNotice(input.deps.onNotice, {
17
+ code: "config.write_protection_unresolved",
18
+ message: `This run's write-protection judge could not resolve a write target in its execution environment ` +
19
+ `(${reason === ABSENCE_REASON.symlink ? "a symlink whose real target is unknowable" : "the environment refused or errored on the spelling"}: ${absence.cause}). ` +
20
+ `A view that cannot answer does not vote, so that write was judged by its SPELLING against the table and nothing else — ` +
21
+ `a target reaching a protected path only through an alias, a bind mount or a ".." walk is cleared. Nothing about this run was ` +
22
+ `refused because of it, and nothing here is a defect in the write itself: an environment that cannot answer path questions is a ` +
23
+ `deployment fact. Announced once per leg per reason; later writes of the same shape are judged the same way, silently.`,
24
+ detail: { reason, cause: absence.cause, sessionId: input.sessionId, runId: input.runId },
25
+ });
26
+ },
9
27
  });
10
28
  if (mount === undefined || mount.targetView !== "spelling-only")
11
29
  return mount;
@@ -185,10 +185,6 @@ export declare class TaskRegistry {
185
185
  tupleIncomplete: true;
186
186
  })>>;
187
187
  };
188
- /** design/151 §7.7 (F-13) — the row's mailbox dies with the row: a WINNING delete also drops
189
- * the (scope, handle) mailbox (advisory — a mailbox fault never blocks the reap; an orphaned
190
- * box is bounded by the mailbox's own `reap` policy). Structural face (drop only), so the
191
- * deployment passes its `RunnerDeps.mailboxStore` directly. */
192
188
  mailbox?: {
193
189
  drop(scope: string, handle: string): Promise<void>;
194
190
  };
@@ -634,8 +630,6 @@ export declare class TaskRegistry {
634
630
  private pollBackgroundBash;
635
631
  private pollWorkflow;
636
632
  private stopBackgroundBash;
637
- /** design/135 G2: TaskOutput over a monitor — a RE-READABLE spool snapshot (the watcher owns the env
638
- * cursor, exactly the bash G2b spool posture): repeated polls return the same accumulated output. */
639
633
  private pollMonitor;
640
634
  private stopMonitor;
641
635
  private stopWorkflow;
@@ -38,6 +38,30 @@ export type TaskStatus = "completed" | "blocked" | "failed" | "suspended" | "nee
38
38
  * One remote-workspace lifecycle failure, reported as data on {@link TaskResult.remoteEnvFailures}.
39
39
  * See that field for why the eleven-code taxonomy needed a structured channel to reach a caller at all.
40
40
  */
41
+ /**
42
+ * ONE `humanReview` ledger row — what `stats.humanReview.gates[]` carries, what the runner's live
43
+ * accumulator holds, and what a checkpoint persists. It lived as six structurally-identical inline
44
+ * copies, the persisted one NARROWER than the rest, so "does a row carry the call it gated?" had six
45
+ * answers and the persisted one said no while the bytes it stored said yes. One declaration now.
46
+ */
47
+ export interface HumanReviewGateRow {
48
+ /** `kind` / `decision` / `toolName` / `toolArg` are documented once, on `stats.humanReview.gates`. */
49
+ kind: string;
50
+ waitMs: number;
51
+ decision?: string;
52
+ toolName?: string;
53
+ toolArg?: string;
54
+ /**
55
+ * The id of the tool call this row gated — the row's IDENTITY, not a display field. Minted with the
56
+ * row at each of the four mint sites, never re-derived downstream: a run pushes rows from several
57
+ * mints while only one passes through the host's `onAsk`, so the two ledgers' counts are legitimately
58
+ * unequal and there was no key to reconcile on. Absent means one thing — the mint had no call id to
59
+ * give (a probe row; a durable gate whose checkpoint carries a non-`tool_approval` pending action,
60
+ * since a `resource_limit` / `needs_review` / `plan_review` pause gates no call; or an older engine).
61
+ * Never a placeholder.
62
+ */
63
+ toolCallId?: string;
64
+ }
41
65
  export interface RemoteEnvFailureNote {
42
66
  /** Which VM-lifecycle call failed. */
43
67
  op: "suspendVM" | "resumeVM" | "postResumeInit";
@@ -781,16 +805,10 @@ export interface TaskResult {
781
805
  * `irreversible_ask` for durable; `human` for a synchronous ask). `decision` = the adjudication when
782
806
  * known (`allow`/`deny`). design/99 MF-24: `toolName` = the gated tool; `toolArg` = a
783
807
  * SHORT, SECRET-SCRUBBED one-line summary of its input (the same `primaryActivityArg` boundary as the MF-W
784
- * activity arg) — the permission-denial ledger's "denied: Bash(rm …)" display. `toolArg` is set on the
785
- * SYNCHRONOUS-ask path (the input is in scope); a durable-resume gate carries `toolName` only (its input is
786
- * not threaded onto the persisted gate — a documented follow-on). */
787
- gates: Array<{
788
- kind: string;
789
- waitMs: number;
790
- decision?: string;
791
- toolName?: string;
792
- toolArg?: string;
793
- }>;
808
+ * activity arg) — the permission-denial ledger's "denied: Bash(rm …)" display. `toolArg` rides every mint
809
+ * that has the input in scope: the synchronous ask from the request, the durable-resume gate from the
810
+ * checkpoint's `pendingAction`; `toolCallId` names the call the row gated (absent = not joinable). */
811
+ gates: HumanReviewGateRow[];
794
812
  };
795
813
  /**
796
814
  * design/80 D-E-core: a thin FINANCE TAXONOMY of the LLM-derived costs the engine actually prices,