@oh-my-pi/pi-coding-agent 15.3.2 → 15.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/dist/types/cli/file-processor.d.ts +1 -1
  3. package/dist/types/config/settings-schema.d.ts +45 -3
  4. package/dist/types/config/settings.d.ts +1 -1
  5. package/dist/types/debug/raw-sse.d.ts +2 -0
  6. package/dist/types/edit/file-read-cache.d.ts +15 -4
  7. package/dist/types/edit/index.d.ts +3 -8
  8. package/dist/types/edit/renderer.d.ts +1 -2
  9. package/dist/types/eval/__tests__/shared-executors.test.d.ts +1 -0
  10. package/dist/types/eval/js/shared/local-module-loader.d.ts +16 -0
  11. package/dist/types/eval/js/shared/rewrite-imports.d.ts +4 -0
  12. package/dist/types/eval/js/shared/runtime.d.ts +14 -8
  13. package/dist/types/eval/py/executor.d.ts +1 -2
  14. package/dist/types/eval/py/kernel.d.ts +6 -0
  15. package/dist/types/eval/py/tool-bridge.d.ts +1 -5
  16. package/dist/types/eval/session-id.d.ts +3 -0
  17. package/dist/types/extensibility/extensions/types.d.ts +1 -3
  18. package/dist/types/hashline/anchors.d.ts +15 -9
  19. package/dist/types/hashline/constants.d.ts +0 -2
  20. package/dist/types/hashline/diff.d.ts +1 -2
  21. package/dist/types/hashline/executor.d.ts +52 -0
  22. package/dist/types/hashline/hash.d.ts +44 -93
  23. package/dist/types/hashline/index.d.ts +2 -1
  24. package/dist/types/hashline/input.d.ts +2 -9
  25. package/dist/types/hashline/recovery.d.ts +3 -9
  26. package/dist/types/hashline/tokenizer.d.ts +91 -0
  27. package/dist/types/hashline/types.d.ts +5 -7
  28. package/dist/types/modes/components/extensions/types.d.ts +0 -4
  29. package/dist/types/modes/types.d.ts +1 -0
  30. package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
  31. package/dist/types/sdk.d.ts +2 -0
  32. package/dist/types/session/agent-session.d.ts +11 -15
  33. package/dist/types/session/agent-storage.d.ts +11 -10
  34. package/dist/types/slash-commands/acp-builtins.d.ts +3 -3
  35. package/dist/types/slash-commands/types.d.ts +0 -5
  36. package/dist/types/task/executor.d.ts +2 -0
  37. package/dist/types/tool-discovery/tool-index.d.ts +0 -50
  38. package/dist/types/tools/index.d.ts +2 -8
  39. package/dist/types/tools/match-line-format.d.ts +4 -4
  40. package/dist/types/tools/output-schema-validator.d.ts +64 -0
  41. package/dist/types/tools/review.d.ts +13 -0
  42. package/dist/types/tools/search-tool-bm25.d.ts +1 -1
  43. package/dist/types/tools/search.d.ts +4 -3
  44. package/dist/types/utils/edit-mode.d.ts +1 -1
  45. package/dist/types/web/kagi.d.ts +4 -2
  46. package/dist/types/web/parallel.d.ts +4 -3
  47. package/dist/types/web/scrapers/types.d.ts +2 -1
  48. package/dist/types/web/search/index.d.ts +12 -4
  49. package/dist/types/web/search/provider.d.ts +2 -1
  50. package/dist/types/web/search/providers/anthropic.d.ts +9 -4
  51. package/dist/types/web/search/providers/base.d.ts +34 -2
  52. package/dist/types/web/search/providers/brave.d.ts +8 -1
  53. package/dist/types/web/search/providers/codex.d.ts +13 -9
  54. package/dist/types/web/search/providers/exa.d.ts +10 -1
  55. package/dist/types/web/search/providers/gemini.d.ts +20 -23
  56. package/dist/types/web/search/providers/jina.d.ts +2 -1
  57. package/dist/types/web/search/providers/kagi.d.ts +4 -1
  58. package/dist/types/web/search/providers/kimi.d.ts +10 -1
  59. package/dist/types/web/search/providers/parallel.d.ts +3 -2
  60. package/dist/types/web/search/providers/perplexity.d.ts +5 -2
  61. package/dist/types/web/search/providers/searxng.d.ts +2 -1
  62. package/dist/types/web/search/providers/synthetic.d.ts +5 -8
  63. package/dist/types/web/search/providers/tavily.d.ts +11 -4
  64. package/dist/types/web/search/providers/utils.d.ts +8 -6
  65. package/dist/types/web/search/providers/zai.d.ts +12 -3
  66. package/package.json +7 -7
  67. package/src/cli/file-processor.ts +12 -2
  68. package/src/cli.ts +0 -8
  69. package/src/commands/commit.ts +8 -8
  70. package/src/config/prompt-templates.ts +6 -6
  71. package/src/config/settings-schema.ts +47 -3
  72. package/src/config/settings.ts +5 -5
  73. package/src/debug/raw-sse.ts +68 -3
  74. package/src/edit/file-read-cache.ts +68 -25
  75. package/src/edit/index.ts +6 -37
  76. package/src/edit/renderer.ts +9 -47
  77. package/src/edit/streaming.ts +43 -56
  78. package/src/eval/__tests__/shared-executors.test.ts +520 -0
  79. package/src/eval/js/context-manager.ts +64 -53
  80. package/src/eval/js/shared/local-module-loader.ts +265 -0
  81. package/src/eval/js/shared/prelude.txt +4 -0
  82. package/src/eval/js/shared/rewrite-imports.ts +85 -0
  83. package/src/eval/js/shared/runtime.ts +129 -86
  84. package/src/eval/js/worker-core.ts +23 -38
  85. package/src/eval/py/executor.ts +155 -84
  86. package/src/eval/py/kernel.ts +10 -1
  87. package/src/eval/py/prelude.py +22 -24
  88. package/src/eval/py/runner.py +203 -85
  89. package/src/eval/py/tool-bridge.ts +17 -10
  90. package/src/eval/session-id.ts +8 -0
  91. package/src/exec/bash-executor.ts +27 -16
  92. package/src/extensibility/extensions/runner.ts +0 -1
  93. package/src/extensibility/extensions/types.ts +1 -3
  94. package/src/hashline/anchors.ts +56 -65
  95. package/src/hashline/apply.ts +29 -31
  96. package/src/hashline/constants.ts +0 -3
  97. package/src/hashline/diff-preview.ts +4 -5
  98. package/src/hashline/diff.ts +30 -4
  99. package/src/hashline/execute.ts +91 -26
  100. package/src/hashline/executor.ts +239 -0
  101. package/src/hashline/grammar.lark +12 -10
  102. package/src/hashline/hash.ts +69 -114
  103. package/src/hashline/index.ts +2 -1
  104. package/src/hashline/input.ts +48 -41
  105. package/src/hashline/prefixes.ts +21 -11
  106. package/src/hashline/recovery.ts +63 -71
  107. package/src/hashline/stream.ts +2 -2
  108. package/src/hashline/tokenizer.ts +467 -0
  109. package/src/hashline/types.ts +6 -8
  110. package/src/internal-urls/docs-index.generated.ts +7 -7
  111. package/src/modes/components/extensions/types.ts +0 -5
  112. package/src/modes/components/session-observer-overlay.ts +11 -2
  113. package/src/modes/components/tree-selector.ts +10 -2
  114. package/src/modes/controllers/command-controller.ts +1 -3
  115. package/src/modes/controllers/extension-ui-controller.ts +10 -11
  116. package/src/modes/controllers/selector-controller.ts +5 -5
  117. package/src/modes/types.ts +4 -1
  118. package/src/modes/utils/ui-helpers.ts +4 -0
  119. package/src/prompts/agents/explore.md +1 -1
  120. package/src/prompts/tools/ast-edit.md +1 -1
  121. package/src/prompts/tools/ast-grep.md +1 -1
  122. package/src/prompts/tools/eval.md +1 -1
  123. package/src/prompts/tools/hashline.md +73 -94
  124. package/src/prompts/tools/read.md +4 -4
  125. package/src/prompts/tools/search.md +3 -3
  126. package/src/sdk.ts +17 -23
  127. package/src/session/agent-session.ts +59 -66
  128. package/src/session/agent-storage.ts +13 -14
  129. package/src/slash-commands/acp-builtins.ts +3 -3
  130. package/src/slash-commands/types.ts +0 -6
  131. package/src/task/executor.ts +26 -57
  132. package/src/task/index.ts +8 -4
  133. package/src/tool-discovery/tool-index.ts +0 -134
  134. package/src/tools/ast-edit.ts +36 -13
  135. package/src/tools/ast-grep.ts +45 -4
  136. package/src/tools/browser/tab-worker.ts +3 -2
  137. package/src/tools/eval.ts +2 -1
  138. package/src/tools/fetch.ts +23 -14
  139. package/src/tools/index.ts +2 -8
  140. package/src/tools/irc.ts +59 -5
  141. package/src/tools/match-line-format.ts +5 -7
  142. package/src/tools/output-schema-validator.ts +132 -0
  143. package/src/tools/read.ts +142 -31
  144. package/src/tools/review.ts +23 -0
  145. package/src/tools/search-tool-bm25.ts +3 -30
  146. package/src/tools/search.ts +48 -16
  147. package/src/tools/write.ts +3 -3
  148. package/src/tools/yield.ts +32 -41
  149. package/src/utils/edit-mode.ts +1 -2
  150. package/src/utils/file-mentions.ts +2 -2
  151. package/src/web/kagi.ts +15 -6
  152. package/src/web/parallel.ts +9 -6
  153. package/src/web/scrapers/types.ts +7 -1
  154. package/src/web/scrapers/youtube.ts +13 -7
  155. package/src/web/search/index.ts +37 -11
  156. package/src/web/search/provider.ts +5 -3
  157. package/src/web/search/providers/anthropic.ts +30 -21
  158. package/src/web/search/providers/base.ts +35 -2
  159. package/src/web/search/providers/brave.ts +4 -4
  160. package/src/web/search/providers/codex.ts +118 -89
  161. package/src/web/search/providers/exa.ts +3 -2
  162. package/src/web/search/providers/gemini.ts +58 -155
  163. package/src/web/search/providers/jina.ts +4 -4
  164. package/src/web/search/providers/kagi.ts +17 -11
  165. package/src/web/search/providers/kimi.ts +29 -13
  166. package/src/web/search/providers/parallel.ts +171 -23
  167. package/src/web/search/providers/perplexity.ts +38 -37
  168. package/src/web/search/providers/searxng.ts +3 -1
  169. package/src/web/search/providers/synthetic.ts +16 -19
  170. package/src/web/search/providers/tavily.ts +23 -18
  171. package/src/web/search/providers/utils.ts +11 -17
  172. package/src/web/search/providers/zai.ts +16 -8
  173. package/dist/types/hashline/parser.d.ts +0 -7
  174. package/dist/types/mcp/discoverable-tool-metadata.d.ts +0 -7
  175. package/dist/types/tools/vim.d.ts +0 -58
  176. package/dist/types/vim/buffer.d.ts +0 -41
  177. package/dist/types/vim/commands.d.ts +0 -6
  178. package/dist/types/vim/engine.d.ts +0 -47
  179. package/dist/types/vim/parser.d.ts +0 -3
  180. package/dist/types/vim/render.d.ts +0 -25
  181. package/dist/types/vim/types.d.ts +0 -182
  182. package/src/hashline/parser.ts +0 -246
  183. package/src/mcp/discoverable-tool-metadata.ts +0 -24
  184. package/src/prompts/tools/vim.md +0 -98
  185. package/src/tools/vim.ts +0 -949
  186. package/src/vim/buffer.ts +0 -309
  187. package/src/vim/commands.ts +0 -382
  188. package/src/vim/engine.ts +0 -2409
  189. package/src/vim/parser.ts +0 -134
  190. package/src/vim/render.ts +0 -252
  191. package/src/vim/types.ts +0 -197
