@sema-agent/core 7.40.1 → 7.41.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 (134) hide show
  1. package/CHANGELOG.md +188 -0
  2. package/dist/agents/agent-transcript-tool.d.ts +10 -2
  3. package/dist/agents/agent-transcript-tool.js +52 -26
  4. package/dist/agents/list-agents-tool.d.ts +17 -2
  5. package/dist/agents/list-agents-tool.js +28 -16
  6. package/dist/agents/peer-session-drain.d.ts +3 -1
  7. package/dist/agents/peer-session-drain.js +1 -0
  8. package/dist/agents/subagent-steps.d.ts +12 -2
  9. package/dist/agents/subagent-steps.js +8 -4
  10. package/dist/agents/subagent.d.ts +1 -17
  11. package/dist/agents/subagent.js +12 -24
  12. package/dist/core/agent-definition.d.ts +19 -40
  13. package/dist/core/agent-state.d.ts +91 -0
  14. package/dist/core/agent-state.js +79 -0
  15. package/dist/core/ask-origin.d.ts +28 -6
  16. package/dist/core/ask-origin.js +9 -5
  17. package/dist/core/ask-unresolvable-notice.d.ts +27 -4
  18. package/dist/core/ask-unresolvable-notice.js +8 -0
  19. package/dist/core/auto-promote.d.ts +14 -27
  20. package/dist/core/auto-promote.js +5 -5
  21. package/dist/core/checkpoint-store.d.ts +11 -12
  22. package/dist/core/decision-receipt.d.ts +5 -2
  23. package/dist/core/decision-receipt.js +1 -1
  24. package/dist/core/engine-notice.d.ts +7 -1
  25. package/dist/core/gate-crash-trace.d.ts +1 -1
  26. package/dist/core/gate-fold.js +7 -3
  27. package/dist/core/gate-lanes.js +22 -3
  28. package/dist/core/governance-codes.d.ts +3 -1
  29. package/dist/core/governance-codes.js +3 -0
  30. package/dist/core/hooks.d.ts +20 -6
  31. package/dist/core/hooks.js +28 -28
  32. package/dist/core/mcp-tool-meta.d.ts +24 -0
  33. package/dist/core/mcp-tool-meta.js +17 -0
  34. package/dist/core/mcp.d.ts +1 -6
  35. package/dist/core/mcp.js +6 -7
  36. package/dist/core/mode-policy.d.ts +25 -0
  37. package/dist/core/mode-policy.js +8 -0
  38. package/dist/core/protocol-table.d.ts +3 -0
  39. package/dist/core/protocol-table.js +3 -0
  40. package/dist/core/reminder-mint.d.ts +21 -1
  41. package/dist/core/reminder-mint.js +20 -0
  42. package/dist/core/retired-keys.d.ts +2 -1
  43. package/dist/core/retired-keys.js +13 -2
  44. package/dist/core/rule-denial-sentence.d.ts +2 -2
  45. package/dist/core/runner/call-hook-stations.d.ts +106 -0
  46. package/dist/core/runner/call-hook-stations.js +118 -0
  47. package/dist/core/runner/contracts.d.ts +35 -25
  48. package/dist/core/runner/gate-exit.d.ts +5 -1
  49. package/dist/core/runner/gate-exit.js +10 -6
  50. package/dist/core/runner/prepare-delegation-surface.d.ts +1 -0
  51. package/dist/core/runner/prepare-delegation-surface.js +4 -4
  52. package/dist/core/runner/prepare-gate-stations.d.ts +8 -3
  53. package/dist/core/runner/prepare-gate-stations.js +13 -70
  54. package/dist/core/runner/prepare-inherited-gate.d.ts +6 -3
  55. package/dist/core/runner/prepare-inherited-gate.js +5 -3
  56. package/dist/core/runner/prepare-park-ask.d.ts +4 -0
  57. package/dist/core/runner/prepare-park-ask.js +4 -1
  58. package/dist/core/runner/prepare-policy-chain.d.ts +6 -5
  59. package/dist/core/runner/prepare-policy-chain.js +14 -25
  60. package/dist/core/runner/prepare-project-context.d.ts +15 -16
  61. package/dist/core/runner/prepare-project-context.js +15 -11
  62. package/dist/core/runner/prepare-run-refs.d.ts +8 -8
  63. package/dist/core/runner/prepare-run-refs.js +3 -3
  64. package/dist/core/runner/prepare-safety-scan.js +3 -3
  65. package/dist/core/runner/prepare-suspend-saga.d.ts +2 -1
  66. package/dist/core/runner/prepare-suspend-saga.js +2 -1
  67. package/dist/core/runner/prepare-task.js +14 -12
  68. package/dist/core/runner/prepare-turn-wiring.d.ts +3 -1
  69. package/dist/core/runner/prepare-turn-wiring.js +16 -13
  70. package/dist/core/runner/resume-admission.js +2 -1
  71. package/dist/core/runner/resume-apply.js +12 -23
  72. package/dist/core/runner/resume-checkpoint-screen.js +3 -0
  73. package/dist/core/runner/run-leg.js +61 -47
  74. package/dist/core/runner/runtask.d.ts +1 -1
  75. package/dist/core/runner/session-rule-policy.d.ts +7 -19
  76. package/dist/core/runner/session-rule-policy.js +1 -2
  77. package/dist/core/runner/skill-base-dir.d.ts +10 -0
  78. package/dist/core/runner/skill-base-dir.js +4 -0
  79. package/dist/core/runner/synthetic-tools.d.ts +11 -17
  80. package/dist/core/runner/synthetic-tools.js +7 -8
  81. package/dist/core/safety-axis-vocab.d.ts +2 -2
  82. package/dist/core/safety-axis-vocab.js +2 -0
  83. package/dist/core/session-policy-store.d.ts +63 -11
  84. package/dist/core/session-policy-store.js +52 -6
  85. package/dist/core/skill-grant.d.ts +239 -0
  86. package/dist/core/skill-grant.js +313 -0
  87. package/dist/core/skills-directory.d.ts +5 -34
  88. package/dist/core/skills-directory.js +9 -58
  89. package/dist/core/task-registry-agent.d.ts +14 -2
  90. package/dist/core/task-registry-agent.js +23 -8
  91. package/dist/core/task-registry-shared.d.ts +6 -0
  92. package/dist/core/task-registry.d.ts +16 -0
  93. package/dist/core/task-registry.js +19 -6
  94. package/dist/core/task-spec.d.ts +10 -0
  95. package/dist/core/tool-face.d.ts +3 -2
  96. package/dist/core/tool-name-aliases.d.ts +38 -12
  97. package/dist/core/tool-name-aliases.js +16 -0
  98. package/dist/core/tool-name-readings.d.ts +6 -2
  99. package/dist/core/tool-name-readings.js +6 -4
  100. package/dist/core/tool-policy.d.ts +2 -2
  101. package/dist/core/tool-registry.d.ts +1 -4
  102. package/dist/core/tool-registry.js +0 -3
  103. package/dist/core/tool-spec.d.ts +11 -13
  104. package/dist/core/tools.d.ts +1 -1
  105. package/dist/core/trace.d.ts +15 -0
  106. package/dist/core/types.d.ts +1 -1
  107. package/dist/engine/harness/agent-harness.d.ts +12 -1
  108. package/dist/engine/harness/agent-harness.js +17 -13
  109. package/dist/engine/loop/agent-loop.d.ts +16 -1
  110. package/dist/engine/loop/agent-loop.js +9 -15
  111. package/dist/engine/loop/types.d.ts +2 -0
  112. package/dist/index.d.ts +9 -4
  113. package/dist/index.js +4 -2
  114. package/dist/stores/file/adoption/adopt.js +23 -16
  115. package/dist/stores/file/index.d.ts +8 -2
  116. package/dist/stores/file/index.js +2 -2
  117. package/dist/stores/file/mailbox-store.js +2 -2
  118. package/dist/stores/file/permission-rule-adopt.js +10 -1
  119. package/dist/stores/file/permission-rule-store.d.ts +6 -4
  120. package/dist/stores/file/permission-rule-store.js +127 -59
  121. package/dist/stores/file/session-policy-store.d.ts +86 -14
  122. package/dist/stores/file/session-policy-store.js +135 -118
  123. package/dist/stores/file/task-list-store.d.ts +5 -0
  124. package/dist/stores/file/task-list-store.js +18 -4
  125. package/dist/stores/file/usage-window-store.d.ts +9 -1
  126. package/dist/stores/file/usage-window-store.js +47 -33
  127. package/dist/tools/fs/safety.d.ts +1 -1
  128. package/package.json +1 -1
  129. package/test/export-surface.snapshot.json +45 -5
  130. package/wire-changes/core@7.41.0.json +1865 -0
  131. package/dist/core/runner/active-skill-scope.d.ts +0 -58
  132. package/dist/core/runner/active-skill-scope.js +0 -133
  133. package/dist/core/skill-tool-specifier.d.ts +0 -80
  134. package/dist/core/skill-tool-specifier.js +0 -57
