@oh-my-pi/pi-coding-agent 4.2.0 → 4.2.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 (64) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/docs/sdk.md +5 -5
  3. package/examples/sdk/10-settings.ts +2 -2
  4. package/package.json +5 -5
  5. package/src/capability/fs.ts +90 -0
  6. package/src/capability/index.ts +41 -227
  7. package/src/capability/types.ts +1 -11
  8. package/src/cli/args.ts +4 -0
  9. package/src/core/agent-session.ts +7 -7
  10. package/src/core/agent-storage.ts +50 -0
  11. package/src/core/auth-storage.ts +102 -3
  12. package/src/core/bash-executor.ts +1 -1
  13. package/src/core/custom-tools/loader.ts +2 -2
  14. package/src/core/export-html/index.ts +1 -33
  15. package/src/core/extensions/loader.ts +2 -2
  16. package/src/core/extensions/types.ts +1 -1
  17. package/src/core/hooks/loader.ts +2 -2
  18. package/src/core/mcp/config.ts +2 -2
  19. package/src/core/model-registry.ts +46 -0
  20. package/src/core/sdk.ts +37 -29
  21. package/src/core/settings-manager.ts +152 -135
  22. package/src/core/skills.ts +72 -51
  23. package/src/core/slash-commands.ts +3 -3
  24. package/src/core/system-prompt.ts +52 -10
  25. package/src/core/tools/complete.ts +5 -2
  26. package/src/core/tools/edit.ts +7 -4
  27. package/src/core/tools/index.test.ts +16 -0
  28. package/src/core/tools/index.ts +21 -8
  29. package/src/core/tools/lsp/index.ts +4 -1
  30. package/src/core/tools/ssh.ts +6 -6
  31. package/src/core/tools/task/commands.ts +3 -9
  32. package/src/core/tools/task/executor.ts +88 -3
  33. package/src/core/tools/task/index.ts +4 -0
  34. package/src/core/tools/task/model-resolver.ts +10 -7
  35. package/src/core/tools/task/worker-protocol.ts +48 -2
  36. package/src/core/tools/task/worker.ts +152 -7
  37. package/src/core/tools/write.ts +7 -4
  38. package/src/discovery/agents-md.ts +13 -19
  39. package/src/discovery/builtin.ts +368 -293
  40. package/src/discovery/claude.ts +183 -345
  41. package/src/discovery/cline.ts +30 -10
  42. package/src/discovery/codex.ts +188 -272
  43. package/src/discovery/cursor.ts +106 -121
  44. package/src/discovery/gemini.ts +72 -97
  45. package/src/discovery/github.ts +7 -10
  46. package/src/discovery/helpers.ts +114 -57
  47. package/src/discovery/index.ts +1 -2
  48. package/src/discovery/mcp-json.ts +15 -18
  49. package/src/discovery/ssh.ts +9 -17
  50. package/src/discovery/vscode.ts +10 -5
  51. package/src/discovery/windsurf.ts +52 -86
  52. package/src/main.ts +5 -1
  53. package/src/modes/interactive/components/extensions/extension-dashboard.ts +24 -11
  54. package/src/modes/interactive/components/extensions/state-manager.ts +19 -15
  55. package/src/modes/interactive/controllers/selector-controller.ts +9 -5
  56. package/src/modes/interactive/interactive-mode.ts +22 -15
  57. package/src/prompts/agents/plan.md +107 -30
  58. package/src/prompts/agents/task.md +5 -4
  59. package/src/prompts/system/system-prompt.md +5 -0
  60. package/src/prompts/tools/task.md +25 -19
  61. package/src/utils/shell.ts +2 -2
  62. package/src/prompts/agents/architect-plan.md +0 -10
  63. package/src/prompts/agents/implement-with-critic.md +0 -11
  64. package/src/prompts/agents/implement.md +0 -11
@@ -5,7 +5,11 @@
5
5
  */
6
6
 
7
7
  import type { AgentEvent } from "@oh-my-pi/pi-agent-core";
8
+ import type { AuthStorage } from "../../auth-storage";
8
9
  import type { EventBus } from "../../event-bus";
10
+ import { callTool } from "../../mcp/client";
11
+ import type { MCPManager } from "../../mcp/manager";
12
+ import type { ModelRegistry } from "../../model-registry";
9
13
  import { ensureArtifactsDir, getArtifactPaths } from "./artifacts";
