@oh-my-pi/pi-coding-agent 15.3.1 → 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 (200) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/dist/types/cli/auth-gateway-cli.d.ts +1 -1
  3. package/dist/types/cli/file-processor.d.ts +1 -1
  4. package/dist/types/config/settings-schema.d.ts +45 -3
  5. package/dist/types/config/settings.d.ts +1 -1
  6. package/dist/types/debug/raw-sse.d.ts +2 -0
  7. package/dist/types/edit/file-read-cache.d.ts +15 -4
  8. package/dist/types/edit/index.d.ts +3 -8
  9. package/dist/types/edit/renderer.d.ts +1 -2
  10. package/dist/types/eval/__tests__/shared-executors.test.d.ts +1 -0
  11. package/dist/types/eval/js/shared/local-module-loader.d.ts +16 -0
  12. package/dist/types/eval/js/shared/rewrite-imports.d.ts +4 -0
  13. package/dist/types/eval/js/shared/runtime.d.ts +14 -8
  14. package/dist/types/eval/py/executor.d.ts +1 -2
  15. package/dist/types/eval/py/kernel.d.ts +6 -0
  16. package/dist/types/eval/py/tool-bridge.d.ts +1 -5
  17. package/dist/types/eval/session-id.d.ts +3 -0
  18. package/dist/types/extensibility/extensions/types.d.ts +1 -3
  19. package/dist/types/hashline/anchors.d.ts +15 -9
  20. package/dist/types/hashline/constants.d.ts +0 -2
  21. package/dist/types/hashline/diff.d.ts +1 -2
  22. package/dist/types/hashline/executor.d.ts +52 -0
  23. package/dist/types/hashline/hash.d.ts +44 -93
  24. package/dist/types/hashline/index.d.ts +2 -1
  25. package/dist/types/hashline/input.d.ts +2 -9
  26. package/dist/types/hashline/recovery.d.ts +3 -9
  27. package/dist/types/hashline/tokenizer.d.ts +91 -0
  28. package/dist/types/hashline/types.d.ts +5 -7
  29. package/dist/types/modes/components/extensions/types.d.ts +0 -4
  30. package/dist/types/modes/types.d.ts +1 -0
  31. package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
  32. package/dist/types/sdk.d.ts +2 -0
  33. package/dist/types/session/agent-session.d.ts +11 -15
  34. package/dist/types/session/agent-storage.d.ts +11 -10
  35. package/dist/types/slash-commands/acp-builtins.d.ts +3 -3
  36. package/dist/types/slash-commands/types.d.ts +0 -5
  37. package/dist/types/task/executor.d.ts +2 -0
  38. package/dist/types/task/types.d.ts +8 -0
  39. package/dist/types/tool-discovery/tool-index.d.ts +0 -50
  40. package/dist/types/tools/index.d.ts +2 -8
  41. package/dist/types/tools/match-line-format.d.ts +4 -4
  42. package/dist/types/tools/output-schema-validator.d.ts +64 -0
  43. package/dist/types/tools/review.d.ts +13 -0
  44. package/dist/types/tools/search-tool-bm25.d.ts +1 -1
  45. package/dist/types/tools/search.d.ts +4 -3
  46. package/dist/types/utils/edit-mode.d.ts +1 -1
  47. package/dist/types/web/kagi.d.ts +4 -2
  48. package/dist/types/web/parallel.d.ts +4 -3
  49. package/dist/types/web/scrapers/types.d.ts +2 -1
  50. package/dist/types/web/search/index.d.ts +12 -4
  51. package/dist/types/web/search/provider.d.ts +2 -1
  52. package/dist/types/web/search/providers/anthropic.d.ts +9 -4
  53. package/dist/types/web/search/providers/base.d.ts +34 -2
  54. package/dist/types/web/search/providers/brave.d.ts +8 -1
  55. package/dist/types/web/search/providers/codex.d.ts +13 -9
  56. package/dist/types/web/search/providers/exa.d.ts +10 -1
  57. package/dist/types/web/search/providers/gemini.d.ts +20 -23
  58. package/dist/types/web/search/providers/jina.d.ts +2 -1
  59. package/dist/types/web/search/providers/kagi.d.ts +4 -1
  60. package/dist/types/web/search/providers/kimi.d.ts +10 -1
  61. package/dist/types/web/search/providers/parallel.d.ts +3 -2
  62. package/dist/types/web/search/providers/perplexity.d.ts +5 -2
  63. package/dist/types/web/search/providers/searxng.d.ts +2 -1
  64. package/dist/types/web/search/providers/synthetic.d.ts +5 -8
  65. package/dist/types/web/search/providers/tavily.d.ts +11 -4
  66. package/dist/types/web/search/providers/utils.d.ts +8 -6
  67. package/dist/types/web/search/providers/zai.d.ts +12 -3
  68. package/package.json +7 -7
  69. package/src/cli/auth-gateway-cli.ts +71 -2
  70. package/src/cli/file-processor.ts +12 -2
  71. package/src/cli.ts +0 -8
  72. package/src/commands/auth-gateway.ts +2 -0
  73. package/src/commands/commit.ts +8 -8
  74. package/src/config/prompt-templates.ts +6 -6
  75. package/src/config/settings-schema.ts +47 -3
  76. package/src/config/settings.ts +5 -5
  77. package/src/debug/raw-sse.ts +68 -3
  78. package/src/edit/file-read-cache.ts +68 -25
  79. package/src/edit/index.ts +6 -37
  80. package/src/edit/renderer.ts +9 -47
  81. package/src/edit/streaming.ts +43 -56
  82. package/src/eval/__tests__/shared-executors.test.ts +520 -0
  83. package/src/eval/js/context-manager.ts +64 -53
  84. package/src/eval/js/shared/local-module-loader.ts +265 -0
  85. package/src/eval/js/shared/prelude.txt +4 -0
  86. package/src/eval/js/shared/rewrite-imports.ts +85 -0
  87. package/src/eval/js/shared/runtime.ts +129 -86
  88. package/src/eval/js/worker-core.ts +23 -38
  89. package/src/eval/py/executor.ts +155 -84
  90. package/src/eval/py/kernel.ts +10 -1
  91. package/src/eval/py/prelude.py +22 -24
  92. package/src/eval/py/runner.py +203 -85
  93. package/src/eval/py/tool-bridge.ts +17 -10
  94. package/src/eval/session-id.ts +8 -0
  95. package/src/exec/bash-executor.ts +27 -16
  96. package/src/extensibility/extensions/runner.ts +0 -1
  97. package/src/extensibility/extensions/types.ts +1 -3
  98. package/src/extensibility/plugins/marketplace/manager.ts +20 -1
  99. package/src/hashline/anchors.ts +56 -65
  100. package/src/hashline/apply.ts +29 -31
  101. package/src/hashline/constants.ts +0 -3
  102. package/src/hashline/diff-preview.ts +4 -5
  103. package/src/hashline/diff.ts +30 -4
  104. package/src/hashline/execute.ts +91 -26
  105. package/src/hashline/executor.ts +239 -0
  106. package/src/hashline/grammar.lark +12 -10
  107. package/src/hashline/hash.ts +69 -114
  108. package/src/hashline/index.ts +2 -1
  109. package/src/hashline/input.ts +48 -41
  110. package/src/hashline/prefixes.ts +21 -11
  111. package/src/hashline/recovery.ts +63 -71
  112. package/src/hashline/stream.ts +2 -2
  113. package/src/hashline/tokenizer.ts +467 -0
  114. package/src/hashline/types.ts +6 -8
  115. package/src/internal-urls/docs-index.generated.ts +9 -8
  116. package/src/lsp/config.ts +87 -22
  117. package/src/modes/components/extensions/types.ts +0 -5
  118. package/src/modes/components/session-observer-overlay.ts +11 -2
  119. package/src/modes/components/tree-selector.ts +10 -2
  120. package/src/modes/controllers/command-controller.ts +1 -3
  121. package/src/modes/controllers/extension-ui-controller.ts +10 -11
  122. package/src/modes/controllers/selector-controller.ts +5 -5
  123. package/src/modes/types.ts +4 -1
  124. package/src/modes/utils/ui-helpers.ts +4 -0
  125. package/src/prompts/agents/explore.md +1 -1
  126. package/src/prompts/tools/ast-edit.md +1 -1
  127. package/src/prompts/tools/ast-grep.md +1 -1
  128. package/src/prompts/tools/eval.md +1 -1
  129. package/src/prompts/tools/hashline.md +73 -94
  130. package/src/prompts/tools/read.md +4 -4
  131. package/src/prompts/tools/search.md +3 -3
  132. package/src/sdk.ts +21 -24
  133. package/src/session/agent-session.ts +59 -66
  134. package/src/session/agent-storage.ts +13 -14
  135. package/src/slash-commands/acp-builtins.ts +3 -3
  136. package/src/slash-commands/types.ts +0 -6
  137. package/src/task/executor.ts +55 -57
  138. package/src/task/index.ts +8 -4
  139. package/src/task/render.ts +53 -1
  140. package/src/task/types.ts +8 -0
  141. package/src/tool-discovery/tool-index.ts +0 -134
  142. package/src/tools/ast-edit.ts +36 -13
  143. package/src/tools/ast-grep.ts +45 -4
  144. package/src/tools/browser/tab-worker.ts +3 -2
  145. package/src/tools/eval.ts +2 -1
  146. package/src/tools/fetch.ts +23 -14
  147. package/src/tools/index.ts +2 -8
  148. package/src/tools/irc.ts +59 -5
  149. package/src/tools/jtd-to-json-schema.ts +5 -1
  150. package/src/tools/match-line-format.ts +5 -7
  151. package/src/tools/output-schema-validator.ts +132 -0
  152. package/src/tools/read.ts +142 -63
  153. package/src/tools/review.ts +23 -0
  154. package/src/tools/search-tool-bm25.ts +3 -30
  155. package/src/tools/search.ts +48 -16
  156. package/src/tools/write.ts +3 -3
  157. package/src/tools/yield.ts +32 -41
  158. package/src/utils/edit-mode.ts +1 -2
  159. package/src/utils/file-mentions.ts +2 -2
  160. package/src/web/kagi.ts +15 -6
  161. package/src/web/parallel.ts +9 -6
  162. package/src/web/scrapers/types.ts +7 -1
  163. package/src/web/scrapers/youtube.ts +13 -7
  164. package/src/web/search/index.ts +37 -11
  165. package/src/web/search/provider.ts +5 -3
  166. package/src/web/search/providers/anthropic.ts +30 -21
  167. package/src/web/search/providers/base.ts +35 -2
  168. package/src/web/search/providers/brave.ts +4 -4
  169. package/src/web/search/providers/codex.ts +118 -89
  170. package/src/web/search/providers/exa.ts +3 -2
  171. package/src/web/search/providers/gemini.ts +58 -155
  172. package/src/web/search/providers/jina.ts +4 -4
  173. package/src/web/search/providers/kagi.ts +17 -11
  174. package/src/web/search/providers/kimi.ts +29 -13
  175. package/src/web/search/providers/parallel.ts +171 -23
  176. package/src/web/search/providers/perplexity.ts +38 -37
  177. package/src/web/search/providers/searxng.ts +3 -1
  178. package/src/web/search/providers/synthetic.ts +16 -19
  179. package/src/web/search/providers/tavily.ts +23 -18
  180. package/src/web/search/providers/utils.ts +11 -17
  181. package/src/web/search/providers/zai.ts +16 -8
  182. package/dist/types/hashline/parser.d.ts +0 -7
  183. package/dist/types/mcp/discoverable-tool-metadata.d.ts +0 -7
  184. package/dist/types/tools/vim.d.ts +0 -58
  185. package/dist/types/vim/buffer.d.ts +0 -41
  186. package/dist/types/vim/commands.d.ts +0 -6
  187. package/dist/types/vim/engine.d.ts +0 -47
  188. package/dist/types/vim/parser.d.ts +0 -3
  189. package/dist/types/vim/render.d.ts +0 -25
  190. package/dist/types/vim/types.d.ts +0 -182
  191. package/src/hashline/parser.ts +0 -212
  192. package/src/mcp/discoverable-tool-metadata.ts +0 -24
  193. package/src/prompts/tools/vim.md +0 -98
  194. package/src/tools/vim.ts +0 -949
  195. package/src/vim/buffer.ts +0 -309
  196. package/src/vim/commands.ts +0 -382
  197. package/src/vim/engine.ts +0 -2409
  198. package/src/vim/parser.ts +0 -134
  199. package/src/vim/render.ts +0 -252
  200. package/src/vim/types.ts +0 -197
