@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.
- package/CHANGELOG.md +210 -1
- package/dist/core/auto-compaction.d.ts +2 -2
- package/dist/core/checkpoint-store.d.ts +1 -5
- package/dist/core/context-edit.js +1 -1
- package/dist/core/engine-notice.d.ts +13 -0
- package/dist/core/gate-bounded-await.d.ts +14 -4
- package/dist/core/gate-bounded-await.js +1 -1
- package/dist/core/gate-crash-trace.d.ts +7 -0
- package/dist/core/gate-crash-trace.js +3 -0
- package/dist/core/gate-fold.js +5 -4
- package/dist/core/gate-lanes.js +2 -1
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/hooks.js +2 -1
- package/dist/core/park-selfcheck.js +1 -1
- package/dist/core/path-identity.d.ts +11 -4
- package/dist/core/path-identity.js +5 -2
- package/dist/core/read-root-candidate.js +11 -1
- package/dist/core/runner/assemble-result.d.ts +2 -7
- package/dist/core/runner/assemble-result.js +25 -8
- package/dist/core/runner/contracts.d.ts +15 -15
- package/dist/core/runner/gate-exit.d.ts +0 -5
- package/dist/core/runner/gate-exit.js +0 -3
- package/dist/core/runner/inherited-ask-grants.d.ts +2 -7
- package/dist/core/runner/inherited-ask-grants.js +1 -0
- package/dist/core/runner/prepare-ask-lane.js +1 -0
- package/dist/core/runner/prepare-context-lane.d.ts +43 -1
- package/dist/core/runner/prepare-context-lane.js +25 -4
- package/dist/core/runner/prepare-task.js +2 -2
- package/dist/core/runner/prepare-turn-wiring.js +2 -2
- package/dist/core/runner/run-harness-handlers.js +4 -2
- package/dist/core/runner/run-recovery-lanes.js +6 -3
- package/dist/core/runner/run-telemetry-and-budget-seats.js +11 -1
- package/dist/core/runner/write-protection-station.d.ts +7 -25
- package/dist/core/runner/write-protection-station.js +18 -0
- package/dist/core/task-registry.d.ts +0 -6
- package/dist/core/task-result.d.ts +28 -10
- package/dist/core/types.d.ts +1 -1
- package/dist/core/write-protect.d.ts +64 -12
- package/dist/core/write-protect.js +87 -34
- package/dist/engine/execution-env/node-execution-env.d.ts +19 -2
- package/dist/engine/execution-env/node-execution-env.js +5 -1
- package/dist/index.d.ts +1 -1
- package/dist/tools/fs/bash-lexer.d.ts +12 -5
- package/dist/tools/fs/bash-lexer.js +5 -2
- package/dist/tools/fs/bash-program-position.d.ts +18 -0
- package/dist/tools/fs/bash-program-position.js +13 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +10 -3
- package/dist/tools/fs/bash-readonly-classifier.js +70 -41
- package/dist/tools/fs/fs-bash.d.ts +22 -0
- package/dist/tools/fs/fs-bash.js +19 -12
- package/dist/tools/fs/fs-read.js +2 -1
- package/dist/tools/fs/fs-shared.d.ts +33 -7
- package/dist/tools/fs/fs-shared.js +12 -1
- package/dist/tools/fs/safety.d.ts +37 -0
- package/dist/tools/fs/safety.js +31 -3
- package/package.json +2 -2
- package/test/export-surface.snapshot.json +3 -1
- package/wire-changes/core@7.23.0.json +316 -0
- 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:
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (code
|
|
21
|
-
|
|
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
|
-
|
|
39
|
+
cur = cur.cause;
|
|
24
40
|
}
|
|
25
|
-
|
|
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 =
|
|
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:
|
|
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:
|
|
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:
|
|
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 };
|
|
@@ -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
|
-
|
|
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 {
|
|
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) =>
|
|
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
|
|
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
|
|
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
|
-
|
|
149
|
-
prepared.microCompact.ledger
|
|
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
|
-
|
|
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`
|
|
785
|
-
*
|
|
786
|
-
*
|
|
787
|
-
gates:
|
|
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,
|