@sema-agent/core 7.21.1 → 7.22.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 (79) hide show
  1. package/CHANGELOG.md +123 -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-mode-rebuild.js +6 -2
  8. package/dist/core/auto-mode.d.ts +40 -18
  9. package/dist/core/auto-mode.js +63 -45
  10. package/dist/core/checkpoint-store.d.ts +8 -8
  11. package/dist/core/engine-notice.d.ts +49 -1
  12. package/dist/core/engine-notice.js +14 -1
  13. package/dist/core/governance-codes.d.ts +1 -1
  14. package/dist/core/governance-codes.js +4 -0
  15. package/dist/core/mcp.d.ts +65 -0
  16. package/dist/core/mcp.js +190 -41
  17. package/dist/core/memory-engine/consolidation.d.ts +5 -0
  18. package/dist/core/memory-engine/consolidation.js +7 -3
  19. package/dist/core/memory-engine/engine.d.ts +8 -0
  20. package/dist/core/memory-engine/engine.js +25 -19
  21. package/dist/core/memory-engine/layout.d.ts +47 -9
  22. package/dist/core/memory-engine/layout.js +157 -57
  23. package/dist/core/memory-engine/session-incarnation.d.ts +27 -0
  24. package/dist/core/memory-engine/session-incarnation.js +63 -0
  25. package/dist/core/parent-spec-seat.d.ts +15 -3
  26. package/dist/core/parent-spec-seat.js +5 -0
  27. package/dist/core/permission-rule-model.d.ts +1 -2
  28. package/dist/core/retention.d.ts +8 -1
  29. package/dist/core/retention.js +2 -0
  30. package/dist/core/runner/classifier-ladder.js +1 -0
  31. package/dist/core/runner/contracts.d.ts +11 -0
  32. package/dist/core/runner/mcp-redial.d.ts +109 -0
  33. package/dist/core/runner/mcp-redial.js +60 -0
  34. package/dist/core/runner/memory-consolidation-driver.d.ts +2 -0
  35. package/dist/core/runner/prepare-caps-and-workflow.js +9 -12
  36. package/dist/core/runner/prepare-hands-readface.d.ts +3 -0
  37. package/dist/core/runner/prepare-hands-readface.js +13 -3
  38. package/dist/core/runner/prepare-memory.js +3 -3
  39. package/dist/core/runner/prepare-protocol-tools.d.ts +6 -1
  40. package/dist/core/runner/prepare-protocol-tools.js +20 -38
  41. package/dist/core/runner/prepare-task.js +3 -3
  42. package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -0
  43. package/dist/core/runner/prepare-wiring-manifest.js +2 -1
  44. package/dist/core/runner/run-identity-wiring.d.ts +3 -1
  45. package/dist/core/runner/run-identity-wiring.js +4 -1
  46. package/dist/core/runner/run-settle-and-teardown.d.ts +3 -1
  47. package/dist/core/runner/run-settle-and-teardown.js +6 -2
  48. package/dist/core/runner/run-terminal-adoption.d.ts +4 -1
  49. package/dist/core/runner/run-terminal-adoption.js +2 -6
  50. package/dist/core/runner/runtask.js +8 -5
  51. package/dist/core/runner/stream-lifecycle-verbs.d.ts +5 -1
  52. package/dist/core/runner/stream-lifecycle-verbs.js +23 -2
  53. package/dist/core/runner-deps.d.ts +16 -7
  54. package/dist/core/skill-listing-budget.d.ts +5 -4
  55. package/dist/core/task-result.d.ts +3 -2
  56. package/dist/core/task-spec.d.ts +2 -1
  57. package/dist/core/task-stream.d.ts +26 -0
  58. package/dist/core/tool-spec.d.ts +5 -1
  59. package/dist/core/trace.d.ts +5 -0
  60. package/dist/core/types.d.ts +2 -2
  61. package/dist/core/types.js +1 -1
  62. package/dist/core/wiring-manifest.d.ts +23 -0
  63. package/dist/core/wiring-manifest.js +3 -0
  64. package/dist/index.d.ts +4 -3
  65. package/dist/index.js +2 -2
  66. package/dist/orchestration/workflow-governance.js +1 -1
  67. package/dist/orchestration/workflow-primitives.d.ts +2 -2
  68. package/dist/tools/fs/bash-program-position.d.ts +26 -5
  69. package/dist/tools/fs/bash-program-position.js +63 -16
  70. package/dist/tools/fs/bash-readonly-classifier.d.ts +27 -17
  71. package/dist/tools/fs/bash-readonly-classifier.js +134 -55
  72. package/dist/tools/fs/fs-bash.js +43 -18
  73. package/dist/tools/fs/index.d.ts +4 -2
  74. package/dist/tools/fs/read-deny.d.ts +25 -11
  75. package/dist/tools/fs/read-deny.js +5 -1
  76. package/dist/tools/fs/safety.d.ts +1 -4
  77. package/package.json +1 -1
  78. package/test/export-surface.snapshot.json +9 -1
  79. package/wire-changes/core@7.22.0.json +637 -0
