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

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 (193) hide show
  1. package/CHANGELOG.md +110 -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/settings-selector.ts +10 -1
  114. package/src/modes/components/tree-selector.ts +10 -2
  115. package/src/modes/controllers/command-controller.ts +1 -3
  116. package/src/modes/controllers/extension-ui-controller.ts +10 -11
  117. package/src/modes/controllers/selector-controller.ts +5 -5
  118. package/src/modes/theme/theme.ts +4 -2
  119. package/src/modes/types.ts +4 -1
  120. package/src/modes/utils/ui-helpers.ts +4 -0
  121. package/src/prompts/agents/explore.md +1 -1
  122. package/src/prompts/tools/ast-edit.md +1 -1
  123. package/src/prompts/tools/ast-grep.md +1 -1
  124. package/src/prompts/tools/eval.md +1 -1
  125. package/src/prompts/tools/hashline.md +73 -94
  126. package/src/prompts/tools/read.md +4 -4
  127. package/src/prompts/tools/search.md +3 -3
  128. package/src/sdk.ts +33 -26
  129. package/src/session/agent-session.ts +59 -66
  130. package/src/session/agent-storage.ts +13 -14
  131. package/src/slash-commands/acp-builtins.ts +3 -3
  132. package/src/slash-commands/types.ts +0 -6
  133. package/src/task/executor.ts +26 -57
  134. package/src/task/index.ts +8 -4
  135. package/src/tool-discovery/tool-index.ts +0 -134
  136. package/src/tools/ast-edit.ts +36 -13
  137. package/src/tools/ast-grep.ts +45 -4
  138. package/src/tools/browser/tab-worker.ts +3 -2
  139. package/src/tools/eval.ts +2 -1
  140. package/src/tools/fetch.ts +23 -14
  141. package/src/tools/index.ts +2 -8
  142. package/src/tools/irc.ts +59 -5
  143. package/src/tools/match-line-format.ts +5 -7
  144. package/src/tools/output-schema-validator.ts +132 -0
  145. package/src/tools/read.ts +142 -31
  146. package/src/tools/review.ts +23 -0
  147. package/src/tools/search-tool-bm25.ts +3 -30
  148. package/src/tools/search.ts +48 -16
  149. package/src/tools/write.ts +3 -3
  150. package/src/tools/yield.ts +32 -41
  151. package/src/utils/edit-mode.ts +1 -2
  152. package/src/utils/file-mentions.ts +2 -2
  153. package/src/web/kagi.ts +15 -6
  154. package/src/web/parallel.ts +9 -6
  155. package/src/web/scrapers/types.ts +7 -1
  156. package/src/web/scrapers/youtube.ts +13 -7
  157. package/src/web/search/index.ts +37 -11
  158. package/src/web/search/provider.ts +5 -3
  159. package/src/web/search/providers/anthropic.ts +30 -21
  160. package/src/web/search/providers/base.ts +35 -2
  161. package/src/web/search/providers/brave.ts +4 -4
  162. package/src/web/search/providers/codex.ts +118 -89
  163. package/src/web/search/providers/exa.ts +3 -2
  164. package/src/web/search/providers/gemini.ts +58 -155
  165. package/src/web/search/providers/jina.ts +4 -4
  166. package/src/web/search/providers/kagi.ts +17 -11
  167. package/src/web/search/providers/kimi.ts +29 -13
  168. package/src/web/search/providers/parallel.ts +171 -23
  169. package/src/web/search/providers/perplexity.ts +38 -37
  170. package/src/web/search/providers/searxng.ts +3 -1
  171. package/src/web/search/providers/synthetic.ts +16 -19
  172. package/src/web/search/providers/tavily.ts +23 -18
  173. package/src/web/search/providers/utils.ts +11 -17
  174. package/src/web/search/providers/zai.ts +16 -8
  175. package/dist/types/hashline/parser.d.ts +0 -7
  176. package/dist/types/mcp/discoverable-tool-metadata.d.ts +0 -7
  177. package/dist/types/tools/vim.d.ts +0 -58
  178. package/dist/types/vim/buffer.d.ts +0 -41
  179. package/dist/types/vim/commands.d.ts +0 -6
  180. package/dist/types/vim/engine.d.ts +0 -47
  181. package/dist/types/vim/parser.d.ts +0 -3
  182. package/dist/types/vim/render.d.ts +0 -25
  183. package/dist/types/vim/types.d.ts +0 -182
  184. package/src/hashline/parser.ts +0 -246
  185. package/src/mcp/discoverable-tool-metadata.ts +0 -24
  186. package/src/prompts/tools/vim.md +0 -98
  187. package/src/tools/vim.ts +0 -949
  188. package/src/vim/buffer.ts +0 -309
  189. package/src/vim/commands.ts +0 -382
  190. package/src/vim/engine.ts +0 -2409
  191. package/src/vim/parser.ts +0 -134
  192. package/src/vim/render.ts +0 -252
  193. 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,
@@ -1665,9 +1658,22 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1665
1658
  };
1666
1659
 
1667
1660
  const toolNamesFromRegistry = Array.from(toolRegistry.keys());
1668
- const requestedToolNames =
1669
- (options.toolNames ? [...new Set(options.toolNames.map(name => name.toLowerCase()))] : undefined) ??
1670
- toolNamesFromRegistry;
1661
+ const explicitlyRequestedToolNames = options.toolNames
1662
+ ? [...new Set(options.toolNames.map(name => name.toLowerCase()))]
1663
+ : undefined;
1664
+ // When `requireYieldTool` is set, the subagent's prompts and idle-reminders demand a
1665
+ // `yield` call to terminate. The tool registry already includes `yield` (see
1666
+ // `createTools`), but an explicit `toolNames` list would otherwise drop it from the
1667
+ // active set — leaving the model unable to satisfy the contract. Mirror the same
1668
+ // invariant `parseAgentFields` enforces on frontmatter `tools`.
1669
+ if (
1670
+ options.requireYieldTool === true &&
1671
+ explicitlyRequestedToolNames &&
1672
+ !explicitlyRequestedToolNames.includes("yield")
1673
+ ) {
1674
+ explicitlyRequestedToolNames.push("yield");
1675
+ }
1676
+ const requestedToolNames = explicitlyRequestedToolNames ?? toolNamesFromRegistry;
1671
1677
  const normalizedRequested = requestedToolNames.filter(name => toolRegistry.has(name));
1672
1678
  const requestedToolNameSet = new Set(normalizedRequested);
1673
1679
  // Effective discovery mode: tools.discoveryMode takes precedence; mcp.discoveryMode is back-compat alias.
@@ -1693,8 +1699,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1693
1699
  (settings.get("mcp.discoveryDefaultServers") ?? []).map(serverName => serverName.trim()).filter(Boolean),
1694
1700
  );
1695
1701
  const discoveryDefaultServerToolNames = mcpDiscoveryEnabled
1696
- ? selectDiscoverableMCPToolNamesByServer(
1697
- collectDiscoverableMCPTools(toolRegistry.values()),
1702
+ ? selectDiscoverableToolNamesByServer(
1703
+ filterBySource(collectDiscoverableTools(toolRegistry.values()), "mcp"),
1698
1704
  discoveryDefaultServers,
1699
1705
  )
1700
1706
  : [];
@@ -1994,6 +2000,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1994
2000
  agentId: resolvedAgentId,
1995
2001
  agentRegistry,
1996
2002
  providerSessionId: options.providerSessionId,
2003
+ parentEvalSessionId: options.parentEvalSessionId,
1997
2004
  });
1998
2005
  hasSession = true;
1999
2006
  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 };