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
186 changes: 186 additions & 0 deletions packages/cli/src/commands/doctor-pi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,192 @@ describe("Pi doctor", () => {
expect(output).toContain("WARN 2");
});

it("skips unrelated local dev-path packages and broken trees when probing the embedding runtime", async () => {
const root = makeTempRoot();
const cwd = makeTempRoot("mc-pi-doctor-cwd-");
const agentDir = setEnv(root, cwd);
writeHealthyFiles(agentDir, cwd);

// Unrelated local extension: has a package.json but is NOT the
// magic-context plugin. Must not be probed as an embedding candidate.
const unrelatedPlugin = makeTempRoot("mc-pi-doctor-unrelated-");
writeFileSync(
join(unrelatedPlugin, "package.json"),
JSON.stringify({ name: "pi-tree-git-checkpoint", version: "0.0.0" }),
);
// Local dev tree of the actual plugin that is missing all embedding deps.
const brokenDevTree = makeTempRoot("mc-pi-doctor-dev-");
writeFileSync(
join(brokenDevTree, "package.json"),
JSON.stringify({ name: "@cortexkit/pi-magic-context", version: "0.0.0-dev" }),
);

writeFileSync(
join(agentDir, "settings.json"),
JSON.stringify({
packages: ["npm:@cortexkit/pi-magic-context", unrelatedPlugin, brokenDevTree],
}),
);
createInstalledPiPlugin(agentDir, true);
const prompts = new MockPrompts();

const code = await runDoctor(baseOptions(root, cwd, prompts));

expect(code).toBe(1);
const output = prompts.messages.join("\n");
expect(output).toContain("Multiple magic-context entries in Pi packages[]");
expect(output).toContain("PASS Embedding provider: local (native runtime selected and OK)");
expect(output).not.toContain(
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
"WARN Embedding provider: local — native runtime and WASM fallback both unavailable",
);
});

it("prefers a later native-capable install over an earlier WASM fallback", async () => {
const root = makeTempRoot();
const cwd = makeTempRoot("mc-pi-doctor-cwd-");
const agentDir = setEnv(root, cwd);
writeHealthyFiles(agentDir, cwd);

// Local dev tree of the actual plugin with only a WASM fallback
// (no native binding) — probing it alone would report a degraded
// runtime.
const wasmDevTree = makeTempRoot("mc-pi-doctor-wasm-dev-");
mkdirSync(join(wasmDevTree, "node_modules", "onnxruntime-web"), {
recursive: true,
});
writeFileSync(
join(wasmDevTree, "node_modules", "onnxruntime-web", "package.json"),
JSON.stringify({ name: "onnxruntime-web", main: "index.js" }),
);
writeFileSync(
join(wasmDevTree, "node_modules", "onnxruntime-web", "index.js"),
"module.exports = {};\n",
);
mkdirSync(join(wasmDevTree, "dist"), { recursive: true });
writeFileSync(join(wasmDevTree, "dist", "transformers-node-wasm.js"), "export {};\n");
writeFileSync(
join(wasmDevTree, "package.json"),
JSON.stringify({ name: "@cortexkit/pi-magic-context", version: "0.0.0-dev" }),
);

writeFileSync(
join(agentDir, "settings.json"),
JSON.stringify({
packages: ["npm:@cortexkit/pi-magic-context", wasmDevTree],
}),
);
createInstalledPiPlugin(agentDir, true);
const prompts = new MockPrompts();

const code = await runDoctor(baseOptions(root, cwd, prompts));

expect(code).toBe(1);
const output = prompts.messages.join("\n");
expect(output).toContain("Multiple magic-context entries in Pi packages[]");
expect(output).toContain("PASS Embedding provider: local (native runtime selected and OK)");
expect(output).not.toContain(
"WARN Embedding provider: local — onnxruntime-node native binding failed",
);
});

