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