@sema-agent/core 7.30.1 → 7.31.1

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.
Files changed (80) hide show
  1. package/CHANGELOG.md +284 -0
  2. package/dist/agents/cross-session-judge.d.ts +4 -12
  3. package/dist/agents/cross-session-judge.js +4 -11
  4. package/dist/agents/peer-session-drain.js +0 -5
  5. package/dist/agents/send-message-tool.js +1 -0
  6. package/dist/agents/subagent.d.ts +1 -0
  7. package/dist/agents/subagent.js +1 -0
  8. package/dist/core/ask-origin.d.ts +6 -0
  9. package/dist/core/ask-origin.js +10 -0
  10. package/dist/core/checkpoint-store.d.ts +14 -0
  11. package/dist/core/checkpoint-store.js +1 -0
  12. package/dist/core/engine-notice.d.ts +4 -0
  13. package/dist/core/governance-codes.d.ts +1 -1
  14. package/dist/core/governance-codes.js +4 -0
  15. package/dist/core/memory-engine/data-plane.d.ts +3 -1
  16. package/dist/core/memory-engine/data-plane.js +1 -1
  17. package/dist/core/memory-engine/delegation-settlement.d.ts +44 -3
  18. package/dist/core/memory-engine/delegation-settlement.js +38 -3
  19. package/dist/core/memory-engine/engine.d.ts +133 -27
  20. package/dist/core/memory-engine/engine.js +452 -151
  21. package/dist/core/memory-engine/entry-scan.d.ts +23 -0
  22. package/dist/core/memory-engine/entry-scan.js +55 -0
  23. package/dist/core/memory-engine/file-backend.d.ts +31 -23
  24. package/dist/core/memory-engine/file-backend.js +174 -100
  25. package/dist/core/memory-engine/layer-observations.d.ts +109 -0
  26. package/dist/core/memory-engine/layer-observations.js +148 -0
  27. package/dist/core/memory-engine/layout.d.ts +40 -4
  28. package/dist/core/memory-engine/layout.js +43 -7
  29. package/dist/core/memory-engine/types.d.ts +26 -2
  30. package/dist/core/parent-spec-seat.d.ts +4 -1
  31. package/dist/core/parent-spec-seat.js +2 -0
  32. package/dist/core/park-selfcheck.js +1 -0
  33. package/dist/core/runner/announce-once-ledger.d.ts +26 -0
  34. package/dist/core/runner/announce-once-ledger.js +37 -0
  35. package/dist/core/runner/contracts.d.ts +22 -5
  36. package/dist/core/runner/denial-limit-arms.d.ts +2 -2
  37. package/dist/core/runner/prepare-acquire-reconcile.d.ts +0 -4
  38. package/dist/core/runner/prepare-acquire-reconcile.js +1 -3
  39. package/dist/core/runner/prepare-file-history.d.ts +13 -15
  40. package/dist/core/runner/prepare-file-history.js +57 -43
  41. package/dist/core/runner/prepare-hands-readface.d.ts +2 -2
  42. package/dist/core/runner/prepare-listings.d.ts +3 -1
  43. package/dist/core/runner/prepare-listings.js +2 -2
  44. package/dist/core/runner/prepare-memory.js +16 -5
  45. package/dist/core/runner/prepare-park-ask.js +27 -16
  46. package/dist/core/runner/prepare-prompt-inputs.d.ts +8 -1
  47. package/dist/core/runner/prepare-prompt-inputs.js +5 -3
  48. package/dist/core/runner/prepare-task.js +7 -5
  49. package/dist/core/runner/prepare-wiring-manifest.d.ts +1 -0
  50. package/dist/core/runner/prepare-wiring-manifest.js +2 -1
  51. package/dist/core/runner/run-attachment-seats.js +1 -1
  52. package/dist/core/runner/run-leg.js +61 -52
  53. package/dist/core/runner/run-settle-and-teardown.js +4 -0
  54. package/dist/core/runner/run-turn-boundary.js +6 -6
  55. package/dist/core/runner/turn-attachments.d.ts +6 -6
  56. package/dist/core/runner-deps.d.ts +3 -1
  57. package/dist/core/secret-env.d.ts +12 -1
  58. package/dist/core/secret-env.js +27 -2
  59. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +2 -0
  60. package/dist/core/store-contracts/checkpoint-store-contract.js +22 -0
  61. package/dist/core/task-result.d.ts +1 -1
  62. package/dist/core/task-spec.d.ts +17 -8
  63. package/dist/core/tool-spec.d.ts +1 -1
  64. package/dist/core/wiring-manifest.d.ts +10 -0
  65. package/dist/core/wiring-manifest.js +7 -1
  66. package/dist/engine/execution-env/node-execution-env.d.ts +6 -0
  67. package/dist/engine/execution-env/node-execution-env.js +15 -17
  68. package/dist/engine/harness/agent-harness.js +7 -5
  69. package/dist/engine/lsp/node-lsp-manager.d.ts +4 -0
  70. package/dist/engine/lsp/node-lsp-manager.js +10 -4
  71. package/dist/index.d.ts +1 -1
  72. package/dist/index.js +1 -1
  73. package/dist/tools/fs/shell-read-plan.d.ts +2 -2
  74. package/dist/tools/fs/shell-read-plan.js +15 -10
  75. package/dist/tools/fs/shell-read-word.d.ts +5 -4
  76. package/dist/tools/fs/shell-read-word.js +3 -2
  77. package/package.json +1 -1
  78. package/test/export-surface.snapshot.json +7 -1
  79. package/wire-changes/core@7.31.0.json +705 -0
  80. package/wire-changes/core@7.31.1.json +189 -0
@@ -27,7 +27,8 @@ import type { LockedPreflight } from "../locked-config.js";
27
27
  import type { MaterializedMcp } from "../mcp.js";
28
28
  import type { ResolvedRole } from "../roles.js";
29
29
  import type { RunnerDeps, TaskSpec } from "../types.js";
30
- import type { PrepareResume, PromptFeatureFlags, RunInternals, ToolFaceSnapshot } from "./contracts.js";
30
+ import type { SessionTreeEntry } from "../../internal/harness-types.js";
31
+ import type { DateAnchorRead, PrepareResume, PromptFeatureFlags, RunInternals, ToolFaceSnapshot } from "./contracts.js";
31
32
  import type { AdvertisedWritableDirs } from "./advertised-writable-dirs.js";
32
33
  import { type GitStatusLaneRef } from "./git-status-frame.js";
33
34
  import { type ContextWindowOverlay } from "../context-settings.js";
