@sema-agent/core 7.52.0 → 7.53.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 (191) hide show
  1. package/CHANGELOG.md +194 -0
  2. package/README.md +3 -1
  3. package/dist/agents/cross-session-envelope.d.ts +8 -0
  4. package/dist/agents/cross-session-envelope.js +6 -0
  5. package/dist/agents/cross-session-judge.d.ts +21 -1
  6. package/dist/agents/cross-session-judge.js +8 -0
  7. package/dist/agents/cross-session-ref.d.ts +5 -0
  8. package/dist/agents/cross-session-ref.js +1 -0
  9. package/dist/agents/org-delivery-gate.d.ts +122 -0
  10. package/dist/agents/org-delivery-gate.js +69 -0
  11. package/dist/agents/peer-held-queue.d.ts +9 -4
  12. package/dist/agents/peer-held-queue.js +6 -5
  13. package/dist/agents/peer-session-drain.d.ts +35 -5
  14. package/dist/agents/peer-session-drain.js +271 -180
  15. package/dist/agents/read-inbox-tool.d.ts +7 -0
  16. package/dist/agents/read-inbox-tool.js +5 -1
  17. package/dist/agents/subagent.d.ts +3 -0
  18. package/dist/agents/subagent.js +99 -72
  19. package/dist/agents/tool-filter.d.ts +18 -0
  20. package/dist/agents/tool-filter.js +6 -0
  21. package/dist/brain/anthropic.d.ts +6 -1
  22. package/dist/brain/anthropic.js +4 -4
  23. package/dist/brain/model-presets.js +2 -1
  24. package/dist/brain/route-adjudicator.d.ts +6 -0
  25. package/dist/brain/routing.d.ts +24 -1
  26. package/dist/brain/routing.js +54 -19
  27. package/dist/brain/timeout.js +5 -4
  28. package/dist/core/a2a.d.ts +5 -2
  29. package/dist/core/a2a.js +30 -13
  30. package/dist/core/allow-entry.d.ts +2 -1
  31. package/dist/core/allow-entry.js +2 -2
  32. package/dist/core/auto-compaction.d.ts +57 -0
  33. package/dist/core/auto-compaction.js +28 -2
  34. package/dist/core/checkpoint-store.d.ts +29 -20
  35. package/dist/core/checkpoint-store.js +3 -3
  36. package/dist/core/compliance.js +3 -1
  37. package/dist/core/effective-path-target.d.ts +25 -6
  38. package/dist/core/effective-path-target.js +24 -2
  39. package/dist/core/engine-notice.d.ts +13 -2
  40. package/dist/core/gate-fold.js +3 -2
  41. package/dist/core/gate-lanes.js +11 -13
  42. package/dist/core/gate-outcome.d.ts +11 -6
  43. package/dist/core/governance-codes.d.ts +3 -1
  44. package/dist/core/governance-codes.js +3 -0
  45. package/dist/core/hands-band-screen.d.ts +7 -0
  46. package/dist/core/hands-band-screen.js +32 -0
  47. package/dist/core/hooks.d.ts +7 -5
  48. package/dist/core/hooks.js +5 -3
  49. package/dist/core/locked-config.d.ts +84 -32
  50. package/dist/core/locked-config.js +128 -42
  51. package/dist/core/mcp-probe.js +1 -1
  52. package/dist/core/mcp-server-spec.d.ts +88 -10
  53. package/dist/core/mcp-server-spec.js +183 -33
  54. package/dist/core/mcp.d.ts +21 -14
  55. package/dist/core/mcp.js +55 -51
  56. package/dist/core/memory-engine/engine.d.ts +14 -19
  57. package/dist/core/memory-engine/engine.js +7 -5
  58. package/dist/core/mode-policy.d.ts +3 -18
  59. package/dist/core/mode-policy.js +2 -16
  60. package/dist/core/mods/kernel/dispatch.js +13 -15
  61. package/dist/core/mods/kernel/events.d.ts +9 -6
  62. package/dist/core/mods/kernel/events.js +2 -2
  63. package/dist/core/mods/kernel/receive.d.ts +39 -10
  64. package/dist/core/mods/kernel/receive.js +119 -54
  65. package/dist/core/mods/kernel/site-rules.d.ts +80 -15
  66. package/dist/core/mods/kernel/site-rules.js +232 -40
  67. package/dist/core/mods/stations.d.ts +0 -6
  68. package/dist/core/mods/stations.js +0 -7
  69. package/dist/core/mounted-mcp.d.ts +57 -0
  70. package/dist/core/mounted-mcp.js +71 -2
  71. package/dist/core/own-property-receipt.d.ts +13 -0
  72. package/dist/core/own-property-receipt.js +3 -1
  73. package/dist/core/parent-spec-seat.d.ts +2 -2
  74. package/dist/core/permission-rule-model.d.ts +58 -9
  75. package/dist/core/permission-rule-model.js +81 -7
  76. package/dist/core/permission-rule-org.d.ts +163 -33
  77. package/dist/core/permission-rule-org.js +205 -71
  78. package/dist/core/permission-rules.d.ts +7 -1
  79. package/dist/core/permission-rules.js +37 -8
  80. package/dist/core/retention.js +7 -2
  81. package/dist/core/retired-keys.d.ts +8 -2
  82. package/dist/core/retired-keys.js +12 -1
  83. package/dist/core/roles.js +60 -10
  84. package/dist/core/rule-lane-reads.js +1 -1
  85. package/dist/core/runner/contracts.d.ts +1 -0
  86. package/dist/core/runner/gate-exit.js +4 -3
  87. package/dist/core/runner/mcp-redial.d.ts +52 -10
  88. package/dist/core/runner/mcp-redial.js +38 -9
  89. package/dist/core/runner/permission-rule-lanes.d.ts +5 -2
  90. package/dist/core/runner/permission-rule-lanes.js +22 -10
  91. package/dist/core/runner/person-path-rows.d.ts +3 -1
  92. package/dist/core/runner/person-path-rows.js +2 -2
  93. package/dist/core/runner/prepare-config-doors.d.ts +48 -2
  94. package/dist/core/runner/prepare-config-doors.js +161 -145
  95. package/dist/core/runner/prepare-defer-classify.js +4 -7
  96. package/dist/core/runner/prepare-delegation-surface.d.ts +2 -2
  97. package/dist/core/runner/prepare-delegation-surface.js +7 -5
  98. package/dist/core/runner/prepare-hands-readface.d.ts +1 -1
  99. package/dist/core/runner/prepare-hands-readface.js +2 -1
  100. package/dist/core/runner/prepare-inherited-gate.js +2 -2
  101. package/dist/core/runner/prepare-listings.d.ts +1 -1
  102. package/dist/core/runner/prepare-listings.js +4 -2
  103. package/dist/core/runner/prepare-memory-engine-session.d.ts +0 -6
  104. package/dist/core/runner/prepare-memory-engine-session.js +19 -6
  105. package/dist/core/runner/prepare-memory.d.ts +3 -2
  106. package/dist/core/runner/prepare-memory.js +6 -2
  107. package/dist/core/runner/prepare-offload-wrappers.d.ts +4 -4
  108. package/dist/core/runner/prepare-offload-wrappers.js +2 -1
  109. package/dist/core/runner/prepare-policy-chain.d.ts +1 -1
  110. package/dist/core/runner/prepare-policy-chain.js +12 -12
  111. package/dist/core/runner/prepare-project-context.d.ts +1 -1
  112. package/dist/core/runner/prepare-project-context.js +6 -4
  113. package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
  114. package/dist/core/runner/prepare-prompt-assembly.js +20 -16
  115. package/dist/core/runner/prepare-prompt-inputs.d.ts +12 -6
  116. package/dist/core/runner/prepare-prompt-inputs.js +6 -3
  117. package/dist/core/runner/prepare-protocol-tools.d.ts +7 -5
  118. package/dist/core/runner/prepare-protocol-tools.js +19 -12
  119. package/dist/core/runner/prepare-question-face.d.ts +4 -2
  120. package/dist/core/runner/prepare-question-face.js +4 -3
  121. package/dist/core/runner/prepare-task.js +10 -10
  122. package/dist/core/runner/prepare-wiring-manifest.d.ts +8 -3
  123. package/dist/core/runner/prepare-wiring-manifest.js +8 -5
  124. package/dist/core/runner/resume-admission.d.ts +4 -1
  125. package/dist/core/runner/resume-admission.js +7 -5
  126. package/dist/core/runner/resume-internals-and-config.d.ts +3 -3
  127. package/dist/core/runner/resume-internals-and-config.js +3 -8
  128. package/dist/core/runner/retired-runner-deps.d.ts +5 -6
  129. package/dist/core/runner/retired-runner-deps.js +1 -4
  130. package/dist/core/runner/run-leg.js +42 -12
  131. package/dist/core/runner/run-recovery-lanes.js +5 -3
  132. package/dist/core/runner/run-terminal-adoption.js +6 -5
  133. package/dist/core/runner/run-turn-boundary.js +9 -3
  134. package/dist/core/runner/runtask.js +1 -1
  135. package/dist/core/runner/skills-seat.d.ts +15 -0
  136. package/dist/core/runner/skills-seat.js +57 -0
  137. package/dist/core/runner/sub-agent-deny-lists.d.ts +13 -11
  138. package/dist/core/runner/sub-agent-deny-lists.js +7 -5
  139. package/dist/core/runner/synthetic-tools.d.ts +5 -2
  140. package/dist/core/runner/synthetic-tools.js +2 -12
  141. package/dist/core/runner-deps.d.ts +29 -14
  142. package/dist/core/session-policy-store.d.ts +19 -0
  143. package/dist/core/session-policy-store.js +22 -2
  144. package/dist/core/store-contracts/mailbox-store-contract.js +13 -0
  145. package/dist/core/task-event.d.ts +38 -2
  146. package/dist/core/task-notification.js +2 -2
  147. package/dist/core/task-spec.d.ts +1 -1
  148. package/dist/core/tool-policy.d.ts +3 -0
  149. package/dist/core/tool-policy.js +10 -4
  150. package/dist/core/types.d.ts +2 -2
  151. package/dist/core/types.js +1 -1
  152. package/dist/core/untrusted-text.d.ts +16 -0
  153. package/dist/core/untrusted-text.js +7 -2
  154. package/dist/core/user-attachment.d.ts +13 -2
  155. package/dist/core/user-attachment.js +47 -41
  156. package/dist/core/wiring-manifest.d.ts +4 -2
  157. package/dist/engine/harness/agent-harness.js +2 -1
  158. package/dist/engine/harness/types.d.ts +3 -0
  159. package/dist/engine/loop/agent-loop.d.ts +12 -1
  160. package/dist/engine/loop/agent-loop.js +17 -5
  161. package/dist/engine/loop/types.d.ts +7 -1
  162. package/dist/index.d.ts +15 -9
  163. package/dist/index.js +12 -6
  164. package/dist/internal/harness.d.ts +1 -0
  165. package/dist/internal/harness.js +1 -0
  166. package/dist/orchestration/goal.d.ts +24 -1
  167. package/dist/orchestration/goal.js +3 -2
  168. package/dist/prompt-assembly/artifact-store.d.ts +44 -9
  169. package/dist/prompt-assembly/artifact-store.js +38 -12
  170. package/dist/prompt-assembly/artifact.d.ts +12 -11
  171. package/dist/prompt-assembly/artifact.js +52 -21
  172. package/dist/prompt-assembly/assemble.d.ts +2 -0
  173. package/dist/prompt-assembly/assemble.js +2 -1
  174. package/dist/prompt-assembly/catalog/index.js +2 -1
  175. package/dist/prompt-assembly/catalog/memory.d.ts +13 -0
  176. package/dist/prompt-assembly/catalog/memory.js +6 -0
  177. package/dist/prompt-assembly/catalog/schema.d.ts +16 -2
  178. package/dist/prompt-assembly/composer.d.ts +2 -0
  179. package/dist/prompt-assembly/composer.js +1 -1
  180. package/dist/prompt-assembly/overlay.d.ts +21 -11
  181. package/dist/prompt-assembly/overlay.js +55 -17
  182. package/dist/prompt-assembly/select.d.ts +21 -6
  183. package/dist/prompt-assembly/select.js +39 -2
  184. package/dist/prompts/default.d.ts +0 -11
  185. package/dist/prompts/default.js +0 -3
  186. package/dist/stores/file/permission-rule-store.d.ts +1 -1
  187. package/dist/stores/file/permission-rule-store.js +2 -1
  188. package/dist/tools/artifact/artifact-tool.js +4 -3
  189. package/package.json +1 -1
  190. package/test/export-surface.snapshot.json +65 -5
  191. package/wire-changes/core@7.53.0.json +1946 -0
