@sema-agent/core 7.21.1 → 7.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/CHANGELOG.md +290 -1
  2. package/dist/agents/send-message-tool.js +1 -0
  3. package/dist/agents/subagent.d.ts +1 -0
  4. package/dist/agents/subagent.js +2 -1
  5. package/dist/core/ask-origin.d.ts +1 -0
  6. package/dist/core/ask-origin.js +3 -2
  7. package/dist/core/auto-compaction.d.ts +2 -2
  8. package/dist/core/auto-mode-rebuild.js +6 -2
  9. package/dist/core/auto-mode.d.ts +40 -18
  10. package/dist/core/auto-mode.js +63 -45
  11. package/dist/core/checkpoint-store.d.ts +9 -13
  12. package/dist/core/context-edit.js +1 -1
  13. package/dist/core/engine-notice.d.ts +62 -1
  14. package/dist/core/engine-notice.js +14 -1
  15. package/dist/core/gate-bounded-await.d.ts +13 -3
  16. package/dist/core/gate-fold.js +3 -3
  17. package/dist/core/governance-codes.d.ts +1 -1
  18. package/dist/core/governance-codes.js +6 -0
  19. package/dist/core/mcp.d.ts +65 -0
  20. package/dist/core/mcp.js +190 -41
  21. package/dist/core/memory-engine/consolidation.d.ts +5 -0
  22. package/dist/core/memory-engine/consolidation.js +7 -3
  23. package/dist/core/memory-engine/engine.d.ts +8 -0
  24. package/dist/core/memory-engine/engine.js +25 -19
  25. package/dist/core/memory-engine/layout.d.ts +47 -9
  26. package/dist/core/memory-engine/layout.js +157 -57
  27. package/dist/core/memory-engine/session-incarnation.d.ts +27 -0
  28. package/dist/core/memory-engine/session-incarnation.js +63 -0
  29. package/dist/core/parent-spec-seat.d.ts +15 -3
  30. package/dist/core/parent-spec-seat.js +5 -0
  31. package/dist/core/park-selfcheck.js +1 -1
  32. package/dist/core/path-identity.d.ts +11 -4
  33. package/dist/core/path-identity.js +5 -2
  34. package/dist/core/permission-rule-model.d.ts +1 -2
  35. package/dist/core/read-root-candidate.js +11 -1
  36. package/dist/core/retention.d.ts +8 -1
  37. package/dist/core/retention.js +2 -0
  38. package/dist/core/runner/assemble-result.d.ts +2 -7
  39. package/dist/core/runner/assemble-result.js +25 -8
  40. package/dist/core/runner/classifier-ladder.js +1 -0
  41. package/dist/core/runner/contracts.d.ts +26 -15
  42. package/dist/core/runner/inherited-ask-grants.d.ts +2 -7
  43. package/dist/core/runner/inherited-ask-grants.js +1 -0
  44. package/dist/core/runner/mcp-redial.d.ts +109 -0
  45. package/dist/core/runner/mcp-redial.js +60 -0
  46. package/dist/core/runner/memory-consolidation-driver.d.ts +2 -0
  47. package/dist/core/runner/prepare-ask-lane.js +1 -0
  48. package/dist/core/runner/prepare-caps-and-workflow.js +9 -12
  49. package/dist/core/runner/prepare-context-lane.d.ts +43 -1
  50. package/dist/core/runner/prepare-context-lane.js +25 -4
  51. package/dist/core/runner/prepare-hands-readface.d.ts +3 -0
  52. package/dist/core/runner/prepare-hands-readface.js +13 -3
  53. package/dist/core/runner/prepare-memory.js +3 -3
  54. package/dist/core/runner/prepare-protocol-tools.d.ts +6 -1
  55. package/dist/core/runner/prepare-protocol-tools.js +20 -38
  56. package/dist/core/runner/prepare-task.js +4 -4
  57. package/dist/core/runner/prepare-turn-wiring.js +2 -2
  58. package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -0
  59. package/dist/core/runner/prepare-wiring-manifest.js +2 -1
  60. package/dist/core/runner/run-harness-handlers.js +4 -2
  61. package/dist/core/runner/run-identity-wiring.d.ts +3 -1
  62. package/dist/core/runner/run-identity-wiring.js +4 -1
  63. package/dist/core/runner/run-recovery-lanes.js +6 -3
  64. package/dist/core/runner/run-settle-and-teardown.d.ts +3 -1
  65. package/dist/core/runner/run-settle-and-teardown.js +6 -2
  66. package/dist/core/runner/run-telemetry-and-budget-seats.js +11 -1
  67. package/dist/core/runner/run-terminal-adoption.d.ts +4 -1
  68. package/dist/core/runner/run-terminal-adoption.js +2 -6
  69. package/dist/core/runner/runtask.js +8 -5
  70. package/dist/core/runner/stream-lifecycle-verbs.d.ts +5 -1
  71. package/dist/core/runner/stream-lifecycle-verbs.js +23 -2
  72. package/dist/core/runner/write-protection-station.d.ts +7 -25
  73. package/dist/core/runner/write-protection-station.js +18 -0
  74. package/dist/core/runner-deps.d.ts +16 -7
  75. package/dist/core/skill-listing-budget.d.ts +5 -4
  76. package/dist/core/task-registry.d.ts +0 -6
  77. package/dist/core/task-result.d.ts +31 -12
  78. package/dist/core/task-spec.d.ts +2 -1
  79. package/dist/core/task-stream.d.ts +26 -0
  80. package/dist/core/tool-spec.d.ts +5 -1
  81. package/dist/core/trace.d.ts +5 -0
  82. package/dist/core/types.d.ts +3 -3
  83. package/dist/core/types.js +1 -1
  84. package/dist/core/wiring-manifest.d.ts +23 -0
  85. package/dist/core/wiring-manifest.js +3 -0
  86. package/dist/core/write-protect.d.ts +64 -12
  87. package/dist/core/write-protect.js +88 -34
  88. package/dist/engine/execution-env/node-execution-env.d.ts +19 -2
  89. package/dist/engine/execution-env/node-execution-env.js +5 -1
  90. package/dist/index.d.ts +5 -4
  91. package/dist/index.js +2 -2
  92. package/dist/orchestration/workflow-governance.js +1 -1
  93. package/dist/orchestration/workflow-primitives.d.ts +2 -2
  94. package/dist/tools/fs/bash-program-position.d.ts +26 -5
  95. package/dist/tools/fs/bash-program-position.js +63 -16
  96. package/dist/tools/fs/bash-readonly-classifier.d.ts +27 -17
  97. package/dist/tools/fs/bash-readonly-classifier.js +134 -55
  98. package/dist/tools/fs/fs-bash.js +43 -18
  99. package/dist/tools/fs/fs-read.js +2 -1
  100. package/dist/tools/fs/fs-shared.d.ts +33 -7
  101. package/dist/tools/fs/fs-shared.js +12 -1
  102. package/dist/tools/fs/index.d.ts +4 -2
  103. package/dist/tools/fs/read-deny.d.ts +25 -11
  104. package/dist/tools/fs/read-deny.js +5 -1
  105. package/dist/tools/fs/safety.d.ts +38 -4
  106. package/dist/tools/fs/safety.js +31 -3
  107. package/package.json +2 -2
  108. package/test/export-surface.snapshot.json +11 -1
  109. package/wire-changes/core@7.22.0.json +637 -0
  110. package/wire-changes/core@7.23.0.json +316 -0