it("reports unverified, not a broken-runtime WARN, when only unrelated local packages are registered", async () => {
const root = makeTempRoot();
const cwd = makeTempRoot("mc-pi-doctor-cwd-");
const agentDir = setEnv(root, cwd);
writeHealthyFiles(agentDir, cwd);

// Only an unrelated local extension is registered; the magic-context
// managed install tree is absent. The unrelated package must not be
// probed as an embedding candidate, so doctor reports unverified
// instead of blaming it for a missing onnxruntime.
const unrelatedPlugin = makeTempRoot("mc-pi-doctor-unrelated-");
writeFileSync(
join(unrelatedPlugin, "package.json"),
JSON.stringify({ name: "pi-tree-git-checkpoint", version: "0.0.0" }),
);
writeFileSync(
join(agentDir, "settings.json"),
JSON.stringify({
packages: ["npm:@cortexkit/pi-magic-context", unrelatedPlugin],
}),
);
const prompts = new MockPrompts();

const code = await runDoctor(baseOptions(root, cwd, prompts));

expect(code).toBe(0);
const output = prompts.messages.join("\n");
expect(output).toContain(
"selected runtime unverified (no installed plugin tree found to inspect)",
);
expect(output).not.toContain(
"WARN Embedding provider: local — native runtime and WASM fallback both unavailable",
);
});

it("reports every broken candidate with its native and WASM reasons", async () => {
const root = makeTempRoot();
const cwd = makeTempRoot("mc-pi-doctor-cwd-");
const agentDir = setEnv(root, cwd);
writeHealthyFiles(agentDir, cwd);
const brokenTrees = [makeTempRoot("mc-broken-first-"), makeTempRoot("mc-broken-second-")];
for (const tree of brokenTrees) {
writeFileSync(
join(tree, "package.json"),
JSON.stringify({ name: "@cortexkit/pi-magic-context" }),
);
}
writeFileSync(
join(agentDir, "settings.json"),
JSON.stringify({ packages: ["npm:@cortexkit/pi-magic-context", ...brokenTrees] }),
);
const prompts = new MockPrompts();
await runDoctor(baseOptions(root, cwd, prompts));
const warnings = prompts.messages.filter((message) =>
message.includes("WARN Embedding provider: local"),
);
for (const tree of brokenTrees) {
expect(
warnings.some(
(warning) =>
warning.includes(tree) &&
warning.includes("native:") &&
warning.includes("WASM:"),
),
).toBe(true);
}
});

it.each([
false,
true,
])("detects npm plus local Magic Context identity (object source: %s)", async (objectSource) => {
const root = makeTempRoot();
const cwd = makeTempRoot("mc-pi-doctor-cwd-");
const agentDir = setEnv(root, cwd);
writeHealthyFiles(agentDir, cwd);
const localDir = join(agentDir, "local-plugin");
mkdirSync(localDir);
writeFileSync(
join(localDir, "package.json"),
JSON.stringify({ name: "@cortexkit/pi-magic-context" }),
);
const source = "./local-plugin";
writeFileSync(
join(agentDir, "settings.json"),
JSON.stringify({
packages: ["npm:@cortexkit/pi-magic-context", objectSource ? { source } : source],
}),
);
const prompts = new MockPrompts();
expect(await runDoctor(baseOptions(root, cwd, prompts))).toBe(1);
const output = prompts.messages.join("\n");
expect(output).toContain("Multiple magic-context entries in Pi packages[]");
expect(output).toContain(source);
expect(output).not.toContain("Other Pi extensions registered:");
expect(output).not.toContain("selected runtime unverified");
});