@@ -1,4 +1,4 @@
1
- import { DEFAULT_COMPACTION_INSTRUCTIONS, isCompactionManualCancel, maybeCompact } from "../auto-compaction.js";
1
+ import { compactionPassReport, DEFAULT_COMPACTION_INSTRUCTIONS, isCompactionManualCancel, maybeCompact } from "../auto-compaction.js";
2
2
  import { eventDefaultOn } from "../../prompt-assembly/event-registry.js";
3
3
  import { appendInterruptionMarker, reconcileInterruptedSession } from "../session-reconcile.js";
4
4
  import { emitTrace } from "../trace.js";
@@ -120,7 +120,8 @@ export async function runTerminalAdoption(input) {
120
120
  minTokens: rs.counters.compactionFloor,
121
121
  brain: compactionBrain,
122
122
  windowSafety: windowSafetyOptions(prepared.harness.getModel()),
123
- onCompactionOutcome: (outcome, reason) => queue.push({ type: "compaction_outcome", outcome, trigger: "auto", reason, ...ident() }),
123
+ passReport: compactionPassReport(queue, ident),
124
+ onCompactionOutcome: (outcome, reason, end) => queue.push({ type: "compaction_outcome", outcome, trigger: "auto", reason, ...end, ...ident() }),
124
125
  }, runner, sessions);
125
126
  return await next({ threw, comp, answerSettled: settled, owedUserInput });
126
127
  }
@@ -153,7 +154,7 @@ async function finish(spec, prepared, opts, runner, sessions) {
153
154
  ...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
154
155
  ...seamCCompactionOptions(runner, prepared),
155
156
  ...compactionRestateOptions(prepared),
156
- ...compactionHookOptions(runner, prepared, "auto", opts.callerStop.signal),
157
+ ...compactionHookOptions(runner, prepared, "auto", opts.callerStop.signal), onStarted: opts.passReport.onStarted,
157
158
  });
158
159
  recordCompactionReuse(runner, prepared, finishComp);
159
160
  if (finishComp.unevaluableWindow) {
@@ -170,7 +171,7 @@ async function finish(spec, prepared, opts, runner, sessions) {
170
171
  reason: "cancelled",
171
172
  ts: Date.now(),
172
173
  }));
173
- opts?.onCompactionOutcome?.("mooted", "cancelled");
174
+ opts?.onCompactionOutcome?.("mooted", "cancelled", opts.passReport.end);
174
175
  }
175
176
  else {
176
177
  const failMsg = thrownText(err);
@@ -183,7 +184,7 @@ async function finish(spec, prepared, opts, runner, sessions) {
183
184
  reason: failReason,
184
185
  ts: Date.now(),
185
186
  }));
186
- opts?.onCompactionOutcome?.("failed", failReason);
187
+ opts?.onCompactionOutcome?.("failed", failReason, opts.passReport.end);
187
188
  runner.deps.onError?.(err, { phase: "compaction", sessionId: prepared.sessionId });
188
189
  }
189
190
  }
@@ -1,7 +1,7 @@
1
1
  import { compactionKindOf } from "../task-notification.js";
2
2
  import { mintSystemReminder } from "../reminder-mint.js";
3
3
  import { eventDefaultOn } from "../../prompt-assembly/event-registry.js";
4
- import { DEFAULT_COMPACTION_INSTRUCTIONS, isCompactionManualCancel, maybeCompact, mintCompactedEvent, nextTrimForceBackoff, recordCompactionAndCheckRapidRefill } from "../auto-compaction.js";
4
+ import { compactionPassReport, DEFAULT_COMPACTION_INSTRUCTIONS, isCompactionManualCancel, maybeCompact, mintCompactedEvent, nextTrimForceBackoff, recordCompactionAndCheckRapidRefill } from "../auto-compaction.js";
5
5
  import { emitTrace } from "../trace.js";
6
6
  import { redactThenCut } from "../../agents/subagent-steps.js";
7
7
  import { ATTACHMENT_BYTE_CAP, CHANGED_FILES_MAX, TOTAL_TOKENS_REMINDER_DEFAULT_MODE, attachmentEnvelopeTags, clipToBytes, collectDateChange, collectDueAttachments, collectInstructionsChange, commitAgentListing, commitInstructionsChange, commitSkillsListing, isIntactSource, rebaseCadenceWindows, renderAgentListingFrame, renderMcpDroppedTools, renderMcpInstructionsDelta, selectMcpDroppedBatch, renderSkillsListingFrame, renderToolsDelta } from "./turn-attachments.js";
@@ -558,6 +558,7 @@ export function createTurnBoundary(input) {
558
558
  const passTrigger = trimPressure ? "forced" : forceManual ? "manual" : "auto";
559
559
  let manualOutcome = "failed";
560
560
  const manualInstructions = claimedManual?.instructions;
561
+ const passReport = compactionPassReport(queue, ident);
561
562
  try {
562
563
  const comp = await maybeCompact({
563
564
  session: prepared.session,
@@ -587,6 +588,7 @@ export function createTurnBoundary(input) {
587
588
  ...compactionRestateOptions(prepared),
588
589
  ...windowSafetyOptions(event.model),
589
590
  ...compactionHookOptions(runner, prepared, passTrigger, compactionPassSignal),
591
+ onStarted: passReport.onStarted,
590
592
  });
591
593
  if (comp.blocked) {
592
594
  emitTrace(rs.telemetry.tracer, () => ({
@@ -670,7 +672,7 @@ export function createTurnBoundary(input) {
670
672
  catch (err) {
671
673
  if (isCompactionManualCancel(err)) {
672
674
  emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.mooted", version: 1, taskId: rs.telemetry.taskId, reason: "cancelled", ts: Date.now() }));
673
- queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: passTrigger, reason: "cancelled", ...ident() });
675
+ queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: passTrigger, reason: "cancelled", ...passReport.end, ...ident() });
674
676
  manualOutcome = "mooted";
675
677
  }
676
678
  else if (!prepared.abortController.signal.aborted) {
@@ -686,11 +688,15 @@ export function createTurnBoundary(input) {
686
688
  reason: failReason,
687
689
  ts: Date.now(),
688
690
  }));
689
- queue.push({ type: "compaction_outcome", outcome: "failed", trigger: passTrigger, reason: failReason, ...ident() });
691
+ queue.push({ type: "compaction_outcome", outcome: "failed", trigger: passTrigger, reason: failReason, ...passReport.end, ...ident() });
690
692
  manualOutcome = "failed";
691
693
  }
692
694
  else {
693
695
  manualOutcome = "mooted";
696
+ if (passReport.pass !== undefined) {
697
+ emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.mooted", version: 1, taskId: rs.telemetry.taskId, reason: "task_ending", ts: Date.now() }));
698
+ queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: passTrigger, reason: "task_ending", ...passReport.end, ...ident() });
699
+ }
694
700
  }
695
701
  }
696
702
  if (forceManual)
@@ -481,7 +481,7 @@ export class Runner {
481
481
  outcome,
482
482
  taskConfig,
483
483
  internals,
484
- runner: this.depsSeat,
484
+ runner: this.depsSeat, sessions: this.sessions,
485
485
  parentConstraintRegistry: this.parentConstraintRegistry,
486
486
  next: (admitted) => this.resumeLadder(token, outcome, admitted),
487
487
  });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * THE reading of `TaskSpec.skills` — the one place the caller's skill objects are read. The config doors call it before any
3
+ * resource (a resume before its claim) and hand its copy on as the leg's `skills`; the disclosure block and the `Skill` tool read
4
+ * that copy through `normalizeSkills` (synthetic-tools.ts), its sorted view.
5
+ */
6
+ import type { SkillSpec } from "../agent-definition.js";
7
+ /**
8
+ * Total, and once: a value that is not a list, an entry that is not an object, a `name` / `description` / `content` that is not a
9
+ * string, or `files` that is not a list of `{ path, content }` strings refuses by name (`config.invalid_argument`, `detail.field` =
10
+ * the member's path); `baseDir` by its own reader ({@link skillBaseDirOf}, `config.skill_base_dir_invalid`). Every entry is
11
+ * screened, a later duplicate of a name included. Each field is read ONCE, BY NAME (an inherited or non-enumerable list counts;
12
+ * a rest copy would drop it). What it returns is plain data in declaration order, first of a name: every member and attachment
13
+ * copied, the two tables read ({@link readSkillTable}: a non-list is one entry that reads as nothing).
14
+ */
15
+ export declare function readSkillsSeat(skills: unknown): SkillSpec[];
@@ -0,0 +1,57 @@
1
+ import { readSkillTable } from "../skill-grant.js";
2
+ import { assertNoRetiredKeys, RETIRED_TASK_SPEC_KEYS } from "../retired-keys.js";
3
+ import { skillBaseDirOf } from "./skill-base-dir.js";
4
+ const SKILLS_SEAT = "TaskSpec.skills";
5
+ const kindOf = (v) => (v === null ? "null" : Array.isArray(v) ? "an array" : typeof v);
6
+ function skillsSeatRefusal(field, problem) {
7
+ const e = new Error(`${field} ${problem} — an entry the engine cannot read is refused rather than dropped.`);
8
+ e.code = "config.invalid_argument";
9
+ e.detail = { field };
10
+ return e;
11
+ }
12
+ function readSkillFiles(files, at) {
13
+ if (files === undefined)
14
+ return undefined;
15
+ if (!Array.isArray(files))
16
+ throw skillsSeatRefusal(at, `must be a list of { path, content } attachments (got ${kindOf(files)})`);
17
+ const out = [];
18
+ for (const [j, f] of files.entries()) {
19
+ if (typeof f !== "object" || f === null || Array.isArray(f))
20
+ throw skillsSeatRefusal(`${at}[${j}]`, `must be an attachment object { path, content } (got ${kindOf(f)})`);
21
+ const { path, content } = f;
22
+ if (typeof path !== "string")
23
+ throw skillsSeatRefusal(`${at}[${j}].path`, `must be a string (got ${kindOf(path)})`);
24
+ if (typeof content !== "string")
25
+ throw skillsSeatRefusal(`${at}[${j}].content`, `must be a string (got ${kindOf(content)})`);
26
+ out.push({ path, content });
27
+ }
28
+ return out;
29
+ }
30
+ export function readSkillsSeat(skills) {
31
+ if (skills === undefined)
32
+ return [];
33
+ if (!Array.isArray(skills))
34
+ throw skillsSeatRefusal(SKILLS_SEAT, `must be a list (got ${kindOf(skills)})`);
35
+ assertNoRetiredKeys({ skills }, RETIRED_TASK_SPEC_KEYS, "TaskSpec");
36
+ const seen = new Set();
37
+ const out = [];
38
+ for (const [i, raw] of skills.entries()) {
39
+ const at = `${SKILLS_SEAT}[${i}]`;
40
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw))
41
+ throw skillsSeatRefusal(at, `must be an object { name, description, content } (got ${kindOf(raw)})`);
42
+ const { name, description, content, allowedTools, disallowedTools, files: rawFiles, baseDir, ...rest } = raw;
43
+ void rest;
44
+ for (const [member, value] of [["name", name], ["description", description], ["content", content]]) {
45
+ if (typeof value !== "string")
46
+ throw skillsSeatRefusal(`${at}.${member}`, `must be a string (got ${kindOf(value)})`);
47
+ }
48
+ const files = readSkillFiles(rawFiles, `${at}.files`);
49
+ skillBaseDirOf(baseDir);
50
+ if (seen.has(name))
51
+ continue;
52
+ seen.add(name);
53
+ const [allowed, denied] = [readSkillTable(allowedTools), readSkillTable(disallowedTools)];
54
+ out.push({ name, description, content, ...(allowed !== undefined ? { allowedTools: allowed } : {}), ...(denied !== undefined ? { disallowedTools: denied } : {}), ...(files !== undefined ? { files } : {}), ...(baseDir !== undefined ? { baseDir } : {}) });
55
+ }
56
+ return out;
57
+ }
@@ -34,8 +34,9 @@
34
34
  * another spelling of a tool of the universe) — synchronous, like the phase that calls it.