10
14
  import { resolveModelPattern } from "./model-resolver";
11
15
  import { subprocessToolRegistry } from "./subprocess-tool-registry";
@@ -18,7 +22,12 @@ import {
18
22
  TASK_SUBAGENT_EVENT_CHANNEL,
19
23
  TASK_SUBAGENT_PROGRESS_CHANNEL,
20
24
  } from "./types";
21
- import type { SubagentWorkerRequest, SubagentWorkerResponse } from "./worker-protocol";
25
+ import type {
26
+ MCPToolCallRequest,
27
+ MCPToolMetadata,
28
+ SubagentWorkerRequest,
29
+ SubagentWorkerResponse,
30
+ } from "./worker-protocol";
22
31
 
23
32
  /** Options for worker execution */
24
33
  export interface ExecutorOptions {
@@ -31,12 +40,16 @@ export interface ExecutorOptions {
31
40
  context?: string;
32
41
  modelOverride?: string;
33
42
  outputSchema?: unknown;
43
+ enableLsp?: boolean;
34
44
  signal?: AbortSignal;
35
45
  onProgress?: (progress: AgentProgress) => void;
36
46
  sessionFile?: string | null;
37
47
  persistArtifacts?: boolean;
38
48
  artifactsDir?: string;
39
49
  eventBus?: EventBus;
50
+ mcpManager?: MCPManager;
51
+ authStorage?: AuthStorage;
52
+ modelRegistry?: ModelRegistry;
40
53
  }
41
54
 
42
55
  /**
@@ -133,11 +146,45 @@ function getUsageTokens(usage: unknown): number {
133
146
  return input + output + cacheRead + cacheWrite;
134
147
  }
135
148
 
149
+ /**
150
+ * Parse MCP tool name to extract server and tool names.
151
+ * Format: mcp_<serverName>_<toolName>
152
+ * Note: Uses lastIndexOf to handle server names with underscores.
153
+ */
154
+ function parseMCPToolName(fullName: string): { serverName: string; toolName: string } | undefined {
155
+ if (!fullName.startsWith("mcp_")) return undefined;
156
+ const rest = fullName.slice(4);
157
+ const underscoreIndex = rest.lastIndexOf("_");
158
+ if (underscoreIndex === -1) return undefined;
159
+ return {
160
+ serverName: rest.slice(0, underscoreIndex),
161
+ toolName: rest.slice(underscoreIndex + 1),
162
+ };
163
+ }
164
+
165
+ /**
166
+ * Extract MCP tool metadata from MCPManager for passing to worker.
167
+ */
168
+ function extractMCPToolMetadata(mcpManager: MCPManager): MCPToolMetadata[] {
169
+ return mcpManager.getTools().map((tool) => {
170
+ const parsed = parseMCPToolName(tool.name);
171
+ return {
172
+ name: tool.name,
173
+ label: tool.label ?? tool.name,
174
+ description: tool.description ?? "",
175
+ parameters: tool.parameters,
176
+ serverName: parsed?.serverName ?? "",
177
+ mcpToolName: parsed?.toolName ?? "",
178
+ };
179
+ });
180
+ }
181
+
136
182
  /**
137
183
  * Run a single agent in a worker.
138
184
  */
139
185
  export async function runSubprocess(options: ExecutorOptions): Promise<SingleResult> {
140
- const { cwd, agent, task, index, taskId, context, modelOverride, outputSchema, signal, onProgress } = options;
186
+ const { cwd, agent, task, index, taskId, context, modelOverride, outputSchema, enableLsp, signal, onProgress } =
187
+ options;
141
188
  const startTime = Date.now();
142
189
 
143
190
  // Initialize progress
@@ -208,7 +255,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
208
255
  }
209
256
 
210
257
  // Resolve and add model
211
- const resolvedModel = resolveModelPattern(modelOverride || agent.model);
258
+ const resolvedModel = await resolveModelPattern(modelOverride || agent.model);
212
259
  const sessionFile = subtaskSessionFile ?? options.sessionFile ?? null;
213
260
  const spawnsEnv = agent.spawns === undefined ? "" : agent.spawns === "*" ? "*" : agent.spawns.join(",");
214
261
 
@@ -535,6 +582,10 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
535
582
  outputSchema,
536
583
  sessionFile,
537
584
  spawnsEnv,
585
+ enableLsp,
586
+ serializedAuth: options.authStorage?.serialize(),
587
+ serializedModels: options.modelRegistry?.serialize(),
588
+ mcpTools: options.mcpManager ? extractMCPToolMetadata(options.mcpManager) : undefined,
538
589
  },
