@earendil-works/pi-coding-agent 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +75 -0
- package/dist/bundle/chunks/{anthropic-messages-Y22YDKSW.js → anthropic-messages-KT2ID7DZ.js} +1 -1
- package/dist/bundle/chunks/anthropic.js +1 -1
- package/dist/bundle/chunks/{azure-openai-responses-YJDJIIDQ.js → azure-openai-responses-6A4PNZTH.js} +1 -1
- package/dist/bundle/chunks/bedrock-converse-stream.js +2 -2
- package/dist/bundle/chunks/chunk-33CRHCAK.js +2 -0
- package/dist/bundle/chunks/{chunk-UBHCV62G.js → chunk-BK6FEHYX.js} +3 -3
- package/dist/bundle/chunks/chunk-CH4ZBH45.js +11 -0
- package/dist/bundle/chunks/{chunk-6DBOMCBS.js → chunk-GXLAGZ3Z.js} +1 -1
- package/dist/bundle/chunks/{chunk-J52J6TFP.js → chunk-HKYL362W.js} +1 -1
- package/dist/bundle/chunks/chunk-LPXNMA2W.js +2 -0
- package/dist/bundle/chunks/{chunk-TAOL3JFA.js → chunk-LTSARG5Q.js} +1 -1
- package/dist/bundle/chunks/{chunk-ZSQEXT6X.js → chunk-MXTZIDYW.js} +4 -4
- package/dist/bundle/chunks/chunk-O7YIWAPD.js +2 -0
- package/dist/bundle/chunks/{chunk-BFNE7BHG.js → chunk-OIM2DMFI.js} +83 -72
- package/dist/bundle/chunks/{chunk-AXOERKKW.js → chunk-OTL6MGLJ.js} +6 -4
- package/dist/bundle/chunks/{chunk-LD2P6SFZ.js → chunk-PMRZO46Y.js} +1 -1
- package/dist/bundle/chunks/{chunk-7TP2J6F2.js → chunk-SYCGMWFH.js} +1 -1
- package/dist/bundle/chunks/{chunk-2VBQLN7Y.js → chunk-UH5J7I7X.js} +9 -6
- package/dist/bundle/chunks/{chunk-WE4MXUQS.js → chunk-VLRCF7MR.js} +1 -1
- package/dist/bundle/chunks/chunk-W5MPVSHA.js +82 -0
- package/dist/bundle/chunks/chunk-Y4NYF362.js +2 -0
- package/dist/bundle/chunks/{cli-HTJP6F6Z.js → cli-OQYLWLA3.js} +2 -2
- package/dist/bundle/chunks/cloudflare-workers-ai-system-one-U3AH4ODQ.js +2 -0
- package/dist/bundle/chunks/codemode-worker.js +101 -5
- package/dist/bundle/chunks/{easter-egg-3d-6YCNESG3.js → easter-egg-3d-TQFQS342.js} +1 -1
- package/dist/bundle/chunks/{execute-JRUOLYDA.js → execute-7IRB5TUX.js} +110 -8
- package/dist/bundle/chunks/github-copilot.js +1 -1
- package/dist/bundle/chunks/{google-generative-ai-XMFHGQC5.js → google-generative-ai-X2VFFS4I.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-4XRRNP74.js → google-vertex-KMGHFTSP.js} +1 -1
- package/dist/bundle/chunks/image-resize-worker.js +1 -1
- package/dist/bundle/chunks/{llama-cpp-classify-XT44PI3C.js → llama-cpp-classify-FYN623RH.js} +2 -2
- package/dist/bundle/chunks/{mistral-conversations-Q22M3IBG.js → mistral-conversations-PRZLM26U.js} +2 -2
- package/dist/bundle/chunks/openai-chatgpt.js +1 -1
- package/dist/bundle/chunks/{openai-codex-responses-4MYQSO5E.js → openai-codex-responses-ZSLJZYOE.js} +2 -2
- package/dist/bundle/chunks/openai-codex.js +1 -1
- package/dist/bundle/chunks/{openai-completions-OSRCWAAA.js → openai-completions-WWQW7S3V.js} +1 -1
- package/dist/bundle/chunks/openai-decisions-QLZKZLAK.js +5 -0
- package/dist/bundle/chunks/{openai-responses-UHF3DHNG.js → openai-responses-KV7QHJY6.js} +1 -1
- package/dist/bundle/chunks/{openrouter-images-ZVXWCSIM.js → openrouter-images-DUPG5DZO.js} +1 -1
- package/dist/bundle/chunks/{pi-messages-TM5SOOIW.js → pi-messages-YHKPQIHC.js} +1 -1
- package/dist/bundle/chunks/{runtime-NW7GPWKS.js → runtime-ROGZ2EXI.js} +1 -1
- package/dist/bundle/chunks/typesafe-system-one-B4NNEG67.js +2 -0
- package/dist/bundle/chunks/{virtual-modules-CFHJO5SY.js → virtual-modules-4YN2GNVW.js} +1 -1
- package/dist/bundle/cli-runtime.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/cli/args.d.ts +2 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +24 -5
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts +33 -4
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +59 -21
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +40 -45
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
- package/dist/core/export-html/tool-renderer.js +2 -0
- package/dist/core/export-html/tool-renderer.js.map +1 -1
- package/dist/core/extensions/types.d.ts +13 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/mcp-servers.d.ts +8 -0
- package/dist/core/mcp-servers.d.ts.map +1 -1
- package/dist/core/mcp-servers.js +30 -7
- package/dist/core/mcp-servers.js.map +1 -1
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +2 -1
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/nested-tool-calls.d.ts +1 -0
- package/dist/core/nested-tool-calls.d.ts.map +1 -1
- package/dist/core/nested-tool-calls.js +1 -0
- package/dist/core/nested-tool-calls.js.map +1 -1
- package/dist/core/resource-loader.d.ts +3 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +4 -3
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts +13 -4
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +19 -6
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/settings-manager.d.ts +13 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +37 -13
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts +4 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +6 -1
- package/dist/core/skills.js.map +1 -1
- package/dist/core/system-prompt.d.ts +6 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +9 -4
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/read.d.ts +13 -0
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +18 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/renderers/bash.d.ts.map +1 -1
- package/dist/core/tools/renderers/bash.js +8 -3
- package/dist/core/tools/renderers/bash.js.map +1 -1
- package/dist/core/tools/renderers/edit.d.ts.map +1 -1
- package/dist/core/tools/renderers/edit.js +5 -4
- package/dist/core/tools/renderers/edit.js.map +1 -1
- package/dist/extensions/codemode/execute.d.ts.map +1 -1
- package/dist/extensions/codemode/execute.js +65 -8
- package/dist/extensions/codemode/execute.js.map +1 -1
- package/dist/extensions/codemode/index.d.ts.map +1 -1
- package/dist/extensions/codemode/index.js +1 -0
- package/dist/extensions/codemode/index.js.map +1 -1
- package/dist/extensions/codemode/tool.d.ts +9 -2
- package/dist/extensions/codemode/tool.d.ts.map +1 -1
- package/dist/extensions/codemode/tool.js +14 -6
- package/dist/extensions/codemode/tool.js.map +1 -1
- package/dist/extensions/llama/provider.d.ts.map +1 -1
- package/dist/extensions/llama/provider.js +34 -11
- package/dist/extensions/llama/provider.js.map +1 -1
- package/dist/extensions/mcp/cli.js +17 -23
- package/dist/extensions/mcp/cli.js.map +1 -1
- package/dist/extensions/mcp/index.d.ts.map +1 -1
- package/dist/extensions/mcp/index.js +165 -64
- package/dist/extensions/mcp/index.js.map +1 -1
- package/dist/extensions/mcp/oauth.d.ts +5 -3
- package/dist/extensions/mcp/oauth.d.ts.map +1 -1
- package/dist/extensions/mcp/oauth.js +36 -10
- package/dist/extensions/mcp/oauth.js.map +1 -1
- package/dist/extensions/mcp/resources.d.ts +3 -4
- package/dist/extensions/mcp/resources.d.ts.map +1 -1
- package/dist/extensions/mcp/resources.js +3 -4
- package/dist/extensions/mcp/resources.js.map +1 -1
- package/dist/extensions/mcp/runtime.d.ts +4 -1
- package/dist/extensions/mcp/runtime.d.ts.map +1 -1
- package/dist/extensions/mcp/runtime.js +22 -5
- package/dist/extensions/mcp/runtime.js.map +1 -1
- package/dist/extensions/mcp/tools.d.ts +2 -1
- package/dist/extensions/mcp/tools.d.ts.map +1 -1
- package/dist/extensions/mcp/tools.js +2 -1
- package/dist/extensions/mcp/tools.js.map +1 -1
- package/dist/extensions/mcp/ui.d.ts +3 -3
- package/dist/extensions/mcp/ui.d.ts.map +1 -1
- package/dist/extensions/mcp/ui.js +10 -2
- package/dist/extensions/mcp/ui.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +1 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts +5 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +17 -14
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts +2 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js +5 -2
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts +2 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +5 -2
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/custom-entry.d.ts +3 -1
- package/dist/modes/interactive/components/custom-entry.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-entry.js +8 -2
- package/dist/modes/interactive/components/custom-entry.js.map +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js +1 -0
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +1 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.d.ts +2 -1
- package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.js +5 -2
- package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +5 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +10 -0
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +2 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +50 -29
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/program-status-reporter.d.ts +34 -0
- package/dist/modes/interactive/program-status-reporter.d.ts.map +1 -0
- package/dist/modes/interactive/program-status-reporter.js +110 -0
- package/dist/modes/interactive/program-status-reporter.js.map +1 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +1 -0
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +24 -0
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/utils/ansi.d.ts +8 -0
- package/dist/utils/ansi.d.ts.map +1 -1
- package/dist/utils/ansi.js +30 -11
- package/dist/utils/ansi.js.map +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +12 -11
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/image-resize-core.d.ts +12 -0
- package/dist/utils/image-resize-core.d.ts.map +1 -1
- package/dist/utils/image-resize-core.js +5 -0
- package/dist/utils/image-resize-core.js.map +1 -1
- package/dist/utils/image-resize-worker.js +3 -2
- package/dist/utils/image-resize-worker.js.map +1 -1
- package/dist/utils/image-resize.d.ts.map +1 -1
- package/dist/utils/image-resize.js +9 -10
- package/dist/utils/image-resize.js.map +1 -1
- package/dist/utils/syntax-highlight.d.ts.map +1 -1
- package/dist/utils/syntax-highlight.js +6 -1
- package/dist/utils/syntax-highlight.js.map +1 -1
- package/docs/cli.md +18 -6
- package/docs/codemode.md +26 -5
- package/docs/environment-variables.md +1 -0
- package/docs/extensions.md +1 -1
- package/docs/json.md +3 -3
- package/docs/llama-cpp.md +8 -1
- package/docs/mcp.md +3 -1
- package/docs/message-types.md +22 -4
- package/docs/models.md +4 -1
- package/docs/providers.md +27 -0
- package/docs/settings.md +3 -3
- package/docs/terminal-setup.md +14 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +12 -12
- package/dist/bundle/chunks/chunk-A3JYRWB6.js +0 -2
- package/dist/bundle/chunks/chunk-C47CLQZK.js +0 -2
- package/dist/bundle/chunks/chunk-HQHD6PZI.js +0 -2
- package/dist/bundle/chunks/chunk-KJUDFE6Y.js +0 -11
- package/dist/bundle/chunks/chunk-PSQJVYXD.js +0 -82
- package/dist/bundle/chunks/cloudflare-workers-ai-system-one-NM3JRRWQ.js +0 -2
- package/dist/bundle/chunks/typesafe-system-one-PRR5K6CI.js +0 -2
|
@@ -197,8 +197,6 @@ export function createMcpExtension(options = {}) {
|
|
|
197
197
|
let projectConfig;
|
|
198
198
|
/** Registered servers that `mcp.json` overrides, shown in `/mcp`. */
|
|
199
199
|
let overridden = [];
|
|
200
|
-
/** Between session_start and session_shutdown. Registrations before that are read on session_start. */
|
|
201
|
-
let sessionActive = false;
|
|
202
200
|
let autoEnableCodemode = true;
|
|
203
201
|
/** Whether the "codemode tools unreachable" warning was shown since the session started. */
|
|
204
202
|
let warnedUnreachable = false;
|
|
@@ -206,8 +204,6 @@ export function createMcpExtension(options = {}) {
|
|
|
206
204
|
/** Whether a prompt already waited for the startup connections since the session started. */
|
|
207
205
|
let waitedForStartup = false;
|
|
208
206
|
const startupWaitMs = options.startupWaitMs ?? DEFAULT_STARTUP_WAIT_MS;
|
|
209
|
-
/** Bumped on every session start and shutdown so a runtime load that resolves late is dropped. */
|
|
210
|
-
let generation = 0;
|
|
211
207
|
/** Working directory of the session, for stdio servers. */
|
|
212
208
|
let sessionCwd = process.cwd();
|
|
213
209
|
let credentials = options.credentials;
|
|
@@ -219,6 +215,23 @@ export function createMcpExtension(options = {}) {
|
|
|
219
215
|
((entry, patch) => updateMcpServerConfig(entry.override ?? entry.source, entry.name, patch, {
|
|
220
216
|
override: entry.override !== undefined,
|
|
221
217
|
}));
|
|
218
|
+
/**
|
|
219
|
+
* Lifetime of the current session: aborted on session_shutdown, and before the first session_start
|
|
220
|
+
* (registrations before that are read on session_start). Work captures `session.signal` when it
|
|
221
|
+
* starts and drops late results once it aborted, for example a runtime load that resolves after
|
|
222
|
+
* shutdown. Work that outlives the command that started it also stops on the signal and is
|
|
223
|
+
* registered with `track`, so shutdown can wait for its cleanup.
|
|
224
|
+
*/
|
|
225
|
+
let session = new AbortController();
|
|
226
|
+
session.abort();
|
|
227
|
+
/** Work registered with `track`: manager actions opening or closing connections, and sign-ins. */
|
|
228
|
+
const backgroundActions = new Set();
|
|
229
|
+
const track = (work) => {
|
|
230
|
+
const task = work.then(() => undefined, () => undefined);
|
|
231
|
+
backgroundActions.add(task);
|
|
232
|
+
void task.finally(() => backgroundActions.delete(task));
|
|
233
|
+
return work;
|
|
234
|
+
};
|
|
222
235
|
const listeners = new Set();
|
|
223
236
|
const emitChange = () => {
|
|
224
237
|
for (const listener of listeners)
|
|
@@ -301,8 +314,22 @@ export function createMcpExtension(options = {}) {
|
|
|
301
314
|
exposure: getMcpToolExposure(entry.config, tool.name),
|
|
302
315
|
namespace,
|
|
303
316
|
timeoutMs: connection.timeoutMs,
|
|
304
|
-
getClient: async () =>
|
|
305
|
-
|
|
317
|
+
getClient: async () => {
|
|
318
|
+
// A prepared call can outlive this definition's connection while the readiness
|
|
319
|
+
// hook waits for a disable/re-enable. Resolve the replacement at execution time.
|
|
320
|
+
const current = findServer(server);
|
|
321
|
+
if (!current || !isEnabled(current))
|
|
322
|
+
throw new Error(`MCP server "${server}" is disabled.`);
|
|
323
|
+
const client = current.connection;
|
|
324
|
+
if (!client)
|
|
325
|
+
throw new Error(`MCP server "${server}" is still starting.`);
|
|
326
|
+
if (getMcpToolExposure(current.entry.config, tool.name) === "hidden" ||
|
|
327
|
+
(client.state === "connected" && !client.tools.some((offered) => offered.name === tool.name))) {
|
|
328
|
+
throw new Error(`MCP tool "${server}/${tool.name}" is no longer available.`);
|
|
329
|
+
}
|
|
330
|
+
return client;
|
|
331
|
+
},
|
|
332
|
+
readableResources: () => resourceServers().some((current) => current.name === server),
|
|
306
333
|
});
|
|
307
334
|
definitions.set(definition.name, definition);
|
|
308
335
|
pi.registerTool(definition);
|
|
@@ -423,8 +450,10 @@ export function createMcpExtension(options = {}) {
|
|
|
423
450
|
ensureDiscoveryActive(ctx);
|
|
424
451
|
};
|
|
425
452
|
/** Create the server's connection, loading the MCP runtime on first use. */
|
|
426
|
-
const createConnection = async (server) => {
|
|
453
|
+
const createConnection = async (server, isCurrent) => {
|
|
427
454
|
const runtime = await loadMcpRuntime();
|
|
455
|
+
if (!isCurrent())
|
|
456
|
+
return undefined;
|
|
428
457
|
const connection = new runtime.McpServerConnection({
|
|
429
458
|
entry: server.entry,
|
|
430
459
|
cwd: sessionCwd,
|
|
@@ -432,8 +461,16 @@ export function createMcpExtension(options = {}) {
|
|
|
432
461
|
credentials: getCredentials(runtime),
|
|
433
462
|
providerToken: async (provider) => modelRegistry?.getApiKeyForProvider(provider),
|
|
434
463
|
log: getServerLog(runtime),
|
|
435
|
-
onTools:
|
|
436
|
-
|
|
464
|
+
onTools: (connection) => {
|
|
465
|
+
if (isCurrent())
|
|
466
|
+
registerTools(connection);
|
|
467
|
+
},
|
|
468
|
+
onChange: (connection) => {
|
|
469
|
+
if (isCurrent())
|
|
470
|
+
onConnectionChange(connection);
|
|
471
|
+
else
|
|
472
|
+
tokensAtSignIn.delete(connection);
|
|
473
|
+
},
|
|
437
474
|
});
|
|
438
475
|
server.connection = connection;
|
|
439
476
|
emitChange();
|
|
@@ -441,37 +478,48 @@ export function createMcpExtension(options = {}) {
|
|
|
441
478
|
};
|
|
442
479
|
/**
|
|
443
480
|
* Connect the server in the background. `server.ready` settles when it connected or failed;
|
|
444
|
-
* failures show in its state. It starts after `after
|
|
481
|
+
* failures show in its state. It starts after `after`, unless disabled, replaced, or shut down
|
|
445
482
|
* meanwhile. The returned promise rejects when the MCP runtime cannot be loaded.
|
|
446
483
|
*/
|
|
447
|
-
const startConnection = (server,
|
|
484
|
+
const startConnection = (server, after) => {
|
|
485
|
+
const { signal } = session;
|
|
486
|
+
const attempt = Symbol();
|
|
487
|
+
server.attempt = attempt;
|
|
488
|
+
const isCurrent = () => !signal.aborted && server.attempt === attempt && isEnabled(server) && servers.includes(server);
|
|
448
489
|
const ready = (async () => {
|
|
449
490
|
await after;
|
|
450
491
|
if (!isCurrent())
|
|
451
492
|
return;
|
|
452
|
-
const connection = await createConnection(server);
|
|
453
|
-
if (!isCurrent())
|
|
493
|
+
const connection = await createConnection(server, isCurrent);
|
|
494
|
+
if (!connection || !isCurrent())
|
|
454
495
|
return;
|
|
455
496
|
await connection.getClient().catch(() => undefined);
|
|
456
497
|
})();
|
|
457
498
|
server.ready = ready.catch(() => undefined);
|
|
458
499
|
return ready;
|
|
459
500
|
};
|
|
460
|
-
/** Wait for
|
|
501
|
+
/** Wait for the latest connection attempts, including ones queued while waiting, or an abort. */
|
|
461
502
|
const waitForServers = async (waiting, signal) => {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
Promise.
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
503
|
+
for (;;) {
|
|
504
|
+
const ready = waiting.flatMap((server) => isEnabled(server) && servers.includes(server) && server.ready ? [{ server, ready: server.ready }] : []);
|
|
505
|
+
if (ready.length === 0 || signal?.aborted)
|
|
506
|
+
return;
|
|
507
|
+
let onAbort;
|
|
508
|
+
await Promise.race([
|
|
509
|
+
Promise.all(ready.map((attempt) => attempt.ready)),
|
|
510
|
+
new Promise((resolve) => {
|
|
511
|
+
onAbort = () => resolve();
|
|
512
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
513
|
+
}),
|
|
514
|
+
]);
|
|
515
|
+
if (onAbort)
|
|
516
|
+
signal?.removeEventListener("abort", onAbort);
|
|
517
|
+
if (signal?.aborted ||
|
|
518
|
+
ready.every((attempt) => !isEnabled(attempt.server) ||
|
|
519
|
+
!servers.includes(attempt.server) ||
|
|
520
|
+
attempt.ready === attempt.server.ready))
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
475
523
|
};
|
|
476
524
|
/**
|
|
477
525
|
* One message for everything that needs the user after startup, or only for `only`, servers
|
|
@@ -506,20 +554,23 @@ export function createMcpExtension(options = {}) {
|
|
|
506
554
|
server.entry = { ...entry, config: { ...entry.config, ...patch } };
|
|
507
555
|
return undefined;
|
|
508
556
|
};
|
|
509
|
-
|
|
557
|
+
/** Sign in, then reconnect. Returns an error message on failure. `cancel` or the session's end stops the sign-in. */
|
|
558
|
+
const signIn = async (server, prompt, cancel) => {
|
|
510
559
|
const connection = server.connection;
|
|
511
560
|
const url = connection?.oauthUrl;
|
|
512
561
|
if (!connection || !url)
|
|
513
562
|
return `MCP server "${server.entry.name}" does not use OAuth.`;
|
|
514
563
|
const runtime = await loadMcpRuntime();
|
|
564
|
+
const signal = cancel ? AbortSignal.any([session.signal, cancel]) : session.signal;
|
|
515
565
|
try {
|
|
516
|
-
await runtime.signInMcpServer({
|
|
566
|
+
await track(runtime.signInMcpServer({
|
|
517
567
|
serverUrl: url,
|
|
518
568
|
store: getCredentials(runtime).forServer(server.entry.name, url),
|
|
519
569
|
settings: connection.oauthSettings(),
|
|
520
570
|
challenge: connection.challenge,
|
|
521
571
|
prompt,
|
|
522
|
-
|
|
572
|
+
signal,
|
|
573
|
+
}));
|
|
523
574
|
}
|
|
524
575
|
catch (error) {
|
|
525
576
|
if (error instanceof runtime.McpSignInCancelledError)
|
|
@@ -549,8 +600,14 @@ export function createMcpExtension(options = {}) {
|
|
|
549
600
|
const connection = server.connection;
|
|
550
601
|
if (!connection)
|
|
551
602
|
return `MCP server "${server.entry.name}" is disabled.`;
|
|
552
|
-
|
|
603
|
+
const previous = server.ready;
|
|
604
|
+
const ready = (async () => {
|
|
605
|
+
await previous;
|
|
553
606
|
await connection.reconnect();
|
|
607
|
+
})();
|
|
608
|
+
server.ready = ready.catch(() => undefined);
|
|
609
|
+
try {
|
|
610
|
+
await ready;
|
|
554
611
|
return undefined;
|
|
555
612
|
}
|
|
556
613
|
catch (error) {
|
|
@@ -564,13 +621,26 @@ export function createMcpExtension(options = {}) {
|
|
|
564
621
|
return failed;
|
|
565
622
|
if (!enabled) {
|
|
566
623
|
const connection = server.connection;
|
|
624
|
+
server.attempt = undefined;
|
|
567
625
|
server.connection = undefined;
|
|
626
|
+
if (connection) {
|
|
627
|
+
// A reconnect may already have detached its old client while awaiting transport
|
|
628
|
+
// shutdown. Closing the connection alone does not wait for that reconnect.
|
|
629
|
+
server.closing = Promise.all([connection.close(), server.ready]).then(() => undefined);
|
|
630
|
+
}
|
|
631
|
+
const closing = server.closing;
|
|
568
632
|
hideTools(server.entry.name);
|
|
569
633
|
emitChange();
|
|
570
|
-
|
|
634
|
+
try {
|
|
635
|
+
await closing;
|
|
636
|
+
}
|
|
637
|
+
finally {
|
|
638
|
+
if (server.closing === closing)
|
|
639
|
+
server.closing = undefined;
|
|
640
|
+
}
|
|
571
641
|
return undefined;
|
|
572
642
|
}
|
|
573
|
-
await startConnection(server,
|
|
643
|
+
await startConnection(server, server.closing);
|
|
574
644
|
return undefined;
|
|
575
645
|
};
|
|
576
646
|
const setExposure = (server, exposure) => {
|
|
@@ -718,11 +788,13 @@ export function createMcpExtension(options = {}) {
|
|
|
718
788
|
return undefined;
|
|
719
789
|
return setExposure(server, choice);
|
|
720
790
|
};
|
|
721
|
-
/** Sign in with the manager view's sign-in screen, which shows the URL with a copy key. */
|
|
791
|
+
/** Sign in with the manager view's sign-in screen, which shows the URL with a copy key and cancels with Esc. */
|
|
722
792
|
const signInWithUi = (ui, server) => {
|
|
723
793
|
const title = `Sign in to ${server.entry.name}`;
|
|
794
|
+
const cancel = new AbortController();
|
|
795
|
+
const status = (message) => ui.status(title, message, () => cancel.abort());
|
|
724
796
|
let authorizationUrl = "";
|
|
725
|
-
|
|
797
|
+
status("Contacting the authorization server…");
|
|
726
798
|
return signIn(server, {
|
|
727
799
|
showAuthorizationUrl: (url) => {
|
|
728
800
|
authorizationUrl = url.href;
|
|
@@ -730,22 +802,38 @@ export function createMcpExtension(options = {}) {
|
|
|
730
802
|
},
|
|
731
803
|
promptForRedirectUrl: async (signal) => {
|
|
732
804
|
const value = await ui.redirectUrl(title, authorizationUrl, signal);
|
|
733
|
-
|
|
805
|
+
status("Connecting…");
|
|
734
806
|
return value;
|
|
735
807
|
},
|
|
736
|
-
});
|
|
808
|
+
}, cancel.signal);
|
|
809
|
+
};
|
|
810
|
+
/** Keep the subscribed menu usable while a connection opens or closes. */
|
|
811
|
+
const runInBackground = (ctx, server, operation) => {
|
|
812
|
+
const ready = operation();
|
|
813
|
+
const attempt = server.attempt;
|
|
814
|
+
const { signal } = session;
|
|
815
|
+
const finish = (message) => {
|
|
816
|
+
if (signal.aborted || server.attempt !== attempt || !servers.includes(server))
|
|
817
|
+
return;
|
|
818
|
+
server.message = message;
|
|
819
|
+
ensureDiscoveryActive(ctx);
|
|
820
|
+
emitChange();
|
|
821
|
+
};
|
|
822
|
+
void track(ready.then(finish, (error) => finish(errorMessage(error))));
|
|
737
823
|
};
|
|
738
824
|
const runAction = async (ui, ctx, server, action) => {
|
|
739
|
-
const {
|
|
825
|
+
const { signal } = session;
|
|
740
826
|
let message;
|
|
741
827
|
switch (action) {
|
|
742
828
|
case "signin":
|
|
743
829
|
message = await signInWithUi(ui, server);
|
|
744
830
|
break;
|
|
745
831
|
case "reconnect":
|
|
746
|
-
//
|
|
747
|
-
|
|
748
|
-
|
|
832
|
+
// Connection state and error already report failures, including required sign-ins.
|
|
833
|
+
runInBackground(ctx, server, async () => {
|
|
834
|
+
await reconnect(server);
|
|
835
|
+
return undefined;
|
|
836
|
+
});
|
|
749
837
|
break;
|
|
750
838
|
case "signout":
|
|
751
839
|
await signOut(server);
|
|
@@ -761,11 +849,13 @@ export function createMcpExtension(options = {}) {
|
|
|
761
849
|
case "enable-project":
|
|
762
850
|
case "disable-project": {
|
|
763
851
|
const enable = action.startsWith("enable");
|
|
764
|
-
|
|
765
|
-
message = await setEnabled(server, enable, action.endsWith("-project"));
|
|
852
|
+
runInBackground(ctx, server, () => setEnabled(server, enable, action.endsWith("-project")));
|
|
766
853
|
break;
|
|
767
854
|
}
|
|
768
855
|
}
|
|
856
|
+
// The session ended meanwhile (for example during a sign-in), which made ctx stale.
|
|
857
|
+
if (signal.aborted)
|
|
858
|
+
return;
|
|
769
859
|
server.message = message;
|
|
770
860
|
ensureDiscoveryActive(ctx);
|
|
771
861
|
emitChange();
|
|
@@ -849,6 +939,7 @@ export function createMcpExtension(options = {}) {
|
|
|
849
939
|
ctx.ui.notify(`Signing in to MCP server "${name}" requires interactive mode.`, "error");
|
|
850
940
|
return;
|
|
851
941
|
}
|
|
942
|
+
const { signal } = session;
|
|
852
943
|
let failure;
|
|
853
944
|
if (ctx.mode === "tui") {
|
|
854
945
|
await showMcpManager(ctx, async (ui) => {
|
|
@@ -864,6 +955,9 @@ export function createMcpExtension(options = {}) {
|
|
|
864
955
|
promptForRedirectUrl: (signal) => ctx.ui.input(`Waiting for sign-in to "${name}". If the browser cannot reach this machine, paste the URL it was redirected to.`, "http://127.0.0.1:.../callback?code=...", { signal }),
|
|
865
956
|
});
|
|
866
957
|
}
|
|
958
|
+
// The session ended meanwhile, which cancelled the sign-in and made ctx stale.
|
|
959
|
+
if (signal.aborted)
|
|
960
|
+
return;
|
|
867
961
|
if (failure) {
|
|
868
962
|
ctx.ui.notify(failure, failure === "Sign-in cancelled." ? "info" : "error");
|
|
869
963
|
return;
|
|
@@ -880,8 +974,8 @@ export function createMcpExtension(options = {}) {
|
|
|
880
974
|
waitedForStartup = false;
|
|
881
975
|
sessionCwd = ctx.cwd;
|
|
882
976
|
modelRegistry = ctx.modelRegistry;
|
|
883
|
-
|
|
884
|
-
|
|
977
|
+
session = new AbortController();
|
|
978
|
+
const { signal } = session;
|
|
885
979
|
configuredEntries = loaded.servers;
|
|
886
980
|
const registered = registeredServers();
|
|
887
981
|
overridden = registered.overridden;
|
|
@@ -898,10 +992,9 @@ export function createMcpExtension(options = {}) {
|
|
|
898
992
|
// The MCP client loads only now, so sessions without servers never pay for it. Waiting one
|
|
899
993
|
// event loop turn lets the first render happen before loading and connecting.
|
|
900
994
|
const runtime = new Promise((resolve) => setImmediate(resolve)).then(() => loadMcpRuntime());
|
|
901
|
-
|
|
902
|
-
pending = Promise.all(enabled.map((server) => startConnection(server, isCurrent, runtime)))
|
|
995
|
+
pending = Promise.all(enabled.map((server) => startConnection(server, runtime)))
|
|
903
996
|
.then(() => {
|
|
904
|
-
if (
|
|
997
|
+
if (!signal.aborted)
|
|
905
998
|
reportProblems(ctx);
|
|
906
999
|
})
|
|
907
1000
|
.catch((error) => {
|
|
@@ -947,22 +1040,29 @@ export function createMcpExtension(options = {}) {
|
|
|
947
1040
|
});
|
|
948
1041
|
// A codemode script waits for the servers it names, or for every server when it searches or
|
|
949
1042
|
// enumerates tools, so their tools are registered before the script runs. tool_search and the
|
|
950
|
-
// resource tools reach every server, so they wait for all of them.
|
|
1043
|
+
// resource tools reach every server, so they wait for all of them. Direct MCP calls wait only
|
|
1044
|
+
// for their own server, including a reconnect still closing its old transport.
|
|
951
1045
|
pi.on("tool_call", async (event, ctx) => {
|
|
952
1046
|
const tool = pi.getAllTools().find((candidate) => candidate.name === event.toolName);
|
|
953
1047
|
if (!tool)
|
|
954
1048
|
return;
|
|
955
|
-
|
|
956
|
-
|
|
1049
|
+
// Readiness can be pending while the display state is still connected: reconnect first
|
|
1050
|
+
// closes the old transport, then opens a new one. Settled promises return immediately.
|
|
1051
|
+
const readyServers = servers.filter((server) => isEnabled(server) && server.ready);
|
|
1052
|
+
if (readyServers.length === 0)
|
|
957
1053
|
return;
|
|
958
1054
|
let waiting = [];
|
|
959
1055
|
if (isCodemodeTool(tool)) {
|
|
960
1056
|
const { code } = event.input;
|
|
961
1057
|
const source = typeof code === "string" ? code : "";
|
|
962
|
-
waiting =
|
|
1058
|
+
waiting = readyServers.filter((server) => scriptNeedsServer(source, server.entry.name));
|
|
963
1059
|
}
|
|
964
1060
|
else if (isToolSearchTool(tool) || RESOURCE_TOOL_NAMES.has(tool.name)) {
|
|
965
|
-
waiting =
|
|
1061
|
+
waiting = readyServers;
|
|
1062
|
+
}
|
|
1063
|
+
else {
|
|
1064
|
+
const owner = toolOwners.get(event.toolName)?.split("\0", 1)[0];
|
|
1065
|
+
waiting = readyServers.filter((server) => server.entry.name === owner);
|
|
966
1066
|
}
|
|
967
1067
|
await waitForServers(waiting, ctx.signal);
|
|
968
1068
|
});
|
|
@@ -973,9 +1073,9 @@ export function createMcpExtension(options = {}) {
|
|
|
973
1073
|
});
|
|
974
1074
|
// Servers registered or unregistered during the session connect or disconnect right away.
|
|
975
1075
|
pi.on("mcp_servers_change", async (_event, ctx) => {
|
|
976
|
-
|
|
1076
|
+
const { signal } = session;
|
|
1077
|
+
if (signal.aborted)
|
|
977
1078
|
return;
|
|
978
|
-
const current = generation;
|
|
979
1079
|
const registered = registeredServers();
|
|
980
1080
|
overridden = registered.overridden;
|
|
981
1081
|
const next = new Map(registered.servers.map((server) => [server.entry.name, server]));
|
|
@@ -991,11 +1091,11 @@ export function createMcpExtension(options = {}) {
|
|
|
991
1091
|
ensureDiscoveryActive(ctx);
|
|
992
1092
|
await Promise.all(removed.map((server) => server.connection?.close()));
|
|
993
1093
|
const connecting = added.filter(isEnabled);
|
|
994
|
-
if (
|
|
1094
|
+
if (signal.aborted || connecting.length === 0)
|
|
995
1095
|
return;
|
|
996
1096
|
try {
|
|
997
|
-
await Promise.all(connecting.map((server) => startConnection(server
|
|
998
|
-
if (
|
|
1097
|
+
await Promise.all(connecting.map((server) => startConnection(server)));
|
|
1098
|
+
if (signal.aborted) {
|
|
999
1099
|
await Promise.all(connecting.map((server) => server.connection?.close()));
|
|
1000
1100
|
return;
|
|
1001
1101
|
}
|
|
@@ -1004,17 +1104,16 @@ export function createMcpExtension(options = {}) {
|
|
|
1004
1104
|
ctx.ui.notify(`MCP failed to load: ${errorMessage(error)}`, "error");
|
|
1005
1105
|
return;
|
|
1006
1106
|
}
|
|
1007
|
-
if (
|
|
1107
|
+
if (signal.aborted)
|
|
1008
1108
|
return;
|
|
1009
1109
|
reportProblems(ctx, connecting);
|
|
1010
1110
|
});
|
|
1011
1111
|
pi.on("session_shutdown", async () => {
|
|
1012
|
-
|
|
1013
|
-
generation++;
|
|
1112
|
+
session.abort();
|
|
1014
1113
|
const closing = connections();
|
|
1015
1114
|
servers = [];
|
|
1016
1115
|
emitChange();
|
|
1017
|
-
await Promise.all(closing.map((connection) => connection.close()));
|
|
1116
|
+
await Promise.all([...backgroundActions, ...closing.map((connection) => connection.close())]);
|
|
1018
1117
|
});
|
|
1019
1118
|
pi.registerCommand("mcp", {
|
|
1020
1119
|
description: "Manage MCP servers: sign in, reconnect, enable or disable, and change exposure",
|
|
@@ -1040,19 +1139,21 @@ export function createMcpExtension(options = {}) {
|
|
|
1040
1139
|
return items.length > 0 ? items : null;
|
|
1041
1140
|
},
|
|
1042
1141
|
handler: async (args, ctx) => {
|
|
1043
|
-
await pending;
|
|
1044
1142
|
const [action, name, ...extra] = args.trim().split(/\s+/).filter(Boolean);
|
|
1045
1143
|
if (action === undefined) {
|
|
1046
1144
|
if (ctx.mode === "tui")
|
|
1047
1145
|
await showMcpManager(ctx, (ui) => manage(ui, ctx));
|
|
1048
|
-
else
|
|
1146
|
+
else {
|
|
1147
|
+
await pending;
|
|
1049
1148
|
ctx.ui.notify(formatStatus(), "info");
|
|
1149
|
+
}
|
|
1050
1150
|
return;
|
|
1051
1151
|
}
|
|
1052
1152
|
if (extra.length > 0) {
|
|
1053
1153
|
ctx.ui.notify(MCP_USAGE, "warning");
|
|
1054
1154
|
return;
|
|
1055
1155
|
}
|
|
1156
|
+
await pending;
|
|
1056
1157
|
switch (action) {
|
|
1057
1158
|
case "login": {
|
|
1058
1159
|
const server = await pickServer(name, ctx, oauthPick);
|