@sema-agent/core 7.50.2 → 7.51.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/CHANGELOG.md +313 -0
  2. package/dist/agents/child-failure.d.ts +5 -16
  3. package/dist/agents/child-failure.js +4 -24
  4. package/dist/agents/subagent-steps.js +2 -2
  5. package/dist/agents/subagent.d.ts +4 -13
  6. package/dist/agents/subagent.js +94 -204
  7. package/dist/agents/tool-filter.d.ts +2 -2
  8. package/dist/brain/anthropic.d.ts +1 -1
  9. package/dist/brain/anthropic.js +7 -5
  10. package/dist/brain/open-responses.d.ts +1 -1
  11. package/dist/brain/open-responses.js +3 -2
  12. package/dist/brain/openai.d.ts +1 -1
  13. package/dist/brain/openai.js +3 -2
  14. package/dist/brain/request-route.d.ts +2 -2
  15. package/dist/brain/route-adjudicator.d.ts +16 -7
  16. package/dist/brain/route-adjudicator.js +23 -7
  17. package/dist/brain/route-conformance.js +23 -5
  18. package/dist/brain/stream-engine.js +9 -3
  19. package/dist/core/a2a.js +6 -8
  20. package/dist/core/allow-entry.d.ts +45 -26
  21. package/dist/core/allow-entry.js +48 -24
  22. package/dist/core/arg-summary.d.ts +8 -85
  23. package/dist/core/arg-summary.js +4 -386
  24. package/dist/core/ask-origin.d.ts +35 -33
  25. package/dist/core/ask-origin.js +9 -11
  26. package/dist/core/checkpoint-requirements.d.ts +6 -0
  27. package/dist/core/checkpoint-requirements.js +1 -0
  28. package/dist/core/checkpoint-store.d.ts +15 -34
  29. package/dist/core/checkpoint-store.js +5 -4
  30. package/dist/core/classifier-unavailable-arm.d.ts +12 -8
  31. package/dist/core/classifier-unavailable-arm.js +7 -2
  32. package/dist/core/credential-vocabulary.d.ts +3 -2
  33. package/dist/core/credential-vocabulary.js +27 -18
  34. package/dist/core/delegation-frames.d.ts +40 -15
  35. package/dist/core/delegation-frames.js +34 -0
  36. package/dist/core/engine-capabilities.d.ts +15 -0
  37. package/dist/core/engine-capabilities.js +1 -0
  38. package/dist/core/engine-notice.d.ts +41 -14
  39. package/dist/core/gate-fold.js +9 -7
  40. package/dist/core/gate-lanes.js +6 -4
  41. package/dist/core/gate-outcome.d.ts +81 -15
  42. package/dist/core/gate-outcome.js +168 -67
  43. package/dist/core/governance-codes.d.ts +7 -1
  44. package/dist/core/governance-codes.js +9 -0
  45. package/dist/core/hooks.d.ts +4 -4
  46. package/dist/core/hooks.js +4 -3
  47. package/dist/core/mcp-connection-pool.d.ts +2 -0
  48. package/dist/core/mcp-connection-pool.js +2 -1
  49. package/dist/core/mcp-failure.js +2 -1
  50. package/dist/core/mcp-server-spec.d.ts +27 -5
  51. package/dist/core/mcp.d.ts +13 -38
  52. package/dist/core/mcp.js +28 -23
  53. package/dist/core/media-byte-cap.d.ts +25 -0
  54. package/dist/core/media-byte-cap.js +18 -2
  55. package/dist/core/memory-engine/consolidation-driver.js +1 -1
  56. package/dist/core/mode-policy.d.ts +25 -1
  57. package/dist/core/mode-policy.js +28 -13
  58. package/dist/core/own-property-receipt.d.ts +18 -0
  59. package/dist/core/own-property-receipt.js +39 -0
  60. package/dist/core/permission-rule-model.d.ts +32 -11
  61. package/dist/core/permission-rule-model.js +12 -7
  62. package/dist/core/persisted-rule-arms.d.ts +3 -3
  63. package/dist/core/prompt-section-seat.d.ts +54 -0
  64. package/dist/core/prompt-section-seat.js +49 -0
  65. package/dist/core/protocol-http-auth.d.ts +44 -0
  66. package/dist/core/protocol-http-auth.js +45 -0
  67. package/dist/core/read-face-seats.d.ts +30 -0
  68. package/dist/core/read-face-seats.js +38 -0
  69. package/dist/core/redaction-passes.d.ts +82 -0
  70. package/dist/core/redaction-passes.js +378 -0
  71. package/dist/core/remote-env.d.ts +4 -3
  72. package/dist/core/resume-startup-cause.d.ts +56 -0
  73. package/dist/core/resume-startup-cause.js +43 -0
  74. package/dist/core/rule-denial-sentence.d.ts +2 -2
  75. package/dist/core/runner/announce-once-ledger.js +2 -2
  76. package/dist/core/runner/classifier-ladder.d.ts +17 -13
  77. package/dist/core/runner/classifier-ladder.js +12 -10
  78. package/dist/core/runner/construction-doors.js +9 -20
  79. package/dist/core/runner/contracts.d.ts +4 -4
  80. package/dist/core/runner/gate-exit.js +2 -2
  81. package/dist/core/runner/memory-consolidation-write-judge.d.ts +53 -9
  82. package/dist/core/runner/memory-consolidation-write-judge.js +39 -1
  83. package/dist/core/runner/permission-rule-lanes.d.ts +8 -13
  84. package/dist/core/runner/person-path-rows.d.ts +4 -3
  85. package/dist/core/runner/policy-throw.js +4 -4
  86. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -3
  87. package/dist/core/runner/prepare-caps-and-workflow.js +8 -6
  88. package/dist/core/runner/prepare-gate-stations.js +4 -4
  89. package/dist/core/runner/prepare-hands-readface.js +1 -19
  90. package/dist/core/runner/prepare-inherited-gate.js +10 -9
  91. package/dist/core/runner/prepare-policy-chain.d.ts +3 -5
  92. package/dist/core/runner/prepare-policy-chain.js +10 -13
  93. package/dist/core/runner/prepare-project-context.d.ts +4 -4
  94. package/dist/core/runner/prepare-project-context.js +2 -1
  95. package/dist/core/runner/prepare-prompt-assembly.js +1 -0
  96. package/dist/core/runner/prepare-prompt-inputs.d.ts +1 -1
  97. package/dist/core/runner/prepare-prompt-inputs.js +4 -4
  98. package/dist/core/runner/prepare-protocol-tools.d.ts +4 -3
  99. package/dist/core/runner/prepare-protocol-tools.js +3 -2
  100. package/dist/core/runner/prepare-task.js +6 -4
  101. package/dist/core/runner/prepare-turn-wiring.d.ts +16 -13
  102. package/dist/core/runner/prepare-turn-wiring.js +8 -7
  103. package/dist/core/runner/run-harness-handlers.js +2 -2
  104. package/dist/core/runner/run-leg.js +10 -9
  105. package/dist/core/runner/run-settle-and-teardown.js +5 -5
  106. package/dist/core/runner/run-turn-boundary.js +3 -2
  107. package/dist/core/runner/runtask.d.ts +1 -2
  108. package/dist/core/runner/runtask.js +8 -12
  109. package/dist/core/runner/session-rule-policy.d.ts +15 -11
  110. package/dist/core/runner/session-rule-policy.js +43 -33
  111. package/dist/core/runner/stream-lifecycle-verbs.js +2 -2
  112. package/dist/core/runner/stream-settle-backstop.js +2 -1
  113. package/dist/core/runner/sub-agent-deny-lists.d.ts +12 -8
  114. package/dist/core/runner/sub-agent-deny-lists.js +23 -12
  115. package/dist/core/runner/terminal-account.js +3 -3
  116. package/dist/core/runner/tool-output-projection.d.ts +5 -0
  117. package/dist/core/runner/tool-output-projection.js +6 -11
  118. package/dist/core/runner/tool-result-media-seat.d.ts +19 -0
  119. package/dist/core/runner/tool-result-media-seat.js +13 -0
  120. package/dist/core/runner/turn-attachments.d.ts +13 -12
  121. package/dist/core/runner/turn-attachments.js +11 -7
  122. package/dist/core/runner-deps.d.ts +4 -3
  123. package/dist/core/safety-merge-corpus.js +21 -21
  124. package/dist/core/secret-env.d.ts +41 -15
  125. package/dist/core/secret-env.js +85 -19
  126. package/dist/core/session-policy-store.d.ts +89 -72
  127. package/dist/core/session-policy-store.js +100 -93
  128. package/dist/core/shell-read-silence.d.ts +38 -4
  129. package/dist/core/shell-read-silence.js +102 -0
  130. package/dist/core/side-query.d.ts +7 -8
  131. package/dist/core/skill-grant-binding.d.ts +32 -11
  132. package/dist/core/skill-grant-binding.js +11 -3
  133. package/dist/core/skill-grant.d.ts +4 -3
  134. package/dist/core/skill-grant.js +5 -4
  135. package/dist/core/skills-directory.d.ts +12 -1
  136. package/dist/core/skills-directory.js +17 -8
  137. package/dist/core/spec-contract.d.ts +5 -3
  138. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +2 -0
  139. package/dist/core/store-contracts/checkpoint-store-contract.js +3 -0
  140. package/dist/core/swappable-deps.d.ts +15 -1
  141. package/dist/core/swappable-deps.js +25 -1
  142. package/dist/core/synthetic-read-verdict.d.ts +16 -14
  143. package/dist/core/synthetic-read-verdict.js +28 -8
  144. package/dist/core/task-notification.d.ts +13 -3
  145. package/dist/core/task-notification.js +12 -1
  146. package/dist/core/task-spec.d.ts +14 -15
  147. package/dist/core/tool-name-readings.d.ts +0 -4
  148. package/dist/core/tool-name-readings.js +0 -1
  149. package/dist/core/tool-policy.d.ts +40 -9
  150. package/dist/core/tool-policy.js +25 -7
  151. package/dist/core/types.d.ts +3 -3
  152. package/dist/core/types.js +1 -1
  153. package/dist/core/untrusted-egress.d.ts +60 -11
  154. package/dist/core/untrusted-egress.js +120 -9
  155. package/dist/core/untrusted-text.js +16 -0
  156. package/dist/core/wiring-manifest.d.ts +22 -8
  157. package/dist/core/wiring-manifest.js +12 -4
  158. package/dist/engine/execution-env/kill-tree.js +4 -4
  159. package/dist/engine/execution-env/node-execution-env.d.ts +11 -4
  160. package/dist/engine/execution-env/node-execution-env.js +9 -4
  161. package/dist/engine/harness/agent-harness.d.ts +2 -1
  162. package/dist/engine/harness/agent-harness.js +4 -1
  163. package/dist/engine/harness/types.d.ts +3 -0
  164. package/dist/engine/llm/types.d.ts +7 -9
  165. package/dist/engine/loop/agent-loop.d.ts +8 -3
  166. package/dist/engine/loop/agent-loop.js +26 -9
  167. package/dist/engine/loop/types.d.ts +10 -0
  168. package/dist/engine/lsp/node-lsp-manager.d.ts +2 -1
  169. package/dist/engine/lsp/node-lsp-manager.js +2 -2
  170. package/dist/index.d.ts +22 -5
  171. package/dist/index.js +18 -5
  172. package/dist/orchestration/run-workflow-tool.js +8 -8
  173. package/dist/orchestration/workflow.js +2 -2
  174. package/dist/prompt-assembly/assemble.d.ts +3 -1
  175. package/dist/prompt-assembly/assemble.js +6 -0
  176. package/dist/prompts/cowork.d.ts +42 -0
  177. package/dist/prompts/cowork.js +529 -0
  178. package/dist/prompts/default.d.ts +14 -1
  179. package/dist/prompts/default.js +3 -0
  180. package/dist/stores/file/fs-atomic.js +2 -2
  181. package/dist/stores/file/session-policy-store.d.ts +6 -3
  182. package/dist/stores/file/session-policy-store.js +5 -16
  183. package/dist/tools/artifact/artifact-tool.js +2 -0
  184. package/dist/tools/fs/image-admission.d.ts +57 -4
  185. package/dist/tools/fs/image-admission.js +85 -0
  186. package/dist/tools/fs/path-containment.d.ts +2 -0
  187. package/dist/tools/fs/path-containment.js +5 -0
  188. package/dist/tools/fs/shell-read-check.d.ts +31 -6
  189. package/dist/tools/fs/shell-read-check.js +2 -4
  190. package/dist/tools/fs/shell-read-environment.d.ts +10 -25
  191. package/dist/tools/fs/shell-read-environment.js +6 -18
  192. package/dist/tools/fs/shell-read-establish.d.ts +3 -2
  193. package/dist/tools/fs/shell-read-establish.js +5 -5
  194. package/dist/tools/fs/shell-read-judge.d.ts +38 -8
  195. package/dist/tools/fs/shell-read-judge.js +12 -10
  196. package/dist/tools/web.js +2 -2
  197. package/package.json +1 -1
  198. package/test/export-surface.snapshot.json +112 -10
  199. package/wire-changes/core@7.51.0.json +2421 -0
  200. package/wire-changes/core@7.51.1.json +259 -0