package/src/sdk.ts CHANGED
@@ -51,6 +51,7 @@ import "./discovery";
51
51
  import { resolveConfigValue } from "./config/resolve-config-value";
52
52
  import { initializeWithSettings } from "./discovery";
53
53
  import { disposeAllKernelSessions, disposeKernelSessionsByOwner } from "./eval/py/executor";
54
+ import { defaultEvalSessionId } from "./eval/session-id";
54
55
  import { TtsrManager } from "./export/ttsr";
55
56
  import {
56
57
  type CustomCommandsLoadResult,
@@ -84,11 +85,7 @@ import type { HindsightSessionState } from "./hindsight/state";
84
85
  import { LocalProtocolHandler, type LocalProtocolOptions } from "./internal-urls";
85
86
  import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "./lsp/startup-events";
86
87
  import { discoverAndLoadMCPTools, MCPManager, type MCPToolsLoadResult } from "./mcp";
87
- import {
88
- collectDiscoverableMCPTools,
89
- formatDiscoverableMCPToolServerSummary,
90
- selectDiscoverableMCPToolNamesByServer,
91
- } from "./mcp/discoverable-tool-metadata";
88
+
92
89
  import { resolveMemoryBackend } from "./memory-backend";
93
90
  import asyncResultTemplate from "./prompts/tools/async-result.md" with { type: "text" };
94
91
  import { AgentRegistry, MAIN_AGENT_ID } from "./registry/agent-registry";
@@ -117,6 +114,9 @@ import { parseThinkingLevel, resolveThinkingLevelForModel, toReasoningEffort } f
117
114
  import {
118
115
  collectDiscoverableTools,
119
116
  type DiscoverableTool,
117
+ filterBySource,
118
+ formatDiscoverableToolServerSummary,
119
+ selectDiscoverableToolNamesByServer,
120
120
  summarizeDiscoverableTools,
121
121
  } from "./tool-discovery/tool-index";
122
122
  import {
@@ -318,6 +318,8 @@ export interface CreateAgentSessionOptions {
318
318
  agentRegistry?: AgentRegistry;
319
319
  /** Parent task ID prefix for nested artifact naming (e.g., "6-Extensions") */
320
320
  parentTaskPrefix?: string;
321
+ /** Inherited eval executor session id for subagents sharing parent eval state. */
322
+ parentEvalSessionId?: string;
321
323
 
322
324
  /** Session manager. Default: session stored under the configured agentDir sessions root */
323
325
  sessionManager?: SessionManager;
@@ -1177,6 +1179,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1177
1179
  taskDepth: options.taskDepth ?? 0,
1178
1180
  getSessionFile: () => sessionManager.getSessionFile() ?? null,
1179
1181
  getEvalKernelOwnerId: () => evalKernelOwnerId,
1182
+ getEvalSessionId: () =>
1183
+ session?.getEvalSessionId() ?? options.parentEvalSessionId ?? defaultEvalSessionId(toolSession),
1180
1184
  assertEvalExecutionAllowed: () => session?.assertEvalExecutionAllowed(),
1181
1185
  trackEvalExecution: (execution, abortController) =>
1182
1186
  session ? session.trackEvalExecution(execution, abortController) : execution,
@@ -1196,8 +1200,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1196
1200
  getTodoPhases: () => session.getTodoPhases(),
1197
1201
  setTodoPhases: phases => session.setTodoPhases(phases),
1198
1202
  isMCPDiscoveryEnabled: () => session.isMCPDiscoveryEnabled(),
1199
- getDiscoverableMCPTools: () => session.getDiscoverableMCPTools(),
1200
- getDiscoverableMCPSearchIndex: () => session.getDiscoverableMCPSearchIndex(),
1201
1203
  getSelectedMCPToolNames: () => session.getSelectedMCPToolNames(),
1202
1204
  activateDiscoveredMCPTools: toolNames => session.activateDiscoveredMCPTools(toolNames),
1203
1205
  // Generic tool discovery (unified — covers built-in + MCP + extension)
@@ -1581,7 +1583,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1581
1583
  tools: Map<string, AgentTool>,
1582
1584
  ): Promise<BuildSystemPromptResult> => {
1583
1585
  toolContextStore.setToolNames(toolNames);
1584
- const discoverableMCPTools = mcpDiscoveryEnabled ? collectDiscoverableMCPTools(tools.values()) : [];
1586
+ const discoverableMCPTools: DiscoverableTool[] = mcpDiscoveryEnabled
1587
+ ? filterBySource(collectDiscoverableTools(tools.values()), "mcp")
1588
+ : [];
1585
1589
  const activeToolNames = new Set(toolNames);
1586
1590
  const discoverableBuiltinTools: DiscoverableTool[] =
1587
1591
  effectiveDiscoveryMode === "all"
@@ -1592,18 +1596,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1592
1596
  { source: "builtin" },
1593
1597
  )
1594
1598
  : [];
1595
- const discoverableToolsForDesc: DiscoverableTool[] = [
1596
- ...discoverableBuiltinTools,
1597
- ...discoverableMCPTools.map(t => ({
1598
- name: t.name,
1599
- label: t.label,
1600
- summary: t.description,
1601
- source: "mcp" as const,
1602
- serverName: t.serverName,
1603
- mcpToolName: t.mcpToolName,
1604
- schemaKeys: t.schemaKeys,
1605
- })),
1606
- ];
1599
+ const discoverableToolsForDesc: DiscoverableTool[] = [...discoverableBuiltinTools, ...discoverableMCPTools];
1607
1600
  const discoverableToolSummary = summarizeDiscoverableTools(discoverableToolsForDesc);
1608
1601
  const hasDiscoverableTools =
1609
1602
  mcpDiscoveryEnabled && toolNames.includes("search_tool_bm25") && discoverableToolsForDesc.length > 0;
@@ -1647,7 +1640,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1647
1640
  repeatToolDescriptions,
1648
1641
  intentField,
1649
1642
  mcpDiscoveryMode: hasDiscoverableTools,
1650
- mcpDiscoveryServerSummaries: discoverableToolSummary.servers.map(formatDiscoverableMCPToolServerSummary),
1643
+ mcpDiscoveryServerSummaries: discoverableToolSummary.servers.map(formatDiscoverableToolServerSummary),
1651
1644
  eagerTasks,
1652
1645
  secretsEnabled,
1653
1646
  workspaceTree: workspaceTreePromise,
@@ -1693,8 +1686,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1693
1686
  (settings.get("mcp.discoveryDefaultServers") ?? []).map(serverName => serverName.trim()).filter(Boolean),
1694
1687
  );
1695
1688
  const discoveryDefaultServerToolNames = mcpDiscoveryEnabled
1696
- ? selectDiscoverableMCPToolNamesByServer(
1697
- collectDiscoverableMCPTools(toolRegistry.values()),
1689
+ ? selectDiscoverableToolNamesByServer(
1690
+ filterBySource(collectDiscoverableTools(toolRegistry.values()), "mcp"),
1698
1691
  discoveryDefaultServers,
1699
1692
  )
1700
1693
  : [];
@@ -1994,6 +1987,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1994
1987
  agentId: resolvedAgentId,
1995
1988
  agentRegistry,
1996
1989
  providerSessionId: options.providerSessionId,
1990
+ parentEvalSessionId: options.parentEvalSessionId,
1997
1991
  });
