@sema-agent/core 7.5.1 → 7.6.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 +61 -0
- package/dist/agents/cascade.d.ts +2 -2
- package/dist/agents/cascade.js +12 -10
- package/dist/agents/repair-loop.d.ts +5 -3
- package/dist/agents/repair-loop.js +13 -15
- package/dist/agents/subagent.d.ts +24 -42
- package/dist/agents/subagent.js +119 -105
- package/dist/agents/suspend-guard.d.ts +31 -19
- package/dist/agents/suspend-guard.js +14 -8
- package/dist/agents/teacher.js +9 -9
- package/dist/agents/team.d.ts +4 -3
- package/dist/agents/team.js +10 -8
- package/dist/agents/verify.d.ts +3 -3
- package/dist/agents/verify.js +17 -17
- package/dist/core/a2a.js +2 -1
- package/dist/core/ask-origin.d.ts +60 -7
- package/dist/core/ask-origin.js +26 -1
- package/dist/core/checkpoint-store.d.ts +78 -76
- package/dist/core/checkpoint-store.js +17 -1
- package/dist/core/gate-outcome.d.ts +189 -0
- package/dist/core/gate-outcome.js +70 -0
- package/dist/core/hooks.d.ts +18 -92
- package/dist/core/hooks.js +88 -85
- package/dist/core/mcp-failure.d.ts +104 -0
- package/dist/core/mcp-failure.js +128 -0
- package/dist/core/mcp.d.ts +21 -77
- package/dist/core/mcp.js +76 -150
- package/dist/core/pause-registry.d.ts +131 -0
- package/dist/core/pause-registry.js +27 -0
- package/dist/core/protocol-table.d.ts +5 -0
- package/dist/core/protocol-table.js +1 -0
- package/dist/core/runner/abort-race.d.ts +41 -0
- package/dist/core/runner/abort-race.js +38 -0
- package/dist/core/runner/assemble-result.d.ts +32 -41
- package/dist/core/runner/assemble-result.js +55 -74
- package/dist/core/runner/checkpoint-scope.d.ts +15 -3
- package/dist/core/runner/checkpoint-scope.js +3 -0
- package/dist/core/runner/compaction-call-options.d.ts +1 -1
- package/dist/core/runner/content-ask-bindings.d.ts +27 -0
- package/dist/core/runner/content-ask-bindings.js +1 -0
- package/dist/core/runner/contracts.d.ts +144 -50
- package/dist/core/runner/denial-limit-arms.d.ts +24 -1
- package/dist/core/runner/denial-limit-arms.js +24 -3
- package/dist/core/runner/gate-exit.d.ts +74 -0
- package/dist/core/runner/gate-exit.js +55 -0
- package/dist/core/runner/inherited-ask-grants.d.ts +46 -0
- package/dist/core/runner/inherited-ask-grants.js +29 -0
- package/dist/core/runner/park-commit.d.ts +102 -0
- package/dist/core/runner/park-commit.js +31 -0
- package/dist/core/runner/{prepare-permission-rules.d.ts → permission-rule-lanes.d.ts} +109 -3
- package/dist/core/runner/{prepare-permission-rules.js → permission-rule-lanes.js} +47 -1
- package/dist/core/runner/prepare-ask-lane.d.ts +107 -0
- package/dist/core/runner/prepare-ask-lane.js +131 -0
- package/dist/core/runner/prepare-boundary-parks.d.ts +102 -0
- package/dist/core/runner/prepare-boundary-parks.js +169 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
- package/dist/core/runner/prepare-context-lane.d.ts +119 -0
- package/dist/core/runner/prepare-context-lane.js +230 -0
- package/dist/core/runner/prepare-gate-stations.d.ts +174 -0
- package/dist/core/runner/prepare-gate-stations.js +265 -0
- package/dist/core/runner/prepare-hands-readface.d.ts +4 -4
- package/dist/core/runner/prepare-inherited-gate.d.ts +4 -4
- package/dist/core/runner/prepare-inherited-gate.js +1 -1
- package/dist/core/runner/prepare-memory-engine-session.d.ts +84 -0
- package/dist/core/runner/prepare-memory-engine-session.js +233 -0
- package/dist/core/runner/prepare-memory.d.ts +44 -26
- package/dist/core/runner/prepare-park-ask.d.ts +162 -0
- package/dist/core/runner/prepare-park-ask.js +377 -0
- package/dist/core/runner/prepare-policy-chain.d.ts +208 -0
- package/dist/core/runner/prepare-policy-chain.js +584 -0
- package/dist/core/runner/prepare-project-context.d.ts +1 -13
- package/dist/core/runner/prepare-project-context.js +1 -3
- package/dist/core/runner/prepare-prompt-assembly.d.ts +95 -0
- package/dist/core/runner/prepare-prompt-assembly.js +162 -0
- package/dist/core/runner/prepare-prompt-inputs.d.ts +1 -20
- package/dist/core/runner/prepare-protocol-tools.d.ts +3 -3
- package/dist/core/runner/prepare-protocol-tools.js +0 -3
- package/dist/core/runner/prepare-question-face.d.ts +3 -21
- package/dist/core/runner/prepare-question-face.js +2 -1
- package/dist/core/runner/prepare-safety-scan.d.ts +0 -5
- package/dist/core/runner/prepare-safety-scan.js +1 -2
- package/dist/core/runner/prepare-suspend-saga.d.ts +170 -0
- package/dist/core/runner/prepare-suspend-saga.js +308 -0
- package/dist/core/runner/prepare-task.d.ts +9 -136
- package/dist/core/runner/prepare-task.js +47 -2745
- package/dist/core/runner/prepare-turn-wiring.d.ts +154 -0
- package/dist/core/runner/prepare-turn-wiring.js +201 -0
- package/dist/core/runner/prepare-wiring-manifest.d.ts +17 -17
- package/dist/core/runner/prepare-wiring-manifest.js +16 -10
- package/dist/core/runner/prepare-workspace-restore.d.ts +2 -29
- package/dist/core/runner/prepare-workspace-restore.js +3 -16
- package/dist/core/runner/prompt-hash-salt.d.ts +1 -0
- package/dist/core/runner/prompt-hash-salt.js +2 -0
- package/dist/core/runner/remote-env-retry.d.ts +29 -0
- package/dist/core/runner/remote-env-retry.js +16 -0
- package/dist/core/runner/runtask.d.ts +16 -31
- package/dist/core/runner/runtask.js +110 -121
- package/dist/core/runner/terminal-projection.d.ts +22 -0
- package/dist/core/runner/terminal-projection.js +28 -0
- package/dist/core/session.d.ts +12 -0
- package/dist/core/session.js +3 -0
- package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +8 -2
- package/dist/core/terminal-cause.d.ts +137 -0
- package/dist/core/terminal-cause.js +9 -0
- package/dist/core/tool-policy.d.ts +43 -139
- package/dist/core/tool-policy.js +79 -112
- package/dist/core/types.d.ts +67 -164
- package/dist/core/wiring-manifest.d.ts +6 -3
- package/dist/core/workflow-journal-store.js +3 -4
- package/dist/engine/harness/agent-harness.d.ts +1 -1
- package/dist/index.d.ts +10 -7
- package/dist/index.js +8 -5
- package/dist/orchestration/builtin-workflows.d.ts +2 -2
- package/dist/orchestration/builtin-workflows.js +1 -1
- package/dist/orchestration/goal.js +8 -7
- package/dist/orchestration/run-spec.js +5 -3
- package/dist/orchestration/run-workflow-tool.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.js +4 -4
- package/dist/orchestration/workflow-governance.d.ts +4 -4
- package/dist/orchestration/workflow-governance.js +4 -2
- package/dist/orchestration/workflow-primitives.d.ts +1 -1
- package/dist/orchestration/workflow-primitives.js +1 -1
- package/dist/orchestration/workflow.d.ts +11 -0
- package/dist/orchestration/workflow.js +64 -39
- package/dist/prompts/supervisor.d.ts +1 -1
- package/dist/prompts/supervisor.js +3 -3
- package/dist/scenarios/scenario-registry.js +1 -1
- package/package.json +3 -1
- package/test/export-surface.snapshot.json +74 -22
- /package/dist/core/runner/{prepare-announce-once.d.ts → announce-once-ledger.d.ts} +0 -0
- /package/dist/core/runner/{prepare-announce-once.js → announce-once-ledger.js} +0 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* design/390 §1.2 M19 (boundary parks) — prepareTask's three BOUNDARY-FAMILY park lanes, verbatim from the driver's
|
|
3
|
+
* gate-machinery block: the ONE resource mint (`mintResourceSuspend`) behind the task-axis `suspendForResource` (exposed
|
|
4
|
+
* under the caller's `resourceSuspend` opt-in) and the infrastructure `suspendForPlatformLimit` (exposed on durable infra
|
|
5
|
+
* alone), and the `plan_review` mint behind `suspendForReview` (exposed with a checkpoint store). Every mint reuses the
|
|
6
|
+
* suspend saga's serializer, commit saga, loop cap and env split — this phase adds no second copy of any of them. The
|
|
7
|
+
* interface is the dependency list the segment had implicitly (design/238 R-1); the three lanes used to be forward-declared
|
|
8
|
+
* `let`s at task scope, assigned inside the block, so the driver's Prepared could expose them — they are this phase's
|
|
9
|
+
* Result now, and the driver binds them as consts.
|
|
10
|
+
*
|
|
11
|
+
* SYNCHRONOUS FUNCTION, SYNCHRONOUS CALL: the stretch has no await of its own (the mints run at a turn boundary), so the
|
|
12
|
+
* phase adds no yield between the suspend saga before it and the park closure after it (design/390 §1.5). Read-stability
|
|
13
|
+
* of the host handles for the call: {@link RunInternals} (`@contract prepare.deps-read-stable`).
|
|
14
|
+
*
|
|
15
|
+
* The lanes exist exactly when the saga does (the ask lane's presence law) — an absent saga answers three absent lanes,
|
|
16
|
+
* so the orchestrator grows no branch.
|
|
17
|
+
*/
|
|
18
|
+
import type { AgentHarness } from "../../internal/harness.js";
|
|
19
|
+
import { type CheckpointStore } from "../checkpoint-store.js";
|
|
20
|
+
import type { SessionStore } from "../session.js";
|
|
21
|
+
import type { RunnerDeps, TaskSpec } from "../types.js";
|
|
22
|
+
import type { Prepared, SuspendSaga } from "./contracts.js";
|
|
23
|
+
export interface PrepareBoundaryParksInput {
|
|
24
|
+
/** borrowed-readonly — the suspend saga's eight seats (the serializer, the commit saga, the loop cap, the env split, the
|
|
25
|
+
* park-only handle, the in-flight spend), or undefined when the gate machinery is inactive (then all three lanes are
|
|
26
|
+
* absent). Called at park time; never rebuilt here. */
|
|
27
|
+
saga: SuspendSaga | undefined;
|
|
28
|
+
/** borrowed-readonly — the REBOUND spec. Read: `resourceSuspend` (the slice scope/ttl/maxSlices), `retainBackgroundProcesses`
|
|
29
|
+
* (the park-only sweep gate), `durableApproval` (the review pause's ttl), `principal` (the rows' identity record and
|
|
30
|
+
* the review scope). Never mutated. */
|
|
31
|
+
spec: Pick<TaskSpec, "resourceSuspend" | "retainBackgroundProcesses" | "durableApproval" | "principal">;
|
|
32
|
+
/** borrowed-readonly — the leg's checkpoint store, or undefined (then the review lane is absent and the resource mint
|
|
33
|
+
* answers false). */
|
|
34
|
+
checkpointStore: CheckpointStore | undefined;
|
|
35
|
+
/** borrowed-mutable — prepare's abort seat. `.signal` is read (a cancelling run mints nothing); `abort()` is called on
|
|
36
|
+
* the review lane's commit (the review pause stops the run the human gate's way). */
|
|
37
|
+
abortController: {
|
|
38
|
+
readonly signal: AbortSignal;
|
|
39
|
+
abort(): void;
|
|
40
|
+
};
|
|
41
|
+
/** borrowed-mutable — the built harness. Writers here: `requestStopAfterTurn()` (the resource lanes' clean stop) and
|
|
42
|
+
* `abort()` (the review lane's stop). */
|
|
43
|
+
harness: Pick<AgentHarness, "abort" | "requestStopAfterTurn">;
|
|
44
|
+
/** borrowed-readonly — the acquired session; only `getLeafId` is read (no committed leaf ⇒ no consistent resume point). */
|
|
45
|
+
session: Pick<Prepared["session"], "getLeafId">;
|
|
46
|
+
/** borrowed-readonly — the session store; only the optional `pin` is called (best-effort, after commit). */
|
|
47
|
+
sessions: Pick<SessionStore, "pin">;
|
|
48
|
+
/** borrowed-readonly — the acquired session id (the rows' session, the source attribution, every disclosure's tag). */
|
|
49
|
+
sessionId: string;
|
|
50
|
+
/** borrowed-readonly — the deployment's error face, as a Pick over the SAME `deps` object (receiver preserved for
|
|
51
|
+
* `deps.onError?.()`): the suspendVM refusals, the skipped-review lines, the pin failures. */
|
|
52
|
+
deps: Pick<RunnerDeps, "onError">;
|
|
53
|
+
/** borrowed-readonly — the cross-slice ledger carried on the resumed checkpoint, or undefined on the first slice. */
|
|
54
|
+
priorLedger: Prepared["resourceLedger"];
|
|
55
|
+
/** borrowed-readonly — the resource-slice cap, or undefined (no slice ceiling). */
|
|
56
|
+
maxSlices: number | undefined;
|
|
57
|
+
/** borrowed-readonly — the restart-loop cap the review lane is bounded by. */
|
|
58
|
+
maxSuspends: number;
|
|
59
|
+
/** borrowed-readonly — the human total the first slice seeds the ledger from, or undefined. */
|
|
60
|
+
resourceTotal: {
|
|
61
|
+
totalBudgetMicroUsd?: number;
|
|
62
|
+
totalTokens?: number;
|
|
63
|
+
} | undefined;
|
|
64
|
+
/** borrowed-readonly — how many times this task already suspended (carried on the resumed checkpoint); a slice carries
|
|
65
|
+
* it UNCHANGED. */
|
|
66
|
+
priorSuspendCount: number;
|
|
67
|
+
/** borrowed-readonly — the restart-loop chain base (resets after an approved execution); the review lane's count. */
|
|
68
|
+
suspendChainBase: () => number;
|
|
69
|
+
/** borrowed-readonly — the per-task human-review accumulator; snapshotted onto every row (never written here). */
|
|
70
|
+
humanReviewRef: Prepared["humanReviewRef"];
|
|
71
|
+
/** borrowed-readonly — the loop's live spend seat; `.get` is read at mint time for the review ledger. */
|
|
72
|
+
liveSpendRef: Prepared["liveSpendRef"];
|
|
73
|
+
/** borrowed-readonly — the injectable wall clock (the review pause's `suspendedAt`). */
|
|
74
|
+
now: () => number;
|
|
75
|
+
/** borrowed-readonly — the inherited-gate phase's three version-stamp predicates (face / F-012 / org admission). */
|
|
76
|
+
faceCheckpointState: () => boolean;
|
|
77
|
+
/** borrowed-readonly — see `faceCheckpointState`. */
|
|
78
|
+
f012CheckpointState: () => boolean;
|
|
79
|
+
/** borrowed-readonly — see `faceCheckpointState`. */
|
|
80
|
+
orgAdmissionCheckpointState: () => boolean;
|
|
81
|
+
/** borrowed-mutable — the run's committed-pause holder (`Prepared.pausedRef`). Writer here: the commit-side publication
|
|
82
|
+
* of a committed resource / platform / plan-review checkpoint. */
|
|
83
|
+
pausedRef: Prepared["pausedRef"];
|
|
84
|
+
/** borrowed-mutable — the run-scoped remote-lifecycle failure log. Writer here: a refused `suspendVM` (`push`). */
|
|
85
|
+
remoteEnvFailures: Prepared["remoteEnvFailures"];
|
|
86
|
+
/** borrowed-readonly — the wiring-manifest phase's task-axis predicate (opt-in ∧ durable infra): exposes `suspendForResource`. */
|
|
87
|
+
resourceSuspendEligible: boolean;
|
|
88
|
+
/** borrowed-readonly — the wiring-manifest phase's durable-infra predicate: exposes `suspendForPlatformLimit`. */
|
|
89
|
+
durableSuspendInfraReady: boolean;
|
|
90
|
+
/** borrowed-readonly — the restore-incomplete adapter's missing members, or undefined (the review lane refuses loudly). */
|
|
91
|
+
incompleteSuspendAdapter: readonly ("resumeVM" | "postResumeInit")[] | undefined;
|
|
92
|
+
}
|
|
93
|
+
export interface PrepareBoundaryParksResult {
|
|
94
|
+
/** owned — the task-axis resource lane (`Prepared.suspendForResource`), or undefined (no opt-in / no infra / inactive). */
|
|
95
|
+
suspendForResource: Prepared["suspendForResource"];
|
|
96
|
+
/** owned — the platform lane (`Prepared.suspendForPlatformLimit`), or undefined (no durable infra / inactive). */
|
|
97
|
+
suspendForPlatformLimit: Prepared["suspendForPlatformLimit"];
|
|
98
|
+
/** owned — the plan-review lane (`Prepared.suspendForReview`), or undefined (no store / inactive). */
|
|
99
|
+
suspendForReview: Prepared["suspendForReview"];
|
|
100
|
+
}
|
|
101
|
+
/** The M19 boundary-parks phase body — prepareTask's three boundary lanes, verbatim (see the module header). */
|
|
102
|
+
export declare function prepareBoundaryParks(input: PrepareBoundaryParksInput): PrepareBoundaryParksResult;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.js";
|
|
2
|
+
import { BINDING_CHECKPOINT_VERSION, debitLedger, F012_CHECKPOINT_VERSION, FACE_CHECKPOINT_VERSION, mintCheckpointId, mintCheckpointToken, ORG_ADMISSION_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, } from "../checkpoint-store.js";
|
|
3
|
+
import { defaultTaskRegistry } from "../task-registry.js";
|
|
4
|
+
import { checkpointScopeOf } from "./checkpoint-scope.js";
|
|
5
|
+
import { DEFAULT_RESOURCE_TTL_MS, publishCommittedSuspend, sanitizedTtlMs, USAGE_WINDOW_REAP_MARGIN_MS } from "./park-commit.js";
|
|
6
|
+
import { remoteEnvFailureNote } from "./remote-env-retry.js";
|
|
7
|
+
export function prepareBoundaryParks(input) {
|
|
8
|
+
const { saga, spec, checkpointStore, abortController, harness, session, sessions, sessionId, deps, priorLedger, maxSlices, maxSuspends, resourceTotal, priorSuspendCount, suspendChainBase, humanReviewRef, liveSpendRef, now, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, pausedRef, remoteEnvFailures, resourceSuspendEligible, durableSuspendInfraReady, incompleteSuspendAdapter } = input;
|
|
9
|
+
if (saga === undefined)
|
|
10
|
+
return { suspendForResource: undefined, suspendForPlatformLimit: undefined, suspendForReview: undefined };
|
|
11
|
+
const { inFlightSpendMicroUsd, serializeCheckpointState, commitSuspendSaga, suspendLoopCapHit, suspendableEnv, parkOnlyRemoteEnv, parkOnlyHandle } = saga;
|
|
12
|
+
const mintResourceSuspend = async (reason, sliceSpend, hint) => {
|
|
13
|
+
const rs = spec.resourceSuspend;
|
|
14
|
+
if (!checkpointStore)
|
|
15
|
+
return false;
|
|
16
|
+
const scope = rs?.scope ?? checkpointScopeOf(spec);
|
|
17
|
+
if (abortController.signal.aborted)
|
|
18
|
+
return false;
|
|
19
|
+
if (suspendLoopCapHit(priorLedger?.sliceCount ?? 0, maxSlices, " (resource slice limit)"))
|
|
20
|
+
return false;
|
|
21
|
+
const leafId = await session.getLeafId();
|
|
22
|
+
if (!leafId)
|
|
23
|
+
return false;
|
|
24
|
+
if (abortController.signal.aborted)
|
|
25
|
+
return false;
|
|
26
|
+
let remoteHandle;
|
|
27
|
+
if (suspendableEnv !== undefined) {
|
|
28
|
+
if (hasBackgroundShell(suspendableEnv))
|
|
29
|
+
await sweepBackgroundShells(suspendableEnv, defaultTaskRegistry);
|
|
30
|
+
const snap = await suspendableEnv.suspendVM({ abortSignal: abortController.signal });
|
|
31
|
+
if (!snap.ok) {
|
|
32
|
+
remoteEnvFailures.push(remoteEnvFailureNote("suspendVM", snap.error, 1));
|
|
33
|
+
deps.onError?.(new Error(`resource suspendVM failed (${snap.error.code}): ${snap.error.message}`, { cause: snap.error }), { phase: "config", sessionId });
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
remoteHandle = { ...suspendableEnv.workspaceHandle(), snapshotId: snap.value };
|
|
37
|
+
}
|
|
38
|
+
else if (parkOnlyRemoteEnv !== undefined) {
|
|
39
|
+
if (hasBackgroundShell(parkOnlyRemoteEnv) && spec.retainBackgroundProcesses !== true)
|
|
40
|
+
await sweepBackgroundShells(parkOnlyRemoteEnv, defaultTaskRegistry);
|
|
41
|
+
remoteHandle = parkOnlyHandle(parkOnlyRemoteEnv);
|
|
42
|
+
}
|
|
43
|
+
const token = mintCheckpointToken();
|
|
44
|
+
const gate = { kind: "resource_limit", reason, ...(hint !== undefined ? { resumeAfterMs: hint.resumeAfterMs } : {}) };
|
|
45
|
+
const resourceLedgerOut = debitLedger(priorLedger, sliceSpend, resourceTotal);
|
|
46
|
+
const mintedAt = Date.now();
|
|
47
|
+
const cp = {
|
|
48
|
+
token,
|
|
49
|
+
checkpointId: mintCheckpointId(),
|
|
50
|
+
version: faceCheckpointState() ? FACE_CHECKPOINT_VERSION : f012CheckpointState() ? F012_CHECKPOINT_VERSION : orgAdmissionCheckpointState() ? ORG_ADMISSION_CHECKPOINT_VERSION : resourceLedgerOut.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : RESOURCE_CHECKPOINT_VERSION,
|
|
51
|
+
scope,
|
|
52
|
+
sessionId,
|
|
53
|
+
leafId,
|
|
54
|
+
gate,
|
|
55
|
+
pendingAction: { kind: "resource_limit", reason },
|
|
56
|
+
state: serializeCheckpointState(remoteHandle),
|
|
57
|
+
status: "pending",
|
|
58
|
+
createdAt: mintedAt,
|
|
59
|
+
deadline: mintedAt + Math.max(sanitizedTtlMs(rs?.ttlMs) ?? DEFAULT_RESOURCE_TTL_MS, hint !== undefined ? hint.resumeAfterMs + USAGE_WINDOW_REAP_MARGIN_MS : 0),
|
|
60
|
+
suspendCount: priorSuspendCount,
|
|
61
|
+
humanReview: humanReviewRef.count > 0
|
|
62
|
+
? { count: humanReviewRef.count, totalWaitMs: humanReviewRef.totalWaitMs, gates: [...humanReviewRef.gates] }
|
|
63
|
+
: undefined,
|
|
64
|
+
resourceLedger: resourceLedgerOut,
|
|
65
|
+
...(spec.durableApproval !== undefined ? { durableApproval: { ...spec.durableApproval } } : {}),
|
|
66
|
+
...(spec.principal ? { principal: spec.principal } : {}),
|
|
67
|
+
};
|
|
68
|
+
if ((await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)).tag !== "committed")
|
|
69
|
+
return false;
|
|
70
|
+
publishCommittedSuspend({ pausedRef }, token, gate, scope, remoteHandle, cp.checkpointId, cp.pendingAction);
|
|
71
|
+
try {
|
|
72
|
+
await sessions.pin?.(sessionId);
|
|
73
|
+
}
|
|
74
|
+
catch (pinErr) {
|
|
75
|
+
deps.onError?.(pinErr, { phase: "config", sessionId });
|
|
76
|
+
}
|
|
77
|
+
harness.requestStopAfterTurn();
|
|
78
|
+
return true;
|
|
79
|
+
};
|
|
80
|
+
const suspendForResource = resourceSuspendEligible ? mintResourceSuspend : undefined;
|
|
81
|
+
const suspendForPlatformLimit = durableSuspendInfraReady ? mintResourceSuspend : undefined;
|
|
82
|
+
const suspendForReview = checkpointStore
|
|
83
|
+
? async (reason) => {
|
|
84
|
+
if (!checkpointStore)
|
|
85
|
+
return false;
|
|
86
|
+
if (abortController.signal.aborted)
|
|
87
|
+
return false;
|
|
88
|
+
if (incompleteSuspendAdapter !== undefined) {
|
|
89
|
+
try {
|
|
90
|
+
deps.onError?.(new Error(`plan_review park skipped: the per-task execution env declares capabilities.suspendable but its adapter does not implement ${incompleteSuspendAdapter.join(" or ")}, so its workspace could not be restored on resume (the plan-review request was dropped; the run continues without pausing)`), { phase: "config", sessionId });
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
if (suspendLoopCapHit(suspendChainBase(), maxSuspends, " for a plan_review (likely a resume/restart loop)."))
|
|
97
|
+
return false;
|
|
98
|
+
const leafId = await session.getLeafId();
|
|
99
|
+
if (!leafId) {
|
|
100
|
+
try {
|
|
101
|
+
deps.onError?.(new Error("plan_review park skipped: no committed session leaf (the plan-review request was dropped; the run continues without pausing)"), { phase: "config", sessionId });
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
if (abortController.signal.aborted)
|
|
108
|
+
return false;
|
|
109
|
+
let remoteHandle;
|
|
110
|
+
if (suspendableEnv !== undefined) {
|
|
111
|
+
if (hasBackgroundShell(suspendableEnv))
|
|
112
|
+
await sweepBackgroundShells(suspendableEnv, defaultTaskRegistry);
|
|
113
|
+
const snap = await suspendableEnv.suspendVM({ abortSignal: abortController.signal });
|
|
114
|
+
if (!snap.ok) {
|
|
115
|
+
remoteEnvFailures.push(remoteEnvFailureNote("suspendVM", snap.error, 1));
|
|
116
|
+
deps.onError?.(new Error(`plan_review suspendVM failed (${snap.error.code}): ${snap.error.message}`, { cause: snap.error }), { phase: "config", sessionId });
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
remoteHandle = { ...suspendableEnv.workspaceHandle(), snapshotId: snap.value };
|
|
120
|
+
}
|
|
121
|
+
else if (parkOnlyRemoteEnv !== undefined) {
|
|
122
|
+
if (hasBackgroundShell(parkOnlyRemoteEnv) && spec.retainBackgroundProcesses !== true)
|
|
123
|
+
await sweepBackgroundShells(parkOnlyRemoteEnv, defaultTaskRegistry);
|
|
124
|
+
remoteHandle = parkOnlyHandle(parkOnlyRemoteEnv);
|
|
125
|
+
}
|
|
126
|
+
const token = mintCheckpointToken();
|
|
127
|
+
const scope = checkpointScopeOf(spec);
|
|
128
|
+
const gate = { kind: "plan_review", reason: reason ?? `plan review requested` };
|
|
129
|
+
const reviewLedger = debitLedger(priorLedger, liveSpendRef.get?.() ?? { costMicroUsd: 0, tokens: 0, turns: 0, walltimeMs: 0 }, resourceTotal, { countSlice: false });
|
|
130
|
+
const mintedAt = Date.now();
|
|
131
|
+
const cp = {
|
|
132
|
+
token,
|
|
133
|
+
checkpointId: mintCheckpointId(),
|
|
134
|
+
version: faceCheckpointState() ? FACE_CHECKPOINT_VERSION : f012CheckpointState() ? F012_CHECKPOINT_VERSION : orgAdmissionCheckpointState() ? ORG_ADMISSION_CHECKPOINT_VERSION : reviewLedger.totalTokens !== undefined ? TOKEN_CHECKPOINT_VERSION : BINDING_CHECKPOINT_VERSION,
|
|
135
|
+
scope,
|
|
136
|
+
sessionId,
|
|
137
|
+
leafId,
|
|
138
|
+
gate,
|
|
139
|
+
pendingAction: { kind: "plan_review" },
|
|
140
|
+
state: serializeCheckpointState(remoteHandle, inFlightSpendMicroUsd()),
|
|
141
|
+
status: "pending",
|
|
142
|
+
createdAt: mintedAt,
|
|
143
|
+
suspendedAt: now(),
|
|
144
|
+
deadline: mintedAt + (sanitizedTtlMs(spec.durableApproval?.ttlMs) ?? DEFAULT_RESOURCE_TTL_MS),
|
|
145
|
+
suspendCount: suspendChainBase() + 1,
|
|
146
|
+
humanReview: humanReviewRef.count > 0
|
|
147
|
+
? { count: humanReviewRef.count, totalWaitMs: humanReviewRef.totalWaitMs, gates: [...humanReviewRef.gates] }
|
|
148
|
+
: undefined,
|
|
149
|
+
resourceLedger: reviewLedger,
|
|
150
|
+
sourceTaskId: sessionId,
|
|
151
|
+
...(spec.principal ? { principal: spec.principal } : {}),
|
|
152
|
+
...(spec.durableApproval !== undefined ? { durableApproval: { ...spec.durableApproval } } : {}),
|
|
153
|
+
};
|
|
154
|
+
if ((await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)).tag !== "committed")
|
|
155
|
+
return false;
|
|
156
|
+
publishCommittedSuspend({ pausedRef }, token, gate, scope, remoteHandle, cp.checkpointId, cp.pendingAction);
|
|
157
|
+
try {
|
|
158
|
+
await sessions.pin?.(sessionId);
|
|
159
|
+
}
|
|
160
|
+
catch (pinErr) {
|
|
161
|
+
deps.onError?.(pinErr, { phase: "config", sessionId });
|
|
162
|
+
}
|
|
163
|
+
abortController.abort();
|
|
164
|
+
void harness.abort();
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
: undefined;
|
|
168
|
+
return { suspendForResource, suspendForPlatformLimit, suspendForReview };
|
|
169
|
+
}
|
|
@@ -244,7 +244,7 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
244
244
|
},
|
|
245
245
|
...(spec.handsReadOnly === true ? { parentHandsReadOnly: true } : {}), ...(spec.interactiveTools === false ? { parentInteractiveTools: false } : {}),
|
|
246
246
|
onNotice: deps.onNotice,
|
|
247
|
-
parentCheckpointStoreDisabled: spec.checkpointStore ===
|
|
247
|
+
parentCheckpointStoreDisabled: spec.checkpointStore === "disabled",
|
|
248
248
|
parentCenterArtifactDigest: () => input.centerAdoption()?.artifact.artifactDigest,
|
|
249
249
|
parentCenterSourceRevision: () => input.centerAdoption()?.sourceRevision,
|
|
250
250
|
...(forwardEvent ? { forwardEvent } : {}),
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* design/390 §1.2 M21 — prepareTask's CONTEXT-LANE phase, verbatim from the driver: the edit/guard budgets, the
|
|
3
|
+
* microCompact machine state with the trigger-window clamp's one loud outlet, the model's chars-per-token coordinate,
|
|
4
|
+
* and the `context` handler registered on the harness — heal → ledger replay → aggregate tool-result and media caps →
|
|
5
|
+
* frontier clear → the guard chain (arms A/B/C) → the git-frame guard → the orphan belt → the lossy-projection flag →
|
|
6
|
+
* the projection seat. The four module-level helpers only this lane called moved with it (the machine-state builder,
|
|
7
|
+
* the frontier-clear recorder, the observation-buffer flush, the guard chain with its two types). The interface is the
|
|
8
|
+
* dependency list the segment had implicitly (design/238 R-1).
|
|
9
|
+
*
|
|
10
|
+
* SYNCHRONOUS FUNCTION, SYNCHRONOUS CALL: the stretch has no await of its own (the handler it registers is async and
|
|
11
|
+
* runs per provider request), so the phase adds no yield between the gate stations before it and the turn wiring after
|
|
12
|
+
* it (design/390 §1.5). Read-stability of the host handles: {@link RunInternals} (`@contract prepare.deps-read-stable`).
|
|
13
|
+
*
|
|
14
|
+
* REGISTRATION SIDE EFFECT, OWNED: this phase registers the ONE `context` handler on the harness; the harness owns the
|
|
15
|
+
* listener for the run's life (it is never unregistered — the harness is per prepare). No other station registers on
|
|
16
|
+
* that hook.
|
|
17
|
+
*/
|
|
18
|
+
import { type AgentHarness, type AgentMessage } from "../../internal/harness.js";
|
|
19
|
+
import type { Model } from "../../internal/llm.js";
|
|
20
|
+
import { type ContextEditMachine, type OccurrenceIndex } from "../context-edit.js";
|
|
21
|
+
import { type ToolResultStore } from "../tool-result-store.js";
|
|
22
|
+
import { type RunnerDeps } from "../types.js";
|
|
23
|
+
import type { Prepared, PreparedMicroCompact } from "./contracts.js";
|
|
24
|
+
import { type GitStatusLaneRef } from "./git-status-frame.js";
|
|
25
|
+
export interface PrepareContextLaneInput {
|
|
26
|
+
/** borrowed-readonly — the task model: the budgets, the trigger-window geometry and `charsPerToken` are read off it. */
|
|
27
|
+
model: Model;
|
|
28
|
+
/** borrowed-readonly — the deployment seats, whole: `onNotice` (the clamp outlet, the offload-loss announcement), `tracer`
|
|
29
|
+
* (the cap / clear / trim frames), `mediaByteCapBytes` and `onMediaStripped` (the media cap), `onError` (the belt line, the
|
|
30
|
+
* git-frame degrade) — the guard chain and the machine-state builder read through the whole record. */
|
|
31
|
+
deps: RunnerDeps;
|
|
32
|
+
/** borrowed-readonly — the acquired session id (the offload persist key; every disclosure's attribution). */
|
|
33
|
+
sessionId: string;
|
|
34
|
+
/** borrowed-readonly — the run's invocation id (the clamp notice's per-run claim). */
|
|
35
|
+
runId: string;
|
|
36
|
+
/** borrowed-readonly — the per-task identity (the trace frames' taskId). */
|
|
37
|
+
hostTaskId: string;
|
|
38
|
+
/** borrowed-readonly — the run's offload store, or undefined (the caps' and the machine's persist seat). */
|
|
39
|
+
offloadStore: ToolResultStore | undefined;
|
|
40
|
+
/** borrowed-readonly — the doors' RESOLVED microCompact knob (immutable values, never the deps bag). */
|
|
41
|
+
microCompactKnob: {
|
|
42
|
+
machine: "off" | ContextEditMachine;
|
|
43
|
+
clearOnRejection: boolean;
|
|
44
|
+
};
|
|
45
|
+
/** borrowed-mutable — the built harness. Writer here: `on("context", …)` — the ONE registration of the request-build
|
|
46
|
+
* handler (the harness owns the listener; nothing unregisters it). */
|
|
47
|
+
harness: AgentHarness;
|
|
48
|
+
/** borrowed-mutable — the turn's halt fact (`Prepared.batchHaltRef`). Writer here: the handler CLEARS it at every new
|
|
49
|
+
* provider request (a new request means the halted boundary was legitimately passed). The judgment site sets it. */
|
|
50
|
+
batchHaltRef: Prepared["batchHaltRef"];
|
|
51
|
+
/** borrowed-mutable — the git-status lane ref. Writers here: the git-frame guard's degrade latch (`overBudgetShrunk`) inside
|
|
52
|
+
* the handler; the prompt-inputs phase seeds it. */
|
|
53
|
+
gitStatusRef: GitStatusLaneRef;
|
|
54
|
+
/** borrowed-mutable — the per-request lossy-projection flag. Writer here: the handler re-derives it at every request
|
|
55
|
+
* build; the provider-seam recording shim reads it immediately after. */
|
|
56
|
+
requestLossyRef: {
|
|
57
|
+
current: boolean;
|
|
58
|
+
};
|
|
59
|
+
/** borrowed-mutable — the trim-pressure flag (`Prepared.trimPressureRef`). Writer here: the guard chain's trim arm sets
|
|
60
|
+
* `droppedMessages`; the run loop's boundary reads and clears it. */
|
|
61
|
+
trimPressureRef: {
|
|
62
|
+
droppedMessages: boolean;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export interface PrepareContextLaneResult {
|
|
66
|
+
/** owned — the per-run microCompact machine state (`Prepared.microCompact`): machine, MC-R flag, ledger, the projection
|
|
67
|
+
* and in-turn-compaction seats the handler and the runtask arms fill. */
|
|
68
|
+
microCompact: PreparedMicroCompact;
|
|
69
|
+
/** owned — the model's structural coefficient (`charsPerToken ?? DEFAULT`), the ONE coordinate the request-layer
|
|
70
|
+
* defenses here and the prompt-overhead term downstream share with the compaction trigger estimate. */
|
|
71
|
+
charsPerToken: number;
|
|
72
|
+
}
|
|
73
|
+
/** The M21 phase body — prepareTask's context-lane stretch, verbatim (see the module header). */
|
|
74
|
+
export declare function prepareContextLane(input: PrepareContextLaneInput): PrepareContextLaneResult;
|
|
75
|
+
/** See {@link runGuardChain}. */
|
|
76
|
+
export interface GuardChainArgs {
|
|
77
|
+
/** The post-frontier view the chain starts from. */
|
|
78
|
+
edited: AgentMessage[];
|
|
79
|
+
/** The pre-cap counterpart view (arm A's clearSource — RB-212 durable composition seat). */
|
|
80
|
+
replayed: AgentMessage[];
|
|
81
|
+
index: OccurrenceIndex;
|
|
82
|
+
microCompact: PreparedMicroCompact;
|
|
83
|
+
guardAt: number;
|
|
84
|
+
charsPerToken: number;
|
|
85
|
+
offloadStore: import("../tool-result-store.js").ToolResultStore | undefined;
|
|
86
|
+
sessionId: string;
|
|
87
|
+
taskId: string;
|
|
88
|
+
deps: RunnerDeps;
|
|
89
|
+
trimPressureRef: {
|
|
90
|
+
droppedMessages: boolean;
|
|
91
|
+
};
|
|
92
|
+
recheck: boolean | undefined;
|
|
93
|
+
signal: AbortSignal | undefined;
|
|
94
|
+
/** The r7 per-request observation buffer — the chain DROPS it on its early returns (a discarded
|
|
95
|
+
* provisional projection must not narrate itself). */
|
|
96
|
+
pendingProjectionObservations: Array<() => void>;
|
|
97
|
+
}
|
|
98
|
+
export type GuardChainOutcome = {
|
|
99
|
+
earlyReturn: {
|
|
100
|
+
messages: AgentMessage[];
|
|
101
|
+
adoptSessionRebuild?: boolean;
|
|
102
|
+
};
|
|
103
|
+
} | {
|
|
104
|
+
working: AgentMessage[];
|
|
105
|
+
trimmed: AgentMessage[];
|
|
106
|
+
trimDroppedMessages: boolean;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* design/374 slice 3 — the GUARD CHAIN over one request build (D-7 case-ii transfer table),
|
|
110
|
+
* module-level per the design/238 body-span bank. design/123 D3: the chain reads the anchored
|
|
111
|
+
* coordinate re-estimated on the EDITED array (pre-anchor clears don't lower it — deliberately;
|
|
112
|
+
* trimToBudget doc). The opt-out machine = the pre-374 backstop order byte-identical
|
|
113
|
+
* (trim as the ordinary second line); otherwise arm A (blocking machine run, only when the
|
|
114
|
+
* frontier pass is off) → arm B (in-turn forced compaction behind the adopt seam) → arm C (trim,
|
|
115
|
+
* the disaster-only last resort and this chain's sole `context.trim` emitter). Returns either the
|
|
116
|
+
* post-chain view for the pipeline tail, or an EARLY hook result the context handler must return
|
|
117
|
+
* verbatim (arm-B adoption / the r8 dead-turn stand-down).
|
|
118
|
+
*/
|
|
119
|
+
export declare function runGuardChain(args: GuardChainArgs): Promise<GuardChainOutcome>;
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { DEFAULT_CHARS_PER_TOKEN, estimateContextTokens } from "../../internal/harness.js";
|
|
2
|
+
import { clearStaleToolResults, createClearedProjectionLedger, dropEmptyFailureAssistants, editBudget, replayClearedProjection, resolveTriggerWindow } from "../context-edit.js";
|
|
3
|
+
import { dropOrphanToolResults, guardBudget, insertTrimNotice, trimToBudget } from "../context-guard.js";
|
|
4
|
+
import { AGGREGATE_MEDIA_BUDGET_BYTES, capAggregateMediaBytes } from "../media-byte-cap.js";
|
|
5
|
+
import { capAggregateToolResults } from "../tool-result-budget.js";
|
|
6
|
+
import { createOffloadPersist } from "../tool-result-store.js";
|
|
7
|
+
import { emitTrace } from "../trace.js";
|
|
8
|
+
import { deliverEngineNotice } from "../types.js";
|
|
9
|
+
import { applyGitFrameGuard } from "./git-status-frame.js";
|
|
10
|
+
export function prepareContextLane(input) {
|
|
11
|
+
const { model, deps, sessionId, runId, hostTaskId, offloadStore, microCompactKnob, harness, batchHaltRef, gitStatusRef, requestLossyRef, trimPressureRef } = input;
|
|
12
|
+
const editAt = editBudget(model);
|
|
13
|
+
const guardAt = guardBudget(model);
|
|
14
|
+
const microCompact = buildMicroCompactState(deps, model, sessionId, runId, offloadStore, microCompactKnob);
|
|
15
|
+
const charsPerToken = model.charsPerToken ?? DEFAULT_CHARS_PER_TOKEN;
|
|
16
|
+
harness.on("context", async ({ messages, recheck, signal }) => {
|
|
17
|
+
batchHaltRef.current = undefined;
|
|
18
|
+
const pendingProjectionObservations = [];
|
|
19
|
+
try {
|
|
20
|
+
const healed = dropEmptyFailureAssistants(messages);
|
|
21
|
+
const replay = replayClearedProjection(healed, microCompact.ledger);
|
|
22
|
+
const replayed = replay.messages;
|
|
23
|
+
const capped = await capAggregateToolResults(replayed, {
|
|
24
|
+
store: offloadStore,
|
|
25
|
+
sessionId,
|
|
26
|
+
onCapped: (info) => pendingProjectionObservations.push(() => emitTrace(deps.tracer, () => ({
|
|
27
|
+
kind: "tool_result.capped",
|
|
28
|
+
version: 1,
|
|
29
|
+
taskId: hostTaskId,
|
|
30
|
+
...(info.tool !== undefined ? { tool: info.tool } : {}),
|
|
31
|
+
sizeChars: info.sizeChars,
|
|
32
|
+
storeFallback: info.storeFallback,
|
|
33
|
+
ts: Date.now(),
|
|
34
|
+
}))),
|
|
35
|
+
});
|
|
36
|
+
const mediaCapped = capAggregateMediaBytes(capped, {
|
|
37
|
+
limitBytes: deps.mediaByteCapBytes ?? AGGREGATE_MEDIA_BUDGET_BYTES,
|
|
38
|
+
onStripped: (info) => {
|
|
39
|
+
const onMediaStripped = deps.onMediaStripped;
|
|
40
|
+
if (onMediaStripped !== undefined)
|
|
41
|
+
pendingProjectionObservations.push(() => onMediaStripped(info));
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
let ccPass;
|
|
45
|
+
const edited = microCompact.machine === "off" ? mediaCapped : clearStaleToolResults(mediaCapped, {
|
|
46
|
+
budgetTokens: editAt,
|
|
47
|
+
machine: microCompact.machine,
|
|
48
|
+
...(microCompact.machine === "cc" || microCompact.clearOnRejection ? { recognizeCcMarkers: true } : {}),
|
|
49
|
+
...(microCompact.machine === "cc"
|
|
50
|
+
? {
|
|
51
|
+
clearSource: replayed,
|
|
52
|
+
onCleared: (pass) => { ccPass = pass; },
|
|
53
|
+
}
|
|
54
|
+
: {}),
|
|
55
|
+
anchoredTotalTokens: estimateContextTokens(mediaCapped, charsPerToken).tokens,
|
|
56
|
+
charsPerToken,
|
|
57
|
+
...(offloadStore
|
|
58
|
+
? {
|
|
59
|
+
offload: {
|
|
60
|
+
persist: createOffloadPersist(offloadStore, sessionId, deps.onNotice),
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
: {}),
|
|
64
|
+
});
|
|
65
|
+
if (ccPass !== undefined) {
|
|
66
|
+
recordFrontierClears({ pass: ccPass, index: replay.index, edited, ledger: microCompact.ledger, tracer: deps.tracer, taskId: hostTaskId, trigger: "frontier" });
|
|
67
|
+
}
|
|
68
|
+
const chain = await runGuardChain({
|
|
69
|
+
edited, replayed, index: replay.index, microCompact, guardAt, charsPerToken, offloadStore,
|
|
70
|
+
sessionId, taskId: hostTaskId, deps, trimPressureRef, recheck, signal,
|
|
71
|
+
pendingProjectionObservations,
|
|
72
|
+
});
|
|
73
|
+
if ("earlyReturn" in chain) {
|
|
74
|
+
return chain.earlyReturn;
|
|
75
|
+
}
|
|
76
|
+
const { working, trimDroppedMessages } = chain;
|
|
77
|
+
let trimmed = chain.trimmed;
|
|
78
|
+
trimmed = applyGitFrameGuard({
|
|
79
|
+
before: working,
|
|
80
|
+
trimmed,
|
|
81
|
+
budgetTokens: guardAt,
|
|
82
|
+
ref: gitStatusRef,
|
|
83
|
+
charsPerToken,
|
|
84
|
+
onDegrade: (message) => {
|
|
85
|
+
try {
|
|
86
|
+
deps.onError?.(new Error(message), { phase: "degraded", sessionId, classification: "env-git-snapshot" });
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
const swept = dropOrphanToolResults(trimmed);
|
|
93
|
+
if (swept.dropped.length > 0) {
|
|
94
|
+
try {
|
|
95
|
+
deps.onError?.(new Error(`context belt: dropped ${swept.dropped.length} orphan toolResult(s) whose tool-call assistant was not in the request view — ` +
|
|
96
|
+
swept.dropped
|
|
97
|
+
.map((d) => `toolCallId=${d.toolCallId} at index ${d.index} (prev=${d.prevRole}, next=${d.nextRole})`)
|
|
98
|
+
.join("; ")), { phase: "hook", sessionId });
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
requestLossyRef.current =
|
|
104
|
+
replayed !== healed ||
|
|
105
|
+
capped !== replayed ||
|
|
106
|
+
mediaCapped !== capped ||
|
|
107
|
+
edited !== mediaCapped ||
|
|
108
|
+
working !== edited ||
|
|
109
|
+
trimDroppedMessages ||
|
|
110
|
+
swept.dropped.length > 0 ||
|
|
111
|
+
gitStatusRef.overBudgetShrunk === true;
|
|
112
|
+
microCompact.projectionRef.current = { messages: swept.messages, keyOf: replay.index.keyOf };
|
|
113
|
+
return { messages: swept.messages };
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
flushProjectionObservations(pendingProjectionObservations);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
return { microCompact, charsPerToken };
|
|
120
|
+
}
|
|
121
|
+
function buildMicroCompactState(deps, model, sessionId, runId, offloadStore, knob) {
|
|
122
|
+
const triggerWindow = resolveTriggerWindow(model);
|
|
123
|
+
if (triggerWindow.clamped) {
|
|
124
|
+
deliverEngineNotice(deps.onNotice, {
|
|
125
|
+
code: "config.autocompact_window_clamped",
|
|
126
|
+
message: `model "${model.id}" declares autoCompactTokens=${triggerWindow.declaredAutoCompactTokens} ABOVE its physical window ` +
|
|
127
|
+
`${triggerWindow.physicalWindow}; the autocompact window only ever LOWERS the trigger-side geometry, so the value was ` +
|
|
128
|
+
`clamped to the physical window — fix the model config (declare a value at or below the physical window, or omit it)`,
|
|
129
|
+
detail: {
|
|
130
|
+
modelId: model.id,
|
|
131
|
+
declaredAutoCompactTokens: triggerWindow.declaredAutoCompactTokens,
|
|
132
|
+
physicalWindow: triggerWindow.physicalWindow,
|
|
133
|
+
sessionId,
|
|
134
|
+
runId,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
machine: knob.machine,
|
|
140
|
+
clearOnRejection: knob.clearOnRejection,
|
|
141
|
+
ledger: createClearedProjectionLedger(),
|
|
142
|
+
projectionRef: {},
|
|
143
|
+
inTurnCompactionRef: {},
|
|
144
|
+
...(offloadStore ? { offloadPersist: createOffloadPersist(offloadStore, sessionId, deps.onNotice) } : {}),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function recordFrontierClears(args) {
|
|
148
|
+
for (const { index } of args.pass.clears) {
|
|
149
|
+
const at = args.index.keyAt(index);
|
|
150
|
+
const clearedMsg = args.edited[index];
|
|
151
|
+
const markerText = clearedMsg.content?.[0]?.text;
|
|
152
|
+
if (at !== undefined && typeof markerText === "string") {
|
|
153
|
+
args.ledger.entries.set(at.key, { marker: markerText, groupCount: at.groupCount, fp: at.fp });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
emitTrace(args.tracer, () => ({
|
|
157
|
+
kind: "context.mc_clear",
|
|
158
|
+
version: 1,
|
|
159
|
+
taskId: args.taskId,
|
|
160
|
+
clearedCount: args.pass.clears.length,
|
|
161
|
+
tokensSavedEstimate: args.pass.tokensSavedEstimate,
|
|
162
|
+
trigger: args.trigger,
|
|
163
|
+
ts: Date.now(),
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
function flushProjectionObservations(pending) {
|
|
167
|
+
for (const fire of pending.splice(0)) {
|
|
168
|
+
try {
|
|
169
|
+
fire();
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
export async function runGuardChain(args) {
|
|
176
|
+
const { edited, microCompact, guardAt, charsPerToken, deps, taskId } = args;
|
|
177
|
+
const applyGuardTrim = (view, anchoredTotal) => {
|
|
178
|
+
let trimmed = trimToBudget(view, guardAt, anchoredTotal, charsPerToken);
|
|
179
|
+
const dropped = trimmed.length < view.length;
|
|
180
|
+
if (dropped) {
|
|
181
|
+
args.trimPressureRef.droppedMessages = true;
|
|
182
|
+
const droppedCount = view.length - trimmed.length;
|
|
183
|
+
trimmed = insertTrimNotice(trimmed);
|
|
184
|
+
emitTrace(deps.tracer, () => ({
|
|
185
|
+
kind: "context.trim",
|
|
186
|
+
version: 1,
|
|
187
|
+
taskId,
|
|
188
|
+
dropped: droppedCount,
|
|
189
|
+
ts: Date.now(),
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
return { trimmed, dropped };
|
|
193
|
+
};
|
|
194
|
+
let working = edited;
|
|
195
|
+
let anchoredWorking = estimateContextTokens(working, charsPerToken).tokens;
|
|
196
|
+
if (microCompact.machine === "legacy") {
|
|
197
|
+
const t = applyGuardTrim(working, anchoredWorking);
|
|
198
|
+
return { working, trimmed: t.trimmed, trimDroppedMessages: t.dropped };
|
|
199
|
+
}
|
|
200
|
+
if (microCompact.machine === "off" && anchoredWorking > guardAt) {
|
|
201
|
+
let blockingPass;
|
|
202
|
+
const blockingEdited = clearStaleToolResults(working, {
|
|
203
|
+
budgetTokens: guardAt,
|
|
204
|
+
machine: "cc",
|
|
205
|
+
clearSource: args.replayed,
|
|
206
|
+
onCleared: (pass) => { blockingPass = pass; },
|
|
207
|
+
anchoredTotalTokens: anchoredWorking,
|
|
208
|
+
charsPerToken,
|
|
209
|
+
...(args.offloadStore ? { offload: { persist: createOffloadPersist(args.offloadStore, args.sessionId, deps.onNotice) } } : {}),
|
|
210
|
+
});
|
|
211
|
+
if (blockingPass !== undefined) {
|
|
212
|
+
recordFrontierClears({ pass: blockingPass, index: args.index, edited: blockingEdited, ledger: microCompact.ledger, tracer: deps.tracer, taskId, trigger: "blocking" });
|
|
213
|
+
working = blockingEdited;
|
|
214
|
+
anchoredWorking = estimateContextTokens(working, charsPerToken).tokens;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (anchoredWorking > guardAt && args.recheck !== true) {
|
|
218
|
+
const compacted = (await microCompact.inTurnCompactionRef.current?.(args.signal, anchoredWorking)) === true;
|
|
219
|
+
if (compacted) {
|
|
220
|
+
args.pendingProjectionObservations.length = 0;
|
|
221
|
+
return { earlyReturn: { messages: working, adoptSessionRebuild: true } };
|
|
222
|
+
}
|
|
223
|
+
if (args.signal?.aborted === true) {
|
|
224
|
+
args.pendingProjectionObservations.length = 0;
|
|
225
|
+
return { working, trimmed: working, trimDroppedMessages: false };
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const t = applyGuardTrim(working, anchoredWorking);
|
|
229
|
+
return { working, trimmed: t.trimmed, trimDroppedMessages: t.dropped };
|
|
230
|
+
}
|