@@ -501,9 +501,9 @@ export interface ToolSpec<TParams extends TSchema = TSchema> {
501
501
  * gate all read it off the call's face instead of a name table. ABSENT ⇒ the tool is handled as a
502
502
  * write whose target cannot be confirmed (fail-closed under every path-confining face) — exactly how
503
503
  * a tool outside the retired name tables was handled. `param` outranks `aliases` (NotebookEdit's
504
- * `notebook_path` beats a decoy `file_path`). `skillScopeEligible` is a SEPARATE grant: only a tool
505
- * that sets it enters the active-skill `allowPaths` face (Write/Edit do; NotebookEdit deliberately
506
- * does not; a caller tool that merely takes a built-in's NAME inherits nothing).
504
+ * `notebook_path` beats a decoy `file_path`). `skillScopeEligible` is a SEPARATE grant whose one reader (the
505
+ * skill-scope write fence) is retired (Write/Edit set it; NotebookEdit deliberately does not; a caller
506
+ * tool that merely takes a built-in's NAME inherits nothing).
507
507
  */
508
508
  pathTarget?: ToolPathTarget;
509
509
  /**
@@ -1071,16 +1071,14 @@ export interface ToolExecuteContext {
1071
1071
  */
1072
1072
  getApiKeyAndHeaders?: TaskSpec["getApiKeyAndHeaders"];
1073
1073
  /**
1074
- * 🔴 design/77 §3 / §7 — read-only SNAPSHOT of the parent task's ACTIVE skill-manifest frames (the
1075
- * Gate-3 deny-narrowing scope, see {@link SkillManifest}), filled by the Runner. A delegation tool
1076
- * (`createSubagentTool`) reads it AT SPAWN TIME and propagates it into the child task so the child
1077
- * inherits the parent skill's tool/path restrictions (a child of a manifested skill is at most as
1078
- * capable as the manifest). Returns an opaque, frozen array of frames; an empty array = no manifest is
1079
- * active (the common case). The model/worker can neither read nor set it (it is not a tool argument).
1080
- * Undefined when the tool runs outside a Runner task. The returned frame objects are intentionally
1081
- * opaque to callers other than the Runner internals that re-seed them.
1074
+ * A read-only CAPTURE of this (parent) task's skill grant rows for the run — the tools its active skills list in
1075
+ * `allowed-tools` / `disallowed-tools` ({@link import("./skill-grant.js").SkillGrantRecord}: skill, list, entry),
1076
+ * filled by the Runner. A delegation tool (`createSubagentTool`) reads it AT SPAWN TIME and threads it into the child,
1077
+ * which holds the rows until it ends. A copy: the parent's later activations never reach it. An empty array = no active
1078
+ * skill lists (the common case). The model can neither read nor set it (it is not a tool argument). Undefined when the
1079
+ * tool runs outside a Runner task.
1082
1080
  */
1083
- activeSkillScope?: () => readonly unknown[];
1081
+ activeSkillGrants?: () => readonly import("./skill-grant.js").SkillGrantRecord[];
1084
1082
  /**
1085
1083
  * Parent effective-policy inheritance (tighten-only) — the Runner-filled chain accessor a delegation tool
1086
1084
  * (`createSubagentTool` / the `run_workflow` spawn legs) calls AT SPAWN TIME and threads verbatim into the
@@ -1089,7 +1087,7 @@ export interface ToolExecuteContext {
1089
1087
  * (`spec.toolPolicy ?? deps.toolPolicy` — the exact slot this task's own gate enforces) with the frozen
1090
1088
  * task onAsk (opaque half), and its effective shellGate. The child's prepare folds these as
1091
1089
  * ADDITIONAL constraint layers — inheritance can only narrow a child, never widen it. Runner-filled,
1092
- * read-only, NEVER a model/tool argument (same posture as {@link activeSkillScope}). Undefined when the
1090
+ * read-only, NEVER a model/tool argument (same posture as {@link activeSkillGrants}). Undefined when the
1093
1091
  * tool runs outside a Runner task.
1094
1092
  */
1095
1093
  inheritedGateForChildren?: () => import("./runner/contracts.js").InheritedGate;
@@ -54,7 +54,7 @@ export declare function rebindDefineToolCtx(product: AgentTool, enrich: ToolCtxE
54
54
  * tool read them as `undefined` on every call and its ctx-first arms were structurally dead. This
55
55
  * closes the gap at MOUNT time instead: the mount — which does hold the Runner's trusted context —
56
56
  * supplies a builder, and the adapter runs it PER CALL, so call-time snapshots (the run's current
57
- * model, the live skill scope) stay current rather than freezing at factory time.
57
+ * model, the live skill grants) stay current rather than freezing at factory time.
58
58
  *
59
59
  * Shape is deliberately the SAME `(ctx) => ctx` one the Runner's `spec.tools` wrapper already applies,
60
60
  * so a mount passes THAT builder itself instead of re-listing the axes — one source for the trusted
@@ -441,6 +441,21 @@ export type TraceEvent = {
441
441
  toolCallId: string;
442
442
  command: string;
443
443
  ts: number;
444
+ } | {
445
+ /**
446
+ * An active skill's `allowed-tools` row cleared the call, so no person and no classifier was asked: the allow
447
+ * layer's third attribution channel beside `permission.persisted_rule_allowed` and `permission.read_only_allowed`.
448
+ * `skill` names the skill whose list spoke and `entries` the rows the call stands on, as the skill spells them
449
+ * (deployment text; a consumer rendering it applies its own display sanitizer).
450
+ */
451
+ kind: "permission.skill_allowed";
452
+ version: 1;
453
+ taskId: string;
454
+ toolName: string;
455
+ toolCallId: string;
456
+ skill: string;
457
+ entries: string[];
458
+ ts: number;
444
459
  } | {
445
460
  /**
446
461
  * design/179 — the person's persisted rule store could not be read (at prepare, or for one call), so
@@ -17,7 +17,7 @@ export type { StaleToolResultOffloadOptions, ImageInput, TaskSpec, ShellGateDoct
17
17
  export type { PermissionMode } from "./ask-origin.js";
18
18
  export type { ToolEffect, ToolContentOrigin, ReversibilityVerdict, ReadRootGrantCandidate, ToolSpec, ToolReturn, NestedUsage, NestedUsageAccum, ToolExecuteContext } from "./tool-spec.js";
19
19
  export type { BeforeWriteRequest, BeforeWriteResult, BeforeWriteHook, TrackEditRequest, TrackEditResult, TrackFileEditHook, FileEditedNotice, FileEditedHook, HandsBandOptions } from "./hands-band.js";
20
- export type { AgentDefinition, SkillManifest, SkillSpec } from "./agent-definition.js";
20
+ export type { AgentDefinition, SkillSpec } from "./agent-definition.js";
21
21
  export type { McpServerSpec, A2aServerSpec, McpElicitRequest, McpElicitResponse, OnElicit } from "./mcp-server-spec.js";
22
22
  export type { TaskLimits } from "./task-limits.js";
23
23
  export type { TaskStatus, RemoteEnvFailureNote, EffectiveMemoryScopes, TaskResult, HumanReviewGateRow } from "./task-result.js";
@@ -1,7 +1,8 @@
1
1
  import type { ActorAssertion, AssistantMessage, ImageContent, Model, UserMessage } from "../llm/index.js";
2
2
  import { type ToolTurnRepair } from "../llm/index.js";
3
+ import { type ExecutedToolCallOutcome } from "../loop/agent-loop.js";
3
4
  import type { HaltRemainingDirective } from "../loop/types.js";
4
- import type { AgentMessage, AgentTool, LoopMalformedToolUseRecovery, LoopThinkingOnlyRecovery, LoopTruncatedOutputRecovery, QueueMode, ThinkingLevel } from "../loop/types.js";
5
+ import type { AgentMessage, AgentTool, AgentToolCall, LoopMalformedToolUseRecovery, LoopThinkingOnlyRecovery, LoopTruncatedOutputRecovery, QueueMode, ThinkingLevel } from "../loop/types.js";
5
6
  import { type EngineSegment } from "../../core/untrusted-text.js";
6
7
  import type { CommittedFrameCarrier } from "../../core/frame-settlement.js";
7
8
  import type { AbortResult, AgentHarnessEvent, AgentHarnessEventResultMap, AgentHarnessOptions, AgentHarnessOwnEvent, AgentHarnessResources, AgentHarnessStreamOptions, ExecutionEnv, PromptTemplate, Skill } from "./types.js";
@@ -522,6 +523,16 @@ export declare class AgentHarness<TSkill extends Skill = Skill, TPromptTemplate
522
523
  images?: ImageContent[];
523
524
  } & UserMessageProvenance): Promise<void>;
524
525
  appendMessage(message: AgentMessage): Promise<void>;
526
+ /** The loop's `afterToolCall`: one `tool_result` hook emit per executed call (every registered handler, one fold). */
527
+ private afterToolCallHook;
528
+ /**
529
+ * Finalize a call this harness's caller executed OUTSIDE a turn (a resumed, already-approved call) exactly as the
530
+ * loop finalizes its own: the same {@link finalizeToolOutcome} over the same `tool_result` emit, so the call meets the
531
+ * same handlers, the same patch application and the same throwing-hook posture as a live one.
532
+ */
533
+ finalizeToolCall(toolCall: AgentToolCall, args: unknown, executed: ExecutedToolCallOutcome): Promise<ExecutedToolCallOutcome & {
534
+ toolCall: AgentToolCall;
535
+ }>;
525
536
  getModel(): Model;
526
537
  /** The ref that chose the CURRENT model — {@link getModel}'s companion (a word re-resolves, an object pins). */
527
538
  getModelRef(): import("../../core/model-seat.js").ModelRef;
@@ -1,5 +1,5 @@
1
1
  import { createAssistantMessageEventStream, ensureToolTurnInvariants, snapshotActorAssertion, stripEngineMetadata } from "../llm/index.js";
2
- import { runAgentLoop } from "../loop/agent-loop.js";
2
+ import { finalizeToolOutcome, runAgentLoop } from "../loop/agent-loop.js";
3
3
  import { resolveAgentCoreStreamFn } from "../loop/runtime-deps.js";
4
4
  import { normalizeEngineSegments } from "../../core/untrusted-text.js";
5
5
  import { AUTH_CARRIER_NAMES } from "../../brain/request-params.js";
@@ -674,18 +674,7 @@ export class AgentHarness {
674
674
  });
675
675
  return result;
676
676
  },
677
- afterToolCall: async ({ toolCall, args, result, isError }) => {
678
- const patch = await this.emitHook({
679
- type: "tool_result",
680
- toolCallId: toolCall.id,
681
- toolName: toolCall.name,
682
- input: args,
683
- content: result.content,
684
- details: result.details,
685
- isError,
686
- });
687
- return patch;
688
- },
677
+ afterToolCall: ({ toolCall, args, result, isError }) => this.afterToolCallHook(toolCall, args, result, isError),
689
678
  prepareNextTurn: async () => {
690
679
  await this.flushPendingSessionWrites();
691
680
  await this.emitHook({ type: "turn_boundary", model: this.model });
@@ -1217,6 +1206,21 @@ export class AgentHarness {
1217
1206
  throw normalizeHarnessError(error, "session");
1218
1207
  }
1219
1208
  }
1209
+ async afterToolCallHook(toolCall, args, result, isError) {
1210
+ const patch = await this.emitHook({
1211
+ type: "tool_result",
1212
+ toolCallId: toolCall.id,
1213
+ toolName: toolCall.name,
1214
+ input: args,
1215
+ content: result.content,
1216
+ details: result.details,
1217
+ isError,
1218
+ });
1219
+ return patch;
1220
+ }
1221
+ finalizeToolCall(toolCall, args, executed) {
1222
+ return finalizeToolOutcome(toolCall, executed, (result, isError) => this.afterToolCallHook(toolCall, args, result, isError));
1223
+ }
1220
1224
  getModel() {
1221
1225
  return this.model;
1222
1226
  }
@@ -23,7 +23,7 @@
23
23
  */
24
24
  import type { AssistantMessage, ToolResultMessage } from "../llm/index.js";
25
25
  import { type AgentCoreStreamRuntimeDeps } from "./runtime-deps.js";
26
- import type { AgentContext, AgentEvent, AgentLoopConfig, AgentMessage, HaltRemainingDirective, StreamFn } from "./types.js";
26
+ import type { AfterToolCallResult, AgentContext, AgentEvent, AgentLoopConfig, AgentMessage, AgentToolResult, HaltRemainingDirective, StreamFn } from "./types.js";
27
27
  /** Callback used by synchronous loop runners to publish agent lifecycle events. */
28
28
  export type AgentEventSink = (event: AgentEvent) => Promise<void> | void;
29
29
  /** Named reasons the loop goes around again. Assertable in tests via the trace sink. */
@@ -154,6 +154,11 @@ type ExecutedToolCallBatch = {
154
154
  messages: ToolResultMessage[];
155
155
  terminate: boolean;
156
156
  };
157
+ /** An executed call's raw outcome, before (and, from {@link finalizeToolOutcome}, after) the post-execution half. */
158
+ export type ExecutedToolCallOutcome = {
159
+ result: AgentToolResult<unknown>;
160
+ isError: boolean;
161
+ };
157
162
  /**
158
163
  * How many tool names an unknown-tool miss spells out. A miss is a NAME problem: seeing a couple of dozen
159
164
  * real names is what lets a model spot the one it meant, while a full listing grows with the deployment
@@ -174,4 +179,14 @@ export declare const TOOL_VALIDATION_REFUSED_CODE = "tool.validation_refused";
174
179
  * reported on the fault seat): a validator is a precondition read, not a network call, so a single
175
180
  * bound serves every tool; a deployment validator that needs longer is a validator doing too much. */
176
181
  export declare const TOOL_INPUT_VALIDATION_TIMEOUT_MS = 10000;
182
+ /**
183
+ * The post-execution half EVERY executed tool call passes through — the loop's own calls here, and a call
184
+ * the harness executed outside a turn (a resumed approved call, `AgentHarness.finalizeToolCall`) through the
185
+ * same function, so the two cannot drift into two dialects of one finalization: the `afterToolCall` patch
186
+ * (content / details / terminate / isError), the D4 posture on a throwing hook, and the delivery mark.
187
+ * `after` absent ⇒ the executed outcome stands as is (same object).
188
+ */
189
+ export declare function finalizeToolOutcome<C>(toolCall: C, executed: ExecutedToolCallOutcome, after: ((result: AgentToolResult<unknown>, isError: boolean) => Promise<AfterToolCallResult | undefined>) | undefined): Promise<ExecutedToolCallOutcome & {
190
+ toolCall: C;
191
+ }>;
177
192
  export {};
@@ -1489,20 +1489,18 @@ async function executePreparedWithDisclosure(currentContext, assistantMessage, p
1489
1489
  }
1490
1490
  return finalizeExecutedToolCall(currentContext, assistantMessage, prepared, executed, config, signal);
1491
1491
  }