@@ -99,6 +100,10 @@ export interface PreparePromptInputsInput {
99
100
  advertisedWritableDirs?: AdvertisedWritableDirs;
100
101
  /** borrowed-readonly — the acquired session's id (operator-line context). */
101
102
  sessionId: string;
103
+ /** borrowed-readonly — the acquired session; only `getBranch` is read (the date anchor). */
104
+ session: {
105
+ getBranch(): Promise<ReadonlyArray<SessionTreeEntry>>;
106
+ };
102
107
  /** borrowed-readonly — the deployment's window overlay for this leg, as it reads at PREPARE. The prompt's
103
108
  * context-management sentence must not claim mid-task summarization a deployment has turned off for this
104
109
  * model; the seat is live, so a later swap's effect on the SENTENCE lands at the next prepare while its
@@ -123,6 +128,8 @@ export interface PreparePromptInputsResult {
123
128
  tzValid: boolean;
124
129
  /** owned — the client's declared zone as given (paired with `tzValid`; unvalidated on its own). */
125
130
  userTz: string | undefined;
131
+ /** owned — the session's date anchor as read here; `envFacts.date` is its prefix date when present. */
132
+ dateAnchor: DateAnchorRead | undefined;
126
133
  /** owned — the git-status lane's run-local state (empty on a hands-less leg); the run loop and the checkpoint
127
134
  * serializer mutate its fields from here on (`announced`, `mirrorOwed`, …; the frame's receipt rides the leg's shared `Prepared.frameReceipts`). */
128
135
  gitStatusRef: GitStatusLaneRef;
@@ -1,11 +1,12 @@
1
1
  import { defaultPromptProvider, formatLocalDate, isValidTimeZone } from "../../prompts/default.js";
2
2
  import { isIsolated } from "../remote-env.js";
3
+ import { readDateAnchor } from "./announce-once-ledger.js";
3
4
  import { probeGitStatusLane } from "./git-status-frame.js";
4
5
  import { fenceMcpServerInstructions } from "./turn-attachments.js";
5
6
  const ULTRA_REASONING_TIERS = new Set(["xhigh", "max"]);
6
7
  import { compactionEnabled } from "../context-settings.js";
7
8
  export async function preparePromptInputs(input) {
8
- const { spec, deps, failClosedReason, lockedPreflight, selfOrchestrationActive, executionEnv, reminderMark, resolvedReadFace, resolvedWriteFace, toolFaceSnapshot, promptProfile, fableMitigations, internals, thinking, ownedEnv, delegation, resolvedRole, mcp, model, externalContentTargetActive, resume, handsEnabled, taskRootFinal, additionalRootsCanonical, additionalReadRootsCanonical, sessionId, advertisedWritableDirs } = input;
9
+ const { spec, deps, failClosedReason, lockedPreflight, selfOrchestrationActive, executionEnv, reminderMark, resolvedReadFace, resolvedWriteFace, toolFaceSnapshot, promptProfile, fableMitigations, internals, thinking, ownedEnv, delegation, resolvedRole, mcp, model, externalContentTargetActive, resume, handsEnabled, taskRootFinal, additionalRootsCanonical, additionalReadRootsCanonical, sessionId, session, advertisedWritableDirs } = input;
9
10
  const provider = spec.promptProvider ?? deps.promptProvider ?? defaultPromptProvider;
10
11
  const promptPolicyEnabled = Boolean(lockedPreflight.toolPolicy);
11
12
  const promptHooks = spec.hooks ?? deps.hooks;
@@ -37,7 +38,8 @@ export async function preparePromptInputs(input) {
37
38
  : undefined;
38
39
  const userTz = spec.clientContext?.timeZone;
39
40
  const tzValid = userTz !== undefined && isValidTimeZone(userTz);
40
- const envFacts = { date: formatLocalDate(new Date(), tzValid ? userTz : undefined), modelId: model.id };
41
+ const dateAnchor = await readDateAnchor(session);
42
+ const envFacts = { date: dateAnchor?.current?.prefixDate ?? formatLocalDate(new Date(), tzValid ? userTz : undefined), modelId: model.id };
41
43
  if (tzValid)
42
44
  envFacts.timeZone = userTz;
43
45
  if (spec.clientContext?.userEmail)
@@ -141,5 +143,5 @@ export async function preparePromptInputs(input) {
141
143
  taskRoot: taskRootFinal,
142
144
  onDegrade: (reason) => deps.onError?.(new Error(`env git snapshot degraded — ${reason}`), { phase: "degraded", sessionId, classification: "env-git-snapshot" }),
143
145
  });
144
- return { provider, featureFlags, userSystemPrompt, userAppendSystemPrompt, mcpInstructionsBlock, envFacts, tzValid, userTz, gitStatusRef };
146
+ return { provider, featureFlags, userSystemPrompt, userAppendSystemPrompt, mcpInstructionsBlock, envFacts, tzValid, userTz, dateAnchor, gitStatusRef };
145
147
  }
@@ -283,7 +283,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
283
283
  let shellGatedTools = new Set();
284
284
  refuseRequireExistingWithoutSession(spec);
285
285
  refuseRegistryScopeMismatch(spec, internals);
286
- const { acquired, session, conflictRef, wakeRecovered, resumeAtBeforeParentId } = await prepareAcquireReconcile({ sessions, spec, resume, toolEffects, ...(internals?.sessionPlacement !== undefined ? { placement: internals.sessionPlacement } : {}), ...(() => { const g = durableParkGapFor(deps, spec); return g !== undefined ? { durableParkGap: g } : {}; })() });
286
+ const { acquired, session, conflictRef, wakeRecovered } = await prepareAcquireReconcile({ sessions, spec, resume, toolEffects, ...(internals?.sessionPlacement !== undefined ? { placement: internals.sessionPlacement } : {}), ...(() => { const g = durableParkGapFor(deps, spec); return g !== undefined ? { durableParkGap: g } : {}; })() });
287
287
  const sessionId = acquired.sessionId;
288
288
  const onceLedger = await loadAnnounceOnceLedger(session, resume?.seed.announcedListings, deps.onError);
289
289
  const hostTaskId = spec.taskId ?? sessionId;
@@ -382,7 +382,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
382
382
  const ownedEnv = ownedEnvSeat.current;
383
383
  const { taskRootFinal, rebaseRestoredPath } = await prepareWorkspaceRestore({ ownedEnv, deps, spec, internals, resume, taskRootInitial, abortController, sessionId });
384
384
  const { canonical: taskRootCanonical } = await resolveTaskRootCanonical({ env: executionEnv, given: taskRootFinal, onNotice: deps.onNotice, sessionId, runId });
385
- const { trackFileEdit, fileHistoryBoundary, restoredFilePaths, rewindNotes, fileHistoryEnabled, historyScope, historyRoot, historyFs } = await prepareFileHistory({ deps, spec, internals, executionEnv, taskRootFinal, handsEnabled, sessionId, session, resumeAtBeforeParentId, abortController });
385
+ const { trackFileEdit, fileHistoryBoundary, restoredFilePaths, rewindNotes, fileHistoryEnabled, fileHistoryFact, historyScope, historyRoot, historyFs } = await prepareFileHistory({ deps, spec, internals, executionEnv, taskRootFinal, handsEnabled, sessionId, session, abortController });
386
386
  const { noteFileEdited, editedFilesSnapshot, harnessRef, toolProgressRef, skillScope, forwardEvent, reviewRequestRef, requestReview, stopRequestedRef, requestStopAfterTurn, planModeRef, enterPlanMode, subagentRetain, worktreeIsolation } = prepareRunRefs({ spec, internals, executionEnv, taskRootFinal });
387
387
  const legContextOverlay = () => runnerSelf?.contextOverlayOf?.(harnessRef.current?.getModelRef() ?? resolvedRole.modelRef);
388
388
  let resolvedReadFace;
@@ -521,6 +521,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
521
521
  governedProvenance: orgGovernedProvenance,
522
522
  },
523
523
  });