539
590
  };
540
591
 
@@ -556,9 +607,43 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
556
607
  cleanup();
557
608
  resolve(message);
558
609
  };
610
+ const handleMCPCall = async (request: MCPToolCallRequest) => {
611
+ const mcpManager = options.mcpManager;
612
+ if (!mcpManager) {
613
+ worker.postMessage({
614
+ type: "mcp_tool_result",
615
+ callId: request.callId,
616
+ error: "MCP not available",
617
+ });
618
+ return;
619
+ }
620
+ try {
621
+ const parsed = parseMCPToolName(request.toolName);
622
+ if (!parsed) throw new Error(`Invalid MCP tool name: ${request.toolName}`);
623
+ const connection = mcpManager.getConnection(parsed.serverName);
624
+ if (!connection) throw new Error(`MCP server not connected: ${parsed.serverName}`);
625
+ const result = await callTool(connection, parsed.toolName, request.params);
626
+ worker.postMessage({
627
+ type: "mcp_tool_result",
628
+ callId: request.callId,
629
+ result: { content: result.content ?? [], isError: result.isError },
630
+ });
631
+ } catch (error) {
632
+ worker.postMessage({
633
+ type: "mcp_tool_result",
634
+ callId: request.callId,
635
+ error: error instanceof Error ? error.message : String(error),
636
+ });
637
+ }
638
+ };
639
+
559
640
  const onMessage = (event: WorkerMessageEvent<SubagentWorkerResponse>) => {
560
641
  const message = event.data;
561
642
  if (!message || resolved) return;
643
+ if (message.type === "mcp_tool_call") {
644
+ handleMCPCall(message as MCPToolCallRequest);
645
+ return;
646
+ }
562
647
  if (message.type === "event") {
563
648
  try {
564
649
  processEvent(message.event);
@@ -349,12 +349,16 @@ export async function createTaskTool(
349
349
  sessionFile,
350
350
  persistArtifacts: !!artifactsDir,
351
351
  artifactsDir: effectiveArtifactsDir,
352
+ enableLsp: false,
352
353
  signal,
353
354
  eventBus: undefined,
354
355
  onProgress: (progress) => {
355
356
  progressMap.set(index, structuredClone(progress));
356
357
  emitProgress();
357
358
  },
359
+ authStorage: session.authStorage,
360
+ modelRegistry: session.modelRegistry,
361
+ mcpManager: session.mcpManager,
358
362
  });
359
363
  },
360
364
  signal,
@@ -12,7 +12,7 @@
12
12
  */
13
13
 
14
14
  import { type Settings, settingsCapability } from "../../../capability/settings";
15
- import { loadSync } from "../../../discovery";
15
+ import { loadCapability } from "../../../discovery";
16
16
  import { resolveOmpCommand } from "./omp-command";
17
17
 
18
18
  /** Cache for available models (provider/modelId format) */
@@ -79,8 +79,8 @@ export function clearModelCache(): void {
79
79
  /**
80
80
  * Load model roles from settings files using capability API.
81
81
  */
82
- function loadModelRoles(): Record<string, string> {
83
- const result = loadSync<Settings>(settingsCapability.id, { cwd: process.cwd() });
82
+ async function loadModelRoles(): Promise<Record<string, string>> {
83
+ const result = await loadCapability<Settings>(settingsCapability.id, { cwd: process.cwd() });
84
84
 
85
85
  // Merge all settings, prioritizing first (highest priority)
86
86
  let modelRoles: Record<string, string> = {};
@@ -99,8 +99,8 @@ function loadModelRoles(): Record<string, string> {
99
99
  * Looks up the role in settings.modelRoles and returns the configured model.
100
100
  * Returns undefined if the role isn't configured.
101
101
  */
102
- function resolveOmpAlias(role: string, availableModels: string[]): string | undefined {
103
- const roles = loadModelRoles();
102
+ async function resolveOmpAlias(role: string, availableModels: string[]): Promise<string | undefined> {
103
+ const roles = await loadModelRoles();
104
104
 
105
105
  // Look up role in settings (case-insensitive)
106
106
  const configured = roles[role] || roles[role.toLowerCase()];
@@ -127,7 +127,10 @@ function getModelId(fullModel: string): string {
127
127
  * @param pattern - Model pattern to resolve
128
128
  * @param availableModels - Optional pre-fetched list of available models (in provider/modelId format)
129
129
  */
130
- export function resolveModelPattern(pattern: string | undefined, availableModels?: string[]): string | undefined {
130
+ export async function resolveModelPattern(
131
+ pattern: string | undefined,
132
+ availableModels?: string[],
133
+ ): Promise<string | undefined> {
131
134
  if (!pattern || pattern === "default") {
132
135
  return undefined;
133
136
  }
@@ -149,7 +152,7 @@ export function resolveModelPattern(pattern: string | undefined, availableModels
149
152
  const lower = p.toLowerCase();
150
153
  if (lower.startsWith("omp/") || lower.startsWith("pi/")) {
151
154
  const role = lower.startsWith("omp/") ? p.slice(4) : p.slice(3);
152
- const resolved = resolveOmpAlias(role, models);
155
+ const resolved = await resolveOmpAlias(role, models);
153
156
  if (resolved) return resolved;
154
157
  continue; // Role not configured, try next pattern
155
158
  }
@@ -1,4 +1,42 @@
1
1
  import type { AgentEvent } from "@oh-my-pi/pi-agent-core";
2
+ import type { SerializedAuthStorage } from "../../auth-storage";
3
+ import type { SerializedModelRegistry } from "../../model-registry";
4
+
5
+ /**
6
+ * MCP tool metadata passed from parent to worker for proxy tool creation.
7
+ */
8
+ export interface MCPToolMetadata {
9
+ name: string;
10
+ label: string;
11
+ description: string;
12
+ parameters: unknown;
13
+ serverName: string;
14
+ mcpToolName: string;
15
+ timeoutMs?: number;
16
+ }
17
+
18
+ /**
19
+ * Worker -> Parent: request to execute an MCP tool via parent's connection.
20
+ */
21
+ export interface MCPToolCallRequest {
22
+ type: "mcp_tool_call";
23
+ callId: string;
24
+ toolName: string;
25
+ params: Record<string, unknown>;
26
+ }
27
+
28
+ /**
29
+ * Parent -> Worker: result of an MCP tool call.
30
+ */
31
+ export interface MCPToolCallResponse {
32
+ type: "mcp_tool_result";
33
+ callId: string;
34
+ result?: {
35
+ content: Array<{ type: string; text?: string; [key: string]: unknown }>;
36
+ isError?: boolean;
37
+ };
38
+ error?: string;
39
+ }
2
40
 
3
41
  export interface SubagentWorkerStartPayload {
4
42
  cwd: string;
@@ -7,12 +45,20 @@ export interface SubagentWorkerStartPayload {
7
45
  model?: string;
8
46
  toolNames?: string[];
9
47
  outputSchema?: unknown;
48
+ enableLsp?: boolean;
10
49
  sessionFile?: string | null;
11
50
  spawnsEnv?: string;
51
+ serializedAuth?: SerializedAuthStorage;
52
+ serializedModels?: SerializedModelRegistry;
53
+ mcpTools?: MCPToolMetadata[];
12
54
  }
13
55
 
14
- export type SubagentWorkerRequest = { type: "start"; payload: SubagentWorkerStartPayload } | { type: "abort" };
56
+ export type SubagentWorkerRequest =
57
+ | { type: "start"; payload: SubagentWorkerStartPayload }
58
+ | { type: "abort" }
59
+ | MCPToolCallResponse;
15
60
 
16
61
  export type SubagentWorkerResponse =
17
62
  | { type: "event"; event: AgentEvent }
18
- | { type: "done"; exitCode: number; durationMs: number; error?: string; aborted?: boolean };
63
+ | { type: "done"; exitCode: number; durationMs: number; error?: string; aborted?: boolean }
64
+ | MCPToolCallRequest;
@@ -15,12 +15,22 @@
15
15
 
16
16
  import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
17
17
  import type { Api, Model } from "@oh-my-pi/pi-ai";
18
+ import type { TSchema } from "@sinclair/typebox";
18
19
  import type { AgentSessionEvent } from "../../agent-session";
20
+ import { AuthStorage } from "../../auth-storage";
21
+ import type { CustomTool } from "../../custom-tools/types";
22
+ import { ModelRegistry } from "../../model-registry";
19
23
  import { parseModelPattern, parseModelString } from "../../model-resolver";
20
24
  import { createAgentSession, discoverAuthStorage, discoverModels } from "../../sdk";
21
25
  import { SessionManager } from "../../session-manager";
22
26
  import { untilAborted } from "../../utils";
23
- import type { SubagentWorkerRequest, SubagentWorkerResponse, SubagentWorkerStartPayload } from "./worker-protocol";
27
+ import type {
28
+ MCPToolCallResponse,
29
+ MCPToolMetadata,
30
+ SubagentWorkerRequest,
31
+ SubagentWorkerResponse,
32
+ SubagentWorkerStartPayload,
33
+ } from "./worker-protocol";
24
34
 
25
35
  type PostMessageFn = (message: SubagentWorkerResponse) => void;
26
36
 
@@ -32,6 +42,120 @@ const postMessageSafe: PostMessageFn = (message) => {
32
42
  }
33
43
  };
34
44
 
45
+ interface PendingMCPCall {
46
+ resolve: (result: MCPToolCallResponse["result"]) => void;
47
+ reject: (error: Error) => void;
48
+ timeoutId: ReturnType<typeof setTimeout>;
49
+ }
50
+
51
+ const pendingMCPCalls = new Map<string, PendingMCPCall>();
52
+ const MCP_CALL_TIMEOUT_MS = 60_000;
53
+ let mcpCallIdCounter = 0;
54
+
55
+ function generateMCPCallId(): string {
56
+ return `mcp_${Date.now()}_${++mcpCallIdCounter}`;
57
+ }
58
+
59
+ function callMCPToolViaParent(
60
+ toolName: string,
61
+ params: Record<string, unknown>,
62
+ signal?: AbortSignal,
63
+ timeoutMs = MCP_CALL_TIMEOUT_MS,
64
+ ): Promise<{ content: Array<{ type: string; text?: string; [key: string]: unknown }>; isError?: boolean }> {
65
+ return new Promise((resolve, reject) => {
66
+ const callId = generateMCPCallId();
67
+ if (signal?.aborted) {
68
+ reject(new Error("Aborted"));
69
+ return;
70
+ }
71
+
72
+ const timeoutId = setTimeout(() => {
73
+ pendingMCPCalls.delete(callId);
74
+ reject(new Error(`MCP call timed out after ${timeoutMs}ms`));
75
+ }, timeoutMs);
76
+
77
+ const cleanup = () => {
78
+ clearTimeout(timeoutId);
79
+ pendingMCPCalls.delete(callId);
80
+ };
81
+
82
+ signal?.addEventListener(
83
+ "abort",
84
+ () => {
85
+ cleanup();
86
+ reject(new Error("Aborted"));
87
+ },
88
+ { once: true },
89
+ );
90
+
91
+ pendingMCPCalls.set(callId, {
92
+ resolve: (result) => {
93
+ cleanup();
94
+ resolve(result ?? { content: [] });
95
+ },
96
+ reject: (error) => {
97
+ cleanup();
98
+ reject(error);
99
+ },
100
+ timeoutId,
101
+ });
102
+
103
+ postMessageSafe({
104
+ type: "mcp_tool_call",
105
+ callId,
106
+ toolName,
107
+ params,
108
+ } as SubagentWorkerResponse);
109
+ });
110
+ }
111
+
112
+ function handleMCPToolResult(response: MCPToolCallResponse): void {
113
+ const pending = pendingMCPCalls.get(response.callId);
114
+ if (!pending) return;
115
+ if (response.error) {
116
+ pending.reject(new Error(response.error));
117
+ } else {
118
+ pending.resolve(response.result);
119
+ }
120
+ }
121
+
122
+ function createMCPProxyTool(metadata: MCPToolMetadata): CustomTool<TSchema> {
123
+ return {
124
+ name: metadata.name,
125
+ label: metadata.label,
126
+ description: metadata.description,
127
+ parameters: metadata.parameters as TSchema,
128
+ execute: async (_toolCallId, params, _onUpdate, _ctx, signal) => {
129
+ try {
130
+ const result = await callMCPToolViaParent(
131
+ metadata.name,
132
+ params as Record<string, unknown>,
133
+ signal,
134
+ metadata.timeoutMs,
135
+ );
136
+ return {
137
+ content: result.content.map((c) =>
138
+ c.type === "text"
139
+ ? { type: "text" as const, text: c.text ?? "" }
140
+ : { type: "text" as const, text: JSON.stringify(c) },
141
+ ),
142
+ details: { serverName: metadata.serverName, mcpToolName: metadata.mcpToolName, isError: result.isError },
143
+ };
144
+ } catch (error) {
145
+ return {
146
+ content: [
147
+ {
148
+ type: "text" as const,
149
+ text: `MCP error: ${error instanceof Error ? error.message : String(error)}`,
150
+ },
151
+ ],
152
+ details: { serverName: metadata.serverName, mcpToolName: metadata.mcpToolName, isError: true },
153
+ };
154
+ }
155
+ },
156
+ };
157
+ }
158
+
35
159
  interface WorkerMessageEvent<T> {
36
160
  data: T;
37
161
  }
@@ -145,11 +269,22 @@ async function runTask(runState: RunState, payload: SubagentWorkerStartPayload):
145
269
  // Set working directory (CLI does this implicitly)
146
270
  process.chdir(payload.cwd);
147
271
 
148
- // Discover auth and models (equivalent to CLI's discoverAuthStorage/discoverModels)
149
- const authStorage = await discoverAuthStorage();
150
- checkAbort();
151
- const modelRegistry = await discoverModels(authStorage);
152
- checkAbort();
272
+ // Use serialized auth/models if provided, otherwise discover from disk
273
+ let authStorage: AuthStorage;
274
+ let modelRegistry: ModelRegistry;
275
+
276
+ if (payload.serializedAuth && payload.serializedModels) {
277
+ authStorage = AuthStorage.fromSerialized(payload.serializedAuth);
278
+ modelRegistry = ModelRegistry.fromSerialized(payload.serializedModels, authStorage);
279
+ } else {
280
+ authStorage = await discoverAuthStorage();
281
+ checkAbort();
282
+ modelRegistry = await discoverModels(authStorage);
283
+ checkAbort();
284
+ }
285
+
286
+ // Create MCP proxy tools if provided
287
+ const mcpProxyTools = payload.mcpTools?.map(createMCPProxyTool) ?? [];
153
288
 
154
289
  // Resolve model override (equivalent to CLI's parseModelPattern with --model)
155
290
  const { model, thinkingLevel } = resolveModelOverride(payload.model, modelRegistry);
@@ -180,6 +315,11 @@ async function runTask(runState: RunState, payload: SubagentWorkerStartPayload):
180
315
  hasUI: false,
181
316
  // Pass spawn restrictions to nested tasks
182
317
  spawns: payload.spawnsEnv,
318
+ enableLsp: payload.enableLsp ?? true,
319
+ // Disable local MCP discovery if using proxy tools
320
+ enableMCP: !payload.mcpTools,
321
+ // Add MCP proxy tools
322
+ customTools: mcpProxyTools.length > 0 ? mcpProxyTools : undefined,
183
323
  });
184
324
 
185
325
  runState.session = session;
@@ -394,7 +534,7 @@ self.addEventListener("messageerror", () => {
394
534
  reportFatal("Failed to deserialize parent message");
395
535
  });
396
536
 
397
- // Message handler - receives start/abort commands from parent
537
+ // Message handler - receives start/abort/mcp_tool_result commands from parent
398
538
  globalThis.addEventListener("message", (event: WorkerMessageEvent<SubagentWorkerRequest>) => {
399
539
  const message = event.data;
400
540
  if (!message) return;
@@ -404,6 +544,11 @@ globalThis.addEventListener("message", (event: WorkerMessageEvent<SubagentWorker
404
544
  return;
405
545
  }
406
546
 
547
+ if (message.type === "mcp_tool_result") {
548
+ handleMCPToolResult(message);
549
+ return;
550
+ }
551
+
407
552
  if (message.type === "start") {
408
553
  // Only allow one task per worker
409
554
  if (activeRun) return;
@@ -8,7 +8,7 @@ import type { RenderResultOptions } from "../custom-tools/types";
8
8
  import { renderPromptTemplate } from "../prompt-templates";
9
9
  import type { ToolSession } from "../sdk";
10
10
  import { untilAborted } from "../utils";
11
- import { createLspWritethrough, type FileDiagnosticsResult } from "./lsp/index";
11
+ import { createLspWritethrough, type FileDiagnosticsResult, writethroughNoop } from "./lsp/index";
12
12
  import { resolveToCwd } from "./path-utils";
13
13
  import { formatDiagnostics, replaceTabs, shortenPath } from "./render-utils";
14
14
 
@@ -23,9 +23,12 @@ export interface WriteToolDetails {
23
23
  }
24
24
 
25
25
  export function createWriteTool(session: ToolSession): AgentTool<typeof writeSchema, WriteToolDetails> {
26
- const enableFormat = session.settings?.getLspFormatOnWrite() ?? true;
27
- const enableDiagnostics = session.settings?.getLspDiagnosticsOnWrite() ?? true;
28
- const writethrough = createLspWritethrough(session.cwd, { enableFormat, enableDiagnostics });
26
+ const enableLsp = session.enableLsp ?? true;
27
+ const enableFormat = enableLsp ? (session.settings?.getLspFormatOnWrite() ?? true) : false;
28
+ const enableDiagnostics = enableLsp ? (session.settings?.getLspDiagnosticsOnWrite() ?? true) : false;
29
+ const writethrough = enableLsp
30
+ ? createLspWritethrough(session.cwd, { enableFormat, enableDiagnostics })
31
+ : writethroughNoop;
29
32
  return {
30
33
  name: "write",
31
34
  label: "Write",
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { dirname, join, sep } from "node:path";
10
10
  import { type ContextFile, contextFileCapability } from "../capability/context-file";
11
+ import { readFile } from "../capability/fs";
11
12
  import { registerProvider } from "../capability/index";
12
13
  import type { LoadContext, LoadResult } from "../capability/types";
13
14
  import { calculateDepth, createSourceMeta } from "./helpers";
@@ -19,7 +20,7 @@ const MAX_DEPTH = 20; // Prevent walking up excessively far from cwd
19
20
  /**
20
21
  * Load standalone AGENTS.md files.
21
22
  */
22
- function loadAgentsMd(ctx: LoadContext): LoadResult<ContextFile> {
23
+ async function loadAgentsMd(ctx: LoadContext): Promise<LoadResult<ContextFile>> {
23
24
  const items: ContextFile[] = [];
24
25
  const warnings: string[] = [];
25
26
 
@@ -29,30 +30,23 @@ function loadAgentsMd(ctx: LoadContext): LoadResult<ContextFile> {
29
30
 
30
31
  while (depth < MAX_DEPTH) {
31
32
  const candidate = join(current, "AGENTS.md");
33
+ const content = await readFile(candidate);
32
34
 
33
- if (ctx.fs.isFile(candidate)) {
34
- // Skip if it's inside a config directory (handled by other providers)
35
+ if (content !== null) {
35
36
  const parent = dirname(candidate);
36
37
  const baseName = parent.split(sep).pop() ?? "";
37
38
 
38
- // Skip if inside .codex, .gemini, or other config dirs
39
39
  if (!baseName.startsWith(".")) {
40
- const content = ctx.fs.readFile(candidate);
40
+ const fileDir = dirname(candidate);
41
+ const calculatedDepth = calculateDepth(ctx.cwd, fileDir, sep);
41
42
 
42
- if (content === null) {
43
- warnings.push(`Failed to read: ${candidate}`);
44
- } else {
45
- const fileDir = dirname(candidate);
46
- const calculatedDepth = calculateDepth(ctx.cwd, fileDir, sep);
47
-
48
- items.push({
49
- path: candidate,
50
- content,
51
- level: "project",
52
- depth: calculatedDepth,
53
- _source: createSourceMeta(PROVIDER_ID, candidate, "project"),
54
- });
55
- }
43
+ items.push({
44
+ path: candidate,
45
+ content,
46
+ level: "project",
47
+ depth: calculatedDepth,
48
+ _source: createSourceMeta(PROVIDER_ID, candidate, "project"),
49
+ });
56
50
  }
57
51
  }
58
52