@@ -1,6 +1,7 @@
1
1
  import { readFileSync, readdirSync, statSync } from "node:fs";
2
- import { isAbsolute, join, relative } from "node:path";
2
+ import { join } from "node:path";
3
3
  import { physicalPathOf } from "./physical-path.js";
4
+ import { isPhysicallyWithin } from "../tools/fs/path-containment.js";
4
5
  import { assertNoRetiredKeys } from "./retired-keys.js";
5
6
  import { thrownText } from "./thrown-value.js";
6
7
  const SKILL_FILE = "SKILL.md";
@@ -167,12 +168,6 @@ function unquote(value) {
167
168
  const inner = value.slice(1, -1);
168
169
  return q === '"' ? inner.replace(/\\t/g, "\t").replace(/\\n/g, "\n") : inner;
169
170
  }
170
- function isInsideRoot(root, p) {
171
- if (p === root)
172
- return true;
173
- const rel = relative(root, p);
174
- return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
175
- }
176
171
  function listRelativeFiles(base, dir, prefix, out, walk) {
177
172
  let entries;
178
173
  try {
@@ -222,7 +217,7 @@ function resolveInsideSkill(abs, rel, walk) {
222
217
  walk.warn({ code: "attachment_skipped", skill: walk.skill, detail: `${rel}: could not be resolved (${errText(err)})` });
223
218
  return undefined;
224
219
  }
225
- if (!isInsideRoot(walk.realRoot, real)) {
220
+ if (!isPhysicallyWithin(walk.realRoot, real)) {
226
221
  walk.warn({
227
222
  code: "attachment_skipped",
228
223
  skill: walk.skill,
@@ -337,6 +332,18 @@ export function createSkillsFromDirectory(dir, options = {}) {
337
332
  catch (err) {
338
333
  throw new Error(`skills directory could not be read: ${dir} (${errText(err)})`);
339
334
  }
335
+ const containWithin = options.containWithin;
336
+ if (containWithin !== undefined) {
337
+ const isDir = typeof containWithin === "string" && statSync(containWithin, { throwIfNoEntry: false })?.isDirectory() === true;
338
+ if (!isDir)
339
+ throw new Error(`skills directory containment root is not a readable directory: ${String(containWithin)}`);
340
+ }
341
+ const outside = (skill, path, what) => {
342
+ if (containWithin === undefined || isPhysicallyWithin(containWithin, path))
343
+ return false;
344
+ warn({ code: "outside_root", skill, detail: `${what} resolves outside ${containWithin} — skipped` });
345
+ return true;
346
+ };
340
347
  const dirNames = [];
341
348
  for (const e of entries) {
342
349
  if (e.isDirectory()) {
@@ -357,6 +364,8 @@ export function createSkillsFromDirectory(dir, options = {}) {
357
364
  const skills = [];
358
365
  for (const name of dirNames) {
359
366
  const skillDir = join(dir, name);
367
+ if (outside(name, skillDir, "its directory") || outside(name, join(skillDir, SKILL_FILE), SKILL_FILE))
368
+ continue;
360
369
  let text;
361
370
  try {
362
371
  text = readFileSync(join(skillDir, SKILL_FILE), "utf8");
@@ -7,7 +7,8 @@
7
7
  * 任意命令)。它只能来自部署作者/可信代码路径;LLM 生成的 contract 直接喂 runSpec = 不受信输入获得
8
8
  * 命令执行权(越红线),禁止。contract 里的 `spec` **文本**才是 DATA-tier(runSpec 恒围栏置入 objective)。
9
9
  * 命令可信 ≠ 它跑的代码可信:门跑的测试会加载模型改过的代码,故门子进程的环境缺省经 `scrubSecretEnv`
10
- * (`OracleGateSpec.inheritEnv`,与 tool shell 同一三态),凭据名只在部署显式列名 / `"all"` 时交给它。
10
+ * (`OracleGateSpec.inheritEnv`,与 tool shell 同一三态),凭据名只在部署显式列名 / `"all"` 时交给它;
11
+ * 部署声明的自有凭据名(`declareWithheldEnv`)在任一形下都不交。
11
12
  *
12
13
  * 🔴 **oracle 三值(fable-M6)**:每门结果 ∈ {green, red, error} —— 判据不满足 = red;基础设施故障
13
14
  * (ENOENT/timeout/maxBuffer/parse 解析不出)= **error 非 red**。假红防线:error 绝不冒充"曾经红过"
@@ -56,8 +57,9 @@ export interface OracleGateSpec {
56
57
  * way. Absent / `"scrub"`: the environment without its credential-shaped variables (`scrubSecretEnv`); the command
57
58
  * runs code the model wrote, so the credentials are not handed to it by default. `"all"`: the whole environment.
58
59
  * A list: exactly the named variables that are set (a strict allow list — `PATH` too, when the command is looked
59
- * up on it). Names only, never values: the contract is serializable data. A value of any other shape makes the
60
- * gate `error`.
60
+ * up on it). Under every form, the deployment's declared own credentials (`declareWithheldEnv`) are withheld, and a
61
+ * list naming one makes the gate `error`. Names only, never values: the contract is serializable data. A value of
62
+ * any other shape makes the gate `error`.
61
63
  */
62
64
  inheritEnv?: "all" | "scrub" | string[];
63
65
  }
@@ -61,6 +61,7 @@ export declare const EXPECTED_LISTBYSCOPE_SUMMARIES: ({
61
61
  mandated: boolean;
62
62
  mandate: string;
63
63
  origin: string;
64
+ boundGrantAbsence: string;
64
65
  spentMicroUsd?: undefined;
65
66
  } | {
66
67
  token: string;
@@ -79,4 +80,5 @@ export declare const EXPECTED_LISTBYSCOPE_SUMMARIES: ({
79
80
  mandated?: undefined;
80
81
  mandate?: undefined;
81
82
  origin?: undefined;
83
+ boundGrantAbsence?: undefined;
82
84
  })[];
@@ -18,6 +18,7 @@ export function createCheckpointFixture(over = {}) {
18
18
  toolName: "Write",
19
19
  args: { path: "/x", content: "y" },
20
20
  boundInputHash: "h0",
21
+ boundGrantAbsence: "not_bindable",
21
22
  batchToolCallIds: ["call-3"],
22
23
  completedCallIds: [],
23
24
  },
@@ -461,6 +462,7 @@ export async function checkpointListByScopeSummaries(store) {
461
462
  origin: "policy",
462
463
  mandated: true,
463
464
  mandate: "tool_marks",
465
+ boundGrantAbsence: "not_bindable",
464
466
  toolCallId: "call-pr",
465
467
  toolName: "open_pr",
466
468
  args: { repo: "x" },
@@ -503,6 +505,7 @@ export const EXPECTED_LISTBYSCOPE_SUMMARIES = [
503
505
  mandated: true,
504
506
  mandate: "tool_marks",
505
507
  origin: "policy",
508
+ boundGrantAbsence: "not_bindable",
506
509
  },
507
510
  {
508
511
  token: "tok-resource",
@@ -38,6 +38,7 @@
38
38
  */
39
39
  import type { AssertAllKeysHandled } from "./ask-origin.js";
40
40
  import type { RunnerDeps } from "./runner-deps.js";
41
+ import { type ReadDenyDeploymentSeat } from "./read-face-seats.js";
41
42
  /**
42
43
  * The closed set of hot-swappable deployment seats.
43
44
  * - `models` — the model catalog generation (the same-name redirect, re-pricing and retirement rules
@@ -59,8 +60,12 @@ import type { RunnerDeps } from "./runner-deps.js";
59
60
  * - `contextSettings` — the per-model context-window overlay ({@link RunnerDeps.contextSettings}); screened
60
61
  * by the same value gate construction applies (`assertContextSettingsValue`). THE ONE SEAT
61
62
  * IN-FLIGHT LEGS RE-READ — see the header: it carries geometry, not route.
63
+ * - `readDenyPatterns` / `readDenyBuiltinTiers` / `readDenyBuiltinExclude` — the deployment's read-deny
64
+ * declaration (additions, built-in tier selection, built-in row removal); screened by the
65
+ * gates a prepare applies ({@link screenReadDenySeat}). A resumed leg compiles against the
66
+ * seats in force at its prepare (the checkpoint keeps additions only, stricter-wins).
62
67
  */
63
- export declare const SWAPPABLE_DEP_SEATS: readonly ["models", "tiers", "readFace", "writeFace", "contextSettings"];
68
+ export declare const SWAPPABLE_DEP_SEATS: readonly ["models", "tiers", "readFace", "writeFace", "contextSettings", "readDenyPatterns", "readDenyBuiltinTiers", "readDenyBuiltinExclude"];
64
69
  export type SwappableDepSeat = (typeof SWAPPABLE_DEP_SEATS)[number];
65
70
  /** The candidate a swap takes: the swappable seats of {@link RunnerDeps}, each optional (presence = instruction). */
66
71
  export type SwappableDeps = Pick<RunnerDeps, SwappableDepSeat>;
@@ -77,6 +82,9 @@ export declare const SWAP_SEAT_NOTICE: {
77
82
  readonly readFace: "config.read_face_swapped";
78
83
  readonly writeFace: "config.write_face_swapped";
79
84
  readonly contextSettings: "config.context_settings_swapped";
85
+ readonly readDenyPatterns: "config.read_deny_swapped";
86
+ readonly readDenyBuiltinTiers: "config.read_deny_swapped";
87
+ readonly readDenyBuiltinExclude: "config.read_deny_swapped";
80
88
  };
81
89
  /** The fence over the notice table: `never` while every seat has a row. */
82
90
  export type SwapNoticeTableCoversEverySeat = AssertAllKeysHandled<Exclude<SwappableDepSeat, keyof typeof SWAP_SEAT_NOTICE>>;
@@ -106,3 +114,9 @@ export interface ScreenedSwappableDeps {
106
114
  * `assertContainmentFaceValue`), so a value the door admits is a value every later prepare admits.
107
115
  */
108
116
  export declare function screenSwappableDeps(next: unknown): ScreenedSwappableDeps;
117
+ /**
118
+ * Announce a swap of the deployment's read-deny declaration, after the write: ONE `config.read_deny_swapped`
119
+ * line however many of its three seats the swap named (the line's contract lives at the notice catalog,
120
+ * `EngineNotice.code`).
121
+ */
122
+ export declare function announceReadDenySwap(onNotice: RunnerDeps["onNotice"], seats: readonly SwappableDepSeat[], inForce: Pick<RunnerDeps, ReadDenyDeploymentSeat>): void;
@@ -1,6 +1,9 @@
1
1
  import { assertContainmentFaceValue } from "../tools/fs/containment-face.js";
2
2
  import { assertContextSettingsValue } from "./context-settings.js";
3
- export const SWAPPABLE_DEP_SEATS = Object.freeze(["models", "tiers", "readFace", "writeFace", "contextSettings"]);
3
+ import { READ_DENY_DEPLOYMENT_SEATS, screenReadDenySeat } from "./read-face-seats.js";
4
+ import { deliverEngineNotice } from "./engine-notice.js";
5
+ import { uuidv7 } from "../internal/harness.js";
6
+ export const SWAPPABLE_DEP_SEATS = Object.freeze(["models", "tiers", "readFace", "writeFace", "contextSettings", "readDenyPatterns", "readDenyBuiltinTiers", "readDenyBuiltinExclude"]);
4
7
  const SWAPPABLE_DEP_SEAT_SET = new Set(SWAPPABLE_DEP_SEATS);
5
8
  export function isSwappableDepSeat(k) {
6
9
  return SWAPPABLE_DEP_SEAT_SET.has(k);
@@ -11,6 +14,9 @@ export const SWAP_SEAT_NOTICE = {
11
14
  readFace: "config.read_face_swapped",
12
15
  writeFace: "config.write_face_swapped",
13
16
  contextSettings: "config.context_settings_swapped",
17
+ readDenyPatterns: "config.read_deny_swapped",
18
+ readDenyBuiltinTiers: "config.read_deny_swapped",
19
+ readDenyBuiltinExclude: "config.read_deny_swapped",
14
20
  };
15
21
  export function screenSwappableDeps(next) {
16
22
  if (next === null || typeof next !== "object" || Array.isArray(next)) {
@@ -54,6 +60,11 @@ export function screenSwappableDeps(next) {
54
60
  case "contextSettings":
55
61
  candidate.contextSettings = assertContextSettingsValue(v, "swapDeps");
56
62
  break;
63
+ case "readDenyPatterns":
64
+ case "readDenyBuiltinTiers":
65
+ case "readDenyBuiltinExclude":
66
+ candidate[seat] = screenReadDenySeat(seat, v);
67
+ break;
57
68
  default: {
58
69
  const _exhaustive = seat;
59
70
  throw new Error(`swapDeps: unhandled seat ${String(_exhaustive)}`);
@@ -62,3 +73,16 @@ export function screenSwappableDeps(next) {
62
73
  }
63
74
  return { seats: present, candidate: candidate };
64
75
  }
76
+ export function announceReadDenySwap(onNotice, seats, inForce) {
77
+ const named = READ_DENY_DEPLOYMENT_SEATS.filter((s) => seats.includes(s));
78
+ if (named.length === 0)
79
+ return;
80
+ const patterns = inForce.readDenyPatterns?.length ?? null;
81
+ const tiers = inForce.readDenyBuiltinTiers !== undefined ? [...inForce.readDenyBuiltinTiers] : null;
82
+ const exclude = inForce.readDenyBuiltinExclude?.length ?? null;
83
+ deliverEngineNotice(onNotice, {
84
+ code: "config.read_deny_swapped",
85
+ message: `deployment read-deny declaration swapped (${named.join(", ")}): ${patterns ?? "no"} addition(s), built-in tiers ${tiers === null ? "(default)" : `[${tiers.join(", ")}]`}, ${exclude ?? "no"} built-in row removal(s); in-flight legs keep the deny face they prepared under, new legs compile against the new declaration`,
86
+ detail: { seats: named, patterns, tiers, exclude, occurrenceId: uuidv7() },
87
+ });
88
+ }
@@ -52,20 +52,22 @@
52
52
  * | any layer | the judgment's budget (one dependency timeout, or a | unanswerable `timed_out` |
53
53
  * | | batch's shared deadline) is spent | |
54
54
  *
55
- * NEVER AN APPROVER. A deployment policy (the caller's, an inherited parent's) is consulted with an
56
- * ALREADY-ABORTED signal and no approver seat: the `ToolPolicy.check` contract is that an aborted signal
57
- * releases a pending approval, and every engine policy factory checks it before reaching a seat (the
58
- * approval factory answers a deny attested `task_aborted`, read here as `approval_seat`; the inherited
59
- * PreToolUse constraint does the same before invoking its hook). A deny that carries no engine settlement
60
- * is a rule's deny and stands. RESIDUALS (stated, not closed): a deployment closure that reaches an
61
- * approver before it reads its signal can still be asked; a deployment wrapper that answers an aborted
62
- * signal with a deny of its own (no settlement) is read as a deny — the attachment face then withholds,
63
- * the notice face stays silent. The engine's own layers (narrowing, skill rows, the two lanes) get the
64
- * run's real signal: they are pure matchers and store reads.
55
+ * NEVER AN APPROVER. Every probe STATES that nobody can be asked about it — `approvalReach: "none"` on the request
56
+ * (`ToolCallRequest.approvalReach`) — and every policy layer is handed a LIVE signal of its own: it ends when the judge stops
57
+ * waiting for that layer (the layer answered, or its share of the judgment's budget is spent) or when the run's signal ends.
58
+ * An abort means only that: the task ended, or the judge no longer waits — never "there is no approver". Every engine
59
+ * policy factory reads the statement before reaching a seat (the approval factory answers a deny attested `no_approver`,
60
+ * read here as `approval_seat`; the inherited PreToolUse constraint does the same before invoking its hook), and the
61
+ * engine's own guards composed into a deployment's policy judge the probe as they judge a call — a lookup bounded by a
62
+ * live deadline answers. A deny that carries no engine settlement is a rule's deny and stands. RESIDUALS (stated, not
63
+ * closed): a deployment closure that ignores the statement and reaches an approver IS asked — the judge waits for it at
64
+ * most the judgment's budget (`timed_out`: the write face refuses, the notice face stays silent), then its signal ends
65
+ * so a closure that honors it releases its approval; a deployment wrapper that answers the statement with a deny of its
66
+ * own (no settlement) is read as a deny — the attachment face then withholds, the notice face stays silent.
65
67
  *
66
- * THE PROBE'S IDENTITY. Every layer sees the probed tool's name (`Read` / `Write`), the probed arguments, and a stable
67
- * synthetic call id (`<face>-visibility-probe` for a read, `<face>-write-probe` for a write; {@link syntheticReadProbeId},
68
- * {@link syntheticWriteProbeId}), so a deployment's audit can tell a probe from a model's call. The write face stamps the
68
+ * THE PROBE'S IDENTITY. Every layer sees the probed tool's name (`Read` / `Write`), the probed arguments, the statement
69
+ * above, and a stable synthetic call id (`<face>-visibility-probe` for a read, `<face>-write-probe` for a write;
70
+ * {@link syntheticReadProbeId}, {@link syntheticWriteProbeId}), so a deployment's audit can tell a probe from a model's call. The write face stamps the
69
71
  * facts the live gate stamps on a write (the roster's `Write` face, the tracked cwd and root, the advertised writable
70
72
  * directories, the data root's exemption): a containment fence and a data-root exemption judge the probe as they judge the call.
71
73
  */
@@ -120,7 +122,7 @@ export interface SyntheticReadLayers {
120
122
  };
121
123
  /** The leg's target-reading stamp: the probe's request carries a reading of its target, as the live gate's does. */
122
124
  stampCallTarget?: CallTargetStamp;
123
- /** The run's signal, for the engine's own layers. */
125
+ /** The run's signal: its end ends the judgment's every wait (each policy layer's own signal ends with it). */
124
126
  signal?: AbortSignal;
125
127
  }
126
128
  /** The arguments a synthetic `Read` carries, and the facts a request stamps. */
@@ -19,7 +19,6 @@ function probeFaceWord(face) {
19
19
  throw new Error(`synthetic probe id: the probe face must be a lowercase word (letters and hyphens), got ${JSON.stringify(face)}`);
20
20
  return face;
21
21
  }
22
- const ABORTED = AbortSignal.abort();
23
22
  function sameArguments(value, probed) {
24
23
  try {
25
24
  return deepJsonEqual(value, probed);
@@ -28,6 +27,22 @@ function sameArguments(value, probed) {
28
27
  return false;
29
28
  }
30
29
  }
30
+ function layerWait(run) {
31
+ const wait = new AbortController();
32
+ const runEnded = () => wait.abort(run?.reason);
33
+ if (run?.aborted === true)
34
+ runEnded();
35
+ else
36
+ run?.addEventListener("abort", runEnded, { once: true });
37
+ return {
38
+ signal: wait.signal,
39
+ end: () => {
40
+ run?.removeEventListener("abort", runEnded);
41
+ if (!wait.signal.aborted)
42
+ wait.abort(new DOMException("the judgment stopped waiting for this layer", "TimeoutError"));
43
+ },
44
+ };
45
+ }
31
46
  export function createSyntheticReadJudge(layers) {
32
47
  const judge = createProbeJudge(layers, "read");
33
48
  return async (call, face, budget) => {
@@ -61,9 +76,10 @@ export function createSyntheticWriteJudge(layers, facts) {
61
76
  }
62
77
  function createProbeJudge(layers, kind) {
63
78
  const everyAskHeld = kind === "write";
64
- return async (probe, budget) => {
79
+ return async (unstated, budget) => {
65
80
  const deadlineAt = budget?.deadlineAt ?? Date.now() + ORG_ADJUDICATION_TIMEOUT_MS;
66
81
  const left = () => Math.max(0, Math.min(ORG_ADJUDICATION_TIMEOUT_MS, deadlineAt - Date.now()));
82
+ const probe = { ...unstated, approvalReach: "none" };
67
83
  const probedArgs = structuredClone(probe.args);
68
84
  const stamped = layers.stampCallTarget?.(probe, layers.signal);
69
85
  const req = stamped !== undefined ? await stamped : probe;
@@ -84,7 +100,7 @@ function createProbeJudge(layers, kind) {
84
100
  cannot(layer, "timed_out", "the judgment's wait budget was spent before this layer was asked");
85
101
  return true;
86
102
  };
87
- const policyLayer = async (layer, policy, signal, ancestorOwned = false) => {
103
+ const policyLayer = async (layer, policy, ancestorOwned = false) => {
88
104
  if (spent(layer))
89
105
  return undefined;
90
106
  const waitMs = left();
@@ -92,18 +108,22 @@ function createProbeJudge(layers, kind) {
92
108
  let thrown = undefined;
93
109
  const TIMEOUT = Symbol("timeout");
94
110
  let d;
111
+ const wait = layerWait(layers.signal);
95
112
  try {
96
113
  d = await settleOrgVerdictWithin(Promise.resolve()
97
- .then(() => policy.check(req, signal))
114
+ .then(() => policy.check(req, wait.signal))
98
115
  .catch((err) => {
99
116
  thrown = { err };
100
117
  return TIMEOUT;
101
- }), TIMEOUT, { ...(layers.signal !== undefined ? { signal: layers.signal } : {}), timeoutMs: waitMs, onFallback: () => void (timedOut = true) });
118
+ }), TIMEOUT, { signal: wait.signal, timeoutMs: waitMs, onFallback: () => void (timedOut = true) });
102
119
  }
103
120
  catch (err) {
104
121
  cannot(layer, "threw", describeThrown(err));
105
122
  return undefined;
106
123
  }
124
+ finally {
125
+ wait.end();
126
+ }
107
127
  if (thrown !== undefined) {
108
128
  cannot(layer, "threw", describeThrown(thrown.err));
109
129
  return undefined;
@@ -144,19 +164,19 @@ function createProbeJudge(layers, kind) {
144
164
  cannot("skill_rows", "threw", describeThrown(err));
145
165
  }
146
166
  if (layers.denyNarrowingPolicy !== undefined) {
147
- const v = await policyLayer("narrowing", layers.denyNarrowingPolicy, layers.signal);
167
+ const v = await policyLayer("narrowing", layers.denyNarrowingPolicy);
148
168
  if (v !== undefined)
149
169
  return v;
150
170
  }
151
171
  if (layers.callerPolicy !== undefined) {
152
- const v = await policyLayer("caller_policy", layers.callerPolicy, ABORTED, (layers.inheritedPolicies ?? []).includes(layers.callerPolicy));
172
+ const v = await policyLayer("caller_policy", layers.callerPolicy, (layers.inheritedPolicies ?? []).includes(layers.callerPolicy));
153
173
  if (v !== undefined)
154
174
  return v;
155
175
  }
156
176
  for (const parent of layers.inheritedPolicies ?? []) {
157
177
  if (parent === layers.callerPolicy)
158
178
  continue;
159
- const v = await policyLayer("inherited_constraint", parent, ABORTED, true);
179
+ const v = await policyLayer("inherited_constraint", parent, true);
160
180
  if (v !== undefined)
161
181
  return v;
162
182
  }
@@ -1,7 +1,11 @@
1
1
  import type { MailboxAppendMessage, MailboxStore } from "./mailbox-store.js";
2
+ /** The TERMINAL words of {@link TaskNotificationStatus}: the one table every "is this frame a completion?"
3
+ * reading asks, exported as a value so a consumer outside the package reads these words rather than its own list. */
4
+ export declare const TASK_NOTIFICATION_TERMINAL_STATUSES: readonly ["completed", "failed", "killed", "cancelled"];
5
+ export type TaskNotificationTerminalStatus = (typeof TASK_NOTIFICATION_TERMINAL_STATUSES)[number];
2
6
  /** design/135 G2: `"event"` is the ONE non-terminal member — a Monitor batch notification (new stdout
3
7
  * lines from a still-running watch). Everything else is a terminal completion notification. */
4
- export type TaskNotificationStatus = "completed" | "failed" | "killed" | "cancelled" | "event";
8
+ export type TaskNotificationStatus = TaskNotificationTerminalStatus | "event";
5
9
  export type SystemInjectionPriority = "now" | "next" | "later";
6
10
  /**
7
11
  * The outcome an injection entry settles with — what the session did with the frame:
@@ -84,8 +88,8 @@ export interface TaskNotificationPayload {
84
88
  * `inlineUntrusted` + attribute-escaping, never as authority. Absent on every internal lane. */
85
89
  source?: string;
86
90
  /** design/134 §3.3: only on `status:"killed"` — who stopped it. MIRROR of task-registry `StopSource`
87
- * (the single source; inlined here to avoid an import cycle — task-registry imports this module;
88
- * third mirror: types.ts `BackgroundChildEvent.stoppedBy` — keep all three in sync). OPEN ENUM:
91
+ * (the single source; inlined here to avoid an import cycle — task-registry imports this module);
92
+ * `BackgroundChildEvent.stoppedBy` is typed by this member and projected from it. OPEN ENUM:
89
93
  * consumers must tolerate unknown values (a future "timeout" etc. is not breaking) and fall back
90
94
  * to default copy. */
91
95
  stoppedBy?: "user" | "parent" | "system" | (string & {});
@@ -507,6 +511,12 @@ export declare function terminalFrameFacets(facts: {
507
511
  warning?: string | undefined;
508
512
  compaction?: TaskNotificationPayload["compaction"] | undefined;
509
513
  }): Pick<TaskNotificationPayload, "spawnName" | "handback" | "compaction">;
514
+ /**
515
+ * A delegated child's report as its completion frame carries it — the one value the parent's notification and the fleet
516
+ * terminal projected from that frame both read: redacted BEFORE the cut, with the pass the task row's copy uses (a token
517
+ * across the cut is never half-shown), then cut with an honest total. Absent in ⇒ absent out.
518
+ */
519
+ export declare function notifyResultField(result: string | undefined): string | undefined;
510
520
  /** The codec version this engine writes into an `engine_frame` record and the highest it reads. */
511
521
  export declare const ENGINE_FRAME_CODEC_VERSION = 1;
512
522
  /** The engine's own lanes — the only frames the durable park may encode (an `external` event never is). */
@@ -1,7 +1,9 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { escapeEnvelopeTag, inlineUntrusted, sanitizeUntrustedText } from "./untrusted-text.js";
3
+ import { egressText } from "./untrusted-egress.js";
3
4
  import { PEER_MESSAGE_NOTICE } from "../agents/peer-admission.js";
4
5
  import { buildCrossSessionEnvelope, CROSS_SESSION_MESSAGE_NOTICE, neutralizePeerBody } from "../agents/cross-session-envelope.js";
6
+ export const TASK_NOTIFICATION_TERMINAL_STATUSES = Object.freeze(["completed", "failed", "killed", "cancelled"]);
5
7
  export const NOTIFICATION_INJECTION_OUTCOMES = Object.freeze(["queued", "parked", "parked_durably", "dropped_duplicate"]);
6
8
  export function isNotificationInjectionOutcome(value) {
7
9
  return typeof value === "string" && NOTIFICATION_INJECTION_OUTCOMES.includes(value);
@@ -175,6 +177,15 @@ export function terminalFrameFacets(facts) {
175
177
  ...(facts.compaction !== undefined ? { compaction: { passes: facts.compaction.passes, breakerOpen: facts.compaction.breakerOpen, ...(facts.compaction.kind !== undefined ? { kind: facts.compaction.kind } : {}) } } : {}),
176
178
  };
177
179
  }
180
+ const COMPLETION_REPORT_MAX = 2_000;
181
+ export function notifyResultField(result) {
182
+ if (!result)
183
+ return undefined;
184
+ const report = egressText("same-principal", result);
185
+ return report.length > COMPLETION_REPORT_MAX
186
+ ? `${report.slice(0, COMPLETION_REPORT_MAX)}\n[result truncated: ${report.length} chars total — ask the agent for it via SendMessage, or read it with TaskOutput where mounted]`
187
+ : report;
188
+ }
178
189
  export const ENGINE_FRAME_CODEC_VERSION = 1;
179
190
  export const ENGINE_FRAME_TASK_TYPES = Object.freeze(["background_agent", "monitor", "workflow"]);
180
191
  const SHELL_LESS_CARRIERS = ["agentMessage", "crossSessionMessage", "crossSessionNotice", "inboxNotice", "_sema_provenance"];
@@ -257,7 +268,7 @@ export function engineFrameRecordOf(payload, priority, box) {
257
268
  };
258
269
  }
259
270
  const MAX_TOMBSTONES = 5_000;
260
- const TERMINAL_STATUSES = new Set(["completed", "failed", "killed", "cancelled"]);
271
+ const TERMINAL_STATUSES = new Set(TASK_NOTIFICATION_TERMINAL_STATUSES);
261
272
  export function isDelegatedAgentTerminal(n) {
262
273
  return n.task_type === "background_agent" && TERMINAL_STATUSES.has(n.status);
263
274
  }
@@ -478,10 +478,10 @@ export interface TaskSpec {
478
478
  *
479
479
  * Entries are read like {@link excludeTools}: a wire name or an alias it declares, `mcp__srv` / `mcp__srv__*` for every tool
480
480
  * of a server (including tools it lists later), a glob over the tool segment. The engine resolves the list against its OWN
481
- * roster at prepare — a host passes the names it was given and never computes a complement. A name no tool answers to, a
482
- * protocol-wide `mcp__*`, or an entry that is not a string is REFUSED at prepare (`config.tool_face_control`). The same
483
- * compiled selector is applied on every later roster change (an MCP refresh or re-dial, the deferred-search lane): an exact name
484
- * admits no new tool. Deferral is reached through ToolSearch, so a list that leaves it out defers nothing.
481
+ * roster at prepare — a host passes the names it was given. A spelling no tool name can have, `mcp__*`, or an entry that is not
482
+ * a string is REFUSED at prepare (`config.tool_face_control`); a name no tool answers to is kept and told once (`onError`, phase
483
+ * `"config"`). The same compiled selector is applied on every later roster change (an MCP refresh or re-dial, the
484
+ * deferred-search lane). Deferral is reached through ToolSearch, so a list that leaves it out defers nothing.
485
485
  *
486
486
  * A tool a leg relies on (a delegated child's ReportBlocked, a teammate's SendMessage, a goal iteration's DeclareDone, an
487
487
  * observer's report tool — the catalog's protocol column) is not an allowlist's to name; a denylist that reaches one on such a
@@ -494,8 +494,8 @@ export interface TaskSpec {
494
494
  * ships the schema bytes every request — measured at zero token savings). The DENY half of the run's tool selector, read
495
495
  * exactly as a sub-agent's denied tools are: a wire name or an alias it declares; `Tool(content)` names the whole `Tool`;
496
496
  * `mcp__srv` / `mcp__srv__*` / a glob over the tool segment for a server's tools, `mcp__*` for every MCP tool (including tools
497
- * a server lists later). A name no tool answers to, `"*"`, or an entry that is not a string is REFUSED at prepare
498
- * (`config.tool_face_control`) — a misspelt exclusion guards nothing. A deny wins over {@link allowTools}.
497
+ * a server lists later). A spelling no tool name can have, `"*"`, or an entry that is not a string is REFUSED at prepare; a name
498
+ * no tool answers to is kept (it hides a tool of that name mounted later) and told once. A deny wins over {@link allowTools}.
499
499
  * ⚠️ A list that reaches a tool this kind of leg relies on (see {@link allowTools}) is refused; excluding a host-requested
500
500
  * structural tool (the structured-output tool) can still strand the task — the caller owns that risk.
501
501
  */
@@ -610,15 +610,14 @@ export interface TaskSpec {
610
610
  * real model ref passed through — core carries no model-word vocabulary). When set (non-empty),
611
611
  * `prepareTask` REBUILDS the mounted delegation tool (via {@link ToolSpec.withAgents}) so its roster
612
612
  * is the UNION of the boot roster and these definitions — a same-name per-task definition WINS over
613
- * the boot one. Validation contract (revision of the posture, dual-leg verified against
614
- * upstream): an `allowTools` entry naming a tool that does not exist is ITEM-FILTERED at spawn (the
615
- * agent stays listed and delegable; the only host disclosure is a prepare-time HEURISTIC advisory
616
- * via `RunnerDeps.onError` phase `"config"` for entries unknown to the parent's assembled roster —
617
- * zero model-facing warning, the verified upstream shape); a `denyTools` entry is read as the
618
- * reference reads it (`Tool(content)` hides the whole `Tool`, with a host notice) and judged once, here and for the
619
- * delegation tool's own lists alike: a spelling that names no tool, or a stale one (another case of a mounted tool, a
620
- * retired name), is REJECTED at task start (`config.agent.unknown_tool`), any other name no tool carries is told to the
621
- * host and hides nothing; and
613
+ * the boot one. Validation contract (the reference's shape — an agent's tool list filters by name,
614
+ * and a name nothing answers never fails the run): both lists are judged once, here and for the
615
+ * delegation tool's own lists alike; a `denyTools` entry is read as the reference reads it
616
+ * (`Tool(content)` hides the whole `Tool`, with a host notice); an entry whose spelling no tool name
617
+ * can have is REJECTED at task start (`config.agent.unknown_tool`); an entry naming a tool that does
618
+ * not exist (a server this deployment does not declare, a stale or misspelt name) is kept as written
619
+ * — it denies, or admits, a tool of that name if one is mounted later — and told to the host once
620
+ * via `RunnerDeps.onError` phase `"config"` (zero model-facing warning); and
622
621
  * providing this field with NO delegation tool mounted is rejected too
623
622
  * (`config.agents.no_delegation_tool`) — never a silent no-op. POLICY-NEUTRAL: a
624
623
  * roster listing only; the 1.294 tighten-only parent-policy inheritance is applied at spawn time,
@@ -126,10 +126,6 @@ export declare const NAME_READINGS: {
126
126
  readonly kind: "any";
127
127
  readonly file: string;
128
128
  };
129
- readonly "policy.sessionRules": {
130
- readonly kind: "any";
131
- readonly file: string;
132
- };
133
129
  readonly "policy.sessionRules.effect": {
134
130
  readonly kind: "wire";
135
131
  readonly file: string;
@@ -33,7 +33,6 @@ export const NAME_READINGS = {
33
33
  "policy.nameLists": any(POLICY),
34
34
  "policy.shellToolsList": any(POLICY),
35
35
  "policy.projectionReplay": any(POLICY),
36
- "policy.sessionRules": any("src/core/runner/session-rule-policy.ts"),
37
36
  "policy.sessionRules.effect": wire("src/core/runner/session-rule-policy.ts"),
38
37
  "allowEntry.names": any("src/core/allow-entry.ts"),
39
38
  "skillGrant.binding": wire("src/core/skill-grant.ts"),