@sema-agent/core 7.33.7 → 7.34.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 (206) hide show
  1. package/CHANGELOG.md +357 -0
  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/open-responses.js +1 -0
  11. package/dist/brain/openai.js +1 -0
  12. package/dist/brain/stream-engine.d.ts +3 -0
  13. package/dist/brain/stream-engine.js +2 -1
  14. package/dist/core/a2a.d.ts +3 -1
  15. package/dist/core/a2a.js +1 -1
  16. package/dist/core/ask-origin.d.ts +351 -154
  17. package/dist/core/ask-origin.js +223 -45
  18. package/dist/core/ask-unresolvable-notice.d.ts +5 -2
  19. package/dist/core/ask-unresolvable-notice.js +4 -3
  20. package/dist/core/auto-mode-arming.d.ts +17 -4
  21. package/dist/core/auto-mode-arming.js +31 -6
  22. package/dist/core/auto-mode-defaults.d.ts +16 -7
  23. package/dist/core/auto-mode-defaults.js +2 -1
  24. package/dist/core/auto-mode-directive.d.ts +18 -30
  25. package/dist/core/auto-mode-directive.js +9 -18
  26. package/dist/core/auto-mode-prompt.d.ts +62 -1
  27. package/dist/core/auto-mode-prompt.js +42 -4
  28. package/dist/core/auto-mode-rebuild.d.ts +2 -1
  29. package/dist/core/auto-mode-rebuild.js +1 -0
  30. package/dist/core/auto-mode.d.ts +32 -3
  31. package/dist/core/auto-mode.js +80 -41
  32. package/dist/core/checkpoint-store.d.ts +50 -35
  33. package/dist/core/checkpoint-store.js +58 -3
  34. package/dist/core/child-clamp-seats.js +1 -0
  35. package/dist/core/classifier-unavailable-arm.d.ts +57 -0
  36. package/dist/core/classifier-unavailable-arm.js +17 -0
  37. package/dist/core/effective-path-target.d.ts +12 -0
  38. package/dist/core/effective-path-target.js +18 -7
  39. package/dist/core/engine-notice.d.ts +53 -2
  40. package/dist/core/fs-write-gate-policy.d.ts +5 -1
  41. package/dist/core/fs-write-gate-policy.js +4 -1
  42. package/dist/core/gate-fold.js +17 -1
  43. package/dist/core/gate-lanes.js +111 -46
  44. package/dist/core/gate-outcome.d.ts +40 -7
  45. package/dist/core/gate-outcome.js +26 -4
  46. package/dist/core/governance-codes.d.ts +1 -1
  47. package/dist/core/governance-codes.js +12 -0
  48. package/dist/core/hooks.d.ts +50 -29
  49. package/dist/core/hooks.js +1 -3
  50. package/dist/core/mcp.js +1 -1
  51. package/dist/core/mode-policy.d.ts +182 -0
  52. package/dist/core/mode-policy.js +179 -0
  53. package/dist/core/oracle-isolation.d.ts +3 -3
  54. package/dist/core/permission-rule-model.d.ts +16 -3
  55. package/dist/core/permission-rule-model.js +36 -3
  56. package/dist/core/permission-rule-provider.d.ts +22 -11
  57. package/dist/core/permission-rule-provider.js +17 -7
  58. package/dist/core/permission-rules.d.ts +23 -1
  59. package/dist/core/permission-rules.js +35 -8
  60. package/dist/core/persisted-rule-arms.d.ts +10 -9
  61. package/dist/core/persisted-rule-arms.js +9 -6
  62. package/dist/core/present-plan-tool.d.ts +1 -1
  63. package/dist/core/present-plan-tool.js +3 -3
  64. package/dist/core/read-only-shell.d.ts +19 -19
  65. package/dist/core/read-only-shell.js +38 -12
  66. package/dist/core/retired-keys.d.ts +9 -3
  67. package/dist/core/retired-keys.js +5 -1
  68. package/dist/core/runner/classifier-ladder.d.ts +43 -1
  69. package/dist/core/runner/classifier-ladder.js +29 -0
  70. package/dist/core/runner/contracts.d.ts +37 -35
  71. package/dist/core/runner/denial-limit-arms.d.ts +30 -7
  72. package/dist/core/runner/denial-limit-arms.js +15 -9
  73. package/dist/core/runner/env-capability-census.js +1 -1
  74. package/dist/core/runner/gate-exit.d.ts +30 -4
  75. package/dist/core/runner/gate-exit.js +28 -7
  76. package/dist/core/runner/permission-rule-lanes.d.ts +9 -5
  77. package/dist/core/runner/permission-rule-lanes.js +44 -40
  78. package/dist/core/runner/persisted-path-rows.d.ts +64 -0
  79. package/dist/core/runner/persisted-path-rows.js +89 -0
  80. package/dist/core/runner/prepare-artifact.d.ts +5 -1
  81. package/dist/core/runner/prepare-artifact.js +9 -3
  82. package/dist/core/runner/prepare-ask-lane.d.ts +5 -3
  83. package/dist/core/runner/prepare-ask-lane.js +1 -1
  84. package/dist/core/runner/prepare-caps-and-workflow.d.ts +39 -13
  85. package/dist/core/runner/prepare-caps-and-workflow.js +58 -29
  86. package/dist/core/runner/prepare-gate-stations.d.ts +33 -6
  87. package/dist/core/runner/prepare-gate-stations.js +32 -19
  88. package/dist/core/runner/prepare-hands-readface.d.ts +40 -17
  89. package/dist/core/runner/prepare-hands-readface.js +64 -53
  90. package/dist/core/runner/prepare-inherited-gate.d.ts +22 -22
  91. package/dist/core/runner/prepare-inherited-gate.js +37 -23
  92. package/dist/core/runner/prepare-park-ask.d.ts +2 -2
  93. package/dist/core/runner/prepare-park-ask.js +2 -1
  94. package/dist/core/runner/prepare-policy-chain.d.ts +22 -2
  95. package/dist/core/runner/prepare-policy-chain.js +39 -12
  96. package/dist/core/runner/prepare-protocol-tools.d.ts +28 -0
  97. package/dist/core/runner/prepare-protocol-tools.js +13 -3
  98. package/dist/core/runner/prepare-run-refs.d.ts +6 -6
  99. package/dist/core/runner/prepare-run-refs.js +9 -4
  100. package/dist/core/runner/prepare-safety-scan.d.ts +5 -0
  101. package/dist/core/runner/prepare-safety-scan.js +19 -3
  102. package/dist/core/runner/prepare-suspend-saga.d.ts +5 -5
  103. package/dist/core/runner/prepare-suspend-saga.js +6 -6
  104. package/dist/core/runner/prepare-task.js +51 -22
  105. package/dist/core/runner/prepare-wiring-manifest.d.ts +10 -6
  106. package/dist/core/runner/prepare-wiring-manifest.js +5 -3
  107. package/dist/core/runner/read-root-grant.d.ts +9 -0
  108. package/dist/core/runner/read-root-grant.js +12 -0
  109. package/dist/core/runner/retired-runner-deps.js +1 -0
  110. package/dist/core/runner/run-identity-wiring.js +1 -1
  111. package/dist/core/runner/run-leg.js +24 -15
  112. package/dist/core/runner/run-notification-lane.js +1 -0
  113. package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +2 -2
  114. package/dist/core/runner/run-telemetry-and-budget-seats.js +1 -1
  115. package/dist/core/runner/run-turn-boundary.js +1 -1
  116. package/dist/core/runner/runtask.js +3 -0
  117. package/dist/core/runner/stream-mode-verb.d.ts +27 -0
  118. package/dist/core/runner/stream-mode-verb.js +19 -0
  119. package/dist/core/runner/turn-attachments.d.ts +3 -3
  120. package/dist/core/runner/turn-attachments.js +1 -1
  121. package/dist/core/runner/write-protection-station.d.ts +7 -1
  122. package/dist/core/runner/write-protection-station.js +6 -3
  123. package/dist/core/runner-deps.d.ts +31 -34
  124. package/dist/core/runtime-caps.d.ts +12 -3
  125. package/dist/core/safety-axis-vocab.d.ts +2 -2
  126. package/dist/core/safety-axis-vocab.js +1 -1
  127. package/dist/core/secret-env.d.ts +16 -2
  128. package/dist/core/secret-env.js +25 -1
  129. package/dist/core/sensitive-path-policy.d.ts +4 -2
  130. package/dist/core/sensitive-path-policy.js +39 -30
  131. package/dist/core/spec-contract.d.ts +15 -0
  132. package/dist/core/spec-contract.js +19 -10
  133. package/dist/core/task-event.d.ts +1 -1
  134. package/dist/core/task-result.d.ts +1 -1
  135. package/dist/core/task-spec.d.ts +41 -89
  136. package/dist/core/task-stream.d.ts +16 -0
  137. package/dist/core/terminal-cause.js +1 -0
  138. package/dist/core/tighten-task-spec.d.ts +2 -2
  139. package/dist/core/tighten-task-spec.js +12 -14
  140. package/dist/core/tool-catalog-entries.js +4 -3
  141. package/dist/core/tool-policy.d.ts +54 -25
  142. package/dist/core/tool-policy.js +43 -2
  143. package/dist/core/tool-registry.d.ts +2 -0
  144. package/dist/core/tool-registry.js +3 -0
  145. package/dist/core/tool-spec.d.ts +8 -9
  146. package/dist/core/trace.d.ts +7 -6
  147. package/dist/core/types.d.ts +2 -1
  148. package/dist/core/wiring-manifest.d.ts +31 -5
  149. package/dist/core/wiring-manifest.js +28 -1
  150. package/dist/core/write-protect.d.ts +11 -1
  151. package/dist/core/write-protect.js +26 -15
  152. package/dist/engine/execution-env/kill-tree.js +4 -1
  153. package/dist/engine/execution-env/node-execution-env.d.ts +3 -0
  154. package/dist/engine/execution-env/node-execution-env.js +34 -35
  155. package/dist/engine/harness/types.d.ts +6 -7
  156. package/dist/engine/llm/types.d.ts +2 -4
  157. package/dist/engine/loop/types.d.ts +7 -0
  158. package/dist/index.d.ts +15 -5
  159. package/dist/index.js +12 -3
  160. package/dist/orchestration/run-spec.d.ts +4 -0
  161. package/dist/orchestration/run-spec.js +4 -3
  162. package/dist/orchestration/workflow-governance.js +1 -1
  163. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  164. package/dist/orchestration/workflow-size-guideline.js +2 -2
  165. package/dist/stores/file/fs-atomic.js +2 -1
  166. package/dist/tools/artifact/artifact-tool.d.ts +17 -0
  167. package/dist/tools/artifact/artifact-tool.js +16 -1
  168. package/dist/tools/fs/bash-readonly-classifier.d.ts +28 -7
  169. package/dist/tools/fs/bash-readonly-classifier.js +88 -0
  170. package/dist/tools/fs/containment-face.d.ts +9 -3
  171. package/dist/tools/fs/containment-face.js +2 -0
  172. package/dist/tools/fs/file-read-check.d.ts +49 -0
  173. package/dist/tools/fs/file-read-check.js +71 -0
  174. package/dist/tools/fs/fs-bash.d.ts +3 -3
  175. package/dist/tools/fs/fs-read.d.ts +1 -0
  176. package/dist/tools/fs/fs-search-tools.d.ts +2 -2
  177. package/dist/tools/fs/fs-search-tools.js +4 -4
  178. package/dist/tools/fs/fs-shared.js +20 -6
  179. package/dist/tools/fs/fs-write.d.ts +4 -3
  180. package/dist/tools/fs/fs-write.js +9 -9
  181. package/dist/tools/fs/index.d.ts +39 -3
  182. package/dist/tools/fs/index.js +38 -15
  183. package/dist/tools/fs/read-deny-home.d.ts +93 -0
  184. package/dist/tools/fs/read-deny-home.js +225 -0
  185. package/dist/tools/fs/read-deny.d.ts +176 -34
  186. package/dist/tools/fs/read-deny.js +268 -61
  187. package/dist/tools/fs/safety.d.ts +6 -1
  188. package/dist/tools/fs/safety.js +18 -5
  189. package/dist/tools/fs/search.d.ts +3 -0
  190. package/dist/tools/fs/search.js +47 -9
  191. package/dist/tools/fs/shell-read-check.d.ts +42 -11
  192. package/dist/tools/fs/shell-read-check.js +188 -13
  193. package/dist/tools/fs/shell-read-establish.d.ts +7 -3
  194. package/dist/tools/fs/shell-read-establish.js +134 -21
  195. package/dist/tools/fs/shell-read-identity.d.ts +7 -0
  196. package/dist/tools/fs/shell-read-identity.js +21 -0
  197. package/dist/tools/fs/shell-read-judge.d.ts +92 -32
  198. package/dist/tools/fs/shell-read-judge.js +43 -18
  199. package/dist/tools/fs/shell-read-plan.d.ts +4 -0
  200. package/dist/tools/fs/shell-read-plan.js +26 -4
  201. package/dist/tools/fs/shell-read-write-targets.d.ts +73 -0
  202. package/dist/tools/fs/shell-read-write-targets.js +173 -0
  203. package/package.json +3 -1
  204. package/test/export-surface.snapshot.json +103 -11
  205. package/wire-changes/core@7.33.8.json +165 -0
  206. package/wire-changes/core@7.34.0.json +2621 -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");
@@ -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);
@@ -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;
@@ -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)