@@ -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";
@@ -49,6 +51,7 @@ import {
49
51
  TASK_SUBAGENT_EVENT_CHANNEL,
50
52
  TASK_SUBAGENT_LIFECYCLE_CHANNEL,
51
53
  TASK_SUBAGENT_PROGRESS_CHANNEL,
54
+ type TaskToolDetails,
52
55
  } from "./types";
53
56
 
54
57
  const MCP_CALL_TIMEOUT_MS = 60_000;
@@ -182,6 +185,8 @@ export interface ExecutorOptions {
182
185
  */
183
186
  parentArtifactManager?: ArtifactManager;
184
187
  parentHindsightSessionState?: HindsightSessionState;
188
+ /** Parent agent's eval executor session id. Subagents reuse it so eval state is shared. */
189
+ parentEvalSessionId?: string;
185
190
  /**
186
191
  * Parent agent's OpenTelemetry configuration. When defined, the subagent's
187
192
  * loop is started with the same tracer/hooks but its own agent identity
@@ -207,51 +212,6 @@ function parseStringifiedJson(value: unknown): unknown {
207
212
  }
208
213
  }
209
214
 
210
- interface OutputValidator {
211
- validate: (value: unknown) => { ok: true } | { ok: false; message: string; missingRequired: string[] };
212
- requiredFields: string[];
213
- }
214
-
215
- function buildOutputValidator(schema: unknown): { validator?: OutputValidator; error?: string } {
216
- const { normalized, error } = normalizeSchema(schema);
217
- if (error) return { error };
218
- if (normalized === undefined) return {};
219
- const jsonSchema = jtdToJsonSchema(normalized);
220
- const required = extractRequiredFields(jsonSchema);
221
- return {
222
- validator: {
223
- requiredFields: required,
224
- validate: value => {
225
- const result = validateJsonSchemaValue(jsonSchema, value);
226
- if (result.success) return { ok: true };
227
- const missing = computeMissingRequired(required, value);
228
- const message = formatValidationIssue(result.issues[0]) ?? "schema validation failed";
229
- return { ok: false, message, missingRequired: missing };
230
- },
231
- },
232
- };
233
- }
234
-
235
- function extractRequiredFields(jsonSchema: unknown): string[] {
236
- if (!jsonSchema || typeof jsonSchema !== "object") return [];
237
- const required = (jsonSchema as { required?: unknown }).required;
238
- return Array.isArray(required) ? required.filter((k): k is string => typeof k === "string") : [];
239
- }
240
-
241
- function computeMissingRequired(required: readonly string[], value: unknown): string[] {
242
- if (required.length === 0) return [];
243
- if (value === null || value === undefined) return [...required];
244
- if (typeof value !== "object" || Array.isArray(value)) return [];
245
- const record = value as Record<string, unknown>;
246
- return required.filter(key => !(key in record) || record[key] === undefined);
247
- }
248
-
249
- function formatValidationIssue(issue: JsonSchemaValidationIssue | undefined): string | undefined {
250
- if (!issue) return undefined;
251
- const path = issue.path.length > 0 ? issue.path.map(String).join(".") : "(root)";
252
- return `${path}: ${issue.message}`;
253
- }
254
-
255
215
  function previewOffendingData(value: unknown, maxLength = 500): string {
256
216
  let serialized: string;
257
217
  try {
@@ -305,7 +265,7 @@ function resolveFallbackCompletion(rawOutput: string, outputSchema: unknown): {
305
265
  if (candidate === undefined) return null;
306
266
  const { validator, error } = buildOutputValidator(outputSchema);
307
267
  if (error) return null;
308
- if (validator && !validator.validate(candidate).ok) return null;
268
+ if (validator && !validator.validate(candidate).success) return null;
309
269
  return { data: candidate };
310
270
  }
311
271
 
@@ -392,9 +352,10 @@ export function finalizeSubprocessOutput(args: FinalizeSubprocessOutputArgs): Fi
392
352
  stderr = `schema_violation: invalid output schema: ${schemaError}`;
393
353
  exitCode = 1;
394
354
  } else {
395
- const verdict = validator ? validator.validate(completeData) : { ok: true as const };
396
- if (!verdict.ok) {
397
- 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);
398
359
  rawOutput = outcome.rawOutput;
399
360
  stderr = outcome.stderr;
400
361
  exitCode = outcome.exitCode;
@@ -419,9 +380,10 @@ export function finalizeSubprocessOutput(args: FinalizeSubprocessOutputArgs): Fi
419
380
  if (fallback) {
420
381
  const completeData = normalizeCompleteData(fallback.data, reportFindings);
421
382
  const { validator } = buildOutputValidator(outputSchema);
422
- const verdict = validator ? validator.validate(completeData) : { ok: true as const };
423
- if (!verdict.ok) {
424
- 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);
425
387
  rawOutput = outcome.rawOutput;
426
388
  stderr = outcome.stderr;
427
389
  exitCode = outcome.exitCode;
@@ -909,6 +871,11 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
909
871
  if (intent) {
910
872
  progress.lastIntent = intent;
911
873
  }
874
+ // Reset any prior in-flight task snapshot so we don't show stale
875
+ // nested progress when the agent enters a fresh `task` call.
876
+ if (event.toolName === "task") {
877
+ progress.inflightTaskDetails = undefined;
878
+ }
912
879
  break;
913
880
  }
914
881
 
@@ -927,6 +894,12 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
927
894
  progress.currentTool = undefined;
928
895
  progress.currentToolArgs = undefined;
929
896
  progress.currentToolStartMs = undefined;
897
+ // The finalized TaskToolDetails will be captured below into
898
+ // `extractedToolData.task`; drop the in-flight snapshot so the
899
+ // renderer doesn't double-count it against the final entry.
900
+ if (event.toolName === "task") {
901
+ progress.inflightTaskDetails = undefined;
902
+ }
930
903
 
931
904
  // Check for registered subagent tool handler
932
905
  const handler = subprocessToolRegistry.getHandler(event.toolName);
@@ -979,6 +952,23 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
979
952
  break;
980
953
  }
981
954
 
955
+ case "tool_execution_update": {
956
+ // Surface nested-subagent progress mid-flight. The child task
957
+ // tool emits incremental `onUpdate` calls carrying its current
958
+ // `TaskToolDetails` (results + progress); we stash the latest
959
+ // snapshot so the parent UI can render the in-flight subtree
960
+ // without waiting for the call to finish.
961
+ if (event.toolName === "task") {
962
+ const partial = (event as { partialResult?: { details?: unknown } }).partialResult;
963
+ const details = partial && typeof partial === "object" ? partial.details : undefined;
964
+ if (details && typeof details === "object" && "results" in (details as TaskToolDetails)) {
965
+ progress.inflightTaskDetails = details as TaskToolDetails;
966
+ flushProgress = true;
967
+ }
968
+ }
969
+ break;
970
+ }
971
+
982
972
  case "message_update": {
983
973
  if (event.message?.role !== "assistant") break;
984
974
  const assistantEvent = (
@@ -1248,6 +1238,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1248
1238
  customTools: mcpProxyTools.length > 0 ? mcpProxyTools : undefined,
1249
1239
  localProtocolOptions: options.localProtocolOptions,
1250
1240
  telemetry: subagentTelemetry,
1241
+ parentEvalSessionId: options.parentEvalSessionId,
1251
1242
  }),
1252
1243
  );
1253
1244
 
@@ -1295,22 +1286,25 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1295
1286
  }
1296
1287
 
1297
1288
  const extensionRunner = session.extensionRunner;
1289
+ const pendingExtensionMessages: Promise<void>[] = [];
1298
1290
  if (extensionRunner) {
1299
1291
  extensionRunner.initialize(
1300
1292
  {
1301
1293
  sendMessage: (message, options) => {
1302
- session.sendCustomMessage(message, options).catch(e => {
1294
+ const sendPromise = session.sendCustomMessage(message, options).catch(e => {
1303
1295
  logger.error("Extension sendMessage failed", {
1304
1296
  error: e instanceof Error ? e.message : String(e),
1305
1297
  });
1306
1298
  });
1299
+ pendingExtensionMessages.push(sendPromise);
1307
1300
  },
1308
1301
  sendUserMessage: (content, options) => {
1309
- session.sendUserMessage(content, options).catch(e => {
1302
+ const sendPromise = session.sendUserMessage(content, options).catch(e => {
1310
1303
  logger.error("Extension sendUserMessage failed", {
1311
1304
  error: e instanceof Error ? e.message : String(e),
1312
1305
  });
1313
1306
  });
1307
+ pendingExtensionMessages.push(sendPromise);
1314
1308
  },
1315
1309
  appendEntry: (customType, data) => {
1316
1310
  session.sessionManager.appendCustomEntry(customType, data);
@@ -1346,6 +1340,9 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1346
1340
  logger.error("Extension error", { path: err.extensionPath, error: err.error });
1347
1341
  });
1348
1342
  await awaitAbortable(extensionRunner.emit({ type: "session_start" }));
1343
+ while (pendingExtensionMessages.length > 0) {
1344
+ await awaitAbortable(Promise.all(pendingExtensionMessages.splice(0)));
1345
+ }
1349
1346
  }
1350
1347
 
1351
1348
  const MAX_YIELD_RETRIES = 3;
@@ -1518,7 +1515,8 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1518
1515
  // Use final output if available, otherwise accumulated output
1519
1516
  let rawOutput = finalOutputChunks.length > 0 ? finalOutputChunks.join("") : outputChunks.join("");
1520
1517
  const yieldItems = progress.extractedToolData?.yield as YieldItem[] | undefined;
1521
- 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);
1522
1520
  const finalized = finalizeSubprocessOutput({
1523
1521
  rawOutput,
1524
1522
  exitCode,
package/src/task/index.ts CHANGED
@@ -558,6 +558,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
558
558
  const commitStyle = this.session.settings.get("task.isolation.commits");
559
559
  const maxConcurrency = this.session.settings.get("task.maxConcurrency");
560
560
  const taskDepth = this.session.taskDepth ?? 0;
561
+ const subagentLspEnabled = (this.session.enableLsp ?? true) && this.session.settings.get("task.enableLsp");
561
562
 
562
563
  if (isolationMode === "none" && "isolated" in params) {
563
564
  return {
@@ -843,6 +844,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
843
844
  file => path.basename(file.path).toLowerCase() !== "agents.md",
844
845
  );
845
846
  const promptTemplates = this.session.promptTemplates;
847
+ const parentEvalSessionId = this.session.getEvalSessionId?.() ?? undefined;
846
848
 
847
849
  // Initialize progress for all tasks
848
850
  for (let i = 0; i < tasksWithUniqueIds.length; i++) {
@@ -872,7 +874,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
872
874
  if (!isIsolated) {
873
875
  return runSubprocess({
874
876
  cwd: this.session.cwd,
875
- agent,
877
+ agent: effectiveAgent,
876
878
  task: renderSubagentUserPrompt(task.assignment, simpleMode),
877
879
  assignment: task.assignment.trim(),
878
880
  context: sharedContext,
@@ -888,7 +890,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
888
890
  persistArtifacts: !!artifactsDir,
889
891
  artifactsDir: effectiveArtifactsDir,
890
892
  contextFile: contextFilePath,
891
- enableLsp: false,
893
+ enableLsp: subagentLspEnabled,
892
894
  signal,
893
895
  eventBus: this.session.eventBus,
894
896
  onProgress: progress => {
@@ -910,6 +912,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
910
912
  parentArtifactManager,
911
913
  parentHindsightSessionState: this.session.getHindsightSessionState?.(),
912
914
  parentTelemetry: this.session.getTelemetry?.(),
915
+ parentEvalSessionId,
913
916
  });
914
917
  }
915
918
 
@@ -927,7 +930,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
927
930
  const result = await runSubprocess({
928
931
  cwd: this.session.cwd,
929
932
  worktree: isolationDir,
930
- agent,
933
+ agent: effectiveAgent,
931
934
  task: renderSubagentUserPrompt(task.assignment, simpleMode),
932
935
  assignment: task.assignment.trim(),
933
936
  context: sharedContext,
@@ -943,7 +946,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
943
946
  persistArtifacts: !!artifactsDir,
944
947
  artifactsDir: effectiveArtifactsDir,
945
948
  contextFile: contextFilePath,
946
- enableLsp: false,
949
+ enableLsp: subagentLspEnabled,
947
950
  signal,
948
951
  eventBus: this.session.eventBus,
949
952
  onProgress: progress => {
@@ -965,6 +968,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
965
968
  parentArtifactManager,
966
969
  parentHindsightSessionState: this.session.getHindsightSessionState?.(),
967
970
  parentTelemetry: this.session.getTelemetry?.(),
971
+ parentEvalSessionId,
968
972
  });
969
973
  if (mergeMode === "branch" && result.exitCode === 0) {
970
974
  try {