1998
1992
  hasSession = true;
1999
1993
  if (asyncJobManager) {
@@ -108,6 +108,7 @@ import {
108
108
  executePython as executePythonCommand,
109
109
  type PythonResult,
110
110
  } from "../eval/py/executor";
111
+ import { defaultEvalSessionId } from "../eval/session-id";
111
112
  import { type BashResult, executeBash as executeBashCommand } from "../exec/bash-executor";
112
113
  import { exportSessionToHtml } from "../export/html";
113
114
  import type { TtsrManager, TtsrMatchContext } from "../export/ttsr";
@@ -141,14 +142,6 @@ import { GoalRuntime } from "../goals/runtime";
141
142
  import type { Goal, GoalModeState } from "../goals/state";
142
143
  import type { HindsightSessionState } from "../hindsight/state";
143
144
  import { type LocalProtocolOptions, resolveLocalUrlToPath } from "../internal-urls";
144
- import {
145
- buildDiscoverableMCPSearchIndex,
146
- collectDiscoverableMCPTools,
147
- type DiscoverableMCPSearchIndex,
148
- type DiscoverableMCPTool,
149
- isMCPToolName,
150
- selectDiscoverableMCPToolNamesByServer,
151
- } from "../mcp/discoverable-tool-metadata";
152
145
  import { resolveMemoryBackend } from "../memory-backend";
153
146
  import { getCurrentThemeName, theme } from "../modes/theme/theme";
154
147
  import type { PlanModeState } from "../plan-mode/state";
@@ -171,6 +164,9 @@ import {
171
164
  collectDiscoverableTools,
172
165
  type DiscoverableTool,
173
166
  type DiscoverableToolSearchIndex,
167
+ filterBySource,
168
+ isMCPToolName,
169
+ selectDiscoverableToolNamesByServer,
174
170
  } from "../tool-discovery/tool-index";
175
171
  import { assertEditableFile } from "../tools/auto-generated-guard";
176
172
  import type { CheckpointState } from "../tools/checkpoint";
@@ -312,6 +308,8 @@ export interface AgentSessionConfig {
312
308
  ttsrManager?: TtsrManager;
313
309
  /** Secret obfuscator for deobfuscating streaming edit content */
314
310
  obfuscator?: SecretObfuscator;
311
+ /** Inherited eval executor session id from a parent agent. */
312
+ parentEvalSessionId?: string;
315
313
  /** Logical owner for retained Python kernels created by this session. */
316
314
  evalKernelOwnerId?: string;
317
315
  /**
@@ -808,6 +806,7 @@ export class AgentSession {
808
806
  // Python execution state
809
807
  #evalAbortControllers = new Set<AbortController>();
810
808
  #evalKernelOwnerId: string;
809
+ #parentEvalSessionId: string | undefined;
811
810
  /**
812
811
  * AsyncJobManager owned by this session (top-level only). Subagents leave
813
812
  * this undefined and **MUST NOT** dispose the global instance on teardown.
@@ -865,8 +864,7 @@ export class AgentSession {
865
864
  */
866
865
  #lastAppliedToolSignature: string | undefined;
867
866
  #mcpDiscoveryEnabled = false;
868
- #discoverableMCPTools = new Map<string, DiscoverableMCPTool>();
869
- #discoverableMCPSearchIndex: DiscoverableMCPSearchIndex | null = null;
867
+ #discoverableMCPTools = new Map<string, DiscoverableTool>();
870
868
  #selectedMCPToolNames = new Set<string>();
871
869
  // Generic tool discovery (covers built-in + MCP + extension when tools.discoveryMode === "all")
872
870
  #discoverableToolSearchIndex: DiscoverableToolSearchIndex | null = null;
@@ -997,6 +995,7 @@ export class AgentSession {
997
995
  this.settings = config.settings;
998
996
  // Power assertions are taken per turn (see #beginInFlight); nothing acquired here.
999
997
  this.#evalKernelOwnerId = config.evalKernelOwnerId ?? `agent-session:${Snowflake.next()}`;
998
+ this.#parentEvalSessionId = config.parentEvalSessionId;
1000
999
  this.#ownedAsyncJobManager = config.ownedAsyncJobManager;
1001
1000
  this.#scopedModels = config.scopedModels ?? [];
1002
1001
  this.#thinkingLevel = config.thinkingLevel;
@@ -2876,11 +2875,12 @@ export class AgentSession {
2876
2875
  return this.#retryAttempt;
2877
2876
  }
2878
2877
 
2879
- #collectDiscoverableMCPToolsFromRegistry(): Map<string, DiscoverableMCPTool> {
2880
- return new Map(collectDiscoverableMCPTools(this.#toolRegistry.values()).map(tool => [tool.name, tool] as const));
2878
+ #collectDiscoverableMCPToolsFromRegistry(): Map<string, DiscoverableTool> {
2879
+ const mcpTools = filterBySource(collectDiscoverableTools(this.#toolRegistry.values()), "mcp");
2880
+ return new Map(mcpTools.map(tool => [tool.name, tool] as const));
2881
2881
  }
2882
2882
 
2883
- #setDiscoverableMCPTools(discoverableMCPTools: Map<string, DiscoverableMCPTool>): void {
2883
+ #setDiscoverableMCPTools(discoverableMCPTools: Map<string, DiscoverableTool>): void {
2884
2884
  this.#discoverableMCPTools = discoverableMCPTools;
2885
2885
  this.#invalidateDiscoveryCaches();
2886
2886
  }
@@ -2889,7 +2889,6 @@ export class AgentSession {
2889
2889
  * affect which tools should be discoverable: registry mutations (refreshMCPTools,
2890
2890
  * refreshRpcHostTools) or active-tool mutations (#applyActiveToolsByName). */
2891
2891
  #invalidateDiscoveryCaches(): void {
2892
- this.#discoverableMCPSearchIndex = null;
2893
2892
  this.#discoverableToolSearchIndex = null;
2894
2893
  }
2895
2894
 
@@ -2900,7 +2899,7 @@ export class AgentSession {
2900
2899
  #getConfiguredDefaultSelectedMCPToolNames(): string[] {
2901
2900
  return this.#filterSelectableMCPToolNames([
2902
2901
  ...this.#defaultSelectedMCPToolNames,
2903
- ...selectDiscoverableMCPToolNamesByServer(
2902
+ ...selectDiscoverableToolNamesByServer(
2904
2903
  this.#discoverableMCPTools.values(),
2905
2904
  this.#defaultSelectedMCPServerNames,
2906
2905
  ),
@@ -2993,28 +2992,6 @@ export class AgentSession {
2993
2992
  return this.#mcpDiscoveryEnabled;
2994
2993
  }
2995
2994
 
2996
- /** @deprecated Use {@link getDiscoverableTools} with `{ source: "mcp" }` instead.
2997
- * Preserves the legacy `description`-bearing MCP shape for back-compat callers. */
2998
- getDiscoverableMCPTools(): DiscoverableMCPTool[] {
2999
- return Array.from(this.#discoverableMCPTools.values()).map(t => ({
3000
- name: t.name,
3001
- label: t.label,
3002
- description: t.description,
3003
- serverName: t.serverName,
3004
- mcpToolName: t.mcpToolName,
3005
- schemaKeys: t.schemaKeys,
3006
- }));
3007
- }
3008
-
3009
- /** @deprecated Use {@link getDiscoverableToolSearchIndex} instead.
3010
- * Returns the legacy MCP search index whose documents expose `tool.description`. */
3011
- getDiscoverableMCPSearchIndex(): DiscoverableMCPSearchIndex {
3012
- if (!this.#discoverableMCPSearchIndex) {
3013
- this.#discoverableMCPSearchIndex = buildDiscoverableMCPSearchIndex(this.#discoverableMCPTools.values());
3014
- }
3015
- return this.#discoverableMCPSearchIndex;
3016
- }
3017
-
3018
2995
  getSelectedMCPToolNames(): string[] {
3019
2996
  if (!this.#mcpDiscoveryEnabled) {
3020
2997
  return this.getActiveToolNames().filter(name => isMCPToolName(name) && this.#toolRegistry.has(name));
@@ -3062,17 +3039,7 @@ export class AgentSession {
3062
3039
  // For "mcp-only" mode we only return MCP tools.
3063
3040
  const mode = this.#resolveEffectiveDiscoveryMode();
3064
3041
  const activeNames = new Set(this.getActiveToolNames());
3065
- const mcpTools: DiscoverableTool[] = Array.from(this.#discoverableMCPTools.values())
3066
- .filter(t => !activeNames.has(t.name))
3067
- .map(t => ({
3068
- name: t.name,
3069
- label: t.label,
3070
- summary: t.description,
3071
- source: "mcp" as const,
3072
- serverName: t.serverName,
3073
- mcpToolName: t.mcpToolName,
3074
- schemaKeys: t.schemaKeys,
3075
- }));
3042
+ const mcpTools = Array.from(this.#discoverableMCPTools.values()).filter(t => !activeNames.has(t.name));
3076
3043
  const builtinTools: DiscoverableTool[] = mode === "all" ? this.#collectDiscoverableBuiltinTools() : [];
3077
3044
  const allTools = [...builtinTools, ...mcpTools];
3078
3045
  return filter?.source ? allTools.filter(t => t.source === filter.source) : allTools;
@@ -3693,6 +3660,13 @@ export class AgentSession {
3693
3660
  get sessionId(): string {
3694
3661
  return this.#providerSessionId ?? this.sessionManager.getSessionId();
3695
3662
  }
3663
+ getEvalSessionId(): string | null {
3664
+ if (this.#parentEvalSessionId !== undefined) return this.#parentEvalSessionId;
3665
+ return defaultEvalSessionId({
3666
+ cwd: this.sessionManager.getCwd(),
3667
+ getSessionFile: () => this.sessionManager.getSessionFile() ?? null,
3668
+ });
3669
+ }
3696
3670
 
3697
3671
  /** Current session display name, if set */
3698
3672
  get sessionName(): string | undefined {
@@ -4225,7 +4199,6 @@ export class AgentSession {
4225
4199
  void this.dispose();
4226
4200
  process.exit(0);
4227
4201
  },
4228
- hasQueuedMessages: () => this.queuedMessageCount > 0,
4229
4202
  getContextUsage: () => this.getContextUsage(),
4230
4203
  waitForIdle: () => this.waitForIdle(),
4231
4204
  newSession: async options => {
@@ -4558,11 +4531,11 @@ export class AgentSession {
4558
4531
  }
4559
4532
 
4560
4533
  /**
4561
- * Send a user message to the agent. Always triggers a turn.
4562
- * When the agent is streaming, use deliverAs to specify how to queue the message.
4534
+ * Send a user message to the agent.
4535
+ * When deliverAs is set, queue the message instead of starting a new turn.
4563
4536
  *
4564
4537
  * @param content User message content (string or content array)
4565
- * @param options.deliverAs Delivery mode when streaming: "steer" or "followUp"
4538
+ * @param options.deliverAs Delivery mode: "steer" or "followUp"
4566
4539
  */
4567
4540
  async sendUserMessage(
4568
4541
  content: string | (TextContent | ImageContent)[],
@@ -4588,10 +4561,18 @@ export class AgentSession {
4588
4561
  if (images.length === 0) images = undefined;
4589
4562
  }
4590
4563
 
4564
+ if (options?.deliverAs === "followUp") {
4565
+ await this.#queueFollowUp(text, images);
4566
+ return;
4567
+ }
4568
+ if (options?.deliverAs === "steer") {
4569
+ await this.#queueSteer(text, images);
4570
+ return;
4571
+ }
4572
+
4591
4573
  // Use prompt() with expandPromptTemplates: false to skip command handling and template expansion
4592
4574
  await this.prompt(text, {
4593
4575
  expandPromptTemplates: false,
4594
- streamingBehavior: options?.deliverAs,
4595
4576
  images,
4596
4577
  });
4597
4578
  }
@@ -6781,10 +6762,11 @@ export class AgentSession {
6781
6762
  #isTransientTransportErrorMessage(errorMessage: string): boolean {
6782
6763
  // Match: overloaded_error, provider returned error, rate limit, 429, 500, 502, 503, 504,
6783
6764
  // service unavailable, provider-suggested retry, network/connection/socket errors, fetch failed,
6784
- // terminated, retry delay exceeded
6765
+ // terminated, retry delay exceeded, Bun HTTP/2 stream resets (RST_STREAM / REFUSED_STREAM /
6766
+ // ENHANCE_YOUR_CALM, surfaced verbatim from src/http/h2_client/dispatch.zig)
6785
6767
  return (
6786
6768
  isUnexpectedSocketCloseMessage(errorMessage) ||
6787
- /overloaded|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|retry your request|network.?error|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|reset before headers|socket hang up|timed? out|timeout|terminated|retry delay|stream stall|no error details in response/i.test(
6769
+ /overloaded|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|retry your request|network.?error|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|reset before headers|socket hang up|timed? out|timeout|terminated|retry delay|stream stall|no error details in response|HTTP2(?:StreamReset|RefusedStream|EnhanceYourCalm)/i.test(
6788
6770
  errorMessage,
6789
6771
  )
6790
6772
  );
@@ -7421,9 +7403,13 @@ export class AgentSession {
7421
7403
  }
7422
7404
  }
7423
7405
 
7424
- // Use the same session ID as eval's Python backend for kernel sharing
7425
- const sessionFile = this.sessionManager.getSessionFile();
7426
- const sessionId = sessionFile ? `session:${sessionFile}:cwd:${cwd}` : `cwd:${cwd}`;
7406
+ // Use the same session ID as eval's Python backend for kernel sharing.
7407
+ const sessionId =
7408
+ this.getEvalSessionId() ??
7409
+ defaultEvalSessionId({
7410
+ cwd,
7411
+ getSessionFile: () => this.sessionManager.getSessionFile() ?? null,
7412
+ });
7427
7413
  const result = await executePythonCommand(code, {
7428
7414
  cwd,
7429
7415
  sessionId,
@@ -7562,11 +7548,11 @@ export class AgentSession {
7562
7548
  * Generate an ephemeral reply to a background message (e.g. an IRC ping from
7563
7549
  * another agent) using this session's current model + system prompt + history.
7564
7550
  *
7565
- * The reply is computed via a side-channel `streamSimple` call (analogous to
7566
- * `/btw`) so it never blocks on the recipient's in-flight tool calls. After
7567
- * the reply is generated, both the incoming question and the auto-reply are
7568
- * queued for injection into the recipient's persisted history so the model
7569
- * sees the exchange on its next turn. Injection happens immediately when the
7551
+ * The incoming message is queued for injection into the recipient's persisted
7552
+ * history immediately so timeouts/abort still preserve delivery. The reply is
7553
+ * computed via a side-channel `streamSimple` call (analogous to `/btw`) so it
7554
+ * never blocks on the recipient's in-flight tool calls. When a reply is
7555
+ * generated, it is queued separately. Injection happens immediately when the
7570
7556
  * session is idle, otherwise it is deferred until streaming ends.
7571
7557
  */
7572
7558
  async respondAsBackground(args: {
@@ -7595,8 +7581,8 @@ export class AgentSession {
7595
7581
  timestamp: incomingTimestamp,
7596
7582
  });
7597
7583
 
7584
+ this.#queueBackgroundExchangeInjection([incomingRecord]);
7598
7585
  if (!awaitReply) {
7599
- this.#queueBackgroundExchangeInjection([incomingRecord]);
7600
7586
  return { replyText: null };
7601
7587
  }
7602
7588
 
@@ -7626,7 +7612,7 @@ export class AgentSession {
7626
7612
  kind: "reply",
7627
7613
  timestamp: replyRecord.timestamp,
7628
7614
  });
7629
- this.#queueBackgroundExchangeInjection([incomingRecord, replyRecord]);
7615
+ this.#queueBackgroundExchangeInjection([replyRecord]);
7630
7616
 
7631
7617
  return { replyText };
7632
7618
  }
@@ -7708,10 +7694,17 @@ export class AgentSession {
7708
7694
  // removes the surface entirely.
7709
7695
  tools: [],
7710
7696
  };
7697
+ const cacheSessionId = this.sessionId;
7711
7698
  const options = this.prepareSimpleStreamOptions(
7712
7699
  {
7713
7700
  apiKey,
7714
- sessionId: this.sessionId,
7701
+ // Side-channel turns must not share OpenAI/Codex append-only
7702
+ // conversation state with the main agent turn: IRC and /btw can run
7703
+ // while the main turn is mid-tool-call. Keep the prompt-cache key
7704
+ // stable, but give provider routing a unique request lineage.
7705
+ sessionId: `${cacheSessionId}:side:${Snowflake.next()}`,
7706
+ promptCacheKey: cacheSessionId,
7707
+ preferWebsockets: false,
7715
7708
  reasoning: toReasoningEffort(this.thinkingLevel),
7716
7709
  hideThinkingSummary: this.agent.hideThinkingSummary,
7717
7710
  serviceTier: this.serviceTier,
@@ -240,11 +240,10 @@ FROM model_usage_legacy
240
240
  }
241
241
 
242
242
  /**
243
- * Retrieves all settings from storage (legacy, for migration only).
244
- * Settings are now stored in config.yml. This method is only used
245
- * during migration from agent.db to config.yml.
243
+ * Reads legacy settings persisted in the agent.db `settings` table.
244
+ * The canonical settings store is `config.yml`; this accessor only
245
+ * exists so the config loader can migrate values from older installs.
246
246
  * @returns Settings object, or null if no settings are stored
247
- * @deprecated Use config.yml instead. This is only for migration.
248
247
  */
249
248
  getSettings(): Settings | null {
250
249
  const rows = (this.#listSettingsStmt.all() as SettingsRow[]) ?? [];
@@ -263,16 +262,6 @@ FROM model_usage_legacy
263
262
  return settings as Settings;
264
263
  }
265
264
 
266
- /**
267
- * @deprecated Settings are now stored in config.yml, not agent.db.
268
- * This method is kept for backward compatibility but does nothing.
269
- */
270
- saveSettings(settings: Settings): void {
271
- logger.warn("AgentStorage.saveSettings is deprecated - settings are now stored in config.yml", {
272
- keys: Object.keys(settings),
273
- });
274
- }
275
-
276
265
  /**
277
266
  * Records model usage, updating the last-used timestamp.
278
267
  * @param modelKey - Model key in "provider/modelId" format
@@ -313,6 +302,16 @@ FROM model_usage_legacy
313
302
  return this.#authStore.listAuthCredentials().length > 0;
314
303
  }
315
304
 
305
+ /**
306
+ * Returns the underlying {@link AuthCredentialStore} so callers that need
307
+ * the lower-level pi-ai abstraction (e.g. `findAnthropicAuth(store)`) can
308
+ * reuse this storage's open database connection instead of opening their
309
+ * own.
310
+ */
311
+ get authStore(): AuthCredentialStore {
312
+ return this.#authStore;
313
+ }
314
+
316
315
  /**
317
316
  * Lists auth credentials, optionally filtered by provider.
318
317
  * Only returns active (non-disabled) credentials by default.
@@ -1,9 +1,9 @@
1
1
  import type { AvailableCommand } from "@agentclientprotocol/sdk";
2
2
  import { BUILTIN_SLASH_COMMANDS_INTERNAL, lookupBuiltinSlashCommand } from "./builtin-registry";
3
3
  import { parseSlashCommand } from "./helpers/parse";
4
- import type { AcpBuiltinCommandRuntime, AcpBuiltinSlashCommandResult } from "./types";
4
+ import type { AcpBuiltinSlashCommandResult, SlashCommandRuntime } from "./types";
5
5
 
6
- export type { AcpBuiltinCommandRuntime, AcpBuiltinSlashCommandResult } from "./types";
6
+ export type { AcpBuiltinSlashCommandResult } from "./types";
7
7
 
8
8
  /**
9
9
  * Commands advertised to ACP clients. Entries without a text-mode `handle`
@@ -34,7 +34,7 @@ export const ACP_BUILTIN_SLASH_COMMANDS: AvailableCommand[] = BUILTIN_SLASH_COMM
34
34
  */
35
35
  export async function executeAcpBuiltinSlashCommand(
36
36
  text: string,
37
- runtime: AcpBuiltinCommandRuntime,
37
+ runtime: SlashCommandRuntime,
38
38
  ): Promise<AcpBuiltinSlashCommandResult> {
39
39
  const parsed = parseSlashCommand(text);
40
40
  if (!parsed) return false;
@@ -116,11 +116,5 @@ export interface SlashCommandSpec extends BuiltinSlashCommand {
116
116
  ) => Promise<SlashCommandResult> | SlashCommandResult;
117
117
  }
118
118
 
119
- /**
120
- * @deprecated Use `SlashCommandRuntime` directly. Retained as an alias so
121
- * downstream code that imported the ACP-specific name keeps compiling.
122
- */
123
- export type AcpBuiltinCommandRuntime = SlashCommandRuntime;
124
-
125
119
  /** Result returned by `executeAcpBuiltinSlashCommand`. */
126
120
  export type AcpBuiltinSlashCommandResult = false | { consumed: true } | { prompt: string };
@@ -7,7 +7,6 @@
7
7
  import path from "node:path";
8
8
  import type { AgentEvent, AgentIdentity, AgentTelemetryConfig, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
9
9
  import { recordHandoff, resolveTelemetry } from "@oh-my-pi/pi-agent-core";
10
- import { type JsonSchemaValidationIssue, validateJsonSchemaValue } from "@oh-my-pi/pi-ai/utils/schema";
11
10
  import { logger, prompt, untilAborted } from "@oh-my-pi/pi-utils";
12
11
  import { ModelRegistry } from "../config/model-registry";
13
12
  import { resolveModelOverrideWithAuthFallback } from "../config/model-resolver";
@@ -33,7 +32,10 @@ import { SKILL_PROMPT_MESSAGE_TYPE } from "../session/messages";
33
32
  import { SessionManager } from "../session/session-manager";
34
33
  import { truncateTail } from "../session/streaming-output";
35
34
  import type { ContextFileEntry } from "../tools";
36
- import { jtdToJsonSchema, normalizeSchema } from "../tools/jtd-to-json-schema";
35
+ import { normalizeSchema } from "../tools/jtd-to-json-schema";
36
+ import { buildOutputValidator, summarizeValidationFailure } from "../tools/output-schema-validator";
37
+
38
+ import { type ReportFindingDetails, toReviewFinding } from "../tools/review";
37
39
  import { ToolAbortError } from "../tools/tool-errors";
38
40
  import type { EventBus } from "../utils/event-bus";
39
41
  import { buildNamedToolChoice } from "../utils/tool-choice";
@@ -183,6 +185,8 @@ export interface ExecutorOptions {
183
185
  */
184
186
  parentArtifactManager?: ArtifactManager;
185
187
  parentHindsightSessionState?: HindsightSessionState;
188
+ /** Parent agent's eval executor session id. Subagents reuse it so eval state is shared. */
189
+ parentEvalSessionId?: string;
186
190
  /**
187
191
  * Parent agent's OpenTelemetry configuration. When defined, the subagent's
188
192
  * loop is started with the same tracer/hooks but its own agent identity
@@ -208,51 +212,6 @@ function parseStringifiedJson(value: unknown): unknown {
208
212
  }
209
213
  }
210
214
 
211
- interface OutputValidator {
212
- validate: (value: unknown) => { ok: true } | { ok: false; message: string; missingRequired: string[] };
213
- requiredFields: string[];
214
- }
215
-
216
- function buildOutputValidator(schema: unknown): { validator?: OutputValidator; error?: string } {
217
- const { normalized, error } = normalizeSchema(schema);
218
- if (error) return { error };
219
- if (normalized === undefined) return {};
220
- const jsonSchema = jtdToJsonSchema(normalized);
221
- const required = extractRequiredFields(jsonSchema);
222
- return {
223
- validator: {
224
- requiredFields: required,
225
- validate: value => {
226
- const result = validateJsonSchemaValue(jsonSchema, value);
227
- if (result.success) return { ok: true };
228
- const missing = computeMissingRequired(required, value);
229
- const message = formatValidationIssue(result.issues[0]) ?? "schema validation failed";
230
- return { ok: false, message, missingRequired: missing };
231
- },
232
- },
233
- };
234
- }
235
-
236
- function extractRequiredFields(jsonSchema: unknown): string[] {
237
- if (!jsonSchema || typeof jsonSchema !== "object") return [];
238
- const required = (jsonSchema as { required?: unknown }).required;
239
- return Array.isArray(required) ? required.filter((k): k is string => typeof k === "string") : [];
240
- }
241
-
242
- function computeMissingRequired(required: readonly string[], value: unknown): string[] {
243
- if (required.length === 0) return [];
244
- if (value === null || value === undefined) return [...required];
245
- if (typeof value !== "object" || Array.isArray(value)) return [];
246
- const record = value as Record<string, unknown>;
247
- return required.filter(key => !(key in record) || record[key] === undefined);
248
- }
249
-
250
- function formatValidationIssue(issue: JsonSchemaValidationIssue | undefined): string | undefined {
251
- if (!issue) return undefined;
252
- const path = issue.path.length > 0 ? issue.path.map(String).join(".") : "(root)";
253
- return `${path}: ${issue.message}`;
254
- }
255
-
256
215
  function previewOffendingData(value: unknown, maxLength = 500): string {
257
216
  let serialized: string;
258
217
  try {
@@ -306,7 +265,7 @@ function resolveFallbackCompletion(rawOutput: string, outputSchema: unknown): {
306
265
  if (candidate === undefined) return null;
307
266
  const { validator, error } = buildOutputValidator(outputSchema);
308
267
  if (error) return null;
309
- if (validator && !validator.validate(candidate).ok) return null;
268
+ if (validator && !validator.validate(candidate).success) return null;
310
269
  return { data: candidate };
311
270
  }
312
271
 
@@ -393,9 +352,10 @@ export function finalizeSubprocessOutput(args: FinalizeSubprocessOutputArgs): Fi
393
352
  stderr = `schema_violation: invalid output schema: ${schemaError}`;
394
353
  exitCode = 1;
395
354
  } else {
396
- const verdict = validator ? validator.validate(completeData) : { ok: true as const };
397
- if (!verdict.ok) {
398
- const outcome = buildSchemaViolationOutcome(verdict, completeData);
355
+ const result = validator?.validate(completeData) ?? { success: true as const };
356
+ if (!result.success) {
357
+ const summary = summarizeValidationFailure(result, completeData, validator?.requiredFields ?? []);
358
+ const outcome = buildSchemaViolationOutcome(summary, completeData);
399
359
  rawOutput = outcome.rawOutput;
400
360
  stderr = outcome.stderr;
401
361
  exitCode = outcome.exitCode;
@@ -420,9 +380,10 @@ export function finalizeSubprocessOutput(args: FinalizeSubprocessOutputArgs): Fi
420
380
  if (fallback) {
421
381
  const completeData = normalizeCompleteData(fallback.data, reportFindings);
422
382
  const { validator } = buildOutputValidator(outputSchema);
423
- const verdict = validator ? validator.validate(completeData) : { ok: true as const };
424
- if (!verdict.ok) {
425
- const outcome = buildSchemaViolationOutcome(verdict, completeData);
383
+ const result = validator?.validate(completeData) ?? { success: true as const };
384
+ if (!result.success) {
385
+ const summary = summarizeValidationFailure(result, completeData, validator?.requiredFields ?? []);
386
+ const outcome = buildSchemaViolationOutcome(summary, completeData);
426
387
  rawOutput = outcome.rawOutput;
427
388
  stderr = outcome.stderr;
428
389
  exitCode = outcome.exitCode;
@@ -1277,6 +1238,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1277
1238
  customTools: mcpProxyTools.length > 0 ? mcpProxyTools : undefined,
1278
1239
  localProtocolOptions: options.localProtocolOptions,
1279
1240
  telemetry: subagentTelemetry,
1241
+ parentEvalSessionId: options.parentEvalSessionId,
1280
1242
  }),
1281
1243
  );
1282
1244
 
@@ -1324,22 +1286,25 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1324
1286
  }
1325
1287
 
1326
1288
  const extensionRunner = session.extensionRunner;
1289
+ const pendingExtensionMessages: Promise<void>[] = [];
1327
1290
  if (extensionRunner) {
1328
1291
  extensionRunner.initialize(
1329
1292
  {
1330
1293
  sendMessage: (message, options) => {
1331
- session.sendCustomMessage(message, options).catch(e => {
1294
+ const sendPromise = session.sendCustomMessage(message, options).catch(e => {
1332
1295
  logger.error("Extension sendMessage failed", {
1333
1296
  error: e instanceof Error ? e.message : String(e),
1334
1297
  });
1335
1298
  });
1299
+ pendingExtensionMessages.push(sendPromise);
1336
1300
  },
1337
1301
  sendUserMessage: (content, options) => {
1338
- session.sendUserMessage(content, options).catch(e => {
1302
+ const sendPromise = session.sendUserMessage(content, options).catch(e => {
1339
1303
  logger.error("Extension sendUserMessage failed", {
1340
1304
  error: e instanceof Error ? e.message : String(e),
1341
1305
  });
1342
1306
  });
1307
+ pendingExtensionMessages.push(sendPromise);
1343
1308
  },
1344
1309
  appendEntry: (customType, data) => {
1345
1310
  session.sessionManager.appendCustomEntry(customType, data);
@@ -1375,6 +1340,9 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1375
1340
  logger.error("Extension error", { path: err.extensionPath, error: err.error });
1376
1341
  });
1377
1342
  await awaitAbortable(extensionRunner.emit({ type: "session_start" }));
1343
+ while (pendingExtensionMessages.length > 0) {
1344
+ await awaitAbortable(Promise.all(pendingExtensionMessages.splice(0)));
1345
+ }
1378
1346
  }
1379
1347
 
1380
1348
  const MAX_YIELD_RETRIES = 3;
@@ -1547,7 +1515,8 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1547
1515
  // Use final output if available, otherwise accumulated output
1548
1516
  let rawOutput = finalOutputChunks.length > 0 ? finalOutputChunks.join("") : outputChunks.join("");
1549
1517
  const yieldItems = progress.extractedToolData?.yield as YieldItem[] | undefined;
1550
- const reportFindings = progress.extractedToolData?.report_finding as ReviewFinding[] | undefined;
1518
+ const reportFindingDetails = progress.extractedToolData?.report_finding as ReportFindingDetails[] | undefined;
1519
+ const reportFindings: ReviewFinding[] | undefined = reportFindingDetails?.map(toReviewFinding);
1551
1520
  const finalized = finalizeSubprocessOutput({
1552
1521
  rawOutput,
1553
1522
  exitCode,