pi-subagents 0.66.0 → 0.68.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 (172) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/README.md +5 -4
  3. package/agents/evidence-auditor.md +34 -0
  4. package/agents/reviewer.md +3 -2
  5. package/docs/agents.md +43 -15
  6. package/docs/configuration.md +67 -23
  7. package/docs/extension-api.md +38 -19
  8. package/docs/missions.md +10 -2
  9. package/docs/models.md +11 -79
  10. package/docs/observability.md +22 -12
  11. package/docs/standalone-background.md +59 -0
  12. package/docs/tool-reference.md +33 -20
  13. package/docs/watchdog.md +39 -10
  14. package/docs/workflows.md +37 -13
  15. package/index.ts +5 -2
  16. package/inspector-runner.mjs +2 -2
  17. package/package.json +4 -2
  18. package/prompts/parallel-review.md +1 -1
  19. package/runner-peer-loader.mjs +24 -0
  20. package/runner-peer-preload.mjs +32 -0
  21. package/skills/pi-subagents/SKILL.md +32 -21
  22. package/skills/pi-subagents/references/constraints-and-recipes.md +3 -2
  23. package/skills/pi-subagents/references/execution-controls.md +11 -9
  24. package/skills/pi-subagents/references/management-authoring-rpc.md +0 -1
  25. package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
  26. package/skills/pi-subagents/references/prompting-and-roles.md +17 -13
  27. package/skills/pi-subagents/references/review-and-validation.md +3 -3
  28. package/src/agents/advertised-agent-prompt.ts +34 -3
  29. package/src/agents/agent-management.ts +57 -58
  30. package/src/agents/agent-serializer.ts +4 -3
  31. package/src/agents/agents.ts +190 -71
  32. package/src/agents/builtin-names.ts +1 -0
  33. package/src/agents/chain-serializer.ts +5 -0
  34. package/src/agents/runtime-agent-registry.ts +7 -6
  35. package/src/api/delegation.ts +4 -0
  36. package/src/api/preflight.ts +94 -59
  37. package/src/api/required-child-extensions.ts +6 -0
  38. package/src/api/shared-types.ts +2 -0
  39. package/src/extension/config.ts +10 -37
  40. package/src/extension/fanout-child.ts +66 -4
  41. package/src/extension/herdr-pi-bridge.ts +160 -0
  42. package/src/extension/index.ts +62 -39
  43. package/src/extension/public-execution.ts +7 -5
  44. package/src/extension/rpc.ts +4 -0
  45. package/src/extension/schemas.ts +81 -81
  46. package/src/extension/tool-description.ts +30 -82
  47. package/src/inspectors/actions.ts +148 -0
  48. package/src/inspectors/ghostty/actions.ts +74 -0
  49. package/src/inspectors/ghostty/plugin.ts +17 -0
  50. package/src/inspectors/herdr/actions.ts +99 -179
  51. package/src/inspectors/herdr/plugin.ts +20 -0
  52. package/src/inspectors/herdr/project-panes.ts +1 -1
  53. package/src/inspectors/{herdr/inspector-runner.ts → inspector-runner.ts} +12 -12
  54. package/src/inspectors/plugins.ts +8 -0
  55. package/src/inspectors/{herdr/session-roots-codec.ts → session-roots-codec.ts} +3 -14
  56. package/src/inspectors/types.ts +51 -0
  57. package/src/intercom/intercom-bridge.ts +50 -8
  58. package/src/intercom/native-supervisor-channel.ts +44 -31
  59. package/src/policy/authority.ts +4 -0
  60. package/src/profiles/profiles.ts +12 -6
  61. package/src/runs/background/active-async-capacity.ts +4 -0
  62. package/src/runs/background/active-run-index.ts +17 -1
  63. package/src/runs/background/async-execution.ts +348 -176
  64. package/src/runs/background/async-job-tracker.ts +8 -6
  65. package/src/runs/background/async-resume.ts +17 -12
  66. package/src/runs/background/async-status.ts +15 -4
  67. package/src/runs/background/auto-drain.ts +23 -10
  68. package/src/runs/background/binary-bootstrap.ts +38 -0
  69. package/src/runs/background/chain-append.ts +1 -1
  70. package/src/runs/background/chain-root-attachment.ts +14 -33
  71. package/src/runs/background/fleet-view.ts +30 -2
  72. package/src/runs/background/notify.ts +105 -7
  73. package/src/runs/background/owned-process-tree.ts +29 -2
  74. package/src/runs/background/result-files.ts +8 -4
  75. package/src/runs/background/result-watcher.ts +19 -2
  76. package/src/runs/background/run-child-session.ts +81 -33
  77. package/src/runs/background/run-status.ts +3 -0
  78. package/src/runs/background/runner-aliases.ts +12 -33
  79. package/src/runs/background/runner-child-launch.ts +6 -1
  80. package/src/runs/background/runner-child-sessions.ts +5 -4
  81. package/src/runs/background/runner-http-dispatcher.ts +119 -0
  82. package/src/runs/background/scheduled-runs.ts +51 -18
  83. package/src/runs/background/stale-run-reconciler.ts +35 -11
  84. package/src/runs/background/steering.ts +20 -2
  85. package/src/runs/background/subagent-runner.ts +441 -304
  86. package/src/runs/background/subagent-wait.ts +176 -28
  87. package/src/runs/background/wait-completions.ts +75 -27
  88. package/src/runs/background/wait-subscriptions.ts +9 -3
  89. package/src/runs/background/wait-tool.ts +5 -3
  90. package/src/runs/foreground/async-steering-action.ts +18 -7
  91. package/src/runs/foreground/async-stop-action.ts +93 -3
  92. package/src/runs/foreground/execution.ts +134 -248
  93. package/src/runs/foreground/foreground-history.ts +2 -1
  94. package/src/runs/foreground/prompt-audit.ts +3 -1
  95. package/src/runs/foreground/subagent-executor.ts +374 -178
  96. package/src/runs/foreground/workflow-detach-reconcile.ts +2 -0
  97. package/src/runs/foreground/workflow-foreground-steering.ts +2 -1
  98. package/src/runs/shared/acceptance.ts +38 -11
  99. package/src/runs/shared/async-status-projection.ts +127 -33
  100. package/src/runs/shared/capability-ceiling.ts +2 -0
  101. package/src/runs/shared/child-hooks.ts +25 -10
  102. package/src/runs/shared/child-launch-plan.ts +15 -3
  103. package/src/runs/shared/child-launch.ts +28 -5
  104. package/src/runs/shared/child-lifecycle.ts +6 -3
  105. package/src/runs/shared/child-runtime-config.ts +8 -1
  106. package/src/runs/shared/child-session.ts +127 -52
  107. package/src/runs/shared/child-tool-plan.ts +142 -11
  108. package/src/runs/shared/completion-guard.ts +5 -3
  109. package/src/runs/shared/dynamic-fanout.ts +2 -2
  110. package/src/runs/shared/effective-system-prompt.ts +33 -0
  111. package/src/runs/shared/external-cli-contract.ts +11 -1
  112. package/src/runs/shared/external-cli-preflight.ts +6 -2
  113. package/src/runs/shared/external-cli-runner.ts +9 -7
  114. package/src/runs/shared/herdr-connection.ts +134 -0
  115. package/src/runs/shared/herdr-external-adapters.ts +169 -0
  116. package/src/runs/shared/herdr-machine.ts +279 -0
  117. package/src/runs/shared/herdr-pi-protocol.ts +59 -0
  118. package/src/runs/shared/herdr-placed-run.ts +263 -0
  119. package/src/runs/shared/llm-intent-arbiter.ts +12 -3
  120. package/src/runs/shared/model-resolution-diagnostic.ts +76 -0
  121. package/src/runs/shared/{model-fallback.ts → model-resolution.ts} +22 -235
  122. package/src/runs/shared/model-scope.ts +1 -1
  123. package/src/runs/shared/nested-events.ts +11 -2
  124. package/src/runs/shared/orca-progress-tabs.ts +1 -1
  125. package/src/runs/shared/parallel-utils.ts +7 -2
  126. package/src/runs/shared/pi-spawn.ts +10 -0
  127. package/src/runs/shared/subagent-prompt-runtime.ts +12 -4
  128. package/src/runs/shared/task-intent.ts +46 -13
  129. package/src/runs/shared/workflow-async-child-guidance.ts +18 -0
  130. package/src/runs/shared/worktree-setup-command.ts +27 -4
  131. package/src/runs/shared/worktree.ts +45 -15
  132. package/src/shared/child-cache-retention.ts +43 -0
  133. package/src/shared/fork-context.ts +15 -72
  134. package/src/shared/launch-contract.ts +68 -8
  135. package/src/shared/opencode-session-headers.ts +30 -0
  136. package/src/shared/pruned-fork.ts +1 -1
  137. package/src/shared/required-child-extensions.ts +81 -0
  138. package/src/shared/settings.ts +5 -2
  139. package/src/shared/shortcuts.ts +0 -4
  140. package/src/shared/types.ts +74 -30
  141. package/src/slash/delegation-adapters.ts +3 -1
  142. package/src/slash/delegation-request.ts +14 -0
  143. package/src/slash/slash-commands.ts +2 -7
  144. package/src/slash/subagents-admin.ts +24 -13
  145. package/src/tui/fleet-status.ts +164 -19
  146. package/src/tui/fleet.ts +16 -14
  147. package/src/tui/render.ts +168 -37
  148. package/src/watchdog/child-status.ts +28 -28
  149. package/src/watchdog/lsp-diagnostics.ts +1 -1
  150. package/src/watchdog/model-selection.ts +21 -1
  151. package/src/watchdog/permission-arbiter.ts +3 -1
  152. package/src/watchdog/register-child.ts +10 -2
  153. package/src/watchdog/register-main.ts +39 -35
  154. package/src/watchdog/render.ts +1 -1
  155. package/src/watchdog/review.ts +123 -74
  156. package/src/watchdog/rules.ts +1 -1
  157. package/src/watchdog/runtime.ts +100 -27
  158. package/src/watchdog/scope.ts +1 -1
  159. package/src/watchdog/settings.ts +3 -0
  160. package/src/watchdog/tool-actions.ts +13 -12
  161. package/src/watchdog/turn-delta.ts +23 -0
  162. package/src/watchdog/types.ts +5 -3
  163. package/src/watchdog/warning-format.ts +1 -1
  164. package/src/workflows/scripted-workflow.ts +279 -10
  165. package/src/workflows/workflow-checklist.ts +2 -2
  166. package/src/workflows/workflow-receipt.ts +21 -3
  167. package/src/workflows/workflow-resources.ts +13 -2
  168. package/runner-server-preload.mjs +0 -13
  169. package/src/runs/shared/model-exclusions.ts +0 -374
  170. package/src/runs/shared/readonly-model-continuation.ts +0 -69
  171. package/src/runs/shared/readonly-session-evidence.ts +0 -307
  172. /package/src/inspectors/{herdr/shell-command.ts → shell-command.ts} +0 -0