524
+ if (memoryEngineSession !== undefined)
525
+ rollback.push(() => memoryEngineSession.release());
524
526
  memoryAdmittedOrgScopesRef.current = memoryAdmittedOrgScopes;
525
527
  ownOrgVerdictRef.current = memoryOwnOrgVerdict ?? priorOwnOrgVerdict;
526
528
  if (internals?.ownOrgAdmissionRef !== undefined && ownOrgVerdictRef.current !== undefined) {
@@ -594,7 +596,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
594
596
  }
595
597
  const projectContext = await prepareProjectContext({ deps, spec, internals, session, sessionId, hostTaskId, taskRootFinal, handsEnabled, delegation, seedContextFiles, memoryBlockFromEngine, skillScope, roster, toolFaceSnapshot, onceLedger, memoryTools, memoryEngineSession, memorySearchToolsPlanned, memoryPairExcludedName, memoryPairOccupiedName, resume, skillListingBudget: doors.skillListingBudget });
596
598
  const { memoryBlock: memoryBlockWithProject, instructionSources, projectInstructionContent, hasSkillManifest, skillsListing, sharedMemoryPairMounted, memoryEnginePairMounted } = projectContext;
597
- const { provider, featureFlags, userSystemPrompt, userAppendSystemPrompt, mcpInstructionsBlock, envFacts, tzValid, userTz, gitStatusRef } = await preparePromptInputs({ spec, deps, contextOverlay: legContextOverlay(), failClosedReason: selfOrchestrationFailClosedReason(spec, deps), lockedPreflight, selfOrchestrationActive, executionEnv, reminderMark, resolvedReadFace, resolvedWriteFace, toolFaceSnapshot, promptProfile, fableMitigations, internals, thinking, ownedEnv, delegation, resolvedRole, mcp, model, externalContentTargetActive, resume, handsEnabled, taskRootFinal, additionalRootsCanonical, additionalReadRootsCanonical, sessionId, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}) });
599
+ const { provider, featureFlags, userSystemPrompt, userAppendSystemPrompt, mcpInstructionsBlock, envFacts, tzValid, userTz, dateAnchor, gitStatusRef } = await preparePromptInputs({ spec, deps, contextOverlay: legContextOverlay(), failClosedReason: selfOrchestrationFailClosedReason(spec, deps), lockedPreflight, selfOrchestrationActive, executionEnv, reminderMark, resolvedReadFace, resolvedWriteFace, toolFaceSnapshot, promptProfile, fableMitigations, internals, thinking, ownedEnv, delegation, resolvedRole, mcp, model, externalContentTargetActive, resume, handsEnabled, taskRootFinal, additionalRootsCanonical, additionalReadRootsCanonical, sessionId, session, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}) });
598
600
  const { deferred, memoryBlock, toolSearch: toolSearchPolicy } = prepareDeferClassify({ spec, deps, roster, toolFaceSnapshot, handsEnabled, fullShellReachable, promptProfile, model, mcp, a2a, sharedMemoryPairMounted, memoryEnginePairMounted, memoryBlock: memoryBlockWithProject, memoryRecallSegment, onceLedger, sessionId });
599
601
  prepareMemoryEngineSession({ memoryEngineSession, internals, memoryDelegationEvidence, externalContentTargetActive, lockedPreflight, mcp, sessionId, runId, deps, provenanceForChildrenRef, delegationSettlementRef, spec, envHandToolNames, roster, contentOriginWrapRef });
600
602
  const { systemPromptSeat, renderWithDate, systemBlocks, promptManifest, epochDeclaredSections, epochArtifactDigestForSnapshot, buildAssembleInputs, centerAdoptionRef } = await preparePromptAssembly({ session, deps, internals, resume, provider, userSystemPrompt, userAppendSystemPrompt, featureFlags, mcpInstructionsBlock, memoryBlock, envFacts, promptProfile, fableMitigations, tools, model, sessionId, reminderMark, onceLedger });
@@ -608,7 +610,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
608
610
  declaredCards: toolRoster.entries.flatMap((e) => e.renderHints?.resultCards ?? []),
609
611
  onUndeclared: (type) => deliverEngineNotice(deps.onNotice, { code: "config.tool_card_undeclared", message: `a tool minted a \`details.type\` card ${JSON.stringify(type)} its definition never declared — dropped from tool_end.structured (declare it on the definition's catalog row or the caller's renderHints.resultCards)`, detail: { type, sessionId, runId } }),
610
612
  });
611
- const { agentListing, announcedListingsRef, dateChange } = prepareListings({ spec, tools, onceLedger, sessionId, toolFaceSnapshot, activeTools, resume, skillsListing, listingBudget: doors.skillListingBudget, listingRideRef, renderWithDate, envFacts, tzValid, userTz });
613
+ const { agentListing, announcedListingsRef, dateChange } = prepareListings({ spec, tools, onceLedger, sessionId, toolFaceSnapshot, activeTools, resume, skillsListing, listingBudget: doors.skillListingBudget, listingRideRef, renderWithDate, envFacts, tzValid, userTz, dateAnchor });
612
614
  const lastBrainContextRef = {};
613
615
  const requestLossyRef = { current: false };
614
616
  const staleOffloadWrittenRefs = new Set();
@@ -707,7 +709,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
707
709
  }
708
710
  }
709
711
  const { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions } = await preparePolicyChain({ spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, lockedPreflight, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, shellGatedTools, envHandToolNames, handsCwdRef, readDenyMatcher, questionToolMounted, runtimeCaps, inheritedAncestorRules, ancestorSessionRulesRef, inheritedParentConstraints, seedInheritedGate, hasSkillManifest, skillScope, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef });
