@sema-agent/core 5.65.0 → 7.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 +58 -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 +13 -3
- package/dist/core/governance-codes.js +15 -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 +62 -2
- package/dist/core/permission-rule-consent.js +105 -14
- package/dist/core/permission-rule-model.d.ts +118 -16
- package/dist/core/permission-rule-model.js +57 -9
- package/dist/core/permission-rule-session.d.ts +124 -0
- package/dist/core/permission-rule-session.js +121 -0
- package/dist/core/permission-rule-store.d.ts +65 -2
- package/dist/core/permission-rule-store.js +75 -16
- package/dist/core/permission-rule-sync.d.ts +9 -0
- package/dist/core/permission-rule-sync.js +52 -19
- 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 +478 -81
- 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/store-contracts/permission-rule-sync-contract.js +15 -1
- 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 +31 -2
- package/dist/core/types.d.ts +795 -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 +20 -9
- package/dist/index.js +16 -7
- 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.d.ts +11 -0
- package/dist/stores/file/permission-rule-store.js +23 -9
- 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 +169 -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
|
@@ -6,6 +6,7 @@ const PROMPT_HASH_SALT = randomBytes(16);
|
|
|
6
6
|
import { sanitizeCompactionSettings } from "../auto-compaction.js";
|
|
7
7
|
import { projectStaleToolResults, resolveStaleToolResultOffload } from "./compaction-call-options.js";
|
|
8
8
|
import { createAutoModeDecider } from "../auto-mode.js";
|
|
9
|
+
import { autoModeArmingRecipeOf } from "../auto-mode-arming.js";
|
|
9
10
|
import { buildAutoModePrompt, renderAutoModeAction, renderAutoModeWindow } from "../auto-mode-prompt.js";
|
|
10
11
|
import { resolveTaskModel } from "../roles.js";
|
|
11
12
|
import { primaryActivityArg } from "../arg-summary.js";
|
|
@@ -20,9 +21,10 @@ import { createSubagentWorktreeHelper, forkGovernanceDenial, resolveDelegationEn
|
|
|
20
21
|
import { createAgentTranscriptTool, AGENT_TRANSCRIPT_TOOL_NAME } from "../../agents/agent-transcript-tool.js";
|
|
21
22
|
import { createSendMessageTool, SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
|
|
22
23
|
import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
|
|
23
|
-
import { askApproverIdentity, carriesBidiControls, checkToolPolicyProjection, combinePolicies, constraintChainDigest,
|
|
24
|
+
import { askApproverIdentity, carriesBidiControls, checkToolPolicyProjection, combinePolicies, constraintChainDigest, constraintChainEntryOfLayer, isApprovalSettledBy, isAskDenyResolution, screenApproverAttribution, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, describeThrown, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, tryCloneArgs } from "../tool-policy.js";
|
|
24
25
|
const PERSISTED_RULE_TOOL = "Bash";
|
|
25
26
|
import { findAdmittingRule, segmentCoverageOf, suggestRulesForCommand } from "../permission-rule-model.js";
|
|
27
|
+
import { normalizePersistedRule } from "../permission-rule-store.js";
|
|
26
28
|
import { ActiveSkillScope, createActiveSkillScopePolicy } from "./active-skill-scope.js";
|
|
27
29
|
import { CHANGED_FILES_MTIME_EPS_MS, fenceMcpServerInstructions, renderAgentListingDelta } from "./turn-attachments.js";
|
|
28
30
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
@@ -49,7 +51,6 @@ import { TOOL_SEARCH_NAME, buildDeferredRegistry, classifyDeferred, extractDisco
|
|
|
49
51
|
import { createSharedMemoryTools } from "../shared-memory/tools.js";
|
|
50
52
|
import { SHARED_MEMORY_TOOL_NAMES } from "../shared-memory/types.js";
|
|
51
53
|
import { MEMORY_ENGINE_TOOL_NAMES } from "../memory-engine/tools.js";
|
|
52
|
-
import { memoryRecallDisciplineSegment } from "../memory-engine/engine.js";
|
|
53
54
|
import { classifyToolContentOrigin, contentOriginPollutes, delegationCallIsExternal } from "../memory-engine/content-origin.js";
|
|
54
55
|
import { narrowContentSafety, readCardAttestation } from "../memory-engine/delegation-provenance.js";
|
|
55
56
|
import { recordSyncUnattestable, sessionSettlements } from "../memory-engine/delegation-settlement.js";
|
|
@@ -97,6 +98,7 @@ import { countElicitOptIns, deriveWiringManifest, resolveAskSeamForm, resolveDec
|
|
|
97
98
|
import { durableParkGapFor } from "../park-selfcheck.js";
|
|
98
99
|
import { GLOBAL_USAGE_KEY, usageRetryAfterMs, windowsGovernCost } from "../usage-window-store.js";
|
|
99
100
|
import { deliverEngineNotice } from "../types.js";
|
|
101
|
+
import { resolveTrackKey } from "../file-history-store.js";
|
|
100
102
|
let announcedMaterializeEnvBySink = new WeakMap();
|
|
101
103
|
const announcedMaterializeEnvConsole = new Set();
|
|
102
104
|
function materializeEnvLedger(onNotice) {
|
|
@@ -218,6 +220,7 @@ function announceDeclaredMcpContentClasses(args) {
|
|
|
218
220
|
toolCount,
|
|
219
221
|
...(declaredClass === "execution" ? { execIsExternalContent: args.execIsExternalContent } : {}),
|
|
220
222
|
sessionId: args.sessionId,
|
|
223
|
+
runId: args.runId,
|
|
221
224
|
},
|
|
222
225
|
});
|
|
223
226
|
}
|
|
@@ -324,7 +327,7 @@ class ParkRefusal extends Error {
|
|
|
324
327
|
export { resolveCheckpointStore } from "../checkpoint-store.js";
|
|
325
328
|
export { isFableFamilyModelId, resolveAttachmentsConfig, resolveModelPromptTraits, resolveTaskLimits } from "./prepare-config-doors.js";
|
|
326
329
|
export { rebaseWorkspacePath, rebaseWorkspacePathAcross } from "./prepare-workspace-restore.js";
|
|
327
|
-
function buildMicroCompactState(deps, model, sessionId, offloadStore, knob) {
|
|
330
|
+
function buildMicroCompactState(deps, model, sessionId, runId, offloadStore, knob) {
|
|
328
331
|
const triggerWindow = resolveTriggerWindow(model);
|
|
329
332
|
if (triggerWindow.clamped) {
|
|
330
333
|
deliverEngineNotice(deps.onNotice, {
|
|
@@ -337,6 +340,7 @@ function buildMicroCompactState(deps, model, sessionId, offloadStore, knob) {
|
|
|
337
340
|
declaredAutoCompactTokens: triggerWindow.declaredAutoCompactTokens,
|
|
338
341
|
physicalWindow: triggerWindow.physicalWindow,
|
|
339
342
|
sessionId,
|
|
343
|
+
runId,
|
|
340
344
|
},
|
|
341
345
|
});
|
|
342
346
|
}
|
|
@@ -433,6 +437,187 @@ export async function runGuardChain(args) {
|
|
|
433
437
|
const t = applyGuardTrim(working, anchoredWorking);
|
|
434
438
|
return { working, trimmed: t.trimmed, trimDroppedMessages: t.dropped };
|
|
435
439
|
}
|
|
440
|
+
function restoredAbsPathsOf(restored, root) {
|
|
441
|
+
const out = [];
|
|
442
|
+
for (const key of [...restored.filesChanged, ...restored.failed.map((f) => f.path)]) {
|
|
443
|
+
const abs = resolveTrackKey(root, key);
|
|
444
|
+
if (abs.ok)
|
|
445
|
+
out.push(abs.abs);
|
|
446
|
+
}
|
|
447
|
+
return out;
|
|
448
|
+
}
|
|
449
|
+
function resolveWiredFileHistoryStore(candidate) {
|
|
450
|
+
if (candidate === undefined)
|
|
451
|
+
return undefined;
|
|
452
|
+
const verbs = ["trackEdit", "annulTrack", "snapshot", "restore", "canRestore", "reap", "adoptScope"];
|
|
453
|
+
const verbOf = (v) => {
|
|
454
|
+
try {
|
|
455
|
+
return candidate[v];
|
|
456
|
+
}
|
|
457
|
+
catch {
|
|
458
|
+
return undefined;
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
const probeable = (typeof candidate === "object" && candidate !== null) || typeof candidate === "function";
|
|
462
|
+
const missing = probeable ? verbs.filter((v) => typeof verbOf(v) !== "function") : [...verbs];
|
|
463
|
+
if (missing.length > 0) {
|
|
464
|
+
const shape = candidate === null ? "null" : probeable ? "a value that is not a FileHistoryStore" : `a ${typeof candidate}`;
|
|
465
|
+
const e = new Error(`RunnerDeps.fileHistoryStore was wired with ${shape} — missing contract verbs: ${missing.join(", ")}. Refusing prepare rather than half-wiring the rewind seat (a present-but-unusable store makes every Edit/Write/NotebookEdit fail its first-touch backup while restore requests report "no store wired"). Omit the key to run without file history.`);
|
|
466
|
+
e.code = "config.invalid_file_history_store";
|
|
467
|
+
throw e;
|
|
468
|
+
}
|
|
469
|
+
return candidate;
|
|
470
|
+
}
|
|
471
|
+
const LEGACY_PROBE_TIMEOUT_MS = 2_000;
|
|
472
|
+
const LEGACY_PROBE_TIMED_OUT = Symbol("legacy-rewind-probe-timeout");
|
|
473
|
+
async function legacyRewindEpochTail(deps, sessionId, entryId, form) {
|
|
474
|
+
const probe = deps.legacyRewindBoundaryProbe;
|
|
475
|
+
let verdict;
|
|
476
|
+
if (probe !== undefined) {
|
|
477
|
+
try {
|
|
478
|
+
let timer;
|
|
479
|
+
const timeout = new Promise((resolve) => {
|
|
480
|
+
timer = setTimeout(() => resolve(LEGACY_PROBE_TIMED_OUT), LEGACY_PROBE_TIMEOUT_MS);
|
|
481
|
+
});
|
|
482
|
+
try {
|
|
483
|
+
verdict = await Promise.race([Promise.resolve(probe({ sessionId, entryId })), timeout]);
|
|
484
|
+
}
|
|
485
|
+
finally {
|
|
486
|
+
clearTimeout(timer);
|
|
487
|
+
}
|
|
488
|
+
if (verdict === LEGACY_PROBE_TIMED_OUT) {
|
|
489
|
+
verdict = undefined;
|
|
490
|
+
try {
|
|
491
|
+
deps.onError?.(new Error(`RunnerDeps.legacyRewindBoundaryProbe did not answer within ${LEGACY_PROBE_TIMEOUT_MS}ms while explaining a rewind refusal — the refusal stands and names both possibilities`), { phase: "rewind", sessionId });
|
|
492
|
+
}
|
|
493
|
+
catch {
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
catch (err) {
|
|
498
|
+
verdict = undefined;
|
|
499
|
+
try {
|
|
500
|
+
deps.onError?.(new Error(`RunnerDeps.legacyRewindBoundaryProbe threw while explaining a rewind refusal (${err.message}) — the refusal stands and names both possibilities`), { phase: "rewind", sessionId });
|
|
501
|
+
}
|
|
502
|
+
catch {
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (verdict !== undefined && typeof verdict !== "boolean") {
|
|
506
|
+
try {
|
|
507
|
+
deps.onError?.(new Error(`RunnerDeps.legacyRewindBoundaryProbe answered ${JSON.stringify(String(verdict))} for entry "${entryId}" — only true/false/undefined are meaningful, so the refusal names both possibilities instead of trusting it`), { phase: "rewind", sessionId });
|
|
508
|
+
}
|
|
509
|
+
catch {
|
|
510
|
+
}
|
|
511
|
+
verdict = undefined;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (verdict === true) {
|
|
515
|
+
return `This deployment's legacy-epoch probe reports that entry "${entryId}" DOES carry a boundary from the retired whole-tree snapshot era, which this engine's per-edited-file seat does not restore — the two epochs are not interchangeable (a whole-tree manifest carries no tracked-set provenance, so replaying it here would converge files this session never edited). Restore it with the whole-tree-era tooling this deployment kept, or re-run the turns under the current engine.`;
|
|
516
|
+
}
|
|
517
|
+
if (verdict === false) {
|
|
518
|
+
return `This deployment's legacy-epoch probe reports no whole-tree-era boundary for entry "${entryId}" either, so the boundary simply does not exist: its turns ran without a RunnerDeps.fileHistoryStore wired, or it was reaped.`;
|
|
519
|
+
}
|
|
520
|
+
return form === "keyed"
|
|
521
|
+
? `Boundaries are keyed by a turn's INITIAL entry and exist only for turns that ran with a RunnerDeps.fileHistoryStore wired; a boundary from the retired whole-tree snapshot era is not restorable by this seat, and reaped boundaries have none.`
|
|
522
|
+
: `Either the boundary does not exist (its turns ran without a RunnerDeps.fileHistoryStore, or it was reaped), or it was produced in the retired whole-tree snapshot era, which this engine's per-edited-file seat does not restore.`;
|
|
523
|
+
}
|
|
524
|
+
const FILE_HISTORY_BOUNDARY_TIMEOUT_MS = 30_000;
|
|
525
|
+
const FILE_HISTORY_CARRY_MEMO = new WeakMap();
|
|
526
|
+
async function adoptForkedFileHistory(store, session, sessionId, onError) {
|
|
527
|
+
const memo = FILE_HISTORY_CARRY_MEMO.get(store) ?? { settled: new Set(), disclosed: new Set(), inFlight: new Map() };
|
|
528
|
+
FILE_HISTORY_CARRY_MEMO.set(store, memo);
|
|
529
|
+
const discloseOnce = (err) => {
|
|
530
|
+
if (memo.disclosed.has(sessionId))
|
|
531
|
+
return;
|
|
532
|
+
memo.disclosed.add(sessionId);
|
|
533
|
+
onError?.(err, { phase: "rewind", sessionId });
|
|
534
|
+
};
|
|
535
|
+
try {
|
|
536
|
+
if (memo.settled.has(sessionId))
|
|
537
|
+
return;
|
|
538
|
+
const forkedFrom = (await session.getMetadata()).forkedFrom;
|
|
539
|
+
if (forkedFrom === undefined || forkedFrom === sessionId)
|
|
540
|
+
return;
|
|
541
|
+
const shared = memo.inFlight.get(sessionId);
|
|
542
|
+
if (shared !== undefined)
|
|
543
|
+
return await shared;
|
|
544
|
+
if (memo.settled.has(sessionId))
|
|
545
|
+
return;
|
|
546
|
+
const attempt = store.adoptScope(forkedFrom, sessionId).then((carried) => {
|
|
547
|
+
if (carried.ok || carried.error.code === "conflict") {
|
|
548
|
+
memo.settled.add(sessionId);
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
discloseOnce(new Error(`file-history fork carry from session "${forkedFrom}" failed (${carried.error.code}): ${carried.error.message} — this forked session starts with an EMPTY rewind history, so boundaries recorded before the fork are not restorable from it (the run itself proceeds; the carry is retried on this session's next turn while its scope is still empty)`));
|
|
552
|
+
}, (err) => discloseOnce(err));
|
|
553
|
+
memo.inFlight.set(sessionId, attempt);
|
|
554
|
+
try {
|
|
555
|
+
await attempt;
|
|
556
|
+
}
|
|
557
|
+
finally {
|
|
558
|
+
memo.inFlight.delete(sessionId);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
catch (err) {
|
|
562
|
+
try {
|
|
563
|
+
discloseOnce(err);
|
|
564
|
+
}
|
|
565
|
+
catch {
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
function createFileHistoryBoundarySeat(opts) {
|
|
570
|
+
let inFlight;
|
|
571
|
+
let begun = false;
|
|
572
|
+
return {
|
|
573
|
+
begin(entryId) {
|
|
574
|
+
if (begun)
|
|
575
|
+
return;
|
|
576
|
+
begun = true;
|
|
577
|
+
const ac = new AbortController();
|
|
578
|
+
let timer;
|
|
579
|
+
const timeout = new Promise((resolve) => {
|
|
580
|
+
timer = setTimeout(() => {
|
|
581
|
+
ac.abort();
|
|
582
|
+
resolve({ ok: false });
|
|
583
|
+
}, FILE_HISTORY_BOUNDARY_TIMEOUT_MS);
|
|
584
|
+
});
|
|
585
|
+
let work;
|
|
586
|
+
try {
|
|
587
|
+
work = opts.store.snapshot(opts.sessionId, entryId, opts.env, opts.root, ac.signal);
|
|
588
|
+
}
|
|
589
|
+
catch (err) {
|
|
590
|
+
work = Promise.resolve({ ok: false, error: { code: "snapshot_failed", message: `history store threw synchronously: ${err.message}` } });
|
|
591
|
+
}
|
|
592
|
+
work.catch(() => { });
|
|
593
|
+
inFlight = Promise.race([
|
|
594
|
+
work.then((r) => (r.ok ? undefined : `boundary capture failed (${r.error.code}): ${r.error.message}`)),
|
|
595
|
+
timeout.then(() => `boundary capture timed out after ${FILE_HISTORY_BOUNDARY_TIMEOUT_MS / 1000}s (fenced — the attempt can never publish)`),
|
|
596
|
+
])
|
|
597
|
+
.then((failure) => {
|
|
598
|
+
if (failure !== undefined) {
|
|
599
|
+
try {
|
|
600
|
+
opts.onError?.(new Error(`file-history ${failure} — this turn has no boundary and cannot be rewound to (the turn itself proceeds)`), { phase: "rewind", sessionId: opts.sessionId });
|
|
601
|
+
}
|
|
602
|
+
catch {
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
})
|
|
606
|
+
.catch((err) => {
|
|
607
|
+
try {
|
|
608
|
+
opts.onError?.(err, { phase: "rewind", sessionId: opts.sessionId });
|
|
609
|
+
}
|
|
610
|
+
catch {
|
|
611
|
+
}
|
|
612
|
+
})
|
|
613
|
+
.finally(() => clearTimeout(timer));
|
|
614
|
+
},
|
|
615
|
+
async settle() {
|
|
616
|
+
if (inFlight !== undefined)
|
|
617
|
+
await inFlight;
|
|
618
|
+
},
|
|
619
|
+
};
|
|
620
|
+
}
|
|
436
621
|
export function gatedCallIdOf(p) {
|
|
437
622
|
if (p.suspendRef.token !== undefined)
|
|
438
623
|
return p.suspendRef.gatedCallId;
|
|
@@ -508,7 +693,7 @@ function createDenyObserverNotifier(hooks, hookTimeoutMs, signal, report) {
|
|
|
508
693
|
}
|
|
509
694
|
};
|
|
510
695
|
}
|
|
511
|
-
function mcpRevocationWiring(deps) {
|
|
696
|
+
function mcpRevocationWiring(deps, runId) {
|
|
512
697
|
if (deps.mcpRevocations === undefined)
|
|
513
698
|
return undefined;
|
|
514
699
|
const ledger = deps.mcpRevocations;
|
|
@@ -517,7 +702,7 @@ function mcpRevocationWiring(deps) {
|
|
|
517
702
|
onProbeFailure: (e) => deliverEngineNotice(deps.onNotice, {
|
|
518
703
|
code: "mcp.revocation_probe_failed",
|
|
519
704
|
message: `the mcpRevocations.isRevoked probe threw — MCP dispatch fails OPEN (no server treated as revoked) until the probe recovers: ${e instanceof Error ? e.message : String(e)}`,
|
|
520
|
-
detail: { message: e instanceof Error ? e.message : String(e) },
|
|
705
|
+
detail: { message: e instanceof Error ? e.message : String(e), runId },
|
|
521
706
|
}),
|
|
522
707
|
};
|
|
523
708
|
}
|
|
@@ -674,7 +859,7 @@ async function derivedRouteFallsBack(args) {
|
|
|
674
859
|
const verdict = await adjudicateDerivedRoute({ brain: args.brain, model: args.derived, getApiKeyAndHeaders: args.getApiKeyAndHeaders });
|
|
675
860
|
if (verdict === undefined || verdict.ok)
|
|
676
861
|
return false;
|
|
677
|
-
deliverEngineNotice(args.onNotice, fallbackToPrimaryNotice({ seat: args.seat, from: args.derived.id, to: args.primary.id, verdict, ...(args.sessionId !== undefined ? { sessionId: args.sessionId } : {}) }));
|
|
862
|
+
deliverEngineNotice(args.onNotice, fallbackToPrimaryNotice({ seat: args.seat, from: args.derived.id, to: args.primary.id, verdict, ...(args.sessionId !== undefined ? { sessionId: args.sessionId } : {}), ...(args.runId !== undefined ? { runId: args.runId } : {}) }));
|
|
678
863
|
return true;
|
|
679
864
|
}
|
|
680
865
|
catch {
|
|
@@ -703,6 +888,43 @@ export function placementValueOrAbsent(value) {
|
|
|
703
888
|
function stampPlacementRootSessionId(placementRootResolved) {
|
|
704
889
|
return placementValueOrAbsent(placementRootResolved);
|
|
705
890
|
}
|
|
891
|
+
function explicitlyDeferredMemoryTrio(mounted, roster, deferNames) {
|
|
892
|
+
return mounted ? MEMORY_ENGINE_TOOL_NAMES.filter((n) => roster.some((t) => t.name === n) && (deferNames ?? []).includes(n)) : [];
|
|
893
|
+
}
|
|
894
|
+
function memoryGroupRetractionSet(builtinDeferPairNames, engineTrioInPlay) {
|
|
895
|
+
return new Set([...builtinDeferPairNames, ...(engineTrioInPlay ? MEMORY_ENGINE_TOOL_NAMES : [])]);
|
|
896
|
+
}
|
|
897
|
+
async function spliceSessionOverlayRows(overlay, sessionId, persisted, tracer, hostTaskId) {
|
|
898
|
+
if (overlay === undefined)
|
|
899
|
+
return persisted;
|
|
900
|
+
try {
|
|
901
|
+
const served = structuredClone(await overlay.read(sessionId));
|
|
902
|
+
const sessionRows = served.filter((r) => {
|
|
903
|
+
const scope = r?.scope;
|
|
904
|
+
return scope?.kind === "session" && scope.sessionId === sessionId && !("reject" in normalizePersistedRule(r));
|
|
905
|
+
});
|
|
906
|
+
if (sessionRows.length < served.length) {
|
|
907
|
+
emitTrace(tracer, () => ({
|
|
908
|
+
kind: "permission.rule_store_unreadable",
|
|
909
|
+
version: 1,
|
|
910
|
+
taskId: hostTaskId,
|
|
911
|
+
message: `session-rule overlay served ${served.length - sessionRows.length} row(s) that are not canonical session rows of this session — dropped, not adjudicated`,
|
|
912
|
+
ts: Date.now(),
|
|
913
|
+
}));
|
|
914
|
+
}
|
|
915
|
+
return sessionRows.length > 0 ? [...sessionRows, ...persisted] : persisted;
|
|
916
|
+
}
|
|
917
|
+
catch (err) {
|
|
918
|
+
emitTrace(tracer, () => ({
|
|
919
|
+
kind: "permission.rule_store_unreadable",
|
|
920
|
+
version: 1,
|
|
921
|
+
taskId: hostTaskId,
|
|
922
|
+
message: `session-rule overlay: ${err instanceof Error ? err.message : String(err)}`,
|
|
923
|
+
ts: Date.now(),
|
|
924
|
+
}));
|
|
925
|
+
return persisted;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
706
928
|
function refuseRequireExistingWithoutSession(spec) {
|
|
707
929
|
if (spec.requireExistingSession && !spec.sessionId) {
|
|
708
930
|
const e = new Error(`requireExistingSession requires a sessionId — cannot require an existing session without one (design/114 Phase3)`);
|
|
@@ -710,10 +932,13 @@ function refuseRequireExistingWithoutSession(spec) {
|
|
|
710
932
|
throw e;
|
|
711
933
|
}
|
|
712
934
|
}
|
|
713
|
-
export async function prepareTask(spec, deps, sessions, resume, internals, runnerSelf) {
|
|
935
|
+
export async function prepareTask(spec, deps, sessions, resume, internals, runnerSelf, runIdSink) {
|
|
936
|
+
const runId = uuidv7();
|
|
937
|
+
if (runIdSink !== undefined)
|
|
938
|
+
runIdSink.runId = runId;
|
|
714
939
|
const doors = prepareConfigDoors({ spec, deps, sessions, resume, internals });
|
|
715
940
|
spec = doors.spec;
|
|
716
|
-
const { toolFaceSnapshot, promptProfile, lockedPreflight, resolvedInteractionPosture, microCompactKnob, resolvedRole, model, thinking, compModel, fableMitigations, memoryDelegationEvidence, memoryProvenance, usageWindows, brainCallGuardrailRef, brainCallGuardrailMs } = doors;
|
|
941
|
+
const { toolFaceSnapshot, promptProfile, lockedPreflight, resolvedInteractionPosture, microCompactKnob, resolvedRole, model, thinking, compModel, fableMitigations, memoryDelegationEvidence, memoryProvenance, memoryCapturePolicy, usageWindows, brainCallGuardrailRef, brainCallGuardrailMs } = doors;
|
|
717
942
|
announceToolModelGate(deps.onNotice, model.id, doors.modelGate);
|
|
718
943
|
const { toolEffects, egressTools, irreversibleTools, irreversibilityTier, axisExplicitNegatives, reversibilityProbes, ownToolNames } = prepareSafetyScan({ spec, deps });
|
|
719
944
|
let shellGatedBash = false;
|
|
@@ -727,7 +952,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
727
952
|
let effectiveCompModel = compModel;
|
|
728
953
|
if (spec.compactionModel === undefined &&
|
|
729
954
|
compModel !== undefined &&
|
|
730
|
-
(await derivedRouteFallsBack({ seat: "compaction-summary", derived: compModel, primary: model, brain: deps.brain, getApiKeyAndHeaders: spec.getApiKeyAndHeaders, onNotice: deps.onNotice, sessionId }))) {
|
|
955
|
+
(await derivedRouteFallsBack({ seat: "compaction-summary", derived: compModel, primary: model, brain: deps.brain, getApiKeyAndHeaders: spec.getApiKeyAndHeaders, onNotice: deps.onNotice, sessionId, runId }))) {
|
|
731
956
|
effectiveCompModel = undefined;
|
|
732
957
|
}
|
|
733
958
|
warnCompactionWindowHazard(deps.tracer, spec, model, effectiveCompModel, hostTaskId);
|
|
@@ -907,79 +1132,165 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
907
1132
|
}
|
|
908
1133
|
const { taskRootFinal, rebaseRestoredPath } = await prepareWorkspaceRestore({ ownedEnv, deps, spec, internals, resume, taskRootInitial, abortController, sessionId });
|
|
909
1134
|
const rewindNotes = [];
|
|
910
|
-
const
|
|
1135
|
+
const onTrackFailure = deps.onTrackFailure === undefined ? "refuse" : deps.onTrackFailure;
|
|
1136
|
+
if (onTrackFailure !== "refuse" && onTrackFailure !== "proceed-unprotected") {
|
|
1137
|
+
const e = new Error(`RunnerDeps.onTrackFailure must be "refuse" or "proceed-unprotected"; got ${JSON.stringify(deps.onTrackFailure)} — refusing prepare rather than silently defaulting a first-touch-failure policy.`);
|
|
1138
|
+
e.code = "config.invalid_on_track_failure";
|
|
1139
|
+
throw e;
|
|
1140
|
+
}
|
|
1141
|
+
const fileHistoryStore = resolveWiredFileHistoryStore(deps.fileHistoryStore);
|
|
1142
|
+
if (fileHistoryStore !== undefined) {
|
|
1143
|
+
await adoptForkedFileHistory(fileHistoryStore, session, sessionId, deps.onError);
|
|
1144
|
+
}
|
|
1145
|
+
const legacyRewindFiles = spec.rewindFiles;
|
|
1146
|
+
if (legacyRewindFiles === true) {
|
|
1147
|
+
if (spec.resumeAt !== undefined) {
|
|
1148
|
+
const e = new Error(`rewind-files: TaskSpec.rewindFiles is retired (design/381 — rewind now converges the per-edited-file history, not a whole-tree snapshot). This request pairs it with resumeAt, i.e. the RESTORE sense: set restoreFiles: true instead. (The capture sense needs no request anymore — first-touch tracking is always on when a RunnerDeps.fileHistoryStore is wired.)`);
|
|
1149
|
+
e.code = "rewind.rewind_files_retired";
|
|
1150
|
+
throw e;
|
|
1151
|
+
}
|
|
1152
|
+
try {
|
|
1153
|
+
deps.onError?.(new Error(`TaskSpec.rewindFiles (capture sense) is retired and was ignored: per-edited-file first-touch tracking is always on when RunnerDeps.fileHistoryStore is wired, so a per-turn capture request has nothing left to ask for. Drop the field; the restore sense moved to TaskSpec.restoreFiles.`), { phase: "config", sessionId });
|
|
1154
|
+
}
|
|
1155
|
+
catch {
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
if (spec.restoreFiles === true && spec.resumeAt === undefined) {
|
|
1159
|
+
const e = new Error(`rewind-files: restoreFiles: true needs a resumeAt anchor — restoring files means converging them to a specific prior entry's boundary, and this spec names none. Use resumeAt + restoreFiles to move conversation and files together, or rewindFilesTo alone for a code-only restore.`);
|
|
1160
|
+
e.code = "rewind.invalid_spec";
|
|
1161
|
+
throw e;
|
|
1162
|
+
}
|
|
911
1163
|
if (spec.resumeAt !== undefined && spec.rewindFilesTo !== undefined) {
|
|
912
|
-
const e = new Error(`rewind-files: resumeAt ("${spec.resumeAt}") and rewindFilesTo ("${spec.rewindFilesTo}") were both set — resumeAt already anchors the file restore when
|
|
1164
|
+
const e = new Error(`rewind-files: resumeAt ("${spec.resumeAt}") and rewindFilesTo ("${spec.rewindFilesTo}") were both set — resumeAt already anchors the file restore when restoreFiles is true, so a separate rewindFilesTo target is a conflicting request. Drop one of the two.`);
|
|
913
1165
|
e.code = "rewind.conflicting_targets";
|
|
914
1166
|
throw e;
|
|
915
1167
|
}
|
|
916
|
-
let rewindTarget = spec.resumeAt !== undefined ? (
|
|
1168
|
+
let rewindTarget = spec.resumeAt !== undefined ? (spec.restoreFiles === true ? spec.resumeAt : undefined) : spec.rewindFilesTo;
|
|
917
1169
|
const rewindBefore = rewindTarget !== undefined && spec.resumeAt !== undefined && spec.resumeAtMode === "before";
|
|
918
|
-
if (spec.resumeAt !== undefined &&
|
|
1170
|
+
if (spec.resumeAt !== undefined && spec.restoreFiles !== true) {
|
|
919
1171
|
rewindNotes.push({
|
|
920
1172
|
code: "conversation_only",
|
|
921
|
-
message: `the conversation was branched at entry "${spec.resumeAt}" but the working tree was NOT rewound — this task set resumeAt without
|
|
1173
|
+
message: `the conversation was branched at entry "${spec.resumeAt}" but the working tree was NOT rewound — this task set resumeAt without restoreFiles, so files remain at their current state`,
|
|
922
1174
|
});
|
|
923
1175
|
}
|
|
924
|
-
|
|
1176
|
+
const fileHistoryEnabled = fileHistoryStore !== undefined && handsEnabled;
|
|
1177
|
+
let historyRoot = taskRootFinal;
|
|
1178
|
+
if (fileHistoryEnabled) {
|
|
1179
|
+
const canonRoot = await executionEnv.canonicalPath(taskRootFinal);
|
|
1180
|
+
if (canonRoot.ok)
|
|
1181
|
+
historyRoot = canonRoot.value;
|
|
1182
|
+
}
|
|
1183
|
+
if (fileHistoryStore === undefined) {
|
|
925
1184
|
if (rewindTarget !== undefined) {
|
|
926
|
-
const e = new Error(`rewind-files: restoring
|
|
1185
|
+
const e = new Error(`rewind-files: restoring files to entry "${rewindTarget}" requires a history backend, but this deployment wired no RunnerDeps.fileHistoryStore — no file history was ever recorded, so the files were NOT rewound`);
|
|
927
1186
|
e.code = "rewind.store_unconfigured";
|
|
928
1187
|
throw e;
|
|
929
1188
|
}
|
|
930
|
-
if (rewindCaptureRequested) {
|
|
931
|
-
rewindNotes.push({
|
|
932
|
-
code: "snapshot_store_unconfigured",
|
|
933
|
-
message: "rewindFiles was requested but no RunnerDeps.fileSnapshotStore is wired — no working-tree snapshot was captured for this turn, so it cannot be rewound to later",
|
|
934
|
-
});
|
|
935
|
-
}
|
|
936
1189
|
}
|
|
937
1190
|
else if (!handsEnabled) {
|
|
938
|
-
if (rewindTarget !== undefined
|
|
1191
|
+
if (rewindTarget !== undefined) {
|
|
939
1192
|
rewindNotes.push({
|
|
940
1193
|
code: "files_env_unsupported",
|
|
941
|
-
message: "the file side of rewind was inert: this deployment mounts no filesystem-capable ExecutionEnv, so no
|
|
1194
|
+
message: "the file side of rewind was inert: this deployment mounts no filesystem-capable ExecutionEnv, so no file history was recorded or restored",
|
|
942
1195
|
});
|
|
943
1196
|
}
|
|
944
1197
|
}
|
|
945
|
-
|
|
1198
|
+
const legacyEpochTail = (entryId, form = "at-or-above") => legacyRewindEpochTail(deps, sessionId, entryId, form);
|
|
1199
|
+
let restoredFilePaths = [];
|
|
1200
|
+
if (rewindTarget !== undefined && fileHistoryStore !== undefined && handsEnabled) {
|
|
1201
|
+
const historyStore = fileHistoryStore;
|
|
946
1202
|
if (rewindBefore) {
|
|
947
1203
|
let anchor;
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1204
|
+
if (await historyStore.canRestore(sessionId, rewindTarget)) {
|
|
1205
|
+
anchor = rewindTarget;
|
|
1206
|
+
}
|
|
1207
|
+
else {
|
|
1208
|
+
const walked = new Set();
|
|
1209
|
+
let cursor = resumeAtBeforeParentId;
|
|
1210
|
+
while (cursor !== null && !walked.has(cursor)) {
|
|
1211
|
+
walked.add(cursor);
|
|
1212
|
+
if (await historyStore.canRestore(sessionId, cursor)) {
|
|
1213
|
+
anchor = cursor;
|
|
1214
|
+
break;
|
|
1215
|
+
}
|
|
1216
|
+
cursor = (await session.getEntry(cursor))?.parentId ?? null;
|
|
955
1217
|
}
|
|
956
|
-
cursor = (await session.getEntry(cursor))?.parentId ?? null;
|
|
957
1218
|
}
|
|
958
1219
|
if (anchor === undefined) {
|
|
959
|
-
const e = new Error(`rewind-files: no file
|
|
1220
|
+
const e = new Error(`rewind-files: no file-history boundary exists at or above the "before" branch point on session "${sessionId}" — cannot restore files consistent with the rewound context. ${await legacyEpochTail(rewindTarget)}`);
|
|
960
1221
|
e.code = "rewind_snapshot.unresolvable";
|
|
961
1222
|
throw e;
|
|
962
1223
|
}
|
|
963
1224
|
rewindTarget = anchor;
|
|
964
1225
|
}
|
|
965
|
-
const restoreRoot =
|
|
1226
|
+
const restoreRoot = historyRoot;
|
|
966
1227
|
const restoreSignal = spec.signal ? AbortSignal.any([abortController.signal, spec.signal]) : abortController.signal;
|
|
967
|
-
const restored = await
|
|
968
|
-
|
|
969
|
-
|
|
1228
|
+
const restored = await historyStore.restore(sessionId, rewindTarget, executionEnv, restoreRoot, restoreSignal);
|
|
1229
|
+
const ledgerLine = (r) => `applied ${r.filesChanged.length}, identical ${r.identicalSkipped.length}, refused ${r.refused.length}${r.refused.length > 0 ? ` (${r.refused.map((f) => `${f.path}: ${f.reason}`).join("; ")})` : ""}, failed ${r.failed.length}${r.failed.length > 0 ? ` (${r.failed.map((f) => `${f.path}: ${f.reason}`).join("; ")})` : ""}`;
|
|
1230
|
+
if (restored.outcome === "failed") {
|
|
1231
|
+
if (restored.error?.code === "not_found") {
|
|
970
1232
|
const e = new Error(rewindBefore
|
|
971
|
-
? `rewind-files: the resolved "before"
|
|
972
|
-
: `rewind-files: no file
|
|
1233
|
+
? `rewind-files: the resolved "before" history anchor "${rewindTarget}" disappeared before restore — files were NOT rewound`
|
|
1234
|
+
: `rewind-files: no file-history boundary exists for entry "${rewindTarget}" on session "${sessionId}" — the working tree was NOT rewound. ${await legacyEpochTail(rewindTarget, "keyed")}`);
|
|
973
1235
|
e.code = "rewind_snapshot.unresolvable";
|
|
974
1236
|
throw e;
|
|
975
1237
|
}
|
|
1238
|
+
const e = new Error(`rewind-files restore failed (${restored.error?.code ?? "restore_failed"}): ${restored.error?.message ?? "unknown"} — ledger: ${ledgerLine(restored)}`);
|
|
1239
|
+
e.code = "rewind.restore_failed";
|
|
1240
|
+
throw e;
|
|
1241
|
+
}
|
|
1242
|
+
if (restored.outcome === "partial") {
|
|
1243
|
+
if (spec.acceptPartialRestore === true) {
|
|
1244
|
+
rewindNotes.push({
|
|
1245
|
+
code: "restore_partial",
|
|
1246
|
+
message: `file restore to entry "${rewindTarget}" was PARTIAL and was tolerated by acceptPartialRestore — ledger: ${ledgerLine(restored)}. Re-running the same restore converges (per-file idempotent).`,
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
976
1249
|
else {
|
|
977
|
-
const e = new Error(`rewind-files restore
|
|
1250
|
+
const e = new Error(`rewind-files: restore to entry "${rewindTarget}" was PARTIAL — ledger: ${ledgerLine(restored)}. The task is refused rather than run on a mixed-state tree (set acceptPartialRestore: true to tolerate and disclose instead); re-running the same restore converges (per-file idempotent).`);
|
|
978
1251
|
e.code = "rewind.restore_failed";
|
|
979
1252
|
throw e;
|
|
980
1253
|
}
|
|
981
1254
|
}
|
|
1255
|
+
restoredFilePaths = restoredAbsPathsOf(restored, restoreRoot);
|
|
982
1256
|
}
|
|
1257
|
+
const trackFileEdit = fileHistoryEnabled
|
|
1258
|
+
? async (req) => {
|
|
1259
|
+
const r = await fileHistoryStore.trackEdit(sessionId, req.key, executionEnv, historyRoot, req.signal);
|
|
1260
|
+
if (r.ok) {
|
|
1261
|
+
if (!r.minted)
|
|
1262
|
+
return { ok: true };
|
|
1263
|
+
return {
|
|
1264
|
+
ok: true,
|
|
1265
|
+
annul: async (proof) => {
|
|
1266
|
+
const a = await fileHistoryStore.annulTrack(sessionId, req.key, historyRoot, proof === "verify" ? { env: executionEnv, ...(req.signal !== undefined ? { signal: req.signal } : {}) } : undefined);
|
|
1267
|
+
if (a.ok)
|
|
1268
|
+
return;
|
|
1269
|
+
try {
|
|
1270
|
+
deps.onError?.(new Error(`file-history could not retract the first-touch record for "${req.key}" after its edit failed to land (${a.error.code}: ${a.error.message}) — the path stays tracked, so a rewind to a boundary below it may converge bytes this engine never wrote`), { phase: "rewind", sessionId });
|
|
1271
|
+
}
|
|
1272
|
+
catch {
|
|
1273
|
+
}
|
|
1274
|
+
},
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
if (onTrackFailure === "proceed-unprotected") {
|
|
1278
|
+
try {
|
|
1279
|
+
deps.onError?.(new Error(`file-history first-touch backup for "${req.key}" failed (${r.error.code}: ${r.error.message}) — proceeding UNPROTECTED per RunnerDeps.onTrackFailure: the pristine promise for this path is void, and only in this process's memory (no durable trace exists, so after a process restart the next edit can still mint already-modified bytes as pristine v1)`), { phase: "rewind", sessionId });
|
|
1280
|
+
}
|
|
1281
|
+
catch {
|
|
1282
|
+
}
|
|
1283
|
+
return { ok: true };
|
|
1284
|
+
}
|
|
1285
|
+
return {
|
|
1286
|
+
ok: false,
|
|
1287
|
+
refusal: `Error (${req.tool}): the edit was refused because its first-touch file-history backup could not be persisted (${r.error.code}: ${r.error.message}). Proceeding would leave "${req.path}" with no durable pristine record — after the history store recovers, a retried edit would mint already-modified bytes as the pristine baseline. Retry when the file-history store is healthy.`,
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
: undefined;
|
|
1291
|
+
const fileHistoryBoundary = fileHistoryEnabled
|
|
1292
|
+
? createFileHistoryBoundarySeat({ store: fileHistoryStore, sessionId, env: executionEnv, root: historyRoot, onError: deps.onError })
|
|
1293
|
+
: undefined;
|
|
983
1294
|
const harnessRef = {};
|
|
984
1295
|
const skillScope = new ActiveSkillScope();
|
|
985
1296
|
const forwardSink = internals?.onForwardEvent;
|
|
@@ -1121,7 +1432,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1121
1432
|
...(frozenOnAsk !== undefined ? { onAsk: frozenOnAsk } : {}),
|
|
1122
1433
|
...(durableMandate ? { durableMandate: true } : {}),
|
|
1123
1434
|
...(contentMandate ? { contentMandate: true } : {}),
|
|
1124
|
-
...(autoModeDecider !== undefined
|
|
1435
|
+
...(autoModeDecider !== undefined
|
|
1436
|
+
? { autoMode: { decider: autoModeDecider, ...(autoModeArming !== undefined ? { arming: autoModeArming } : {}) } }
|
|
1437
|
+
: {}),
|
|
1125
1438
|
},
|
|
1126
1439
|
]
|
|
1127
1440
|
: [];
|
|
@@ -1135,7 +1448,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1135
1448
|
...(frozenOnAsk !== undefined ? { onAsk: frozenOnAsk } : {}),
|
|
1136
1449
|
...(durableMandate ? { durableMandate: true } : {}),
|
|
1137
1450
|
...(contentMandate ? { contentMandate: true } : {}),
|
|
1138
|
-
...(autoModeDecider !== undefined
|
|
1451
|
+
...(autoModeDecider !== undefined
|
|
1452
|
+
? { autoMode: { decider: autoModeDecider, ...(autoModeArming !== undefined ? { arming: autoModeArming } : {}) } }
|
|
1453
|
+
: {}),
|
|
1139
1454
|
},
|
|
1140
1455
|
]
|
|
1141
1456
|
: []),
|
|
@@ -1159,6 +1474,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1159
1474
|
spec.handsReadOnly !== true &&
|
|
1160
1475
|
!(toolFaceSnapshot.exclude?.includes("Bash") ?? false);
|
|
1161
1476
|
let autoModeDecider;
|
|
1477
|
+
let autoModeArming;
|
|
1162
1478
|
const delegationEntryCapsResolved = resolveDelegationEntryCaps(deps.delegationEntryCaps);
|
|
1163
1479
|
const enrichSpecToolCtx = (ctx) => ({
|
|
1164
1480
|
...ctx,
|
|
@@ -1184,6 +1500,19 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1184
1500
|
...(spec.additionalReadDirectories !== undefined ? { additionalReadDirectories: Object.freeze([...spec.additionalReadDirectories]) } : {}),
|
|
1185
1501
|
...(spec.envFacts !== undefined ? { envFacts: { ...spec.envFacts } } : {}),
|
|
1186
1502
|
...(spec.memoryPersistenceCapable !== undefined ? { memoryPersistenceCapable: spec.memoryPersistenceCapable } : {}),
|
|
1503
|
+
get memoryCaptureOptedOut() {
|
|
1504
|
+
return memoryEngineSession?.captureOptOut?.optedOut() === true;
|
|
1505
|
+
},
|
|
1506
|
+
get memoryCaptureIndeterminate() {
|
|
1507
|
+
return memoryEngineSession?.captureOptOut?.indeterminate() === true;
|
|
1508
|
+
},
|
|
1509
|
+
get memoryCaptureControlDir() {
|
|
1510
|
+
return memoryEngineSession?.engine.controlPlaneDir;
|
|
1511
|
+
},
|
|
1512
|
+
get memoryCaptureAncestors() {
|
|
1513
|
+
const ctl = memoryEngineSession?.engine.controlPlaneDir;
|
|
1514
|
+
return [...(internals?.memoryCaptureAncestors ?? []), { sessionId, ...(ctl !== undefined ? { controlDir: ctl } : {}) }];
|
|
1515
|
+
},
|
|
1187
1516
|
getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
|
|
1188
1517
|
parentCwd: taskRootFinal,
|
|
1189
1518
|
reminderMark,
|
|
@@ -1262,6 +1591,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1262
1591
|
}
|
|
1263
1592
|
}
|
|
1264
1593
|
let runtimeCaps;
|
|
1594
|
+
let runtimeCapsFaulted = false;
|
|
1265
1595
|
if (deps.runtimeCapsResolver) {
|
|
1266
1596
|
try {
|
|
1267
1597
|
runtimeCaps = (await deps.runtimeCapsResolver(spec.principal)) ?? undefined;
|
|
@@ -1269,6 +1599,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1269
1599
|
catch (err) {
|
|
1270
1600
|
deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "config", sessionId });
|
|
1271
1601
|
runtimeCaps = { allowWorkflows: false, allowFork: false };
|
|
1602
|
+
runtimeCapsFaulted = true;
|
|
1272
1603
|
}
|
|
1273
1604
|
}
|
|
1274
1605
|
let complianceDenies = new Set();
|
|
@@ -1315,7 +1646,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1315
1646
|
catch {
|
|
1316
1647
|
classifierModel = model;
|
|
1317
1648
|
}
|
|
1318
|
-
if (await derivedRouteFallsBack({ seat: "auto-mode-classifier", derived: classifierModel, primary: model, brain: deps.brain, getApiKeyAndHeaders: spec.getApiKeyAndHeaders, onNotice: deps.onNotice, sessionId })) {
|
|
1649
|
+
if (await derivedRouteFallsBack({ seat: "auto-mode-classifier", derived: classifierModel, primary: model, brain: deps.brain, getApiKeyAndHeaders: spec.getApiKeyAndHeaders, onNotice: deps.onNotice, sessionId, runId })) {
|
|
1319
1650
|
classifierModel = model;
|
|
1320
1651
|
}
|
|
1321
1652
|
const classifierSystemPrompt = buildAutoModePrompt(am);
|
|
@@ -1340,6 +1671,17 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1340
1671
|
.join("");
|
|
1341
1672
|
},
|
|
1342
1673
|
});
|
|
1674
|
+
if (am.persistArming === true) {
|
|
1675
|
+
autoModeArming = autoModeArmingRecipeOf(am, {
|
|
1676
|
+
promptDigest: `apv1:${createHash("sha256").update(classifierSystemPrompt).digest("hex")}`,
|
|
1677
|
+
});
|
|
1678
|
+
if (autoModeArming === undefined) {
|
|
1679
|
+
deps.onError?.(new Error("RunnerDeps.autoMode.persistArming is set but the auto-mode face did not canonicalize into an arming recipe " +
|
|
1680
|
+
"(a rule list carrying a non-string, or a non-finite/out-of-range timeoutMs / failureThreshold / window bound) — " +
|
|
1681
|
+
"NO arming recipe is recorded on this run's parked constraint chain, and a cross-process redemption keeps the " +
|
|
1682
|
+
"conservative behavior (the ancestor classifier answers unavailable and the inherited ask flows to a human)"), { phase: "config", sessionId });
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1343
1685
|
}
|
|
1344
1686
|
const deploymentWorkflowReady = runnerSelf !== undefined && isSelfOrchestrationActive(spec, deps);
|
|
1345
1687
|
const selfOrchestrationActive = deploymentWorkflowReady && runtimeCaps?.allowWorkflows !== false;
|
|
@@ -1387,6 +1729,15 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1387
1729
|
principal: spec.principal,
|
|
1388
1730
|
oneShot: spec.oneShot,
|
|
1389
1731
|
...(resolvedInteractionPosture !== undefined ? { parentInteractionPosture: resolvedInteractionPosture } : {}),
|
|
1732
|
+
parentMemoryCaptureState: () => {
|
|
1733
|
+
const ctl = memoryEngineSession?.engine.controlPlaneDir;
|
|
1734
|
+
return {
|
|
1735
|
+
optedOut: memoryEngineSession?.captureOptOut?.optedOut() === true,
|
|
1736
|
+
indeterminate: memoryEngineSession?.captureOptOut?.indeterminate() === true,
|
|
1737
|
+
...(ctl !== undefined ? { controlDir: ctl } : {}),
|
|
1738
|
+
ancestors: [...(internals?.memoryCaptureAncestors ?? []), { sessionId, ...(ctl !== undefined ? { controlDir: ctl } : {}) }],
|
|
1739
|
+
};
|
|
1740
|
+
},
|
|
1390
1741
|
autoModeReview: () => (autoModeDecider !== undefined ? { decider: autoModeDecider } : undefined),
|
|
1391
1742
|
workflowDepth: internals?.workflowDepth,
|
|
1392
1743
|
parentCwd: taskRootFinal,
|
|
@@ -1465,7 +1816,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1465
1816
|
tools.push(createOutputTool(outputRef, spec.outputSchema, compiled.strict ? compiled.modelSchema : undefined));
|
|
1466
1817
|
}
|
|
1467
1818
|
mcp = lockedPreflight.mcp?.length
|
|
1468
|
-
? await materializeMcpTools(lockedPreflight.mcp, spec.principal, deps.onElicit, deps.mcpImageResizer, { reminderMark, counts: reminderDisclosureCounts }, mcpRevocationWiring(deps))
|
|
1819
|
+
? await materializeMcpTools(lockedPreflight.mcp, spec.principal, deps.onElicit, deps.mcpImageResizer, { reminderMark, counts: reminderDisclosureCounts }, mcpRevocationWiring(deps, runId))
|
|
1469
1820
|
: { tools: [], toolAxes: [], warnings: [], serverInstructions: [], instructionsDelta: { pendingAdds: [], pendingRemovals: [] }, droppedTools: [], statuses: [], refresh: async () => [], dispose: async () => { } };
|
|
1470
1821
|
for (const w of mcp.warnings)
|
|
1471
1822
|
deps.onError?.(w, { phase: "mcp", sessionId });
|
|
@@ -1621,9 +1972,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1621
1972
|
foldProtocolAxes(a2a.toolAxes, "A2A");
|
|
1622
1973
|
const callIssuedAtRef = {};
|
|
1623
1974
|
const memoryWriteGateRef = {};
|
|
1624
|
-
const handsReadFaceInput = { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGateRef, firstPartyOffload, tools, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes, reminderMark, reminderDisclosureCounts };
|
|
1975
|
+
const handsReadFaceInput = { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGateRef, firstPartyOffload, tools, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes, reminderMark, reminderDisclosureCounts, ...(trackFileEdit !== undefined ? { trackFileEdit } : {}), ...(restoredFilePaths.length > 0 ? { restoredFilePaths } : {}) };
|
|
1625
1976
|
const handsReadFace = handsEnabled ? await prepareHandsMount(handsReadFaceInput) : resolveHandsLessReadFace(handsReadFaceInput);
|
|
1626
|
-
const { readFileStateForCheckpoint, seedContextFiles, handsCwdRef, workspaceStateSettle, wsSnapshot, rebaseWsPath, backgroundTaskToolsActive, additionalRootsCanonical, additionalReadRootsCanonical, attachmentRootCanonical, readDenyMatcher, envHandToolNames } = handsReadFace;
|
|
1977
|
+
const { readFileStateForCheckpoint, seedContextFiles, handsCwdRef, workspaceStateSettle, wsSnapshot, rebaseWsPath, backgroundTaskToolsActive, additionalRootsCanonical, additionalReadRootsCanonical, attachmentRootCanonical, readDenyMatcher, envHandToolNames, sealReadStateSeat } = handsReadFace;
|
|
1627
1978
|
resolvedReadFace = handsReadFace.resolvedReadFace;
|
|
1628
1979
|
readDenyAdditionsNormalized = handsReadFace.readDenyAdditionsNormalized;
|
|
1629
1980
|
handsLessResolvedFace = handsReadFace.handsLessResolvedFace;
|
|
@@ -1868,7 +2219,16 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1868
2219
|
const memoryPairExcludedName = MEMORY_ENGINE_TOOL_NAMES.find((n) => (toolFaceSnapshot.exclude ?? []).includes(n));
|
|
1869
2220
|
const memoryPairOccupiedName = MEMORY_ENGINE_TOOL_NAMES.find((n) => memoryPairNameDomain.has(n));
|
|
1870
2221
|
const memorySearchToolsPlanned = memoryPairExcludedName === undefined && memoryPairOccupiedName === undefined;
|
|
1871
|
-
const
|
|
2222
|
+
const captureLineage = await (async () => {
|
|
2223
|
+
try {
|
|
2224
|
+
const ff = (await session.getMetadata()).forkedFrom;
|
|
2225
|
+
return { ...(ff !== undefined && ff !== sessionId ? { origin: ff } : {}), fault: false };
|
|
2226
|
+
}
|
|
2227
|
+
catch {
|
|
2228
|
+
return { fault: true };
|
|
2229
|
+
}
|
|
2230
|
+
})();
|
|
2231
|
+
const { memoryEngineSession, memoryBlock: memoryBlockFromEngine, memoryTools, recallDisciplineSegment: memoryRecallSegment, seedFiles: memorySeedFiles, admittedOrgScopes: memoryAdmittedOrgScopes, ownOrgVerdict: memoryOwnOrgVerdict, effectiveMemoryScopes: memoryEffectiveScopes, } = await prepareMemory({
|
|
1872
2232
|
spec,
|
|
1873
2233
|
deps,
|
|
1874
2234
|
sessionId,
|
|
@@ -1879,6 +2239,20 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1879
2239
|
!(toolFaceSnapshot.exclude?.includes("Write") ?? false) &&
|
|
1880
2240
|
!(handsEnabled && isRemoteExecutionEnv(executionEnv) && spec.memoryPersistenceCapable !== true),
|
|
1881
2241
|
memoryProvenance,
|
|
2242
|
+
memoryCapturePolicy,
|
|
2243
|
+
memoryCapturePolicyDeclared: deps.memoryCapturePolicy !== undefined,
|
|
2244
|
+
captureEntitlement: {
|
|
2245
|
+
value: typeof runtimeCaps?.allowMemoryOptOut === "boolean" ? runtimeCaps.allowMemoryOptOut : undefined,
|
|
2246
|
+
faulted: runtimeCapsFaulted || (runtimeCaps?.allowMemoryOptOut !== undefined && typeof runtimeCaps.allowMemoryOptOut !== "boolean"),
|
|
2247
|
+
},
|
|
2248
|
+
captureCarrierUnsupported: isRemoteExecutionEnv(executionEnv) && deps.memoryCaptureRecordStore === undefined,
|
|
2249
|
+
...(internals?.parentSessionId !== undefined ? { parentSessionId: internals.parentSessionId } : {}),
|
|
2250
|
+
captureFloor: internals?.memoryCaptureFloor === true,
|
|
2251
|
+
...(internals?.memoryCaptureFloorIndeterminate === true ? { captureFloorIndeterminate: true } : {}),
|
|
2252
|
+
...(captureLineage.origin !== undefined ? { captureForkOrigin: captureLineage.origin } : {}),
|
|
2253
|
+
...(captureLineage.fault ? { captureLineageFault: true } : {}),
|
|
2254
|
+
...(internals?.memoryCaptureQueryDir !== undefined ? { parentCaptureQueryDir: internals.memoryCaptureQueryDir } : {}),
|
|
2255
|
+
...(internals?.memoryCaptureAncestors !== undefined ? { captureAncestors: internals.memoryCaptureAncestors } : {}),
|
|
1882
2256
|
memoryPersistenceDeclared: spec.memoryPersistenceCapable,
|
|
1883
2257
|
rosterCanPersist: spec.memoryPersistenceCapable ??
|
|
1884
2258
|
tools.some((t) => {
|
|
@@ -1937,6 +2311,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1937
2311
|
}
|
|
1938
2312
|
}
|
|
1939
2313
|
let instructionSources;
|
|
2314
|
+
let projectInstructionContent;
|
|
1940
2315
|
if (deps.loadProjectMemory) {
|
|
1941
2316
|
let projectMemoryPhase = spec.sessionId ? "resume" : "fresh";
|
|
1942
2317
|
if (spec.sessionId) {
|
|
@@ -1988,6 +2363,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1988
2363
|
}
|
|
1989
2364
|
}
|
|
1990
2365
|
if (projectMem != null && projectMem.trim()) {
|
|
2366
|
+
projectInstructionContent = projectMem;
|
|
1991
2367
|
const projectBlock = `${PROJECT_CONTEXT_FRAMING}\n\n${composeMemoryBlock(projectMem, "project")}`;
|
|
1992
2368
|
memoryBlock = memoryBlock ? `${memoryBlock}\n\n${projectBlock}` : projectBlock;
|
|
1993
2369
|
}
|
|
@@ -2052,7 +2428,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2052
2428
|
(memoryPairExcludedName !== undefined
|
|
2053
2429
|
? `excludeTools removes "${memoryPairExcludedName}"`
|
|
2054
2430
|
: `this task already declares a tool named "${memoryPairOccupiedName}" (the built-in yields to it)`) +
|
|
2055
|
-
" —
|
|
2431
|
+
" — these tools mount together or not at all."), { phase: "config", sessionId, classification: "memory-tools-not-mounted" });
|
|
2056
2432
|
}
|
|
2057
2433
|
const skillsListing = skillSpecs.length > 0
|
|
2058
2434
|
? {
|
|
@@ -2251,7 +2627,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2251
2627
|
return deferredSet;
|
|
2252
2628
|
};
|
|
2253
2629
|
const sharedMemoryPair = sharedMemoryPairMounted ? SHARED_MEMORY_TOOL_NAMES.filter((n) => tools.some((t) => t.name === n)) : [];
|
|
2254
|
-
const memoryEnginePair = memoryEnginePairMounted
|
|
2630
|
+
const memoryEnginePair = explicitlyDeferredMemoryTrio(memoryEnginePairMounted, tools, toolFaceSnapshot.defer);
|
|
2255
2631
|
const builtinDeferPairNames = [...sharedMemoryPair, ...memoryEnginePair];
|
|
2256
2632
|
let deferred;
|
|
2257
2633
|
if (builtinDeferPairNames.length > 0) {
|
|
@@ -2261,8 +2637,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2261
2637
|
const soleCause = d0.size === 0 && d1.size > 0;
|
|
2262
2638
|
const supportNameTaken = tools.some((t) => t.name === TOOL_SEARCH_NAME || (t.aliases ?? []).includes(TOOL_SEARCH_NAME));
|
|
2263
2639
|
if (soleCause && supportNameTaken) {
|
|
2640
|
+
const retractNames = memoryGroupRetractionSet(builtinDeferPairNames, memoryEnginePair.length > 0);
|
|
2264
2641
|
for (let i = tools.length - 1; i >= 0; i--) {
|
|
2265
|
-
if (
|
|
2642
|
+
if (retractNames.has(tools[i].name))
|
|
2266
2643
|
tools.splice(i, 1);
|
|
2267
2644
|
}
|
|
2268
2645
|
if (sharedMemoryPair.length > 0) {
|
|
@@ -2272,8 +2649,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2272
2649
|
}
|
|
2273
2650
|
if (memoryEnginePair.length > 0) {
|
|
2274
2651
|
memoryEnginePairMounted = false;
|
|
2275
|
-
if (memoryBlock !== undefined) {
|
|
2276
|
-
const segment =
|
|
2652
|
+
if (memoryBlock !== undefined && memoryRecallSegment !== undefined) {
|
|
2653
|
+
const segment = memoryRecallSegment;
|
|
2277
2654
|
if (memoryBlock === segment)
|
|
2278
2655
|
memoryBlock = undefined;
|
|
2279
2656
|
else if (memoryBlock.includes(`\n\n${segment}`))
|
|
@@ -2282,7 +2659,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2282
2659
|
memoryBlock = memoryBlock.slice(segment.length + 2);
|
|
2283
2660
|
}
|
|
2284
2661
|
deps.onError?.(new Error(`Memory tools ${MEMORY_ENGINE_TOOL_NAMES.join("/")} were NOT mounted: mounting them would inject ` +
|
|
2285
|
-
`the "${TOOL_SEARCH_NAME}" tool, whose name this task already declares —
|
|
2662
|
+
`the "${TOOL_SEARCH_NAME}" tool, whose name this task already declares — these tools mount together or not at all.`), { phase: "config", sessionId, classification: "memory-tools-not-mounted" });
|
|
2286
2663
|
}
|
|
2287
2664
|
deferred = d0;
|
|
2288
2665
|
}
|
|
@@ -2307,6 +2684,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2307
2684
|
statuses: mcp?.statuses,
|
|
2308
2685
|
execIsExternalContent,
|
|
2309
2686
|
sessionId,
|
|
2687
|
+
runId,
|
|
2310
2688
|
onNotice: deps.onNotice,
|
|
2311
2689
|
});
|
|
2312
2690
|
if (memoryEngineSession?.settlement !== undefined) {
|
|
@@ -2470,6 +2848,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2470
2848
|
reason: waivedReason,
|
|
2471
2849
|
...(typeof requested === "string" ? { subagentType: inlineUntrusted(requested, 120) } : {}),
|
|
2472
2850
|
sessionId,
|
|
2851
|
+
runId,
|
|
2473
2852
|
},
|
|
2474
2853
|
});
|
|
2475
2854
|
}
|
|
@@ -2930,7 +3309,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2930
3309
|
emitTrace(deps.tracer, () => ({
|
|
2931
3310
|
kind: "loop.recovery",
|
|
2932
3311
|
version: 1,
|
|
2933
|
-
taskId:
|
|
3312
|
+
taskId: hostTaskId,
|
|
2934
3313
|
reason: step.reason,
|
|
2935
3314
|
ts: Date.now(),
|
|
2936
3315
|
}));
|
|
@@ -3161,7 +3540,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3161
3540
|
emitTrace(deps.tracer, () => ({
|
|
3162
3541
|
kind: "permission.sandbox_admitted",
|
|
3163
3542
|
version: 1,
|
|
3164
|
-
taskId:
|
|
3543
|
+
taskId: hostTaskId,
|
|
3165
3544
|
toolCallId: info.toolCallId,
|
|
3166
3545
|
toolName: info.toolName,
|
|
3167
3546
|
askClasses: info.askClasses.map((a) => a.cls),
|
|
@@ -3247,16 +3626,17 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3247
3626
|
emitTrace(deps.tracer, () => ({
|
|
3248
3627
|
kind: "permission.rule_store_unreadable",
|
|
3249
3628
|
version: 1,
|
|
3250
|
-
taskId:
|
|
3629
|
+
taskId: hostTaskId,
|
|
3251
3630
|
message: err instanceof Error ? err.message : String(err),
|
|
3252
3631
|
ts: Date.now(),
|
|
3253
3632
|
}));
|
|
3254
3633
|
return { unreadable: true };
|
|
3255
3634
|
}
|
|
3256
|
-
const
|
|
3635
|
+
const table = await spliceSessionOverlayRows(deps.sessionPermissionRules, sessionId, listed.rules, deps.tracer, hostTaskId);
|
|
3636
|
+
const admitting = findAdmittingRule(table, { tool: req.toolName, command, cwd: root, sessionId });
|
|
3257
3637
|
if (admitting !== undefined)
|
|
3258
3638
|
return persistedRuleHitOf(admitting);
|
|
3259
|
-
const coverage = segmentCoverageOf(command, { persisted:
|
|
3639
|
+
const coverage = segmentCoverageOf(command, { persisted: table }, { tool: req.toolName, cwd: root, sessionId });
|
|
3260
3640
|
return coverage !== undefined ? { segmentCoverage: coverage } : undefined;
|
|
3261
3641
|
},
|
|
3262
3642
|
};
|
|
@@ -3292,26 +3672,36 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3292
3672
|
return {};
|
|
3293
3673
|
if ((spec.principal === undefined || spec.principal === "") && deps.localOwnerRules !== true)
|
|
3294
3674
|
return {};
|
|
3295
|
-
|
|
3296
|
-
ask?.
|
|
3297
|
-
|
|
3298
|
-
ask?.
|
|
3299
|
-
|
|
3300
|
-
ask?.
|
|
3301
|
-
|
|
3302
|
-
|
|
3675
|
+
const closedDoor = (() => {
|
|
3676
|
+
if (ask?.requiresRealApproval === true)
|
|
3677
|
+
return "mandated";
|
|
3678
|
+
if (ask?.persistedRuleShadowed !== undefined)
|
|
3679
|
+
return "shadowed";
|
|
3680
|
+
if (ask?.decisionReason === "hook")
|
|
3681
|
+
return "mandated";
|
|
3682
|
+
if (ask?.matchedAskRule !== undefined)
|
|
3683
|
+
return "shadowed";
|
|
3684
|
+
if (ask?.inheritedUnresolved === true)
|
|
3685
|
+
return "mandated";
|
|
3686
|
+
if (ask?.ancestorResolved === true)
|
|
3687
|
+
return "mandated";
|
|
3688
|
+
return undefined;
|
|
3689
|
+
})();
|
|
3690
|
+
if (closedDoor !== undefined)
|
|
3691
|
+
return { ruleOffersAbsence: closedDoor };
|
|
3303
3692
|
if (persistedRuleMandateOf({
|
|
3304
3693
|
egress: egressTools.has(toolName),
|
|
3305
3694
|
irreversibility: irreversibilityTier.get(toolName),
|
|
3306
3695
|
shellGated: shellGatedBash,
|
|
3696
|
+
probeMandated: ask?.probeMandated === true,
|
|
3307
3697
|
}) !== undefined) {
|
|
3308
|
-
return {};
|
|
3698
|
+
return { ruleOffersAbsence: "mandated" };
|
|
3309
3699
|
}
|
|
3310
3700
|
const command = args?.command;
|
|
3311
3701
|
if (typeof command !== "string")
|
|
3312
|
-
return {};
|
|
3702
|
+
return { ruleOffersAbsence: "lane_cannot_speak" };
|
|
3313
3703
|
const offers = suggestRulesForCommand(command, ask?.segmentCoverage !== undefined ? { coverage: ask.segmentCoverage } : undefined);
|
|
3314
|
-
return offers.length > 0 ? { ruleOffers: offers } : {};
|
|
3704
|
+
return offers.length > 0 ? { ruleOffers: offers } : { ruleOffersAbsence: "lane_cannot_speak" };
|
|
3315
3705
|
};
|
|
3316
3706
|
const frozenClassifierExcluded = (d) => d.decisionReason === "hook" || d.matchedAskRule !== undefined;
|
|
3317
3707
|
const recheckApprovedEdit = async (pol, onAskOf, creq, edit, csignal, ancestorDecider) => {
|
|
@@ -3748,7 +4138,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3748
4138
|
const parkLaneArmed = wiringManifest.parkLane.effective === true;
|
|
3749
4139
|
const hookIdentity = mintHookInvocationIdentity({
|
|
3750
4140
|
sessionId,
|
|
3751
|
-
taskId:
|
|
4141
|
+
taskId: hostTaskId,
|
|
3752
4142
|
legKind: wiringManifest.leg.kind,
|
|
3753
4143
|
isDelegatedChild: delegation.isDelegatedChild,
|
|
3754
4144
|
...(internals?.insideFork === true ? { insideFork: true } : {}),
|
|
@@ -3757,6 +4147,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3757
4147
|
});
|
|
3758
4148
|
const hookContextConsumerWired = hooks?.preToolUse !== undefined || hooks?.postToolUse !== undefined || hooks?.postToolUseFailure !== undefined;
|
|
3759
4149
|
const hookEnvFace = hookContextConsumerWired && (ownedEnv ?? deps.executionEnv) != null ? createHookEnvCapabilities(executionEnv) : undefined;
|
|
4150
|
+
if (fileHistoryBoundary !== undefined) {
|
|
4151
|
+
harness.on("tool_call", async () => {
|
|
4152
|
+
await fileHistoryBoundary.settle();
|
|
4153
|
+
return undefined;
|
|
4154
|
+
});
|
|
4155
|
+
}
|
|
3760
4156
|
if (effectivePolicy || hooks?.preToolUse || egressTools.size > 0 || irreversibleTools.size > 0 || resourceSuspendEligible || platformSuspendArmed || spec.enablePlanMode === true) {
|
|
3761
4157
|
const adjudicate = effectivePolicy
|
|
3762
4158
|
? (req) => raceAbort(Promise.resolve(effectivePolicy.check({ ...req, budget: budgetSnapshot, ...(handsCwdRef !== undefined ? { cwd: handsCwdRef.current } : {}) }, abortController.signal)), abortController.signal, () => ({
|
|
@@ -3884,7 +4280,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3884
4280
|
const requiresParentConstraint = (inheritedParentConstraints?.length ?? 0) > 0 || seedInheritedGate?.requiresParentConstraint === true;
|
|
3885
4281
|
const parentConstraintCount = (inheritedParentConstraints?.length ?? 0) > 0 ? inheritedParentConstraints.length : seedInheritedGate?.parentConstraintCount;
|
|
3886
4282
|
const constraintChain = (inheritedParentConstraints?.length ?? 0) > 0
|
|
3887
|
-
? inheritedParentConstraints.map((pc) =>
|
|
4283
|
+
? inheritedParentConstraints.map((pc) => constraintChainEntryOfLayer(pc))
|
|
3888
4284
|
: seedInheritedGate?.constraintChain;
|
|
3889
4285
|
const constraintDigest = (inheritedParentConstraints?.length ?? 0) > 0
|
|
3890
4286
|
? constraintChainDigest(constraintChain)
|
|
@@ -4282,7 +4678,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4282
4678
|
}
|
|
4283
4679
|
};
|
|
4284
4680
|
const suspendAsk = parkLaneArmed && checkpointStore !== undefined
|
|
4285
|
-
? async (req, postHookArgs, safety, liveFaceUnavailable, realApproval, shadowedRule, askDecisionReason, probeReason, probeCause, segmentCoverage, matchedAskRule) => {
|
|
4681
|
+
? async (req, postHookArgs, safety, liveFaceUnavailable, realApproval, shadowedRule, askDecisionReason, probeReason, probeCause, segmentCoverage, matchedAskRule, probeMandated) => {
|
|
4286
4682
|
const syncFirstEligible = req.toolName === ASK_USER_QUESTION_TOOL_NAME ? contentAskRoutable(req.toolCallId) : isLiveApproverSeat(onAsk);
|
|
4287
4683
|
if (syncFirstEligible &&
|
|
4288
4684
|
runtimeCaps?.forceDurableGate !== true &&
|
|
@@ -4453,6 +4849,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4453
4849
|
...(shadowedRule !== undefined ? { persistedRuleShadowed: shadowedRule } : {}),
|
|
4454
4850
|
...(askDecisionReason !== undefined ? { decisionReason: askDecisionReason } : {}),
|
|
4455
4851
|
...(matchedAskRule !== undefined ? { matchedAskRule } : {}),
|
|
4852
|
+
...(probeMandated === true ? { probeMandated: true } : {}),
|
|
4456
4853
|
...(inheritedUnavailableAsks.has(req.toolCallId) ? { inheritedUnresolved: true } : {}),
|
|
4457
4854
|
...(segmentCoverage !== undefined ? { segmentCoverage } : {}),
|
|
4458
4855
|
}),
|
|
@@ -4552,7 +4949,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4552
4949
|
let humanBareRejection = false;
|
|
4553
4950
|
try {
|
|
4554
4951
|
result = await runToolGate({
|
|
4555
|
-
onNotifyError: (f) => emitTrace(deps.tracer, () => ({ kind: "observer.notify_failed", version: 1, taskId:
|
|
4952
|
+
onNotifyError: (f) => emitTrace(deps.tracer, () => ({ kind: "observer.notify_failed", version: 1, taskId: hostTaskId, site: f.site, message: f.error.message, ts: Date.now() })),
|
|
4556
4953
|
event: e,
|
|
4557
4954
|
identity: hookIdentity,
|
|
4558
4955
|
reminderMark,
|
|
@@ -4581,7 +4978,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4581
4978
|
onResolved: (info) => emitTrace(deps.tracer, () => ({
|
|
4582
4979
|
kind: "permission.persisted_rule_allowed",
|
|
4583
4980
|
version: 2,
|
|
4584
|
-
taskId:
|
|
4981
|
+
taskId: hostTaskId,
|
|
4585
4982
|
toolName: info.toolName,
|
|
4586
4983
|
toolCallId: info.toolCallId,
|
|
4587
4984
|
rules: [...info.rules],
|
|
@@ -4598,7 +4995,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4598
4995
|
onUnavailable: (info) => emitTrace(deps.tracer, () => ({
|
|
4599
4996
|
kind: "permission.org_snapshot_unavailable",
|
|
4600
4997
|
version: 1,
|
|
4601
|
-
taskId:
|
|
4998
|
+
taskId: hostTaskId,
|
|
4602
4999
|
toolName: info.toolName,
|
|
4603
5000
|
toolCallId: info.toolCallId,
|
|
4604
5001
|
message: info.message,
|
|
@@ -4720,7 +5117,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4720
5117
|
}
|
|
4721
5118
|
const editAt = editBudget(model);
|
|
4722
5119
|
const guardAt = guardBudget(model);
|
|
4723
|
-
const microCompact = buildMicroCompactState(deps, model, sessionId, offloadStore, microCompactKnob);
|
|
5120
|
+
const microCompact = buildMicroCompactState(deps, model, sessionId, runId, offloadStore, microCompactKnob);
|
|
4724
5121
|
const charsPerToken = model.charsPerToken ?? DEFAULT_CHARS_PER_TOKEN;
|
|
4725
5122
|
harness.on("context", async ({ messages, recheck, signal }) => {
|
|
4726
5123
|
batchHaltRef.current = undefined;
|
|
@@ -4735,7 +5132,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4735
5132
|
onCapped: (info) => pendingProjectionObservations.push(() => emitTrace(deps.tracer, () => ({
|
|
4736
5133
|
kind: "tool_result.capped",
|
|
4737
5134
|
version: 1,
|
|
4738
|
-
taskId:
|
|
5135
|
+
taskId: hostTaskId,
|
|
4739
5136
|
...(info.tool !== undefined ? { tool: info.tool } : {}),
|
|
4740
5137
|
sizeChars: info.sizeChars,
|
|
4741
5138
|
storeFallback: info.storeFallback,
|
|
@@ -4772,11 +5169,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4772
5169
|
: {}),
|
|
4773
5170
|
});
|
|
4774
5171
|
if (ccPass !== undefined) {
|
|
4775
|
-
recordFrontierClears({ pass: ccPass, index: replay.index, edited, ledger: microCompact.ledger, tracer: deps.tracer, taskId:
|
|
5172
|
+
recordFrontierClears({ pass: ccPass, index: replay.index, edited, ledger: microCompact.ledger, tracer: deps.tracer, taskId: hostTaskId, trigger: "frontier" });
|
|
4776
5173
|
}
|
|
4777
5174
|
const chain = await runGuardChain({
|
|
4778
5175
|
edited, replayed, index: replay.index, microCompact, guardAt, charsPerToken, offloadStore,
|
|
4779
|
-
sessionId, taskId:
|
|
5176
|
+
sessionId, taskId: hostTaskId, deps, trimPressureRef, recheck, signal,
|
|
4780
5177
|
pendingProjectionObservations,
|
|
4781
5178
|
});
|
|
4782
5179
|
if ("earlyReturn" in chain) {
|
|
@@ -5011,7 +5408,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
5011
5408
|
const effectiveReadFaceObserved = carrierReadFace();
|
|
5012
5409
|
const effectiveReadDenyObserved = readDenyAdditionsNormalized.length > 0 ? readDenyAdditionsNormalized.map((e) => ({ ...e })) : undefined;
|
|
5013
5410
|
const preparedHolder = {};
|
|
5014
|
-
const buildPrepared = () => ({ harness, session, sessionId, reminderMark, reminderDisclosureCounts, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettlement, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
5411
|
+
const buildPrepared = () => ({ harness, session, sessionId, runId, reminderMark, reminderDisclosureCounts, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettlement, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
5015
5412
|
const prepared = buildPrepared();
|
|
5016
5413
|
preparedHolder.current = prepared;
|
|
5017
5414
|
return prepared;
|