it("reports the WASM fallback when onnxruntime-node is completely absent", async () => {
const root = makeTempRoot();
const cwd = makeTempRoot("mc-pi-doctor-cwd-");
Expand Down
81 changes: 63 additions & 18 deletions packages/cli/src/commands/doctor-pi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,18 +319,50 @@ function packagesFrom(settings: Record<string, unknown>): unknown[] {
* <cwd>/.pi/npm/node_modules/<pkg> (project). We collect every plausible dir
* with a package.json; the resolver stays SILENT for any that don't exist.
*/
/** True when the directory's package.json declares the magic-context Pi plugin. */
function isPiMagicContextPackageDir(dir: string): boolean {
const packageJson = join(dir, "package.json");
if (!existsSync(packageJson)) return false;
try {
const pkg = JSON.parse(readFileSync(packageJson, "utf-8")) as {
name?: unknown;
};
return typeof pkg.name === "string" && pkg.name === PACKAGE_NAME;
} catch {
return false;
}
}

function localPiMagicContextPackageDir(entry: unknown): string | null {
const source =
typeof entry === "string"
? entry
: entry && typeof entry === "object" && "source" in entry
? entry.source
: null;
const spec = typeof source === "string" ? source.trim() : "";
if (!spec || spec.startsWith("npm:")) return null;
const dir = isAbsolute(spec) ? spec : join(getPiAgentConfigDir(), spec);
return isPiMagicContextPackageDir(dir) ? dir : null;
}

function isConfiguredPiMagicContextEntry(entry: unknown): boolean {
return isPiMagicContextPackageEntry(entry) || localPiMagicContextPackageDir(entry) !== null;
}

function piPluginDirCandidates(packages: unknown[], cwd: string): string[] {
const dirs: string[] = [];
const agentDir = getPiAgentConfigDir();

// Local dev-path entries: a string spec that is NOT an npm: specifier and
// resolves to a directory on disk. Relative entries are resolved against the
// Pi agent dir (Pi's settings.packages base).
// Pi agent dir (Pi's settings.packages base). Only directories whose
// package.json names the magic-context plugin itself are candidates — other
// local extensions registered in packages[] must not be probed for the
// embedding runtime.
for (const entry of packages) {
const spec = typeof entry === "string" ? entry.trim() : "";
if (!spec || spec.startsWith("npm:")) continue;
const resolved = isAbsolute(spec) ? spec : join(agentDir, spec);
dirs.push(resolved);
const resolved = localPiMagicContextPackageDir(entry);
if (resolved) dirs.push(resolved);
}

// Managed npm install roots (hoisted): <root>/node_modules/<pkg>.
Expand Down Expand Up @@ -851,6 +883,8 @@ async function runHealthChecks(options: {
// persistence-capable Node WASM fallback. Resolution starts from the
// installed plugin dir and stays silent when no tree can be inspected.
let runtimeReported = false;
let firstFallback: ReturnType<typeof checkLocalEmbeddingRuntimeByResolution> | null = null;
const brokenWarnings: string[] = [];
let runtimeUnverifiedReason = "no installed plugin tree found to inspect";
for (const pluginDir of piPluginDirCandidates(packages, options.cwd)) {
const runtime = checkLocalEmbeddingRuntimeByResolution(
Expand Down Expand Up @@ -878,31 +912,42 @@ async function runHealthChecks(options: {
break;
}
if (runtime.state === "wasm-fallback") {
add(results, "warn", formatLocalEmbeddingRuntimeWasmFallback(runtime));
runtimeReported = true;
break;
// Remember the best degraded candidate but keep probing: a WASM
// fallback in an earlier tree must not mask a later native-capable
// install.
firstFallback ??= runtime;
continue;
}
if (isLocalEmbeddingRuntimeBroken(runtime)) {
add(results, "warn", formatLocalEmbeddingRuntimeDoctorWarning(runtime));
runtimeReported = true;
break;
// Keep probing: an earlier broken candidate (e.g. a stale local
// dev-path tree) must not mask a healthy managed install.
brokenWarnings.push(
`${formatLocalEmbeddingRuntimeDoctorWarning(runtime)} Candidate: ${pluginDir}`,
);
continue;
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
}
if (runtime.state === "unknown") runtimeUnverifiedReason = runtime.reason;
}
if (!runtimeReported) {
add(
results,
"warn",
`Embedding provider ${loadedConfig.config.embedding.provider}: selected runtime unverified (${runtimeUnverifiedReason})`,
);
if (firstFallback) {
add(results, "warn", formatLocalEmbeddingRuntimeWasmFallback(firstFallback));
} else if (brokenWarnings.length > 0) {
for (const warning of brokenWarnings) add(results, "warn", warning);
} else {
add(
results,
"warn",
`Embedding provider ${loadedConfig.config.embedding.provider}: selected runtime unverified (${runtimeUnverifiedReason})`,
);
}
}
}

// Conflict detection — Pi doesn't have known competing context-management
// extensions today, but we still check for self-conflicts that the user
// can hit (e.g. accidentally registering both an npm entry AND a local
// dev-path entry, which causes duplicate plugin loading).
const piEntries = packages.filter(isPiMagicContextPackageEntry).map(describePiPackageEntry);
const piEntries = packages.filter(isConfiguredPiMagicContextEntry).map(describePiPackageEntry);
if (piEntries.length > 1) {
add(
results,
Expand All @@ -914,7 +959,7 @@ async function runHealthChecks(options: {
}

const otherExtensions = packages
.filter((entry) => !isPiMagicContextPackageEntry(entry))
.filter((entry) => !isConfiguredPiMagicContextEntry(entry))
.map(describePiPackageEntry);
if (otherExtensions.length > 0) {
add(results, "info", `Other Pi extensions registered: ${otherExtensions.join(", ")}`);
Expand Down