710
- const { onAsk, preToolContexts, blockedToolCalls, gateOutcomes, batchHaltRef, blockedTracked, incompleteSuspendAdapter, durableSuspendInfraReady, resourceSuspendEligible, usageGovernance, durableApproval, wiringManifest, writeProtectionCheck, parkLaneArmed, gateMachineryActive, hookIdentity, hookEnvFace, sessionBoxBound } = prepareWiringManifest({ spec, deps, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, effectivePolicy, handsEnabled, taskRootFinal, mcp, irreversibleTools, egressTools, shellReadStationArmed: shellReadCheck !== undefined, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, lockedPreflight, peerLaneRefs, frameSettlement, harness, pausedRef, frozenOnAsk, liveInheritedGate, delegation, toolRoster, readDenyBuiltinTiersActive: handsReadFace.readDenyBuiltinTiersActive, lspManagerWired, lspDiagnosticsLive: lspDiagnostics !== undefined, sessionBox });
712
+ const { onAsk, preToolContexts, blockedToolCalls, gateOutcomes, batchHaltRef, blockedTracked, incompleteSuspendAdapter, durableSuspendInfraReady, resourceSuspendEligible, usageGovernance, durableApproval, wiringManifest, writeProtectionCheck, parkLaneArmed, gateMachineryActive, hookIdentity, hookEnvFace, sessionBoxBound } = prepareWiringManifest({ spec, deps, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, effectivePolicy, handsEnabled, taskRootFinal, mcp, irreversibleTools, egressTools, shellReadStationArmed: shellReadCheck !== undefined, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, lockedPreflight, peerLaneRefs, frameSettlement, harness, pausedRef, frozenOnAsk, liveInheritedGate, delegation, toolRoster, readDenyBuiltinTiersActive: handsReadFace.readDenyBuiltinTiersActive, lspManagerWired, lspDiagnosticsLive: lspDiagnostics !== undefined, fileHistoryFact, sessionBox });
711
713
  if (fileHistoryBoundary !== undefined) {
712
714
  harness.on("tool_call", async () => {
713
715
  await fileHistoryBoundary.settle();
@@ -118,6 +118,7 @@ export interface PrepareWiringManifestInput {
118
118
  readDenyBuiltinTiersActive: readonly string[];
119
119
  /** borrowed-readonly — from the LSP phase's manager fold: gates the manifest's LSP section (absent ⇒ byte-identical manifest). */ lspManagerWired: boolean;
120
120
  /** borrowed-readonly — from the LSP phase: is the diagnostics lane live? The `lane` word's only input. */ lspDiagnosticsLive: boolean;
121
+ /** borrowed-readonly — from the file-history phase's one fold of the two off switches: the manifest's word, relayed. */ fileHistoryFact: NonNullable<WiringManifest["fileHistory"]>;
121
122
  /** borrowed-readonly — the arming outcome the caps phase minted (the manifest's auto-mode face). */
122
123
  autoModeArmReason: AutoModeArmFact;
123
124
  /** borrowed-readonly — the frozen preflight; only `mcp` (the declared servers the manifest face joins). */
@@ -116,7 +116,7 @@ function announceDurableGateUnavailable(args) {
116
116
  });
117
117
  }
118
118
  export function prepareWiringManifest(input) {
119
- const { spec, deps, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, effectivePolicy, handsEnabled, taskRootFinal, mcp, irreversibleTools, egressTools, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, lockedPreflight, peerLaneRefs, frameSettlement, sessionBox, harness, pausedRef, frozenOnAsk, liveInheritedGate, delegation, toolRoster, readDenyBuiltinTiersActive, lspManagerWired, shellReadStationArmed, lspDiagnosticsLive } = input;
119
+ const { spec, deps, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, effectivePolicy, handsEnabled, taskRootFinal, mcp, irreversibleTools, egressTools, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, lockedPreflight, peerLaneRefs, frameSettlement, sessionBox, harness, pausedRef, frozenOnAsk, liveInheritedGate, delegation, toolRoster, readDenyBuiltinTiersActive, lspManagerWired, shellReadStationArmed, lspDiagnosticsLive, fileHistoryFact } = input;
120
120
  const onAsk = spec.onAsk ?? deps.onAsk;
121
121
  const handWriteTools = toolRoster.entries.filter((e) => e.mountedBy === "hands" && (e.effect === "write" || e.effect === "idempotent")).map((e) => e.name);
122
122
  const hasEffectAwareGate = Boolean(policyLayers.length > 0 || (hooks?.preToolUse !== undefined && !preToolUseObservational));
@@ -218,6 +218,7 @@ export function prepareWiringManifest(input) {
218
218
  tools: toolRoster,
219
219
  hooks: wiredHookEntries(hooks, spec.hooks !== undefined ? "spec" : "deps", preToolUseObservational),
220
220
  ...(lspManagerWired ? { lsp: deriveLspSeam(toolRoster.entries.some((e) => e.contract.contractId === LSP_CONTRACT_ID), lspDiagnosticsLive) } : {}),
221
+ fileHistory: fileHistoryFact,
221
222
  });
222
223
  peerLaneRefs.askEffective = wiringManifest.ask.effective;
223
224
  const sessionBoxBound = sessionBox !== undefined && internals?.onTaskNotification !== undefined ? sessionBox : undefined;
@@ -14,7 +14,7 @@ export async function runAttachmentSeats(input) {
14
14
  const listingsLive = (rs.attach.agentListingOn && prepared.agentListing !== undefined) || (rs.attach.skillsListingOn && prepared.skillsListing !== undefined);
15
15
  const backgroundTasksLive = (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true;
16
16
  rs.attach.attachState = rs.attach.attachmentsCfg !== undefined || listingsLive || backgroundTasksLive ? createAttachmentState() : undefined;
17
- rs.attach.dateState = prepared.dateChange !== undefined ? { announcedDate: prepared.dateChange.legDate } : undefined;
17
+ rs.attach.dateState = prepared.dateChange !== undefined ? { announcedDate: prepared.dateChange.anchor?.current?.announcedDate ?? prepared.dateChange.legDate } : undefined;
18
18
  rs.attach.instrProbe = runner.deps.probeInstructionSources;
19
19
  rs.attach.instrState =
20
20
  rs.attach.instrProbe !== undefined && prepared.instructionSources !== undefined && prepared.instructionSources.length > 0
@@ -30,7 +30,7 @@ import { nextHumanInputSeq } from "./steer-admission.js";
30
30
  import { settleTeardownLeg } from "./teardown-bounded.js";
31
31
  import { toolEndBodyFrom, toolResultMsg } from "./tool-end-body.js";
32
32
  import { announceListingTruncation } from "../skill-listing-budget.js";
33
- import { attachmentEnvelopeTags, commitAgentListing, commitSkillsListing, reduceToolEnd, renderAgentListingFrame, renderOrphanedBackgroundTasks, renderSkillsListingFrame, stampWriteAnchor } from "./turn-attachments.js";
33
+ import { attachmentEnvelopeTags, collectDateChange, commitAgentListing, commitSkillsListing, reduceToolEnd, renderAgentListingFrame, renderOrphanedBackgroundTasks, renderSkillsListingFrame, stampWriteAnchor } from "./turn-attachments.js";
34
34
  import { flushOwedAnnouncedSnapshot } from "./announce-once-ledger.js";
35
35
  import { asThrownError, thrownMarksOf, thrownText } from "../thrown-value.js";
36
36
  import { ruleSourcePhrase } from "../rule-denial-sentence.js";
@@ -235,22 +235,6 @@ export async function runLeg(input) {
235
235
  ...ident(),
236
236
  });
237
237
  }
238
- if (resume !== undefined)
239
- resume.decisionDelivered = true;
240
- if (gitResumeDelivered !== undefined) {
241
- const delivered = gitResumeDelivered;
242
- prepared.gitStatusRef.protectedText = delivered.wrapped;
243
- prepared.frameReceipts.pending.push({
244
- source: "git_status",
245
- text: continuation,
246
- commit: (entryId) => {
247
- prepared.gitStatusRef.announced = { kind: delivered.kind, hash: delivered.hash, entryId };
248
- queue.push({ type: "steering_injected", source: "git_status", preview: GIT_STATUS_ECHO_PREVIEW[delivered.kind], ...ident() });
249
- rs.attach.attachmentsInjected += 1;
250
- prepared.gitStatusRef.mirrorOwed = { kind: delivered.kind, hash: delivered.hash, entryId };
251
- },
252
- });
253
- }
254
238
  const pending = resume.cp.pendingAction;
255
239
  const rejectionHalt = pending.kind === "tool_approval" && resume.outcome.gate === "policy_ask"
256
240
  ? maybeHumanRejectionHalt({ gate: resume.outcome.gateOutcome, isDelegatedChild, toolName: pending.toolName, toolCallId: pending.toolCallId })
@@ -258,18 +242,36 @@ export async function runLeg(input) {
258
242
  if (rejectionHalt !== undefined && pending.kind === "tool_approval")
259
243
  prepared.batchHaltRef.current = { rejectedToolCallId: pending.toolCallId };
260
244
  prepared.undeliveredContinuationRef.current = { parkedSteers: resumeFrames.map(({ entry }) => entry).filter((e) => !withheldSteerIds.has(e.inputId)), orphanTasks };
261
- final = await withBrainSinks(() => prepared.harness.prompt(continuation, {
262
- engineSegments,
263
- settleUnansweredToolCalls: rejectionHalt !== undefined ? { halt: rejectionHalt } : {},
264
- onDelivered: () => {
265
- prepared.undeliveredContinuationRef.current = { parkedSteers: [], orphanTasks: [] };
266
- for (const e of appliedEvents)
267
- queue.push(e);
268
- },
269
- }));
270
- await flushGitMirror();
271
- if (prepared.gitStatusRef.announced?.pending === true)
272
- await prepared.gitStatusRef.reassert?.();
245
+ if (!prepared.abortController.signal.aborted) {
246
+ if (resume !== undefined)
247
+ resume.decisionDelivered = true;
248
+ if (gitResumeDelivered !== undefined) {
249
+ const delivered = gitResumeDelivered;
250
+ prepared.gitStatusRef.protectedText = delivered.wrapped;
251
+ prepared.frameReceipts.pending.push({
252
+ source: "git_status",
253
+ text: continuation,
254
+ commit: (entryId) => {
255
+ prepared.gitStatusRef.announced = { kind: delivered.kind, hash: delivered.hash, entryId };
256
+ queue.push({ type: "steering_injected", source: "git_status", preview: GIT_STATUS_ECHO_PREVIEW[delivered.kind], ...ident() });
257
+ rs.attach.attachmentsInjected += 1;
258
+ prepared.gitStatusRef.mirrorOwed = { kind: delivered.kind, hash: delivered.hash, entryId };
259
+ },
260
+ });
261
+ }
262
+ final = await withBrainSinks(() => prepared.harness.prompt(continuation, {
263
+ engineSegments,
264
+ settleUnansweredToolCalls: rejectionHalt !== undefined ? { halt: rejectionHalt } : {},
265
+ onDelivered: () => {
266
+ prepared.undeliveredContinuationRef.current = { parkedSteers: [], orphanTasks: [] };
267
+ for (const e of appliedEvents)
268
+ queue.push(e);
269
+ },
270
+ }));
271
+ await flushGitMirror();
272
+ if (prepared.gitStatusRef.announced?.pending === true)
273
+ await prepared.gitStatusRef.reassert?.();
274
+ }
273
275
  }
274
276
  }
275
277
  else {
@@ -339,6 +341,10 @@ export async function runLeg(input) {
339
341
  }
340
342
  }
341
343
  const firstFrames = [];
344
+ const legToday = rs.attach.dateState !== undefined ? prepared.dateChange.today() : undefined;
345
+ const dateFrame = legToday !== undefined ? collectDateChange(rs.attach.dateState, legToday) : undefined;
346
+ if (dateFrame !== undefined)
347
+ firstFrames.push({ source: "date_change", body: dateFrame.body, commit: () => void (rs.attach.dateState.announcedDate = legToday), disclose: () => undefined });
342
348
  if (rs.attach.attachState !== undefined) {
343
349
  const attach = rs.attach.attachState;
344
350
  if (rs.attach.agentListingOn && prepared.agentListing !== undefined) {
@@ -350,6 +356,7 @@ export async function runLeg(input) {
350
356
  body: frame.text,
351
357
  commit: () => {
352
358
  commitAgentListing(attach, listing.entries, listing.models);
359
+ prepared.frameReceipts.listingSnapshotOwed = true;
353
360
  prepared.announcedListingsRef.agents = listing.entries.map((e) => e.name);
354
361
  if (listing.models !== undefined)
355
362
  prepared.announcedListingsRef.models = [...listing.models];
@@ -367,15 +374,16 @@ export async function runLeg(input) {
367
374
  body: frame.text,
368
375
  commit: () => {
369
376
  commitSkillsListing(attach, listing.entries);
377
+ prepared.frameReceipts.listingSnapshotOwed = true;
370
378
  prepared.announcedListingsRef.skills = listing.entries.map((e) => e.name);
371
379
  },
372
380
  disclose: () => announceListingTruncation(runner.deps.onNotice, { listing: "skills", total: frame.listed, nameOnly: frame.nameOnly, dropped: frame.dropped, budget: listing.budget, sessionId: prepared.sessionId }),
373
381
  });
374
382
  }
375
383
  }
376
- if (firstFrames.length > 0) {
377
- effectiveObjective = `${firstFrames.map((f) => mintSystemReminder(sanitizeUntrustedText(f.body, attachmentEnvelopeTags(f.source)), prepared.reminderMark)).join("\n")}\n${effectiveObjective}`;
378
- }
384
+ }
385
+ if (firstFrames.length > 0) {
386
+ effectiveObjective = `${firstFrames.map((f) => mintSystemReminder(sanitizeUntrustedText(f.body, attachmentEnvelopeTags(f.source)), prepared.reminderMark)).join("\n")}\n${effectiveObjective}`;
379
387
  }
380
388
  if (composeApprovalPostureDirective(prepared.approvalPosture) !== undefined) {
381
389
  const postureDirective = composeApprovalPostureDirective(prepared.approvalPosture, await statedReminderBodiesOf(prepared.session, prepared.reminderMark));
@@ -451,32 +459,33 @@ export async function runLeg(input) {
451
459
  f.commit();
452
460
  queue.push({ type: "steering_injected", source: f.source, preview: redactThenCut(f.body, 220), ...ident() });
453
461
  rs.attach.attachmentsInjected += 1;
454
- prepared.frameReceipts.listingSnapshotOwed = true;
455
462
  f.disclose();
456
463
  },
457
464
  });
