From bfa538fa00b750fa0874015e8e1cd2358fac5e45 Mon Sep 17 00:00:00 2001 From: Advait Paliwal Date: Thu, 9 Apr 2026 10:34:29 -0700 Subject: [PATCH] triage remaining tracker fixes --- CHANGELOG.md | 9 ++++ .../lib/pi-google-legacy-schema-patch.d.mts | 1 + scripts/lib/pi-google-legacy-schema-patch.mjs | 44 +++++++++++++++++++ scripts/patch-embedded-pi.mjs | 10 +++++ src/model/catalog.ts | 8 ++++ tests/model-harness.test.ts | 11 ++++- tests/pi-google-legacy-schema-patch.test.ts | 42 ++++++++++++++++++ .../docs/getting-started/configuration.md | 30 +++++++++++++ .../content/docs/reference/slash-commands.md | 3 ++ 9 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 scripts/lib/pi-google-legacy-schema-patch.d.mts create mode 100644 scripts/lib/pi-google-legacy-schema-patch.mjs create mode 100644 tests/pi-google-legacy-schema-patch.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 7128fec..1831ff6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -140,3 +140,12 @@ Use this file to track chronology, not release notes. Keep entries short, factua - Failed / learned: The first packaged `search status` smoke test still showed the user home path because the native bundle had been built before the `FEYNMAN_HOME` path fix; rebuilding the native bundle resolved that mismatch. - Blockers: PowerShell runtime was unavailable locally, so Windows installer execution remained code-path validated rather than actually executed. - Next: Push the second-pass hardening commit, then keep issue `#46` and issue `#47` open until users on the affected Linux/CJK environments confirm whether the launcher/header fixes fully resolve them. + +### 2026-04-09 10:36 PDT — remaining-tracker-triage-pass + +- Objective: Reduce the remaining open tracker items by landing the lowest-risk missing docs/catalog updates and a targeted Cloud Code Assist compatibility patch instead of only hand-triaging them. +- Changed: Added MiniMax M2.7 recommendation preferences in `src/model/catalog.ts`; documented model switching, authenticated-provider visibility, and `/feynman-model` subagent overrides in `website/src/content/docs/getting-started/configuration.md` and `website/src/content/docs/reference/slash-commands.md`; added a runtime patch helper in `scripts/lib/pi-google-legacy-schema-patch.mjs` and wired `scripts/patch-embedded-pi.mjs` to normalize JSON Schema `const` into `enum` for the legacy `parameters` field used by Cloud Code Assist Claude models. +- Verified: Ran `npm test`, `npm run typecheck`, `npm run build`, and `cd website && npm run build` after the patch/helper/docs changes. +- Failed / learned: The MiniMax provider catalog in Pi already uses canonical IDs like `MiniMax-M2.7`, so the only failure during validation was a test assertion using the wrong casing rather than a runtime bug. +- Blockers: The Cloud Code Assist fix is validated by targeted patch tests and code-path review rather than an end-to-end Google account repro in this environment. +- Next: Push the tracker-triage commit, close the docs/MiniMax PRs as superseded by main, close the support-style model issues against the new docs, and decide whether the remaining feature requests should be left open or closed as not planned/upstream-dependent. diff --git a/scripts/lib/pi-google-legacy-schema-patch.d.mts b/scripts/lib/pi-google-legacy-schema-patch.d.mts new file mode 100644 index 0000000..ccb65af --- /dev/null +++ b/scripts/lib/pi-google-legacy-schema-patch.d.mts @@ -0,0 +1 @@ +export function patchPiGoogleLegacySchemaSource(source: string): string; diff --git a/scripts/lib/pi-google-legacy-schema-patch.mjs b/scripts/lib/pi-google-legacy-schema-patch.mjs new file mode 100644 index 0000000..0685173 --- /dev/null +++ b/scripts/lib/pi-google-legacy-schema-patch.mjs @@ -0,0 +1,44 @@ +const HELPER = [ + "function normalizeLegacyToolSchema(schema) {", + " if (Array.isArray(schema)) return schema.map((item) => normalizeLegacyToolSchema(item));", + ' if (!schema || typeof schema !== "object") return schema;', + " const normalized = {};", + " for (const [key, value] of Object.entries(schema)) {", + ' if (key === "const") {', + " normalized.enum = [value];", + " continue;", + " }", + " normalized[key] = normalizeLegacyToolSchema(value);", + " }", + " return normalized;", + "}", +].join("\n"); + +const ORIGINAL = + ' ...(useParameters ? { parameters: tool.parameters } : { parametersJsonSchema: tool.parameters }),'; +const PATCHED = [ + " ...(useParameters", + " ? { parameters: normalizeLegacyToolSchema(tool.parameters) }", + " : { parametersJsonSchema: tool.parameters }),", +].join("\n"); + +export function patchPiGoogleLegacySchemaSource(source) { + let patched = source; + + if (patched.includes("function normalizeLegacyToolSchema(schema) {")) { + return patched; + } + + if (!patched.includes(ORIGINAL)) { + return source; + } + + patched = patched.replace(ORIGINAL, PATCHED); + const marker = "export function convertTools(tools, useParameters = false) {"; + const markerIndex = patched.indexOf(marker); + if (markerIndex === -1) { + return source; + } + + return `${patched.slice(0, markerIndex)}${HELPER}\n\n${patched.slice(markerIndex)}`; +} diff --git a/scripts/patch-embedded-pi.mjs b/scripts/patch-embedded-pi.mjs index 30553da..ac83c28 100644 --- a/scripts/patch-embedded-pi.mjs +++ b/scripts/patch-embedded-pi.mjs @@ -6,6 +6,7 @@ import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { FEYNMAN_LOGO_HTML } from "../logo.mjs"; import { patchPiExtensionLoaderSource } from "./lib/pi-extension-loader-patch.mjs"; +import { patchPiGoogleLegacySchemaSource } from "./lib/pi-google-legacy-schema-patch.mjs"; import { PI_WEB_ACCESS_PATCH_TARGETS, patchPiWebAccessSource } from "./lib/pi-web-access-patch.mjs"; import { PI_SUBAGENTS_PATCH_TARGETS, patchPiSubagentsSource } from "./lib/pi-subagents-patch.mjs"; @@ -616,6 +617,7 @@ if (existsSync(sessionSearchIndexerPath)) { } const oauthPagePath = piAiRoot ? resolve(piAiRoot, "dist", "utils", "oauth", "oauth-page.js") : null; +const googleSharedPath = piAiRoot ? resolve(piAiRoot, "dist", "providers", "google-shared.js") : null; if (oauthPagePath && existsSync(oauthPagePath)) { let source = readFileSync(oauthPagePath, "utf8"); @@ -628,6 +630,14 @@ if (oauthPagePath && existsSync(oauthPagePath)) { if (changed) writeFileSync(oauthPagePath, source, "utf8"); } +if (googleSharedPath && existsSync(googleSharedPath)) { + const source = readFileSync(googleSharedPath, "utf8"); + const patched = patchPiGoogleLegacySchemaSource(source); + if (patched !== source) { + writeFileSync(googleSharedPath, patched, "utf8"); + } +} + const alphaHubAuthPath = findPackageRoot("@companion-ai/alpha-hub") ? resolve(findPackageRoot("@companion-ai/alpha-hub"), "src", "lib", "auth.js") : null; diff --git a/src/model/catalog.ts b/src/model/catalog.ts index 3468d3b..8bbbda3 100644 --- a/src/model/catalog.ts +++ b/src/model/catalog.ts @@ -95,6 +95,14 @@ const RESEARCH_MODEL_PREFERENCES = [ spec: "zai/glm-5", reason: "good fallback when GLM is the available research model", }, + { + spec: "minimax/minimax-m2.7", + reason: "good fallback when MiniMax is the available research model", + }, + { + spec: "minimax/minimax-m2.7-highspeed", + reason: "good fallback when MiniMax is the available research model", + }, { spec: "kimi-coding/kimi-k2-thinking", reason: "good fallback when Kimi is the available research model", diff --git a/tests/model-harness.test.ts b/tests/model-harness.test.ts index 26ff179..ee9a989 100644 --- a/tests/model-harness.test.ts +++ b/tests/model-harness.test.ts @@ -57,6 +57,16 @@ test("buildModelStatusSnapshotFromRecords flags an invalid current model and sug assert.ok(snapshot.guidance.some((line) => line.includes("Configured default model is unavailable"))); }); +test("chooseRecommendedModel prefers MiniMax M2.7 over highspeed when that is the authenticated provider", () => { + const authPath = createAuthPath({ + minimax: { type: "api_key", key: "minimax-test-key" }, + }); + + const recommendation = chooseRecommendedModel(authPath); + + assert.equal(recommendation?.spec, "minimax/MiniMax-M2.7"); +}); + test("resolveInitialPrompt maps top-level research commands to Pi slash workflows", () => { const workflows = new Set(["lit", "watch", "jobs", "deepresearch"]); assert.equal(resolveInitialPrompt("lit", ["tool-using", "agents"], undefined, workflows), "/lit tool-using agents"); @@ -65,4 +75,3 @@ test("resolveInitialPrompt maps top-level research commands to Pi slash workflow assert.equal(resolveInitialPrompt("chat", ["hello"], undefined, workflows), "hello"); assert.equal(resolveInitialPrompt("unknown", ["topic"], undefined, workflows), "unknown topic"); }); - diff --git a/tests/pi-google-legacy-schema-patch.test.ts b/tests/pi-google-legacy-schema-patch.test.ts new file mode 100644 index 0000000..625e6f2 --- /dev/null +++ b/tests/pi-google-legacy-schema-patch.test.ts @@ -0,0 +1,42 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { patchPiGoogleLegacySchemaSource } from "../scripts/lib/pi-google-legacy-schema-patch.mjs"; + +test("patchPiGoogleLegacySchemaSource rewrites legacy parameters conversion to normalize const", () => { + const input = [ + "export function convertTools(tools, useParameters = false) {", + " if (tools.length === 0) return undefined;", + " return [", + " {", + " functionDeclarations: tools.map((tool) => ({", + " name: tool.name,", + " description: tool.description,", + ' ...(useParameters ? { parameters: tool.parameters } : { parametersJsonSchema: tool.parameters }),', + " })),", + " },", + " ];", + "}", + "", + ].join("\n"); + + const patched = patchPiGoogleLegacySchemaSource(input); + + assert.match(patched, /function normalizeLegacyToolSchema\(schema\)/); + assert.match(patched, /normalized\.enum = \[value\]/); + assert.match(patched, /parameters: normalizeLegacyToolSchema\(tool\.parameters\)/); +}); + +test("patchPiGoogleLegacySchemaSource is idempotent", () => { + const input = [ + "export function convertTools(tools, useParameters = false) {", + ' ...(useParameters ? { parameters: tool.parameters } : { parametersJsonSchema: tool.parameters }),', + "}", + "", + ].join("\n"); + + const once = patchPiGoogleLegacySchemaSource(input); + const twice = patchPiGoogleLegacySchemaSource(once); + + assert.equal(twice, once); +}); diff --git a/website/src/content/docs/getting-started/configuration.md b/website/src/content/docs/getting-started/configuration.md index 406cc51..5e653a1 100644 --- a/website/src/content/docs/getting-started/configuration.md +++ b/website/src/content/docs/getting-started/configuration.md @@ -41,6 +41,36 @@ To see all models you have configured: feynman model list ``` +Only authenticated/configured providers appear in `feynman model list`. If you only see OpenAI models, it usually means only OpenAI auth is configured so far. + +To add another provider, authenticate it first: + +```bash +feynman model login anthropic +feynman model login google +``` + +Then switch the default model: + +```bash +feynman model set anthropic/claude-opus-4-6 +``` + +## Subagent model overrides + +Feynman's bundled subagents inherit the main default model unless you override them explicitly. Inside the REPL, run: + +```bash +/feynman-model +``` + +This opens an interactive picker where you can either: + +- change the main default model for the session environment +- assign a different model to a specific bundled subagent such as `researcher`, `reviewer`, `writer`, or `verifier` + +Per-subagent overrides are persisted in the synced agent files under `~/.feynman/agent/agents/` with a `model:` frontmatter field. Removing that field makes the subagent inherit the main default model again. + ## Thinking levels The `thinkingLevel` field controls how much reasoning the model does before responding. Available levels are `off`, `minimal`, `low`, `medium`, `high`, and `xhigh`. Higher levels produce more thorough analysis at the cost of latency and token usage. You can override per-session: diff --git a/website/src/content/docs/reference/slash-commands.md b/website/src/content/docs/reference/slash-commands.md index a2ab4c5..ed2be2b 100644 --- a/website/src/content/docs/reference/slash-commands.md +++ b/website/src/content/docs/reference/slash-commands.md @@ -30,6 +30,7 @@ These are the primary commands you will use day-to-day. Each workflow dispatches | `/log` | Write a durable session log with completed work, findings, open questions, and next steps | | `/jobs` | Inspect active background work: running processes, scheduled follow-ups, and active watches | | `/help` | Show grouped Feynman commands and prefill the editor with a selected command | +| `/feynman-model` | Open the model picker for the main default model and per-subagent overrides | | `/init` | Bootstrap `AGENTS.md` and session-log folders for a new research project | | `/outputs` | Browse all research artifacts (papers, outputs, experiments, notes) | | `/search` | Search prior session transcripts for past research and findings | @@ -37,6 +38,8 @@ These are the primary commands you will use day-to-day. Each workflow dispatches Session management commands help you organize ongoing work. The `/log` command is particularly useful at the end of a research session to capture what was accomplished and what remains. +The `/feynman-model` command opens an interactive picker that lets you either change the main default model or assign a different model to a bundled subagent like `researcher`, `reviewer`, `writer`, or `verifier`. + ## Running workflows from the CLI All research workflow slash commands can also be run directly from the command line: