@sema-agent/core 7.33.8 → 7.35.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 (228) hide show
  1. package/CHANGELOG.md +462 -3
  2. package/dist/agents/cross-session-judge.d.ts +16 -7
  3. package/dist/agents/cross-session-judge.js +18 -15
  4. package/dist/agents/peer-session-drain.d.ts +4 -0
  5. package/dist/agents/peer-session-drain.js +5 -2
  6. package/dist/agents/repair-loop.d.ts +1 -1
  7. package/dist/agents/subagent.d.ts +3 -0
  8. package/dist/agents/subagent.js +12 -3
  9. package/dist/brain/anthropic.js +1 -0
  10. package/dist/brain/errors.d.ts +23 -1
  11. package/dist/brain/errors.js +19 -2
  12. package/dist/brain/open-responses.js +1 -0
  13. package/dist/brain/openai.js +1 -0
  14. package/dist/brain/status-sink.d.ts +7 -1
  15. package/dist/brain/stream-engine.d.ts +3 -0
  16. package/dist/brain/stream-engine.js +2 -1
  17. package/dist/brain/stream-shared.d.ts +3 -1
  18. package/dist/brain/stream-shared.js +16 -4
  19. package/dist/core/a2a.d.ts +3 -1
  20. package/dist/core/a2a.js +1 -1
  21. package/dist/core/ask-origin.d.ts +379 -154
  22. package/dist/core/ask-origin.js +224 -45
  23. package/dist/core/ask-question.js +2 -1
  24. package/dist/core/ask-unresolvable-notice.d.ts +5 -2
  25. package/dist/core/ask-unresolvable-notice.js +4 -3
  26. package/dist/core/auto-mode-arming.d.ts +17 -4
  27. package/dist/core/auto-mode-arming.js +31 -6
  28. package/dist/core/auto-mode-defaults.d.ts +16 -7
  29. package/dist/core/auto-mode-defaults.js +2 -1
  30. package/dist/core/auto-mode-directive.d.ts +18 -30
  31. package/dist/core/auto-mode-directive.js +9 -18
  32. package/dist/core/auto-mode-prompt.d.ts +62 -1
  33. package/dist/core/auto-mode-prompt.js +42 -4
  34. package/dist/core/auto-mode-rebuild.d.ts +2 -1
  35. package/dist/core/auto-mode-rebuild.js +1 -0
  36. package/dist/core/auto-mode.d.ts +32 -3
  37. package/dist/core/auto-mode.js +80 -41
  38. package/dist/core/checkpoint-store.d.ts +54 -35
  39. package/dist/core/checkpoint-store.js +58 -3
  40. package/dist/core/child-clamp-seats.js +1 -0
  41. package/dist/core/classifier-unavailable-arm.d.ts +57 -0
  42. package/dist/core/classifier-unavailable-arm.js +17 -0
  43. package/dist/core/effective-path-target.d.ts +12 -0
  44. package/dist/core/effective-path-target.js +18 -7
  45. package/dist/core/engine-notice.d.ts +53 -2
  46. package/dist/core/fs-write-gate-policy.d.ts +5 -1
  47. package/dist/core/fs-write-gate-policy.js +4 -1
  48. package/dist/core/gate-fold.js +19 -3
  49. package/dist/core/gate-lanes.js +116 -50
  50. package/dist/core/gate-outcome.d.ts +40 -7
  51. package/dist/core/gate-outcome.js +26 -4
  52. package/dist/core/governance-codes.d.ts +18 -2
  53. package/dist/core/governance-codes.js +23 -1
  54. package/dist/core/hooks.d.ts +53 -29
  55. package/dist/core/hooks.js +1 -3
  56. package/dist/core/mcp.js +1 -1
  57. package/dist/core/message-utils.d.ts +2 -1
  58. package/dist/core/message-utils.js +2 -1
  59. package/dist/core/mode-policy.d.ts +185 -0
  60. package/dist/core/mode-policy.js +184 -0
  61. package/dist/core/oracle-isolation.d.ts +3 -3
  62. package/dist/core/permission-rule-model.d.ts +20 -3
  63. package/dist/core/permission-rule-model.js +40 -3
  64. package/dist/core/permission-rule-provider.d.ts +22 -11
  65. package/dist/core/permission-rule-provider.js +17 -7
  66. package/dist/core/permission-rules.d.ts +23 -1
  67. package/dist/core/permission-rules.js +56 -9
  68. package/dist/core/persisted-rule-arms.d.ts +14 -10
  69. package/dist/core/persisted-rule-arms.js +13 -6
  70. package/dist/core/present-plan-tool.d.ts +1 -1
  71. package/dist/core/present-plan-tool.js +3 -3
  72. package/dist/core/read-only-shell.d.ts +19 -19
  73. package/dist/core/read-only-shell.js +38 -12
  74. package/dist/core/retired-keys.d.ts +9 -3
  75. package/dist/core/retired-keys.js +5 -1
  76. package/dist/core/runner/active-skill-scope.js +5 -4
  77. package/dist/core/runner/assemble-result.js +2 -2
  78. package/dist/core/runner/classifier-ladder.d.ts +43 -1
  79. package/dist/core/runner/classifier-ladder.js +29 -0
  80. package/dist/core/runner/contracts.d.ts +62 -38
  81. package/dist/core/runner/denial-limit-arms.d.ts +35 -7
  82. package/dist/core/runner/denial-limit-arms.js +15 -9
  83. package/dist/core/runner/env-capability-census.js +1 -1
  84. package/dist/core/runner/file-history-request.d.ts +26 -0
  85. package/dist/core/runner/file-history-request.js +90 -0
  86. package/dist/core/runner/gate-exit.d.ts +30 -4
  87. package/dist/core/runner/gate-exit.js +28 -7
  88. package/dist/core/runner/permission-rule-lanes.d.ts +11 -6
  89. package/dist/core/runner/permission-rule-lanes.js +55 -48
  90. package/dist/core/runner/persisted-path-rows.d.ts +64 -0
  91. package/dist/core/runner/persisted-path-rows.js +89 -0
  92. package/dist/core/runner/prepare-artifact.d.ts +5 -1
  93. package/dist/core/runner/prepare-artifact.js +9 -3
  94. package/dist/core/runner/prepare-ask-lane.d.ts +6 -4
  95. package/dist/core/runner/prepare-ask-lane.js +3 -3
  96. package/dist/core/runner/prepare-caps-and-workflow.d.ts +39 -13
  97. package/dist/core/runner/prepare-caps-and-workflow.js +58 -29
  98. package/dist/core/runner/prepare-file-history.d.ts +9 -3
  99. package/dist/core/runner/prepare-file-history.js +4 -90
  100. package/dist/core/runner/prepare-gate-stations.d.ts +37 -9
  101. package/dist/core/runner/prepare-gate-stations.js +46 -29
  102. package/dist/core/runner/prepare-hands-readface.d.ts +40 -17
  103. package/dist/core/runner/prepare-hands-readface.js +64 -53
  104. package/dist/core/runner/prepare-inherited-gate.d.ts +30 -23
  105. package/dist/core/runner/prepare-inherited-gate.js +45 -25
  106. package/dist/core/runner/prepare-park-ask.d.ts +6 -4
  107. package/dist/core/runner/prepare-park-ask.js +7 -5
  108. package/dist/core/runner/prepare-policy-chain.d.ts +25 -4
  109. package/dist/core/runner/prepare-policy-chain.js +203 -180
  110. package/dist/core/runner/prepare-protocol-tools.d.ts +28 -0
  111. package/dist/core/runner/prepare-protocol-tools.js +13 -3
  112. package/dist/core/runner/prepare-run-refs.d.ts +6 -6
  113. package/dist/core/runner/prepare-run-refs.js +9 -4
  114. package/dist/core/runner/prepare-safety-scan.d.ts +5 -0
  115. package/dist/core/runner/prepare-safety-scan.js +19 -3
  116. package/dist/core/runner/prepare-suspend-saga.d.ts +5 -5
  117. package/dist/core/runner/prepare-suspend-saga.js +6 -6
  118. package/dist/core/runner/prepare-task.js +56 -24
  119. package/dist/core/runner/prepare-wiring-manifest.d.ts +10 -6
  120. package/dist/core/runner/prepare-wiring-manifest.js +5 -3
  121. package/dist/core/runner/read-root-grant.d.ts +9 -0
  122. package/dist/core/runner/read-root-grant.js +12 -0
  123. package/dist/core/runner/retired-runner-deps.js +1 -0
  124. package/dist/core/runner/run-brain-sinks.js +3 -1
  125. package/dist/core/runner/run-identity-wiring.js +1 -1
  126. package/dist/core/runner/run-leg.js +29 -18
  127. package/dist/core/runner/run-notification-lane.d.ts +3 -0
  128. package/dist/core/runner/run-notification-lane.js +3 -1
  129. package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +2 -2
  130. package/dist/core/runner/run-telemetry-and-budget-seats.js +1 -1
  131. package/dist/core/runner/run-turn-boundary.js +1 -1
  132. package/dist/core/runner/runtask.d.ts +1 -0
  133. package/dist/core/runner/runtask.js +8 -11
  134. package/dist/core/runner/session-mode-seats.d.ts +48 -0
  135. package/dist/core/runner/session-mode-seats.js +73 -0
  136. package/dist/core/runner/session-rule-policy.js +3 -2
  137. package/dist/core/runner/stream-mode-verb.d.ts +27 -0
  138. package/dist/core/runner/stream-mode-verb.js +19 -0
  139. package/dist/core/runner/turn-attachments.d.ts +3 -3
  140. package/dist/core/runner/turn-attachments.js +1 -1
  141. package/dist/core/runner/write-protection-station.d.ts +7 -1
  142. package/dist/core/runner/write-protection-station.js +6 -3
  143. package/dist/core/runner-deps.d.ts +31 -34
  144. package/dist/core/runtime-caps.d.ts +12 -3
  145. package/dist/core/safety-axis-vocab.d.ts +2 -2
  146. package/dist/core/safety-axis-vocab.js +1 -1
  147. package/dist/core/secret-env.d.ts +30 -58
  148. package/dist/core/secret-env.js +149 -586
  149. package/dist/core/sensitive-path-policy.d.ts +4 -2
  150. package/dist/core/sensitive-path-policy.js +41 -31
  151. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -1
  152. package/dist/core/task-event.d.ts +1 -1
  153. package/dist/core/task-result.d.ts +1 -1
  154. package/dist/core/task-spec.d.ts +41 -89
  155. package/dist/core/task-stream.d.ts +16 -0
  156. package/dist/core/terminal-cause.js +1 -0
  157. package/dist/core/tighten-task-spec.d.ts +2 -2
  158. package/dist/core/tighten-task-spec.js +12 -14
  159. package/dist/core/tool-catalog-entries.js +4 -3
  160. package/dist/core/tool-policy.d.ts +84 -30
  161. package/dist/core/tool-policy.js +76 -16
  162. package/dist/core/tool-registry.d.ts +38 -0
  163. package/dist/core/tool-registry.js +25 -1
  164. package/dist/core/tool-roster.d.ts +17 -2
  165. package/dist/core/tool-roster.js +11 -1
  166. package/dist/core/tool-spec.d.ts +8 -9
  167. package/dist/core/trace.d.ts +18 -6
  168. package/dist/core/types.d.ts +2 -1
  169. package/dist/core/wiring-manifest.d.ts +31 -5
  170. package/dist/core/wiring-manifest.js +28 -1
  171. package/dist/core/write-protect.d.ts +11 -1
  172. package/dist/core/write-protect.js +26 -15
  173. package/dist/engine/execution-env/node-execution-env.d.ts +6 -1
  174. package/dist/engine/execution-env/node-execution-env.js +16 -3
  175. package/dist/engine/harness/agent-harness.js +2 -1
  176. package/dist/engine/harness/types.d.ts +6 -7
  177. package/dist/engine/llm/types.d.ts +2 -4
  178. package/dist/engine/loop/types.d.ts +7 -0
  179. package/dist/engine/lsp/node-lsp-manager.js +1 -1
  180. package/dist/index.d.ts +18 -8
  181. package/dist/index.js +15 -6
  182. package/dist/orchestration/workflow-governance.js +1 -1
  183. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  184. package/dist/orchestration/workflow-size-guideline.js +2 -2
  185. package/dist/stores/file/index.d.ts +5 -3
  186. package/dist/stores/file/session-store.d.ts +8 -2
  187. package/dist/stores/file/session-store.js +5 -5
  188. package/dist/tools/artifact/artifact-tool.d.ts +17 -0
  189. package/dist/tools/artifact/artifact-tool.js +16 -1
  190. package/dist/tools/fs/bash-readonly-classifier.d.ts +28 -7
  191. package/dist/tools/fs/bash-readonly-classifier.js +88 -0
  192. package/dist/tools/fs/containment-face.d.ts +9 -3
  193. package/dist/tools/fs/containment-face.js +2 -0
  194. package/dist/tools/fs/file-read-check.d.ts +50 -0
  195. package/dist/tools/fs/file-read-check.js +79 -0
  196. package/dist/tools/fs/fs-bash.d.ts +3 -3
  197. package/dist/tools/fs/fs-read.d.ts +1 -0
  198. package/dist/tools/fs/fs-search-tools.d.ts +2 -2
  199. package/dist/tools/fs/fs-search-tools.js +4 -4
  200. package/dist/tools/fs/fs-shared.js +20 -6
  201. package/dist/tools/fs/fs-write.d.ts +4 -3
  202. package/dist/tools/fs/fs-write.js +9 -9
  203. package/dist/tools/fs/index.d.ts +39 -3
  204. package/dist/tools/fs/index.js +38 -15
  205. package/dist/tools/fs/read-deny-home.d.ts +93 -0
  206. package/dist/tools/fs/read-deny-home.js +225 -0
  207. package/dist/tools/fs/read-deny.d.ts +176 -34
  208. package/dist/tools/fs/read-deny.js +268 -61
  209. package/dist/tools/fs/safety.d.ts +6 -1
  210. package/dist/tools/fs/safety.js +18 -5
  211. package/dist/tools/fs/search.d.ts +3 -0
  212. package/dist/tools/fs/search.js +47 -9
  213. package/dist/tools/fs/shell-read-check.d.ts +52 -13
  214. package/dist/tools/fs/shell-read-check.js +199 -20
  215. package/dist/tools/fs/shell-read-establish.d.ts +7 -3
  216. package/dist/tools/fs/shell-read-establish.js +134 -21
  217. package/dist/tools/fs/shell-read-identity.d.ts +7 -0
  218. package/dist/tools/fs/shell-read-identity.js +21 -0
  219. package/dist/tools/fs/shell-read-judge.d.ts +92 -32
  220. package/dist/tools/fs/shell-read-judge.js +43 -18
  221. package/dist/tools/fs/shell-read-plan.d.ts +4 -0
  222. package/dist/tools/fs/shell-read-plan.js +26 -4
  223. package/dist/tools/fs/shell-read-write-targets.d.ts +73 -0
  224. package/dist/tools/fs/shell-read-write-targets.js +173 -0
  225. package/package.json +1 -1
  226. package/test/export-surface.snapshot.json +117 -11
  227. package/wire-changes/core@7.34.0.json +2621 -0
  228. package/wire-changes/core@7.35.0.json +584 -0
@@ -1,5 +1,7 @@
1
1
  import type { AskEffective } from "../core/wiring-manifest.js";
2
2
  import { type PermissionModeClass } from "./cross-session-envelope.js";
3
+ /** The permission-mode words the fold reads (the engine's closed set, as the wire spells it). */
4
+ import type { PermissionMode } from "../core/ask-origin.js";
3
5
  /**
4
6
  * The explicit values of `crossSessionInbound`, loosest first: `wake` (deliver, and the HOST may start
5
7
  * a turn on this session when its box grows), `next-turn` (deliver at the session's next turn
@@ -100,14 +102,21 @@ export interface CrossSessionInboundInput {
100
102
  */
101
103
  export declare function judgeCrossSessionInbound(input: CrossSessionInboundInput): CrossSessionInboundVerdict;
102
104
  /**
103
- * design/385 §4.4 — sema's two-class fold of its own permission posture, read off the ONE derivation
104
- * the wiring manifest already owns ({@link AskEffective}): `bypass` = no human decides an ask
105
- * (`auto_allow`); `prompting` = an ask reaches a human or parks for one (`human_reachable`,
106
- * `park_only`), and — CC's literal rule for a never-prompting-never-bypassing mode — `auto_deny`;
107
- * `unresolved` ⇒ `"unknown"` (the judge fails closed on it). Single source: a host must not
108
- * re-derive the class from spec shape.
105
+ * design/385 §4.4 — sema's two-class fold of its own permission posture, from the session's EFFECTIVE
106
+ * permission mode (the engine's own resolution — never the requested one, never re-derived from spec shape)
107
+ * and the wiring manifest's ask derivation ({@link AskEffective}):
108
+ * · `bypass` — the effective mode is `bypassPermissions`, OR no human decides an ask (`auto_allow`, a
109
+ * blanket answering seat — the adapter's own reading of "nobody is watching");
110
+ * · `"unknown"` — otherwise, when the ask derivation is `unresolved` (the judge fails closed on it);
111
+ * · `prompting` — otherwise: an ask reaches a human or parks for one (`human_reachable`, `park_only`),
112
+ * and — CC's literal rule for a never-prompting-never-bypassing mode — `auto_deny`.
113
+ * `effective` absent (a leg with no mode fact) reads the ask derivation alone.
114
+ *
115
+ * WHAT THE CLASS IS: `fromMode` is a presence LABEL — whether a person is watching this session's approvals —
116
+ * attested to a peer so a bypass session does not park work where nobody watches. It is not a permission
117
+ * authority and authorizes no execution; the permission mode itself is read only from `effective`.
109
118
  */
110
- export declare function foldPermissionModeClass(askEffective: AskEffective): PermissionModeClass | "unknown";
119
+ export declare function foldPermissionModeClass(effective: PermissionMode | undefined, askEffective: AskEffective): PermissionModeClass | "unknown";
111
120
  /** The synchronous send outcomes a sender can observe at the send point (CC's ten classes on sema's
112
121
  * codes; `sender_paced` is expressed by the admission gate's `rate_limited` and is deliberately not a
113
122
  * second code). `parked_offline` is a DISPOSITION (durable append succeeded, recipient offline) and
@@ -154,22 +154,25 @@ export function judgeCrossSessionInbound(input) {
154
154
  }
155
155
  return selfModeClass === "bypass" ? holdFor("no-mode-asserted") : { verdict: "deliver" };
156
156
  }
157
- export function foldPermissionModeClass(askEffective) {
158
- switch (askEffective) {
159
- case "auto_allow":
160
- return "bypass";
161
- case "human_reachable":
162
- case "park_only":
163
- case "auto_deny":
164
- return "prompting";
165
- case "unresolved":
166
- return "unknown";
167
- default: {
168
- const _exhaustive = askEffective;
169
- void _exhaustive;
170
- throw new Error(`unreachable ask effective ${String(askEffective)}`);
157
+ export function foldPermissionModeClass(effective, askEffective) {
158
+ const byAsk = (() => {
159
+ switch (askEffective) {
160
+ case "auto_allow":
161
+ return "bypass";
162
+ case "human_reachable":
163
+ case "park_only":
164
+ case "auto_deny":
165
+ return "prompting";
166
+ case "unresolved":
167
+ return "unknown";
168
+ default: {
169
+ const _exhaustive = askEffective;
170
+ void _exhaustive;
171
+ throw new Error(`unreachable ask effective ${String(askEffective)}`);
172
+ }
171
173
  }
172
- }
174
+ })();
175
+ return effective === "bypassPermissions" ? "bypass" : byAsk;
173
176
  }
174
177
  export const PEER_SEND_VERDICT_CODES = Object.freeze([
175
178
  "peer_send.stale_socket",
@@ -7,6 +7,7 @@ import { type CrossSessionInboundSettingLayers } from "./cross-session-judge.js"
7
7
  import { type PeerLaneConfig } from "./peer-admission.js";
8
8
  import { type PeerDirectory } from "./peer-directory.js";
9
9
  import { createListAgentsTool } from "./list-agents-tool.js";
10
+ import { foldPermissionModeClass } from "./cross-session-judge.js";
10
11
  import type { AskEffective } from "../core/wiring-manifest.js";
11
12
  import type { SendMessageToolOptions } from "./send-message-tool.js";
12
13
  import { type PeerClock, type PeerHeldQueue } from "./peer-held-queue.js";
@@ -175,6 +176,9 @@ export declare function createPeerSessionDrain(opts: PeerSessionDrainOptions): P
175
176
  * before it — both read this one cell. */