@@ -0,0 +1,109 @@
1
+ /**
2
+ * The LIVE MCP roster lane: applying one server's intake result to a RUNNING roster, and the
3
+ * host-driven re-dial that is the second caller of it.
4
+ *
5
+ * Two faces change a task's MCP roster after prepare has finished — the model's `RefreshMcpTools`
6
+ * tool and the run handle's `reconnectMcpServer` verb — and what they must do to the roster is the
7
+ * same list in the same order: drop the deployment-excluded names, re-fold the safety axes (with the
8
+ * non-monotone restore arm on refusal), replace the server's whole name-prefix domain at its own
9
+ * anchor, rebuild the harness toolset, and re-mint the roster delta. That list lived inside the
10
+ * refresh tool's execute closure, where the second face could only have copied it; a copy would have
11
+ * been a second answer to "what does a mid-task roster change consist of".
12
+ *
13
+ * It is machinery, not a phase: the protocol-tools phase reaches DOWN for it to build the refresh
14
+ * tool, and the run's verb wiring reaches down for the same functions.
15
+ */
16
+ import type { AgentTool } from "../../internal/harness.js";
17
+ import type { McpDroppedTool, McpReconnectResult, MaterializedMcp, McpToolAxis } from "../mcp.js";
18
+ import type { RunnerDeps, ToolEffect } from "../types.js";
19
+ import type { IrreversibilityTier, ToolFaceSnapshot } from "./contracts.js";
20
+ import type { RosterBuilder, ToolRosterDeltaSeat } from "../tool-roster.js";
21
+ /** One server's assertion about its own prefix domain — the shape both faces reduce to. `tools` is
22
+ * the roster the domain must END UP with (empty withdraws it); `added`/`removed` are the reporting
23
+ * half and never drive the swap. */
24
+ export interface McpRosterDelta {
25
+ server: string;
26
+ prefix: string;
27
+ tools: AgentTool[];
28
+ axes: McpToolAxis[];
29
+ dropped: McpDroppedTool[];
30
+ }
31
+ /** The seats a roster application writes through. Every one of them is borrowed from the phase that
32
+ * minted it — this module owns nothing. */
33
+ export interface McpRosterApplyInput {
34
+ /** borrowed-readonly — the frozen tool face; `exclude` keeps a deployment-excluded tool from ever
35
+ * being re-mounted by a face the model or a host can trigger. */
36
+ toolFaceSnapshot: Pick<ToolFaceSnapshot, "exclude">;
37
+ /** borrowed-readonly — the per-server face overlay (the phase's `withFaces`). */
38
+ withFaces: (tools: AgentTool[], server: string) => AgentTool[];
39
+ /** borrowed-readonly — the large-result wrapper every protocol tool is mounted through. */
40
+ remoteToolOffload: (tool: AgentTool) => AgentTool;
41
+ /** borrowed-readonly — the phase's axis fold (throws on a config contradiction). */
42
+ foldProtocolAxes: (axes: McpToolAxis[], label: string) => void;
43
+ /** borrowed-mutable — the two NON-MONOTONE tables the fold writes and this applier snapshots. */
44
+ toolEffects: Map<string, ToolEffect>;
45
+ axisExplicitNegatives: Map<string, {
46
+ irreversible?: false;
47
+ egress?: false;
48
+ }>;
49
+ /** borrowed-mutable — the run's shared roster. */
50
+ roster: RosterBuilder;
51
+ }
52
+ /** What one application did, for the caller's receipt. `applied:false` means the axis fold refused
53
+ * and the domain was left exactly as it was (the tables restored) — no roster write happened. */
54
+ export type McpRosterApplyOutcome = {
55
+ applied: true;
56
+ pushed: number;
57
+ excluded: string[];
58
+ } | {
59
+ applied: false;
60
+ foldError: string;
61
+ };
62
+ /**
63
+ * Apply one server's delta to the live roster. Behavior-identical to the body this was extracted
64
+ * from; the comments are the extracted reasoning, not new claims.
65
+ */
66
+ export declare function applyMcpRosterDelta(input: McpRosterApplyInput, delta: McpRosterDelta): McpRosterApplyOutcome;
67
+ /** The seats the post-application commit writes through. */
68
+ export interface McpRosterCommitInput {
69
+ rebuildHarnessToolsRef: {
70
+ current?: () => Promise<void>;
71
+ };
72
+ rosterSeat: ToolRosterDeltaSeat;
73
+ toolCallGateArmedRef: {
74
+ armed: boolean;
75
+ };
76
+ irreversibleTools: ReadonlySet<string>;
77
+ egressTools: ReadonlySet<string>;
78
+ }
79
+ /**
80
+ * Publish whatever the applications above changed: rebuild the harness toolset (the content-origin
81
+ * re-wrap runs inside it), then re-mint the roster delta so the stream's rows describe the LIVE
82
+ * objects. Batched deliberately — a refresh spanning several servers rebuilds once.
83
+ *
84
+ * Returns the gate-honesty line when one is due. The built-in integrity policies are composed
85
+ * UNCONDITIONALLY, so `armed` is always true in this engine and the line never fires today; the arm
86
+ * stays as a zero-cost guard for a refactor that would make the composition conditional.
87
+ */
88
+ export declare function commitMcpRosterChanges(input: McpRosterCommitInput): Promise<string | undefined>;
89
+ /** The re-dial's own seats: the handle it drives, the two operator sinks, and the run identity the
90
+ * disclosure names. */
91
+ export interface McpRedialInput extends McpRosterApplyInput, McpRosterCommitInput {
92
+ mcp: MaterializedMcp;
93
+ deps: Pick<RunnerDeps, "onError" | "onNotice">;
94
+ sessionId: string;
95
+ runId: string;
96
+ irreversibilityTier: IrreversibilityTier;
97
+ }
98
+ /**
99
+ * The run handle's `reconnectMcpServer` verb, phase-side: re-dial one server, put whatever it
100
+ * asserted about its prefix domain onto the live roster, and disclose.
101
+ *
102
+ * The roster is written IFF the result carries `tools` — the one rule this lane reads. That covers
103
+ * both directions with no second condition: a successful re-dial asserts its fresh roster, a failed
104
+ * DIAL asserts the empty one (the connection it tore down is gone, so leaving its names mounted would
105
+ * leave the model holding tools that can only fail), and an arm that never touched the connection —
106
+ * unknown name, revoked server, re-dial already in flight, disposed materialization — asserts nothing
107
+ * and the mounted roster is left alone.
108
+ */
109
+ export declare function redialMcpServer(input: McpRedialInput, server: string): Promise<McpReconnectResult>;
@@ -0,0 +1,60 @@
1
+ import { deliverEngineNotice } from "../types.js";
2
+ export function applyMcpRosterDelta(input, delta) {
3
+ const { toolFaceSnapshot, withFaces, remoteToolOffload, foldProtocolAxes, toolEffects, axisExplicitNegatives, roster } = input;
4
+ const excludedSet = new Set(toolFaceSnapshot.exclude ?? []);
5
+ const pushable = delta.tools.filter((t) => !excludedSet.has(t.name));
6
+ const excluded = delta.tools.filter((t) => excludedSet.has(t.name)).map((t) => t.name);
7
+ const domainSnapshot = (m) => new Map([...m].filter(([name]) => name.startsWith(delta.prefix)));
8
+ const restoreDomain = (m, snap) => {
9
+ for (const name of [...m.keys()])
10
+ if (name.startsWith(delta.prefix))
11
+ m.delete(name);
12
+ for (const [name, v] of snap)
13
+ m.set(name, v);
14
+ };
15
+ const priorDomainEffects = domainSnapshot(toolEffects);
16
+ const priorDomainNegatives = domainSnapshot(axisExplicitNegatives);
17
+ for (const name of priorDomainEffects.keys())
18
+ toolEffects.delete(name);
19
+ for (const name of priorDomainNegatives.keys())
20
+ axisExplicitNegatives.delete(name);
21
+ try {
22
+ foldProtocolAxes(delta.axes.filter((a) => !excludedSet.has(a.name)), "MCP");
23
+ }
24
+ catch (foldErr) {
25
+ restoreDomain(toolEffects, priorDomainEffects);
26
+ restoreDomain(axisExplicitNegatives, priorDomainNegatives);
27
+ return { applied: false, foldError: foldErr instanceof Error ? foldErr.message : String(foldErr) };
28
+ }
29
+ roster.replaceDomain(delta.prefix, withFaces(pushable, delta.server).map((t) => remoteToolOffload(t)), { source: "mcp", mountedBy: "optional", origin: { peer: delta.server } });
30
+ return { applied: true, pushed: pushable.length, excluded };
31
+ }
32
+ export async function commitMcpRosterChanges(input) {
33
+ await input.rebuildHarnessToolsRef.current?.();
34
+ input.rosterSeat.republish();
35
+ if (!input.toolCallGateArmedRef.armed && (input.irreversibleTools.size > 0 || input.egressTools.size > 0)) {
36
+ return "WARNING: refreshed tools declare irreversible/egress safety hints, but this task started with no approval gate registered (no policy/hooks and no gated tools at start) — these hints cannot arm a gate mid-task (RB-46); the refreshed tools run ungated on this deployment shape.";
37
+ }
38
+ return undefined;
39
+ }
40
+ export async function redialMcpServer(input, server) {
41
+ const result = await input.mcp.reconnect(server);
42
+ if (result.tools !== undefined) {
43
+ const outcome = applyMcpRosterDelta(input, { server: result.server, prefix: result.prefix, tools: result.tools, axes: result.axes ?? [], dropped: result.dropped ?? [] });
44
+ if (!outcome.applied)
45
+ input.deps.onError?.(Object.assign(new Error(`MCP re-dial of "${server}" connected but its safety-axis fold refused, so its tools were not mounted: ${outcome.foldError}`), { code: "mcp.server_unavailable" }), { phase: "mcp", sessionId: input.sessionId });
46
+ const ungated = await commitMcpRosterChanges(input);
47
+ if (ungated !== undefined)
48
+ input.deps.onError?.(Object.assign(new Error(ungated), { code: "mcp.server_unavailable" }), { phase: "mcp", sessionId: input.sessionId });
49
+ }
50
+ for (const w of result.warnings ?? [])
51
+ input.deps.onError?.(Object.assign(new Error(w), { code: "mcp.server_unavailable" }), { phase: "mcp", sessionId: input.sessionId });
52
+ deliverEngineNotice(input.deps.onNotice, {
53
+ code: "mcp.server_redialed",
54
+ message: result.outcome === "reconnected"
55
+ ? `MCP server "${server}" was re-dialed at the host's request: ${result.toolCount} tool${result.toolCount === 1 ? "" : "s"} mounted (${result.added.length} added, ${result.removed.length} removed)`
56
+ : `MCP server "${server}" was NOT re-dialed (${result.outcome})${result.reason !== undefined ? `: ${result.reason}` : ""}`,
57
+ detail: { server, outcome: result.outcome, added: result.added.length, removed: result.removed.length, ...(result.reason !== undefined ? { reason: result.reason } : {}), sessionId: input.sessionId, runId: input.runId },
58
+ });
59
+ return result;
60
+ }
@@ -94,6 +94,8 @@ export declare const AUTO_RUN_ON_RECOMMENDATION_DEFAULT = true;
94
94
  * switch wired AND a model seat resolvable) — the reference CLI's "auto-dream: off while
