@sema-agent/core 5.65.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/dist/agents/subagent.d.ts +2 -2
- package/dist/agents/subagent.js +11 -0
- package/dist/agents/verify.d.ts +1 -1
- package/dist/brain/anthropic.js +1 -1
- package/dist/brain/errors.d.ts +29 -0
- package/dist/brain/errors.js +20 -0
- package/dist/brain/open-responses.js +2 -2
- package/dist/brain/route-adjudicator.d.ts +8 -1
- package/dist/brain/route-adjudicator.js +1 -0
- package/dist/brain/status-sink.js +12 -1
- package/dist/brain/stream-engine.js +17 -6
- package/dist/core/auto-compaction.d.ts +26 -0
- package/dist/core/auto-compaction.js +7 -2
- package/dist/core/auto-mode-arming.d.ts +138 -0
- package/dist/core/auto-mode-arming.js +181 -0
- package/dist/core/auto-mode-defaults.d.ts +13 -0
- package/dist/core/auto-mode-defaults.js +5 -0
- package/dist/core/auto-mode-prompt.d.ts +14 -3
- package/dist/core/auto-mode-prompt.js +10 -7
- package/dist/core/auto-mode-rebuild.d.ts +75 -0
- package/dist/core/auto-mode-rebuild.js +41 -0
- package/dist/core/auto-mode.d.ts +15 -0
- package/dist/core/auto-mode.js +4 -2
- package/dist/core/checkpoint-store.d.ts +18 -0
- package/dist/core/context-edit.d.ts +47 -5
- package/dist/core/context-guard.d.ts +1 -1
- package/dist/core/file-history-retention.d.ts +106 -0
- package/dist/core/file-history-retention.js +36 -0
- package/dist/core/file-history-store.d.ts +768 -0
- package/dist/core/file-history-store.js +880 -0
- package/dist/core/governance-codes.d.ts +2 -1
- package/dist/core/governance-codes.js +14 -0
- package/dist/core/hooks.d.ts +39 -7
- package/dist/core/hooks.js +38 -21
- package/dist/core/lsp.d.ts +2 -2
- package/dist/core/mcp.d.ts +29 -7
- package/dist/core/memory-engine/consolidation-driver.d.ts +11 -0
- package/dist/core/memory-engine/consolidation-driver.js +71 -4
- package/dist/core/memory-engine/consolidation.d.ts +25 -2
- package/dist/core/memory-engine/consolidation.js +4 -1
- package/dist/core/memory-engine/distiller.d.ts +84 -1
- package/dist/core/memory-engine/distiller.js +68 -0
- package/dist/core/memory-engine/dual-root.js +3 -0
- package/dist/core/memory-engine/engine.d.ts +328 -15
- package/dist/core/memory-engine/engine.js +355 -29
- package/dist/core/memory-engine/file-backend.d.ts +30 -0
- package/dist/core/memory-engine/file-backend.js +14 -13
- package/dist/core/memory-engine/frontmatter.d.ts +22 -1
- package/dist/core/memory-engine/frontmatter.js +3 -0
- package/dist/core/memory-engine/header-hints.d.ts +5 -0
- package/dist/core/memory-engine/index.d.ts +5 -4
- package/dist/core/memory-engine/index.js +5 -4
- package/dist/core/memory-engine/layout.d.ts +88 -2
- package/dist/core/memory-engine/layout.js +112 -3
- package/dist/core/memory-engine/provenance-wording.d.ts +7 -0
- package/dist/core/memory-engine/provenance-wording.js +3 -0
- package/dist/core/memory-engine/tools.d.ts +89 -8
- package/dist/core/memory-engine/tools.js +263 -22
- package/dist/core/memory-engine/types.d.ts +64 -1
- package/dist/core/memory-recall.d.ts +6 -0
- package/dist/core/memory.d.ts +27 -1
- package/dist/core/memory.js +16 -2
- package/dist/core/permission-rule-consent.d.ts +20 -0
- package/dist/core/permission-rule-consent.js +12 -3
- package/dist/core/permission-rule-model.d.ts +67 -7
- package/dist/core/permission-rule-model.js +53 -7
- package/dist/core/permission-rule-store.js +15 -10
- package/dist/core/permission-rule-sync.js +15 -11
- package/dist/core/retention-policy.d.ts +9 -0
- package/dist/core/retention-policy.js +5 -2
- package/dist/core/retention.d.ts +13 -2
- package/dist/core/runner/assemble-result.d.ts +19 -1
- package/dist/core/runner/assemble-result.js +17 -2
- package/dist/core/runner/compaction-call-options.d.ts +93 -0
- package/dist/core/runner/compaction-call-options.js +3 -0
- package/dist/core/runner/memory-capture-optout.d.ts +80 -0
- package/dist/core/runner/memory-capture-optout.js +53 -0
- package/dist/core/runner/prepare-config-doors.d.ts +5 -0
- package/dist/core/runner/prepare-config-doors.js +16 -0
- package/dist/core/runner/prepare-hands-readface.d.ts +110 -5
- package/dist/core/runner/prepare-hands-readface.js +99 -7
- package/dist/core/runner/prepare-memory.d.ts +88 -0
- package/dist/core/runner/prepare-memory.js +305 -24
- package/dist/core/runner/prepare-task.d.ts +141 -1
- package/dist/core/runner/prepare-task.js +443 -79
- package/dist/core/runner/runtask.d.ts +9 -20
- package/dist/core/runner/runtask.js +133 -96
- package/dist/core/runner/session-file-state-replay.d.ts +18 -10
- package/dist/core/runner/session-file-state-replay.js +52 -1
- package/dist/core/runner/tool-disclosure.js +2 -1
- package/dist/core/runner/turn-attachments.d.ts +22 -12
- package/dist/core/session-store.d.ts +1 -1
- package/dist/core/session-store.js +6 -1
- package/dist/core/session.d.ts +34 -1
- package/dist/core/store-contracts/file-history-store-contract.d.ts +3 -0
- package/dist/core/store-contracts/file-history-store-contract.js +720 -0
- package/dist/core/task-registry-shared.js +11 -1
- package/dist/core/tool-errors.js +1 -0
- package/dist/core/tool-policy.d.ts +172 -1
- package/dist/core/tool-policy.js +32 -1
- package/dist/core/tool-result-store.js +2 -1
- package/dist/core/trace.d.ts +24 -0
- package/dist/core/types.d.ts +784 -89
- package/dist/core/types.js +4 -3
- package/dist/core/untrusted-text.d.ts +1 -1
- package/dist/core/untrusted-text.js +8 -0
- package/dist/core/workflow-run-store-contract.js +6 -2
- package/dist/core/workflow-run-store.d.ts +4 -1
- package/dist/engine/compaction/compaction.d.ts +88 -10
- package/dist/engine/compaction/compaction.js +109 -30
- package/dist/engine/execution-env/node-execution-env.d.ts +9 -1
- package/dist/engine/execution-env/node-execution-env.js +28 -0
- package/dist/engine/harness/agent-harness.d.ts +52 -1
- package/dist/engine/harness/agent-harness.js +36 -1
- package/dist/engine/harness/types.d.ts +26 -1
- package/dist/engine/llm/types.d.ts +50 -4
- package/dist/engine/loop/agent-loop.d.ts +5 -1
- package/dist/engine/loop/agent-loop.js +25 -0
- package/dist/engine/loop/types.d.ts +19 -0
- package/dist/engine/lsp/node-lsp-manager.d.ts +1 -1
- package/dist/engine/session/session.js +1 -1
- package/dist/index.d.ts +18 -8
- package/dist/index.js +14 -6
- package/dist/orchestration/run-workflow-tool.d.ts +20 -2
- package/dist/orchestration/run-workflow-tool.js +22 -3
- package/dist/orchestration/workflow-governance.d.ts +59 -1
- package/dist/orchestration/workflow-governance.js +61 -8
- package/dist/orchestration/workflow-meta.d.ts +4 -2
- package/dist/orchestration/workflow-primitives.js +56 -13
- package/dist/orchestration/workflow-types.d.ts +78 -2
- package/dist/orchestration/workflow.d.ts +20 -0
- package/dist/orchestration/workflow.js +163 -14
- package/dist/prompt-assembly/event-registry.js +1 -1
- package/dist/prompts/default.d.ts +7 -7
- package/dist/stores/file/file-history-store.d.ts +368 -0
- package/dist/stores/file/file-history-store.js +1248 -0
- package/dist/stores/file/index.d.ts +22 -13
- package/dist/stores/file/index.js +4 -4
- package/dist/stores/file/permission-rule-store.js +1 -0
- package/dist/stores/file/strategy-store.d.ts +3 -3
- package/dist/tools/fs/bash-readonly-classifier.d.ts +87 -3
- package/dist/tools/fs/bash-readonly-classifier.js +106 -4
- package/dist/tools/fs/fs-bash.js +9 -5
- package/dist/tools/fs/fs-shared.d.ts +52 -1
- package/dist/tools/fs/fs-shared.js +14 -0
- package/dist/tools/fs/fs-write.d.ts +5 -5
- package/dist/tools/fs/fs-write.js +71 -14
- package/dist/tools/fs/index.d.ts +6 -1
- package/dist/tools/fs/index.js +1 -1
- package/dist/tools/web.js +2 -1
- package/package.json +5 -1
- package/test/export-surface.snapshot.json +155 -23
- package/dist/core/file-snapshot-store.d.ts +0 -165
- package/dist/core/file-snapshot-store.js +0 -259
- package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +0 -13
- package/dist/core/store-contracts/file-snapshot-store-contract.js +0 -134
- package/dist/stores/file/file-snapshot-store.d.ts +0 -58
- package/dist/stores/file/file-snapshot-store.js +0 -353
|
@@ -29,6 +29,99 @@ import type { Prepared } from "./prepare-task.js";
|
|
|
29
29
|
* task has no hands (no `readTaskFile` ⇒ nothing can be re-read).
|
|
30
30
|
*/
|
|
31
31
|
export declare function buildWorkingFileAttachments(spec: TaskSpec, prepared: Prepared): MaybeCompactOptions["workingFileAttachments"];
|
|
32
|
+
/**
|
|
33
|
+
* #500 — `MaybeCompactOptions.contextInstructionFiles`: the seat that delivers the deployment's own
|
|
34
|
+
* instruction-file content (the CLAUDE.md family) to the SUMMARIZER, assembled once here and spread
|
|
35
|
+
* on all three lanes. Without it the compaction boundary is the one place a long task loses the
|
|
36
|
+
* standing instructions every other turn has been reading — and it loses them exactly where the
|
|
37
|
+
* summary REPLACES the transcript that carried them, so the loss is durable.
|
|
38
|
+
*
|
|
39
|
+
* WHICH BYTES, and why the neighbours are not candidates. `Prepared.projectInstructionContent` =
|
|
40
|
+
* the RAW loader text (`ProjectMemoryLoad.content`, exactly the string prepare-task feeds
|
|
41
|
+
* `composeMemoryBlock(projectMem, "project")`), captured beside the existing `instructionSources`
|
|
42
|
+
* capture under the SAME non-blank predicate that composes the project layer:
|
|
43
|
+
* · the assembled `memoryBlock` is NOT a candidate. It is the memory ENGINE's composed layers —
|
|
44
|
+
* bytes the model itself authored in-band this session through the memory tools — with the
|
|
45
|
+
* project layer appended at the tail. The seat's contract is host/systemPrompt tier and says in
|
|
46
|
+
* terms: never from tool output, model text, or any other in-band channel. Handing
|
|
47
|
+
* model-authored bytes an envelope whose preamble tells the summarizer they may instruct it is
|
|
48
|
+
* the laundering shape, not a purity nit. (Mechanically moot too — `memoryBlock` is a
|
|
49
|
+
* prepare-task local and never reaches `Prepared`.)
|
|
50
|
+
* · the COMPOSED project block is not it either, and this one is checkable rather than a
|
|
51
|
+
* judgement: `contextInstructionFilesSection` neutralizes the whole authority family
|
|
52
|
+
* (ENGINE_AUTHORITY_ENVELOPE_TAGS) over its payload, and `user_memory`/`scope` ARE in that
|
|
53
|
+
* family — so feeding a composed block would have the engine rewrite the tags of its own
|
|
54
|
+
* composition. Raw text in, one fence at the mint.
|
|
55
|
+
* · `prepared.instructionSources` carries path + contentHash only, by design (core never learns a
|
|
56
|
+
* file-name policy), so it cannot serve this seat at all.
|
|
57
|
+
*
|
|
58
|
+
* PROVENANCE, stated plainly because "the host supplies it" is the easy half of the truth. On this
|
|
59
|
+
* engine the value is REPOSITORY-CONTROLLED text: the loading block one file over calls a CLAUDE.md
|
|
60
|
+
* of unknown authorship untrusted in so many words, the MAIN lane's own framing tells the model it is
|
|
61
|
+
* "repository-controlled DATA, not instructions to obey", and this seat hands the same bytes to the
|
|
62
|
+
* summarizer with instruction standing for the summarization task. Delivery THROUGH a host callback
|
|
63
|
+
* is not host AUTHORSHIP: a prior agent, or anyone who can land a commit, can write a
|
|
64
|
+
* "## Compact Instructions" section, and the mint's neutralization stops tag breakout — not a
|
|
65
|
+
* sentence asking for a fact to be left out of the summary that replaces the transcript.
|
|
66
|
+
* Why it is nevertheless this value: the seat's own contract names this path ("hosts pass the same
|
|
67
|
+
* instruction-file content their prompt assembly mounts... on the runner path"), the parity form this
|
|
68
|
+
* channel exists to reach reads the user's and the project's instruction files at every boundary with
|
|
69
|
+
* exactly this standing, and the two alternatives are worse for reasons above rather than better.
|
|
70
|
+
* The standing is also narrower than the framing gap suggests — the section's preamble scopes it to
|
|
71
|
+
* guidance about HOW to summarize and classifies the rest as background — and a deployment that does
|
|
72
|
+
* not want it has two exits that need no code: do not wire the loader, or return content it vouches
|
|
73
|
+
* for. Recorded as an open design candidate rather than built here, because it is a contract change
|
|
74
|
+
* and not a wiring one: a SEPARATE deployment-vouched summarization-instructions seat, leaving
|
|
75
|
+
* repository-derived context at data tier.
|
|
76
|
+
*
|
|
77
|
+
* RULING ① — BOTH summary forms, no per-form split. Fork eligibility is decided per pass INSIDE
|
|
78
|
+
* maybeCompact (main-model-only, recorded-request-present, not lossy), so a call site cannot say
|
|
79
|
+
* "independent form only"; the closest approximation — pass the seat only when `prepared.compModel`
|
|
80
|
+
* is set, since an independent compaction model has no main prefix to fork — leaves the hole the
|
|
81
|
+
* wrong way round: the configuration it does NOT cover (no compaction model) is precisely the one
|
|
82
|
+
* where a fork degrades mid-pass to the independent form, which would then run WITHOUT the files.
|
|
83
|
+
* Feeding both forms closes that case by construction, and the library face already handles the fork
|
|
84
|
+
* half (the section rides the APPENDED instruction message, never the cached prefix, so the fork's
|
|
85
|
+
* whole economic point is untouched).
|
|
86
|
+
*
|
|
87
|
+
* RULING ② — no new size knob. The bound is the library's own `fitContextInstructionFilesSection`
|
|
88
|
+
* (independent form: cap against the compaction model's window, disclosed elision, and a complete
|
|
89
|
+
* yield when not even the framing fits). Its three KNOWN LIMITS are accepted here KNOWINGLY, as the
|
|
90
|
+
* pre-condition of this wiring rather than something it discovered: a token-dense payload (CJK,
|
|
91
|
+
* emoji) can pass a chars-domain fit and still overflow the gateway's tokenizer; an unknown
|
|
92
|
+
* `contextWindow` leaves the section unbounded (the conversation clamp declines to guess there too);
|
|
93
|
+
* and the FORK form cannot fit at all — its bound is a prompt-too-long that degrades ONCE to the
|
|
94
|
+
* independent form, which the engine's own note records as real but NOT total (an oversize rejection
|
|
95
|
+
* the classifier cannot read as prompt-too-long returns terminally instead).
|
|
96
|
+
*
|
|
97
|
+
* HOW BIG THAT ACCEPTANCE ACTUALLY IS — measured, because the sentence above is easy to read more
|
|
98
|
+
* comfortably than it deserves. The fitted form is NOT the default path: when a fork is eligible and
|
|
99
|
+
* the model answers in its envelope, the fork IS the pass, and its section is delivered WHOLE at any
|
|
100
|
+
* size. The independent form — the only one that fits — runs when there is no fork to take or the
|
|
101
|
+
* fork's answer did not conform. Both facts are pinned in the REF-A2 suite (a 55 KB payload: whole on
|
|
102
|
+
* the fork, elided on the independent fallback). And when a limit does bite, the failure is not a
|
|
103
|
+
* quiet degradation: the section is a FIXED part of the request, the reactive retry loop may drop
|
|
104
|
+
* only CONVERSATION groups, so a request the SECTION overflowed cannot converge and the boundary
|
|
105
|
+
* fails — the task loses that compaction (or that prompt-too-long recovery, or its end-of-task
|
|
106
|
+
* summary) rather than sending a shorter one. The exposure is what the wiring widened: this was
|
|
107
|
+
* previously reachable only by hosts calling the library directly, and is now reachable by every
|
|
108
|
+
* deployment that wires `loadProjectMemory` with a large instruction file and an unknown or small
|
|
109
|
+
* window. Making the section participate in the retry loop is the structural answer; it is a change
|
|
110
|
+
* to that loop's contract, tracked at the library face, and a runner-side knob would only move the
|
|
111
|
+
* decision to a place with less information.
|
|
112
|
+
*
|
|
113
|
+
* WHAT IT COSTS, measured rather than rounded: every deployment that wires `loadProjectMemory` now
|
|
114
|
+
* sends its instruction-file content plus EXACTLY 716 chars of framing (the preamble and the fence)
|
|
115
|
+
* on EVERY summarization request, both forms, once per pass — where a real project instruction file
|
|
116
|
+
* in this family runs to tens of KB, so the payload, not the framing, is the bill. The number is
|
|
117
|
+
* pinned in the REF-A2 suite against the engine's own renderer, so a preamble edit shows up as a
|
|
118
|
+
* changed assertion rather than as prose drifting away from the code it describes.
|
|
119
|
+
* Deployments that never wired the loader pay nothing: the key is absent, not
|
|
120
|
+
* `undefined`, so their requests are byte-identical to the pre-wiring shape on the independent and
|
|
121
|
+
* UPDATE passes (the FORK form appends its ~860-char authority clause with or without the seat, and
|
|
122
|
+
* always did).
|
|
123
|
+
*/
|
|
124
|
+
export declare function contextInstructionFilesOption(prepared: Prepared): Partial<Pick<MaybeCompactOptions, "contextInstructionFiles">>;
|
|
32
125
|
/**
|
|
33
126
|
* design/169-A — the CC-form fork seam, assembled identically on all three lanes (REF-A2): the
|
|
34
127
|
* summary request forks the main lane's latest real request (recorded by prepare-task's provider
|
|
@@ -14,6 +14,9 @@ export function buildWorkingFileAttachments(spec, prepared) {
|
|
|
14
14
|
...(typeof spec.compaction?.attachWorkingFiles === "object" ? spec.compaction.attachWorkingFiles : undefined),
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
|
+
export function contextInstructionFilesOption(prepared) {
|
|
18
|
+
return prepared.projectInstructionContent !== undefined ? { contextInstructionFiles: prepared.projectInstructionContent } : {};
|
|
19
|
+
}
|
|
17
20
|
export function forkContextOption(prepared, disable) {
|
|
18
21
|
return disable ? {} : { forkContext: prepared.lastBrainContext };
|
|
19
22
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* design/383 §3 — the session capture opt-out's ENTITLEMENT ADJUDICATOR (slice 3's core): one pure
|
|
3
|
+
* function every ingress shares — the prepare-time declaration, the resume-time standing-record
|
|
4
|
+
* re-check (§3.3), and the Runner flip verb's fresh-resolve closure — so the posture table cannot
|
|
5
|
+
* fork across call sites.
|
|
6
|
+
*
|
|
7
|
+
* The separation law (§3.3) is the module's spine: THIS adjudicator answers "may the declaration /
|
|
8
|
+
* the continued run proceed"; the one-way control-plane RECORD answers "how does this session run"
|
|
9
|
+
* (record wins for capture — nothing here ever re-enables capture for a recorded session; the
|
|
10
|
+
* governed/capture-required continuation arm refuses to RUN instead, keeping both promises: zero
|
|
11
|
+
* capture and zero out-of-policy running, availability paid).
|
|
12
|
+
*
|
|
13
|
+
* The two fault polarities, and why both are deliberate (the code-side home of the §3.1 cost
|
|
14
|
+
* table):
|
|
15
|
+
* - `"open"` (and absent): a resolver fault ALLOWS the opt-out (+ named disclosure). Wrongly
|
|
16
|
+
* allowing costs one uncaptured session — recoverable. Wrongly denying captures a session whose
|
|
17
|
+
* user explicitly said no — irreversible (read, foldable into products, syncable). The
|
|
18
|
+
* fail-safe faces the irreversible side's OPPOSITE, which is why this member is the documented
|
|
19
|
+
* exception to the caps family's deny-on-outage degrade.
|
|
20
|
+
* - `"governed"`: the deployment has EXPLICITLY declared the compliance side's irreversibility
|
|
21
|
+
* (a retention-required principal acquiring an opt-out through an outage window) the heavier
|
|
22
|
+
* one, so a fault/absent verdict refuses the run — fail-closed, the declared direction.
|
|
23
|
+
* A mutation flipping the governed fault arm to the open polarity must turn the G-姿态 escape pin
|
|
24
|
+
* red (m12).
|
|
25
|
+
*/
|
|
26
|
+
/** The screened posture (prepare-config-doors folds absent → `"open"`). */
|
|
27
|
+
export type MemoryCapturePosture = "open" | "governed" | "capture-required";
|
|
28
|
+
/** The per-principal entitlement input as RESOLVED for this adjudication:
|
|
29
|
+
* - `value` — `RuntimeCaps.allowMemoryOptOut` when the resolver answered a boolean; `undefined`
|
|
30
|
+
* when absent OR when the resolver faulted (the F2 fault-transit contract: a fault must never
|
|
31
|
+
* be encoded as an explicit value);
|
|
32
|
+
* - `faulted` — the resolve THREW / timed out / answered a non-boolean (the non-boolean case is
|
|
33
|
+
* a fault by the member's own contract, never truthiness-read). Distinguished from a clean
|
|
34
|
+
* `undefined` only for DISCLOSURE under `"open"` (both allow there) — under `"governed"` both
|
|
35
|
+
* are the same refusal (verdict absent = face fault). */
|
|
36
|
+
export interface CaptureEntitlementInput {
|
|
37
|
+
value: boolean | undefined;
|
|
38
|
+
faulted: boolean;
|
|
39
|
+
}
|
|
40
|
+
export type CaptureOptOutVerdict = {
|
|
41
|
+
allowed: true;
|
|
42
|
+
/** Present ⇔ the open-posture fault arm fired: the opt-out takes effect AND the caller owes
|
|
43
|
+
* the named `onError({phase:"config"})` disclosure line (this string). */
|
|
44
|
+
faultDisclosure?: string;
|
|
45
|
+
} | {
|
|
46
|
+
allowed: false;
|
|
47
|
+
/** Always `memory.capture_optout_denied` (terminal). The caller throws it; a DECLARATION
|
|
48
|
+
* denial must leave zero record residue (§2.1 order invariant — adjudicate BEFORE marking). */
|
|
49
|
+
code: "memory.capture_optout_denied";
|
|
50
|
+
detail: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Adjudicate one capture opt-out ingress. `subject` names what is being adjudicated, for the
|
|
54
|
+
* refusal detail only: `"declaration"` (a `capture:"off"` spelling / the flip verb) or
|
|
55
|
+
* `"standing-record"` (a resumed session whose one-way record stands — §3.3: under
|
|
56
|
+
* governed/capture-required the question is whether the run may CONTINUE; the record's zero-capture
|
|
57
|
+
* promise is not on the table).
|
|
58
|
+
*/
|
|
59
|
+
export declare function adjudicateCaptureOptOut(input: {
|
|
60
|
+
posture: MemoryCapturePosture;
|
|
61
|
+
entitlement: CaptureEntitlementInput;
|
|
62
|
+
subject: "declaration" | "standing-record";
|
|
63
|
+
}): CaptureOptOutVerdict;
|
|
64
|
+
/** The typed terminal refusal builder (one spelling for every ingress). */
|
|
65
|
+
export declare function captureOptOutDeniedError(detail: string): Error & {
|
|
66
|
+
code: string;
|
|
67
|
+
};
|
|
68
|
+
/** The typed could-not-record refusal (§2.8 — never a success notice's sibling; the caller mints
|
|
69
|
+
* {@link import("../memory-engine/engine.js").memoryCaptureOptOutUnpersistedNotice} beside it).
|
|
70
|
+
*
|
|
71
|
+
* `ingress` is REQUIRED (internal builder, both call sites in prepare-memory state it): the two
|
|
72
|
+
* arms' throws settle different things and the message must say which. The declaration arm's throw
|
|
73
|
+
* is whitelisted out of prepare-memory's fail-open catch and refuses the RUN; the host flip verb's
|
|
74
|
+
* throw rejects only the verb promise — `captureOptedOut` is assigned below it — so the run
|
|
75
|
+
* continues with capture still ON. One sentence for both said "refusing to run", which was false
|
|
76
|
+
* in the direction that matters: it told the caller the session had stopped capturing when it had
|
|
77
|
+
* not. */
|
|
78
|
+
export declare function captureOptOutUnpersistedError(sessionId: string, ingress: "declaration" | "flip-verb"): Error & {
|
|
79
|
+
code: string;
|
|
80
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export function adjudicateCaptureOptOut(input) {
|
|
2
|
+
const { posture, entitlement, subject } = input;
|
|
3
|
+
const what = subject === "standing-record" ? "continuing a session whose one-way capture opt-out record stands" : "the session memory-capture opt-out declaration";
|
|
4
|
+
if (posture === "capture-required") {
|
|
5
|
+
return {
|
|
6
|
+
allowed: false,
|
|
7
|
+
code: "memory.capture_optout_denied",
|
|
8
|
+
detail: `${what} is refused: this deployment declares memoryCapturePolicy "capture-required" (forced retention, resolver-independent). ` +
|
|
9
|
+
(subject === "standing-record"
|
|
10
|
+
? `The standing record keeps its zero-capture promise — the session is refused rather than run out of policy; start a new session without the opt-out.`
|
|
11
|
+
: `Remove the memory.capture declaration and resubmit.`),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
if (posture === "governed") {
|
|
15
|
+
if (entitlement.value === true)
|
|
16
|
+
return { allowed: true };
|
|
17
|
+
return {
|
|
18
|
+
allowed: false,
|
|
19
|
+
code: "memory.capture_optout_denied",
|
|
20
|
+
detail: entitlement.value === false
|
|
21
|
+
? `${what} is refused: the entitlement resolver answered allowMemoryOptOut=false for this principal (governed posture — the deployment requires capture for it).`
|
|
22
|
+
: `${what} is refused: memoryCapturePolicy "governed" requires an explicit per-principal allowMemoryOptOut verdict and none was resolvable (${entitlement.faulted ? "resolver fault" : "verdict absent"}) — fail-closed by the deployment's own declaration (a retention-required principal must not acquire an opt-out through an outage window).`,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (entitlement.value === false) {
|
|
26
|
+
return {
|
|
27
|
+
allowed: false,
|
|
28
|
+
code: "memory.capture_optout_denied",
|
|
29
|
+
detail: `${what} is refused: the entitlement resolver answered allowMemoryOptOut=false for this principal.`,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if (entitlement.faulted) {
|
|
33
|
+
return {
|
|
34
|
+
allowed: true,
|
|
35
|
+
faultDisclosure: `the runtime-caps resolve faulted while adjudicating a memory-capture opt-out; under the "open" posture the opt-out takes effect anyway ` +
|
|
36
|
+
`(privacy-side irreversibility wins the fault arm — an explicit allowMemoryOptOut=false is the only denial). Repair the resolver if denial was intended.`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return { allowed: true };
|
|
40
|
+
}
|
|
41
|
+
export function captureOptOutDeniedError(detail) {
|
|
42
|
+
const e = new Error(`memory capture opt-out denied: ${detail}`);
|
|
43
|
+
e.code = "memory.capture_optout_denied";
|
|
44
|
+
return e;
|
|
45
|
+
}
|
|
46
|
+
export function captureOptOutUnpersistedError(sessionId, ingress) {
|
|
47
|
+
const e = new Error(`memory capture opt-out could not be durably recorded for session ${sessionId} (control-plane write failed) — ` +
|
|
48
|
+
(ingress === "declaration"
|
|
49
|
+
? `refusing to run under an opt-out that would silently stop holding at the next resume. Nothing ran and nothing was captured.`
|
|
50
|
+
: `the opt-out request is REJECTED and not applied: the run continues with memory capture still ON. Nothing was recorded, so an identical retry fails the same way.`));
|
|
51
|
+
e.code = "memory.capture_optout_unpersisted";
|
|
52
|
+
return e;
|
|
53
|
+
}
|
|
@@ -196,6 +196,11 @@ export interface PrepareConfigDoorsResult {
|
|
|
196
196
|
* the synchronous pre-await stretch, screened (#123), absent folded to the `"carry"` default.
|
|
197
197
|
* The memory phase constructs every engine with THIS value — never a live deps re-read. */
|
|
198
198
|
memoryProvenance: "off" | "carry";
|
|
199
|
+
/** owned — the NORMALIZED memory-capture posture (design/383 §3.1): the deps seat read ONCE,
|
|
200
|
+
* screened (#123 — a bad value refuses, never folds), absent folded to `"open"`. The capture
|
|
201
|
+
* opt-out adjudicator consumes THIS value on every leg (declaration, resume record re-check,
|
|
202
|
+
* and the flip verb's frozen closure) — never a live deps re-read. */
|
|
203
|
+
memoryCapturePolicy: "open" | "governed" | "capture-required";
|
|
199
204
|
/** owned — validated deployment governance windows (undefined = ungoverned). */
|
|
200
205
|
usageWindows: readonly UsageWindow[] | undefined;
|
|
201
206
|
/** owned, out-param cell — created EMPTY here; the brain-call wiring later installs into
|
|
@@ -225,6 +225,21 @@ export function prepareConfigDoors(input) {
|
|
|
225
225
|
throw e;
|
|
226
226
|
}
|
|
227
227
|
const memoryProvenance = memoryProvenanceRaw === "off" ? "off" : "carry";
|
|
228
|
+
const memoryCapturePolicyRaw = deps.memoryCapturePolicy;
|
|
229
|
+
if (memoryCapturePolicyRaw !== undefined && memoryCapturePolicyRaw !== "open" && memoryCapturePolicyRaw !== "governed" && memoryCapturePolicyRaw !== "capture-required") {
|
|
230
|
+
const got = typeof memoryCapturePolicyRaw === "string"
|
|
231
|
+
? JSON.stringify(memoryCapturePolicyRaw.length > 64 ? `${memoryCapturePolicyRaw.slice(0, 64)}…` : memoryCapturePolicyRaw)
|
|
232
|
+
: memoryCapturePolicyRaw === null
|
|
233
|
+
? "null"
|
|
234
|
+
: Array.isArray(memoryCapturePolicyRaw)
|
|
235
|
+
? "an array"
|
|
236
|
+
: typeof memoryCapturePolicyRaw;
|
|
237
|
+
const e = new Error(`RunnerDeps.memoryCapturePolicy must be "open", "governed" or "capture-required" when present (got ${got}) — ` +
|
|
238
|
+
`an unevaluable capture posture is refused loudly, never folded to any posture (the fault arms differ per posture, so a guess would pick a polarity nobody configured).`);
|
|
239
|
+
e.code = "config.memory_capture_policy";
|
|
240
|
+
throw e;
|
|
241
|
+
}
|
|
242
|
+
const memoryCapturePolicy = memoryCapturePolicyRaw === "governed" ? "governed" : memoryCapturePolicyRaw === "capture-required" ? "capture-required" : "open";
|
|
228
243
|
if (spec.resumeAtMode !== undefined) {
|
|
229
244
|
if (spec.resumeAt === undefined) {
|
|
230
245
|
const e = new Error(`resumeAtMode "${spec.resumeAtMode}" requires resumeAt (there is no branch target to position against)`);
|
|
@@ -420,6 +435,7 @@ export function prepareConfigDoors(input) {
|
|
|
420
435
|
modelGate,
|
|
421
436
|
memoryDelegationEvidence,
|
|
422
437
|
memoryProvenance,
|
|
438
|
+
memoryCapturePolicy,
|
|
423
439
|
usageWindows,
|
|
424
440
|
brainCallGuardrailRef,
|
|
425
441
|
brainCallGuardrailMs,
|
|
@@ -13,6 +13,91 @@ import type { PrepareResume, RunInternals } from "./prepare-task.js";
|
|
|
13
13
|
* the console arm's key is the process itself, which only this seam can refresh. Moved WITH the
|
|
14
14
|
* phase (design/238 B-5); prepare-task.ts keeps a re-export so the existing import sites stand. */
|
|
15
15
|
export declare function __resetReadFaceClampAnnouncement(): void;
|
|
16
|
+
/**
|
|
17
|
+
* #483 rung-1 — the Runner's per-session read-file-state seats (CC parity: readFileState is minted
|
|
18
|
+
* once per REPL session and mutated across turns; it has NO turn-boundary and NO abort clear site —
|
|
19
|
+
* its only clears are compaction/fork/subagent-cleanup/dispose). sema's map stays per-task (the §11
|
|
20
|
+
* ruling-A closure wiring), so the SESSION scope lives here instead: prepare adopts each task's live
|
|
21
|
+
* map under its sessionId, and the next same-session, same-Runner task INHERITS it (through the same
|
|
22
|
+
* containment judgment the transcript-replay seed applies — see `inheritSessionReadFileState`).
|
|
23
|
+
*
|
|
24
|
+
* This seat is a CACHE, never an authority:
|
|
25
|
+
* - a durable resume's checkpoint seed stays the sole authority on that leg (the seed was cut
|
|
26
|
+
* atomically with the leaf; this seat may be older OR newer than it);
|
|
27
|
+
* - a cross-process continuation misses here and falls back to the transcript replay (rung-2);
|
|
28
|
+
* - eviction (the LRU cap, a Runner restart) degrades to that same replay — never to wrong state.
|
|
29
|
+
*
|
|
30
|
+
* Bounded: an LRU over sessionIds (insertion-order refresh on read), because the SessionStore's own
|
|
31
|
+
* eviction is not observable from here — a dropped seat costs one transcript replay, nothing else.
|
|
32
|
+
* The stored value is the task's LIVE map (object identity), so in-turn mutations — tool write-backs,
|
|
33
|
+
* and above all the RB-197 compaction clear — are the seat's truth by construction; a snapshot copy
|
|
34
|
+
* here would resurrect summarized-away reads on the next turn.
|
|
35
|
+
*
|
|
36
|
+
* BRANCH BINDING (codex r1+r2, adopted): a sessionId alone does not name a transcript — the same id
|
|
37
|
+
* can be rebranched (`resumeAt`), rewound out-of-band, or DELETED and re-created (`release()` + the
|
|
38
|
+
* default create-on-miss acquire), and the seat's map would then vouch for reads the CURRENT branch
|
|
39
|
+
* never saw. Each seat therefore records `anchorLeafId` — a leaf on the lineage its task produced —
|
|
40
|
+
* and inheritance must first prove the CURRENT branch still descends from that anchor
|
|
41
|
+
* (see {@link sessionSeatAnchorHolds}). The anchor is written twice: at ADOPT (the leaf at prepare;
|
|
42
|
+
* `null` when the branch was still empty) and SEALED at the task's settle to the task's own tail
|
|
43
|
+
* (`reanchor`). A `null` anchor is NEVER inheritable (codex r2: "the branch is non-empty now" proves
|
|
44
|
+
* nothing about WHOSE messages those are — another Runner repopulating a released id, or a session
|
|
45
|
+
* import, makes a non-empty branch that never contained this seat's reads). An unprovable anchor
|
|
46
|
+
* falls back to the transcript replay, which reads the current branch by construction — cost, never
|
|
47
|
+
* wrongness.
|
|
48
|
+
*/
|
|
49
|
+
export declare class SessionReadFileStates {
|
|
50
|
+
private readonly seats;
|
|
51
|
+
private static readonly CAP;
|
|
52
|
+
/** The session's seat row, or undefined (cross-process / evicted / first turn) ⇒ caller replays.
|
|
53
|
+
* The caller must verify the anchor against the session's CURRENT branch before using `state`. */
|
|
54
|
+
get(sessionId: string): {
|
|
55
|
+
state: ReadFileState;
|
|
56
|
+
anchorLeafId: string | null;
|
|
57
|
+
} | undefined;
|
|
58
|
+
/** Adopt THIS task's live map as the session's seat (every hands leg: fresh, continuation, resume).
|
|
59
|
+
* `anchorLeafId` = the session's leaf as read at THIS prepare (null = the branch was empty; a
|
|
60
|
+
* null-anchored seat is not inheritable until the settle-time {@link reanchor} names a real leaf). */
|
|
61
|
+
adopt(sessionId: string, state: ReadFileState, anchorLeafId: string | null): void;
|
|
62
|
+
/** #483 codex r2 — the settle-time SEAL: re-anchor the seat to the finished task's own tail, so a
|
|
63
|
+
* first-task (null-anchor) seat becomes inheritable only through a leaf its task provably wrote.
|
|
64
|
+
* Identity-guarded: touches the row only while it still holds THIS task's map (a later prepare on
|
|
65
|
+
* the same session has already replaced the seat and owns its own anchor). */
|
|
66
|
+
reanchor(sessionId: string, state: ReadFileState, anchorLeafId: string): void;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* #483 (codex r1+r2) — does the session's CURRENT branch still descend from the seat's anchor?
|
|
70
|
+
* TRUE is the licence to inherit; anything unprovable answers FALSE (the caller then replays the
|
|
71
|
+
* transcript — the fail-closed direction costs a rebuild, never admits a wrong entry).
|
|
72
|
+
*
|
|
73
|
+
* - anchor `null`: NEVER inheritable (r2). The seat was adopted on an empty branch and its task
|
|
74
|
+
* never sealed (crashed pre-settle, or the leaf read failed) — "the branch is non-empty now"
|
|
75
|
+
* would also be true of a released-and-repopulated id or an imported log, whose messages are not
|
|
76
|
+
* this seat's lineage. The settle-time reanchor is what upgrades such a seat to a real leaf.
|
|
77
|
+
* - anchor non-null: holds iff walking parentIds from the current leaf reaches the anchor within
|
|
78
|
+
* a bounded number of steps (the walk length is one task's appends in the ordinary continuation).
|
|
79
|
+
* A rebranch/rewind moved the leaf OFF the anchor's subtree ⇒ the walk misses ⇒ no inherit; a
|
|
80
|
+
* recreated/foreign log does not contain the anchor ⇒ same answer; a re-IMPORT of the same log
|
|
81
|
+
* keeps the anchor's lineage ⇒ inheritance stays correct there by the same test.
|
|
82
|
+
* - a COMPACTION on the walked segment (A-082 idx2): refuses, even though the branch is perfectly
|
|
83
|
+
* intact. The walked segment is exactly what landed AFTER this seat was anchored, and a
|
|
84
|
+
* compaction there summarized older Read results OUT of the context the model will be shown —
|
|
85
|
+
* the very state RB-197 clears from the live map at the landing site. That clear reaches the seat
|
|
86
|
+
* only when the SAME Runner did both (the seat IS that map, by object identity); a second Runner
|
|
87
|
+
* on the shared durable session clears its own map and commits the compaction as an ORDINARY
|
|
88
|
+
* LINEAR APPEND, which every other test here passes. Inheriting across it would let requireRead
|
|
89
|
+
* and checkStale both pass on a read whose card the model no longer has — a blind edit from a
|
|
90
|
+
* summarized-away recollection, the exact failure the RB-197 clear exists to prevent. Refusing
|
|
91
|
+
* costs one transcript replay, and the replay reads the POST-compaction branch by construction,
|
|
92
|
+
* so the fail-closed direction is also the CORRECT state. A compaction at or ABOVE the anchor is
|
|
93
|
+
* not on the walked segment and does not refuse: the seat that observed it already carries its
|
|
94
|
+
* clear (P-483-4), and an ordinary foreign turn with no compaction still inherits (G-483-5) —
|
|
95
|
+
* dropping every seat a foreign Runner touched would restore the cross-turn amnesia #483 ended.
|
|
96
|
+
*/
|
|
97
|
+
export declare function sessionSeatAnchorHolds(getEntry: (id: string) => Promise<{
|
|
98
|
+
parentId: string | null;
|
|
99
|
+
type?: string;
|
|
100
|
+
} | null | undefined>, anchorLeafId: string | null, currentLeafId: string | null): Promise<boolean>;
|
|
16
101
|
export interface PrepareHandsReadFaceInput {
|
|
17
102
|
/** owned (value) — `ownedEnv !== undefined || deps.executionEnv !== undefined`, computed in the
|
|
18
103
|
* driver: the phase's own branch condition (hands mount vs the hands-less resolver arm). */
|
|
@@ -34,8 +119,9 @@ export interface PrepareHandsReadFaceInput {
|
|
|
34
119
|
resume: Pick<PrepareResume, "seed"> | undefined;
|
|
35
120
|
/** borrowed-readonly — the acquired session (B-3 output), as a Pick (相 API 规则件 R-4: receiver
|
|
36
121
|
* preserved): `getWorkspaceState` feeds the design/155 continuation rung; `buildContext` feeds
|
|
37
|
-
* the transcript replay
|
|
38
|
-
|
|
122
|
+
* the transcript replay; `getStorage`/`getEntry` feed the #483 seat-anchor verification (one
|
|
123
|
+
* leaf read per prepare + a bounded parent walk on the inherit leg only). */
|
|
124
|
+
session: Pick<StoredSession, "getWorkspaceState" | "buildContext" | "getStorage" | "getEntry">;
|
|
39
125
|
/** borrowed-readonly — the acquired session's id, for error/notice tags and the band's session
|
|
40
126
|
* registration. */
|
|
41
127
|
sessionId: string;
|
|
@@ -59,15 +145,23 @@ export interface PrepareHandsReadFaceInput {
|
|
|
59
145
|
model: Model;
|
|
60
146
|
/** borrowed-readonly — the task-spec seats this phase reads, as a Pick over the SAME spec object
|
|
61
147
|
* (R-4). `tools` feeds only the band shadow check; `envFacts` only the advertised scratchpad. */
|
|
62
|
-
spec: Pick<TaskSpec, "additionalDirectories" | "additionalReadDirectories" | "envFacts" | "handsReadOnly" | "readDenyPatterns" | "readFace" | "oneShot" | "sessionId" | "tools">;
|
|
148
|
+
spec: Pick<TaskSpec, "additionalDirectories" | "additionalReadDirectories" | "envFacts" | "handsReadOnly" | "readDenyPatterns" | "readFace" | "oneShot" | "resumeAt" | "sessionId" | "tools">;
|
|
63
149
|
/** borrowed-readonly — the deployment seats this phase reads, as a Pick over the SAME deps
|
|
64
150
|
* object (R-4: `deps.onError?.()` keeps its receiver; a new deps read must widen this Pick).
|
|
65
151
|
* `readDenyBuiltinTiers`/`readDenyBuiltinExclude` are SNAPSHOTTED before the first await that
|
|
66
152
|
* precedes the toolkit compile (#245/#244 — the caller retains these arrays by reference). */
|
|
67
153
|
deps: Pick<RunnerDeps, "onError" | "onNotice" | "tracer" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "readFace" | "permissionRuleOrg" | "hands">;
|
|
68
154
|
/** borrowed-readonly — the trusted spawn-side seats the band forwards: the finished-command
|
|
69
|
-
* notification lane
|
|
70
|
-
|
|
155
|
+
* notification lane, the mid-flight detach hub, and (#483 rung-1) the Runner's per-session
|
|
156
|
+
* read-state seats (absent on a standalone prepare ⇒ the transcript-replay rung covers alone). */
|
|
157
|
+
internals: Pick<RunInternals, "onTaskNotification" | "detachHub" | "sessionReadStates"> | undefined;
|
|
158
|
+
/** borrowed-readonly (A-082 idx7) — ABSOLUTE paths this prepare's design/381 restore wrote, deleted,
|
|
159
|
+
* or failed to write. Every read-state seed above describes the PRE-restore tree, so these keys are
|
|
160
|
+
* dropped from the map: the engine replaced those bytes itself, and a surviving entry makes the
|
|
161
|
+
* next refusal blame "the user or a linter" for the engine's own write. Absent (the common case)
|
|
162
|
+
* when no file restore ran. Fail-closed by construction — dropping a read record only ever
|
|
163
|
+
* produces refusals. */
|
|
164
|
+
restoredFilePaths?: readonly string[];
|
|
71
165
|
/** borrowed (ref cell; the REF itself is never replaced) — design/138 S2-C late-bound memory
|
|
72
166
|
* write gate: the band's beforeWrite closure reads `.current` call-time; prepareMemory (driver,
|
|
73
167
|
* after this phase) installs the gate. Declared in the driver because the SAME ref feeds
|
|
@@ -107,6 +201,9 @@ export interface PrepareHandsReadFaceInput {
|
|
|
107
201
|
* band's Read/notebook/PDF trailer outlets bump keys on it at tool-execute time (the driver owns
|
|
108
202
|
* the object and folds it into result stats). */
|
|
109
203
|
reminderDisclosureCounts: import("../reminder-disclosure.js").ReminderDisclosureCounts;
|
|
204
|
+
/** design/381 — the first-touch history hook prepare built (present iff a fileHistoryStore is
|
|
205
|
+
* wired and the run mounts a real fs env); threaded to the write band's trackFileEdit seat. */
|
|
206
|
+
trackFileEdit?: import("../types.js").TrackFileEditHook;
|
|
110
207
|
}
|
|
111
208
|
/** The phase's outputs (相 API 规则件 four-class form) — ALL settled before the return; the driver
|
|
112
209
|
* binds them as fresh consts (R-5) except the inverted-closure trio and the two shellGated bits,
|
|
@@ -190,6 +287,14 @@ export interface PrepareHandsReadFaceResult {
|
|
|
190
287
|
* misses Monitor/worktree, which are hands — both directions wrong for this consumer). Empty on
|
|
191
288
|
* a hands-less leg (nothing mounts on the env; the driver's env mounts are hands-gated too). */
|
|
192
289
|
envHandToolNames: Set<string>;
|
|
290
|
+
/** owned (#483 codex r2+r3) — the settle-time SEAT SEAL: re-anchors the session seat to the
|
|
291
|
+
* task's OWN last-committed entry (see {@link SessionReadFileStates.reanchor}). The driver calls
|
|
292
|
+
* it once at the run's settle (every terminal, suspend included), under the session lock, passing
|
|
293
|
+
* the last entryId ITS OWN run committed (the message_committed lane) — never a re-read of the
|
|
294
|
+
* shared session leaf, which a concurrent writer on another Runner could have moved onto a branch
|
|
295
|
+
* this task's reads were never on (codex r3). No commits observed ⇒ no-op (the adopt-time anchor
|
|
296
|
+
* stands). Synchronous, never throws. Undefined ⇔ hands-less / no seat channel. */
|
|
297
|
+
sealReadStateSeat: ((ownCommittedTailEntryId: string | undefined) => void) | undefined;
|
|
193
298
|
}
|
|
194
299
|
export declare function resolveHandsLessReadFace(input: PrepareHandsReadFaceInput): PrepareHandsReadFaceResult;
|
|
195
300
|
/** The hands-MOUNT branch — the async half of the phase (see the module header for the contract).
|
|
@@ -2,8 +2,9 @@ import { emitTrace } from "../trace.js";
|
|
|
2
2
|
import { StubExecutionEnv } from "../stub-env.js";
|
|
3
3
|
import { hasBackgroundShell } from "../background-shell.js";
|
|
4
4
|
import { defaultTaskRegistry } from "../task-registry.js";
|
|
5
|
-
import { bashReversibilityProbe, compileReadDeny, createHandsToolkit, deploymentReadFaceClampNotice, pdfModelCapabilitiesOf, resolveReadDenyBuiltins, resolveReadFace, seedReadFileStateFromContext, seedReadFileStateFromTranscript, } from "../../tools/fs/index.js";
|
|
6
|
-
import { resolveKey } from "../../tools/fs/safety.js";
|
|
5
|
+
import { bashReversibilityProbe, compileReadDeny, createHandsToolkit, deploymentReadFaceClampNotice, pdfModelCapabilitiesOf, resolveReadDenyBuiltins, inheritSessionReadFileState, resolveReadFace, seedReadFileStateFromContext, seedReadFileStateFromTranscript, } from "../../tools/fs/index.js";
|
|
6
|
+
import { resolveKey, sha256 } from "../../tools/fs/safety.js";
|
|
7
|
+
import { normalizeFileText } from "../../tools/fs/encoding.js";
|
|
7
8
|
import { wholeFileRecordsFromTranscript } from "./session-file-state-replay.js";
|
|
8
9
|
import { deliverEngineNotice } from "../types.js";
|
|
9
10
|
import { rebaseWorkspacePath } from "./prepare-workspace-restore.js";
|
|
@@ -12,6 +13,49 @@ let readFaceClampConsoleAnnounced = false;
|
|
|
12
13
|
export function __resetReadFaceClampAnnouncement() {
|
|
13
14
|
readFaceClampConsoleAnnounced = false;
|
|
14
15
|
}
|
|
16
|
+
export class SessionReadFileStates {
|
|
17
|
+
seats = new Map();
|
|
18
|
+
static CAP = 1024;
|
|
19
|
+
get(sessionId) {
|
|
20
|
+
const seat = this.seats.get(sessionId);
|
|
21
|
+
if (seat !== undefined) {
|
|
22
|
+
this.seats.delete(sessionId);
|
|
23
|
+
this.seats.set(sessionId, seat);
|
|
24
|
+
}
|
|
25
|
+
return seat;
|
|
26
|
+
}
|
|
27
|
+
adopt(sessionId, state, anchorLeafId) {
|
|
28
|
+
this.seats.delete(sessionId);
|
|
29
|
+
this.seats.set(sessionId, { state, anchorLeafId });
|
|
30
|
+
while (this.seats.size > SessionReadFileStates.CAP) {
|
|
31
|
+
const oldest = this.seats.keys().next().value;
|
|
32
|
+
if (oldest === undefined)
|
|
33
|
+
break;
|
|
34
|
+
this.seats.delete(oldest);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
reanchor(sessionId, state, anchorLeafId) {
|
|
38
|
+
const seat = this.seats.get(sessionId);
|
|
39
|
+
if (seat !== undefined && seat.state === state)
|
|
40
|
+
seat.anchorLeafId = anchorLeafId;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export async function sessionSeatAnchorHolds(getEntry, anchorLeafId, currentLeafId) {
|
|
44
|
+
if (anchorLeafId === null)
|
|
45
|
+
return false;
|
|
46
|
+
let id = currentLeafId;
|
|
47
|
+
for (let steps = 0; id !== null && steps < 100_000; steps++) {
|
|
48
|
+
if (id === anchorLeafId)
|
|
49
|
+
return true;
|
|
50
|
+
const entry = await getEntry(id);
|
|
51
|
+
if (entry === null || entry === undefined)
|
|
52
|
+
return false;
|
|
53
|
+
if (entry.type === "compaction")
|
|
54
|
+
return false;
|
|
55
|
+
id = entry.parentId;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
15
59
|
function assertStringArraySeat(value, seat, entryLaw) {
|
|
16
60
|
if (value === undefined)
|
|
17
61
|
return;
|
|
@@ -74,6 +118,7 @@ export function resolveHandsLessReadFace(input) {
|
|
|
74
118
|
envHandToolNames: new Set(),
|
|
75
119
|
shellGatedBash: false,
|
|
76
120
|
shellGatedMonitor: false,
|
|
121
|
+
sealReadStateSeat: undefined,
|
|
77
122
|
};
|
|
78
123
|
}
|
|
79
124
|
export async function prepareHandsMount(input) {
|
|
@@ -84,6 +129,7 @@ export async function prepareHandsMount(input) {
|
|
|
84
129
|
assertStringArraySeat(deps.readDenyPatterns, "RunnerDeps.readDenyPatterns", "compiler-owned");
|
|
85
130
|
assertStringArraySeat(spec.readDenyPatterns, "TaskSpec.readDenyPatterns", "compiler-owned");
|
|
86
131
|
let readFileStateForCheckpoint;
|
|
132
|
+
let sealReadStateSeat;
|
|
87
133
|
let seedContextFiles;
|
|
88
134
|
let handsCwdRef;
|
|
89
135
|
let workspaceStateSettle;
|
|
@@ -205,14 +251,58 @@ export async function prepareHandsMount(input) {
|
|
|
205
251
|
if (resume !== undefined && (seedReadFaceSection === undefined || seedReadFaceSection.face === "roots"))
|
|
206
252
|
liveReadFace = "roots";
|
|
207
253
|
resolvedReadFace = liveReadFace;
|
|
254
|
+
const sessionSeats = input.internals?.sessionReadStates;
|
|
255
|
+
let seatLeafId;
|
|
256
|
+
try {
|
|
257
|
+
seatLeafId = (await session.getStorage().getLeafId()) ?? null;
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
seatLeafId = undefined;
|
|
261
|
+
}
|
|
262
|
+
const judgeKey = async (path) => {
|
|
263
|
+
const rk = await resolveKey(executionEnv, rootCanonical, path, undefined, undefined, [...additionalRootsCanonical, ...additionalReadRootsCanonical], undefined, undefined, liveReadFace);
|
|
264
|
+
return rk.ok ? rk.key : undefined;
|
|
265
|
+
};
|
|
208
266
|
if (resume === undefined && spec.sessionId !== undefined) {
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
267
|
+
const seatRow = spec.resumeAt === undefined && seatLeafId !== undefined ? sessionSeats?.get(sessionId) : undefined;
|
|
268
|
+
const inheritedSeat = seatRow !== undefined && (await sessionSeatAnchorHolds((id) => session.getEntry(id), seatRow.anchorLeafId, seatLeafId))
|
|
269
|
+
? seatRow.state
|
|
270
|
+
: undefined;
|
|
271
|
+
if (inheritedSeat !== undefined) {
|
|
272
|
+
await inheritSessionReadFileState(readFileState, inheritedSeat, judgeKey);
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
const prior = await session.buildContext().catch(() => undefined);
|
|
276
|
+
for (const rec of wholeFileRecordsFromTranscript(prior?.messages ?? [])) {
|
|
277
|
+
const rk = await judgeKey(rec.path);
|
|
278
|
+
if (rk !== undefined)
|
|
279
|
+
seedReadFileStateFromTranscript(readFileState, rk, rec.content, rec.at);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (input.restoredFilePaths !== undefined) {
|
|
284
|
+
for (const abs of input.restoredFilePaths) {
|
|
285
|
+
const k = await judgeKey(abs);
|
|
286
|
+
if (k === undefined)
|
|
287
|
+
continue;
|
|
288
|
+
const entry = readFileState.get(k);
|
|
289
|
+
if (entry === undefined || entry.seededFromContext === true)
|
|
290
|
+
continue;
|
|
291
|
+
const cur = await executionEnv.readTextFile(abs);
|
|
292
|
+
if (cur.ok && entry.hash === sha256(normalizeFileText(cur.value)))
|
|
293
|
+
continue;
|
|
294
|
+
readFileState.delete(k);
|
|
214
295
|
}
|
|
215
296
|
}
|
|
297
|
+
if (seatLeafId !== undefined)
|
|
298
|
+
sessionSeats?.adopt(sessionId, readFileState, seatLeafId);
|
|
299
|
+
sealReadStateSeat =
|
|
300
|
+
sessionSeats !== undefined && seatLeafId !== undefined
|
|
301
|
+
? (ownCommittedTailEntryId) => {
|
|
302
|
+
if (ownCommittedTailEntryId !== undefined)
|
|
303
|
+
sessionSeats.reanchor(sessionId, readFileState, ownCommittedTailEntryId);
|
|
304
|
+
}
|
|
305
|
+
: undefined;
|
|
216
306
|
const band = createHandsToolkit(executionEnv, readFileState, rootCanonical, {
|
|
217
307
|
...(additionalRootsCanonical.length > 0 ? { additionalRoots: additionalRootsCanonical } : {}),
|
|
218
308
|
...(additionalReadRootsCanonical.length > 0 ? { additionalReadRoots: additionalReadRootsCanonical } : {}),
|
|
@@ -222,6 +312,7 @@ export async function prepareHandsMount(input) {
|
|
|
222
312
|
readFace: liveReadFace,
|
|
223
313
|
reminderMark: input.reminderMark,
|
|
224
314
|
reminderDisclosureCounts: input.reminderDisclosureCounts,
|
|
315
|
+
...(input.trackFileEdit !== undefined ? { trackFileEdit: input.trackFileEdit } : {}),
|
|
225
316
|
includeShell: handsIncludeShell,
|
|
226
317
|
readOnly: handsReadOnly,
|
|
227
318
|
...(handsCwdRef ? { cwdRef: handsCwdRef } : {}),
|
|
@@ -309,5 +400,6 @@ export async function prepareHandsMount(input) {
|
|
|
309
400
|
envHandToolNames,
|
|
310
401
|
shellGatedBash,
|
|
311
402
|
shellGatedMonitor,
|
|
403
|
+
sealReadStateSeat,
|
|
312
404
|
};
|
|
313
405
|
}
|