458
465
  }
459
466
  const enginePrefixChars = effectiveObjective.length - projectedObjective.length;
460
- queue.push({
461
- ...buildHumanInputEvent({
462
- carrier: "objective",
463
- source: "objective",
464
- delivery: "applied",
465
- sessionSeq: nextHumanInputSeq(prepared.harness),
467
+ if (!prepared.abortController.signal.aborted) {
468
+ queue.push({
469
+ ...buildHumanInputEvent({
470
+ carrier: "objective",
471
+ source: "objective",
472
+ delivery: "applied",
473
+ sessionSeq: nextHumanInputSeq(prepared.harness),
474
+ ...(objectiveActor !== undefined ? { actor: objectiveActor } : {}),
475
+ ...(objectiveActor?.issuer !== undefined ? { issuer: objectiveActor.issuer } : {}),
476
+ ...(spec.principal !== undefined ? { principal: spec.principal } : {}),
477
+ }),
478
+ ...ident(),
479
+ });
480
+ final = await withBrainSinks(() => prepared.harness.prompt(effectiveObjective, {
481
+ ...(images && images.length > 0 ? { images } : {}),
482
+ ...(enginePrefixChars > 0 ? { enginePrefixChars } : {}),
466
483
  ...(objectiveActor !== undefined ? { actor: objectiveActor } : {}),
467
- ...(objectiveActor?.issuer !== undefined ? { issuer: objectiveActor.issuer } : {}),
468
- ...(spec.principal !== undefined ? { principal: spec.principal } : {}),
469
- }),
470
- ...ident(),
471
- });
472
- final = await withBrainSinks(() => prepared.harness.prompt(effectiveObjective, {
473
- ...(images && images.length > 0 ? { images } : {}),
474
- ...(enginePrefixChars > 0 ? { enginePrefixChars } : {}),
475
- ...(objectiveActor !== undefined ? { actor: objectiveActor } : {}),
476
- }));
477
- await flushGitMirror();
478
- if (prepared.gitStatusRef.announced?.pending === true)
479
- await prepared.gitStatusRef.reassert?.();
484
+ }));
485
+ await flushGitMirror();
486
+ if (prepared.gitStatusRef.announced?.pending === true)
487
+ await prepared.gitStatusRef.reassert?.();
488
+ }
480
489
  }