@@ -19,7 +19,7 @@ import type { Model } from "../../internal/llm.js";
19
19
  import { type MaybeCompactOptions } from "../auto-compaction.js";
20
20
  import type { SessionStore } from "../session.js";
21
21
  import type { PushQueue } from "../push-queue.js";
22
- import { type Brain, type TaskEvent, type TaskSpec } from "../types.js";
22
+ import type { Brain, OwedUserInputAccount, TaskEvent, TaskSpec } from "../types.js";
23
23
  import { type Stats } from "./assemble-result.js";
24
24
  import type { EndOfTaskCompaction, Prepared, RunnerDepsSeat, RunState } from "./contracts.js";
25
25
  export interface RunTerminalAdoptionInput<R> {
@@ -95,5 +95,8 @@ export interface RunTerminalAdoptionResult {
95
95
  /** This lane's ONE evaluation of {@link answerSettled}: a clean `"stop"` final with no queued continuation lost. The
96
96
  * settle lane hands it to the arbitration (`ResultFlags.answerSettled`), the teardown attribution and the notice. */
97
97
  answerSettled: boolean;
98
+ /** The undrained-input account, UNSETTLED: this lane knows every fact but one — whether this leg's consumed
99
+ * checkpoint row comes back — and that one is the next lane's compensation to perform, not to predict. */
100
+ owedUserInput: OwedUserInputAccount;
98
101
  }
99
102
  export declare function runTerminalAdoption<R>(input: RunTerminalAdoptionInput<R>): Promise<R>;
@@ -3,7 +3,6 @@ import { DEFAULT_COMPACTION_SETTINGS } from "../../internal/harness.js";
3
3
  import { eventDefaultOn } from "../../prompt-assembly/event-registry.js";
4
4
  import { appendInterruptionMarker, reconcileInterruptedSession } from "../session-reconcile.js";
5
5
  import { emitTrace } from "../trace.js";
6
- import { deliverEngineNotice, owedUserInputCounts, undrainedUserInputNotices } from "../types.js";
7
6
  import { answerSettled, errorCodeOf } from "./assemble-result.js";
8
7
  import { awaitChargeWithSlowDisclosure, discloseUnevaluableWindow } from "./clock-and-limits.js";
9
8
  import { buildWorkingFileAttachments, centerAdoptionOption, contextInstructionFilesOption, emitInputTruncated, forkContextOption } from "./compaction-call-options.js";
@@ -109,10 +108,7 @@ export async function runTerminalAdoption(input) {
109
108
  }
110
109
  migratedParked = prepared.harness.dropParkedUserInputs(carried);
111
110
  }
112
- const owed = owedUserInputCounts(undrainedUserAtEnd.current, migratedParked, { frames: prepared.undeliveredContinuationRef.current.parkedSteers, carried: carriedContinuation, rowMayReturn: committedPause === undefined && !prepared.suspendProgressRef.executedApproved });
113
- for (const notice of undrainedUserInputNotices(owed, spec.taskId ?? prepared.sessionId, prepared.sessionId, prepared.runId)) {
114
- deliverEngineNotice(runner.deps.onNotice, notice);
115
- }
111
+ const owedUserInput = { held: undrainedUserAtEnd.current, migrated: migratedParked, continuation: { frames: prepared.undeliveredContinuationRef.current.parkedSteers, carried: carriedContinuation } };
116
112
  if (rs.attach.attachState?.postCompactPending === true && (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true) {
117
113
  emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.announce_dropped", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
118
114
  }
@@ -125,7 +121,7 @@ export async function runTerminalAdoption(input) {
125
121
  onCompactionFailed: (reason) => queue.push({ type: "compaction_outcome", outcome: "failed", trigger: "auto", reason, ...ident() }),
126
122
  cancelled: abortedLive,
127
123
  }, runner, sessions);
128
- return await next({ threw, comp, answerSettled: settled });
124
+ return await next({ threw, comp, answerSettled: settled, owedUserInput });
129
125
  }
