Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions apps/desktop/src/pages/settings-mcp.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => ({
Expand Down
3 changes: 1 addition & 2 deletions apps/desktop/src/pages/settings-mcp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
24 changes: 10 additions & 14 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:*"
}
}
1 change: 0 additions & 1 deletion packages/backend/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1074,7 +1074,6 @@ function requiredBoolean(value: unknown, name: string) {

const MCP_EXPOSURES: readonly McpExposure[] = [
"codemode",
"codemode-deferred",
"deferred",
"direct",
"hidden",
Expand Down
16 changes: 16 additions & 0 deletions packages/backend/src/workspace/mcp-servers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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: {
Expand Down
4 changes: 2 additions & 2 deletions packages/backend/src/workspace/mcp-servers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
}

Expand Down
6 changes: 3 additions & 3 deletions packages/backend/src/workspace/prompt-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/mcp-servers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

export type McpExposure =
| "codemode"
| "codemode-deferred"
| "deferred"
| "direct"
| "hidden";
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/prompt-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 `@"<path>"` — the same rule the Pi
* TUI's `buildCompletionValue` applies. Drift-guarded by
Expand All @@ -47,7 +47,7 @@ const PROMPT_COMMAND_KIND_ORDER: Record<PromptCommandKind, number> = {
};

/**
* 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
Expand Down
Loading