@sema-agent/core 7.21.1 → 7.23.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 +290 -1
- package/dist/agents/send-message-tool.js +1 -0
- package/dist/agents/subagent.d.ts +1 -0
- package/dist/agents/subagent.js +2 -1
- package/dist/core/ask-origin.d.ts +1 -0
- package/dist/core/ask-origin.js +3 -2
- package/dist/core/auto-compaction.d.ts +2 -2
- package/dist/core/auto-mode-rebuild.js +6 -2
- package/dist/core/auto-mode.d.ts +40 -18
- package/dist/core/auto-mode.js +63 -45
- package/dist/core/checkpoint-store.d.ts +9 -13
- package/dist/core/context-edit.js +1 -1
- package/dist/core/engine-notice.d.ts +62 -1
- package/dist/core/engine-notice.js +14 -1
- package/dist/core/gate-bounded-await.d.ts +13 -3
- package/dist/core/gate-fold.js +3 -3
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +6 -0
- package/dist/core/mcp.d.ts +65 -0
- package/dist/core/mcp.js +190 -41
- package/dist/core/memory-engine/consolidation.d.ts +5 -0
- package/dist/core/memory-engine/consolidation.js +7 -3
- package/dist/core/memory-engine/engine.d.ts +8 -0
- package/dist/core/memory-engine/engine.js +25 -19
- package/dist/core/memory-engine/layout.d.ts +47 -9
- package/dist/core/memory-engine/layout.js +157 -57
- package/dist/core/memory-engine/session-incarnation.d.ts +27 -0
- package/dist/core/memory-engine/session-incarnation.js +63 -0
- package/dist/core/parent-spec-seat.d.ts +15 -3
- package/dist/core/parent-spec-seat.js +5 -0
- package/dist/core/park-selfcheck.js +1 -1
- package/dist/core/path-identity.d.ts +11 -4
- package/dist/core/path-identity.js +5 -2
- package/dist/core/permission-rule-model.d.ts +1 -2
- package/dist/core/read-root-candidate.js +11 -1
- package/dist/core/retention.d.ts +8 -1
- package/dist/core/retention.js +2 -0
- package/dist/core/runner/assemble-result.d.ts +2 -7
- package/dist/core/runner/assemble-result.js +25 -8
- package/dist/core/runner/classifier-ladder.js +1 -0
- package/dist/core/runner/contracts.d.ts +26 -15
- package/dist/core/runner/inherited-ask-grants.d.ts +2 -7
- package/dist/core/runner/inherited-ask-grants.js +1 -0
- package/dist/core/runner/mcp-redial.d.ts +109 -0
- package/dist/core/runner/mcp-redial.js +60 -0
- package/dist/core/runner/memory-consolidation-driver.d.ts +2 -0
- package/dist/core/runner/prepare-ask-lane.js +1 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +9 -12
- package/dist/core/runner/prepare-context-lane.d.ts +43 -1
- package/dist/core/runner/prepare-context-lane.js +25 -4
- package/dist/core/runner/prepare-hands-readface.d.ts +3 -0
- package/dist/core/runner/prepare-hands-readface.js +13 -3
- package/dist/core/runner/prepare-memory.js +3 -3
- package/dist/core/runner/prepare-protocol-tools.d.ts +6 -1
- package/dist/core/runner/prepare-protocol-tools.js +20 -38
- package/dist/core/runner/prepare-task.js +4 -4
- package/dist/core/runner/prepare-turn-wiring.js +2 -2
- package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -0
- package/dist/core/runner/prepare-wiring-manifest.js +2 -1
- package/dist/core/runner/run-harness-handlers.js +4 -2
- package/dist/core/runner/run-identity-wiring.d.ts +3 -1
- package/dist/core/runner/run-identity-wiring.js +4 -1
- package/dist/core/runner/run-recovery-lanes.js +6 -3
- package/dist/core/runner/run-settle-and-teardown.d.ts +3 -1
- package/dist/core/runner/run-settle-and-teardown.js +6 -2
- package/dist/core/runner/run-telemetry-and-budget-seats.js +11 -1
- package/dist/core/runner/run-terminal-adoption.d.ts +4 -1
- package/dist/core/runner/run-terminal-adoption.js +2 -6
- package/dist/core/runner/runtask.js +8 -5
- package/dist/core/runner/stream-lifecycle-verbs.d.ts +5 -1
- package/dist/core/runner/stream-lifecycle-verbs.js +23 -2
- package/dist/core/runner/write-protection-station.d.ts +7 -25
- package/dist/core/runner/write-protection-station.js +18 -0
- package/dist/core/runner-deps.d.ts +16 -7
- package/dist/core/skill-listing-budget.d.ts +5 -4
- package/dist/core/task-registry.d.ts +0 -6
- package/dist/core/task-result.d.ts +31 -12
- package/dist/core/task-spec.d.ts +2 -1
- package/dist/core/task-stream.d.ts +26 -0
- package/dist/core/tool-spec.d.ts +5 -1
- package/dist/core/trace.d.ts +5 -0
- package/dist/core/types.d.ts +3 -3
- package/dist/core/types.js +1 -1
- package/dist/core/wiring-manifest.d.ts +23 -0
- package/dist/core/wiring-manifest.js +3 -0
- package/dist/core/write-protect.d.ts +64 -12
- package/dist/core/write-protect.js +88 -34
- package/dist/engine/execution-env/node-execution-env.d.ts +19 -2
- package/dist/engine/execution-env/node-execution-env.js +5 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +2 -2
- package/dist/orchestration/workflow-governance.js +1 -1
- package/dist/orchestration/workflow-primitives.d.ts +2 -2
- package/dist/tools/fs/bash-program-position.d.ts +26 -5
- package/dist/tools/fs/bash-program-position.js +63 -16
- package/dist/tools/fs/bash-readonly-classifier.d.ts +27 -17
- package/dist/tools/fs/bash-readonly-classifier.js +134 -55
- package/dist/tools/fs/fs-bash.js +43 -18
- package/dist/tools/fs/fs-read.js +2 -1
- package/dist/tools/fs/fs-shared.d.ts +33 -7
- package/dist/tools/fs/fs-shared.js +12 -1
- package/dist/tools/fs/index.d.ts +4 -2
- package/dist/tools/fs/read-deny.d.ts +25 -11
- package/dist/tools/fs/read-deny.js +5 -1
- package/dist/tools/fs/safety.d.ts +38 -4
- package/dist/tools/fs/safety.js +31 -3
- package/package.json +2 -2
- package/test/export-surface.snapshot.json +11 -1
- package/wire-changes/core@7.22.0.json +637 -0
- package/wire-changes/core@7.23.0.json +316 -0
|
@@ -3,11 +3,18 @@ import { type PathFamily } from "../tools/fs/safety.js";
|
|
|
3
3
|
/**
|
|
4
4
|
* Every spelling the leaf symlink chain of `path` passes through, or `[]`.
|
|
5
5
|
*
|
|
6
|
-
* `[]` has ONE reading — nothing to judge beyond the spelling and the terminal — reached
|
|
7
|
-
* deliberately the same word: no `canonicalChain` declared,
|
|
8
|
-
* chain
|
|
6
|
+
* `[]` has ONE reading for the VERDICT — nothing to judge beyond the spelling and the terminal — reached four
|
|
7
|
+
* ways that are deliberately the same word: no `canonicalChain` declared, a refused namespace spelling, an
|
|
8
|
+
* empty chain, and the environment answering `ok: false`. An unanswerable chain is never a refusal: this
|
|
9
|
+
* reader's callers are path guards whose own absence law says a view that cannot answer does not vote, and a
|
|
9
10
|
* second refusal law over one target would be a second rule. The spelling is anchored as the canonicalizer
|
|
10
11
|
* anchors it (`baseCwd`). A refused namespace spelling and a win-form UNC spelling are not walked (the reference
|
|
11
12
|
* CLI's variant set stops at a network spelling); the share is recognized by the throat's own `isUncSpelling`.
|
|
13
|
+
*
|
|
14
|
+
* `onUnanswerable` separates ONE of those four from the rest for a caller that DISCLOSES its absent views: the
|
|
15
|
+
* environment was ASKED and could not answer (`ok: false`), which is a fact about that environment. The other
|
|
16
|
+
* three are not failures — nothing was asked, or the answer was genuinely "no hops" — so they stay silent. The
|
|
17
|
+
* verdict is `[]` either way; this sink changes nothing about what is judged, only what is said out loud. A
|
|
18
|
+
* throwing sink is the caller's bug and is not caught here.
|
|
12
19
|
*/
|
|
13
|
-
export declare function symlinkChainHops(env: ExecutionEnv, path: string, signal: AbortSignal | undefined, family: PathFamily, baseCwd?: string): Promise<readonly string[]>;
|
|
20
|
+
export declare function symlinkChainHops(env: ExecutionEnv, path: string, signal: AbortSignal | undefined, family: PathFamily, baseCwd?: string, onUnanswerable?: (cause: string) => void): Promise<readonly string[]>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isAbsoluteForFamily, isUncSpelling, joinForFamily, win32NamespaceScreen } from "../tools/fs/safety.js";
|
|
2
|
-
export async function symlinkChainHops(env, path, signal, family, baseCwd) {
|
|
2
|
+
export async function symlinkChainHops(env, path, signal, family, baseCwd, onUnanswerable) {
|
|
3
3
|
if (typeof env.canonicalChain !== "function")
|
|
4
4
|
return [];
|
|
5
5
|
const screened = win32NamespaceScreen(path, family);
|
|
@@ -10,5 +10,8 @@ export async function symlinkChainHops(env, path, signal, family, baseCwd) {
|
|
|
10
10
|
return [];
|
|
11
11
|
const target = baseCwd !== undefined && baseCwd.length > 0 && !isAbsoluteForFamily(family, spelled) ? joinForFamily(family, baseCwd, spelled) : spelled;
|
|
12
12
|
const chain = await env.canonicalChain(target, signal);
|
|
13
|
-
|
|
13
|
+
if (chain.ok)
|
|
14
|
+
return chain.value.hops;
|
|
15
|
+
onUnanswerable?.(chain.error.message);
|
|
16
|
+
return [];
|
|
14
17
|
}
|
|
@@ -1195,8 +1195,7 @@ export declare function suggestRulesForCommand(command: string, ctx?: {
|
|
|
1195
1195
|
* design/382 §2.4-6 — the deployment's sensitive-read negative control, SAME-SOURCE with its
|
|
1196
1196
|
* compiled read-deny judge (`ReadDenyMatcher.matchPath !== null` is the canonical wiring): the
|
|
1197
1197
|
* engine never PROPOSES a directory authorization for a directory this judge names. Absent ⇒
|
|
1198
|
-
*
|
|
1199
|
-
* passes its own so the two faces cannot disagree). Explicit-consent doors (hand-written,
|
|
1198
|
+
* {@link defaultDeniesDirectoryRead} stands in (refusal-only; every hands leg passes its OWN judge). Explicit-consent doors (hand-written,
|
|
1200
1199
|
* import) are deliberately NOT behind this — they have their own preview and validation.
|
|
1201
1200
|
*/
|
|
1202
1201
|
readonly deniesDirectoryRead?: (directory: string) => boolean;
|
|
@@ -20,5 +20,15 @@ export function normalizeReadRootCandidate(value) {
|
|
|
20
20
|
return undefined;
|
|
21
21
|
if (!win32NamespaceScreen(dir, pathFamilyOf({ root: dir })).ok)
|
|
22
22
|
return undefined;
|
|
23
|
-
|
|
23
|
+
let covers;
|
|
24
|
+
try {
|
|
25
|
+
const d = Object.getOwnPropertyDescriptor(value, "covers");
|
|
26
|
+
covers = d === undefined ? "absent" : "value" in d && d.value === "exact" ? "exact" : "refuse";
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
covers = "refuse";
|
|
30
|
+
}
|
|
31
|
+
if (covers === "refuse")
|
|
32
|
+
return undefined;
|
|
33
|
+
return covers === "exact" ? { dir, clearsThisAsk: true, covers: "exact" } : { dir, clearsThisAsk: true };
|
|
24
34
|
}
|
package/dist/core/retention.d.ts
CHANGED
|
@@ -47,7 +47,7 @@ export interface ManagedRetentionCapability {
|
|
|
47
47
|
/** Lineage-aware transcript/session deletion: a session tree is deleted only when no live
|
|
48
48
|
* reference (fork child, background child, workflow run, pending checkpoint) still points into
|
|
49
49
|
* it — references are re-checked inside the deletion transaction, never assumed from a prior
|
|
50
|
-
* scan. */
|
|
50
|
+
* scan; {@link RETENTION_CASCADE_EXEMPT} is never deleted with one. */
|
|
51
51
|
deleteExpiredSessions(input: {
|
|
52
52
|
domain: string;
|
|
53
53
|
cutoffMs: number;
|
|
@@ -66,6 +66,13 @@ export interface ManagedRetentionCapability {
|
|
|
66
66
|
* own occurrence id, operation, and timestamp inside the deleting transaction. Adding those here
|
|
67
67
|
* would ask a return value to know its own call context.
|
|
68
68
|
*/
|
|
69
|
+
/** @contract retention.cascade_domains — a managed-retention cascade deletes checkpoint rows,
|
|
70
|
+
* session trees (transcripts and lineage) and orphan tool results; nothing else is session data. */
|
|
71
|
+
export declare const RETENTION_CASCADE_DOMAINS: readonly ["checkpoints", "session-trees", "orphan-tool-results"];
|
|
72
|
+
/** @contract retention.governance_records_outlive_the_purge — the pollution marker dir, the capture
|
|
73
|
+
* opt-out record dir and the lineage ledger are governance FACTS about content that exists, not
|
|
74
|
+
* session data: deleting them makes a one-way privacy record revocable. */
|
|
75
|
+
export declare const RETENTION_CASCADE_EXEMPT: readonly ["session-pollution", "session-capture-optout", "lineage.json"];
|
|
69
76
|
export interface RetentionReceipt {
|
|
70
77
|
domain: string;
|
|
71
78
|
/** Rows destroyed by THIS call (idempotent retries count already-gone rows as 0, not errors). */
|
package/dist/core/retention.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export const RETENTION_CASCADE_DOMAINS = ["checkpoints", "session-trees", "orphan-tool-results"];
|
|
2
|
+
export const RETENTION_CASCADE_EXEMPT = ["session-pollution", "session-capture-optout", "lineage.json"];
|
|
1
3
|
function codedError(code, message) {
|
|
2
4
|
const e = new Error(message);
|
|
3
5
|
e.code = code;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AssistantMessage } from "../../internal/llm.js";
|
|
2
|
+
import type { HumanReviewGateRow } from "../task-result.js";
|
|
2
3
|
import type { NestedUsage, PausedCause, TaskResult, TaskSpec, TerminalCause } from "../types.js";
|
|
3
4
|
import type { HaltConsumption } from "../../internal/harness.js";
|
|
4
5
|
/** Per-task usage/cost accumulator (filled across the run loop, surfaced as `TaskResult.stats`). */
|
|
@@ -40,13 +41,7 @@ export interface Stats {
|
|
|
40
41
|
humanReview?: {
|
|
41
42
|
count: number;
|
|
42
43
|
totalWaitMs: number;
|
|
43
|
-
gates:
|
|
44
|
-
kind: string;
|
|
45
|
-
waitMs: number;
|
|
46
|
-
decision?: string;
|
|
47
|
-
toolName?: string;
|
|
48
|
-
toolArg?: string;
|
|
49
|
-
}>;
|
|
44
|
+
gates: HumanReviewGateRow[];
|
|
50
45
|
};
|
|
51
46
|
/** TB telemetry B2: engine-mechanism engagement counters. Set by the run loop
|
|
52
47
|
* only when ≥1 engaged; flows straight through to `TaskResult.stats.mechanisms` via the
|
|
@@ -2,6 +2,7 @@ import { isDegenerateCutMessage } from "../../brain/terminal-cause.js";
|
|
|
2
2
|
import { extractErrorCode, stripErrorCodePrefix } from "../../brain/errors.js";
|
|
3
3
|
import { terminalProjection } from "./terminal-projection.js";
|
|
4
4
|
import { haltConsumed } from "./halt-attribution.js";
|
|
5
|
+
import { describeRejectedValue } from "../tool-search-setting.js";
|
|
5
6
|
const SALVAGE_ELIGIBLE_TERMINALS = new Set([
|
|
6
7
|
"output.degenerate",
|
|
7
8
|
"limits.max_tokens_exceeded",
|
|
@@ -11,18 +12,34 @@ const SALVAGE_ELIGIBLE_TERMINALS = new Set([
|
|
|
11
12
|
"env.lifetime_expired",
|
|
12
13
|
"usage.window_exhausted",
|
|
13
14
|
]);
|
|
15
|
+
function thrownMessage(threw) {
|
|
16
|
+
try {
|
|
17
|
+
if (threw instanceof Error) {
|
|
18
|
+
const m = threw.message;
|
|
19
|
+
if (typeof m === "string")
|
|
20
|
+
return m;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
}
|
|
25
|
+
return describeRejectedValue(threw);
|
|
26
|
+
}
|
|
14
27
|
export function errorCodeOf(err) {
|
|
15
28
|
let cur = err;
|
|
16
29
|
let fallback;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (code
|
|
21
|
-
|
|
30
|
+
try {
|
|
31
|
+
for (let depth = 0; cur && typeof cur === "object" && depth < 8; depth++) {
|
|
32
|
+
const code = cur.code;
|
|
33
|
+
if (typeof code === "string" && code) {
|
|
34
|
+
if (code !== "unknown") {
|
|
35
|
+
return code;
|
|
36
|
+
}
|
|
37
|
+
fallback ??= code;
|
|
22
38
|
}
|
|
23
|
-
|
|
39
|
+
cur = cur.cause;
|
|
24
40
|
}
|
|
25
|
-
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
26
43
|
}
|
|
27
44
|
return fallback;
|
|
28
45
|
}
|
|
@@ -84,7 +101,7 @@ export function terminalCauseOf(final, stats, flags) {
|
|
|
84
101
|
terminal = { kind: "failed", code: "suspend.loop", message };
|
|
85
102
|
}
|
|
86
103
|
else if (flags.threw) {
|
|
87
|
-
const message =
|
|
104
|
+
const message = thrownMessage(flags.threw);
|
|
88
105
|
const code = flags.abortedForTimeout
|
|
89
106
|
? "limits.max_walltime_exceeded"
|
|
90
107
|
: flags.abortedForTurns
|
|
@@ -217,6 +217,7 @@ export function classifierRoundObserver(args) {
|
|
|
217
217
|
ms: info.ms,
|
|
218
218
|
verdict: info.verdict,
|
|
219
219
|
...(info.cause !== undefined ? { cause: info.cause } : {}),
|
|
220
|
+
...(info.notAsked !== undefined ? { notAsked: info.notAsked } : {}),
|
|
220
221
|
ts: Date.now(),
|
|
221
222
|
}));
|
|
222
223
|
announceFallback(info.classifier);
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* without adding a runtime edge anywhere. The blocks below are verbatim moves, in their original
|
|
14
14
|
* order, JSDoc included, so the move stays auditable as a move.
|
|
15
15
|
*/
|
|
16
|
+
import type { HumanReviewGateRow } from "../task-result.js";
|
|
16
17
|
import type { RepairBundle } from "../../agents/repair-loop.js";
|
|
17
18
|
import type { SubagentRetainLedger } from "../../agents/retain-ledger.js";
|
|
18
19
|
import type { BrainCallGuardrailRef } from "../../brain/timeout.js";
|
|
@@ -248,6 +249,11 @@ export interface Prepared {
|
|
|
248
249
|
thinking?: ThinkingLevel;
|
|
249
250
|
compModel?: Model;
|
|
250
251
|
mcp: MaterializedMcp;
|
|
252
|
+
/** The run handle's `reconnectMcpServer` implementation — {@link MaterializedMcp.reconnect} plus the
|
|
253
|
+
* live-roster application and the operator disclosure, which are runner concerns and not the MCP
|
|
254
|
+
* handle's. Present only when this leg DECLARED MCP servers; the stream verb's seat stays unbound
|
|
255
|
+
* otherwise and it refuses typed. */
|
|
256
|
+
mcpRedial?: (server: string) => Promise<import("../mcp.js").McpReconnectResult>;
|
|
251
257
|
/** Present only when `spec.a2a` declared peers — the run loop owes it the same end-of-task dispose
|
|
252
258
|
* it owes {@link mcp} (no sockets are held, but a disposed mount is what makes a post-task tool
|
|
253
259
|
* call refuse loudly instead of dialing a peer on a dead task's behalf). */
|
|
@@ -783,16 +789,14 @@ export interface Prepared {
|
|
|
783
789
|
* prior-leg burden seeded from the resumed checkpoint). The run loop ADDS the durable-resume latency
|
|
784
790
|
* (`now() − cp.suspendedAt`) on a resume, then surfaces it as `stats.humanReview` at assembly (omitted when
|
|
785
791
|
* empty). **Budget-EXCLUDED** — never folded into cost/the budget gate. */
|
|
792
|
+
/** borrowed-mutable — the hands' read-state map (the SAME `Map` object the toolkit and the context
|
|
793
|
+
* lane hold; absent without hands). Read by the provider-rejection recovery arm so its stale-result
|
|
794
|
+
* clears retire the entries those transmissions fed, through the same landing the frontier pass uses. */
|
|
795
|
+
readFileState?: import("../../tools/fs/safety.js").ReadFileState;
|
|
786
796
|
humanReviewRef: {
|
|
787
797
|
count: number;
|
|
788
798
|
totalWaitMs: number;
|
|
789
|
-
gates:
|
|
790
|
-
kind: string;
|
|
791
|
-
waitMs: number;
|
|
792
|
-
decision?: string;
|
|
793
|
-
toolName?: string;
|
|
794
|
-
toolArg?: string;
|
|
795
|
-
}>;
|
|
799
|
+
gates: HumanReviewGateRow[];
|
|
796
800
|
};
|
|
797
801
|
/** The injectable wall-clock the run loop uses for the durable-resume human-review latency
|
|
798
802
|
* (`humanLatencyMs = now() − cp.suspendedAt`), so it reads the SAME clock as the suspend-side `suspendedAt`. */
|
|
@@ -908,7 +912,14 @@ export interface Prepared {
|
|
|
908
912
|
* that were re-attached WHOLE. Present only with hands (same gate as readTaskFile).
|
|
909
913
|
* `preserveReadState` carries the kept-tail-visible files that were
|
|
910
914
|
* deliberately skipped for re-attachment — their existing entry must survive the clear too (same
|
|
911
|
-
* reasoning as a seeded entry: the model's view of the file did not change).
|
|
915
|
+
* reasoning as a seeded entry: the model's view of the file did not change).
|
|
916
|
+
*
|
|
917
|
+
* SCOPE — read "every compaction lane" as exactly that and no wider. The request-layer
|
|
918
|
+
* stale-result pass takes a Read body off the wire with no compaction at all, and retires its own
|
|
919
|
+
* entries at its own landing site (`prepare-context-lane.ts`'s `landStaleResultClears`) through the
|
|
920
|
+
* SAME function this seat calls, on its `tool-results-cleared` arm: ONE retirement mechanism, two
|
|
921
|
+
* ways for content to leave. Taken as a claim about every SHRINK mechanism this sentence was true
|
|
922
|
+
* of the lanes it named and silently false of the one it did not. */
|
|
912
923
|
onCompactionApplied?: (attachedComplete: ReadonlyArray<{
|
|
913
924
|
path: string;
|
|
914
925
|
content: string;
|
|
@@ -1184,13 +1195,7 @@ export interface PrepareResume {
|
|
|
1184
1195
|
priorHumanReview?: {
|
|
1185
1196
|
count: number;
|
|
1186
1197
|
totalWaitMs: number;
|
|
1187
|
-
gates:
|
|
1188
|
-
kind: string;
|
|
1189
|
-
waitMs: number;
|
|
1190
|
-
decision?: string;
|
|
1191
|
-
toolName?: string;
|
|
1192
|
-
toolArg?: string;
|
|
1193
|
-
}>;
|
|
1198
|
+
gates: HumanReviewGateRow[];
|
|
1194
1199
|
};
|
|
1195
1200
|
/** When the suspend ran with a remote workspace, the {@link CheckpointState.workspaceHandle}
|
|
1196
1201
|
* to restore — prepare rebuilds the per-task env via `deps.executionEnvFactory` then `resumeVM(snapshotId)`
|
|
@@ -2659,6 +2664,12 @@ export interface CaptureOptOutRef {
|
|
|
2659
2664
|
outcome: "created" | "existed";
|
|
2660
2665
|
}>;
|
|
2661
2666
|
}
|
|
2667
|
+
/** The MCP re-dial verb's binding; bound only when the run declared MCP servers — a leg with
|
|
2668
|
+
* none has nothing to re-dial, and the stream verb then refuses TYPED rather than answering about a
|
|
2669
|
+
* materialization that was never made. Same shape and same law as {@link CaptureOptOutRef}. */
|
|
2670
|
+
export interface McpRedialRef {
|
|
2671
|
+
redial?: (server: string) => Promise<import("../mcp.js").McpReconnectResult>;
|
|
2672
|
+
}
|
|
2662
2673
|
/**
|
|
2663
2674
|
* The stream's LIVE state as the verb lanes read it — a view over the driver's own locals (getters, and one
|
|
2664
2675
|
* setter), never a copy: `resultValue` is written by the run body's `setResult` callback and by the failure
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { HumanReviewGateRow } from "../task-result.js";
|
|
1
2
|
import { type OnAsk, type PermissionResult, type ResolvedAsk, type ToolCallRequest } from "../tool-policy.js";
|
|
2
3
|
/** JSON.stringify throws on a cyclic value (a deployment
|
|
3
4
|
* policy's rewrite object carries no acyclicity guarantee) — `undefined` here means "no comparable
|
|
@@ -37,11 +38,5 @@ export declare function consumeInheritedAskGrant(grants: Map<string, {
|
|
|
37
38
|
}>, onAsk: OnAsk | undefined, humanReviewRef: {
|
|
38
39
|
count: number;
|
|
39
40
|
totalWaitMs: number;
|
|
40
|
-
gates:
|
|
41
|
-
kind: string;
|
|
42
|
-
waitMs: number;
|
|
43
|
-
decision?: string;
|
|
44
|
-
toolName?: string;
|
|
45
|
-
toolArg?: string;
|
|
46
|
-
}>;
|
|
41
|
+
gates: HumanReviewGateRow[];
|
|
47
42
|
}, decision: PermissionResult, req: ToolCallRequest): ResolvedAsk | undefined;
|
|
@@ -23,6 +23,7 @@ export function consumeInheritedAskGrant(grants, onAsk, humanReviewRef, decision
|
|
|
23
23
|
waitMs: grant.waitMs,
|
|
24
24
|
decision: "allow",
|
|
25
25
|
toolName: req.toolName,
|
|
26
|
+
toolCallId: req.toolCallId,
|
|
26
27
|
...(toolArg !== undefined ? { toolArg } : {}),
|
|
27
28
|
});
|
|
28
29
|
const reused = { action: "allow", presentedInput: grant.presented };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The LIVE MCP roster lane: applying one server's intake result to a RUNNING roster, and the
|
|
3
|
+
* host-driven re-dial that is the second caller of it.
|
|
4
|
+
*
|
|
5
|
+
* Two faces change a task's MCP roster after prepare has finished — the model's `RefreshMcpTools`
|
|
6
|
+
* tool and the run handle's `reconnectMcpServer` verb — and what they must do to the roster is the
|
|
7
|
+
* same list in the same order: drop the deployment-excluded names, re-fold the safety axes (with the
|
|
8
|
+
* non-monotone restore arm on refusal), replace the server's whole name-prefix domain at its own
|
|
9
|
+
* anchor, rebuild the harness toolset, and re-mint the roster delta. That list lived inside the
|
|
10
|
+
* refresh tool's execute closure, where the second face could only have copied it; a copy would have
|
|
11
|
+
* been a second answer to "what does a mid-task roster change consist of".
|
|
12
|
+
*
|
|
13
|
+
* It is machinery, not a phase: the protocol-tools phase reaches DOWN for it to build the refresh
|
|
14
|
+
* tool, and the run's verb wiring reaches down for the same functions.
|
|
15
|
+
*/
|
|
16
|
+
import type { AgentTool } from "../../internal/harness.js";
|
|
17
|
+
import type { McpDroppedTool, McpReconnectResult, MaterializedMcp, McpToolAxis } from "../mcp.js";
|
|
18
|
+
import type { RunnerDeps, ToolEffect } from "../types.js";
|
|
19
|
+
import type { IrreversibilityTier, ToolFaceSnapshot } from "./contracts.js";
|
|
20
|
+
import type { RosterBuilder, ToolRosterDeltaSeat } from "../tool-roster.js";
|
|
21
|
+
/** One server's assertion about its own prefix domain — the shape both faces reduce to. `tools` is
|
|
22
|
+
* the roster the domain must END UP with (empty withdraws it); `added`/`removed` are the reporting
|
|
23
|
+
* half and never drive the swap. */
|
|
24
|
+
export interface McpRosterDelta {
|
|
25
|
+
server: string;
|
|
26
|
+
prefix: string;
|
|
27
|
+
tools: AgentTool[];
|
|
28
|
+
axes: McpToolAxis[];
|
|
29
|
+
dropped: McpDroppedTool[];
|
|
30
|
+
}
|
|
31
|
+
/** The seats a roster application writes through. Every one of them is borrowed from the phase that
|
|
32
|
+
* minted it — this module owns nothing. */
|
|
33
|
+
export interface McpRosterApplyInput {
|
|
34
|
+
/** borrowed-readonly — the frozen tool face; `exclude` keeps a deployment-excluded tool from ever
|
|
35
|
+
* being re-mounted by a face the model or a host can trigger. */
|
|
36
|
+
toolFaceSnapshot: Pick<ToolFaceSnapshot, "exclude">;
|
|
37
|
+
/** borrowed-readonly — the per-server face overlay (the phase's `withFaces`). */
|
|
38
|
+
withFaces: (tools: AgentTool[], server: string) => AgentTool[];
|
|
39
|
+
/** borrowed-readonly — the large-result wrapper every protocol tool is mounted through. */
|
|
40
|
+
remoteToolOffload: (tool: AgentTool) => AgentTool;
|
|
41
|
+
/** borrowed-readonly — the phase's axis fold (throws on a config contradiction). */
|
|
42
|
+
foldProtocolAxes: (axes: McpToolAxis[], label: string) => void;
|
|
43
|
+
/** borrowed-mutable — the two NON-MONOTONE tables the fold writes and this applier snapshots. */
|
|
44
|
+
toolEffects: Map<string, ToolEffect>;
|
|
45
|
+
axisExplicitNegatives: Map<string, {
|
|
46
|
+
irreversible?: false;
|
|
47
|
+
egress?: false;
|
|
48
|
+
}>;
|
|
49
|
+
/** borrowed-mutable — the run's shared roster. */
|
|
50
|
+
roster: RosterBuilder;
|
|
51
|
+
}
|
|
52
|
+
/** What one application did, for the caller's receipt. `applied:false` means the axis fold refused
|
|
53
|
+
* and the domain was left exactly as it was (the tables restored) — no roster write happened. */
|
|
54
|
+
export type McpRosterApplyOutcome = {
|
|
55
|
+
applied: true;
|
|
56
|
+
pushed: number;
|
|
57
|
+
excluded: string[];
|
|
58
|
+
} | {
|
|
59
|
+
applied: false;
|
|
60
|
+
foldError: string;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Apply one server's delta to the live roster. Behavior-identical to the body this was extracted
|
|
64
|
+
* from; the comments are the extracted reasoning, not new claims.
|
|
65
|
+
*/
|
|
66
|
+
export declare function applyMcpRosterDelta(input: McpRosterApplyInput, delta: McpRosterDelta): McpRosterApplyOutcome;
|
|
67
|
+
/** The seats the post-application commit writes through. */
|
|
68
|
+
export interface McpRosterCommitInput {
|
|
69
|
+
rebuildHarnessToolsRef: {
|
|
70
|
+
current?: () => Promise<void>;
|
|
71
|
+
};
|
|
72
|
+
rosterSeat: ToolRosterDeltaSeat;
|
|
73
|
+
toolCallGateArmedRef: {
|
|
74
|
+
armed: boolean;
|
|
75
|
+
};
|
|
76
|
+
irreversibleTools: ReadonlySet<string>;
|
|
77
|
+
egressTools: ReadonlySet<string>;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Publish whatever the applications above changed: rebuild the harness toolset (the content-origin
|
|
81
|
+
* re-wrap runs inside it), then re-mint the roster delta so the stream's rows describe the LIVE
|
|
82
|
+
* objects. Batched deliberately — a refresh spanning several servers rebuilds once.
|
|
83
|
+
*
|
|
84
|
+
* Returns the gate-honesty line when one is due. The built-in integrity policies are composed
|
|
85
|
+
* UNCONDITIONALLY, so `armed` is always true in this engine and the line never fires today; the arm
|
|
86
|
+
* stays as a zero-cost guard for a refactor that would make the composition conditional.
|
|
87
|
+
*/
|
|
88
|
+
export declare function commitMcpRosterChanges(input: McpRosterCommitInput): Promise<string | undefined>;
|
|
89
|
+
/** The re-dial's own seats: the handle it drives, the two operator sinks, and the run identity the
|
|
90
|
+
* disclosure names. */
|
|
91
|
+
export interface McpRedialInput extends McpRosterApplyInput, McpRosterCommitInput {
|
|
92
|
+
mcp: MaterializedMcp;
|
|
93
|
+
deps: Pick<RunnerDeps, "onError" | "onNotice">;
|
|
94
|
+
sessionId: string;
|
|
95
|
+
runId: string;
|
|
96
|
+
irreversibilityTier: IrreversibilityTier;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The run handle's `reconnectMcpServer` verb, phase-side: re-dial one server, put whatever it
|
|
100
|
+
* asserted about its prefix domain onto the live roster, and disclose.
|
|
101
|
+
*
|
|
102
|
+
* The roster is written IFF the result carries `tools` — the one rule this lane reads. That covers
|
|
103
|
+
* both directions with no second condition: a successful re-dial asserts its fresh roster, a failed
|
|
104
|
+
* DIAL asserts the empty one (the connection it tore down is gone, so leaving its names mounted would
|
|
105
|
+
* leave the model holding tools that can only fail), and an arm that never touched the connection —
|
|
106
|
+
* unknown name, revoked server, re-dial already in flight, disposed materialization — asserts nothing
|
|
107
|
+
* and the mounted roster is left alone.
|
|
108
|
+
*/
|
|
109
|
+
export declare function redialMcpServer(input: McpRedialInput, server: string): Promise<McpReconnectResult>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { deliverEngineNotice } from "../types.js";
|
|
2
|
+
export function applyMcpRosterDelta(input, delta) {
|
|
3
|
+
const { toolFaceSnapshot, withFaces, remoteToolOffload, foldProtocolAxes, toolEffects, axisExplicitNegatives, roster } = input;
|
|
4
|
+
const excludedSet = new Set(toolFaceSnapshot.exclude ?? []);
|
|
5
|
+
const pushable = delta.tools.filter((t) => !excludedSet.has(t.name));
|
|
6
|
+
const excluded = delta.tools.filter((t) => excludedSet.has(t.name)).map((t) => t.name);
|
|
7
|
+
const domainSnapshot = (m) => new Map([...m].filter(([name]) => name.startsWith(delta.prefix)));
|
|
8
|
+
const restoreDomain = (m, snap) => {
|
|
9
|
+
for (const name of [...m.keys()])
|
|
10
|
+
if (name.startsWith(delta.prefix))
|
|
11
|
+
m.delete(name);
|
|
12
|
+
for (const [name, v] of snap)
|
|
13
|
+
m.set(name, v);
|
|
14
|
+
};
|
|
15
|
+
const priorDomainEffects = domainSnapshot(toolEffects);
|
|
16
|
+
const priorDomainNegatives = domainSnapshot(axisExplicitNegatives);
|
|
17
|
+
for (const name of priorDomainEffects.keys())
|
|
18
|
+
toolEffects.delete(name);
|
|
19
|
+
for (const name of priorDomainNegatives.keys())
|
|
20
|
+
axisExplicitNegatives.delete(name);
|
|
21
|
+
try {
|
|
22
|
+
foldProtocolAxes(delta.axes.filter((a) => !excludedSet.has(a.name)), "MCP");
|
|
23
|
+
}
|
|
24
|
+
catch (foldErr) {
|
|
25
|
+
restoreDomain(toolEffects, priorDomainEffects);
|
|
26
|
+
restoreDomain(axisExplicitNegatives, priorDomainNegatives);
|
|
27
|
+
return { applied: false, foldError: foldErr instanceof Error ? foldErr.message : String(foldErr) };
|
|
28
|
+
}
|
|
29
|
+
roster.replaceDomain(delta.prefix, withFaces(pushable, delta.server).map((t) => remoteToolOffload(t)), { source: "mcp", mountedBy: "optional", origin: { peer: delta.server } });
|
|
30
|
+
return { applied: true, pushed: pushable.length, excluded };
|
|
31
|
+
}
|
|
32
|
+
export async function commitMcpRosterChanges(input) {
|
|
33
|
+
await input.rebuildHarnessToolsRef.current?.();
|
|
34
|
+
input.rosterSeat.republish();
|
|
35
|
+
if (!input.toolCallGateArmedRef.armed && (input.irreversibleTools.size > 0 || input.egressTools.size > 0)) {
|
|
36
|
+
return "WARNING: refreshed tools declare irreversible/egress safety hints, but this task started with no approval gate registered (no policy/hooks and no gated tools at start) — these hints cannot arm a gate mid-task (RB-46); the refreshed tools run ungated on this deployment shape.";
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
export async function redialMcpServer(input, server) {
|
|
41
|
+
const result = await input.mcp.reconnect(server);
|
|
42
|
+
if (result.tools !== undefined) {
|
|
43
|
+
const outcome = applyMcpRosterDelta(input, { server: result.server, prefix: result.prefix, tools: result.tools, axes: result.axes ?? [], dropped: result.dropped ?? [] });
|
|
44
|
+
if (!outcome.applied)
|
|
45
|
+
input.deps.onError?.(Object.assign(new Error(`MCP re-dial of "${server}" connected but its safety-axis fold refused, so its tools were not mounted: ${outcome.foldError}`), { code: "mcp.server_unavailable" }), { phase: "mcp", sessionId: input.sessionId });
|
|
46
|
+
const ungated = await commitMcpRosterChanges(input);
|
|
47
|
+
if (ungated !== undefined)
|
|
48
|
+
input.deps.onError?.(Object.assign(new Error(ungated), { code: "mcp.server_unavailable" }), { phase: "mcp", sessionId: input.sessionId });
|
|
49
|
+
}
|
|
50
|
+
for (const w of result.warnings ?? [])
|
|
51
|
+
input.deps.onError?.(Object.assign(new Error(w), { code: "mcp.server_unavailable" }), { phase: "mcp", sessionId: input.sessionId });
|
|
52
|
+
deliverEngineNotice(input.deps.onNotice, {
|
|
53
|
+
code: "mcp.server_redialed",
|
|
54
|
+
message: result.outcome === "reconnected"
|
|
55
|
+
? `MCP server "${server}" was re-dialed at the host's request: ${result.toolCount} tool${result.toolCount === 1 ? "" : "s"} mounted (${result.added.length} added, ${result.removed.length} removed)`
|
|
56
|
+
: `MCP server "${server}" was NOT re-dialed (${result.outcome})${result.reason !== undefined ? `: ${result.reason}` : ""}`,
|
|
57
|
+
detail: { server, outcome: result.outcome, added: result.added.length, removed: result.removed.length, ...(result.reason !== undefined ? { reason: result.reason } : {}), sessionId: input.sessionId, runId: input.runId },
|
|
58
|
+
});
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
@@ -94,6 +94,8 @@ export declare const AUTO_RUN_ON_RECOMMENDATION_DEFAULT = true;
|
|
|
94
94
|
* switch wired AND a model seat resolvable) — the reference CLI's "auto-dream: off while
|
|
95
95
|
* auto-memory is off": a default-on world must not arm a deployment that wired no consolidation;
|
|
96
96
|
* - any other value ⇒ off (never arm on garbage; the door refuses it at prepare anyway).
|
|
97
|
+
*
|
|
98
|
+
* @contract memory.auto_consolidation.arming_has_one_resolver — whether a deployment's automatic consolidation is armed is answered here and nowhere else, a host that merely DISPLAYS the arming included: an ABSENT seat is not "off" (it reads the named default, and only where both seats are wired), so a reader branching on `RunnerDeps.autoRunOnRecommendation` alone is wrong in BOTH directions — it calls a default-armed deployment unarmed and a deployment with no consolidation armed. A deployment fact, never a per-session one; exported from the package root for that reason.
|
|
97
99
|
*/
|
|
98
100
|
export declare function autoRunOnRecommendationEffective(deps: MemoryConsolidationDriverDeps & {
|
|
99
101
|
autoRunOnRecommendation?: unknown;
|
|
@@ -7,7 +7,7 @@ import { isSelfOrchestrationActive } from "../../orchestration/workflow-script-r
|
|
|
7
7
|
import { resolveWorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
|
|
8
8
|
import { autoModeArmingRecipeOf } from "../auto-mode-arming.js";
|
|
9
9
|
import { buildAutoModePrompt, renderAutoModeAction, renderAutoModeWindow } from "../auto-mode-prompt.js";
|
|
10
|
-
import { autoModeClassifierFallbackOf,
|
|
10
|
+
import { autoModeClassifierFallbackOf, createAutoModeDecider, createAutoModeDenialTracker } from "../auto-mode.js";
|
|
11
11
|
import { checkpointStoreOfSeat } from "../checkpoint-seat.js";
|
|
12
12
|
import { COMPLIANCE_CAPABILITIES, WEB_FETCH_TOOL_NAME, resolveComplianceDenies } from "../compliance.js";
|
|
13
13
|
import { createPresentPlanTool, createEnterPlanModeTool } from "../present-plan-tool.js";
|
|
@@ -207,27 +207,24 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
207
207
|
const classifierSystemPrompt = buildAutoModePrompt(classifierLaneRule ? { ...am, crossSessionMessagesRule: CROSS_SESSION_CLASSIFIER_RULE } : am);
|
|
208
208
|
const classifierRuntime = brainToRuntime(deps.brain);
|
|
209
209
|
const classifierRungs = classifierLadderRungs(am.classifierFallback, classifierModel, model);
|
|
210
|
-
const roundPrompt = new WeakMap();
|
|
211
210
|
autoModeDenialTracking = createAutoModeDenialTracker(am.denialLimit);
|
|
212
211
|
autoModeDecider = createAutoModeDecider({
|
|
213
212
|
...(am.timeoutMs !== undefined ? { timeoutMs: am.timeoutMs } : {}),
|
|
214
213
|
candidates: classifierRungs.map((m) => m.id),
|
|
215
214
|
onClassified: classifierRoundObserver({ tracer: deps.tracer, onNotice: deps.onNotice, taskId: hostTaskId, seatModelId: classifierModel.id, ...(sessionId !== undefined ? { sessionId } : {}), ...(runId !== undefined ? { runId } : {}) }),
|
|
216
|
-
|
|
215
|
+
assemble: async (input) => {
|
|
216
|
+
const ctx = await session.buildContext();
|
|
217
|
+
const known = ctx.messages.filter((m) => !isSyntheticApiErrorMessage(m) && (m.role === "user" || m.role === "assistant" || m.role === "toolResult"));
|
|
218
|
+
return renderAutoModeWindow(known, am.window) + renderAutoModeAction(input);
|
|
219
|
+
},
|
|
220
|
+
classify: async (_input, signal, rung, userPrompt) => {
|
|
217
221
|
const rungModel = rung !== undefined ? classifierRungs[rung.index] : undefined;
|
|
218
222
|
if (rung === undefined || rungModel === undefined) {
|
|
219
223
|
throw new Error(`the auto-mode classifier was asked at ladder position ${JSON.stringify(rung?.index)}, which this arming did not build`);
|
|
220
224
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
build = (async () => {
|
|
224
|
-
const ctx = await session.buildContext();
|
|
225
|
-
const known = ctx.messages.filter((m) => !isSyntheticApiErrorMessage(m) && (m.role === "user" || m.role === "assistant" || m.role === "toolResult"));
|
|
226
|
-
return renderAutoModeWindow(known, am.window) + renderAutoModeAction(input);
|
|
227
|
-
})();
|
|
228
|
-
roundPrompt.set(input, build);
|
|
225
|
+
if (userPrompt === undefined) {
|
|
226
|
+
throw new Error("the auto-mode classifier was asked with no assembled question, which this arming builds once per classification");
|
|
229
227
|
}
|
|
230
|
-
const userPrompt = await build.catch((e) => { throw autoModeRoundNotAsked(e); });
|
|
231
228
|
const classifierAuth = await spec.getApiKeyAndHeaders?.(rungModel);
|
|
232
229
|
const request = async (seat, suffix) => {
|
|
233
230
|
const response = await classifierRuntime.completeSimple(rungModel, { systemPrompt: classifierSystemPrompt, messages: [{ role: "user", content: userPrompt + suffix, timestamp: Date.now() }] }, {
|
|
@@ -17,11 +17,12 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { type AgentHarness, type AgentMessage } from "../../internal/harness.js";
|
|
19
19
|
import type { Model } from "../../internal/llm.js";
|
|
20
|
-
import { type MicroCompactMachine, type OccurrenceIndex } from "../context-edit.js";
|
|
20
|
+
import { type ClearedProjectionLedger, type MicroCompactMachine, type OccurrenceFingerprint, type OccurrenceIndex } from "../context-edit.js";
|
|
21
21
|
import { type ToolResultStore } from "../tool-result-store.js";
|
|
22
22
|
import { type RunnerDeps } from "../types.js";
|
|
23
23
|
import type { Prepared, PreparedMicroCompact } from "./contracts.js";
|
|
24
24
|
import { type GitStatusLaneRef } from "./git-status-frame.js";
|
|
25
|
+
import type { ReadFileState } from "../../tools/fs/safety.js";
|
|
25
26
|
export interface PrepareContextLaneInput {
|
|
26
27
|
/** borrowed-readonly — the task model: the budgets, the trigger-window geometry and `charsPerToken` are read off it. */
|
|
27
28
|
model: Model;
|
|
@@ -56,6 +57,12 @@ export interface PrepareContextLaneInput {
|
|
|
56
57
|
requestLossyRef: {
|
|
57
58
|
current: boolean;
|
|
58
59
|
};
|
|
60
|
+
/** borrowed-mutable — the hands' read-state map, or undefined without hands. Writer here: a landed stale-result
|
|
61
|
+
* clear RETIRES the entries whose transmission it just replaced with a marker — the same retirement the compaction
|
|
62
|
+
* lanes run through the SAME function (`applyContextShrinkToReadFileState`), because a Read body taken off the wire
|
|
63
|
+
* is off the wire whichever mechanism took it. Without it a re-read of an unchanged file answers "content omitted"
|
|
64
|
+
* pointing at a cleared marker. */
|
|
65
|
+
readFileState?: ReadFileState;
|
|
59
66
|
/** borrowed-mutable — the trim-pressure flag (`Prepared.trimPressureRef`). Writer here: the guard chain's trim arm sets
|
|
60
67
|
* `droppedMessages`; the run loop's boundary reads and clears it. */
|
|
61
68
|
trimPressureRef: {
|
|
@@ -72,6 +79,38 @@ export interface PrepareContextLaneResult {
|
|
|
72
79
|
}
|
|
73
80
|
/** The M21 phase body — prepareTask's context-lane stretch, verbatim (see the module header). */
|
|
74
81
|
export declare function prepareContextLane(input: PrepareContextLaneInput): PrepareContextLaneResult;
|
|
82
|
+
/** Record a cc-machine pass's clears into the ledger + emit the G11
|
|
83
|
+
* frame (one frame per firing pass; the replay of these markers on later requests emits nothing —
|
|
84
|
+
* cardinality is per CLEAR, not per request). `trigger` names the arm: `"frontier"` = the
|
|
85
|
+
* proactive request-build pass, `"blocking"` = the slice-3 arm-A pre-guard pass (the machine's
|
|
86
|
+
* one shot when the frontier hook is off). Marker bytes are read back off the edited array
|
|
87
|
+
* (the machine composed them, ref/media notes included) so the ledger replays EXACTLY what went
|
|
88
|
+
* on the wire; an occurrence the index cannot key (pathological same-id-same-timestamp ambiguity)
|
|
89
|
+
* is simply not recorded — its clear stays request-local, the safe (full-text-returns) direction.
|
|
90
|
+
* `at.fp` is the occurrence's fingerprint in the PRE-CAP replayed view (the same bytes the next
|
|
91
|
+
* session rebuild presents) — the replay-side mis-paint guard's comparand. */
|
|
92
|
+
/**
|
|
93
|
+
* The ONE landing of a set of stale-result clears: the ledger entry that makes the marker survive every
|
|
94
|
+
* later rebuild, and the retirement of the read-state entries those transmissions carried. The pair is
|
|
95
|
+
* inseparable — a ledger entry WITHOUT the retirement leaves a dedup stub pointing at a marker — which
|
|
96
|
+
* is why it is a function rather than two statements repeated at each of the two clear sites (the
|
|
97
|
+
* frontier/blocking pass and the provider-rejection recovery arm, which used to write the ledger
|
|
98
|
+
* independently, so only one of them retired anything). Posture: a ledger entry means the marker is
|
|
99
|
+
* repainted on every later request build, so the occurrence is cleared for good even when the
|
|
100
|
+
* projection that produced it is discarded — which is why the retirement belongs here and not in the
|
|
101
|
+
* per-request observation buffer.
|
|
102
|
+
*/
|
|
103
|
+
export declare function landStaleResultClears(args: {
|
|
104
|
+
ledger: ClearedProjectionLedger;
|
|
105
|
+
cleared: ReadonlyArray<{
|
|
106
|
+
key: string;
|
|
107
|
+
marker: string;
|
|
108
|
+
groupCount: number;
|
|
109
|
+
fp: OccurrenceFingerprint;
|
|
110
|
+
toolCallId?: string;
|
|
111
|
+
}>;
|
|
112
|
+
readFileState?: ReadFileState;
|
|
113
|
+
}): void;
|
|
75
114
|
/** See {@link runGuardChain}. */
|
|
76
115
|
export interface GuardChainArgs {
|
|
77
116
|
/** The post-frontier view the chain starts from. */
|
|
@@ -94,6 +133,9 @@ export interface GuardChainArgs {
|
|
|
94
133
|
/** The r7 per-request observation buffer — the chain DROPS it on its early returns (a discarded
|
|
95
134
|
* provisional projection must not narrate itself). */
|
|
96
135
|
pendingProjectionObservations: Array<() => void>;
|
|
136
|
+
/** The hands' read-state map, or undefined without hands — arm A's clear retires the entries its
|
|
137
|
+
* cleared transmissions carried (see `PrepareContextLaneInput.readFileState`). */
|
|
138
|
+
readFileState?: ReadFileState;
|
|
97
139
|
}
|
|
98
140
|
export type GuardChainOutcome = {
|
|
99
141
|
earlyReturn: {
|