176
177
  export interface PeerLaneRefs {
177
178
  askEffective?: AskEffective;
179
+ /** The session's EFFECTIVE permission mode, read live (it follows a mid-run mode change); absent on a leg
180
+ * that carries no mode fact. Paired with `askEffective` in the one mode-class fold. */
181
+ permissionMode?: () => Parameters<typeof foldPermissionModeClass>[0];
178
182
  /** The bound drain's pull face (filled by {@link bindPeerLaneDrain}); the `ReadInbox` tool reads it late. */
179
183
  pull?: (limit?: number, toolCallId?: string, charBudget?: number) => Promise<PulledInbox>;
180
184
  /** #1036 — the bound drain's BOUNDARY pass (filled by {@link bindPeerLaneDrain}); the leg's
@@ -655,6 +655,9 @@ export function createPeerSessionDrain(opts) {
655
655
  idleMachine,
656
656
  };
657
657
  }
658
+ function peerModeClassOf(refs) {
659
+ return refs.askEffective !== undefined ? foldPermissionModeClass(refs.permissionMode?.(), refs.askEffective) : "unknown";
660
+ }
658
661
  export function announcePeerLaneMount(deps, leg) {
659
662
  const verdict = judgePeerLaneMount(deps, leg.sessionId);
660
663
  if (verdict === undefined)
@@ -676,7 +679,7 @@ export function peerLaneSendMessageSeats(args) {
676
679
  sessionId: args.sessionId,
677
680
  scope: args.scope,
678
681
  ...(args.name !== undefined ? { name: args.name } : {}),
679
- modeClass: () => (args.refs.askEffective !== undefined ? foldPermissionModeClass(args.refs.askEffective) : "unknown"),
682
+ modeClass: () => peerModeClassOf(args.refs),
680
683
  },
681
684
  };
682
685
  }
@@ -753,7 +756,7 @@ export function bindPeerLaneDrain(harness, args) {
753
756
  ...(deps.peerLane !== undefined ? { lane: deps.peerLane } : {}),
754
757
  ownTokens: args.ownTokens,
755
758
  settingLayers: () => (typeof deps.crossSessionInbound === "function" ? deps.crossSessionInbound() : deps.crossSessionInbound),
756
- selfModeClass: () => (args.refs.askEffective !== undefined ? foldPermissionModeClass(args.refs.askEffective) : "unknown"),
759
+ selfModeClass: () => peerModeClassOf(args.refs),
757
760
  dialogExpiry: () => (typeof deps.crossSessionDialogExpiry === "function" ? deps.crossSessionDialogExpiry() : deps.crossSessionDialogExpiry),
758
761
  selfName: () => rowName ?? args.selfName?.(),
759
762
  warmup: lookupRowName,
@@ -142,7 +142,7 @@ export interface RepairLoopConfig {
142
142
  * instead of the bare reference-identity check, and surfaces {@link RepairResult.isolationClass}. Omit it (the
143
143
  * default) to keep the existing necessary-not-sufficient reference-identity check (the merged Slice-1 posture).
144
144
  *
145
- * The real isolated grader env is SERVICE-provided ({@link import("../core/types.js").RunnerDeps.graderEnvFactory});
145
+ * The real isolated grader env is SERVICE-provided (the trusted control plane mints it; the engine has no seat for it);
146
146
  * `graderEnv` here is its `.env`, and the control-plane `provenance` brand is reattached internally for the
147
147
  * assertion. The assertion only matters for the (mandate-OFF) auto-accept path: `isolationClass` caps a
148
148
  * non-`out_of_process` grader at `candidate_only`. SAFE-tier never auto-accepts regardless.
@@ -210,6 +210,9 @@ export interface SubagentWorktreeIsolation {
210
210
  * parent's own (possibly factory-owned) env.
211
211
  */
212
212
  export declare function createSubagentWorktreeHelper(baseEnv: ExecutionEnv, repoRoot: string): SubagentWorktreeIsolation;
213
+ /** Names an `AgentDefinition.name` must not collide with (would confuse the enum vs a real tool name). Includes the
214
+ * default delegation tool name itself, so a child agent can't be named "Agent" even when a custom `opts.name` is set. */
215
+ export declare const RESERVED_AGENT_NAMES: ReadonlySet<string>;
213
216
  /** Cap for the inline-defused worker-controlled report metadata lines (blocked_reason / error). Matches the
214
217
  * ask-question.ts inline-value convention (300) — a one-line operator-facing reason, not a bulk payload. */
215
218
  export declare const REPORT_FIELD_MAX = 300;
@@ -350,7 +350,7 @@ function resumabilityClaim(f) {
350
350
  return f.durableRow && f.named;
351
351
  }
352
352
  const BG_AGENT_COLLATERAL_REAP_REASON = "its parent run ended";
353
- const RESERVED_AGENT_NAMES = new Set([OUTPUT_TOOL_NAME, TOOL_SEARCH_NAME, OFFLOAD_TOOL_NAME, REPORT_BLOCKED_TOOL_NAME, DEFAULT_SUBAGENT_TOOL_NAME]);
353
+ export const RESERVED_AGENT_NAMES = new Set([OUTPUT_TOOL_NAME, TOOL_SEARCH_NAME, OFFLOAD_TOOL_NAME, REPORT_BLOCKED_TOOL_NAME, DEFAULT_SUBAGENT_TOOL_NAME]);
354
354
  export const REPORT_FIELD_MAX = 300;
355
355
  const ROSTER_ECHO_MAX = 400;
356
356
  function configError(message, code) {
@@ -1930,8 +1930,17 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1930
1930
  if (!isObserverTaskId(sid))
1931
1931
  throw new ObserverStoppedByUserError("observer identity revoked — delivery refused");
1932
1932
  const pickedRef = observerModelPick().modelRef;
1933
- const observerInternals = ctx.interactionPosture !== undefined || pickedRef !== undefined
1934
- ? { ...(ctx.interactionPosture !== undefined ? { parentInteractionPosture: ctx.interactionPosture } : {}), ...(pickedRef !== undefined ? { inheritedModelRef: pickedRef } : {}) }
1933
+ const parentGate = ctx.inheritedGateForChildren?.();
1934
+ const observerGate = parentGate === undefined
1935
+ ? undefined
1936
+ : {
1937
+ ...(parentGate.permissionMode !== undefined ? { permissionMode: parentGate.permissionMode } : {}),
1938
+ ...(parentGate.permissionModeCeiling !== undefined ? { permissionModeCeiling: parentGate.permissionModeCeiling } : {}),
1939
+ ...(parentGate.shellGate !== undefined ? { shellGate: parentGate.shellGate } : {}),
1940
+ admittedOrgScopes: parentGate.admittedOrgScopes,
1941
+ };
1942
+ const observerInternals = ctx.interactionPosture !== undefined || pickedRef !== undefined || observerGate !== undefined
1943
+ ? { ...(ctx.interactionPosture !== undefined ? { parentInteractionPosture: ctx.interactionPosture } : {}), ...(pickedRef !== undefined ? { inheritedModelRef: pickedRef } : {}), ...(observerGate !== undefined ? { inheritedGate: observerGate } : {}) }
1935
1944
  : undefined;
1936
1945
  const r = await opts.runner.runTask(spec, observerInternals);
1937
1946
  if (r.terminal.kind !== "completed") {
@@ -240,6 +240,7 @@ export function createAnthropicBrain(config = {}) {
240
240
  signal: options?.signal,
241
241
  config,
242
242
  httpLabel: "anthropic",
243
+ maxRetries: options?.maxRetries,
243
244
  stallTimeouts: options?.stallTimeouts,
244
245
  buildRequest: (overrides) => {
245
246
  const root = resolveRequestRoot(model.baseUrl, config.baseUrl, "https://api.anthropic.com");
@@ -70,11 +70,33 @@ export declare class BrainError extends Error {
70
70
  apiError?: boolean;
71
71
  });
72
72
  }
73
+ /**
74
+ * The shape a provider ECHO field must have to be carried: the request id a provider states on a response
75
+ * header (`AssistantMessage.requestId`, projected as `TaskResult.apiFailure.requestId`) and the model name it
76
+ * reports answering (`AssistantMessage.responseModel`). Both are UPSTREAM BYTES that end up persisted in a
77
+ * transcript and rendered by a UI, so one alphabet governs both: visible ASCII (0x21–0x7E — no space, no
78
+ * control byte, no non-ASCII), 1–128 characters. A value outside it is dropped (the field is absent), never
79
+ * truncated or rewritten; the empty string is outside it. No flags, so `test` is stateless. This object is the
80
+ * consumer's copy: the engine judges with its own instance built from the same source, so recompiling or
81
+ * otherwise mutating this one cannot change what the engine admits.
82
+ */
83
+ export declare const PROVIDER_ECHO_SHAPE: RegExp;
84
+ /** Does `value` fit {@link PROVIDER_ECHO_SHAPE}? A non-string answers `false` (a JS caller's number is not an echo). */
85
+ export declare function isProviderEchoShape(value: string): boolean;
86
+ /** How the brains read one provider echo value: a non-string or the empty string states nothing (`absent` — no field,
87
+ * nothing counted); a string in {@link isProviderEchoShape} is `carried`; any other string is `refused` (dropped and
88
+ * counted as `echo_dropped`). */
89
+ export declare function providerEchoReading(value: unknown): "absent" | "carried" | "refused";
73
90
  /**
74
91
  * Lift the provider's own request identifier off a response, when it stated one in a shape worth
75
- * carrying (see {@link REQUEST_ID_RE}). This is the handle a provider's support channel asks for, and
92
+ * carrying ({@link isProviderEchoShape}). This is the handle a provider's support channel asks for, and
76
93
  * without it a failed turn's only evidence is prose the operator cannot correlate upstream.
77
94
  *
95
+ * A response whose header values were all refused by the shape yields no id and one `echo_dropped`
96
+ * telemetry event (`field: "requestId"`, bridged to the `brain.echo_dropped` trace frame) — one per
97
+ * response, whichever header was refused; a response that states no id at all (no header, or an empty one:
98
+ * {@link providerEchoReading}) yields none.
99
+ *
78
100
  * Duck-typed on `headers.get` rather than requiring a real `Response`: the engine's transport is a
79
101
  * caller-injected `fetch`, so a deployment (or a test) may hand back any response-shaped object, and
80
102
  * a missing/throwing header bag must degrade to "no id" instead of failing the call it is describing.
@@ -1,4 +1,5 @@
1
1
  import { isErrorLike, readMember, thrownText } from "../core/thrown-value.js";
2
+ import { emitBrainTelemetry } from "./status-sink.js";
2
3
  export const ZERO_USAGE = {
3
4
  input: 0,
4
5
  output: 0,
@@ -30,20 +31,36 @@ export class BrainError extends Error {
30
31
  }
31
32
  }
32
33
  const REQUEST_ID_HEADERS = ["request-id", "x-request-id"];
33
- const REQUEST_ID_RE = /^[A-Za-z0-9._:-]{1,128}$/;
34
+ const PROVIDER_ECHO_SOURCE = "^[\\x21-\\x7E]{1,128}$";
35
+ const providerEchoShape = new RegExp(PROVIDER_ECHO_SOURCE);
36
+ export const PROVIDER_ECHO_SHAPE = new RegExp(PROVIDER_ECHO_SOURCE);
37
+ export function isProviderEchoShape(value) {
38
+ return typeof value === "string" && providerEchoShape.test(value);
39
+ }
40
+ export function providerEchoReading(value) {
41
+ if (typeof value !== "string" || value.length === 0)
42
+ return "absent";
43
+ return isProviderEchoShape(value) ? "carried" : "refused";
44
+ }
34
45
  export function readProviderRequestId(res) {
46
+ let refused = false;
35
47
  try {
36
48
  const get = res?.headers?.get;
37
49
  if (typeof get !== "function")
38
50
  return undefined;
39
51
  for (const name of REQUEST_ID_HEADERS) {
40
52
  const raw = get.call(res.headers, name);
41
- if (typeof raw === "string" && REQUEST_ID_RE.test(raw))
53
+ const reading = providerEchoReading(raw);
54
+ if (reading === "carried")
42
55
  return raw;
56
+ if (reading === "refused")
57
+ refused = true;
43
58
  }
44
59
  }
45
60
  catch {
46
61
  }
62
+ if (refused)
63
+ emitBrainTelemetry({ kind: "echo_dropped", field: "requestId" });
47
64
  return undefined;
48
65
  }
49
66
  export function namesTheFailure(status) {
@@ -234,6 +234,7 @@ export function createOpenResponsesBrain(config = {}) {
234
234
  signal: options?.signal,
235
235
  config,
236
236
  httpLabel: "responses",
237
+ maxRetries: options?.maxRetries,
237
238
  stallTimeouts: options?.stallTimeouts,
238
239
  buildRequest: (overrides) => {
239
240
  const root = resolveRequestRoot(model.baseUrl, config.baseUrl);
@@ -297,6 +297,7 @@ export function createOpenAIBrain(config = {}) {
297
297
  signal: options?.signal,
298
298
  config,
299
299
  httpLabel: "gateway",
300
+ maxRetries: options?.maxRetries,
300
301
  stallTimeouts: options?.stallTimeouts,
301
302
  buildRequest: (overrides) => {
302
303
  const root = resolveRequestRoot(model.baseUrl, config.baseUrl);
@@ -31,7 +31,7 @@ export declare function emitBrainStatus(status: BrainStatus): void;
31
31
  * Silent-fallback telemetry (C1/C4/C5/C6) — the brain→runner TELEMETRY channel, a
32
32
  * SECOND ALS sink parallel to the status one. Statuses are user-facing liveness frames (wire
33
33
  * `status` events); telemetry is operator-facing fallback accounting the Runner bridges to trace
34
- * events (`brain.failover` / `breaker.transition` / `brain.retry` / `vision.placeholder`). Kept
34
+ * events (`brain.failover` / `breaker.transition` / `brain.retry` / `vision.placeholder` / `brain.echo_dropped`). Kept
35
35
  * separate so widening telemetry never bloats the shell-visible status vocabulary. Same contract:
36
36
  * fire-and-forget, no-op outside a {@link runWithBrainTelemetry} scope, MUST NOT throw.
37
37
  */
@@ -66,6 +66,12 @@ export type BrainTelemetry = {
66
66
  | {
67
67
  kind: "document_placeholder";
68
68
  count: number;
69
+ }
70
+ /** A provider echo field ({@link import("./errors.js").PROVIDER_ECHO_SHAPE}) refused by its shape: the value was dropped
71
+ * and the field is absent. One event per provider response per field. */
72
+ | {
73
+ kind: "echo_dropped";
74
+ field: "requestId" | "responseModel";
69
75
  };
70
76
  /** Run `fn` with a per-task brain-telemetry sink in scope (Runner-side; compose with runWithStatusSink). */
71
77
  export declare function runWithBrainTelemetry<T>(emit: (t: BrainTelemetry) => void, fn: () => Promise<T>): Promise<T>;
@@ -243,4 +243,7 @@ export declare function runStreamingBrain(args: {
243
243
  firstTokenMs?: number;
244
244
  idleMs?: number;
245
245
  };
246
+ /** Per-call CAP on the transient retry budget (`StreamOptions.maxRetries`): the call retries at most this many times
247
+ * and never more than the brain's own budget ({@link resolveMaxRetries}); a non-finite or negative value caps nothing. */
248
+ maxRetries?: number;
246
249
  }): ReturnType<typeof createAssistantMessageEventStream>;
@@ -265,6 +265,7 @@ function firstByteTimeoutMessage(windowsMs) {
265
265
  }
266
266
  export function runStreamingBrain(args) {
267
267
  const { model, doFetch, signal, config, httpLabel, buildRequest, makeParser, stallTimeouts } = args;
268
+ const callRetryCap = args.maxRetries !== undefined && Number.isFinite(args.maxRetries) && args.maxRetries >= 0 ? Math.floor(args.maxRetries) : Infinity;
268
269
  const out = createAssistantMessageEventStream();
269
270
  let cleanup;
270
271
  let announcedRetry = false;
@@ -326,7 +327,7 @@ export function runStreamingBrain(args) {
326
327
  let req = buildRequest();
327
328
  req.onCommitted?.();
328
329
  let maxOutputTokensOverride;
329
- const maxRetries = resolveMaxRetries(config.maxRetries);
330
+ const maxRetries = Math.min(resolveMaxRetries(config.maxRetries), callRetryCap);
330
331
  const baseDelay = config.retryDelayMs ?? 500;
331
332
  const deterministicConnectBudget = Math.min(resolveDeterministicConnectRetries(config.deterministicConnectRetries), maxRetries);
332
333
  const deterministicLaneActive = deterministicConnectBudget < maxRetries;
@@ -47,7 +47,9 @@ export declare function createRepetitionPoll(): RepetitionPoll;
47
47
  * message every lane spreads `...partial` into) — ONE rule for the three lanes' spellings (`chunk.model` /
48
48
  * `message_start.message.model` / `response.model`), WRITE twin of `providerReportedModel`
49
49
  * (`core/message-utils.ts`). The LAST frame that NAMES a model decides and the seat carries it only when it
50
- * differs; naming nothing is not an update, naming the REQUESTED id RETRACTS an earlier substitution. */
50
+ * differs; naming nothing (no name, or the empty string — {@link providerEchoReading}) is not an update, naming the
51
+ * REQUESTED id RETRACTS an earlier substitution. A name outside {@link isProviderEchoShape} is dropped — not an
52
+ * update — and counted once per response (`echo_dropped`, `field: "responseModel"`). */
51
53
  export declare function noteResponseModel(partial: {
52
54
  responseModel?: string;
53
55
  }, reported: unknown, requestedId: string): void;
@@ -1,4 +1,6 @@
1
+ import { isProviderEchoShape, providerEchoReading } from "./errors.js";
1
2
  import { inspectDegenerate } from "./repetition.js";
3
+ import { emitBrainTelemetry } from "./status-sink.js";
2
4
  export const STREAM_TORN_WARNING = "Connection closed mid-response. The response above may be incomplete.";
3
5
  export function parseStreamedToolArgs(json) {
4
6
  if (!json)
@@ -40,11 +42,21 @@ export function createRepetitionPoll() {
40
42
  },
41
43
  };
42
44
  }
45
+ const refusedModelCounted = new WeakSet();
43
46
  export function noteResponseModel(partial, reported, requestedId) {
44
- if (typeof reported !== "string" || reported === "")
47
+ const reading = providerEchoReading(reported);
48
+ if (reading === "absent")
45
49
  return;
46
- if (reported === requestedId)
50
+ if (reported === requestedId) {
47
51
  delete partial.responseModel;
48
- else
49
- partial.responseModel = reported;
52
+ return;
53
+ }
54
+ if (reading === "refused") {
55
+ if (!refusedModelCounted.has(partial)) {
56
+ refusedModelCounted.add(partial);
57
+ emitBrainTelemetry({ kind: "echo_dropped", field: "responseModel" });
58
+ }
59
+ return;
60
+ }
61
+ partial.responseModel = reported;
50
62
  }
@@ -8,7 +8,7 @@ export declare const A2A_SKILL_DESCRIPTION_MAX_CHARS = 2048;
8
8
  *
9
9
  * Unlike MCP there is no server-hint leg to merge: the protocol has no per-skill annotation vocabulary,
10
10
  * and a remote agent's self-declaration would not be trustworthy if it had one. Every A2A tool therefore
11
- * starts at the ruled default (`egress:true`, `effect:"write"`) and the CALLER's
11
+ * starts at the ruled default (`egress:true`, the effect undeclared — read as `write` everywhere) and the CALLER's
12
12
  * {@link A2aServerSpec.toolAxes} is the only thing that can lower it (design F: caller = trust root).
13
13
  */
14
14
  export interface A2aToolAxis {
@@ -20,6 +20,8 @@ export interface A2aToolAxis {
20
20
  /** `false` appears ONLY via an explicit caller override — same posture: enforcement ignores it,
21
21
  * the report face consumes it (an explicit negative is a judgment, not silence). */
22
22
  egress?: boolean;
23
+ /** Present ONLY when the caller's override declares it; absent is the repeat-unsafe `write` every reader applies (and
24
+ * the roster names `default` provenance — a default is not a declaration). */
23
25
  effect?: ToolEffect;
24
26
  }
25
27
  /** design/99 §E9 equivalent for A2A — a per-peer projection of THIS materialization. A2A holds no
package/dist/core/a2a.js CHANGED
@@ -321,7 +321,7 @@ function skillDescription(peerLabel, skill) {
321
321
  return lines.join("\n");
322
322
  }
323
323
  function a2aAxisFor(name, override) {
324
- const axis = { name, egress: true, effect: "write" };
324
+ const axis = { name, egress: true };
325
325
  if (override === undefined)
326
326
  return axis;
327
327
  if (override.effect !== undefined)