1492
- async function finalizeExecutedToolCall(currentContext, assistantMessage, prepared, executed, config, signal) {
1492
+ function finalizeExecutedToolCall(currentContext, assistantMessage, prepared, executed, config, signal) {
1493
+ return finalizeToolOutcome(prepared.toolCall, executed, config.afterToolCall === undefined
1494
+ ? undefined
1495
+ : (result, isError) => config.afterToolCall({ assistantMessage, toolCall: prepared.toolCall, args: prepared.args, result, isError, context: currentContext }, signal));
1496
+ }
1497
+ export async function finalizeToolOutcome(toolCall, executed, after) {
1493
1498
  let result = executed.result;
1494
1499
  let isError = executed.isError;
1495
1500
  const deliveryMark = readDeliveryFailureMark(executed.result.details);
1496
- if (config.afterToolCall) {
1501
+ if (after) {
1497
1502
  try {
1498
- const afterResult = await config.afterToolCall({
1499
- assistantMessage,
1500
- toolCall: prepared.toolCall,
1501
- args: prepared.args,
1502
- result,
1503
- isError,
1504
- context: currentContext,
1505
- }, signal);
1503
+ const afterResult = await after(result, isError);
1506
1504
  if (afterResult) {
1507
1505
  result = {
1508
1506
  content: afterResult.content ?? result.content,
@@ -1522,11 +1520,7 @@ async function finalizeExecutedToolCall(currentContext, assistantMessage, prepar
1522
1520
  result = { ...result, details };
1523
1521
  }
1524
1522
  }
1525
- return {
1526
- toolCall: prepared.toolCall,
1527
- result,
1528
- isError,
1529
- };
1523
+ return { toolCall, result, isError };
1530
1524
  }
1531
1525
  function safeAbortDetails(fn) {
1532
1526
  try {
@@ -772,6 +772,8 @@ export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = unk
772
772
  * repeat-safety effect below its fail-closed `write` default (only the caller's `toolAxes` does). Absent on non-MCP tools.
773
773
  */
774
774
  mcpReadOnlyHint?: true;
775
+ /** The server's own `_meta["anthropic/requiresUserInteraction"] === true` declaration: a call to this tool must be put to a person (read per call off the live mount array; MCP tools only). */
776
+ mcpRequiresUserInteraction?: true;
775
777
  /**
776
778
  * The tool's permission seat — see `ToolSpec.permissionSeat` (src/core/tool-spec.ts). `"passthrough"` = the tool states no
777
779
  * permission of its own; set by the MCP and A2A materializers on a remote peer's tool and carried by `defineTool` from a
package/dist/index.d.ts CHANGED
@@ -72,7 +72,7 @@ export { WORKFLOW_SIZE_GUIDELINE_AGENT_CAPS } from "./orchestration/workflow-siz
72
72
  export { ENGINE_AUTHORITY_ENVELOPE_TAGS } from "./core/untrusted-text.js";
73
73
  export { materializeA2aTools, A2aRpcError, type MaterializedA2a, type A2aPeerStatus, type A2aRefreshResult, type A2aToolAxis } from "./core/a2a.js";
74
74
  export { PROTOCOL_TABLE, MCP_NAMESPACE, A2A_NAMESPACE, protocolOf, type ProtocolNamespace, type ProtocolId } from "./core/protocol-table.js";
75
- export { InMemorySessionPolicyStore, SessionPolicyError, loosenReasons, normalizeRules, stripRev, type SessionPolicyStore, type SessionPermissionRules, type StoredSessionRules, type SessionRulesRecord, type PutRulesOptions, } from "./core/session-policy-store.js";
75
+ export { InMemorySessionPolicyStore, SessionPolicyError, loosenReasons, normalizeRules, stripRev, assertSessionRuleNamesCurrent, type SessionPolicyStoreOptions, type SessionRuleNameRefusal, type SessionPolicyStore, type SessionPermissionRules, type StoredSessionRules, type SessionRulesRecord, type PutRulesOptions, } from "./core/session-policy-store.js";
76
76
  export { SAFETY_MERGE_CONFORMANCE_CORPUS, type SafetyMergeVector } from "./core/safety-merge-corpus.js";
77
77
  export { SAFETY_AXIS_VOCABULARY } from "./core/safety-axis-vocab.js";
78
78
  export { createSessionRulePolicy } from "./core/runner/session-rule-policy.js";
@@ -120,7 +120,7 @@ export { InMemoryCheckpointStore, CheckpointError, mintCheckpointId, mintCheckpo
120
120
  export { EXECUTION_OUTCOME_RECORD_WORDS, EXECUTION_RECORD_LEAVES_ROW_UNRECORDED, type ExecutionOutcomeRecordWord, type ExecutionRecordRowFacts, type ExecutionVerdict, ExecutionOutcomeConflictError, checkpointExecutionRecorded, executionRecordDisposition, sameExecutionRecord, executionVerdict, } from "./core/checkpoint-execution-record.js";
121
121
  export { PAUSE_REGISTRY, type GateKind, type PendingKind, type ResumeGate, isGateKind, resumeGateMatches, type CheckpointPause, type CheckpointFields, pauseOf, checkpointFrom } from "./core/pause-registry.js";
122
122
  export { InMemoryUsageWindowStore, GLOBAL_USAGE_KEY, EMPTY_USAGE_WINDOW_RECORD, chargeUsageRecord, readUsageRecord, usageRetryAfterMs, resolveUsageWindows, type UsageWindow, type UsageWindowStore, type UsageWindowReading, type UsageWindowRecord, type UsageSlot, type UsageBucketRow, } from "./core/usage-window-store.js";
123
- export { FileUsageWindowStore } from "./stores/file/usage-window-store.js";
123
+ export { FileUsageWindowStore, type FileUsageWindowStoreOptions } from "./stores/file/usage-window-store.js";
124
124
  export { ENV_LIFETIME_SUSPEND_MARGIN_MS, USAGE_WINDOW_REAP_MARGIN_MS } from "./core/runner/prepare-task.js";
125
125
  export { InMemoryFileHistoryStore, boundaryPublishVerdict } from "./core/file-history-store.js";
126
126
  export { captureFileBackupViaEnv, applyFileRestoreViaEnv } from "./core/file-history-store.js";
@@ -141,12 +141,13 @@ export { brainToRuntime } from "./core/runtime.js";
141
141
  export { createSensitivePathPolicy, RECOMMENDED_SENSITIVE_PATTERNS } from "./core/sensitive-path-policy.js";
142
142
  export { createFsWriteGatePolicy, WRITE_EXEMPT_DIR_COVERS_ROOT_CODE, type FsWriteGatePolicyOptions } from "./core/fs-write-gate-policy.js";
143
143
  export { RETIRED_TOOL_NAMES } from "./core/tool-name-aliases.js";
144
+ export { judgeRuleToolName, RULE_TOOL_NAME_VERDICTS, type RuleToolNameVerdict } from "./core/tool-name-aliases.js";
144
145
  export { DEFAULT_SUBAGENT_TOOL_NAME } from "./agents/subagent.js";
145
146
  export { renderTaskNotificationXml, RESUPPLIED_FRAME_PREAMBLE, taskNotificationDedupKey, isDelegatedAgentTerminal, isTerminalTaskNotification, SystemInjectionQueue, type TaskNotificationPayload, type TaskNotificationStatus, type ExternalNotificationInput, type SystemInjection, type SystemInjectionPriority, type NotificationInjectionOutcome, // the injector's outcome words (`parked_durably` = the session's durable box took the frame)
146
147
  NOTIFICATION_INJECTION_OUTCOMES, // …and their value half + guard, so a host wiring its own uplink validates a reported outcome without duplicating the vocabulary
147
148
  isNotificationInjectionOutcome, SYSTEM_INJECTION_PRIORITIES, isSystemInjectionPriority, AGENT_MESSAGE_TAG, renderAgentMessageFrame, type SemaProvenance, HANDBACK_STATES, type HandbackState, COMPACTION_KINDS, type CompactionKind, handbackStateOf, terminalFrameFacets, } from "./core/task-notification.js";
148
149
  export { TOOL_FAMILIES, TOOL_MOUNT_TAGS, TOOL_APPROVAL_CARDS, TOOL_PATH_ACCESSES, TOOL_PATH_BASES, TOOL_PATH_ABSENCES, TOOL_WIRE_NAME_MAX_CHARS, TOOL_CONTRACT_MAX_CHARS, TOOL_CARD_ID_MAX_CHARS, TOOL_KEY_MAX_CHARS, TOOL_KEYS_MAX, RENDER_HINT_MAX_CHARS, RENDER_HINT_ACTIVITY_MAX_CHARS, RENDER_HINT_MAX_LIST, type ToolFamily, type ToolMountTag, type ToolApprovalCard, type ToolPathAccess, type ToolPathBase, type ToolPathAbsence, type ToolPathTarget, type ToolCallPathTarget, type ToolRosterPathTarget, pathTargetSlotOnly, type ToolRuleFace, type ToolRenderHints, type ToolFaceDeclaration, type ToolCatalogEntry, } from "./core/tool-face.js";
149
- export { describeToolCatalog, catalogEntriesNamed, catalogToolNames, catalogRuleFaceOf, pathTargetOf, pathTargetValue, protectivePathTargetOf, declaredPathTargetOf, BARE_REQUEST_CODE, isProtectedWrite, isDeclaredWrite, fileOpAccessOf, type ToolCallLike, pathConfinableWriteToolNames, skillScopeWriteToolNames, pathTargetingToolNames, compactableToolNames, budgetExemptToolNames, handBandEffects, engineCardTypes, predictMountedToolNames, HANDS_BAND_TAGS, MOUNT_TAG_IN_HANDS_BAND, } from "./core/tool-registry.js";
150
+ export { describeToolCatalog, catalogEntriesNamed, catalogToolNames, catalogRuleFaceOf, pathTargetOf, pathTargetValue, protectivePathTargetOf, declaredPathTargetOf, BARE_REQUEST_CODE, isProtectedWrite, isDeclaredWrite, fileOpAccessOf, type ToolCallLike, pathConfinableWriteToolNames, pathTargetingToolNames, compactableToolNames, budgetExemptToolNames, handBandEffects, engineCardTypes, predictMountedToolNames, HANDS_BAND_TAGS, MOUNT_TAG_IN_HANDS_BAND, } from "./core/tool-registry.js";
150
151
  export { ToolRosterEntry, ToolRoster, ToolRosterDelta, ToolRosterRenderHints, ROSTER_SOURCES, type RosterSource, type ToolIdentity, toolIdentityOf, mintCapabilityId, diffToolRoster, judgeParkedToolIdentity, ruleFacesFromRoster, toolCallFaceOf, rosterToolName, ruleNamesOf, inputKeysOf, ruleFaceOfInstance, rosterMemberBoundProblem, type RosterBoundMembers, type RosterBoundCode, ToolRosterDeltaSeat, } from "./core/tool-roster.js";
151
152
  export { type ToolCallFace } from "./core/tool-policy.js";
152
153
  export { effectivePathTargetOf, pathTargetBaseOf, type ToolPathBases, type PathTargetReading } from "./core/effective-path-target.js";
@@ -196,6 +197,8 @@ export { type ReadRootGrantScreen } from "./core/read-root-grant-screen.js";
196
197
  export { type AskOrigin, ASK_ORIGINS, isAskOrigin, ORIGIN_IMPLIES_REAL_APPROVAL, PERSISTED_RULE_MANDATES, type PersistedRuleMandate, isPersistedRuleMandate, screenAskClassMembers } from "./core/ask-origin.js";
197
198
  export { PERMISSION_MODES, type PermissionMode, isPermissionMode, PERMISSION_MODE_RANK, clampPermissionMode, ASK_CLASSES, type AskClass, isAskClass, MODE_ARBITRATION, type ModeArbitrationRow, RELEASABLE_ASK_CLASSES, MODE_RELEASE_REASONS, type ModeReleaseReason, isModeReleaseReason, RRA_EXPLAINERS, classifyAsk, foldArbitrationRow, type AskClassification, type AskClassFacts, type ToolMarkSource, arbitrateByMode, type Arbitration, fileCommandOptionsSentence } from "./core/ask-origin.js";
198
199
  export { modePolicyOf, type ModePolicy, PERMISSION_MODE_FOLD_REASONS, type PermissionModeFoldReason, type PermissionModeReceipt, ACCEPT_EDITS_FILE_COMMANDS, modeAcceptsFileCommand } from "./core/mode-policy.js";
200
+ export type { PermissionSeatInputs, PermissionSeatReading } from "./core/mode-policy.js";
201
+ export type { SkillGateSeats, SkillGrantHit, SkillGrantLane, SkillGrantRecord } from "./core/skill-grant.js";
199
202
  export { readDenyClassifierRules, renderClassifierDenyRules, type DerivedDenyRule } from "./core/auto-mode-prompt.js";
200
203
  export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource, type AutoModeDenySource } from "./core/runtime-caps.js";
201
204
  export { SETTLEMENT_KINDS, type SettlementKind, isSettlementKind, type Settlement, SETTLEMENT_PARTIES, type SettlementParty, isSettlementParty, SETTLEMENT_IS_REFUSAL, DENIED_BY_VALUES, type DeniedBy, isDeniedBy, DENIED_BY_MAY_VETO, CLASSIFIER_DENY_CAUSES, type ClassifierDenyCause, isClassifierDenyCause, DENIED_BY_MAY_CARRY_CAUSE, type GateDisposition, type GateOutcome, type ModeRelease, type ModeRefusal, screenGateOutcome } from "./core/gate-outcome.js";
@@ -308,6 +311,8 @@ export { BACKGROUND_AGENT_CONTRACT_SCOPE, backgroundAgentStoreContract, backgrou
308
311
  export { PEER_DIRECTORY_CONTRACT_SCOPE, peerDirectoryContract, type PeerDirectoryContractHooks } from "./core/store-contracts/peer-directory-contract.js";
309
312
  export { type BackgroundAgentStore, type BackgroundAgentRecord, type BackgroundAgentRowSummary, type BackgroundAgentUsage, canAccessAgentRecord, STALE_RUNNING_REAP_ATTRIBUTION, BackgroundAgentStoreError, InMemoryBackgroundAgentStore, reconcileParkedAgents, queryBackgroundAgents, type BackgroundAgentQuery, } from "./core/background-agent-store.js";
310
313
  export { serveDurableAgentRowLane, buildAgentPollDetails, type AgentPollDetailsInput, } from "./core/task-registry-agent.js";
314
+ export { readAgentState, agentStateSourceOfRecord, agentStateSummary, type AgentState, type AgentApproval, type AgentApprovalState, type AgentStateRow, type AgentStateSource, } from "./core/agent-state.js";
315
+ export type { TranscriptStep } from "./agents/subagent-steps.js";
311
316
  export type { UnifiedTaskResult } from "./core/task-registry-shared.js";
312
317
  export { TASK_OUTPUT_REPLAY_KINDS, type TaskOutputReplay } from "./core/task-registry-shared.js";
313
318
  export { REMOVAL_LIVENESS_WORDS, type RemovalLiveness, RULE_WRITE_COMMITTED_WORDS, type RuleWriteCommitted } from "./core/permission-rule-store.js";
@@ -365,7 +370,7 @@ export { ROUTE_ADJUDICATION_CONFORMANCE_CORPUS, type RouteAdjudicationVector } f
365
370
  export { type BrainTimeoutConfig } from "./brain/timeout.js";
366
371
  export { createAssistantMessageEventStream } from "./internal/llm.js";
367
372
  export type { AssistantMessage, AssistantMessageEvent, CompleteSimpleFn, Context, DocumentContent, ImageContent, Message, StopReason, StreamFn, TextContent, ThinkingContent, ToolCall, ToolResultMessage, Usage, UserMessage, } from "./internal/llm.js";
368
- export type { AgentDefinition, BeforeWriteHook, BeforeWriteRequest, BeforeWriteResult, TrackFileEditHook, TrackEditRequest, TrackEditResult, FileEditedHook, FileEditedNotice, HandsBandOptions, Brain, BrainStatus, BrainStatusPhase, BrainRetryErrClass, ImageInput, McpElicitRequest, McpElicitResponse, McpServerSpec, A2aServerSpec, OnElicit, Model, ModelRef, ProjectMemoryLoad, RunnerDeps, ResumePreflightInfo, ResumePreflightVerdict, EngineNotice, RuntimeCaps, BackgroundChildEvent, DelegationLifecycleEvent, SkillManifest, SkillSpec, TaskEvent, TaskEventIdentity, ToolActivity, TaskLimits, StaleToolResultOffloadOptions, TaskResult, TerminalCause, PausedCause, EffectiveMemoryScopes, RemoteEnvFailureNote, TaskSpec, ShellGateDoctrine, TaskStatus, TaskStream, CompactOutcome, ThinkingLevel, ToolExecuteContext, ToolReturn, ToolSpec, ToolInputVerdict, ToolInputValidationContext, ReversibilityVerdict, ReadRootGrantCandidate, ToolEffect, ToolContentOrigin, WorkflowGovernanceBaseline, DelegationTaskType, } from "./core/types.js";
373
+ export type { AgentDefinition, BeforeWriteHook, BeforeWriteRequest, BeforeWriteResult, TrackFileEditHook, TrackEditRequest, TrackEditResult, FileEditedHook, FileEditedNotice, HandsBandOptions, Brain, BrainStatus, BrainStatusPhase, BrainRetryErrClass, ImageInput, McpElicitRequest, McpElicitResponse, McpServerSpec, A2aServerSpec, OnElicit, Model, ModelRef, ProjectMemoryLoad, RunnerDeps, ResumePreflightInfo, ResumePreflightVerdict, EngineNotice, RuntimeCaps, BackgroundChildEvent, DelegationLifecycleEvent, SkillSpec, TaskEvent, TaskEventIdentity, ToolActivity, TaskLimits, StaleToolResultOffloadOptions, TaskResult, TerminalCause, PausedCause, EffectiveMemoryScopes, RemoteEnvFailureNote, TaskSpec, ShellGateDoctrine, TaskStatus, TaskStream, CompactOutcome, ThinkingLevel, ToolExecuteContext, ToolReturn, ToolSpec, ToolInputVerdict, ToolInputValidationContext, ReversibilityVerdict, ReadRootGrantCandidate, ToolEffect, ToolContentOrigin, WorkflowGovernanceBaseline, DelegationTaskType, } from "./core/types.js";
369
374
  export { Type } from "typebox";
370
375
  export type { TSchema, Static } from "typebox";
371
376
  export { explainPromptAssembly, describeDefaultPack, type DefaultPackDescription, type ExplainInput } from "./prompt-assembly/explain.js";
package/dist/index.js CHANGED
@@ -58,7 +58,7 @@ export { WORKFLOW_SIZE_GUIDELINE_AGENT_CAPS } from "./orchestration/workflow-siz
58
58
  export { ENGINE_AUTHORITY_ENVELOPE_TAGS } from "./core/untrusted-text.js";
59
59
  export { materializeA2aTools, A2aRpcError } from "./core/a2a.js";
60
60
  export { PROTOCOL_TABLE, MCP_NAMESPACE, A2A_NAMESPACE, protocolOf } from "./core/protocol-table.js";
61
- export { InMemorySessionPolicyStore, SessionPolicyError, loosenReasons, normalizeRules, stripRev, } from "./core/session-policy-store.js";
61
+ export { InMemorySessionPolicyStore, SessionPolicyError, loosenReasons, normalizeRules, stripRev, assertSessionRuleNamesCurrent, } from "./core/session-policy-store.js";
62
62
  export { SAFETY_MERGE_CONFORMANCE_CORPUS } from "./core/safety-merge-corpus.js";
63
63
  export { SAFETY_AXIS_VOCABULARY } from "./core/safety-axis-vocab.js";
64
64
  export { createSessionRulePolicy } from "./core/runner/session-rule-policy.js";
@@ -115,10 +115,11 @@ export { brainToRuntime } from "./core/runtime.js";
115
115
  export { createSensitivePathPolicy, RECOMMENDED_SENSITIVE_PATTERNS } from "./core/sensitive-path-policy.js";
116
116
  export { createFsWriteGatePolicy, WRITE_EXEMPT_DIR_COVERS_ROOT_CODE } from "./core/fs-write-gate-policy.js";
117
117
  export { RETIRED_TOOL_NAMES } from "./core/tool-name-aliases.js";
118
+ export { judgeRuleToolName, RULE_TOOL_NAME_VERDICTS } from "./core/tool-name-aliases.js";
118
119
  export { DEFAULT_SUBAGENT_TOOL_NAME } from "./agents/subagent.js";
119
120
  export { renderTaskNotificationXml, RESUPPLIED_FRAME_PREAMBLE, taskNotificationDedupKey, isDelegatedAgentTerminal, isTerminalTaskNotification, SystemInjectionQueue, NOTIFICATION_INJECTION_OUTCOMES, isNotificationInjectionOutcome, SYSTEM_INJECTION_PRIORITIES, isSystemInjectionPriority, AGENT_MESSAGE_TAG, renderAgentMessageFrame, HANDBACK_STATES, COMPACTION_KINDS, handbackStateOf, terminalFrameFacets, } from "./core/task-notification.js";
120
121
  export { TOOL_FAMILIES, TOOL_MOUNT_TAGS, TOOL_APPROVAL_CARDS, TOOL_PATH_ACCESSES, TOOL_PATH_BASES, TOOL_PATH_ABSENCES, TOOL_WIRE_NAME_MAX_CHARS, TOOL_CONTRACT_MAX_CHARS, TOOL_CARD_ID_MAX_CHARS, TOOL_KEY_MAX_CHARS, TOOL_KEYS_MAX, RENDER_HINT_MAX_CHARS, RENDER_HINT_ACTIVITY_MAX_CHARS, RENDER_HINT_MAX_LIST, pathTargetSlotOnly, } from "./core/tool-face.js";
121
- export { describeToolCatalog, catalogEntriesNamed, catalogToolNames, catalogRuleFaceOf, pathTargetOf, pathTargetValue, protectivePathTargetOf, declaredPathTargetOf, BARE_REQUEST_CODE, isProtectedWrite, isDeclaredWrite, fileOpAccessOf, pathConfinableWriteToolNames, skillScopeWriteToolNames, pathTargetingToolNames, compactableToolNames, budgetExemptToolNames, handBandEffects, engineCardTypes, predictMountedToolNames, HANDS_BAND_TAGS, MOUNT_TAG_IN_HANDS_BAND, } from "./core/tool-registry.js";
122
+ export { describeToolCatalog, catalogEntriesNamed, catalogToolNames, catalogRuleFaceOf, pathTargetOf, pathTargetValue, protectivePathTargetOf, declaredPathTargetOf, BARE_REQUEST_CODE, isProtectedWrite, isDeclaredWrite, fileOpAccessOf, pathConfinableWriteToolNames, pathTargetingToolNames, compactableToolNames, budgetExemptToolNames, handBandEffects, engineCardTypes, predictMountedToolNames, HANDS_BAND_TAGS, MOUNT_TAG_IN_HANDS_BAND, } from "./core/tool-registry.js";
122
123
  export { ToolRosterEntry, ToolRoster, ToolRosterDelta, ToolRosterRenderHints, ROSTER_SOURCES, toolIdentityOf, mintCapabilityId, diffToolRoster, judgeParkedToolIdentity, ruleFacesFromRoster, toolCallFaceOf, rosterToolName, ruleNamesOf, inputKeysOf, ruleFaceOfInstance, rosterMemberBoundProblem, ToolRosterDeltaSeat, } from "./core/tool-roster.js";
123
124
  export {} from "./core/tool-policy.js";
124
125
  export { effectivePathTargetOf, pathTargetBaseOf } from "./core/effective-path-target.js";
@@ -255,6 +256,7 @@ export { BACKGROUND_AGENT_CONTRACT_SCOPE, backgroundAgentStoreContract, backgrou
255
256
  export { PEER_DIRECTORY_CONTRACT_SCOPE, peerDirectoryContract } from "./core/store-contracts/peer-directory-contract.js";
256
257
  export { canAccessAgentRecord, STALE_RUNNING_REAP_ATTRIBUTION, BackgroundAgentStoreError, InMemoryBackgroundAgentStore, reconcileParkedAgents, queryBackgroundAgents, } from "./core/background-agent-store.js";
257
258
  export { serveDurableAgentRowLane, buildAgentPollDetails, } from "./core/task-registry-agent.js";
259
+ export { readAgentState, agentStateSourceOfRecord, agentStateSummary, } from "./core/agent-state.js";
258
260
  export { TASK_OUTPUT_REPLAY_KINDS } from "./core/task-registry-shared.js";
259
261
  export { REMOVAL_LIVENESS_WORDS, RULE_WRITE_COMMITTED_WORDS } from "./core/permission-rule-store.js";
260
262
  export { normalizeReadRootCandidate, READ_ROOT_CANDIDATE_DIR_MAX } from "./core/read-root-candidate.js";
@@ -3,7 +3,8 @@ import { existsSync, readdirSync, readFileSync, unlinkSync } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
4
  import { adoptFilePermissionRuleStore } from "../permission-rule-adopt.js";
5
5
  import { BootLock, atomicWriteFile, ensureDir, sanitizeScope } from "../fs-atomic.js";
6
- import { SESSION_POLICY_MARK_SUFFIX, judgeSessionPolicyRowObject, readSessionPolicyMark } from "../session-policy-store.js";
6
+ import { SESSION_POLICY_MARK_SUFFIX, attributeSessionPolicyRow, judgeSessionPolicyRowObject, readSessionPolicyMark } from "../session-policy-store.js";
7
+ import { SessionPolicyError } from "../../../core/session-policy-store.js";
7
8
  import { thrownText } from "../../../core/thrown-value.js";
8
9
  import { AdoptionError, NOT_MIGRATED_BY_DESIGN, readRootAdoptionFile, writeRootAdoptionFile, } from "./marker.js";
9
10
  const CONFIG_ACCOUNT_FILE = "adoption-configs.json";
@@ -193,12 +194,8 @@ function rewriteSessionPolicyRows(root, fromPrincipal, toPrincipal) {
193
194
  const tmpDir = join(dir, "tmp");
194
195
  let rows = 0;
195
196
  for (const file of files) {
196
- if (!file.endsWith(".json"))
197
+ if (!file.endsWith(".json") || file.endsWith(SESSION_POLICY_MARK_SUFFIX))
197
198
  continue;
198
- if (file.endsWith(SESSION_POLICY_MARK_SUFFIX)) {
199
- carrySessionPolicyMark(dir, tmpDir, file, fromPrincipal, toPrincipal);
200
- continue;
201
- }
202
199
  const source = join(dir, file);
203
200
  let raw;
204
201
  try {
@@ -210,15 +207,14 @@ function rewriteSessionPolicyRows(root, fromPrincipal, toPrincipal) {
210
207
  throw err;
211
208
  }
212
209
  const judged = judgeSessionPolicyRowObject(raw);
213
- if ("corrupt" in judged)
210
+ const row = "row" in judged ? judged.row : undefined;
211
+ const { owner } = attributeSessionPolicyRow(dir, file, row);
212
+ if (owner === undefined)
214
213
  continue;
215
- const row = judged.row;
216
- if (row.__principal !== fromPrincipal)
214
+ if (owner.principal !== fromPrincipal)
217
215
  continue;
218
- if (typeof row.__sid !== "string")
219
- continue;
220
- const stem = sanitizeScope(JSON.stringify([row.__sid, toPrincipal]));
221
- const rewritten = JSON.stringify({ ...row, __principal: toPrincipal });
216
+ const stem = sanitizeScope(JSON.stringify([owner.sessionId, toPrincipal]));
217
+ const rewritten = row !== undefined ? JSON.stringify({ ...row, __principal: toPrincipal }) : raw;
222
218
  const target = join(dir, `${stem}.json`);
223
219
  if (existsSync(target)) {
224
220
  if (readFileSync(target, "utf8") === rewritten) {
@@ -226,18 +222,22 @@ function rewriteSessionPolicyRows(root, fromPrincipal, toPrincipal) {
226
222
  rows += 1;
227
223
  continue;
228
224
  }
229
- throw new AdoptionError("adoption_row_collision", `a session-policy row for session "${row.__sid}" already exists under "${toPrincipal}" with different content — ` +
225
+ throw new AdoptionError("adoption_row_collision", `a session-policy row for session "${owner.sessionId}" already exists under "${toPrincipal}" with different content — ` +
230
226
  `merging two existing rows is a sync join, not a rewrite; the adoption marker is left in place for inspection`);
231
227
  }
232
228
  atomicWriteFile(tmpDir, target, rewritten);
233
229
  removeIfPresent(source);
234
230
  rows += 1;
235
231
  }
232
+ for (const file of files) {
233
+ if (file.endsWith(SESSION_POLICY_MARK_SUFFIX))
234
+ carrySessionPolicyMark(dir, tmpDir, file, fromPrincipal, toPrincipal);
235
+ }
236
236
  return rows;
237
237
  }
238
238
  function carrySessionPolicyMark(dir, tmpDir, file, fromPrincipal, toPrincipal) {
239
239
  const source = join(dir, file);
240
- const mark = readSessionPolicyMark(source);
240
+ const mark = readMarkOrRefuse(source);
241
241
  if (mark === null)
242
242
  return;
243
243
  if (mark.__principal !== fromPrincipal)
@@ -245,12 +245,19 @@ function carrySessionPolicyMark(dir, tmpDir, file, fromPrincipal, toPrincipal) {
245
245
  if (typeof mark.__sid !== "string")
246
246
  return;
247
247
  const target = join(dir, `${sanitizeScope(JSON.stringify([mark.__sid, toPrincipal]))}${SESSION_POLICY_MARK_SUFFIX}`);
248
- const there = readSessionPolicyMark(target);
248
+ const there = readMarkOrRefuse(target);
249
249
  const merged = there === null ? mark.__hwm : Math.max(mark.__hwm, there.__hwm);
250
250
  const mergedGen = Math.max(mark.__gen ?? 0, there?.__gen ?? 0);
251
251
  atomicWriteFile(tmpDir, target, JSON.stringify({ ...mark, __principal: toPrincipal, __hwm: merged, __gen: mergedGen }));
252
252
  removeIfPresent(source);
253
253
  }
254
+ function readMarkOrRefuse(path) {
255
+ const read = readSessionPolicyMark(path);
256
+ if ("corrupt" in read) {
257
+ throw new SessionPolicyError("corrupt", `session rules high-water mark ${read.corrupt.reason}: ${path} — refusing rather than restart this key's rev from zero. Deleting that file is the repair, and it gives up the number it was holding.`);
258
+ }
259
+ return read.mark;
260
+ }
254
261
  function removeIfPresent(path) {
255
262
  try {
256
263
  unlinkSync(path);
@@ -4,6 +4,7 @@ import type { ToolResultStore } from "../../core/tool-result-store.js";
4
4
  import { type EvictPolicy } from "../../core/session-store.js";
5
5
  import type { SessionStore } from "../../core/session.js";
6
6
  import { type FileCheckpointStoreOptions } from "./checkpoint-store.js";
7
+ import { type FileSessionPolicyStoreOptions } from "./session-policy-store.js";
7
8
  import { type FileFileHistoryStoreOptions } from "./file-history-store.js";
8
9
  import type { CorruptReadInfo } from "./corrupt-read-sink.js";
9
10
  import type { FileHistoryStore } from "../../core/file-history-store.js";
@@ -21,7 +22,7 @@ export { FileToolResultStore } from "./tool-result-store.js";
21
22
  export { FileSessionPolicyStore, type FileSessionPolicyStoreOptions, type SessionPolicyCorruptReadInfo } from "./session-policy-store.js";
22
23
  export { FileFileHistoryStore, type FileFileHistoryStoreOptions } from "./file-history-store.js";
23
24
  export { FileWorkflowJournalStore, MAX_JOURNAL_RESULT_BYTES, oversizeJournalResult } from "./workflow-journal-store.js";
24
- export { FileUsageWindowStore } from "./usage-window-store.js";
25
+ export { FileUsageWindowStore, type FileUsageWindowStoreOptions } from "./usage-window-store.js";
25
26
  export { FileStrategyStore, type FileStrategyStoreOptions } from "./strategy-store.js";
26
27
  export { resolveDataRoot, sanitizeScope, sanitizePathComponent, createFileConsolidationLock } from "./fs-atomic.js";
27
28
  export { FileStoreLockError, type FileStoreLockErrorCode } from "./fs-atomic.js";
@@ -74,6 +75,10 @@ export interface FileStorageBackendOptions {
74
75
  * FileStorageBackendOptions.onCorruptRead} above, because one failure grammar takes one sink.
75
76
  */
76
77
  fileHistory?: Omit<FileFileHistoryStoreOptions, "onCorruptRead">;
78
+ /** The session-rules store's options — today the names this deployment mounts its own tools under
79
+ * (`mountedToolNames`, the roster its write and loosening checks judge rule names against). The corrupt-read sink is
80
+ * wired from {@link FileStorageBackendOptions.onCorruptRead}, as for {@link fileHistory}. */
81
+ sessionPolicy?: Omit<FileSessionPolicyStoreOptions, "onCorruptRead">;
77
82
  /** design/81 Slice 5 — an optional {@link import("../../core/memory.js").Embedder}. Injected ⇒ the memory
78
83
  * store does portable (in-process) vector recall over a rebuildable sidecar index (`vectorMode:"portable"`);
79
84
  * absent ⇒ the lexical floor, byte-identical to before. Config-driven (the deployment injects it); OFF by
@@ -93,7 +98,8 @@ export interface FileStorageBackendOptions {
93
98
  * Reach TODAY: {@link FileStorageBackend.sessionPolicyStore} (an absent file ⇒ no rules; a present but
94
99
  * unreadable / unparseable / malformed file ⇒ refused, the disclosure delivered alongside the refusal)
95
100
  * and {@link FileStorageBackend.sessionStore}'s repo (a session listing that silently omits what it
96
- * could not read). The aggregated roster and mailbox stores receive the same sink. A store that
101
+ * could not read). The aggregated roster, mailbox and usage-window stores receive the same sink (the
102
+ * usage-window ledger refuses a damaged record and discloses its location here). A store that
97
103
  * grows the seat later is wired here in the same constructor.
98
104
  *
99
105
  * The payload is the UNION shape ({@link FileStorageCorruptReadInfo}): `path` + `reason` always,
@@ -72,11 +72,11 @@ export class FileStorageBackend {
72
72
  built.push(() => this.fileMemory.close());
73
73
  this.memoryStore = guardedMemoryStore(this.fileMemory, opts.utilityGate);
74
74
  this.toolResultStore = new FileToolResultStore(this.root, corruptRead);
75
- this.sessionPolicyStore = new FileSessionPolicyStore(this.root, corruptRead);
75
+ this.sessionPolicyStore = new FileSessionPolicyStore(this.root, { ...opts.sessionPolicy, ...corruptRead });
76
76
  this.fileWorkflowJournal = new FileWorkflowJournalStore(this.root, true, corruptRead);
77
77
  built.push(() => this.fileWorkflowJournal.dispose());
78
78
  this.workflowJournalStore = this.fileWorkflowJournal;
79
- this.usageWindowStore = new FileUsageWindowStore(this.root);
79
+ this.usageWindowStore = new FileUsageWindowStore(this.root, corruptRead);
80
80
  this.fileAgentRows = new FileBackgroundAgentStore(this.root, corruptRead ?? {});
81
81
  built.push(() => this.fileAgentRows.close());
82
82
  this.backgroundAgentStore = this.fileAgentRows;
@@ -145,7 +145,7 @@ export async function acquireMailboxBoxLock(lockPath, waitMs) {
145
145
  return;
146
146
  }
147
147
  if (now === undefined || now === "unreadable" || now.nonce !== mine.nonce) {
148
- const e = new Error(`FileMailboxStore: the box lock ${lockPath} changed hands while this process held it (a stale reclaim overlapped) — the operation just performed may have raced another process; do not trust it`);
148
+ const e = new Error(`FileMailboxStore: the box lock changed hands while this process held it (a stale reclaim overlapped) — the operation just performed may have raced another process; do not trust it. The lock file's location is on this error's \`lockPath\`, not in this message.`);
149
149
  e.code = MAILBOX_BOX_LOCK_COMPROMISED_CODE;
150
150
  e.lockPath = lockPath;
151
151
  throw e;
@@ -195,7 +195,7 @@ export async function acquireMailboxBoxLock(lockPath, waitMs) {
195
195
  sweepLockResidue(join(lockPath, ".."), lockPath.slice(join(lockPath, "..").length + 1));
196
196
  }
197
197
  if (Date.now() >= deadline) {
198
- const e = new Error(`FileMailboxStore: the box lock ${lockPath} was not obtainable within ${waitMs}ms (${holderNote}) — refusing to touch the box without it`);
198
+ const e = new Error(`FileMailboxStore: the box lock was not obtainable within ${waitMs}ms (${holderNote}) — refusing to touch the box without it. The lock file's location is on this error's \`lockPath\`, not in this message.`);
199
199
  e.code = MAILBOX_BOX_LOCK_TIMEOUT_CODE;
200
200
  e.lockPath = lockPath;
201
201
  if (owner !== "unreadable" && owner !== "unobservable")
@@ -4,6 +4,7 @@ import { writerOf } from "../../core/permission-rule-store.js";
4
4
  import { createPermissionRuleStoreProvider } from "../../core/permission-rule-provider.js";
5
5
  import { syncPermissionRules } from "../../core/permission-rule-sync.js";
6
6
  import { thrownText } from "../../core/thrown-value.js";
7
+ import { FileStoreLockError } from "./fs-atomic.js";
7
8
  import { FileDurableRulePartitionProvider, LOCAL_OWNER_FILE, principalRuleFileName, readRuleAdoptionFile, writeRuleAdoptionFile, } from "./permission-rule-store.js";
8
9
  export async function adoptFilePermissionRuleStore(opts) {
9
10
  if (typeof opts.toPrincipal !== "string" || opts.toPrincipal === "") {
@@ -30,7 +31,15 @@ export async function adoptFilePermissionRuleStore(opts) {
30
31
  }
31
32
  const provider = new FileDurableRulePartitionProvider(opts.dir, opts.onError);
32
33
  try {
33
- const lockProbe = writerOf(provider.forPrincipal(opts.toPrincipal));
34
+ let lockProbe;
35
+ try {
36
+ lockProbe = writerOf(provider.forPrincipal(opts.toPrincipal));
37
+ }
38
+ catch (err) {
39
+ if (!(err instanceof FileStoreLockError))
40
+ throw err;
41
+ throw new FileStoreLockError(err.code, `the permission-rule writer lock ${err.lockPath} refuses the adoption (${err.code}${err.ownerPid !== undefined ? `, held by pid ${err.ownerPid}` : ""}) — stop the live writer, then run the adoption again`, err.lockPath, err.ownerPid);
42
+ }
34
43
  if (lockProbe === undefined)
35
44
  throw new Error("the target store refused a write face");
36
45
  let phase = existing !== undefined && "marker" in existing ? existing.marker.phase : 2;
@@ -108,9 +108,8 @@ export type RuleAdoptionFile = {
108
108
  atMs: number;
109
109
  };
110
110
  };
111
- /** Read the adoption marker, `undefined` when none. A corrupt marker THROWS: after phase 4 the marker
112
- * IS the owner-resolution truth, and guessing on a corrupt truth would either resurrect the retired
113
- * bucket (write fork) or invent an adoption that never happened. */
111
+ /** The ADOPTION voice of {@link judgeRuleAdoptionFile}: the adoption helper's reader is the operator who repairs the
112
+ * marker, so it names the directory. A corrupt marker THROWS. */
114
113
  export declare function readRuleAdoptionFile(dir: string): RuleAdoptionFile | undefined;
115
114
  /** Atomically publish the adoption marker (same write-temp → fsync → rename discipline as the store). */
116
115
  export declare function writeRuleAdoptionFile(dir: string, content: RuleAdoptionFile): void;
@@ -123,7 +122,8 @@ declare class FileDurableRulePartition implements WritableDurableRulePartition {
123
122
  readonly durability: StoreDurability;
124
123
  readonly fidelity: StoreFidelity;
125
124
  constructor(dir: string, file: string, acquireWriteLock: () => void, onError?: ((message: string) => void) | undefined);
126
- /** Read and validate. Any refusal answers with an empty set AND says so — never a silent empty store. */
125
+ /** Read and validate. Any refusal answers with an empty set AND says so — never a silent empty store. The answer's
126
+ * sentence names no location ({@link refusalText}); the disclosure that accompanies it does. */
127
127
  private read;
128
128
  private disclose;
129
129
  /**
@@ -204,6 +204,8 @@ export declare class FileDurableRulePartitionProvider implements DurableRulePart
204
204
  });
205
205
  /** The org partition's durable home in this directory (design/182 §7.4 contract; `org.json`). Shares
206
206
  * this provider's writer lock — an install is a write. */
207
+ /** {@link judgeRuleAdoptionFile} at request time (owner resolution runs on every read), in {@link refusalText}'s voice. */
208
+ private adoptionMarker;
207
209
  orgStatePersistence(): OrgRuleStatePersistence;
208
210
  private fileOrgPersistence;
209
211
  /** Take the writer lock, once, on first use of a write face. Reading never calls this. */