diff --git a/apps/desktop/src/pages/settings-mcp.test.tsx b/apps/desktop/src/pages/settings-mcp.test.tsx index 8f32baba..7a555908 100644 --- a/apps/desktop/src/pages/settings-mcp.test.tsx +++ b/apps/desktop/src/pages/settings-mcp.test.tsx @@ -281,6 +281,26 @@ describe("Settings — MCP servers", () => { ); }); + it("describes exposures the way Pi 1.0 resolves them", async () => { + const user = userEvent.setup(); + renderMcpSection(); + + const section = await findMcpSection(); + await user.click(within(section).getByRole("button", { name: "Add server" })); + const dialog = await screen.findByRole("dialog", { name: "Add server" }); + await user.click(within(dialog).getByRole("combobox", { name: "Exposure" })); + + const options = await screen.findAllByRole("option"); + // Pi 1.0 reads "codemode-deferred" as an alias of "codemode" and no longer offers it. + expect(options.map((option) => option.textContent?.split(/(?=[A-Z])/)[0])).toEqual([ + "codemode", + "deferred", + "direct", + "hidden", + ]); + expect(options[0]).toHaveTextContent("found with searchTools()"); + }); + it("keeps the add dialog open on a failed result and splits arguments by line", async () => { const user = userEvent.setup(); const add = vi.fn(async () => ({ diff --git a/apps/desktop/src/pages/settings-mcp.tsx b/apps/desktop/src/pages/settings-mcp.tsx index 098f058e..478c60b9 100644 --- a/apps/desktop/src/pages/settings-mcp.tsx +++ b/apps/desktop/src/pages/settings-mcp.tsx @@ -40,8 +40,7 @@ const MCP_CONFIG_KEY = ["mcp-config"] as const; const MCP_PROBE_KEY = ["mcp-probe"] as const; const EXPOSURE_OPTIONS: Array<{ value: McpExposure; description: string }> = [ - { value: "codemode", description: "Called from codemode scripts, listed in its description" }, - { value: "codemode-deferred", description: "Called from codemode scripts, found with searchTools()" }, + { value: "codemode", description: "Called from codemode scripts, found with searchTools()" }, { value: "deferred", description: "Loaded by tool_search, then called directly" }, { value: "direct", description: "Declared to the model like built-in tools" }, { value: "hidden", description: "Registered but unreachable" }, diff --git a/bun.lock b/bun.lock index 97e24e7b..7d99d55a 100644 --- a/bun.lock +++ b/bun.lock @@ -81,8 +81,8 @@ "name": "@pace/backend", "version": "0.1.0", "dependencies": { - "@earendil-works/pi-ai": "0.99.1", - "@earendil-works/pi-coding-agent": "0.99.1", + "@earendil-works/pi-ai": "1.0.0", + "@earendil-works/pi-coding-agent": "1.0.0", "@pace/core": "workspace:*", }, }, @@ -278,21 +278,21 @@ "@dnd-kit/utilities": ["@dnd-kit/utilities@3.2.2", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg=="], - "@earendil-works/chord": ["@earendil-works/chord@0.99.1", "", { "dependencies": { "esbuild": "0.28.2" } }, "sha512-4xyn0IBzJ+Xu/iOGi2hjXJGAR61QEhEWZsIqTDqr+GmItdquYwBO5jYFnqGiBaTqlY12/EpM7QHoEKSHbyvOug=="], + "@earendil-works/chord": ["@earendil-works/chord@1.0.0", "", { "dependencies": { "esbuild": "0.28.2" } }, "sha512-BIfWfrByM0pKq6tfKYXuwx0ed2CvaqIM3EDA7Dps+fP+d3CiPWdlHi1cDsJC05llqKJoRz5//G0hz0yQwwjISQ=="], - "@earendil-works/pi-agent-core": ["@earendil-works/pi-agent-core@0.99.1", "", { "dependencies": { "@earendil-works/chord": "^0.99.1", "@earendil-works/pi-ai": "^0.99.1", "@earendil-works/pi-telemetry": "^0.99.1", "diff": "8.0.4", "ignore": "7.0.8", "typebox": "1.3.27", "yaml": "2.9.0" } }, "sha512-zywvWnj5FujeuFI/x/CJHwwxhcLIQgjqseTA+bQgX4O8gJTcgjRd/I8SZnQDqJvxC9QcV12ujiGLviv6EgwcCg=="], + "@earendil-works/pi-agent-core": ["@earendil-works/pi-agent-core@1.0.0", "", { "dependencies": { "@earendil-works/pi-ai": "^1.0.0", "typebox": "1.3.27" } }, "sha512-bHFONjtEBDqiV+g1DmmtkSlfAaqHELr+XO+6I5Nah4gswwZrLJO4yZB/JjsnlI4AKWTayBLfgS6DCbeBBz9e2Q=="], - "@earendil-works/pi-ai": ["@earendil-works/pi-ai@0.99.1", "", { "dependencies": { "@anthropic-ai/sdk": "0.124.0", "@aws-sdk/client-bedrock-runtime": "3.1127.0", "@earendil-works/pi-telemetry": "^0.99.1", "@google/genai": "2.21.0", "@smithy/node-http-handler": "4.12.1", "http-proxy-agent": "9.1.0", "https-proxy-agent": "9.1.0", "openai": "7.19.0", "partial-json": "0.1.7", "typebox": "1.3.27" }, "bin": { "pi-ai": "dist/cli.js" } }, "sha512-4nV9JKc94iPX8bwdGPc2nTuVPKIPsffhnp3WoN9NYCNqbtoOF8LhYcIs/+Sn/alroqJK/5QRu6/Z6Ck+n0hyBA=="], + "@earendil-works/pi-ai": ["@earendil-works/pi-ai@1.0.0", "", { "dependencies": { "@anthropic-ai/sdk": "0.124.0", "@aws-sdk/client-bedrock-runtime": "3.1127.0", "@earendil-works/pi-telemetry": "^1.0.0", "@google/genai": "2.21.0", "@smithy/node-http-handler": "4.12.1", "http-proxy-agent": "9.1.0", "https-proxy-agent": "9.1.0", "openai": "7.19.0", "partial-json": "0.1.7", "typebox": "1.3.27" }, "bin": { "pi-ai": "dist/cli.js" } }, "sha512-3/W1vdDaVtpeMd23ElvJC12HLA5yS/BGqqcXF+0SK082dN7cbgNcCwguTBRBC258Ke8SzSvUW1B75iAf8w8IxA=="], - "@earendil-works/pi-codemode": ["@earendil-works/pi-codemode@0.99.1", "", { "dependencies": { "quickjs-wasi": "3.6.2" } }, "sha512-oh8TMsBI3SWTN3xTQtX8u5n+BKhnVXcFagroWumfn6/WWfBnDYL/LmeQtjLb83WRTb9rcu+ZdK8rFa4vggvCJg=="], + "@earendil-works/pi-codemode": ["@earendil-works/pi-codemode@1.0.0", "", { "dependencies": { "quickjs-wasi": "3.6.2" } }, "sha512-LPpFI4+T9NzDnhBDs15izWAolaoM8xnwqdziRd6Zx8BQeoEPvzefD2vMMzSyF0rOtq34TfQqkZ0ki16f6cGdMg=="], - "@earendil-works/pi-coding-agent": ["@earendil-works/pi-coding-agent@0.99.1", "", { "dependencies": { "@earendil-works/chord": "^0.99.1", "@earendil-works/pi-agent-core": "^0.99.1", "@earendil-works/pi-ai": "^0.99.1", "@earendil-works/pi-codemode": "^0.99.1", "@earendil-works/pi-mcp": "^0.99.1", "@earendil-works/pi-tui": "^0.99.1", "@silvia-odwyer/photon-node": "0.3.4", "chalk": "6.0.0", "cross-spawn": "7.0.6", "diff": "8.0.4", "grok-mermaid": "0.2.3", "highlight.js": "10.7.3", "hosted-git-info": "9.0.3", "ignore": "7.0.8", "jiti": "2.7.0", "minimatch": "10.2.6", "proper-lockfile": "4.1.2", "quickjs-wasi": "3.6.2", "semver": "7.8.5", "typebox": "1.3.27", "undici": "8.10.2", "yaml": "2.9.0" }, "bin": { "pi": "dist/bundle/cli.js" } }, "sha512-cWUrTOqA5M73cOYMgsh9PlhDrsBhavd+n5kVY6F7BGbGl1RjqCteVCoeVMVqhngoGACVDyw1tbLjajL8l9jrHg=="], + "@earendil-works/pi-coding-agent": ["@earendil-works/pi-coding-agent@1.0.0", "", { "dependencies": { "@earendil-works/chord": "^1.0.0", "@earendil-works/pi-agent-core": "^1.0.0", "@earendil-works/pi-ai": "^1.0.0", "@earendil-works/pi-codemode": "^1.0.0", "@earendil-works/pi-mcp": "^1.0.0", "@earendil-works/pi-tui": "^1.0.0", "@silvia-odwyer/photon-node": "0.3.4", "chalk": "6.0.0", "cross-spawn": "7.0.6", "diff": "8.0.4", "grok-mermaid": "0.2.3", "highlight.js": "10.7.3", "hosted-git-info": "9.0.3", "ignore": "7.0.8", "jiti": "2.7.0", "minimatch": "10.2.6", "proper-lockfile": "4.1.2", "quickjs-wasi": "3.6.2", "semver": "7.8.5", "typebox": "1.3.27", "undici": "8.10.2", "yaml": "2.9.0" }, "bin": { "pi": "dist/bundle/cli.js" } }, "sha512-/FtbxoSQU/mEv1QnichJjRjqteqaIaMWxmhB4G367+MwZfX7/DI5B9YAg5lqbN7nztFskBEtUSZ+FlmMBECtMw=="], - "@earendil-works/pi-mcp": ["@earendil-works/pi-mcp@0.99.1", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-YCFGPkmDzLwQuIzwfbP6Vuk/g/ukKpZhwTpbcfzomuI1Fkiu6hHRkOGwAqsO3G8cTkZWkM8vmOkFJjStQNC4qA=="], + "@earendil-works/pi-mcp": ["@earendil-works/pi-mcp@1.0.0", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-rYra0aF5iPmJd+fsB+VBuqxWNABGJ3Iiyhg0CqIc/91ta2n7IvQmCp0Cac/YkUTNnvRsVZnjgAiBkRi6+ouWuw=="], - "@earendil-works/pi-telemetry": ["@earendil-works/pi-telemetry@0.99.1", "", {}, "sha512-9PBPjGk+TXRtuMianpqBbHBpYpyKusESF6rwdmgD0WTZSTUQXhcKEO0hAINRLuSwy4V7yPvXV+EVV0ONY7mbpQ=="], + "@earendil-works/pi-telemetry": ["@earendil-works/pi-telemetry@1.0.0", "", {}, "sha512-WjNBj5TYIiPZFQEz2WlULcDwPLaKwIlmsKjVeYM+LJSbnSp38kWsHUJysIDGnu23IcLbKoPtywsvYfUJCeZePA=="], - "@earendil-works/pi-tui": ["@earendil-works/pi-tui@0.99.1", "", { "dependencies": { "get-east-asian-width": "1.6.0", "marked": "18.0.11" } }, "sha512-gZp0Guat96Fr1AuC/xqVz5B2lulZakp/PxD1lXx3lSgBdjiqmwYhJbcQ0HRrGAfy0WtMGn9b05RJr5qJf7oIuw=="], + "@earendil-works/pi-tui": ["@earendil-works/pi-tui@1.0.0", "", { "dependencies": { "get-east-asian-width": "1.6.0", "marked": "18.0.11" } }, "sha512-JsT7kXnpZA2YOtQu6RyriyxEO0eJIzPyfiH09bH+OLN5+s18HYkwaUD/tBkjhnSfMu6/50CQPRYJagzSP6HdPw=="], "@electron-internal/extract-zip": ["@electron-internal/extract-zip@1.0.4", "", {}, "sha512-Zr1Vs7E9tpCNhZHDAbFVXc2gEVCG9RqPDjrno5+bdgB6LRAuvgyMHJut4NCVyYwtAieapMzc3fiQ3CSTi75ARg=="], @@ -2624,10 +2624,6 @@ "@earendil-works/chord/esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="], - "@earendil-works/pi-agent-core/diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], - - "@earendil-works/pi-agent-core/ignore": ["ignore@7.0.8", "", {}, "sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q=="], - "@earendil-works/pi-coding-agent/chalk": ["chalk@6.0.0", "", {}, "sha512-2uNTXIuTTxk7ciZgAU1BQcgnchcG0xXnrs6jzkQfj9SsRa9M2s5zE8WT96hS6KmG4MzWHSrvH43DF1m4XRkrFg=="], "@earendil-works/pi-coding-agent/diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], diff --git a/packages/backend/package.json b/packages/backend/package.json index b89a0b21..521c7ea2 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -12,8 +12,8 @@ ".": "./src/index.ts" }, "dependencies": { - "@earendil-works/pi-ai": "0.99.1", - "@earendil-works/pi-coding-agent": "0.99.1", + "@earendil-works/pi-ai": "1.0.0", + "@earendil-works/pi-coding-agent": "1.0.0", "@pace/core": "workspace:*" } } diff --git a/packages/backend/src/service.ts b/packages/backend/src/service.ts index aec15b28..6ee91837 100644 --- a/packages/backend/src/service.ts +++ b/packages/backend/src/service.ts @@ -1074,7 +1074,6 @@ function requiredBoolean(value: unknown, name: string) { const MCP_EXPOSURES: readonly McpExposure[] = [ "codemode", - "codemode-deferred", "deferred", "direct", "hidden", diff --git a/packages/backend/src/workspace/mcp-servers.test.ts b/packages/backend/src/workspace/mcp-servers.test.ts index 5c7db0a7..e0cbb987 100644 --- a/packages/backend/src/workspace/mcp-servers.test.ts +++ b/packages/backend/src/workspace/mcp-servers.test.ts @@ -5,6 +5,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { McpServerConfigItem } from "@pace/core"; +import { McpOAuthCredentialStore } from "@pace/pi-mcp/oauth.js"; import { createMcpServersService } from "./mcp-servers"; import { handleMcpMessage } from "./fixtures/mcp-test-server.mjs"; @@ -346,6 +347,21 @@ describe("mcp-servers service", () => { ).toBe(false); }); + it("tells apart servers that share a URL by the credentials Pi stored per name", async () => { + const url = "http://127.0.0.1:8998/mcp"; + await writeMcpJson(agentDir, { + mcpServers: { work: { url }, personal: { url } }, + }); + await new McpOAuthCredentialStore() + .forServer("work", url) + .save({ serverUrl: String(new URL(url)), tokens: { access_token: "t", token_type: "Bearer" } }); + + const config = await createMcpServersService({ agentDir }).getConfig(); + const servers = serversByName(config.servers); + expect(servers.get("work")?.hasStoredCredentials).toBe(true); + expect(servers.get("personal")?.hasStoredCredentials).toBe(false); + }); + it("reports no stored credentials for stdio servers", async () => { await writeMcpJson(agentDir, { mcpServers: { diff --git a/packages/backend/src/workspace/mcp-servers.ts b/packages/backend/src/workspace/mcp-servers.ts index cfb0c8f9..97d649a5 100644 --- a/packages/backend/src/workspace/mcp-servers.ts +++ b/packages/backend/src/workspace/mcp-servers.ts @@ -111,9 +111,9 @@ function toConfigItem( : [config.command, ...(config.args ?? [])].join(" "), usesOAuth, // Read through Pi's own store so "signed in" matches what `pi mcp - // logout` would remove; keyed by the normalized URL. + // logout` would remove; keyed by server name and normalized URL. hasStoredCredentials: - usesOAuth && credentials.tokens(config.url) !== undefined, + usesOAuth && credentials.tokens(entry.name, config.url) !== undefined, }; } diff --git a/packages/backend/src/workspace/prompt-commands.ts b/packages/backend/src/workspace/prompt-commands.ts index 7fbfa5df..5d375cfc 100644 --- a/packages/backend/src/workspace/prompt-commands.ts +++ b/packages/backend/src/workspace/prompt-commands.ts @@ -14,7 +14,7 @@ export type ResolveStaticPromptCommandsInput = { agentDir: string; }; -// Mirrors Pi 0.99.1's private loadTemplateFromFile (core/prompt-templates.js): +// Mirrors Pi 1.0.0's private loadTemplateFromFile (core/prompt-templates.js): // the command name is the basename without ".md" and the description comes // from frontmatter, falling back to the first non-empty body line truncated // to 60 characters. Unreadable or unparseable files are skipped. @@ -43,7 +43,7 @@ function readPromptTemplate(filePath: string): { name: string; description: stri return { name: basename(filePath).replace(/\.md$/, ""), description }; } -// Mirrors Pi 0.99.1's private loadTemplatesFromDir: non-recursive, ".md" +// Mirrors Pi 1.0.0's private loadTemplatesFromDir: non-recursive, ".md" // only, and symlinks must resolve to a file. function promptTemplateFiles(resourcePath: string): string[] { let stats; @@ -82,7 +82,7 @@ function promptTemplateFiles(resourcePath: string): string[] { * packageManager.resolve() with no onMissing, which installs any missing — * or merely version-mismatched — npm/git package. A read-only completion * query must never trigger an install, so this walks the resolved resource - * list directly, replicating Pi 0.99.1's loading rules instead. Settings are + * list directly, replicating Pi 1.0.0's loading rules instead. Settings are * still read through the file-backed manager (verified read-only) so trust, * package filters and "!name" disables behave exactly as at runtime. * Extension code is never touched, so extension commands only exist on the diff --git a/packages/core/src/mcp-servers.ts b/packages/core/src/mcp-servers.ts index 792e75c9..a7c130e0 100644 --- a/packages/core/src/mcp-servers.ts +++ b/packages/core/src/mcp-servers.ts @@ -4,7 +4,6 @@ export type McpExposure = | "codemode" - | "codemode-deferred" | "deferred" | "direct" | "hidden"; diff --git a/packages/core/src/prompt-catalog.ts b/packages/core/src/prompt-catalog.ts index 50a405d5..9d93710c 100644 --- a/packages/core/src/prompt-catalog.ts +++ b/packages/core/src/prompt-catalog.ts @@ -31,7 +31,7 @@ export type WorkspaceFileSearchResult = { }; /** - * Verbatim copy of `autocompleteSeparatorRegex` from pi-tui 0.99.1 + * Verbatim copy of `autocompleteSeparatorRegex` from pi-tui 1.0.0 * `dist/utils.js` (it composes cjkPunctuationRegex there). A file reference * whose path matches must serialize as `@""` — the same rule the Pi * TUI's `buildCompletionValue` applies. Drift-guarded by @@ -47,7 +47,7 @@ const PROMPT_COMMAND_KIND_ORDER: Record = { }; /** - * Names declared by Pi 0.99.1's `dist/core/slash-commands.js`. These are TUI + * Names declared by Pi 1.0.0's `dist/core/slash-commands.js`. These are TUI * commands — Pi's session prompt path never sees them, so Pace must not send * them as prompts (the composer blocks submit instead). Not publicly exported * by Pi; `packages/backend/src/workspace/pi-tui-builtins.test.ts` guards