35
35
  */
36
36
  import type { AgentTool } from "../../internal/harness.js";
37
+ import { valveLeaves } from "../../agents/tool-filter.js";
37
38
  import { type ProtocolId } from "../protocol-table.js";
38
- import type { ToolLegKind, ToolSelector, ToolSelectorLayer } from "../tool-face.js";
39
+ import type { ToolLegKind, ToolSelectorLayer } from "../tool-face.js";
39
40
  import { type ConstraintChainEntry } from "../tool-policy.js";
40
41
  import type { TaskSpec } from "../types.js";
41
42
  import type { CheckpointErrorReason } from "../checkpoint-store.js";
@@ -65,14 +66,15 @@ export declare function renamedDelegationMountSentence(...rosters: unknown[]): s
65
66
  */
66
67
  export declare function refuseRenamedDelegationMounts(...rosters: unknown[]): void;
67
68
  /**
68
- * THE reading of what the delegation tools a run mounts hand their children, read after the run's own selector: only a delegation
69
- * tool the selector leaves (`selector.admits`) counts — one it takes out hands nobody anything, and neither its pool names nor an
70
- * unlistable pool of it belong to any universe. The tools themselves, every pool tool's name and alias (a declared `extraToolNames`
71
- * roster included), and whether every one of those pools can be listed: a delegation tool whose factory injects tools per spawn with no
72
- * declared roster has no listable pool (the static `tools` it was given included), so a name outside the listed ones may be one of its
73
- * own. This is the one place the pool side of both universes is defined; the list judgment below and the policy name audit ask it.
69
+ * THE reading of what the delegation tools a run mounts hand their children, read after the run's own tool valve: only a delegation
70
+ * tool the valve leaves (`leaves`, the valve's one reading — `valveLeaves`) counts — one it takes out hands nobody anything, and neither
71
+ * its pool names nor an unlistable pool of it belong to any universe. The tools themselves, every pool tool's name and alias (a declared
72
+ * `extraToolNames` roster included), and whether every one of those pools can be listed: a delegation tool whose factory injects tools
73
+ * per spawn with no declared roster has no listable pool (the static `tools` it was given included), so a name outside the listed ones
74
+ * may be one of its own. This is the one place the pool side of both universes is defined; the list judgment below and the policy name
75
+ * audit ask it.
74
76
  */
75
- export declare function delegationPoolReading(callerTools: NonNullable<TaskSpec["tools"]>, selector: Pick<ToolSelector, "admits">): DelegationPoolReading;
77
+ export declare function delegationPoolReading(callerTools: NonNullable<TaskSpec["tools"]>, leaves: ReturnType<typeof valveLeaves>): DelegationPoolReading;
76
78
  /** The arms of this module's `config.tool_face_control` refusal that a resume carries before the claim as `detail.reason` of a `checkpoint.config_refused` — the same words
77
79
  * ({@link CheckpointErrorReason}): a list the row recorded that is not a list / has a member that is not a string / has a spelling no tool name can have / is not in the
78
80
  * spelling a row records, a record that is not one, a renamed delegation mount, an occupied table of declared names. */
@@ -123,8 +125,8 @@ export declare function boundOwnToolLists(record: unknown, own: {
123
125
  readonly exclude: readonly string[] | undefined;
124
126
  }): RunInternals["ownToolLists"];