95
95
  * auto-memory is off": a default-on world must not arm a deployment that wired no consolidation;
96
96
  * - any other value ⇒ off (never arm on garbage; the door refuses it at prepare anyway).
97
+ *
98
+ * @contract memory.auto_consolidation.arming_has_one_resolver — whether a deployment's automatic consolidation is armed is answered here and nowhere else, a host that merely DISPLAYS the arming included: an ABSENT seat is not "off" (it reads the named default, and only where both seats are wired), so a reader branching on `RunnerDeps.autoRunOnRecommendation` alone is wrong in BOTH directions — it calls a default-armed deployment unarmed and a deployment with no consolidation armed. A deployment fact, never a per-session one; exported from the package root for that reason.
97
99
  */
98
100
  export declare function autoRunOnRecommendationEffective(deps: MemoryConsolidationDriverDeps & {
99
101
  autoRunOnRecommendation?: unknown;
@@ -7,7 +7,7 @@ import { isSelfOrchestrationActive } from "../../orchestration/workflow-script-r
7
7
  import { resolveWorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
8
8
  import { autoModeArmingRecipeOf } from "../auto-mode-arming.js";
9
9
  import { buildAutoModePrompt, renderAutoModeAction, renderAutoModeWindow } from "../auto-mode-prompt.js";
10
- import { autoModeClassifierFallbackOf, autoModeRoundNotAsked, createAutoModeDecider, createAutoModeDenialTracker } from "../auto-mode.js";
10
+ import { autoModeClassifierFallbackOf, createAutoModeDecider, createAutoModeDenialTracker } from "../auto-mode.js";
11
11
  import { checkpointStoreOfSeat } from "../checkpoint-seat.js";
12
12
  import { COMPLIANCE_CAPABILITIES, WEB_FETCH_TOOL_NAME, resolveComplianceDenies } from "../compliance.js";
13
13
  import { createPresentPlanTool, createEnterPlanModeTool } from "../present-plan-tool.js";
@@ -207,27 +207,24 @@ export async function prepareCapsAndWorkflow(input) {
207
207
  const classifierSystemPrompt = buildAutoModePrompt(classifierLaneRule ? { ...am, crossSessionMessagesRule: CROSS_SESSION_CLASSIFIER_RULE } : am);
208
208
  const classifierRuntime = brainToRuntime(deps.brain);
209
209
  const classifierRungs = classifierLadderRungs(am.classifierFallback, classifierModel, model);
210
- const roundPrompt = new WeakMap();
211
210
  autoModeDenialTracking = createAutoModeDenialTracker(am.denialLimit);
212
211
  autoModeDecider = createAutoModeDecider({
213
212
  ...(am.timeoutMs !== undefined ? { timeoutMs: am.timeoutMs } : {}),
214
213
  candidates: classifierRungs.map((m) => m.id),
215
214
  onClassified: classifierRoundObserver({ tracer: deps.tracer, onNotice: deps.onNotice, taskId: hostTaskId, seatModelId: classifierModel.id, ...(sessionId !== undefined ? { sessionId } : {}), ...(runId !== undefined ? { runId } : {}) }),
216
- classify: async (input, signal, rung) => {
215
+ assemble: async (input) => {
216
+ const ctx = await session.buildContext();
217
+ const known = ctx.messages.filter((m) => !isSyntheticApiErrorMessage(m) && (m.role === "user" || m.role === "assistant" || m.role === "toolResult"));
218
+ return renderAutoModeWindow(known, am.window) + renderAutoModeAction(input);
219
+ },
220
+ classify: async (_input, signal, rung, userPrompt) => {
217
221
  const rungModel = rung !== undefined ? classifierRungs[rung.index] : undefined;
218
222
  if (rung === undefined || rungModel === undefined) {
219
223
  throw new Error(`the auto-mode classifier was asked at ladder position ${JSON.stringify(rung?.index)}, which this arming did not build`);
220
224
  }
221
- let build = rung.index > 0 ? roundPrompt.get(input) : undefined;
222
- if (build === undefined) {
223
- build = (async () => {
224
- const ctx = await session.buildContext();
225
- const known = ctx.messages.filter((m) => !isSyntheticApiErrorMessage(m) && (m.role === "user" || m.role === "assistant" || m.role === "toolResult"));
226
- return renderAutoModeWindow(known, am.window) + renderAutoModeAction(input);
227
- })();
228
- roundPrompt.set(input, build);
225
+ if (userPrompt === undefined) {
226
+ throw new Error("the auto-mode classifier was asked with no assembled question, which this arming builds once per classification");
229
227
  }
230
- const userPrompt = await build.catch((e) => { throw autoModeRoundNotAsked(e); });
231
228
  const classifierAuth = await spec.getApiKeyAndHeaders?.(rungModel);
232
229
  const request = async (seat, suffix) => {
233
230
  const response = await classifierRuntime.completeSimple(rungModel, { systemPrompt: classifierSystemPrompt, messages: [{ role: "user", content: userPrompt + suffix, timestamp: Date.now() }] }, {
@@ -284,6 +284,9 @@ export interface PrepareHandsReadFaceResult {
284
284
  pattern: string;
285
285
  caseSensitive: boolean;
286
286
  }>;
287
+ /** owned — the built-in deny TIERS this leg MATERIALLY activated, off the same snapshot the compile
288
+ * judged with; empty ⇒ none. Relayed to the wiring manifest, never re-derived from `deps` there. */
289
+ readDenyBuiltinTiersActive: readonly string[];
287
290
  /** write-back (D-3 hold) — the hands-LESS legs' resolved face (the read posture is a
288
291
  * property of the RUN, not the mount). Driver assigns onto its outer `let`. */
289
292
  handsLessResolvedFace: ContainmentFace | undefined;
@@ -3,7 +3,7 @@ import { StubExecutionEnv } from "../stub-env.js";
3
3
  import { hasBackgroundShell } from "../background-shell.js";
4
4
  import { boundaryIsTheSandbox } from "../remote-env.js";
5
5
  import { defaultTaskRegistry } from "../task-registry.js";
6
- import { bashReadBoundaryProbe, bashReversibilityProbe, compileReadDeny, createHandsToolkit, deploymentFaceClampNotice, pdfModelCapabilitiesOf, resolveReadDenyBuiltins, inheritSessionReadFileState, resolveContainmentFace, seedReadFileStateFromContext, seedReadFileStateFromTranscript, } from "../../tools/fs/index.js";
6
+ import { bashReadBoundaryProbe, bashReversibilityProbe, activeReadDenyBuiltinTiers, compileReadDeny, createHandsToolkit, deploymentFaceClampNotice, pdfModelCapabilitiesOf, inheritSessionReadFileState, resolveContainmentFace, seedReadFileStateFromContext, seedReadFileStateFromTranscript, } from "../../tools/fs/index.js";
7
7
  import { resolveKey, sha256 } from "../../tools/fs/safety.js";
8
8
  import { normalizeFileText } from "../../tools/fs/encoding.js";
9
9
  import { wholeFileRecordsFromTranscript } from "./session-file-state-replay.js";
@@ -98,6 +98,7 @@ export function resolveHandsLessReadFace(input) {
98
98
  let handsLessResolvedFace;
99
99
  let handsLessResolvedWriteFace;
100
100
  let readDenyAdditionsNormalized = [];
101
+ let readDenyBuiltinTiersActive = [];
101
102
  {
102
103
  const liveFace = resolveContainmentFace({
103
104
  kind: "read",
@@ -120,7 +121,7 @@ export function resolveHandsLessReadFace(input) {
120
121
  const seedWrite = resume?.seed.writeFace;
121
122
  handsLessResolvedWriteFace = resume !== undefined && (seedWrite === undefined || seedWrite.face === "roots") ? "roots" : liveWriteFace;
122
123
  const denyAdditions = [...(deps.readDenyPatterns ?? []), ...(spec.readDenyPatterns ?? []), ...(seed?.denyEntries ?? [])];
123
- resolveReadDenyBuiltins({
124
+ readDenyBuiltinTiersActive = activeReadDenyBuiltinTiers({
124
125
  ...(deps.readDenyBuiltinTiers !== undefined ? { tiers: deps.readDenyBuiltinTiers } : {}),
125
126
  ...(deps.readDenyBuiltinExclude !== undefined ? { exclude: deps.readDenyBuiltinExclude } : {}),
126
127
  });
@@ -139,6 +140,7 @@ export function resolveHandsLessReadFace(input) {
139
140
  resolvedReadFace: undefined,
140
141
  resolvedWriteFace: undefined,
141
142
  readDenyAdditionsNormalized,
143
+ readDenyBuiltinTiersActive,
142
144
  handsLessResolvedFace,
143
145
  handsLessResolvedWriteFace,
144
146
  additionalRootsCanonical: [],
@@ -168,6 +170,7 @@ export async function prepareHandsMount(input) {
168
170
  let resolvedReadFace;
169
171
  let resolvedWriteFace;
170
172
  let readDenyAdditionsNormalized = [];
173
+ let readDenyBuiltinTiersActive = [];
171
174
  let handsLessResolvedFace;
172
175
  const handsLessResolvedWriteFace = undefined;
173
176
  const shellGatedTools = new Set();
@@ -245,6 +248,7 @@ export async function prepareHandsMount(input) {
245
248
  ...(deps.readDenyBuiltinExclude !== undefined ? { exclude: [...deps.readDenyBuiltinExclude] } : {}),
246
249
  };
247
250
  readDenyMatcher = compileReadDeny(readDenyAdditions, "readDenyPatterns", readDenyBuiltinCfg);
251
+ readDenyBuiltinTiersActive = activeReadDenyBuiltinTiers(readDenyBuiltinCfg);
248
252
  readDenyAdditionsNormalized = compileReadDeny(readDenyAdditions, "readDenyPatterns", { tiers: [] }).entries;
249
253
  let liveReadFace = resolveContainmentFace({
250
254
  kind: "read",
@@ -323,11 +327,16 @@ export async function prepareHandsMount(input) {
323
327
  sessionSeats.reanchor(sessionId, readFileState, ownCommittedTailEntryId);
324
328
  }
325
329
  : undefined;
330
+ const compiledDeny = readDenyMatcher;
331
+ const denySeatJudge = compiledDeny !== undefined && compiledDeny.entries.length > 0
332
+ ? (p) => compiledDeny.matchPath(p)?.pattern ?? null
333
+ : undefined;
326
334
  const shellReadBoundary = () => ({
327
335
  roots: [rootCanonical, ...additionalRootsCanonical, ...additionalReadRootsCanonical],
328
336
  ...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}),
329
337
  ...(executionEnv.homeDir !== undefined ? { homeDir: executionEnv.homeDir } : {}),
330
- denyMatch: (p) => readDenyMatcher?.matchPath(p)?.pattern ?? null,
338
+ ...(denySeatJudge !== undefined ? { denyMatch: denySeatJudge } : {}),
339
+ denyEntries: (compiledDeny?.entries ?? []).map((e) => e.pattern),
331
340
  ...(resolvedReadFace !== undefined ? { face: resolvedReadFace } : {}),
332
341
  });
333
342
  const band = createHandsToolkit(executionEnv, readFileState, rootCanonical, {
@@ -421,6 +430,7 @@ export async function prepareHandsMount(input) {
421
430
  resolvedWriteFace,
422
431
  handsLessResolvedWriteFace,
423
432
  readDenyAdditionsNormalized,
433
+ readDenyBuiltinTiersActive,
424
434
  handsLessResolvedFace,
425
435
  additionalRootsCanonical,
426
436
  additionalReadRootsCanonical,
@@ -171,7 +171,7 @@ export async function prepareMemory(input) {
171
171
  const choosePersonalBackend = () => typeof pinned === "string" && pinned ? new FileMemoryEngineBackend(personalMemoryDir, { controlDir: personalControlDir }) : backend;
172
172
  const createPersonalEngine = (personalBackend) => {
173
173
  return {
174
- engine: new MemoryEngine({ backend: personalBackend, memoryDir: personalMemoryDir, controlDir: personalControlDir, onIncident: onEngineIncident, provenance: input.memoryProvenance, ...captureStoreOpt(personalBackend, personalControlDir), ...(input.deps.memoryConsolidation !== undefined ? { consolidation: input.deps.memoryConsolidation } : {}) }),
174
+ engine: new MemoryEngine({ backend: personalBackend, memoryDir: personalMemoryDir, controlDir: personalControlDir, onIncident: onEngineIncident, provenance: input.memoryProvenance, ...captureStoreOpt(personalBackend, personalControlDir), ...(deps.memorySessionIncarnation !== undefined ? { sessionIncarnation: deps.memorySessionIncarnation } : {}), ...(input.deps.memoryConsolidation !== undefined ? { consolidation: input.deps.memoryConsolidation } : {}) }),
175
175
  backend: personalBackend,
176
176
  };
177
177
  };
@@ -359,7 +359,7 @@ export async function prepareMemory(input) {
359
359
  controlDir: identityKey !== undefined ? deriveProjectControlDir(engineRoot, identityKey) : deriveRepoControlPlaneDir(engineRoot, repoRoot),
360
360
  onIncident: onEngineIncident,
361
361
  provenance: input.memoryProvenance,
362
- ...captureStoreOpt(backend, identityKey !== undefined ? deriveProjectControlDir(engineRoot, identityKey) : deriveRepoControlPlaneDir(engineRoot, repoRoot)),
362
+ ...captureStoreOpt(backend, identityKey !== undefined ? deriveProjectControlDir(engineRoot, identityKey) : deriveRepoControlPlaneDir(engineRoot, repoRoot)), ...(deps.memorySessionIncarnation !== undefined ? { sessionIncarnation: deps.memorySessionIncarnation } : {}),
363
363
  ...(input.deps.memoryConsolidation !== undefined ? { consolidation: input.deps.memoryConsolidation } : {}),
364
364
  });
365
365
  const personal = createPersonalEngine(personalBackendChosen);
@@ -439,7 +439,7 @@ export async function prepareMemory(input) {
439
439
  controlDir: identityKey !== undefined ? deriveProjectControlDir(engineRoot, identityKey) : deriveRepoControlPlaneDir(engineRoot, repoRoot),
440
440
  onIncident: onEngineIncident,
441
441
  provenance: input.memoryProvenance,
442
- ...captureStoreOpt(backend, identityKey !== undefined ? deriveProjectControlDir(engineRoot, identityKey) : deriveRepoControlPlaneDir(engineRoot, repoRoot)),
442
+ ...captureStoreOpt(backend, identityKey !== undefined ? deriveProjectControlDir(engineRoot, identityKey) : deriveRepoControlPlaneDir(engineRoot, repoRoot)), ...(deps.memorySessionIncarnation !== undefined ? { sessionIncarnation: deps.memorySessionIncarnation } : {}),
443
443
  ...(input.deps.memoryConsolidation !== undefined ? { consolidation: input.deps.memoryConsolidation } : {}),
444
444
  });
445
445
  await armCaptureOptOut(engine);
@@ -1,5 +1,5 @@
1
1
  import type { AgentTool } from "../../internal/harness.js";
2
- import { type MaterializedMcp, type McpDroppedTool } from "../mcp.js";
2
+ import { type MaterializedMcp, type McpDroppedTool, type McpReconnectResult } from "../mcp.js";
3
3
  import { type MaterializedA2a } from "../a2a.js";
4
4
  import type { LockedPreflight } from "../locked-config.js";
5
5
  import type { RunnerDeps, TaskSpec, ToolEffect } from "../types.js";
@@ -90,6 +90,11 @@ export interface PrepareProtocolToolsResult {
90
90
  toolCallGateArmedRef: {
91
91
  armed: boolean;
92
92
  };
93
+ /** owned — the run handle's `reconnectMcpServer` implementation, present ONLY when this leg declared
94
+ * MCP servers (a leg with none has nothing to re-dial, and the stream verb refuses typed rather
95
+ * than answering about a materialization that was never made). The driver projects it onto
96
+ * `Prepared.mcpRedial`, from which the run's wiring binds the verb's seat. */
97
+ mcpRedial?: (server: string) => Promise<McpReconnectResult>;
93
98
  }
94
99
  /** The M5 phase body — prepareTask's protocol-tools stretch, verbatim (see the module header). */
95
100
  export declare function prepareProtocolTools(input: PrepareProtocolToolsInput): Promise<PrepareProtocolToolsResult>;
@@ -1,5 +1,6 @@
1
1
  import { Type } from "typebox";
2
2
  import { materializeMcpTools } from "../mcp.js";
3
+ import { applyMcpRosterDelta, commitMcpRosterChanges, redialMcpServer } from "./mcp-redial.js";
3
4
  import { materializeA2aTools } from "../a2a.js";
4
5
  import { deliverEngineNotice } from "../types.js";
5
6
  import { RosterBuilder } from "../tool-roster.js";
@@ -16,7 +17,8 @@ export async function prepareProtocolTools(input) {
16
17
  }
17
18
  const mcp = lockedPreflight.mcp?.length
18
19
  ? await materializeMcpTools(lockedPreflight.mcp, spec.principal, deps.onElicit, deps.mcpImageResizer, { reminderMark, counts: reminderDisclosureCounts }, mcpRevocationWiring(deps, runId))
19
- : { tools: [], toolAxes: [], warnings: [], serverInstructions: [], instructionsDelta: { pendingAdds: [], pendingRemovals: [] }, droppedTools: [], statuses: [], refresh: async () => [], dispose: async () => { } };
20
+ :
21
+ { tools: [], toolAxes: [], warnings: [], serverInstructions: [], instructionsDelta: { pendingAdds: [], pendingRemovals: [] }, droppedTools: [], statuses: [], refresh: async () => [], reconnect: async (server) => ({ server, prefix: mintNamespacePrefix(MCP_NAMESPACE, server), outcome: "not_declared", reason: "this task declared no MCP servers", toolCount: 0, added: [], removed: [] }), dispose: async () => { } };
20
22
  if (lockedPreflight.mcp?.length)
21
23
  rollback.push(function mcpDispose() { return mcp.dispose(); });
22
24
  for (const w of mcp.warnings)
@@ -58,6 +60,10 @@ export async function prepareProtocolTools(input) {
58
60
  const rebuildHarnessToolsRef = {};
59
61
  const contentOriginWrapRef = {};
60
62
  const toolCallGateArmedRef = { armed: false };
63
+ const rosterApply = { toolFaceSnapshot, withFaces, remoteToolOffload, foldProtocolAxes: (axes, label) => foldProtocolAxes(axes, label), toolEffects, axisExplicitNegatives, roster };
64
+ const rosterCommit = { rebuildHarnessToolsRef, rosterSeat, toolCallGateArmedRef, irreversibleTools, egressTools };
65
+ const redialInput = { ...rosterApply, ...rosterCommit, mcp, deps, sessionId, runId, irreversibilityTier };
66
+ const mcpRedial = lockedPreflight.mcp?.length ? (server) => redialMcpServer(redialInput, server) : undefined;
61
67
  if (lockedPreflight.mcp?.length) {
62
68
  roster.mount({
63
69
  ...toolFace("refresh-mcp-tools"),
@@ -73,42 +79,20 @@ export async function prepareProtocolTools(input) {
73
79
  const lines = [];
74
80
  let changed = false;
75
81
  let anyActiveFailure = false;
82
+ const excludedSet = new Set(toolFaceSnapshot.exclude ?? []);
76
83
  for (const r of results) {
77
- if (r.status !== "refreshed" || r.tools === undefined) {
84
+ if (r.tools === undefined) {
78
85
  lines.push(`${r.server}: ${r.status}${r.error !== undefined ? ` (${r.error})` : ""}`);
79
86
  if (r.status === "failed")
80
87
  anyActiveFailure = true;
81
88
  continue;
82
89
  }
83
- const excludedSet = new Set(toolFaceSnapshot.exclude ?? []);
84
- const pushable = r.tools.filter((t) => !excludedSet.has(t.name));
85
- const excludedNow = r.tools.filter((t) => excludedSet.has(t.name)).map((t) => t.name);
86
- const domainSnapshot = (m) => new Map([...m].filter(([name]) => name.startsWith(r.prefix)));
87
- const restoreDomain = (m, snap) => {
88
- for (const name of [...m.keys()])
89
- if (name.startsWith(r.prefix))
90
- m.delete(name);
91
- for (const [name, v] of snap)
92
- m.set(name, v);
93
- };
94
- const priorDomainEffects = domainSnapshot(toolEffects);
95
- const priorDomainNegatives = domainSnapshot(axisExplicitNegatives);
96
- for (const name of priorDomainEffects.keys())
97
- toolEffects.delete(name);
98
- for (const name of priorDomainNegatives.keys())
99
- axisExplicitNegatives.delete(name);
100
- try {
101
- foldProtocolAxes((r.axes ?? []).filter((a) => !excludedSet.has(a.name)), "MCP");
102
- }
103
- catch (foldErr) {
104
- restoreDomain(toolEffects, priorDomainEffects);
105
- restoreDomain(axisExplicitNegatives, priorDomainNegatives);
106
- lines.push(`${r.server}: failed (${foldErr instanceof Error ? foldErr.message : String(foldErr)})`);
90
+ const applied = applyMcpRosterDelta(rosterApply, { server: r.server, prefix: r.prefix, tools: r.tools, axes: r.axes ?? [], dropped: r.dropped ?? [] });
91
+ if (!applied.applied) {
92
+ lines.push(`${r.server}: failed (${applied.foldError})`);
107
93
  anyActiveFailure = true;
108
94
  continue;
109
95
  }
110
- const refreshedMounts = withFaces(pushable, r.server).map((t) => remoteToolOffload(t));
111
- roster.replaceDomain(r.prefix, refreshedMounts, { source: "mcp", mountedBy: "optional", origin: { peer: r.server } });
112
96
  changed = true;
113
97
  const detail = [];
114
98
  const shownAdded = r.added.filter((n) => !excludedSet.has(n));
@@ -116,18 +100,16 @@ export async function prepareProtocolTools(input) {
116
100
  detail.push(`added: ${shownAdded.join(", ")}`);
117
101
  if (r.removed.length > 0)
118
102
  detail.push(`removed: ${r.removed.join(", ")}`);
119
- if (excludedNow.length > 0)
120
- detail.push(`excluded by deployment config (not mounted): ${excludedNow.join(", ")}`);
103
+ if (applied.excluded.length > 0)
104
+ detail.push(`excluded by deployment config (not mounted): ${applied.excluded.join(", ")}`);
121
105
  if ((r.dropped?.length ?? 0) > 0)
122
106
  detail.push(`dropped: ${r.dropped.map((d) => `${d.tool} (${d.reason.length > 90 ? `${d.reason.slice(0, 90)}…` : d.reason})`).join("; ")}`);
123
- lines.push(`${r.server}: refreshed — ${pushable.length} tool${pushable.length === 1 ? "" : "s"}${detail.length > 0 ? ` (${detail.join("; ")})` : ""}`);
107
+ lines.push(`${r.server}: refreshed — ${applied.pushed} tool${applied.pushed === 1 ? "" : "s"}${detail.length > 0 ? ` (${detail.join("; ")})` : ""}`);
124
108
  }
125
- if (changed)
126
- await rebuildHarnessToolsRef.current?.();
127
- if (changed)
128
- rosterSeat.republish();
129
- if (changed && !toolCallGateArmedRef.armed && (irreversibleTools.size > 0 || egressTools.size > 0)) {
130
- lines.push("WARNING: refreshed tools declare irreversible/egress safety hints, but this task started with no approval gate registered (no policy/hooks and no gated tools at start) — these hints cannot arm a gate mid-task (RB-46); the refreshed tools run ungated on this deployment shape.");
109
+ if (changed) {
110
+ const ungated = await commitMcpRosterChanges(rosterCommit);
111
+ if (ungated !== undefined)
112
+ lines.push(ungated);
131
113
  }
132
114
  const text = lines.length === 0 ? "No connected MCP servers to refresh." : lines.join("\n");
133
115
  return {
@@ -184,7 +166,7 @@ export async function prepareProtocolTools(input) {
184
166
  for (const t of a2a.tools)
185
167
  roster.mount(remoteToolOffload(t), { source: "a2a", mountedBy: "optional", origin: { peer: A2A_NAMESPACE.parse(t.name)?.peer ?? t.name } });
186
168
  foldProtocolAxes(a2a.toolAxes, "A2A");
187
- return { mcp, a2a, rebuildHarnessToolsRef, contentOriginWrapRef, toolCallGateArmedRef };
169
+ return { mcp, a2a, rebuildHarnessToolsRef, contentOriginWrapRef, toolCallGateArmedRef, ...(mcpRedial !== undefined ? { mcpRedial } : {}) };
188
170
  }
189
171
  function mcpRevocationWiring(deps, runId) {
190
172
  if (deps.mcpRevocations === undefined)
@@ -436,7 +436,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
436
436
  roster.mount(createOutputTool(outputRef, spec.outputSchema, compiled.strict ? compiled.modelSchema : undefined), { source: "synthetic", mountedBy: "optional" });
437
437
  }
438
438
  const rosterSeat = new ToolRosterDeltaSeat();
439
- const { mcp, a2a, rebuildHarnessToolsRef, contentOriginWrapRef, toolCallGateArmedRef } = await prepareProtocolTools({ lockedPreflight, spec, deps, reminderMark, reminderDisclosureCounts, runId, sessionId, onceLedger, roster, rosterSeat, remoteToolOffload, toolFaceSnapshot, toolEffects, axisExplicitNegatives, irreversibilityTier, irreversibleTools, egressTools, abortController, rollback });
439
+ const { mcp, a2a, rebuildHarnessToolsRef, contentOriginWrapRef, toolCallGateArmedRef, mcpRedial } = await prepareProtocolTools({ lockedPreflight, spec, deps, reminderMark, reminderDisclosureCounts, runId, sessionId, onceLedger, roster, rosterSeat, remoteToolOffload, toolFaceSnapshot, toolEffects, axisExplicitNegatives, irreversibilityTier, irreversibleTools, egressTools, abortController, rollback });
440
440
  const callIssuedAtRef = {};
441
441
  const workCutRef = { continuation: false, abortedBeforeTurnEnd: false };
442
442
  const advertisedWritableDirs = handsEnabled
@@ -690,7 +690,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
690
690
  }
691
691
  }
692
692
  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, inheritedParentConstraints, seedInheritedGate, hasSkillManifest, skillScope, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef });
693
- const { onAsk, preToolContexts, blockedToolCalls, gateOutcomes, batchHaltRef, blockedTracked, incompleteSuspendAdapter, durableSuspendInfraReady, resourceSuspendEligible, usageGovernance, durableApproval, wiringManifest, writeProtectionCheck, parkLaneArmed, gateMachineryActive, hookIdentity, hookEnvFace } = prepareWiringManifest({ 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, peerLaneActive, harness, pausedRef, frozenOnAsk, liveInheritedGate, delegation, toolRoster, lspManagerWired, lspDiagnosticsLive: lspDiagnostics !== undefined });
693
+ const { onAsk, preToolContexts, blockedToolCalls, gateOutcomes, batchHaltRef, blockedTracked, incompleteSuspendAdapter, durableSuspendInfraReady, resourceSuspendEligible, usageGovernance, durableApproval, wiringManifest, writeProtectionCheck, parkLaneArmed, gateMachineryActive, hookIdentity, hookEnvFace } = prepareWiringManifest({ 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, peerLaneActive, harness, pausedRef, frozenOnAsk, liveInheritedGate, delegation, toolRoster, readDenyBuiltinTiersActive: handsReadFace.readDenyBuiltinTiersActive, lspManagerWired, lspDiagnosticsLive: lspDiagnostics !== undefined });
694
694
  if (fileHistoryBoundary !== undefined) {
695
695
  harness.on("tool_call", async () => {
696
696
  await fileHistoryBoundary.settle();
@@ -706,7 +706,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
706
706
  const preparedHolder = {};
707
707
  const { cacheBreakDetector, cacheFingerprint, promptOverheadTokens, readTaskFile, normalizeAttachmentPath, isDedupStubResult, recentlyReadFiles, onCompactionApplied, detectExternalChanges, listBackgroundTasks, centerCompactionCandidate, effectiveReadFaceObserved, effectiveWriteFaceObserved, effectiveReadDenyObserved } = prepareTurnWiring({ deps, systemPromptSeat, harnessTools, fpRef, model, epochArtifactDigestForSnapshot, promptProfile, fableMitigations, systemBlocks, thinking, spec, sessionId, turnSnapshotRef, promptManifest, charsPerToken, handsEnabled, executionEnv, attachmentRootCanonical, additionalRootsCanonical, additionalReadRootsCanonical, readDenyMatcher, resolvedReadFace, handsCwdRef, readFileStateForCheckpoint, denyNarrowingPolicy, abortController, hostTaskId, taskScope, buildAssembleInputs, centerAdoptionRef, harnessRef, epochDeclaredSections, carrierReadFace, carrierWriteFace, readDenyAdditionsNormalized, preparedHolder });
708
708
  await onceLedger.settle(session, announcedListingsRef);
709
- const buildPrepared = () => ({ harness, session, sessionId, runId, reminderMark, reminderDisclosureCounts, editedFilesSnapshot, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp, ...(a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, gateOutcomes, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved, effectiveWriteFace: effectiveWriteFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, toolProgressRef, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}), ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, ...(permissionRuleLane !== undefined ? { persistedRuleLane: permissionRuleLane } : {}), ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, pausedRef, undeliveredContinuationRef, suspendProgressRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, workCutRef, brainCallGuardrailRef, gateStopRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolRoster, toolRosterDeltas: rosterSeat, structuredProjector, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, stopRequestedRef, announcedSnapshotRecovered: onceLedger.recovered, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, frameReceipts: { pending: [], listingSnapshotOwed: false }, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}), ...(toolDisclosureSeat !== undefined ? { toolDisclosure: toolDisclosureSeat } : {}) });
709
+ const buildPrepared = () => ({ harness, session, sessionId, runId, reminderMark, reminderDisclosureCounts, editedFilesSnapshot, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp, ...(mcpRedial !== undefined ? { mcpRedial } : {}), ...(a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, gateOutcomes, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved, effectiveWriteFace: effectiveWriteFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, toolProgressRef, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}), ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, ...(permissionRuleLane !== undefined ? { persistedRuleLane: permissionRuleLane } : {}), ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, pausedRef, undeliveredContinuationRef, suspendProgressRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, workCutRef, brainCallGuardrailRef, gateStopRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolRoster, toolRosterDeltas: rosterSeat, structuredProjector, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, stopRequestedRef, announcedSnapshotRecovered: onceLedger.recovered, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, frameReceipts: { pending: [], listingSnapshotOwed: false }, gitStatusRef, listBackgroundTasks, hookIdentity, hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}), ...(toolDisclosureSeat !== undefined ? { toolDisclosure: toolDisclosureSeat } : {}) });
710
710
  rollback.commit();
711
711
  const prepared = buildPrepared();
712
712
  preparedHolder.current = prepared;
@@ -108,6 +108,10 @@ export interface PrepareWiringManifestInput {
108
108
  /** borrowed-readonly — the leg's minted tool roster: rides the manifest's `tools` section and is the
109
109
  * source of the hand band's write-capable names for the ungated-write warning (what actually MOUNTED). */
110
110
  toolRoster: ToolRoster;
111
+ /** borrowed-readonly — from the read-face phase: the built-in read-deny TIERS that leg's own
112
+ * resolution materially activated; relayed onto the manifest (present iff non-empty), never
113
+ * re-derived from `deps` here (which by this phase sits past that phase's await). */
114
+ readDenyBuiltinTiersActive: readonly string[];
111
115
  /** borrowed-readonly — from the LSP phase's manager fold: gates the manifest's LSP section (absent ⇒ byte-identical manifest). */ lspManagerWired: boolean;
112
116
  /** borrowed-readonly — from the LSP phase: is the diagnostics lane live? The `lane` word's only input. */ lspDiagnosticsLive: boolean;
113
117
  /** borrowed-readonly — the arming outcome the caps phase minted (the manifest's auto-mode face). */
@@ -113,7 +113,7 @@ function announceDurableGateUnavailable(args) {
113
113
  });
114
114
  }
115
115
  export function prepareWiringManifest(input) {
116
- 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, peerLaneActive, harness, pausedRef, frozenOnAsk, liveInheritedGate, delegation, toolRoster, lspManagerWired, lspDiagnosticsLive } = input;
116
+ 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, peerLaneActive, harness, pausedRef, frozenOnAsk, liveInheritedGate, delegation, toolRoster, readDenyBuiltinTiersActive, lspManagerWired, lspDiagnosticsLive } = input;
117
117
  const onAsk = spec.onAsk ?? deps.onAsk;
118
118
  const handWriteTools = toolRoster.entries.filter((e) => e.mountedBy === "hands" && (e.effect === "write" || e.effect === "idempotent")).map((e) => e.name);
119
119
  const hasEffectAwareGate = Boolean(policyLayers.length > 0 || (hooks?.preToolUse !== undefined && !preToolUseObservational));
@@ -208,6 +208,7 @@ export function prepareWiringManifest(input) {
208
208
  autoMode: { ...autoModeArmReason },
209
209
  mcp: mcpManifestEntries(lockedPreflight.mcp, mcp.statuses),
210
210
  ...(writeProtection !== undefined ? { writeProtectionTargetView: writeProtection.targetView } : {}),
211
+ readDenyBuiltinTiersActive,
211
212
  autoConsolidationOnRecommendation: autoRunOnRecommendationEffective(deps),
212
213
  tools: toolRoster,
213
214
  hooks: wiredHookEntries(hooks, spec.hooks !== undefined ? "spec" : "deps", preToolUseObservational),
@@ -3,7 +3,7 @@ import { type PendingSessionNotifications, type SystemInjectionPriority, type Ta
3
3
  import type { PushQueue } from "../push-queue.js";
4
4
  import type { TaskEvent, TaskSpec } from "../types.js";
5
5
  import type { Stats } from "./assemble-result.js";
6
- import type { CaptureOptOutRef, LiveHandle, ManualCompactRef, NotificationLaneBindings, NotifyRef, Prepared, RunInternals, RunnerDepsSeat, TaskIdRef } from "./contracts.js";
6
+ import type { CaptureOptOutRef, LiveHandle, ManualCompactRef, McpRedialRef, NotificationLaneBindings, NotifyRef, Prepared, RunInternals, RunnerDepsSeat, TaskIdRef } from "./contracts.js";
7
7
  export interface RunIdentityWiringInput {
8
8
  /** borrowed-readonly — the task spec: the task id and the memory / notification seats it names. */
9
9
  spec: TaskSpec;
@@ -27,6 +27,8 @@ export interface RunIdentityWiringInput {
27
27
  notifyRef: NotifyRef | undefined;
28
28
  /** borrowed-mutable — the capture opt-out bridge: `flip` is bound here when a memory session mounted. */
29
29
  captureOptOutRef: CaptureOptOutRef | undefined;
30
+ /** borrowed-mutable — the MCP re-dial bridge: `redial` is bound here when the leg declared MCP servers. */
31
+ mcpRedialRef: McpRedialRef | undefined;
30
32
  /** borrowed-readonly — the notification lane's ONE injection entry (the R0 lane's). */
31
33
  injectTaskNotification: (notification: TaskNotificationPayload, opts?: {
32
34
  priority?: SystemInjectionPriority;
@@ -3,7 +3,7 @@ import { createSafeNotifier } from "../safe-notify.js";
3
3
  import { discloseDroppedPending, renderTaskNotificationXml, taskNotificationDedupKey } from "../task-notification.js";
4
4
  import { deliverDelegationLifecycle, deliverEngineNotice, undrainedUserInputNotices } from "../types.js";
5
5
  export function runIdentityWiring(input) {
6
- const { spec, queue, prepared, internals, taskIdRef, onReady, manualCompactRef, notifyRef, captureOptOutRef, injectTaskNotification, deliveredAtTurnOpen, peerInboundChainRef, notificationLane, pendingSessionNotifications, runner } = input;
6
+ const { spec, queue, prepared, internals, taskIdRef, onReady, manualCompactRef, notifyRef, captureOptOutRef, mcpRedialRef, injectTaskNotification, deliveredAtTurnOpen, peerInboundChainRef, notificationLane, pendingSessionNotifications, runner } = input;
7
7
  notificationLane.harness = prepared.harness;
8
8
  notificationLane.sessionId = prepared.sessionId;
9
9
  if (taskIdRef)
@@ -66,6 +66,9 @@ export function runIdentityWiring(input) {
66
66
  if (captureOptOutRef && prepared.memoryEngineSession?.captureOptOut !== undefined) {
67
67
  captureOptOutRef.flip = prepared.memoryEngineSession.captureOptOut.flip;
68
68
  }
69
+ if (mcpRedialRef && prepared.mcpRedial !== undefined) {
70
+ mcpRedialRef.redial = prepared.mcpRedial;
71
+ }
69
72
  try {
70
73
  internals?.onNotifyInjectorReady?.(injectTaskNotification);
71
74
  }
@@ -23,7 +23,7 @@ import type { AssistantMessage } from "../../internal/llm.js";
23
23
  import type { ResolvedReasoning } from "../../brain/reasoning.js";
24
24
  import type { PushQueue } from "../push-queue.js";
25
25
  import type { SessionStore } from "../session.js";
26
- import { type CompactOutcome, type DelegationLifecycleEvent, type TaskEvent, type TaskResult, type TaskSpec } from "../types.js";
26
+ import { type CompactOutcome, type DelegationLifecycleEvent, type OwedUserInputAccount, type TaskEvent, type TaskResult, type TaskSpec } from "../types.js";
27
27
  import { type Stats } from "./assemble-result.js";
28
28
  import type { EndOfTaskCompaction, InstallingPhaseResult, ManualCompactRef, Prepared, ResumeRun, RunInternals, RunnerDepsSeat, RunState, SuspendReap, TaskIdRef } from "./contracts.js";
29
29
  export interface RunSettleAndTeardownInput {
@@ -94,6 +94,8 @@ export interface RunSettleAndTeardownInput {
94
94
  };
95
95
  /** borrowed-readonly — the end-of-task compaction's outcome (the terminal-adoption lane's `finish` call). */
96
96
  comp: EndOfTaskCompaction;
97
+ /** borrowed-readonly — the adoption lane's unsettled undrained-input account, closed below (after the reopen). */
98
+ owedUserInput: OwedUserInputAccount;
97
99
  /** borrowed-readonly — the stream's result setter: the assembled TaskResult is published through it exactly once. */
98
100
  setResult: (r: TaskResult) => void;
99
101
  /** borrowed-readonly — the stream's suggestions seat: the fire-and-forget suggestion pass's promise is handed to it. */