481
490
  }
482
491
  loopLatch.ended = true;
@@ -19,6 +19,7 @@ import { resumeDecisionWasNegative } from "./decide-continuation.js";
19
19
  import { drainForwardedFramesBeforeDone, forwardsSubagentEvents } from "./prepare-run-refs.js";
20
20
  import { generatePromptSuggestions } from "./prompt-suggestions.js";
21
21
  import { settleTeardownLeg } from "./teardown-bounded.js";
22
+ import { owedDateAnchor } from "./announce-once-ledger.js";
22
23
  import { autoRunConsolidationFromRecommendation, autoRunOnRecommendationEffective, scopeHasUnconsumedRecommendation } from "./memory-consolidation-driver.js";
23
24
  import { amendTerminal, terminalProjection } from "./terminal-projection.js";
24
25
  import { mintCompactedEvent } from "../auto-compaction.js";
@@ -293,6 +294,9 @@ export async function runSettleAndTeardown(input) {
293
294
  }
294
295
  finally {
295
296
  settleOwedUserInput(owedUserInput, !prepared.suspendProgressRef.executedApproved, { taskId: spec.taskId ?? prepared.sessionId, sessionId: prepared.sessionId, runId: prepared.runId }, runner.deps.onNotice);
297
+ const writeDateAnchor = owedDateAnchor(prepared.session, prepared.dateChange, rs.attach.dateState);
298
+ if (writeDateAnchor !== undefined)
299
+ await settleTeardownLeg(writeDateAnchor, "date anchor (settle leg)", (e) => runner.deps.onError?.(e, { phase: "degraded", sessionId: prepared.sessionId }));
296
300
  const ownedEnvDying = prepared.ownedEnv !== undefined &&
297
301
  hasDestroy(prepared.ownedEnv) &&
298
302
  isIsolated(prepared.ownedEnv) &&
@@ -211,7 +211,7 @@ export function createTurnBoundary(input) {
211
211
  }
212
212
  let boundaryAttachmentBytes = 0;
213
213
  let attachmentsPayload;
214
- const listingCommits = [];
214
+ const receiptCommits = [];
215
215
  if ((rs.attach.dateState !== undefined ||
216
216
  rs.attach.instrState !== undefined ||
217
217
  rs.attach.sizeGuidelineState !== undefined ||
@@ -382,7 +382,7 @@ export function createTurnBoundary(input) {
382
382
  if (agentListingDue && due.some((a) => a.source === "agent_listing")) {
383
383
  const listing = prepared.agentListing;
384
384
  const frame = renderAgentListingFrame(attachState, listing.entries, listing.toolName, listing.models, listing.budget);
385
- listingCommits.push({
385
+ receiptCommits.push({
386
386
  source: "agent_listing",
387
387
  commit: () => {
388
388
  commitAgentListing(attachState, listing.entries, listing.models);
@@ -397,7 +397,7 @@ export function createTurnBoundary(input) {
397
397
  if (skillsListingDue && due.some((a) => a.source === "skills_listing")) {
398
398
  const listing = prepared.skillsListing;
399
399
  const frame = renderSkillsListingFrame(attachState, listing.entries, listing.budget);
400
- listingCommits.push({
400
+ receiptCommits.push({
401
401
  source: "skills_listing",
402
402
  commit: () => {
403
403
  commitSkillsListing(attachState, listing.entries);
@@ -410,12 +410,12 @@ export function createTurnBoundary(input) {
410
410
  if (announcedOnceDirty)
411
411
  prepared.frameReceipts.listingSnapshotOwed = true;
412
412
  }
413
- if (rs.attach.dateState !== undefined && rs.turn.lastTurnHadToolCalls) {
413
+ if (rs.attach.dateState !== undefined && rs.turn.lastTurnHadToolCalls && !prepared.frameReceipts.pending.some((r) => r.source === "date_change")) {
414
414
  const today = prepared.dateChange.today();
415
415
  const cand = collectDateChange(rs.attach.dateState, today);
416
416
  if (cand !== undefined) {
417
417
  due.unshift(cand);
418
- rs.attach.dateState.announcedDate = today;
418
+ receiptCommits.push({ source: "date_change", commit: () => void (rs.attach.dateState.announcedDate = today) });
419
419
  }
420
420
  }
421
421
  if (rs.attach.sizeGuidelineState !== undefined && rs.turn.lastTurnHadToolCalls) {
@@ -496,7 +496,7 @@ export function createTurnBoundary(input) {
496
496
  }
497
497
  if ((attachmentsPayload !== undefined || batchContextBlock !== undefined) && prepared.batchHaltRef.current === undefined) {
498
498
  const payload = attachmentsPayload !== undefined && batchContextBlock !== undefined ? `${attachmentsPayload}\n${batchContextBlock}` : (attachmentsPayload ?? batchContextBlock);
499
- for (const c of listingCommits)
499
+ for (const c of receiptCommits)
500
500
  prepared.frameReceipts.pending.push({ source: c.source, text: payload, commit: c.commit });
501
501
  void prepared.harness.steer(payload, { engineMinted: true }).catch(() => {
502
502
  prepared.frameReceipts.pending = prepared.frameReceipts.pending.filter((r) => r.text !== payload);
@@ -309,10 +309,10 @@ export interface AttachmentState {
309
309
  * A1-R — the date_change lane's OWN state, deliberately NOT an
310
310
  * {@link AttachmentState} member: allocating that for a date-only run would activate the cadence
311
311
  * content scan + projection reducers on every non-opted run (the reopen's exact finding). Two
312
- * fields, zero reducers. `announcedDate` starts at the leg's frozen prefix date; the RUN LOOP
313
- * commits it (the producer below is pure) — the date block rides ABOVE the 8KB bundle cap as a
314
- * fixed ~140-byte item, so no clipping face exists and commit-at-append is sound (contrast
315
- * tools_delta's drain-on-intact, which lives inside the capped bundle).
312
+ * fields, zero reducers. `announcedDate` starts at the session anchor's announced date; the RUN LOOP
313
+ * commits it (the producer below is pure) on the carrying message's receipt — the date block rides ABOVE
314
+ * the 8KB bundle cap as a fixed ~140-byte item, so no clipping face exists (contrast tools_delta's
315
+ * drain-on-intact, which lives inside the capped bundle).
316
316
  */
317
317
  export interface DateChangeState {
318
318
  announcedDate: string;
@@ -663,11 +663,11 @@ export declare function renderToolSearchUsageReminder(undiscovered: readonly str
663
663
  export declare function renderDateChange(newDate: string): string;
664
664
  /**
665
665
  * A1-R — the date_change producer (CC attachments.ts:1400-1443 parity): the system-prefix
666
- * date is FROZEN for the leg (upstream: "cached for the duration of the conversation" — re-rendering
666
+ * date is FROZEN for the session segment (upstream: "cached for the duration of the conversation" — re-rendering
667
667
  * the prefix at midnight costs ~920K effective tokens per overnight session, upstream's own comment);
668
668
  * when the boundary-time LOCAL date walks away from the last announced one, the model is told at the
669
669
  * TAIL instead. String INEQUALITY (backward NTP jumps announce too). PURE — the caller commits
670
- * `state.announcedDate = today` when it actually appends the attachment (see {@link DateChangeState}).
670
+ * `state.announcedDate = today` when the carrying message's receipt settles (see {@link DateChangeState}).
671
671
  */
672
672
  export declare function collectDateChange(state: DateChangeState, today: string): TurnAttachment | undefined;
673
673
  /**
@@ -520,7 +520,7 @@ export interface RunnerDeps {
520
520
  /**
521
521
  * design/381 — the per-edited-file rewind history backend (CC FileHistory topology; replaces the
522
522
  * E19 whole-tree `fileSnapshotStore`). WIRING IS THE OPT-IN (DV-9, the reference's own SDK
523
- * posture): with a store present, every Edit/Write/NotebookEdit first-touch is backed up before
523
+ * posture): with a store present (and {@link fileHistoryCapture} not `"off"`), every Edit/Write/NotebookEdit first-touch is backed up before
524
524
  * the byte changes, every turn mints a history boundary keyed by its own initial session entry
525
525
  * ("file state when this turn began"), and a {@link TaskSpec.resumeAt}+{@link TaskSpec.restoreFiles}
526
526
  * (or {@link TaskSpec.rewindFilesTo}) run converges the tracked set back to a boundary. Omitted ⇒
@@ -534,6 +534,8 @@ export interface RunnerDeps {
534
534
  * key earns on a filesystem-capable run (`classification: "file-history-no-store"`).
535
535
  */
536
536
  fileHistoryStore?: import("./file-history-store.js").FileHistoryStore | "disabled";
537
+ /** The deployment's CAPTURE switch, apart from the store seat: `"off"` (the only word, and only beside a wired store; anything else refuses prepare, `config.invalid_file_history_capture`) ⇒ no run records — no first-touch record, no turn boundary, no fork carry, no liveness advisory, manifest `fileHistory: "disabled"` — while a wired {@link fileHistoryStore} still serves {@link TaskSpec.restoreFiles} / {@link TaskSpec.rewindFilesTo} from what it already holds. */
538
+ fileHistoryCapture?: "off";
537
539
  /**
538
540
  * design/381 DV-14 (终审落锤) — what an Edit/Write/NotebookEdit does when its FIRST-TOUCH history
539
541
  * record cannot be persisted (`FileHistoryStore.trackEdit` answered `{ok:false}` = NO durable
@@ -17,8 +17,9 @@
17
17
  * per-adapter concern (each exec env owns how it composes its child env); only the secret PATTERN is shared.
18
18
  */
19
19
  import type { RedactionConfidence, RedactionSource } from "./arg-summary.js";
20
+ import { type EngineNotice } from "./engine-notice.js";
20
21
  /** Which rule classified a key as secret-shaped — the env arm's `kind` vocabulary. */
21
- export type SecretEnvFindingKind = "suffix-rule" | "exact-name";
22
+ export type SecretEnvFindingKind = "suffix-rule" | "namespace-rule" | "exact-name";
22
23
  /** One env entry the `scrub` policy dropped: the key (the VALUE is never carried — this record is meant
23
24
  * to be loggable), the rule that classified it, how strong that claim is, and the minting module. */
24
25
  export interface SecretEnvFinding {
@@ -42,3 +43,13 @@ export declare function isSecretEnvKey(key: string): boolean;
42
43
  * dropped and why, read it back after the call. Omitting it leaves this function byte-for-byte what it
43
44
  * was before the channel existed. */
44
45
  export declare function scrubSecretEnv(env: NodeJS.ProcessEnv, findings?: SecretEnvFinding[]): NodeJS.ProcessEnv;
46
+ /** Test seam: never called by production code. */
47
+ export declare function __resetSecretEnvScrubNoticeAnnouncement(): void;
48
+ /**
49
+ * The ONE disclosure point for a scrub's withheld keys. Routed through {@link deliverEngineNotice} — the
50
+ * engine's one notice throat — so a wired `onNotice` seat REPLACES the console line exactly as every
51
+ * other `EngineNotice` does, and an absent seat prints the same words `console.warn` always printed here.
52
+ * `detail.names` carries every withheld key. Once per PROCESS, never per call or per site; an EMPTY
53
+ * findings array is a no-op.
54
+ */
55
+ export declare function announceSecretEnvScrub(onNotice: ((notice: EngineNotice) => void) | undefined, findings: readonly SecretEnvFinding[]): void;
@@ -1,5 +1,8 @@
1
+ import { deliverEngineNotice } from "./engine-notice.js";
1
2
  const TOKEN_COUNT_WORDS = ["MAX", "OUTPUT", "INPUT", "THINKING", "NUM", "TOTAL"];
2
- const SECRET_ENV_RE = new RegExp(`(^|_)(KEY|KEYS|TOKEN|(?<!(?:^|_)(?:${TOKEN_COUNT_WORDS.join("|")})_)TOKENS|SECRET|SECRETS|PASSWORD|PASSWORDS|PASSWD|CREDENTIAL|CREDENTIALS|PRIVATEKEY|APIKEY|ACCESS_KEY|SESSION_TOKEN)(?:_(?:\\d+|ID))?$`, "i");
3
+ const CREDENTIAL_WORD = `KEY|KEYS|TOKEN|(?<!(?:^|_)(?:${TOKEN_COUNT_WORDS.join("|")})_)TOKENS|SECRET|SECRETS|PASSWORD|PASSWORDS|PASSWD|CREDENTIAL|CREDENTIALS|PRIVATEKEY|APIKEY`;
4
+ const SECRET_ENV_RE = new RegExp(`(^|_)(${CREDENTIAL_WORD})(?:_(?:\\d+|ID))?$`, "i");
5
+ const SEMA_NAMESPACE_SECRET_RE = new RegExp(`^SEMA_(?:.*_)?(?:${CREDENTIAL_WORD})(?:_|$)`, "is");
3
6
  const SECRET_ENV_EXACT_NAMES = new Set([
4
7
  "DATABASE_URL", "CONNECTION_STRING", "DSN", "NPM_AUTH",
5
8
  "PGPASSWORD", "MYSQL_PWD", "SSHPASS", "GPG_PASSPHRASE", "GH_PAT", "GITHUB_PAT",
@@ -7,10 +10,15 @@ const SECRET_ENV_EXACT_NAMES = new Set([
7
10
  ]);
8
11
  const NON_SECRET_ENV_EXACT_NAMES = new Set([
9
12
  "PARTITION_KEY", "SORT_KEY", "RANGE_KEY", "IDEMPOTENCY_KEY", "CACHE_KEY", "ROUTING_KEY",
13
+ "DB_FOREIGN_KEYS", "JSON_SORT_KEYS",
14
+ "MAX_NEW_TOKENS", "MAX_COMPLETION_TOKENS", "AIDER_MAP_TOKENS", "AIDER_MAX_CHAT_HISTORY_TOKENS",
15
+ "MAX_BATCH_PREFILL_TOKENS", "MAX_WAITING_TOKENS", "MAX_TOP_N_TOKENS", "RATE_LIMIT_TOKENS",
16
+ "PROMPT_TOKENS", "CONTEXT_TOKENS",
10
17
  ]);
11
18
  const SECRET_ENV_KIND_CONFIDENCE = {
12
19
  "exact-name": "high",
13
20
  "suffix-rule": "medium",
21
+ "namespace-rule": "medium",
14
22
  };
15
23
  export function classifyExactEnvName(key, credentialNames, exemptNames) {
16
24
  const upper = key.toUpperCase();
@@ -26,7 +34,9 @@ function classifySecretEnvKey(key) {
26
34
  return "exact-name";
27
35
  if (byExactName === "exempt")
28
36
  return undefined;
29
- return SECRET_ENV_RE.test(key) ? "suffix-rule" : undefined;
37
+ if (SECRET_ENV_RE.test(key))
38
+ return "suffix-rule";
39
+ return SEMA_NAMESPACE_SECRET_RE.test(key) ? "namespace-rule" : undefined;
30
40
  }
31
41
  export function isSecretEnvKey(key) {
32
42
  return classifySecretEnvKey(key) !== undefined;
@@ -45,3 +55,18 @@ export function scrubSecretEnv(env, findings) {
45
55
  }
46
56
  return out;
47
57
  }
58
+ let secretEnvScrubNoticeAnnounced = false;
59
+ export function __resetSecretEnvScrubNoticeAnnouncement() {
60
+ secretEnvScrubNoticeAnnounced = false;
61
+ }
62
+ export function announceSecretEnvScrub(onNotice, findings) {
63
+ if (findings.length === 0 || secretEnvScrubNoticeAnnounced)
64
+ return;
65
+ secretEnvScrubNoticeAnnounced = true;
66
+ const names = findings.map((f) => f.key);
67
+ deliverEngineNotice(onNotice, {
68
+ code: "config.secret_env_scrubbed",
69
+ message: `secret_env_scrubbed: ${findings.length} env key(s) withheld from a spawned child's environment (${names.join(", ")}) — once per process; wire onNotice for the structured form`,
70
+ detail: { names },
71
+ });
72
+ }
@@ -50,6 +50,7 @@ export declare const EXPECTED_LISTBYSCOPE_SUMMARIES: ({
50
50
  toolCallId: string;
51
51
  toolName: string;
52
52
  toolInput: string;
53
+ mandated: boolean;
53
54
  origin: string;
54
55
  spentMicroUsd?: undefined;
55
56
  } | {
@@ -66,5 +67,6 @@ export declare const EXPECTED_LISTBYSCOPE_SUMMARIES: ({
66
67
  toolCallId?: undefined;
67
68
  toolName?: undefined;
68
69
  toolInput?: undefined;
70
+ mandated?: undefined;
69
71
  origin?: undefined;
70
72
  })[];
@@ -343,6 +343,26 @@ export async function checkpointStoreContract(make, runAssertion) {
343
343
  assert.equal("previewWithheld" in byId.get("pw-absent"), false, "absent must project the key OMITTED, not null/empty");
344
344
  assert.equal("previewWithheld" in byId.get("pw-bogus"), false, "an out-of-contract spelling must read as absent, never project verbatim");
345
345
  });
346
+ run("#1079 mandated presence bit: a row's `true` survives + projects; absent stays absent; an out-of-contract `false` or string reads as absent", async () => {
347
+ const store = make();
348
+ const asserted = createCheckpointFixture({ token: mintCheckpointToken(), sessionId: "md-true" });
349
+ asserted.pendingAction.mandated = true;
350
+ const absent = createCheckpointFixture({ token: mintCheckpointToken(), sessionId: "md-absent" });
351
+ const falsed = createCheckpointFixture({ token: mintCheckpointToken(), sessionId: "md-false" });
352
+ falsed.pendingAction.mandated = false;
353
+ const stringed = createCheckpointFixture({ token: mintCheckpointToken(), sessionId: "md-string" });
354
+ stringed.pendingAction.mandated = "yes";
355
+ for (const cp of [asserted, absent, falsed, stringed])
356
+ await store.put(cp.token, cp);
357
+ const back = (await store.get(asserted.token)).pendingAction;
358
+ assert.equal(back.mandated, true, "the row's mandated bit must survive the round-trip verbatim");
359
+ assert.equal("mandated" in (await store.get(absent.token)).pendingAction, false, "an absent bit must stay OMITTED on the row");
360
+ const byId = new Map((await store.listByScope("tenant-a")).map((s) => [s.sessionId, s]));
361
+ assert.equal(byId.get("md-true")?.mandated, true);
362
+ assert.equal("mandated" in byId.get("md-absent"), false, "absent must project the key OMITTED, not null/false");
363
+ assert.equal("mandated" in byId.get("md-false"), false, "a written false is out-of-contract and must read as absent, never project");
364
+ assert.equal("mandated" in byId.get("md-string"), false, "a non-boolean spelling must read as absent, never project verbatim");
365
+ });
346
366
  await settle();
347
367
  }
348
368
  const sortByToken = (a, b) => a.token.localeCompare(b.token);
@@ -367,6 +387,7 @@ export async function checkpointListByScopeSummaries(store) {
367
387
  pendingAction: {
368
388
  kind: "tool_approval",
369
389
  origin: "policy",
390
+ mandated: true,
370
391
  toolCallId: "call-pr",
371
392
  toolName: "open_pr",
372
393
  args: { repo: "x" },
@@ -406,6 +427,7 @@ export const EXPECTED_LISTBYSCOPE_SUMMARIES = [
406
427
  toolCallId: "call-pr",
407
428
  toolName: "open_pr",
408
429
  toolInput: '{"repo":"x"}',
430
+ mandated: true,
409
431
  origin: "policy",
410
432
  },
411
433
  {
@@ -402,7 +402,7 @@ export interface TaskResult {
402
402
  * ignores the field is unchanged. A rewind that could NOT be delivered as asked is NOT a note — it is a
403
403
  * terminal failure (`rewind_snapshot.unresolvable` / `rewind.store_unconfigured` /
404
404
  * `rewind.restore_failed` / `rewind.invalid_spec` / `rewind.rewind_files_retired` /
405
- * `rewind.conflicting_targets` / `rewind.child_scope_unsupported`).
405
+ * `rewind.conflicting_targets` / `rewind.child_scope_unsupported` / `rewind.capture_disabled`).
406
406
  *
407
407
  * - `conversation_only` — {@link TaskSpec.resumeAt} branched the transcript WITHOUT
408
408
  * {@link TaskSpec.restoreFiles}, so the working tree was deliberately left where it was (CC's