@@ -1,307 +0,0 @@
1
- /** Internal, opt-in factory evidence. No runner enables continuation through this module. */
2
- import { createHash } from "node:crypto";
3
- import { lstatSync, readFileSync } from "node:fs";
4
- import { resolve } from "node:path";
5
- import { isDeepStrictEqual } from "node:util";
6
- import type { AgentSession, SessionEntry, SessionHeader } from "@earendil-works/pi-coding-agent";
7
- import type { ChildSession, ChildSessionLaunch, PiCodingAgentModule } from "./child-session.ts";
8
- import { captureReadonlyChildDrain, isReadonlyChildHookProfile, isReadonlyChildSessionReporting, observeReadonlyChildHookDrain } from "./child-hooks.ts";
9
-
10
- type Runtime = Awaited<ReturnType<PiCodingAgentModule["ModelRuntime"]["create"]>>;
11
- type Model = NonNullable<AgentSession["model"]>;
12
-
13
- export interface SettledReadonlyEvidence {
14
- readonly sessionFile: string;
15
- readonly sessionId: string;
16
- readonly leafId: string;
17
- readonly provider: string;
18
- readonly model: string;
19
- readonly api: string;
20
- readonly status: 429;
21
- /** Complete active context, not the host's attempt-only event projection. */
22
- readonly contextJson: string;
23
- readonly completedToolResults: number;
24
- readonly fileDigest: string;
25
- }
26
-
27
- const requested = new WeakMap<ChildSessionLaunch, SettledReadonlyEvidence | null>();
28
- const receipts = new WeakMap<ChildSession, SettledReadonlyEvidence>();
29
- // Opaque configured-provider continuity, never an implementation-origin attestation.
30
- const providers = new WeakMap<SettledReadonlyEvidence, object>();
31
-
32
- /** Internal integration seam: opt in before factory creation. Ordinary launches do no evidence I/O. */
33
- export function requestReadonlySessionEvidence(launch: ChildSessionLaunch, expected?: SettledReadonlyEvidence): void {
34
- requested.set(launch, expected ?? null);
35
- }
36
-
37
- /** Only the default factory can produce a receipt; duck-typed fake child properties are ignored. */
38
- export function getReadonlySessionEvidence(child: ChildSession): SettledReadonlyEvidence | undefined {
39
- return receipts.get(child);
40
- }
41
-
42
- function digest(bytes: string): string {
43
- return createHash("sha256").update(bytes).digest("hex");
44
- }
45
-
46
- function absent(file: string): boolean {
47
- try { lstatSync(file); return false; }
48
- catch (error) { return (error as NodeJS.ErrnoException).code === "ENOENT"; }
49
- }
50
-
51
- function sameStored(disk: unknown, live: unknown): boolean {
52
- // The SDK keeps optional undefined fields in memory which JSONL intentionally omits.
53
- return isDeepStrictEqual(disk, JSON.parse(JSON.stringify(live)));
54
- }
55
-
56
- /** Must run before opening a retained file. Never repairs, truncates, or creates it. */
57
- export function validateReadonlySessionCheckpoint(evidence: SettledReadonlyEvidence): boolean {
58
- if (!providers.has(evidence)) return false;
59
- try { return digest(readFileSync(evidence.sessionFile, "utf8")) === evidence.fileDigest; }
60
- catch { return false; }
61
- }
62
-
63
- function readHistory(file: string): { header: SessionHeader; entries: SessionEntry[]; bytes: string } {
64
- const bytes = readFileSync(file, "utf8");
65
- if (!bytes.endsWith("\n")) throw new Error("Incomplete session file");
66
- const rows = bytes.slice(0, -1).split("\n").map((line) => JSON.parse(line));
67
- const [header, ...entries] = rows;
68
- if (header?.type !== "session" || header.version !== 3 || typeof header.id !== "string" || !header.id || typeof header.cwd !== "string" || !entries.length) throw new Error("Unsupported session header");
69
- const ids = new Set<string>();
70
- for (const entry of entries) {
71
- if (!entry || typeof entry.id !== "string" || !entry.id || ids.has(entry.id) || typeof entry.type !== "string" || typeof entry.timestamp !== "string" || (entry.parentId !== null && !ids.has(entry.parentId))) throw new Error("Broken session chain");
72
- ids.add(entry.id);
73
- }
74
- return { header, entries, bytes };
75
- }
76
-
77
- function activeMessages(branch: SessionEntry[]): AgentSession["messages"] {
78
- const messages = branch.flatMap((entry) => {
79
- if (entry.type === "message") return [entry.message];
80
- // Compaction/branch summaries and custom context need their own proof; do not reconstruct them.
81
- if (!["model_change", "thinking_level_change", "session_info", "label"].includes(entry.type)) throw new Error("Unsupported active context");
82
- return [];
83
- });
84
- // Snapshot the persisted representation, not mutable SDK message references.
85
- return JSON.parse(JSON.stringify(messages));
86
- }
87
-
88
- function completedResults(messages: AgentSession["messages"]): number {
89
- const pending = new Map<string, string>();
90
- const seen = new Set<string>();
91
- let completed = 0;
92
- for (const message of messages) {
93
- if (message.role === "toolResult") {
94
- if (typeof message.toolCallId !== "string" || !pending.has(message.toolCallId) || pending.get(message.toolCallId) !== message.toolName || !Array.isArray(message.content)) throw new Error("Unmatched tool result");
95
- pending.delete(message.toolCallId);
96
- if (!message.isError && message.content.length) completed++;
97
- } else {
98
- if (pending.size) throw new Error("Unresolved tool call");
99
- if (message.role === "assistant") {
100
- if (!Array.isArray(message.content)) throw new Error("Invalid assistant content");
101
- for (const block of message.content) {
102
- if (block.type !== "toolCall") continue;
103
- if (!block.id || typeof block.id !== "string" || seen.has(block.id) || !["read", "ls"].includes(block.name)) throw new Error("Uncertified tool call");
104
- seen.add(block.id);
105
- pending.set(block.id, block.name);
106
- }
107
- } else if (message.role !== "user") throw new Error("Unsupported context message");
108
- }
109
- }
110
- if (pending.size) throw new Error("Unresolved tool call");
111
- return completed;
112
- }
113
-
114
- function eligibleLaunch(launch: ChildSessionLaunch): boolean {
115
- const r = launch.runtime;
116
- return launch.ambientExtensions === false && !launch.extensionPaths.length
117
- && isReadonlyChildSessionReporting(launch)
118
- && (!launch.hooks.length || (launch.storage.kind === "file" && isReadonlyChildHookProfile(launch.hooks, r)))
119
- && launch.tools !== undefined && launch.tools.every((tool) => tool === "read" || tool === "ls")
120
- && !r.toolBudget && !r.permissions && !r.childWatchdog && !r.watchdogStatus && !r.structuredOutput
121
- && !r.waitTool.enabled && !r.fanoutChild && !r.fast && !r.nestedRoute && !r.nestedParent && !r.runFanoutBudget
122
- && !r.supervisorChannelDir && !r.mcpDirectTools?.length;
123
- }
124
-
125
- export interface ReadonlyEvidenceObserver {
126
- start(): void;
127
- settled(): void;
128
- invalidate(): void;
129
- beforeShutdown(): void;
130
- finish(child: ChildSession): void;
131
- }
132
-
133
- /** Called only on the explicit opt-in path, before SessionManager.open can permissively repair input. */
134
- export function prepareReadonlySessionEvidence(launch: ChildSessionLaunch): { loadingHooks(enabled: boolean): void; beforeOpen(): void; opened(manager: AgentSession["sessionManager"]): void; observe(pi: PiCodingAgentModule, runtime: Runtime, session: AgentSession): ReadonlyEvidenceObserver | undefined } | undefined {
135
- if (!requested.has(launch)) return undefined;
136
- const expected = requested.get(launch);
137
- requested.delete(launch);
138
- const deny = (): undefined => {
139
- if (expected) throw new Error("Unsupported read-only continuation session");
140
- return undefined;
141
- };
142
- const checkExpected = (): void => {
143
- if (expected && (launch.storage.kind !== "file" || resolve(launch.storage.sessionFile) !== expected.sessionFile || !validateReadonlySessionCheckpoint(expected))) throw new Error("Read-only continuation checkpoint changed");
144
- };
145
- checkExpected();
146
- if (!eligibleLaunch(launch) || launch.storage.kind !== "file") return deny();
147
- const file = resolve(launch.storage.sessionFile);
148
- // Only an initially absent *assigned exact file* may use SDK initialization.
149
- // Empty files, dangling symlinks, inaccessible and corrupt inputs are not fresh.
150
- let fresh = !expected && absent(file);
151
- let initialHeader: SessionHeader | undefined;
152
- try { if (!fresh) initialHeader = readHistory(file).header; }
153
- catch { return deny(); }
154
- const beforeOpen = (): void => {
155
- checkExpected();
156
- if (fresh) fresh = absent(file);
157
- };
158
- const opened = (manager: AgentSession["sessionManager"]): void => {
159
- if (fresh && manager.getSessionFile() === file && manager.getEntries().length === 0 && manager.getLeafId() === null) {
160
- // Capture the real SDK-generated identity immediately after open, before hooks.
161
- initialHeader = JSON.parse(JSON.stringify(manager.getHeader()));
162
- }
163
- };
164
- const observe = (pi: PiCodingAgentModule, runtime: Runtime, session: AgentSession): ReadonlyEvidenceObserver | undefined => {
165
- const drainSettled = captureReadonlyChildDrain(launch.hooks);
166
- // A deliberately tested capability floor, not an attestation of installed code origin.
167
- if (!initialHeader || pi.VERSION !== "0.85.1" || !session.model || !session.agent.streamFunction) return deny();
168
- const header = initialHeader;
169
- const model = { ...session.model };
170
- const provider = runtime.getProvider(model.provider);
171
- if (expected && provider !== providers.get(expected)) return deny();
172
- // Coverage is namespace/API plus the observed POST topology below, not builtin provider origin.
173
- const supported = (m: Model): boolean => m.provider === "baseten" && m.api === "openai-completions"
174
- && runtime.getProvider(m.provider) === provider
175
- && !runtime.getRegisteredNativeProvider(m.provider) && !runtime.getRegisteredProviderConfig(m.provider);
176
- const allowed = launch.tools!.filter((name) => !launch.excludeTools?.includes(name)).sort();
177
- const toolsMatch = (): boolean => {
178
- const tools = session.getAllTools();
179
- return isDeepStrictEqual(session.getActiveToolNames().sort(), allowed)
180
- && isDeepStrictEqual(tools.map((tool) => tool.name).sort(), allowed)
181
- && tools.every((tool) => tool.sourceInfo?.source === "builtin");
182
- };
183
- const idle = (): boolean => session.isIdle && !session.isRetrying && !session.agent.hasQueuedMessages()
184
- && session.agent.state.pendingToolCalls.size === 0 && !session.hasPendingBashMessages;
185
- const continuity = (): boolean => eligibleLaunch(launch) && toolsMatch() && session.model !== undefined
186
- && session.model.id === model.id && session.model.provider === model.provider && session.model.api === model.api && supported(session.model);
187
- const history = () => {
188
- const disk = readHistory(file);
189
- const manager = session.sessionManager;
190
- const leaf = manager.getLeafId();
191
- if (session.sessionFile !== file || session.sessionId !== header.id || !leaf || leaf !== disk.entries.at(-1)?.id
192
- || !isDeepStrictEqual(disk.header, header) || !sameStored(disk.header, manager.getHeader())
193
- || !sameStored(disk.entries, manager.getEntries())) throw new Error("Session checkpoint mismatch");
194
- // The disk chain and live entries already agree; use the SDK's raw branch walk.
195
- const messages = activeMessages(manager.getBranch());
196
- if (!sameStored(messages, session.messages)) throw new Error("Incomplete active context");
197
- return { ...disk, messages, leaf, completed: completedResults(messages) };
198
- };
199
- const startingHistory = () => {
200
- // SDK defers persistence until its first assistant response. This exception
201
- // is for the empty source baseline only; settlement always reads strict disk history.
202
- if (fresh && absent(file)) {
203
- const manager = session.sessionManager;
204
- if (session.sessionFile !== file || session.sessionId !== header.id || !sameStored(header, manager.getHeader())
205
- || session.messages.length
206
- || manager.getEntries().some((entry) => !["model_change", "thinking_level_change", "session_info", "label"].includes(entry.type))) throw new Error("Fresh session initialization changed");
207
- return { messages: [] as AgentSession["messages"], completed: 0 };
208
- }
209
- return history();
210
- };
211
- if (!provider || !eligibleLaunch(launch) || !supported(model) || !toolsMatch() || !idle()) return deny();
212
- try {
213
- const restored = startingHistory();
214
- if (expected && (model.provider !== expected.provider || JSON.stringify(restored.messages) !== expected.contextJson)) throw new Error("Restored continuation context changed");
215
- } catch (error) {
216
- if (expected) throw error;
217
- return undefined;
218
- }
219
- let invalid = false;
220
- let phase: "new" | "running" | "settled" = "new";
221
- let owner: ChildSession | undefined;
222
- let baseline = 0;
223
- let before: AgentSession["messages"] = [];
224
- let inFlight = 0;
225
- type Invocation = { status?: number; pending: number; ambiguous: boolean; result?: Awaited<ReturnType<Awaited<ReturnType<typeof session.agent.streamFunction>>["result"]>> };
226
- let latest: Invocation | undefined;
227
- const original = session.agent.streamFunction;
228
- const wrapped: typeof original = async (m, context, options) => {
229
- const invocation: Invocation = { pending: 0, ambiguous: false };
230
- latest = invocation;
231
- if (inFlight++ || phase !== "running" || !eligibleLaunch(launch) || !supported(m) || m.id !== model.id || !toolsMatch()) invalid = true;
232
- if (expected && (!continuity() || session.agent.streamFunction !== wrapped)) invalid = true;
233
- // The repository's older agent-core types omit this documented 0.85.1 request option.
234
- const transport = (options as (typeof options & { fetch?: typeof fetch }))?.fetch ?? globalThis.fetch;
235
- const observedFetch: typeof fetch = async (...args) => {
236
- invocation.status = undefined; // Starting even a rejected retry invalidates any earlier 429.
237
- if (invocation.pending++ || invocation.result) invocation.ambiguous = true;
238
- try {
239
- try {
240
- const [input, init] = args;
241
- const url = new URL(input instanceof Request ? input.url : String(input));
242
- // Only the tested completions POST topology. Radius /messages and auxiliary requests deny.
243
- if (init?.method !== "POST" || !url.pathname.endsWith("/chat/completions") || typeof init.body !== "string") invocation.ambiguous = true;
244
- } catch { invocation.ambiguous = true; }
245
- const response = await transport(...args);
246
- invocation.status = response.status;
247
- return response;
248
- } catch (error) {
249
- invocation.status = undefined;
250
- throw error;
251
- } finally { invocation.pending--; }
252
- };
253
- try {
254
- // Guarded continuations must veto known changes before entering configured SDK dispatch.
255
- if (expected && invalid) throw new Error("Read-only continuation changed before dispatch");
256
- const stream = await original.call(session.agent, m, context, { ...options, fetch: observedFetch });
257
- // Observe result without consuming or replacing the event stream.
258
- void stream.result().then((result) => {
259
- invocation.result = result;
260
- inFlight--;
261
- }, () => { invalid = true; inFlight--; });
262
- return stream;
263
- } catch (error) { invalid = true; inFlight--; throw error; }
264
- };
265
- session.agent.streamFunction = wrapped;
266
- return {
267
- start() {
268
- if (owner) receipts.delete(owner);
269
- if (phase !== "new" || !idle()) invalid = true;
270
- if (expected && (!continuity() || session.agent.streamFunction !== wrapped)) invalid = true;
271
- phase = "running";
272
- latest = undefined;
273
- try {
274
- const h = startingHistory(); baseline = h.completed; before = h.messages;
275
- if (expected && JSON.stringify(h.messages) !== expected.contextJson) invalid = true;
276
- } catch { invalid = true; }
277
- if (expected && invalid) throw new Error("Read-only continuation changed before prompt");
278
- },
279
- settled() { phase = "settled"; },
280
- invalidate() { invalid = true; if (owner) receipts.delete(owner); },
281
- beforeShutdown() {
282
- // Only the identity-certified prompt runtime may finalize owned acknowledgements.
283
- if (!launch.hooks.length || !continuity()) invalid = true;
284
- },
285
- finish(child) {
286
- owner = child;
287
- if (!drainSettled()) return;
288
- if (invalid || child.detached || child.shutDown || phase !== "settled" || inFlight || !eligibleLaunch(launch) || !idle() || !supported(model) || !toolsMatch() || session.agent.streamFunction !== wrapped
289
- || session.model?.id !== model.id || session.model.provider !== model.provider || session.model.api !== model.api) return;
290
- const invocation = latest;
291
- const terminal = invocation?.result;
292
- if (!invocation || invocation.ambiguous || invocation.pending || invocation.status !== 429 || terminal?.stopReason !== "error"
293
- || terminal.content.length || terminal.provider !== model.provider || terminal.model !== model.id || terminal.api !== model.api) return;
294
- try {
295
- const h = history();
296
- if (h.completed <= baseline || !isDeepStrictEqual(h.messages.slice(0, before.length), before) || !sameStored(h.messages.at(-1), terminal)) return;
297
- const receipt: SettledReadonlyEvidence = Object.freeze({ sessionFile: file, sessionId: session.sessionId, leafId: h.leaf,
298
- provider: model.provider, model: model.id, api: model.api, status: 429, contextJson: JSON.stringify(h.messages),
299
- completedToolResults: h.completed, fileDigest: digest(h.bytes) });
300
- providers.set(receipt, provider);
301
- receipts.set(child, receipt);
302
- } catch { /* Strict checkpoint failures only deny evidence; never repair storage. */ }
303
- },
304
- };
305
- };
306
- return { beforeOpen, opened, observe, loadingHooks: (enabled) => observeReadonlyChildHookDrain(launch.hooks, enabled, file) };
307
- }