125
127
  export interface RunToolListsInput {
126
- /** borrowed-readonly — the REBOUND spec: `agents` (the task's definitions) and `tools` (the caller's tools and the mounted delegation tools). */
127
- spec: Pick<TaskSpec, "agents" | "tools">;
128
+ /** borrowed-readonly — the REBOUND spec: `agents` (the task's definitions), `tools` (the caller's tools and the mounted delegation tools) and `excludeAllTools` (the valve's whole-face switch, which takes the delegation tools out too). */
129
+ spec: Pick<TaskSpec, "agents" | "tools" | "excludeAllTools">;
128
130
  /** borrowed-readonly — the assembled mount array BEFORE the selector's valve (names + aliases = the roster half of the universe). */
129
131
  tools: ReadonlyArray<AgentTool>;
130
132
  /** borrowed-readonly — the peers the task declares, per protocol of the protocol table (its MCP servers, its A2A peers): a namespaced
@@ -170,5 +172,5 @@ export declare function judgeRunToolLists(input: RunToolListsInput): void;
170
172
  * `detail.reason: "near_miss"`) rather than kept as a deny that hides nothing. A name no tool answers to stays kept (a tool of that
171
173
  * name may be mounted later), unannounced: the host wrote no spec this leg could name the list of.
172
174
  */
173
- export declare function judgeHostToolCeiling(layers: readonly ToolSelectorLayer[], agent: Pick<TaskSpec, "tools" | "allowTools" | "excludeTools">, above?: readonly ToolSelectorLayer[]): void;
175
+ export declare function judgeHostToolCeiling(layers: readonly ToolSelectorLayer[], agent: Pick<TaskSpec, "tools" | "allowTools" | "excludeTools" | "excludeAllTools">, above?: readonly ToolSelectorLayer[]): void;
174
176
  export {};
@@ -1,4 +1,4 @@
1
- import { compileToolSelector, deniedProtocolTools, delegationListWhere, isCanonicalToolName, isToolSelectorLayer, listReaches, notAListSentence, toolListsDigest, notAStringSentence, readAllowedToolList, readDeniedEntry, readDeniedToolList } from "../../agents/tool-filter.js";
1
+ import { compileToolSelector, valveLeaves, deniedProtocolTools, delegationListWhere, isCanonicalToolName, isToolSelectorLayer, listReaches, notAListSentence, toolListsDigest, notAStringSentence, readAllowedToolList, readDeniedEntry, readDeniedToolList } from "../../agents/tool-filter.js";
2
2
  import { readToolNameList, valueTypeLabel } from "../tool-name-list.js";
3
3
  import { compileAllowList, inertEntryPhrase, renderListEntry, rootFacingToolNames } from "../allow-entry.js";
4
4
  import { inlineUntrusted } from "../untrusted-text.js";
@@ -37,8 +37,8 @@ export function refuseRenamedDelegationMounts(...rosters) {
37
37
  function poolOf(tools) {
38
38
  return { tools, names: mountedToolNames([...tools.map((t) => ({ name: t.name })), ...tools.flatMap((t) => t.agentToolPool ?? [])]), complete: tools.every((t) => t.agentToolPool !== undefined) };
39
39
  }
40
- export function delegationPoolReading(callerTools, selector) {
41
- return poolOf(callerTools.filter((t) => t.agentToolFaces !== undefined && selector.admits(t)));
40
+ export function delegationPoolReading(callerTools, leaves) {
41
+ return poolOf(callerTools.filter((t) => t.agentToolFaces !== undefined && leaves(t)));
42
42
  }
43
43
  function peersOf(known, declaredPeers) {
44
44
  const peers = new Set(PROTOCOL_TABLE.flatMap((ns) => (declaredPeers[ns.id] ?? []).map((p) => mintNamespacePrefix(ns, p.name))));
@@ -275,7 +275,8 @@ export function judgeRunToolLists(input) {
275
275
  const by = [...deniedBy(true, ownWhere ?? "this run's own excludeTools"), ...deniedBy(false, "an inherited list (the spawning run's excludeTools, or one it inherits)")];
276
276
  throw refused(`This kind of leg (${input.legs.join(", ")}) relies on ${names.join(", ")}, but ${by.join(" and ")} — the leg cannot work without ${one ? "it" : "them"}. Remove the entry from the list that names it.`, "config.tool_face_control", "protocol_denied");
277
277
  }
278
- const pool = delegationPoolReading(spec.tools ?? [], compileToolSelector(input.layers, input.legs));
278
+ const selector = compileToolSelector(input.layers, input.legs);
279
+ const pool = delegationPoolReading(spec.tools ?? [], valveLeaves(spec, selector));
279
280
  const mounted = mountedToolNames([...tools, ...(spec.tools ?? [])]);
280
281
  if (!input.judgedUpstream) {
281
282
  const root = rootFacingUniverse(input.ownBy !== undefined ? new Set([...mounted, input.ownBy.toolName]) : mounted, pool, input.declaredPeers);
@@ -310,7 +311,8 @@ export function judgeHostToolCeiling(layers, agent, above = []) {
310
311
  layers.forEach((layer, i) => readSelectorLayer(layer, where(i), () => where(i), false));
311
312
  const callerTools = agent.tools ?? [];
312
313
  const own = { ...(agent.allowTools != null ? { allow: agent.allowTools } : {}), deny: agent.excludeTools ?? [] };
313
- const pool = delegationPoolReading(callerTools, compileToolSelector([...above, ...layers, own], ["delegated"]));
314
+ const selector = compileToolSelector([...above, ...layers, own], ["delegated"]);
315
+ const pool = delegationPoolReading(callerTools, valveLeaves(agent, selector));
314
316
  const universe = rootFacingUniverse(mountedToolNames(callerTools), pool, { mcp: undefined, a2a: undefined });
315
317
  const unannounced = () => undefined;
316
318
  layers.forEach((layer, i) => {
@@ -110,8 +110,11 @@ export interface SkillListingEntry {
110
110
  * and the tool, so what the prompt promises is exactly what the tool serves. Each field is READ ONCE, BY NAME, and
111
111
  * both consumers see the copy: the gate screens this list while the invoke substitutes from it, so a `get baseDir()`
112
112
  * answering those two reads differently passed a screen that measured a value the model never received — and a rest
113
- * copy would silently drop an INHERITED or non-enumerable list. The two tables are read by {@link readSkillTable}. */
114
- export declare function normalizeSkills(skills: SkillLike[] | undefined): SkillLike[];
113
+ * copy would silently drop an INHERITED or non-enumerable list. The two tables are read by {@link readSkillTable}.
114
+ *
115
+ * The sorted view of {@link readSkillsSeat} (skills-seat.ts), which holds the seat's one reading — total over what a caller can
116
+ * hand it, and once. */
117
+ export declare function normalizeSkills(skills: unknown): SkillLike[];
115
118
  /** [c209-C] The skills listing's lead line — single-sourced so the run loop's durable-resume
116
119
  * transcript probe (MED-3② skills arm, runtask) matches EXACTLY what the initial frame carries
117
120
  * (probing the bare `<skills>` fence would be more collision-prone in model-quoted text). */
@@ -5,6 +5,7 @@ import { SKILL_LISTING_DEFAULT_BUDGET, SKILL_LISTING_DESC_MAX_CHARS_DEFAULT } fr
5
5
  import { readSkillTable, skillNamedBy } from "../skill-grant.js";
6
6
  import { toolFace } from "../tool-catalog-entries.js";
7
7
  import { substituteSkillRoot, substituteSkillRootBounded } from "./skill-base-dir.js";
8
+ import { readSkillsSeat } from "./skills-seat.js";
8
9
  import { foldListingWithin } from "./listing-fold.js";
9
10
  export const OUTPUT_TOOL_NAME = "StructuredOutput";
10
11
  export const REPORT_BLOCKED_TOOL_NAME = "ReportBlocked";
@@ -109,18 +110,7 @@ function skillFilesSuffix(files) {
109
110
  return ` [files: ${shown.join(", ")}${more > 0 ? `, +${more} more` : ""}]`;
110
111
  }
111
112
  export function normalizeSkills(skills) {
112
- const seen = new Set();
113
- const out = [];
114
- for (const s of skills ?? []) {
115
- if (!seen.has(s.name)) {
116
- seen.add(s.name);
117
- const { name, description, content, allowedTools, disallowedTools, files, baseDir, ...rest } = s;
118
- void rest;
119
- const [allowed, denied] = [readSkillTable(allowedTools), readSkillTable(disallowedTools)];
120
- out.push({ name, description, content, ...(allowed !== undefined ? { allowedTools: allowed } : {}), ...(denied !== undefined ? { disallowedTools: denied } : {}), ...(files !== undefined ? { files } : {}), ...(baseDir !== undefined ? { baseDir } : {}) });
121
- }
122
- }
123
- return out.toSorted((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
113
+ return readSkillsSeat(skills).toSorted((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
124
114
  }
125
115
  export const SKILLS_LISTING_PROBE_HEADER = "The following skills are available for this task.";
126
116
  export function skillListingLine(s, descMaxChars = SKILL_LISTING_DESC_MAX_CHARS_DEFAULT) {
@@ -159,10 +159,12 @@ export interface RunnerDeps {
159
159
  */
160
160
  rosterStore?: import("../agents/roster-store.js").RosterStore;
161
161
  /**
162
- * The center prompt-artifact source (artifact v2 only — INTEGRATION-CORE §12.11). `current()` supplies the verified
162
+ * The center prompt-artifact source (artifact v3 only — a v2 body is refused `schema`, INTEGRATION-CORE §12.11).
163
+ * `current()` supplies the verified
163
164
  * candidate a top-level session adopts at session start and at a compaction; `get(digest)` resolves a PINNED center
164
165
  * layer on resume (a miss is fail-loud `prompt.snapshot_unavailable` — core never recomposes a hybrid from newer
165
- * content); `sourceState()` is the tri-state every adoption decision keys on. What a source hands a run is re-checked
166
+ * content; a copy the engine refuses is told apart from a missing one, in the failure's sentence and on `onError`);
167
+ * `sourceState()` is the tri-state every adoption decision keys on. What a source hands a run is re-checked
166
168
  * before it is adopted. Core bundles `CenterPromptSource` (+ memory/file artifact & source-state stores); a server
167
169
  * feeds it published artifacts via `applySnapshot`. Omitted = no center layer.
168
170
  */
@@ -961,6 +963,19 @@ export interface RunnerDeps {
961
963
  * everywhere (mode parity, the CC default). Inert without {@link peerDirectory}.
962
964
  */
963
965
  crossSessionInbound?: import("../agents/cross-session-judge.js").CrossSessionInboundSettingLayers | (() => import("../agents/cross-session-judge.js").CrossSessionInboundSettingLayers);
966
+ /**
967
+ * WHO consumes this deployment's principal inbox (`principal.inbox`, where the
968
+ * organization's delivery gate appends cross-principal letters; the mailbox contract's fourth chain).
969
+ * Absent = no consumer, no lane (byte for byte as before); a deployment that mounts a gate DECLARES
970
+ * `{ assistant: "<agent handle>" }` (the scope's assistant is the only consumer; no terminal leg touches the
971
+ * box). `"terminals"` (each terminal drains the box as a second lane) is refused until held decisions are
972
+ * shared across terminals (`detail.reason: "terminals_unsupported"`). Letters are judged on the principal's
973
+ * `crossSessionInbound` layers (managed, user), held when unset. ANY other value fails the prepare of a leg
974
+ * that drains a session box with `config.principal_inbox_consumer_invalid` (`detail.reason: "malformed"`).
975
+ */
976
+ principalInboxConsumer?: "terminals" | {
977
+ assistant: string;
978
+ };
964
979
  /**
965
980
  * design/385 §4.4 (slice 4) — this deployment's `dialogExpiry` (CC settings key, verbatim vocabulary
966
981
  * `"60s" | "5m" | "10m" | "never"`, default `"5m"`): how long a HELD cross-session message whose cause
@@ -1286,18 +1301,18 @@ export interface RunnerDeps {
1286
1301
  * summaries) is invisible without this and lets a session grow until it tops the window.
1287
1302
  * - `"prompt-cache"` — a prefix-cache hazard on the wire: a detected cache break (design/31), or a
1288
1303
  * mis-declared/mismatched `promptCacheFamily`. The prefix cache will miss. Log/alert on both.
1289
- * - `"prompt-constitution"` — a prompt overlay did not take effect as written: a later source (or a later
1290
- * declaration in the same source) superseded it on the same target, its target was not in this run's
1291
- * prompt, or a mods handler's `prompt.compose` answer was skipped (it rewrote another's overlay, or wrote
1292
- * what the band table does not grant its seat). Each overlay is named once per run.
1293
- * - `"degraded"` — the task kept running with a capability quietly reduced: a question auto-answered
1294
- * with no human present, a skipped env git snapshot, a lost checkpoint-put confirmation, an
1295
- * unroutable descendant notification, and similar best-effort arms. The run proceeds; the reduced
1296
- * arm is what is being disclosed (`classification` names it).
1297
- * - `"config"` — a deployment-wiring problem detected while preparing or tearing down a task: tool
1298
- * mount conflicts, refused/ignored knob values, gate-off notices, store/env teardown legs. The
1299
- * broadest class by call-site count — most misconfigurations announce here rather than failing
1300
- * the task.
1304
+ * - `"prompt-constitution"` — a prompt overlay did not take effect as written: a later source (or a later declaration in the same source) superseded
1305
+ * it on the same target, its target was not in this run's prompt, or a mods handler's `prompt.compose` answer was skipped (it rewrote another's
1306
+ * overlay, or wrote what the band table does not grant its seat); each overlay is named once per run. The center prompt too: a pinned session's
1307
+ * stored artifact refused or unreadable (it fails `prompt.snapshot_unavailable`), a fresh session's candidate that does not verify, and a
1308
+ * `CenterPromptSource` boot restore with no usable copy (`sessionId: ""`, `classification: "center-source-restore"`) — that last line only when the
1309
+ * host passes this same sink to `CenterPromptSource` as its own `onError` (the source restores at boot, apart from any Runner; given none, it is silent).
1310
+ * - `"degraded"` — the task kept running with a capability quietly reduced: a question auto-answered with no human
1311
+ * present, a skipped env git snapshot, a lost checkpoint-put confirmation, an unroutable descendant notification, and
1312
+ * similar best-effort arms. The run proceeds; the reduced arm is what is being disclosed (`classification` names it).
1313
+ * - `"config"` — a deployment-wiring problem detected while preparing or tearing down a task: tool mount conflicts,
1314
+ * refused/ignored knob values, gate-off notices, store/env teardown legs. The broadest class by call-site count — most
1315
+ * misconfigurations announce here rather than failing the task.
1301
1316
  * - `"memory"` — a post-task memory-consolidation pass failed or skipped a malformed reconcile
1302
1317
  * decision (design/41). Best-effort: the notes the model saved are kept; the task still succeeds.
1303
1318
  * - `"mcp"` — an MCP server failed to connect / list its tools and was skipped (fail-open, design/29);
@@ -27,6 +27,7 @@
27
27
  * chain); the real isolation boundary is the executionEnv, never these rules.
28
28
  */
29
29
  import { compileAllowList, type AllowEntryInert, type AllowEntryInertDetail, type AllowEntryInertReason } from "./allow-entry.js";
30
+ import type { EngineNotice } from "./engine-notice.js";
30
31
  /** The per-session rules. All optional; an absent bucket imposes no constraint of that kind. */
31
32
  export interface SessionPermissionRules {
32
33
  /** If set, ONLY the calls these entries admit are allowed — a narrowing allowlist: a call no entry admits is refused.
@@ -244,6 +245,24 @@ export declare function compileSessionRuleBucket(rules: SessionRulesInGrammar, f
244
245
  * to make a parenthesized one the rule it now spells, or (`*`, which is no allow rule in any grammar) to leave it out.
245
246
  */
246
247
  export declare function earlierGrammarAdvisory(sessionId: string, entries: readonly string[], field: SessionRuleEntryField, grammar: number): string;
248
+ /** The reader's word for one entry an earlier grammar reads as nothing: `no_rule` — this engine's rule grammar reads it as no
249
+ * rule; `named_no_tool` — a grammar-0 allow entry the bare-name comparison of its day read as naming no tool. */
250
+ export type EarlierGrammarReader = "no_rule" | "named_no_tool";
251
+ /**
252
+ * The notice for the entries of one bucket a stored row's earlier grammar reads as nothing (`config.session_rules_earlier_grammar`,
253
+ * audience `user` — the session's rules are what the person's calls are judged by): the message is {@link earlierGrammarAdvisory}
254
+ * verbatim, and `detail` carries the same facts for a machine — `bucket` (the field), `storedGrammar` (the row's), `entries` (every
255
+ * entry, each as `noticeRuleText` hands rule text to a notice, with its reader's word), `rewrite` (the way out: an operator write of
256
+ * a new value without the stored row's rev, which stamps `stampsGrammar`), `rulesSessionId` (whose rules — an ancestor's on a
257
+ * delegated child's inherited layer) and `sessionId` (the run that read them).
258
+ */
259
+ export declare function earlierGrammarNotice(input: {
260
+ rulesSessionId: string;
261
+ sessionId: string;
262
+ entries: readonly string[];
263
+ field: SessionRuleEntryField;
264
+ grammar: number;
265
+ }): EngineNotice;
247
266
  /** The clause a run-time refusal carries when the allowlist it judged by holds entries its earlier grammar reads as nothing
248
267
  * ({@link compileSessionRuleBucket}; `grammar` = the row's): the grammar the rules are stored in, then which entries name no tool
249
268
  * there (grammar 0: `*` by its spelling, the parenthesized ones counted) and how many this engine's grammar reads as no rule —
@@ -4,6 +4,7 @@ import { NAMESPACED_NAME_SHAPES } from "./protocol-table.js";
4
4
  import { callerToolNameProblem } from "./caller-tool-name.js";
5
5
  import { compileAllowList, compiledEntryName, inertEntryPhrase, readRuleEntry, renderListEntry, rootFacingToolNames } from "./allow-entry.js";
6
6
  import { valueTypeLabel } from "./tool-name-list.js";
7
+ import { noticeRuleText } from "./permission-rule-model.js";
7
8
  const SESSION_RULE_BUCKETS = { toolAllow: "allow", toolDeny: "deny", allowDirs: "dirs" };
8
9
  const BUCKETS = Object.keys(SESSION_RULE_BUCKETS);
9
10
  const ENTRY_FIELDS = BUCKETS.filter((f) => SESSION_RULE_BUCKETS[f] !== "dirs");
@@ -162,11 +163,30 @@ function shownEntries(entries, render) {
162
163
  }
163
164
  const REWRITE_THROUGH = `through this engine (an operator write of a new value without the stored row's rev; the write stamps grammar ${SESSION_RULES_GRAMMAR})`;
164
165
  export function earlierGrammarAdvisory(sessionId, entries, field, grammar) {
165
- const noRule = entries.filter((e) => readsAsNoRule(e, field));
166
- const namedNoTool = entries.filter((e) => !readsAsNoRule(e, field));
166
+ const read = earlierGrammarReadings(entries, field);
167
+ const noRule = read.filter((r) => r.reader === "no_rule").map((r) => r.entry);
168
+ const namedNoTool = read.filter((r) => r.reader === "named_no_tool").map((r) => r.entry);
167
169
  const sentences = [...(namedNoTool.length > 0 ? [namedNoToolInGrammar0(namedNoTool)] : []), ...(noRule.length > 0 ? [noRuleInThisGrammar(noRule, field, grammar)] : [])];
168
170
  return `session permission rules of session ${JSON.stringify(sessionId)}: ${sentences.join(" ")}`;
169
171
  }
172
+ function earlierGrammarReadings(entries, field) {
173
+ return entries.map((entry) => ({ entry, reader: readsAsNoRule(entry, field) ? "no_rule" : "named_no_tool" }));
174
+ }
175
+ export function earlierGrammarNotice(input) {
176
+ const { rulesSessionId, sessionId, entries, field, grammar } = input;
177
+ return {
178
+ code: "config.session_rules_earlier_grammar",
179
+ message: earlierGrammarAdvisory(rulesSessionId, entries, field, grammar),
180
+ detail: {
181
+ bucket: field,
182
+ storedGrammar: grammar,
183
+ entries: earlierGrammarReadings(entries, field).map((r) => ({ entry: noticeRuleText(r.entry), reader: r.reader })),
184
+ rewrite: { by: "operator_write_without_rev", stampsGrammar: SESSION_RULES_GRAMMAR },
185
+ rulesSessionId,
186
+ sessionId,
187
+ },
188
+ };
189
+ }
170
190
  function namedNoToolInGrammar0(entries) {
171
191
  const one = entries.length === 1;
172
192
  const wildcard = entries.some(readsAsWildcard);
@@ -1,6 +1,8 @@
1
1
  import { strict as assert } from "node:assert";
2
2
  import { MAILBOX_INVALID_PEER_META_CODE, MAILBOX_TOMBSTONED_RECIPIENT_CODE } from "../mailbox-store.js";
3
3
  import { beginContract } from "./contract-harness.js";
4
+ import { PRINCIPAL_INBOX_HANDLE } from "../../agents/cross-session-ref.js";
5
+ import { mintCrossPrincipalRecord } from "../../agents/org-delivery-gate.js";
4
6
  export const MAILBOX_CONTRACT_SCOPE = "default";
5
7
  const msg = (content, sentAt = 1000) => ({ content, sentAt });
6
8
  async function withStores(mk, fn) {
@@ -259,6 +261,17 @@ export async function mailboxBundledOnlyContract(mk, runAssertion) {
259
261
  await s.drop("tenant-b", "shared-handle");
260
262
  assert.equal(await s.peekCount("tenant-b", "shared-handle"), 0);
261
263
  });
264
+ run("第四条消费链(principal 收件箱):投递门铸的信落收件人 scope 的 principal.inbox,两只信任锚逐字节回读,发信方 scope 零残留", async (make) => {
265
+ const s = make();
266
+ const letter = mintCrossPrincipalRecord({ from: { scope: "principal-a", name: "alice" }, body: { content: "letter" }, receiptId: "gr-1", sentAt: 10_000, consumer: { kind: "terminals" } });
267
+ assert.equal(await s.append("principal-b", PRINCIPAL_INBOX_HANDLE, letter), 1);
268
+ assert.equal(await s.peekCount("principal-a", PRINCIPAL_INBOX_HANDLE), 0, "发信方 scope 的同名箱不得出现这封信");
269
+ const lease = await s.claimLease("principal-b", PRINCIPAL_INBOX_HANDLE, "drain:r1", 60_000, 10_000);
270
+ assert.deepEqual(lease.messages.map((m) => m.peerMeta), [{ kind: "peer_message", fromScope: "principal-a", gateReceiptId: "gr-1" }]);
271
+ assert.equal(lease.messages[0].from, "alice");
272
+ assert.deepEqual(await s.ack("principal-b", PRINCIPAL_INBOX_HANDLE, "drain:r1", lease.maxSeq), { acked: true });
273
+ assert.equal(await s.peekCount("principal-b", PRINCIPAL_INBOX_HANDLE), 0);
274
+ });
262
275
  await settle();
263
276
  }
264
277
  export async function mailboxCrossProcessContract(hooks) {
@@ -677,6 +677,10 @@ export type TaskEvent = ({
677
677
  * pre-compaction context size. CC parity = `SDKCompactBoundary{trigger, pre_tokens, preserved_segment}`. */
678
678
  type: "compacted";
679
679
  trigger: "auto" | "manual" | "forced";
680
+ /** The pass this frame CONCLUDES — the value the pass's `compaction_started` frame carried. A landing is
681
+ * always preceded by its start, so the engine sets it on every `compacted` frame; it is optional in the
682
+ * type only so a literal a host builds by hand stays valid. */
683
+ pass?: string;
680
684
  tokensBefore: number;
681
685
  /** How much context this compaction FREED, as the engine measured it: one structural sum over
682
686
  * the pre-messages minus the same sum over the post-messages, inside the pass. **The only
@@ -763,8 +767,9 @@ export type TaskEvent = ({
763
767
  * - `"mooted"` — the pass did not run to its end: a manual request no boundary could serve
764
768
  * (`trigger:"manual"`, `reason:"task_ending"`), or a request withdrawn / a pass cut in flight by its
765
769
  * caller — a `compact({ signal })` withdrawal, `halt()`, `interrupt()`, `destroy()` or the task's own
766
- * `signal` (`reason:"cancelled"`), on any trigger: `"auto"`, `"manual"` or `"forced"`. Read
767
- * `"mooted"` with its `trigger` — it is not a manual-only outcome.
770
+ * `signal` (`reason:"cancelled"`), on any trigger: `"auto"`, `"manual"` or `"forced"`, or a turn-boundary
771
+ * pass in flight that the task's own end cut — a limit, a timeout (`reason:"task_ending"`, with the
772
+ * `pass` the start announced). Read `"mooted"` with its `trigger` — it is not a manual-only outcome.
768
773
  */
769
774
  type: "compaction_outcome";
770
775
  outcome: Exclude<CompactOutcome, "compacted"> | "suppressed";
@@ -772,6 +777,37 @@ export type TaskEvent = ({
772
777
  trigger: "auto" | "manual" | "forced";
773
778
  /** Optional human-readable detail (mirrors the trace frame's reason where one exists). */
774
779
  reason?: string;
780
+ /** The pass this frame CONCLUDES — the value its `compaction_started` frame carried. Present exactly when a
781
+ * start was announced for the pass this frame ends, which makes it `failed` or `mooted` (a pass that ran its
782
+ * summary call and did not land, or was cut while it ran). Absent on every outcome of a pass that never
783
+ * started — `blocked`, `suppressed`, `disabled`, `noop`, a `mooted` request no boundary served — and on the
784
+ * rapid-refill `disabled` frame, which reports a state the landing before it caused, not that pass's end. */
785
+ pass?: string;
786
+ } & TaskEventIdentity) | ({
787
+ /**
788
+ * A compaction pass is under way: the engine has committed to it and is about to produce its summary.
789
+ * Until now compaction was announced only when it ended (`compacted`, or `compaction_outcome` for a pass
790
+ * that did not land), so a consumer watching a summary call wait out a provider's retries had nothing to
791
+ * show for the minutes in between.
792
+ *
793
+ * Minted at the one place a pass commits — after the trigger gate, a valid cut point and the
794
+ * `preCompact` hook (a blocking hook ends the pass before it starts), before the summary provider is
795
+ * consulted or the model is called — by every lane that compacts: the turn boundary (`"auto"`, `"manual"`,
796
+ * and `"forced"` under trim pressure), the prompt-too-long recovery and guard-chain arm B (`"forced"`), and
797
+ * the end-of-task summary (`"auto"`). Retries inside the pass's model call never mint a second one.
798
+ *
799
+ * Pairing: exactly one frame concludes each started pass, carrying the same `pass` — `compacted` when it
800
+ * lands, `compaction_outcome{failed | mooted}` when it does not. A pass that never reaches this point
801
+ * (`blocked`, `suppressed`, `disabled`, `noop`) announces nothing here and its outcome carries no `pass`.
802
+ * `pass` is unique across runs, so a consumer multiplexing several tasks on one stream pairs on `pass` alone.
803
+ */
804
+ type: "compaction_started";
805
+ /** Same three-state vocabulary (and the same value) the pass hands the `preCompact` hook and its concluding frame. */
806
+ trigger: "auto" | "manual" | "forced";
807
+ /** The pass's identity: an opaque engine-minted id (uuidv7), carried by the frame that concludes the pass. */
808
+ pass: string;
809
+ /** The engine clock (epoch ms) at the moment the pass committed. */
810
+ startedAt: number;
775
811
  } & TaskEventIdentity) | ({
776
812
  /**
777
813
  * TB telemetry B3: the engine injected a SYSTEM steering message (a deadline nudge
@@ -2,7 +2,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { escapeEnvelopeTag, inlineUntrusted, sanitizeUntrustedText } from "./untrusted-text.js";
3
3
  import { egressText } from "./untrusted-egress.js";
4
4
  import { PEER_MESSAGE_NOTICE } from "../agents/peer-admission.js";
5
- import { buildCrossSessionEnvelope, CROSS_SESSION_MESSAGE_NOTICE, neutralizePeerBody } from "../agents/cross-session-envelope.js";
5
+ import { buildCrossSessionEnvelope, CROSS_PRINCIPAL_MESSAGE_NOTICE, CROSS_SESSION_MESSAGE_NOTICE, neutralizePeerBody } from "../agents/cross-session-envelope.js";
6
6
  export const TASK_NOTIFICATION_TERMINAL_STATUSES = Object.freeze(["completed", "failed", "killed", "cancelled"]);
7
7
  export const NOTIFICATION_INJECTION_OUTCOMES = Object.freeze(["queued", "parked", "parked_durably", "dropped_duplicate"]);
8
8
  export function isNotificationInjectionOutcome(value) {
@@ -88,7 +88,7 @@ export function renderAgentMessageFrame(frame) {
88
88
  }
89
89
  export function renderCrossSessionMessageFrame(frame) {
90
90
  const { body, ...fields } = frame;
91
- return `${buildCrossSessionEnvelope(fields, body)}\n\n${CROSS_SESSION_MESSAGE_NOTICE}`;
91
+ return `${buildCrossSessionEnvelope(fields, body)}\n\n${fields.fromScope !== undefined ? CROSS_PRINCIPAL_MESSAGE_NOTICE : CROSS_SESSION_MESSAGE_NOTICE}`;
92
92
  }
93
93
  export function renderCrossSessionNoticeLine(notice) {
94
94
  return sanitizeUntrustedText(notice.text);
@@ -307,7 +307,7 @@ export interface TaskSpec {
307
307
  * `appendSystemPrompt` stay. Absent ⇒ standard. Rides the parent spec seat to every engine-built child (a clamp-table
308
308
  * row: no child authors the standard form back). A value outside the closed set is refused at prepare. */
309
309
  promptForm?: "minimal";
310
- /** The deployment's center binding (a scenario name): selects the adopted center artifact's `scope.binding` overlays; rides the parent spec seat; out of grammar (non-blank, ≤ 128) ⇒ `config.prompt_selector_invalid`. */
310
+ /** The deployment's binding (a scenario name): a prompt selector value, read by every overlay's `when.binding` (center, deploy and task alike); rides the parent spec seat; out of grammar (non-blank, ≤ 128 Unicode code points — `PROMPT_BINDING_MAX_CHARS`, counted `[...s].length`, not UTF-16 units) ⇒ `config.prompt_selector_invalid`. */
311
311
  promptBinding?: string;
312
312
  /**
313
313
  * APPEND a caller block to the assembled system prompt, keeping the engine's base + harness/safety blocks
@@ -1879,6 +1879,9 @@ export declare function ownArgs<T>(v: T, root?: string): {
1879
1879
  reason: string;
1880
1880
  cause?: unknown;
1881
1881
  };
1882
+ /** A call's argument reading (the loop's: the tool's preparation, the schema's coercions) whose every value is engine-owned
1883
+ * like a pass's args ({@link ownArgs}) — the gate's face seat and the resumed call's belt seat read through this one. */
1884
+ export declare function ownedReading(read: (args: unknown) => unknown): (args: unknown) => unknown;
1882
1885
  /**
1883
1886
  * A label for a value that was THROWN at us, safe to put in a fail-closed refusal. Never coerces: `String(x)`
1884
1887
  * throws on a null-prototype object or a untrusted `toString`/`Symbol.toPrimitive`, and the thrower here is