130
126
  async function finish(spec, prepared, opts, runner, sessions) {
131
127
  let comp;
@@ -296,6 +296,7 @@ export class Runner {
296
296
  const acceptedSteerInputs = new Map();
297
297
  const notifyRef = {};
298
298
  const captureOptOutRef = {};
299
+ const mcpRedialRef = {};
299
300
  const manualCompactRef = { requested: false, waiters: [] };
300
301
  const drainManualCompactWaiters = (outcome) => {
301
302
  manualCompactRef.requested = false;
@@ -348,6 +349,7 @@ export class Runner {
348
349
  internals: { ...(internals ?? {}), detachHub, forwardScope: createForwardSinkScope(this.deps.onNotice) },
349
350
  notifyRef,
350
351
  captureOptOutRef,
352
+ mcpRedialRef,
351
353
  entryActor,
352
354
  entryTracer,
353
355
  });
@@ -383,7 +385,7 @@ export class Runner {
383
385
  locallyClaimedTokens: this.locallyClaimedTokens, parentConstraintRegistry: this.parentConstraintRegistry, suspendedEnvReaps: this.suspendedEnvReaps,
384
386
  });
385
387
  const { steer } = streamSteerVerb({ spec, internals, queue, acceptedSteerInputs, live, ready, orTimeout, readyTimeoutMs: READY_TIMEOUT_MS, steeringError, runner: this.depsSeat });
386
- const { notify, optOutMemoryCapture, compact, detach, interrupt } = streamLifecycleVerbs({ live, ready, orTimeout, steeringError, notifyRef, captureOptOutRef, manualCompactRef, detachHub });
388
+ const { notify, optOutMemoryCapture, reconnectMcpServer, compact, detach, interrupt } = streamLifecycleVerbs({ live, ready, orTimeout, steeringError, notifyRef, captureOptOutRef, mcpRedialRef, manualCompactRef, detachHub });
387
389
  const { halt, destroy } = streamHaltVerbs({ spec, live, ready, orTimeout, readyTimeoutMs: READY_TIMEOUT_MS, run, reapSuspended, runner: this.depsSeat });
388
390
  return {
389
391
  [Symbol.asyncIterator]: () => queue[Symbol.asyncIterator](),
@@ -398,6 +400,7 @@ export class Runner {
398
400
  steer,
399
401
  notify,
400
402
  optOutMemoryCapture,
403
+ reconnectMcpServer,
401
404
  compact,
402
405
  detach,
403
406
  interrupt,
@@ -406,14 +409,14 @@ export class Runner {
406
409
  };
407
410
  }
408
411
  runLocked(input) {
409
- const { spec, queue, setResult, onSuggestions, onReady, onSuspend, manualCompactRef, taskIdRef, resume, internals, notifyRef, captureOptOutRef, entryActor, entryTracer } = input;
412
+ const { spec, queue, setResult, onSuggestions, onReady, onSuspend, manualCompactRef, taskIdRef, resume, internals, notifyRef, captureOptOutRef, mcpRedialRef, entryActor, entryTracer } = input;
410
413
  return runNotificationLane({
411
414
  spec, queue, resume, internals, taskIdRef,
412
415
  runner: this.depsSeat, self: this, sessions: this.sessions, sessionReadStates: this.sessionReadStates, pendingSessionNotifications: this.pendingSessionNotifications,
413
416
  prepareTask,
414
417
  next: ({ prepared, injectTaskNotification, deliveredAtTurnOpen, peerInboundChainRef, modelCatalog, notificationLane, unsubscribeTaskNotifications }) => {
415
418
  const { runSourceTaskId, parentToolCallId, ident, emitDelegationLifecycle, loopLatch, stats, undrainedUserAtEnd, userInputLostAtEnd } = runIdentityWiring({
416
- spec, queue, prepared, internals, taskIdRef, onReady, manualCompactRef, notifyRef, captureOptOutRef, injectTaskNotification, deliveredAtTurnOpen, peerInboundChainRef,
419
+ spec, queue, prepared, internals, taskIdRef, onReady, manualCompactRef, notifyRef, captureOptOutRef, mcpRedialRef, injectTaskNotification, deliveredAtTurnOpen, peerInboundChainRef,
417
420
  notificationLane, pendingSessionNotifications: this.pendingSessionNotifications, runner: this.depsSeat,
418
421
  });
419
422
  const rs = createRunState();
@@ -472,9 +475,9 @@ export class Runner {
472
475
  next: ({ final, threw, abortedLive, userInterruptedLive, strandedHumanAnswers }) => runTerminalAdoption({
473
476
  spec, queue, prepared, rs, stats, ident, emitCommitted, startedToolCallIds, toolLabels, timeout, compactionBrain, compactionBreaker, windowSafetyOptions,
474
477
  undrainedUserAtEnd, userInputLostAtEnd, final, threw, abortedLive, userInterruptedLive, runner: this.depsSeat, sessions: this.sessions,
475
- next: ({ threw, comp, answerSettled }) => runSettleAndTeardown({
478
+ next: ({ threw, comp, answerSettled, owedUserInput }) => runSettleAndTeardown({
476
479
  spec, queue, prepared, resume, internals, rs, stats, ident, parentToolCallId, subagentName, loopLatch, timeout, ownCommittedTailRef,
477
- emitDelegationLifecycle, taskIdRef, manualCompactRef, drainManualCompact, final, threw, abortedLive, userInterruptedLive, answerSettled, strandedHumanAnswers, reasoningResolution, comp,
480
+ emitDelegationLifecycle, taskIdRef, manualCompactRef, drainManualCompact, final, threw, abortedLive, userInterruptedLive, answerSettled, strandedHumanAnswers, reasoningResolution, comp, owedUserInput,
478
481
  setResult, onSuggestions, onSuspend, runner: this.depsSeat, sessions: this.sessions,
479
482
  }),
480
483
  }),
@@ -1,6 +1,6 @@
1
1
  import type { ToolDetachHub } from "../tool-detach.js";
2
2
  import type { TaskStream } from "../types.js";
3
- import type { CaptureOptOutRef, LiveHandle, ManualCompactRef, NotifyRef, TaskStreamLiveSeat } from "./contracts.js";
3
+ import type { CaptureOptOutRef, LiveHandle, ManualCompactRef, McpRedialRef, NotifyRef, TaskStreamLiveSeat } from "./contracts.js";
4
4
  export interface StreamLifecycleVerbsInput {
5
5
  /** borrowed-readonly — the stream's live state: `resultValue` (the finished-task refusals) and `handle`, read at each call. */
6
6
  live: TaskStreamLiveSeat;
@@ -14,6 +14,8 @@ export interface StreamLifecycleVerbsInput {
14
14
  notifyRef: NotifyRef;
15
15
  /** borrowed-readonly — the capture opt-out bridge; `flip` is bound by the run body when a memory session mounted. */
16
16
  captureOptOutRef: CaptureOptOutRef;
17
+ /** borrowed-readonly — the MCP re-dial bridge; `redial` is bound by the run body when the leg declared MCP servers. */
18
+ mcpRedialRef: McpRedialRef;
17
19
  /** borrowed-mutable — the manual-compaction request seat: the compact verb arms it and parks its waiter here. */
18
20
  manualCompactRef: ManualCompactRef;
19
21
  /** borrowed-readonly — the run-local detach hub the detach verb requests through. */
@@ -24,6 +26,8 @@ export interface StreamLifecycleVerbsResult {
24
26
  notify: TaskStream["notify"];
25
27
  /** The stream's `optOutMemoryCapture` verb. */
26
28
  optOutMemoryCapture: TaskStream["optOutMemoryCapture"];
29
+ /** The stream's `reconnectMcpServer` verb. */
30
+ reconnectMcpServer: TaskStream["reconnectMcpServer"];
27
31
  /** The stream's `compact` verb. */
28
32
  compact: TaskStream["compact"];
29
33
  /** The stream's `detach` verb. */
@@ -1,6 +1,6 @@
1
1
  import { isSystemInjectionPriority, SYSTEM_INJECTION_PRIORITIES } from "../task-notification.js";
2
2
  export function streamLifecycleVerbs(input) {
3
- const { live, ready, orTimeout, steeringError, notifyRef, captureOptOutRef, manualCompactRef, detachHub } = input;
3
+ const { live, ready, orTimeout, steeringError, notifyRef, captureOptOutRef, mcpRedialRef, manualCompactRef, detachHub } = input;
4
4
  const notify = async (input, opts) => {
5
5
  const notifyError = (msg, code) => {
6
6
  const e = new Error(`cannot notify: ${msg}`);
@@ -69,6 +69,27 @@ export function streamLifecycleVerbs(input) {
69
69
  }
70
70
  return captureOptOutRef.flip(reason);
71
71
  };
72
+ const reconnectMcpServer = async (server) => {
73
+ const serverIn = server;
74
+ if (typeof serverIn !== "string" || serverIn.length === 0) {
75
+ const e = new Error(`cannot reconnect an MCP server: \`server\` must be a non-empty server name`);
76
+ e.code = "steering.invalid_content";
77
+ throw e;
78
+ }
79
+ if (live.resultValue)
80
+ throw steeringError("the task has already finished");
81
+ const h = live.handle ?? (await orTimeout(ready));
82
+ if (!h)
83
+ throw steeringError("the task is not running");
84
+ if (live.resultValue !== undefined || h.loop.ended)
85
+ throw steeringError("the task is no longer running");
86
+ if (mcpRedialRef.redial === undefined) {
87
+ const e = new Error(`cannot reconnect an MCP server: this run declared no MCP servers — there is nothing to re-dial`);
88
+ e.code = "mcp.reconnect_unavailable";
89
+ throw e;
90
+ }
91
+ return mcpRedialRef.redial(serverIn);
92
+ };
72
93
  const compact = async (opts) => {
73
94
  if (live.resultValue)
74
95
  throw steeringError("the task has already finished");
@@ -131,5 +152,5 @@ export function streamLifecycleVerbs(input) {
131
152
  h.abortController.abort();
132
153
  void h.harness.abort();
133
154
  };
134
- return { notify, optOutMemoryCapture, compact, detach, interrupt };
155
+ return { notify, optOutMemoryCapture, reconnectMcpServer, compact, detach, interrupt };
135
156
  }
@@ -12,36 +12,18 @@
12
12
  *
13
13
  * Two readers, one compile: the gate station mounts `check`, and the leg's wiring manifest reports
14
14
  * `targetView`. Compiling twice would be two judges free to disagree about the seats they were handed.
15
+ *
16
+ * The SECOND absence this seat carries is the per-judgment one: a judge that HAS its target view but
17
+ * whose environment could not answer a particular target drops that view and judges by the spelling alone.
18
+ * That is a run-time fact, so it cannot be announced at compile like the one above — but it is a fact about
19
+ * the ENVIRONMENT rather than about any one write, so it is announced from here, once per leg per reason,
20
+ * and carries no call identity. This is the leg-scoped home the dedupe needs: the judge is compiled once
21
+ * per leg and every judgment of the leg goes through the check minted here.
15
22
  */
16
23
  import type { ExecutionEnv } from "../../internal/harness.js";
17
24
  import type { RunnerDeps } from "../runner-deps.js";
18
25
  import type { ToolRoster } from "../tool-roster.js";
19
26
  import { type WriteProtectionMount } from "../write-protect.js";
20
- /**
21
- * Compile the leg's write-protection judge and announce a missing target view.
22
- *
23
- * Returns the mount, or `undefined` when the deployment emptied the table (`[]`): no judge, no tighten
24
- * mounted at all, and nothing announced — an explicit opt-out is not a degrade. A garbage table refuses
25
- * prepare loudly from here, as it did from the gate station before, so an unusable table cannot lie
26
- * dormant on a write-tool-less task.
27
- *
28
- * The notice fires exactly once per prepared leg and only when a judge with NO target view sits beside at
29
- * least one tool whose writes it covers. Coverage runs the judge's OWN per-call test ({@link
30
- * protectivePathTargetOf}) rather than a copy of it, over every name a row DISPATCHES under — its own and
31
- * its aliases, because the gate hands the judge the name the call was made with. A row that declares a
32
- * path target is covered by that declaration under every name; a row that declares none is covered under
33
- * whichever of its names the catalog gives a non-read slot to. Both halves are load-bearing: a tool named
34
- * `Write` that declares nothing, and a tool aliased to `Write` that declares nothing, are each judged by
35
- * spelling alone on a hands-less leg, and each would otherwise go unnamed by the very line that exists to
36
- * name them. Reported by the ROW's own name — what the deployment mounted.
37
- *
38
- * SNAPSHOT BOUNDARY, deliberate and disclosed: the roster read here is the LEG-START one, the same one
39
- * the wiring manifest reports and for the same reason — this is one announcement per prepared leg, not a
40
- * subscription. A run that gains its FIRST covered write tool after prepare (a mid-run roster delta) is
41
- * judged by the same spelling-only judge and is not announced; the leg's `writeProtection.targetView`
42
- * still reports the judge's reading correctly, because the judge never gains an environment mid-run —
43
- * only the covered SET can widen.
44
- */
45
27
  export declare function mountWriteProtection(input: {
46
28
  /** borrowed-readonly — the deployment seats: the table (`writeProtectedPaths`; absent ⇒ the default
47
29
  * table, `[]` ⇒ opted out), the data root the memory library is materialized under
@@ -1,11 +1,29 @@
1
1
  import { deliverEngineNotice } from "../engine-notice.js";
2
2
  import { protectivePathTargetOf } from "../tool-registry.js";
3
3
  import { createWriteProtectionCheck } from "../write-protect.js";
4
+ const ABSENCE_REASON = { symlink: "unresolved-symlink", target: "unresolvable-target" };
4
5
  export function mountWriteProtection(input) {
6
+ const announced = new Set();
5
7
  const mount = createWriteProtectionCheck(input.deps.writeProtectedPaths, {
6
8
  ...(input.deps.memoryEngineDir !== undefined ? { dataRoot: input.deps.memoryEngineDir } : {}),
7
9
  ...(input.env !== undefined ? { env: input.env } : {}),
8
10
  rootPath: input.rootPath,
11
+ onViewAbsent: (absence) => {
12
+ const reason = absence.unresolvedSymlink ? ABSENCE_REASON.symlink : ABSENCE_REASON.target;
13
+ if (announced.has(reason))
14
+ return;
15
+ announced.add(reason);
16
+ deliverEngineNotice(input.deps.onNotice, {
17
+ code: "config.write_protection_unresolved",
18
+ message: `This run's write-protection judge could not resolve a write target in its execution environment ` +
19
+ `(${reason === ABSENCE_REASON.symlink ? "a symlink whose real target is unknowable" : "the environment refused or errored on the spelling"}: ${absence.cause}). ` +
20
+ `A view that cannot answer does not vote, so that write was judged by its SPELLING against the table and nothing else — ` +
21
+ `a target reaching a protected path only through an alias, a bind mount or a ".." walk is cleared. Nothing about this run was ` +
22
+ `refused because of it, and nothing here is a defect in the write itself: an environment that cannot answer path questions is a ` +
23
+ `deployment fact. Announced once per leg per reason; later writes of the same shape are judged the same way, silently.`,
24
+ detail: { reason, cause: absence.cause, sessionId: input.sessionId, runId: input.runId },
25
+ });
26
+ },
9
27
  });
10
28
  if (mount === undefined || mount.targetView !== "spelling-only")
11
29
  return mount;
@@ -179,20 +179,21 @@ export interface RunnerDeps {
179
179
  /**
180
180
  * design/199 件B — DEPLOYMENT-layer ADDITIONS to the built-in sensitive-path READ deny set
181
181
  * ({@link import("../tools/fs/read-deny.js").READ_FACE_DEFAULT_DENY_ENTRIES}); see
182
- * {@link TaskSpec.readDenyPatterns} for the judged surfaces. Add-only on THIS seat: unions with
183
- * the active built-ins and any task-layer additions, and `[]` ≡ absent — narrowing the built-in
182
+ * {@link TaskSpec.readDenyPatterns} for the judged surfaces. INDEPENDENT of the built-in switch. Add-only on
183
+ * THIS seat: unions with the active built-ins and any task-layer additions, and `[]` ≡ absent — narrowing the built-in
184
184
  * set is the sibling knobs' job ({@link readDenyBuiltinTiers} / {@link readDenyBuiltinExclude},
185
185
  * #245 revision of D-4), not a replacement escape hatch here. Bad entry shapes refuse loudly at
186
186
  * prepare (#123).
187
187
  */
188
188
  readDenyPatterns?: readonly import("../tools/fs/read-deny.js").ReadDenyEntry[];
189
189
  /**
190
- * The deployment's built-in deny-table TIER selection:
191
- * EXACTLY the listed tiers of
190
+ * The deployment's built-in deny-table TIER selection — the ON/OFF switch for the whole built-in
191
+ * table, OFF unless this seat says otherwise. EXACTLY the listed tiers of
192
192
  * {@link import("../tools/fs/read-deny.js").READ_FACE_BUILTIN_DENY_TABLE} are active (`[]` = none —
193
- * explicit and legal); absent = the default selection
194
- * ({@link import("../tools/fs/read-deny.js").READ_DENY_DEFAULT_TIERS}: every tier except
195
- * `shell-history`, which is unrestricted by default). Composes with
193
+ * explicit and legal); ABSENT = {@link import("../tools/fs/read-deny.js").READ_DENY_DEFAULT_TIERS},
194
+ * which is EMPTY: an unconfigured deployment activates no built-in row, so nothing from that table
195
+ * refuses a read or demotes a shell one. A leg's active tiers are REPORTED on its wiring manifest
196
+ * ({@link import("./wiring-manifest.js").WiringManifest.readDeny}). Composes with
196
197
  * {@link readDenyBuiltinExclude} (tiers first, then row removal); never touches the additions
197
198
  * seats or the write faces. Unknown tier names refuse loudly at prepare (#123). DEPLOYMENT seat
198
199
  * ONLY: deliberately not a TaskSpec key and not in the governed workflow whitelist — a task or
@@ -324,6 +325,14 @@ export interface RunnerDeps {
324
325
  memoryCaptureRecordStore?: (plane: {
325
326
  controlDir: string;
326
327
  }) => import("./memory-engine/layout.js").SessionCaptureRecordStore;
328
+ /** which occupant of a session id is running now (a purge may delete a session tree and the
329
+ * id be handed out again; without this seat a standing opt-out or pollution record binds the NEXT
330
+ * occupant too). An opaque token, STABLE for one occupant's life, DIFFERENT for the next, only
331
+ * ever compared ({@link import("./memory-engine/layout.js").sessionRecordBindsIncarnation}).
332
+ * ABSENT ⇒ records bind by id (the pre-incarnation bytes); an empty or throwing answer reads the same. */
333
+ memorySessionIncarnation?: (session: {
334
+ sessionId: string;
335
+ }) => string | undefined;
327
336
  /**
328
337
  * design/339 §6.1 — the v3 memory-consolidation switch, threaded verbatim to the engine seat
329
338
  * ({@link import("../core/memory-engine/engine.js").MemoryEngineOptions.consolidation} — same
@@ -65,10 +65,11 @@ export interface ResolvedSkillListingBudget {
65
65
  * (`200 000 × 4 × 0.01` = 8 000 bytes).
66
66
  *
67
67
  * The window is `contextTokens ?? contextWindow`, the same physical coordinate the deferral gate
68
- * measures against. A window that is absent OR unusable (non-finite, non-positive) reads as
69
- * {@link SKILL_LISTING_DEFAULT_CONTEXT_TOKENS}: one rule, not a case per bad shape — a model's own
70
- * declaration is judged by the model doors, and this seat must still produce a number a renderer can
71
- * compare against.
68
+ * measures against. `contextTokens: 0` (or any other non-finite/non-positive declared value) does
69
+ * NOT fall through `??` to `contextWindow` — `0` is not nullish — so it lands here as a
70
+ * declared-but-unusable window, and this seat folds it to {@link SKILL_LISTING_DEFAULT_CONTEXT_TOKENS}
71
+ * the same as a genuinely absent window: one rule, not a case per bad shape, because this seat's
72
+ * job is to always hand a renderer a usable number.
72
73
  */
73
74
  export declare function resolveSkillListingBudget(opts: {
74
75
  fraction?: unknown;
@@ -185,10 +185,6 @@ export declare class TaskRegistry {
185
185
  tupleIncomplete: true;
186
186
  })>>;
187
187
  };
188
- /** design/151 §7.7 (F-13) — the row's mailbox dies with the row: a WINNING delete also drops
189
- * the (scope, handle) mailbox (advisory — a mailbox fault never blocks the reap; an orphaned
190
- * box is bounded by the mailbox's own `reap` policy). Structural face (drop only), so the
191
- * deployment passes its `RunnerDeps.mailboxStore` directly. */
192
188
  mailbox?: {
193
189
  drop(scope: string, handle: string): Promise<void>;
194
190
  };
@@ -634,8 +630,6 @@ export declare class TaskRegistry {
634
630
  private pollBackgroundBash;
635
631
  private pollWorkflow;
636
632
  private stopBackgroundBash;
637
- /** design/135 G2: TaskOutput over a monitor — a RE-READABLE spool snapshot (the watcher owns the env
638
- * cursor, exactly the bash G2b spool posture): repeated polls return the same accumulated output. */
639
633
  private pollMonitor;
640
634
  private stopMonitor;
641
635
  private stopWorkflow;
@@ -38,6 +38,30 @@ export type TaskStatus = "completed" | "blocked" | "failed" | "suspended" | "nee
38
38
  * One remote-workspace lifecycle failure, reported as data on {@link TaskResult.remoteEnvFailures}.
39
39
  * See that field for why the eleven-code taxonomy needed a structured channel to reach a caller at all.
40
40
  */
41
+ /**
42
+ * ONE `humanReview` ledger row — what `stats.humanReview.gates[]` carries, what the runner's live
43
+ * accumulator holds, and what a checkpoint persists. It lived as six structurally-identical inline
44
+ * copies, the persisted one NARROWER than the rest, so "does a row carry the call it gated?" had six
45
+ * answers and the persisted one said no while the bytes it stored said yes. One declaration now.
46
+ */
47
+ export interface HumanReviewGateRow {
48
+ /** `kind` / `decision` / `toolName` / `toolArg` are documented once, on `stats.humanReview.gates`. */
49
+ kind: string;
50
+ waitMs: number;
51
+ decision?: string;
52
+ toolName?: string;
53
+ toolArg?: string;
54
+ /**
55
+ * The id of the tool call this row gated — the row's IDENTITY, not a display field. Minted with the
56
+ * row at each of the four mint sites, never re-derived downstream: a run pushes rows from several
57
+ * mints while only one passes through the host's `onAsk`, so the two ledgers' counts are legitimately
58
+ * unequal and there was no key to reconcile on. Absent means one thing — the mint had no call id to
59
+ * give (a probe row; a durable gate whose checkpoint carries a non-`tool_approval` pending action,
60
+ * since a `resource_limit` / `needs_review` / `plan_review` pause gates no call; or an older engine).
61
+ * Never a placeholder.
62
+ */
63
+ toolCallId?: string;
64
+ }
41
65
  export interface RemoteEnvFailureNote {
42
66
  /** Which VM-lifecycle call failed. */
43
67
  op: "suspendVM" | "resumeVM" | "postResumeInit";
@@ -562,8 +586,9 @@ export interface TaskResult {
562
586
  * duplicates, so re-supplying deployment-shared entries is idempotent) — that is how the frozen
563
587
  * posture rides every leg that continues this work, not just the delegation subtree.
564
588
  *
565
- * **In-presence condition:** present iff non-empty. Built-in entries are never listed (they are in
566
- * force on every run and re-compile locally). Same terminal coverage as
589
+ * **In-presence condition:** present iff non-empty. Built-in entries are never listed — they are the
590
+ * DEPLOYMENT's own selection (off by default), re-compiled per leg and reported on its wiring
591
+ * manifest ({@link import("./wiring-manifest.js").WiringManifest.readDeny}). Same terminal coverage as
567
592
  * {@link effectiveReadFace}: rides any terminal of a leg that ran; absent on prepare failures.
568
593
  *
569
594
  * On a DURABLE-PAUSE hand-back these seats are additionally the RE-SUPPLY hint: a checkpoint
@@ -780,16 +805,10 @@ export interface TaskResult {
780
805
  * `irreversible_ask` for durable; `human` for a synchronous ask). `decision` = the adjudication when
781
806
  * known (`allow`/`deny`). design/99 MF-24: `toolName` = the gated tool; `toolArg` = a
782
807
  * SHORT, SECRET-SCRUBBED one-line summary of its input (the same `primaryActivityArg` boundary as the MF-W
783
- * activity arg) — the permission-denial ledger's "denied: Bash(rm …)" display. `toolArg` is set on the
784
- * SYNCHRONOUS-ask path (the input is in scope); a durable-resume gate carries `toolName` only (its input is
785
- * not threaded onto the persisted gate — a documented follow-on). */
786
- gates: Array<{
787
- kind: string;
788
- waitMs: number;
789
- decision?: string;
790
- toolName?: string;
791
- toolArg?: string;
792
- }>;
808
+ * activity arg) — the permission-denial ledger's "denied: Bash(rm …)" display. `toolArg` rides every mint
809
+ * that has the input in scope: the synchronous ask from the request, the durable-resume gate from the
810
+ * checkpoint's `pendingAction`; `toolCallId` names the call the row gated (absent = not joinable). */
811
+ gates: HumanReviewGateRow[];
793
812
  };
794
813
  /**
795
814
  * design/80 D-E-core: a thin FINANCE TAXONOMY of the LLM-derived costs the engine actually prices,
@@ -789,7 +789,8 @@ export interface TaskSpec {
789
789
  * {@link RunnerDeps.readDenyPatterns}; `[]` ≡ absent (union identity); NOTHING at the task layer
790
790
  * can remove a built-in — the one removal channel is the DEPLOYMENT's built-in configuration
791
791
  * ({@link RunnerDeps.readDenyBuiltinTiers} / {@link RunnerDeps.readDenyBuiltinExclude}, #245
792
- * revision of D-4), which no TaskSpec key reaches. Bad entry shapes refuse loudly at prepare
792
+ * revision of D-4), which no TaskSpec key reaches — and that half is OFF by default, so on an
793
+ * unconfigured deployment the entries here are the WHOLE judged set. Bad entry shapes refuse loudly at prepare
793
794
  * (#123). The write faces are untouched.
794
795
  */
795
796
  readDenyPatterns?: readonly import("../tools/fs/read-deny.js").ReadDenyEntry[];
@@ -275,6 +275,32 @@ export interface TaskStream extends AsyncIterable<TaskEvent> {
275
275
  }): Promise<{
276
276
  outcome: "created" | "existed";
277
277
  }>;
278
+ /**
279
+ * **RE-DIAL one MCP server** on the running task, the action `RefreshMcpTools` deliberately
280
+ * is not (its contract is "never dials or re-dials"). Core holds no MCP connection across task
281
+ * legs, so once a server's transport closes this leg is stuck with it: every one of its tools fails
282
+ * fast and a refresh can only keep reporting `not_connected`. This is the verb a host hangs a
283
+ * "Reconnect" control on. ONE NAMED SERVER, required — CC 2.1.259's control-channel request carries
284
+ * a required `serverName` and has no all-servers form, and an all-servers form would CLOSE HEALTHY
285
+ * connections to re-dial them (the stdio server owning a device or a port — the case this verb
286
+ * exists for — is the one that cannot be re-dialed while its predecessor still holds the device).
287
+ *
288
+ * ⚠️ **A FAILED RE-DIAL LEAVES THE SERVER DOWN.** The old connection is closed BEFORE the new one is
289
+ * dialed (no other order works for a device-owning server), so a dial that fails has spent a
290
+ * working connection: the server stays disconnected, with its tools WITHDRAWN from the roster,
291
+ * until another re-dial or the next leg's materialize — and calls in flight on the replaced
292
+ * connection fail with it. Both are inherent to re-dialing; CC behaves the same way.
293
+ *
294
+ * Resolves {@link import("./mcp.js").McpReconnectResult} — `outcome` is one of three words, so a
295
+ * consumer mapping it onto a three-state control answer needs no table: `reconnected` (the only
296
+ * success), `refused` (the engine declined, or the dial failed — `reason` says which and
297
+ * `status.errorCode` keys the next action), `not_declared` (no server of that name on this leg).
298
+ * Throws, never silently no-ops: `mcp.reconnect_unavailable` when this run declared NO MCP servers
299
+ * (a host maps that to "unsupported", not "refused"), `steering.invalid_content` on a missing or
300
+ * non-string name, and the steer-family `steering.not_running` once the task has finished.
301
+ * LEG-LOCAL: the result rides no checkpoint, and a durable resume takes a fresh first listing.
302
+ */
303
+ reconnectMcpServer(server: string): Promise<import("./mcp.js").McpReconnectResult>;
278
304
  /**
279
305
  * Hard-**interrupt** the running task — a real abort (not a best-effort hint), equivalent to firing the
280
306
  * task's `signal` (design/47). Refused typed (`steering.not_running`, the steer-family liveness code —
@@ -95,8 +95,12 @@ export interface ReversibilityVerdict {
95
95
  * spelled, which is the only one derivable from the text.
96
96
  */
97
97
  export interface ReadRootGrantCandidate {
98
- /** An absolute, lexically folded directory in the boundary's own spelling; never a file or a pattern. */
98
+ /** An absolute, lexically folded path in the boundary's own spelling; never a pattern. A DIRECTORY
99
+ * unless {@link covers} says otherwise. */
99
100
  readonly dir: string;
101
+ /** `"exact"` ⇒ {@link dir} is the path the command NAMED, offered verbatim rather than a directory
102
+ * containing it. A consumer ADDS the string itself: expanding it into a pattern grants nothing. */
103
+ readonly covers?: "exact";
100
104
  /** Always the literal `true` — a shape, not a value, so the seat cannot be widened into a "maybe this
101
105
  * helps" hint without a visible type change. It claims THIS ask: not the deny layer (judged by pattern
102
106
  * rather than by root, and unmoved by any grant), and not another ask the same call may raise. */
@@ -548,6 +548,11 @@ export type TraceEvent = {
548
548
  verdict: "allow" | "block" | "unavailable" | "parse_error";
549
549
  /** Present iff `verdict === "unavailable"`: `error` / `timeout`. */
550
550
  cause?: "error" | "timeout";
551
+ /** Present exactly when NO MODEL WAS ASKED because the question did not assemble: `timeout` = the
552
+ * assembly outran its cap, `failed` = it threw. The verdict reads `unavailable` / `error` either way
553
+ * (nothing was dispatched), and `ms` is then time spent BUILDING, not waiting on a provider — which is
554
+ * what separates "the transcript store is slow" from "the provider is down". */
555
+ notAsked?: "failed" | "timeout";
551
556
  ts: number;
552
557
  } | {
553
558
  /** C1 — the failover brain served this call from a FALLBACK entry (`createFailoverBrain`): the
@@ -18,7 +18,7 @@ export type { BeforeWriteRequest, BeforeWriteResult, BeforeWriteHook, TrackEditR
18
18
  export type { AgentDefinition, SkillManifest, SkillSpec } from "./agent-definition.js";
19
19
  export type { McpServerSpec, A2aServerSpec, McpElicitRequest, McpElicitResponse, OnElicit } from "./mcp-server-spec.js";
20
20
  export type { TaskLimits } from "./task-limits.js";
21
- export type { TaskStatus, RemoteEnvFailureNote, EffectiveMemoryScopes, TaskResult } from "./task-result.js";
21
+ export type { TaskStatus, RemoteEnvFailureNote, EffectiveMemoryScopes, TaskResult, HumanReviewGateRow } from "./task-result.js";
22
22
  export type { TerminalCause, PausedCause } from "./terminal-cause.js";
23
23
  export type { TaskEventIdentity, BrainStatusPhase, BrainRetryErrClass, BrainStatus, ToolActivity, HumanInputSource, HumanInputDelivery, DelegationTaskType, TaskEvent, HumanInputEvent, CompactOutcome } from "./task-event.js";
24
24
  export type { TaskStream } from "./task-stream.js";
@@ -27,6 +27,6 @@ export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./runtime-caps.js"
27
27
  export type { BackgroundChildEvent, DelegationLifecycleEvent } from "./delegation-frames.js";
28
28
  export { __resetMalformedDelegationSeatAnnouncement, deliverDelegationLifecycle } from "./delegation-frames.js";
29
29
  export type { ProjectMemoryLoad, ResumePreflightInfo, ResumePreflightVerdict, RunnerDeps } from "./runner-deps.js";
30
- export type { EngineNotice } from "./engine-notice.js";
31
- export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, undrainedUserInputNotices } from "./engine-notice.js";
30
+ export type { EngineNotice, OwedUserInputAccount } from "./engine-notice.js";
31
+ export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, settleOwedUserInput, undrainedUserInputNotices } from "./engine-notice.js";
32
32
  export type { AgentTool, Model, StreamFn, ThinkingLevel, ToolInputValidationContext, ToolInputVerdict };
@@ -1,3 +1,3 @@
1
1
  export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./runtime-caps.js";
2
2
  export { __resetMalformedDelegationSeatAnnouncement, deliverDelegationLifecycle } from "./delegation-frames.js";
3
- export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, undrainedUserInputNotices } from "./engine-notice.js";
3
+ export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, settleOwedUserInput, undrainedUserInputNotices } from "./engine-notice.js";
@@ -347,6 +347,26 @@ export interface WiringManifest {
347
347
  writeProtection?: {
348
348
  targetView: WriteProtectionTargetView;
349
349
  };
350
+ /**
351
+ * EFFECTIVE half only; the present-iff law is the contract sentence below. `builtinTiers` names the
352
+ * tiers that MATERIALLY activate rows, in {@link
353
+ * import("../tools/fs/read-deny.js").READ_DENY_BUILTIN_TIERS} order — not the tiers the operator
354
+ * typed: one whose every row `readDenyBuiltinExclude` removed is not named. Deliberately NOT
355
+ * reported: `readDenyPatterns`, deployment-supplied TEXTS rather than an enum, so absence means "no
356
+ * BUILT-IN row judges", never "no read is deny-judged". IN the fingerprint (the
357
+ * {@link WiringManifest.writeProtection} class): the selection follows the deployment seat, so legs
358
+ * of one assembly agree.
359
+ *
360
+ * @contract read_deny.builtin_activation_disclosed — WHETHER the built-in sensitive-path read deny
361
+ * table judges a leg is DISCLOSED, never inferred: the table is a deployment opt-in whose default
362
+ * selection is empty, the leg that compiles the judge relays the tiers its own resolution materially
363
+ * activated, and the manifest carries that section iff at least one built-in row is active (absence
364
+ * = no built-in row judges; an empty array is never emitted — "off" and "on to nothing" are one
365
+ * fact). The disclosure changes no verdict.
366
+ */
367
+ readDeny?: {
368
+ builtinTiers: readonly string[];
369
+ };
350
370
  /**
351
371
  * EFFECTIVE half only. PRESENT IFF auto-consolidation is EFFECTIVELY armed on this
352
372
  * deployment (`autoRunOnRecommendationEffective`: an explicit `true`, or an absent seat under the
@@ -441,6 +461,9 @@ export interface WiringFacts {
441
461
  /** Effective half only — the compiled write-protection judge's OWN target-view word
442
462
  * ({@link WiringManifest.writeProtection}); absent ⇒ this leg compiled no judge (an emptied table). */
443
463
  writeProtectionTargetView?: WriteProtectionTargetView;
464
+ /** Effective half only — the built-in read-deny TIERS this leg's configuration materially activated
465
+ * ({@link WiringManifest.readDeny}); empty and absent derive the same thing (no section). */
466
+ readDenyBuiltinTiersActive?: readonly string[];
444
467
  /** Effective half only — `RunnerDeps.autoRunOnRecommendation === true`
445
468
  * ({@link WiringManifest.autoConsolidation}); false ⇒ no section (byte-identical manifest). */
446
469
  autoConsolidationOnRecommendation?: boolean;
@@ -142,6 +142,9 @@ export function deriveWiringManifest(facts) {
142
142
  ...(facts.half === "effective" && facts.hooks !== undefined && facts.hooks.length > 0 ? { hooks: facts.hooks.map((h) => ({ ...h })) } : {}),
143
143
  ...(facts.half === "effective" && facts.lsp !== undefined ? { lsp: { ...facts.lsp } } : {}),
144
144
  ...(facts.half === "effective" && facts.writeProtectionTargetView !== undefined ? { writeProtection: { targetView: facts.writeProtectionTargetView } } : {}),
145
+ ...(facts.half === "effective" && facts.readDenyBuiltinTiersActive !== undefined && facts.readDenyBuiltinTiersActive.length > 0
146
+ ? { readDeny: { builtinTiers: [...facts.readDenyBuiltinTiersActive] } }
147
+ : {}),
145
148
  ...(facts.half === "effective" && facts.autoConsolidationOnRecommendation === true ? { autoConsolidation: { onRecommendation: true } } : {}),
146
149
  };
147
150
  if (facts.half === "effective") {