From b78eff1f7b640162e56202003a7afc7e1291555f Mon Sep 17 00:00:00 2001 From: cynarAI <155274210+cynarAI@users.noreply.github.com> Date: Mon, 5 Oct 2026 23:18:40 +0200 Subject: [PATCH] Run on the deployment's local models without any model choice, preselect Claude Opus 5.5 otherwise, explain why dictation is unavailable (#151) * Recommend Claude Opus 5.5 by bumping the Pi model catalog The pinned pi-ai 0.85.1 had no Claude Opus 5.5, so the connect flow fell back to Opus 5. pi-ai and pi-agent-core 0.87.1 add it, and the keyless recommendation now preselects it. The new catalog names router aliases "Qwen Max Latest (Qwen3.8 Max)"; those now read "(auto-updates)" like the other alias labels, so no picker entry claims to be "latest". Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD * Name HTTPS as the cause when dictation is unavailable, and translate it Over plain HTTP on a LAN address, Safari hides the microphone and speech recognition, so voice mode said the browser had no dictation, in English inside a German UI. Dictation now checks for a secure context first and says to open the address with https://. Its other errors go through Lingui too, and Safari's blocked recognizer gets a clear message instead of "Dictation failed: not-allowed". Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD * Serve a fake Kokoro voice server in the E2E harness Onboarding's voice step connects to the local Kokoro server on its default port, so the shell E2E failed on any machine where nothing (or a misbehaving server) listened there: the failed request shows up as a browser console error. The harness now starts an offline stand-in and points KOKORO_BASE_URL at it, so the suite no longer depends on the runner host's own voice server. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD * Show only the recommended sign-in until a model is connected With no model connected, "Connect model" opened the full model settings: provider list, tiers and a model picker, even though the recommendation was already preselected. Until something is connected, the Models panel now shows just the preselected model with its sign-in and API key field; "Other providers" reveals the full settings. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD * Give the second chart-links test the same cold-load allowance as the first Each Playwright test runs in a fresh browser context, so the second test's first navigation re-fetches the lazily imported Plot chunk just like the first test's warm-up does. On a loaded CI runner that exceeded the default 20 s expect timeout. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD * Use the recommended model as the Anthropic deployment default A deployment that sets only ANTHROPIC_API_KEY and PI_DEFAULT_PROVIDER=anthropic now runs Claude Opus 5.5, the same model the connect flow preselects, instead of an older default. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD * Run every space on the deployment's local models without a connect step A deployment with PI_DEFAULT_PROVIDER=local and RAKAZO_LOCAL_MODELS set still asked every user to connect a model, because only OpenRouter and Anthropic counted as a deployment model. Configured local models now do, keylessly. RAKAZO_LOCAL_TIERS assigns local models to the tiers the agent delegates to, for spaces that have not mapped their own, and a RAKAZO_LOCAL_MODELS entry can name its own server (id=url) when the models run behind different endpoints. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD * Hide model choices when the deployment routes models itself When the server provides its own models, the system picks and routes them per task, so Settings no longer offers Models and bot settings no longer offer Model and Thinking, on web and mobile. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD * Wait for the workspace pane's width transition before measuring it The pane animates its width over 300 ms, so a single boundingBox read right after it opens can land mid-transition on a slow runner. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD * Allow chart re-renders inside the chart-links loops to take longer Each navigation in the loops renders Observable Plot again. On a loaded CI runner that exceeded the default expect timeout even after the warm-up, so the per-iteration visibility checks get 30 s. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD --------- Co-authored-by: Claude --- .env.example | 4 + apps/api/src/router.test.ts | 4 +- apps/api/src/router.ts | 1 + apps/mobile/app/account.tsx | 20 +- apps/mobile/app/bot-settings.tsx | 40 +- apps/mobile/lib/api.ts | 1 + apps/web/e2e/chart-links.spec.ts | 12 +- apps/web/e2e/first-conversation.spec.ts | 14 +- apps/web/e2e/models-managed.spec.ts | 28 + apps/web/src/lib/dictation.test.ts | 24 + apps/web/src/lib/dictation.ts | 52 +- apps/web/src/locales/de/messages.po | 54 ++ apps/web/src/locales/en/messages.po | 54 ++ apps/web/src/locales/es/messages.po | 54 ++ apps/web/src/locales/hi/messages.po | 54 ++ apps/web/src/locales/ko/messages.po | 54 ++ apps/web/src/locales/pt-BR/messages.po | 54 ++ apps/web/src/locales/ru/messages.po | 54 ++ apps/web/src/locales/tr/messages.po | 54 ++ apps/web/src/locales/zh-CN/messages.po | 54 ++ apps/web/src/pages/ModelSettingsOverlay.tsx | 29 +- apps/web/src/pages/SettingsOverlay.tsx | 6 +- apps/web/src/pages/Shell.tsx | 1 + apps/web/src/pages/shell/bot-panel.tsx | 114 ++-- docs/self-host.md | 13 + packages/adapters/package.json | 4 +- packages/adapters/src/auto-review.ts | 9 +- .../adapters/src/deployment-model.test.ts | 17 +- packages/adapters/src/deployment-model.ts | 10 +- packages/adapters/src/executor.test.ts | 31 +- packages/adapters/src/executor.ts | 8 +- .../adapters/src/pi-local-provider.test.ts | 19 + packages/adapters/src/pi-local-provider.ts | 67 ++- packages/adapters/src/pi-models.test.ts | 5 + packages/adapters/src/pi-models.ts | 7 +- packages/contracts/src/domain.ts | 2 + pnpm-lock.yaml | 502 ++++-------------- 37 files changed, 979 insertions(+), 551 deletions(-) create mode 100644 apps/web/e2e/models-managed.spec.ts diff --git a/.env.example b/.env.example index 5e52003..2a11540 100644 --- a/.env.example +++ b/.env.example @@ -99,8 +99,12 @@ PI_DEFAULT_PROVIDER=openrouter # Computer-use tests can override this with COMPUTER_E2E_MODEL. PI_DEFAULT_MODEL=openai/gpt-5.6-luna # Optional local OpenAI-compatible models. Leave blank to disable the provider. +# With PI_DEFAULT_PROVIDER=local they are the deployment's model and nobody has to connect one. +# An entry may name its own server: id=http://host:port/v1. RAKAZO_LOCAL_MODELS= RAKAZO_LOCAL_MODELS_URL=http://127.0.0.1:11434/v1 +# Optional tiers the agent can delegate to, for every space: fast=id,standard=id,code=id. +RAKAZO_LOCAL_TIERS= RAKAZO_LOCAL_CONTEXT_WINDOW=32768 RAKAZO_LOCAL_MAX_TOKENS=4096 # Allow user-connected OpenAI-compatible endpoints on public hostnames (default: private/loopback only). diff --git a/apps/api/src/router.test.ts b/apps/api/src/router.test.ts index 9cb1b54..8db970a 100644 --- a/apps/api/src/router.test.ts +++ b/apps/api/src/router.test.ts @@ -226,6 +226,7 @@ describe("model setup gate", () => { await expect(response.json()).resolves.toEqual({ json: expect.objectContaining({ needsModel: false, + modelsManaged: true, defaultProvider: "openrouter", defaultModel: "test-model", }), @@ -245,8 +246,9 @@ describe("model setup gate", () => { // The connect flow opens on the recommendation, not the keyless deployment default. json: expect.objectContaining({ needsModel: true, + modelsManaged: false, defaultProvider: "anthropic", - defaultModel: "claude-opus-5", + defaultModel: "claude-opus-5-5", }), }); }); diff --git a/apps/api/src/router.ts b/apps/api/src/router.ts index 0c19853..9165644 100644 --- a/apps/api/src/router.ts +++ b/apps/api/src/router.ts @@ -5543,6 +5543,7 @@ async function meDto(deps: RouterDeps, actor: Actor): Promise { spaceId: actor.spaceId, isDeploymentOwner: actor.isDeploymentOwner, needsModel: setup.needsModel, + modelsManaged: Boolean(deps.env.deploymentModelKey), defaultProvider: setup.credential?.provider ?? setup.settings?.defaultModelProvider ?? fallback.provider, defaultModel: diff --git a/apps/mobile/app/account.tsx b/apps/mobile/app/account.tsx index 19bda74..e4cda63 100644 --- a/apps/mobile/app/account.tsx +++ b/apps/mobile/app/account.tsx @@ -353,15 +353,17 @@ export default function Account() { ) : null} - router.push("/models")} - style={({ pressed }) => [styles.settingsButton, pressed && styles.pressed]} - > - {t("Models")} - › - + {me?.modelsManaged ? null : ( + router.push("/models")} + style={({ pressed }) => [styles.settingsButton, pressed && styles.pressed]} + > + {t("Models")} + › + + )} - setAdvancedOpen((open) => !open)} - style={{ - marginTop: 20, - minHeight: 44, - flexDirection: "row", - alignItems: "center", - justifyContent: "space-between", - }} - > - {t("Advanced")} - - {advancedOpen ? "⌃" : "⌄"} - - - {advancedOpen ? ( + {me?.modelsManaged ? null : ( + setAdvancedOpen((open) => !open)} + style={{ + marginTop: 20, + minHeight: 44, + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + }} + > + {t("Advanced")} + + {advancedOpen ? "⌃" : "⌄"} + + + )} + {advancedOpen && !me?.modelsManaged ? ( ; diff --git a/apps/web/e2e/chart-links.spec.ts b/apps/web/e2e/chart-links.spec.ts index 81f6950..f6d240f 100644 --- a/apps/web/e2e/chart-links.spec.ts +++ b/apps/web/e2e/chart-links.spec.ts @@ -18,7 +18,8 @@ test("chart links cannot execute script when clicked or expanded", async ({ page ]) { await page.goto(`${fixture}?${new URLSearchParams({ href, inspect })}`); const circle = page.locator("svg circle").first(); - await expect(circle).toBeVisible(); + // Every navigation renders Plot again, which takes seconds on a loaded CI runner. + await expect(circle).toBeVisible({ timeout: 30_000 }); const assertInert = async () => { const links = page.locator("svg a"); expect(await links.count()).toBeGreaterThan(0); @@ -40,7 +41,7 @@ test("chart links cannot execute script when clicked or expanded", async ({ page await assertInert(); await page.getByRole("button", { name: "Expand", exact: true }).click(); const dialog = page.getByRole("dialog"); - await expect(dialog.locator("svg circle")).toBeVisible(); + await expect(dialog.locator("svg circle")).toBeVisible({ timeout: 30_000 }); await dialog.locator("svg circle").click(); await assertInert(); await expect(page).toHaveURL(`${fixture}?${new URLSearchParams({ href, inspect })}`); @@ -51,12 +52,16 @@ test("chart links cannot execute script when clicked or expanded", async ({ page }); test("chart links retain supported URLs and browser encoding semantics", async ({ page }) => { + test.setTimeout(120_000); await page.route("https://example.test/**", (route) => route.fulfill({ body: "Chart destination" }), ); await page.route("**/e2e/fixtures/java*", (route) => route.fulfill({ body: "Chart destination" }), ); + // Each test gets a fresh browser context, so this one pays the cold Plot import too. + await page.goto(`${fixture}?${new URLSearchParams({ href: "#details" })}`); + await expect(page.locator("svg circle").first()).toBeVisible({ timeout: 90_000 }); for (const href of [ "https://example.test/chart?a=1&b=2", "mailto:chart@example.test", @@ -67,8 +72,7 @@ test("chart links retain supported URLs and browser encoding semantics", async ( "java%73cript:void(0)", ]) { await page.goto(`${fixture}?${new URLSearchParams({ href })}`); - // ChartCanvas lazy-loads Plot; mirror the inert-link test and wait for marks first. - await expect(page.locator("svg circle").first()).toBeVisible(); + await expect(page.locator("svg circle").first()).toBeVisible({ timeout: 30_000 }); const link = page.locator("svg a").first(); await expect(link).toBeAttached(); expect( diff --git a/apps/web/e2e/first-conversation.spec.ts b/apps/web/e2e/first-conversation.spec.ts index 60aee0f..3e181d4 100644 --- a/apps/web/e2e/first-conversation.spec.ts +++ b/apps/web/e2e/first-conversation.spec.ts @@ -27,7 +27,11 @@ test("without a model the composer offers to connect one", async ({ page }) => { await completeOnboarding(page, undefined, { mainConversation: true }); // Scripted CI has a model; this is what the API returns without one. - const keyless = { needsModel: true, defaultProvider: "anthropic", defaultModel: "claude-opus-5" }; + const keyless = { + needsModel: true, + defaultProvider: "anthropic", + defaultModel: "claude-opus-5-5", + }; await routePatchRpc(page, "bootstrap", (json) => ({ ...json, me: { ...(json.me as Record), ...keyless }, @@ -43,9 +47,15 @@ test("without a model the composer offers to connect one", async ({ page }) => { const settings = page.getByTestId("model-settings"); await expect(settings).toBeVisible(); // The recommendation is preselected, so only the key or sign-in is left. - await expect(settings.getByRole("combobox", { name: "Model" })).toHaveText("Claude Opus 5"); + await expect(settings).toContainText("Claude Opus 5.5"); + await expect(settings.getByRole("combobox", { name: "Model" })).toHaveCount(0); + await expect(settings.getByRole("button", { name: /Sign in with Claude/ })).toBeVisible(); + await expect(settings.getByLabel("Or connect an API key")).toBeVisible(); await captureScreenshot(page, testInfo, "model-settings-recommended"); await page.emulateMedia({ colorScheme: "dark" }); await expect(page.locator("html")).toHaveAttribute("data-theme", "dark"); await captureScreenshot(page, testInfo, "model-settings-recommended-dark"); + + await settings.getByRole("button", { name: "Other providers" }).click(); + await expect(settings.getByRole("combobox", { name: "Model" })).toHaveText("Claude Opus 5.5"); }); diff --git a/apps/web/e2e/models-managed.spec.ts b/apps/web/e2e/models-managed.spec.ts new file mode 100644 index 0000000..1f4c570 --- /dev/null +++ b/apps/web/e2e/models-managed.spec.ts @@ -0,0 +1,28 @@ +import { expect, test } from "@playwright/test"; +import { + captureScreenshot, + completeOnboarding, + openUserSettings, + routePatchRpc, + signup, +} from "./helpers"; + +test("settings hide Models when the deployment routes models itself", async ({ + page, +}, testInfo) => { + await routePatchRpc(page, "me", (json) => ({ ...json, modelsManaged: true })); + await routePatchRpc(page, "bootstrap", (json) => ({ + ...json, + me: { ...(json.me as Record), modelsManaged: true }, + })); + + const stamp = Date.now(); + await signup(page, `models-managed-${stamp}@rakazo.test`, "password12", `Managed ${stamp}`); + await completeOnboarding(page); + + const settings = await openUserSettings(page); + await expect(settings.getByTestId("settings-nav-general")).toBeVisible(); + await expect(settings.getByTestId("settings-nav-memory")).toBeVisible(); + await expect(settings.getByTestId("settings-nav-models")).toHaveCount(0); + await captureScreenshot(page, testInfo, "settings-models-managed"); +}); diff --git a/apps/web/src/lib/dictation.test.ts b/apps/web/src/lib/dictation.test.ts index 40341aa..a01f1a6 100644 --- a/apps/web/src/lib/dictation.test.ts +++ b/apps/web/src/lib/dictation.test.ts @@ -1,4 +1,13 @@ import { afterEach, describe, expect, it, vi } from "vitest"; + +vi.mock("@lingui/core/macro", () => ({ + t: (parts: TemplateStringsArray, ...values: unknown[]) => + parts.reduce( + (text, part, index) => text + part + (index < values.length ? values[index] : ""), + "", + ), +})); + import { Dictation, MAX_TRANSCRIPTION_RESPONSE_BYTES, @@ -662,3 +671,18 @@ describe("Dictation web speech", () => { expect(dictation.state.error).toMatch(/Microphone access denied/i); }); }); + +describe("Dictation outside a secure context", () => { + it("names HTTPS as the cause instead of blaming the browser", async () => { + vi.stubGlobal("window", { isSecureContext: false }); + const getUserMedia = vi.fn(); + vi.stubGlobal("navigator", { mediaDevices: { getUserMedia }, language: "de-DE" }); + const dictation = new Dictation(); + + await dictation.listen({ mode: "endpoint", transcribe: true, onFinal: vi.fn() }); + + expect(dictation.state.status).toBe("idle"); + expect(dictation.state.error).toMatch(/https:\/\//); + expect(getUserMedia).not.toHaveBeenCalled(); + }); +}); diff --git a/apps/web/src/lib/dictation.ts b/apps/web/src/lib/dictation.ts index 425935a..ffab1c6 100644 --- a/apps/web/src/lib/dictation.ts +++ b/apps/web/src/lib/dictation.ts @@ -1,3 +1,4 @@ +import { t } from "@lingui/core/macro"; import { readBoundedResponseBytes } from "@rakazo/core"; import { selectedSpaceId, withSpaceHeaders } from "./rpc.js"; @@ -30,10 +31,15 @@ const ENDPOINT_TICK_MS = 80; const SILENCE_RMS = 0.035; export const TRANSCRIPTION_RESPONSE_TIMEOUT_MS = 70_000; export const MAX_TRANSCRIPTION_RESPONSE_BYTES = 64 * 1024; -const ENDPOINT_UNSUPPORTED = - "This browser can't detect when you stop talking. Use Chrome, the desktop app, or hold-to-talk in the composer."; -const MIC_UNAVAILABLE = - "Microphone access is not available in this browser. Use Chrome, the desktop app, or check system settings."; +const endpointUnsupported = () => + t`This browser can't detect when you stop talking. Use Chrome, the desktop app, or hold-to-talk in the composer.`; +const micUnavailable = () => + t`Microphone access is not available in this browser. Use Chrome, the desktop app, or check system settings.`; + +/** Browsers hide the microphone and speech recognition outside HTTPS and localhost. */ +function insecureContext(): boolean { + return typeof window !== "undefined" && window.isSecureContext === false; +} export function webSpeechAvailable(): boolean { return Boolean(speechRecognitionCtor()); @@ -131,6 +137,13 @@ export class Dictation { const mine = this.token; const spaceId = selectedSpaceId(); this.onFinal = opts.onFinal; + if (insecureContext()) { + this.set({ + ...IDLE, + error: t`Voice input needs a secure connection. Open this address with https://.`, + }); + return; + } this.set({ status: "listening", transcript: "", energy: 0 }); if (webSpeechAvailable()) { const stream = await this.openMic(mine); @@ -146,8 +159,7 @@ export class Dictation { } this.set({ ...IDLE, - error: - "This browser has no on-device dictation. Connect a voice provider with transcription, or use Chrome / the desktop app.", + error: t`This browser can't recognize speech itself. Connect a voice provider with transcription, or use Safari, Chrome or the desktop app.`, }); } @@ -182,7 +194,7 @@ export class Dictation { if (event.error === "aborted" || event.error === "no-speech") return; this.set({ ...IDLE, - error: event.error ? `Dictation failed: ${event.error}` : "Dictation failed.", + error: dictationFailed(event.error), }); }; rec.onend = () => { @@ -200,7 +212,7 @@ export class Dictation { try { rec.start(); } catch { - this.set({ ...IDLE, error: "Dictation ended unexpectedly." }); + this.set({ ...IDLE, error: t`Dictation ended unexpectedly.` }); } }; this.recognition = rec; @@ -210,7 +222,7 @@ export class Dictation { private async openMic(mine: number): Promise { if (!navigator.mediaDevices?.getUserMedia) { if (this.token !== mine) return null; - this.set({ ...IDLE, error: MIC_UNAVAILABLE }); + this.set({ ...IDLE, error: micUnavailable() }); return null; } try { @@ -236,7 +248,7 @@ export class Dictation { } if (mode === "endpoint" && !this.armSilence(stream, mine, endpointMs)) { for (const track of stream.getTracks()) track.stop(); - this.set({ ...IDLE, error: ENDPOINT_UNSUPPORTED }); + this.set({ ...IDLE, error: endpointUnsupported() }); return; } const media = new MediaRecorder(stream); @@ -375,7 +387,7 @@ export class Dictation { const body = await readTranscriptionBody(res, abort.signal); if (this.token !== mine) return; if (!res.ok) { - this.set({ ...IDLE, error: body.error ?? "Could not transcribe that recording." }); + this.set({ ...IDLE, error: body.error ?? t`Could not transcribe that recording.` }); return; } this.finish(body.text ?? "", mine); @@ -389,12 +401,12 @@ export class Dictation { abort.signal.reason.message === "Transcription request timed out.") || (error instanceof Error && error.message === "Transcription request timed out."); if (timedOut || (error instanceof Error && error.name === "AbortError")) { - this.set({ ...IDLE, error: "Transcription request timed out." }); + this.set({ ...IDLE, error: t`Transcription request timed out.` }); return; } this.set({ ...IDLE, - error: error instanceof Error ? error.message : "Could not transcribe that recording.", + error: error instanceof Error ? error.message : t`Could not transcribe that recording.`, }); } finally { clearTimeout(timer); @@ -495,13 +507,21 @@ function rmsEnergyFromAnalyser(analyser: AnalyserNode, data: Uint8Array): number function micErrorMessage(error: unknown): string { if (error instanceof DOMException) { if (error.name === "NotAllowedError") { - return "Microphone access denied. Allow the microphone in your browser or system settings, then try again."; + return t`Microphone access denied. Allow the microphone in your browser or system settings, then try again.`; } if (error.name === "NotFoundError") { - return "No microphone found. Connect a microphone and try again."; + return t`No microphone found. Connect a microphone and try again.`; } } - return error instanceof Error ? error.message : "Microphone failed"; + return error instanceof Error ? error.message : t`Microphone failed`; +} + +function dictationFailed(code: string | undefined): string { + // Safari reports a blocked recognizer (Siri & Dictation off) as not-allowed. + if (code === "not-allowed" || code === "service-not-allowed") { + return t`Speech recognition is blocked. Allow the microphone and turn on dictation in your system settings.`; + } + return code ? t`Dictation failed: ${code}` : t`Dictation failed.`; } export const dictation = new Dictation(); diff --git a/apps/web/src/locales/de/messages.po b/apps/web/src/locales/de/messages.po index 8391d39..a25da63 100644 --- a/apps/web/src/locales/de/messages.po +++ b/apps/web/src/locales/de/messages.po @@ -1352,6 +1352,7 @@ msgstr "Passwort bestätigen" msgid "Connect" msgstr "Verbinden" +#: src/pages/ModelSettingsOverlay.tsx #: src/pages/shell/composer.tsx msgid "Connect a model so your Oo can reply." msgstr "Verbinde ein Modell, damit dein Oo antworten kann." @@ -1778,6 +1779,10 @@ msgstr "Antwort konnte nicht gesendet werden" msgid "Could not take control" msgstr "Kontrolle konnte nicht übernommen werden" +#: src/lib/dictation.ts +msgid "Could not transcribe that recording." +msgstr "Die Aufnahme konnte nicht transkribiert werden." + #: src/pages/ScratchpadSection.tsx msgid "Could not update" msgstr "Aktualisierung fehlgeschlagen" @@ -2089,6 +2094,18 @@ msgstr "Desktop-Update" msgid "Developer tools" msgstr "Entwicklertools" +#: src/lib/dictation.ts +msgid "Dictation ended unexpectedly." +msgstr "Das Diktat wurde unerwartet beendet." + +#: src/lib/dictation.ts +msgid "Dictation failed: {code}" +msgstr "Diktat fehlgeschlagen: {code}" + +#: src/lib/dictation.ts +msgid "Dictation failed." +msgstr "Diktat fehlgeschlagen." + #: src/components/integrations/IntegrationSetup.tsx msgid "Direct MCP" msgstr "Direktes MCP" @@ -3206,6 +3223,15 @@ msgstr "Nachricht an {peer} gesendet" msgid "Messaging" msgstr "Messaging" +#: src/lib/dictation.ts +msgid "Microphone access denied. Allow the microphone in your browser or system settings, then try again." +msgstr "Kein Zugriff aufs Mikrofon. Erlaube das Mikrofon in den Browser- oder Systemeinstellungen und versuch es noch einmal." + +#: src/lib/dictation.ts +msgid "Microphone access is not available in this browser. Use Chrome, the desktop app, or check system settings." +msgstr "Dieser Browser bietet keinen Mikrofonzugriff. Nutze Chrome, die Desktop-App oder prüfe die Systemeinstellungen." + +#: src/lib/dictation.ts #: src/pages/CallView.tsx msgid "Microphone failed" msgstr "Mikrofonfehler" @@ -3518,6 +3544,10 @@ msgstr "Noch keine MCP-Server." msgid "No messages with {peerBotName} yet." msgstr "Noch keine Nachrichten mit {peerBotName}." +#: src/lib/dictation.ts +msgid "No microphone found. Connect a microphone and try again." +msgstr "Kein Mikrofon gefunden. Schließ ein Mikrofon an und versuch es noch einmal." + #: src/pages/ModelSettingsOverlay.tsx msgid "No model catalog is available." msgstr "Kein Modellkatalog verfügbar." @@ -3751,6 +3781,10 @@ msgstr "Organisations-API-Schlüssel" msgid "Other model…" msgstr "Anderes Modell…" +#: src/pages/ModelSettingsOverlay.tsx +msgid "Other providers" +msgstr "Andere Anbieter" + #: src/components/browser/InAppBrowser.tsx msgid "Page" msgstr "Seite" @@ -4737,6 +4771,10 @@ msgstr "Spezialisten-Team" msgid "Species" msgstr "Wesen" +#: src/lib/dictation.ts +msgid "Speech recognition is blocked. Allow the microphone and turn on dictation in your system settings." +msgstr "Die Spracherkennung ist blockiert. Erlaube das Mikrofon und schalte das Diktieren in den Systemeinstellungen ein." + #: src/lib/model-tier-label.ts msgid "Standard" msgstr "Standard" @@ -4965,6 +5003,14 @@ msgstr "Denkmodus" msgid "This bot runs on this computer, not a Linux desktop. Shell and files use your home folder." msgstr "Dieser Bot wird auf diesem Computer und nicht auf einem Linux-Desktop ausgeführt. Shell und Dateien verwenden deinen persönlichen Ordner." +#: src/lib/dictation.ts +msgid "This browser can't detect when you stop talking. Use Chrome, the desktop app, or hold-to-talk in the composer." +msgstr "Dieser Browser erkennt nicht, wann du aufhörst zu sprechen. Nutze Chrome, die Desktop-App oder halte im Eingabefeld zum Sprechen gedrückt." + +#: src/lib/dictation.ts +msgid "This browser can't recognize speech itself. Connect a voice provider with transcription, or use Safari, Chrome or the desktop app." +msgstr "Dieser Browser kann Sprache nicht selbst erkennen. Verbinde einen Sprachanbieter mit Transkription oder nutze Safari, Chrome oder die Desktop-App." + #: src/pages/shell/dialogs.tsx msgid "This cannot be undone." msgstr "Dies kann nicht rückgängig gemacht werden." @@ -5099,6 +5145,10 @@ msgstr "Transkript" msgid "Transcription" msgstr "Transkription" +#: src/lib/dictation.ts +msgid "Transcription request timed out." +msgstr "Die Transkription hat zu lange gedauert." + #: src/pages/ExternalConversationSettings.tsx msgid "Treat like a person" msgstr "Wie eine Person behandeln" @@ -5298,6 +5348,10 @@ msgstr "Stimme" msgid "Voice \"{0}\" not found." msgstr "Stimme „{0}“ nicht gefunden." +#: src/lib/dictation.ts +msgid "Voice input needs a secure connection. Open this address with https://." +msgstr "Spracheingabe braucht eine sichere Verbindung. Öffne diese Adresse mit https://." + #: src/components/onboarding/OnboardingVoiceStep.tsx msgid "Voice is not available yet. You can turn it on later in settings." msgstr "Die Stimme ist noch nicht verfügbar. Du kannst sie später in den Einstellungen aktivieren." diff --git a/apps/web/src/locales/en/messages.po b/apps/web/src/locales/en/messages.po index 048931c..c029676 100644 --- a/apps/web/src/locales/en/messages.po +++ b/apps/web/src/locales/en/messages.po @@ -1352,6 +1352,7 @@ msgstr "Confirm password" msgid "Connect" msgstr "Connect" +#: src/pages/ModelSettingsOverlay.tsx #: src/pages/shell/composer.tsx msgid "Connect a model so your Oo can reply." msgstr "Connect a model so your Oo can reply." @@ -1778,6 +1779,10 @@ msgstr "Could not submit this answer" msgid "Could not take control" msgstr "Could not take control" +#: src/lib/dictation.ts +msgid "Could not transcribe that recording." +msgstr "Could not transcribe that recording." + #: src/pages/ScratchpadSection.tsx msgid "Could not update" msgstr "Could not update" @@ -2089,6 +2094,18 @@ msgstr "Desktop update" msgid "Developer tools" msgstr "Developer tools" +#: src/lib/dictation.ts +msgid "Dictation ended unexpectedly." +msgstr "Dictation ended unexpectedly." + +#: src/lib/dictation.ts +msgid "Dictation failed: {code}" +msgstr "Dictation failed: {code}" + +#: src/lib/dictation.ts +msgid "Dictation failed." +msgstr "Dictation failed." + #: src/components/integrations/IntegrationSetup.tsx msgid "Direct MCP" msgstr "Direct MCP" @@ -3206,6 +3223,15 @@ msgstr "Messaged {peer}" msgid "Messaging" msgstr "Messaging" +#: src/lib/dictation.ts +msgid "Microphone access denied. Allow the microphone in your browser or system settings, then try again." +msgstr "Microphone access denied. Allow the microphone in your browser or system settings, then try again." + +#: src/lib/dictation.ts +msgid "Microphone access is not available in this browser. Use Chrome, the desktop app, or check system settings." +msgstr "Microphone access is not available in this browser. Use Chrome, the desktop app, or check system settings." + +#: src/lib/dictation.ts #: src/pages/CallView.tsx msgid "Microphone failed" msgstr "Microphone failed" @@ -3518,6 +3544,10 @@ msgstr "No MCP servers yet." msgid "No messages with {peerBotName} yet." msgstr "No messages with {peerBotName} yet." +#: src/lib/dictation.ts +msgid "No microphone found. Connect a microphone and try again." +msgstr "No microphone found. Connect a microphone and try again." + #: src/pages/ModelSettingsOverlay.tsx msgid "No model catalog is available." msgstr "No model catalog is available." @@ -3751,6 +3781,10 @@ msgstr "Organization API key" msgid "Other model…" msgstr "Other model…" +#: src/pages/ModelSettingsOverlay.tsx +msgid "Other providers" +msgstr "Other providers" + #: src/components/browser/InAppBrowser.tsx msgid "Page" msgstr "Page" @@ -4737,6 +4771,10 @@ msgstr "Specialist crew" msgid "Species" msgstr "Species" +#: src/lib/dictation.ts +msgid "Speech recognition is blocked. Allow the microphone and turn on dictation in your system settings." +msgstr "Speech recognition is blocked. Allow the microphone and turn on dictation in your system settings." + #: src/lib/model-tier-label.ts msgid "Standard" msgstr "Standard" @@ -4965,6 +5003,14 @@ msgstr "Thinking" msgid "This bot runs on this computer, not a Linux desktop. Shell and files use your home folder." msgstr "This bot runs on this computer, not a Linux desktop. Shell and files use your home folder." +#: src/lib/dictation.ts +msgid "This browser can't detect when you stop talking. Use Chrome, the desktop app, or hold-to-talk in the composer." +msgstr "This browser can't detect when you stop talking. Use Chrome, the desktop app, or hold-to-talk in the composer." + +#: src/lib/dictation.ts +msgid "This browser can't recognize speech itself. Connect a voice provider with transcription, or use Safari, Chrome or the desktop app." +msgstr "This browser can't recognize speech itself. Connect a voice provider with transcription, or use Safari, Chrome or the desktop app." + #: src/pages/shell/dialogs.tsx msgid "This cannot be undone." msgstr "This cannot be undone." @@ -5099,6 +5145,10 @@ msgstr "Transcript" msgid "Transcription" msgstr "Transcription" +#: src/lib/dictation.ts +msgid "Transcription request timed out." +msgstr "Transcription request timed out." + #: src/pages/ExternalConversationSettings.tsx msgid "Treat like a person" msgstr "Treat like a person" @@ -5298,6 +5348,10 @@ msgstr "Voice" msgid "Voice \"{0}\" not found." msgstr "Voice \"{0}\" not found." +#: src/lib/dictation.ts +msgid "Voice input needs a secure connection. Open this address with https://." +msgstr "Voice input needs a secure connection. Open this address with https://." + #: src/components/onboarding/OnboardingVoiceStep.tsx msgid "Voice is not available yet. You can turn it on later in settings." msgstr "Voice is not available yet. You can turn it on later in settings." diff --git a/apps/web/src/locales/es/messages.po b/apps/web/src/locales/es/messages.po index 2c24115..c7ef11c 100644 --- a/apps/web/src/locales/es/messages.po +++ b/apps/web/src/locales/es/messages.po @@ -1352,6 +1352,7 @@ msgstr "Confirmar contraseña" msgid "Connect" msgstr "Conectar" +#: src/pages/ModelSettingsOverlay.tsx #: src/pages/shell/composer.tsx msgid "Connect a model so your Oo can reply." msgstr "" @@ -1778,6 +1779,10 @@ msgstr "No se pudo enviar esta respuesta" msgid "Could not take control" msgstr "No se pudo tomar el control" +#: src/lib/dictation.ts +msgid "Could not transcribe that recording." +msgstr "" + #: src/pages/ScratchpadSection.tsx msgid "Could not update" msgstr "No se pudo actualizar" @@ -2089,6 +2094,18 @@ msgstr "" msgid "Developer tools" msgstr "" +#: src/lib/dictation.ts +msgid "Dictation ended unexpectedly." +msgstr "" + +#: src/lib/dictation.ts +msgid "Dictation failed: {code}" +msgstr "" + +#: src/lib/dictation.ts +msgid "Dictation failed." +msgstr "" + #: src/components/integrations/IntegrationSetup.tsx msgid "Direct MCP" msgstr "" @@ -3206,6 +3223,15 @@ msgstr "Mensaje enviado a {peer}" msgid "Messaging" msgstr "Mensajería" +#: src/lib/dictation.ts +msgid "Microphone access denied. Allow the microphone in your browser or system settings, then try again." +msgstr "" + +#: src/lib/dictation.ts +msgid "Microphone access is not available in this browser. Use Chrome, the desktop app, or check system settings." +msgstr "" + +#: src/lib/dictation.ts #: src/pages/CallView.tsx msgid "Microphone failed" msgstr "Falló el micrófono" @@ -3518,6 +3544,10 @@ msgstr "Aún no hay servidores MCP." msgid "No messages with {peerBotName} yet." msgstr "Aún no hay mensajes con {peerBotName}." +#: src/lib/dictation.ts +msgid "No microphone found. Connect a microphone and try again." +msgstr "" + #: src/pages/ModelSettingsOverlay.tsx msgid "No model catalog is available." msgstr "No hay un catálogo de modelos disponible." @@ -3751,6 +3781,10 @@ msgstr "Clave de API de organización" msgid "Other model…" msgstr "Otro modelo…" +#: src/pages/ModelSettingsOverlay.tsx +msgid "Other providers" +msgstr "" + #: src/components/browser/InAppBrowser.tsx msgid "Page" msgstr "" @@ -4737,6 +4771,10 @@ msgstr "" msgid "Species" msgstr "" +#: src/lib/dictation.ts +msgid "Speech recognition is blocked. Allow the microphone and turn on dictation in your system settings." +msgstr "" + #: src/lib/model-tier-label.ts msgid "Standard" msgstr "" @@ -4965,6 +5003,14 @@ msgstr "Pensando" msgid "This bot runs on this computer, not a Linux desktop. Shell and files use your home folder." msgstr "Este bot se ejecuta en esta computadora, no en un escritorio Linux. El shell y los archivos usan tu carpeta de inicio." +#: src/lib/dictation.ts +msgid "This browser can't detect when you stop talking. Use Chrome, the desktop app, or hold-to-talk in the composer." +msgstr "" + +#: src/lib/dictation.ts +msgid "This browser can't recognize speech itself. Connect a voice provider with transcription, or use Safari, Chrome or the desktop app." +msgstr "" + #: src/pages/shell/dialogs.tsx msgid "This cannot be undone." msgstr "Esto no se puede deshacer." @@ -5099,6 +5145,10 @@ msgstr "" msgid "Transcription" msgstr "" +#: src/lib/dictation.ts +msgid "Transcription request timed out." +msgstr "" + #: src/pages/ExternalConversationSettings.tsx msgid "Treat like a person" msgstr "" @@ -5298,6 +5348,10 @@ msgstr "Voz" msgid "Voice \"{0}\" not found." msgstr "" +#: src/lib/dictation.ts +msgid "Voice input needs a secure connection. Open this address with https://." +msgstr "" + #: src/components/onboarding/OnboardingVoiceStep.tsx msgid "Voice is not available yet. You can turn it on later in settings." msgstr "" diff --git a/apps/web/src/locales/hi/messages.po b/apps/web/src/locales/hi/messages.po index f5d2eac..ff32c2c 100644 --- a/apps/web/src/locales/hi/messages.po +++ b/apps/web/src/locales/hi/messages.po @@ -1352,6 +1352,7 @@ msgstr "पासवर्ड की पुष्टि करें" msgid "Connect" msgstr "कनेक्ट करें" +#: src/pages/ModelSettingsOverlay.tsx #: src/pages/shell/composer.tsx msgid "Connect a model so your Oo can reply." msgstr "" @@ -1778,6 +1779,10 @@ msgstr "यह उत्तर सबमिट नहीं किया जा msgid "Could not take control" msgstr "नियंत्रण नहीं लिया जा सका" +#: src/lib/dictation.ts +msgid "Could not transcribe that recording." +msgstr "" + #: src/pages/ScratchpadSection.tsx msgid "Could not update" msgstr "अपडेट नहीं हो सका" @@ -2089,6 +2094,18 @@ msgstr "" msgid "Developer tools" msgstr "" +#: src/lib/dictation.ts +msgid "Dictation ended unexpectedly." +msgstr "" + +#: src/lib/dictation.ts +msgid "Dictation failed: {code}" +msgstr "" + +#: src/lib/dictation.ts +msgid "Dictation failed." +msgstr "" + #: src/components/integrations/IntegrationSetup.tsx msgid "Direct MCP" msgstr "" @@ -3206,6 +3223,15 @@ msgstr "{peer} को संदेश भेजा" msgid "Messaging" msgstr "" +#: src/lib/dictation.ts +msgid "Microphone access denied. Allow the microphone in your browser or system settings, then try again." +msgstr "" + +#: src/lib/dictation.ts +msgid "Microphone access is not available in this browser. Use Chrome, the desktop app, or check system settings." +msgstr "" + +#: src/lib/dictation.ts #: src/pages/CallView.tsx msgid "Microphone failed" msgstr "माइक्रोफ़ोन विफल" @@ -3518,6 +3544,10 @@ msgstr "अभी तक कोई MCP सर्वर नहीं।" msgid "No messages with {peerBotName} yet." msgstr "{peerBotName} के साथ अभी कोई संदेश नहीं." +#: src/lib/dictation.ts +msgid "No microphone found. Connect a microphone and try again." +msgstr "" + #: src/pages/ModelSettingsOverlay.tsx msgid "No model catalog is available." msgstr "कोई मॉडल कैटलॉग उपलब्ध नहीं है।" @@ -3751,6 +3781,10 @@ msgstr "संगठन API कुंजी" msgid "Other model…" msgstr "अन्य मॉडल…" +#: src/pages/ModelSettingsOverlay.tsx +msgid "Other providers" +msgstr "" + #: src/components/browser/InAppBrowser.tsx msgid "Page" msgstr "" @@ -4737,6 +4771,10 @@ msgstr "" msgid "Species" msgstr "" +#: src/lib/dictation.ts +msgid "Speech recognition is blocked. Allow the microphone and turn on dictation in your system settings." +msgstr "" + #: src/lib/model-tier-label.ts msgid "Standard" msgstr "" @@ -4965,6 +5003,14 @@ msgstr "सोच रहा है" msgid "This bot runs on this computer, not a Linux desktop. Shell and files use your home folder." msgstr "यह बॉट इसी कंप्यूटर पर चलता है, किसी Linux डेस्कटॉप पर नहीं। शेल और फ़ाइलें आपके होम फ़ोल्डर का उपयोग करती हैं।" +#: src/lib/dictation.ts +msgid "This browser can't detect when you stop talking. Use Chrome, the desktop app, or hold-to-talk in the composer." +msgstr "" + +#: src/lib/dictation.ts +msgid "This browser can't recognize speech itself. Connect a voice provider with transcription, or use Safari, Chrome or the desktop app." +msgstr "" + #: src/pages/shell/dialogs.tsx msgid "This cannot be undone." msgstr "इसे पूर्ववत नहीं किया जा सकता।" @@ -5099,6 +5145,10 @@ msgstr "" msgid "Transcription" msgstr "" +#: src/lib/dictation.ts +msgid "Transcription request timed out." +msgstr "" + #: src/pages/ExternalConversationSettings.tsx msgid "Treat like a person" msgstr "" @@ -5298,6 +5348,10 @@ msgstr "आवाज़" msgid "Voice \"{0}\" not found." msgstr "" +#: src/lib/dictation.ts +msgid "Voice input needs a secure connection. Open this address with https://." +msgstr "" + #: src/components/onboarding/OnboardingVoiceStep.tsx msgid "Voice is not available yet. You can turn it on later in settings." msgstr "" diff --git a/apps/web/src/locales/ko/messages.po b/apps/web/src/locales/ko/messages.po index bb01eae..51ebe9a 100644 --- a/apps/web/src/locales/ko/messages.po +++ b/apps/web/src/locales/ko/messages.po @@ -1352,6 +1352,7 @@ msgstr "비밀번호 확인" msgid "Connect" msgstr "연결" +#: src/pages/ModelSettingsOverlay.tsx #: src/pages/shell/composer.tsx msgid "Connect a model so your Oo can reply." msgstr "" @@ -1778,6 +1779,10 @@ msgstr "답변을 보내지 못했습니다." msgid "Could not take control" msgstr "직접 조작으로 전환하지 못했습니다." +#: src/lib/dictation.ts +msgid "Could not transcribe that recording." +msgstr "" + #: src/pages/ScratchpadSection.tsx msgid "Could not update" msgstr "업데이트하지 못했습니다." @@ -2089,6 +2094,18 @@ msgstr "" msgid "Developer tools" msgstr "" +#: src/lib/dictation.ts +msgid "Dictation ended unexpectedly." +msgstr "" + +#: src/lib/dictation.ts +msgid "Dictation failed: {code}" +msgstr "" + +#: src/lib/dictation.ts +msgid "Dictation failed." +msgstr "" + #: src/components/integrations/IntegrationSetup.tsx msgid "Direct MCP" msgstr "" @@ -3206,6 +3223,15 @@ msgstr "{peer}에게 메시지 보냄" msgid "Messaging" msgstr "메시징" +#: src/lib/dictation.ts +msgid "Microphone access denied. Allow the microphone in your browser or system settings, then try again." +msgstr "" + +#: src/lib/dictation.ts +msgid "Microphone access is not available in this browser. Use Chrome, the desktop app, or check system settings." +msgstr "" + +#: src/lib/dictation.ts #: src/pages/CallView.tsx msgid "Microphone failed" msgstr "마이크를 사용할 수 없습니다." @@ -3518,6 +3544,10 @@ msgstr "아직 연결된 MCP 서버가 없습니다." msgid "No messages with {peerBotName} yet." msgstr "{peerBotName}와의 메시지가 아직 없습니다." +#: src/lib/dictation.ts +msgid "No microphone found. Connect a microphone and try again." +msgstr "" + #: src/pages/ModelSettingsOverlay.tsx msgid "No model catalog is available." msgstr "사용 가능한 모델 목록이 없습니다." @@ -3751,6 +3781,10 @@ msgstr "조직 API 키" msgid "Other model…" msgstr "다른 모델 직접 입력…" +#: src/pages/ModelSettingsOverlay.tsx +msgid "Other providers" +msgstr "" + #: src/components/browser/InAppBrowser.tsx msgid "Page" msgstr "" @@ -4737,6 +4771,10 @@ msgstr "" msgid "Species" msgstr "" +#: src/lib/dictation.ts +msgid "Speech recognition is blocked. Allow the microphone and turn on dictation in your system settings." +msgstr "" + #: src/lib/model-tier-label.ts msgid "Standard" msgstr "" @@ -4965,6 +5003,14 @@ msgstr "생각 깊이" msgid "This bot runs on this computer, not a Linux desktop. Shell and files use your home folder." msgstr "이 Bot은 별도 Linux 화면이 아니라 현재 컴퓨터에서 실행됩니다. 터미널과 파일 작업은 홈 폴더를 사용합니다." +#: src/lib/dictation.ts +msgid "This browser can't detect when you stop talking. Use Chrome, the desktop app, or hold-to-talk in the composer." +msgstr "" + +#: src/lib/dictation.ts +msgid "This browser can't recognize speech itself. Connect a voice provider with transcription, or use Safari, Chrome or the desktop app." +msgstr "" + #: src/pages/shell/dialogs.tsx msgid "This cannot be undone." msgstr "이 작업은 되돌릴 수 없습니다." @@ -5099,6 +5145,10 @@ msgstr "" msgid "Transcription" msgstr "" +#: src/lib/dictation.ts +msgid "Transcription request timed out." +msgstr "" + #: src/pages/ExternalConversationSettings.tsx msgid "Treat like a person" msgstr "" @@ -5298,6 +5348,10 @@ msgstr "음성" msgid "Voice \"{0}\" not found." msgstr "" +#: src/lib/dictation.ts +msgid "Voice input needs a secure connection. Open this address with https://." +msgstr "" + #: src/components/onboarding/OnboardingVoiceStep.tsx msgid "Voice is not available yet. You can turn it on later in settings." msgstr "" diff --git a/apps/web/src/locales/pt-BR/messages.po b/apps/web/src/locales/pt-BR/messages.po index 2a0f257..87e92c4 100644 --- a/apps/web/src/locales/pt-BR/messages.po +++ b/apps/web/src/locales/pt-BR/messages.po @@ -1352,6 +1352,7 @@ msgstr "Confirmar senha" msgid "Connect" msgstr "Conectar" +#: src/pages/ModelSettingsOverlay.tsx #: src/pages/shell/composer.tsx msgid "Connect a model so your Oo can reply." msgstr "" @@ -1778,6 +1779,10 @@ msgstr "Não foi possível enviar esta resposta" msgid "Could not take control" msgstr "Não foi possível assumir o controle" +#: src/lib/dictation.ts +msgid "Could not transcribe that recording." +msgstr "" + #: src/pages/ScratchpadSection.tsx msgid "Could not update" msgstr "Não foi possível atualizar" @@ -2089,6 +2094,18 @@ msgstr "" msgid "Developer tools" msgstr "" +#: src/lib/dictation.ts +msgid "Dictation ended unexpectedly." +msgstr "" + +#: src/lib/dictation.ts +msgid "Dictation failed: {code}" +msgstr "" + +#: src/lib/dictation.ts +msgid "Dictation failed." +msgstr "" + #: src/components/integrations/IntegrationSetup.tsx msgid "Direct MCP" msgstr "" @@ -3206,6 +3223,15 @@ msgstr "Enviou mensagem para {peer}" msgid "Messaging" msgstr "" +#: src/lib/dictation.ts +msgid "Microphone access denied. Allow the microphone in your browser or system settings, then try again." +msgstr "" + +#: src/lib/dictation.ts +msgid "Microphone access is not available in this browser. Use Chrome, the desktop app, or check system settings." +msgstr "" + +#: src/lib/dictation.ts #: src/pages/CallView.tsx msgid "Microphone failed" msgstr "Falha no microfone" @@ -3518,6 +3544,10 @@ msgstr "Ainda não há servidores MCP." msgid "No messages with {peerBotName} yet." msgstr "Ainda sem mensagens com {peerBotName}." +#: src/lib/dictation.ts +msgid "No microphone found. Connect a microphone and try again." +msgstr "" + #: src/pages/ModelSettingsOverlay.tsx msgid "No model catalog is available." msgstr "Nenhum catálogo de modelos está disponível." @@ -3751,6 +3781,10 @@ msgstr "Chave da API da organização" msgid "Other model…" msgstr "Outro modelo..." +#: src/pages/ModelSettingsOverlay.tsx +msgid "Other providers" +msgstr "" + #: src/components/browser/InAppBrowser.tsx msgid "Page" msgstr "" @@ -4737,6 +4771,10 @@ msgstr "" msgid "Species" msgstr "" +#: src/lib/dictation.ts +msgid "Speech recognition is blocked. Allow the microphone and turn on dictation in your system settings." +msgstr "" + #: src/lib/model-tier-label.ts msgid "Standard" msgstr "" @@ -4965,6 +5003,14 @@ msgstr "Pensando" msgid "This bot runs on this computer, not a Linux desktop. Shell and files use your home folder." msgstr "Este bot é executado neste computador, não em um desktop Linux. Shell e arquivos usam sua pasta inicial." +#: src/lib/dictation.ts +msgid "This browser can't detect when you stop talking. Use Chrome, the desktop app, or hold-to-talk in the composer." +msgstr "" + +#: src/lib/dictation.ts +msgid "This browser can't recognize speech itself. Connect a voice provider with transcription, or use Safari, Chrome or the desktop app." +msgstr "" + #: src/pages/shell/dialogs.tsx msgid "This cannot be undone." msgstr "Essa ação não pode ser desfeita." @@ -5099,6 +5145,10 @@ msgstr "" msgid "Transcription" msgstr "" +#: src/lib/dictation.ts +msgid "Transcription request timed out." +msgstr "" + #: src/pages/ExternalConversationSettings.tsx msgid "Treat like a person" msgstr "" @@ -5298,6 +5348,10 @@ msgstr "Voz" msgid "Voice \"{0}\" not found." msgstr "" +#: src/lib/dictation.ts +msgid "Voice input needs a secure connection. Open this address with https://." +msgstr "" + #: src/components/onboarding/OnboardingVoiceStep.tsx msgid "Voice is not available yet. You can turn it on later in settings." msgstr "" diff --git a/apps/web/src/locales/ru/messages.po b/apps/web/src/locales/ru/messages.po index 998be77..8495ec6 100644 --- a/apps/web/src/locales/ru/messages.po +++ b/apps/web/src/locales/ru/messages.po @@ -1352,6 +1352,7 @@ msgstr "Подтвердите пароль" msgid "Connect" msgstr "Подключить" +#: src/pages/ModelSettingsOverlay.tsx #: src/pages/shell/composer.tsx msgid "Connect a model so your Oo can reply." msgstr "" @@ -1778,6 +1779,10 @@ msgstr "Не удалось отправить этот ответ" msgid "Could not take control" msgstr "Не смог взять под контроль" +#: src/lib/dictation.ts +msgid "Could not transcribe that recording." +msgstr "" + #: src/pages/ScratchpadSection.tsx msgid "Could not update" msgstr "Не удалось обновить" @@ -2089,6 +2094,18 @@ msgstr "Обновление настольного приложения" msgid "Developer tools" msgstr "" +#: src/lib/dictation.ts +msgid "Dictation ended unexpectedly." +msgstr "" + +#: src/lib/dictation.ts +msgid "Dictation failed: {code}" +msgstr "" + +#: src/lib/dictation.ts +msgid "Dictation failed." +msgstr "" + #: src/components/integrations/IntegrationSetup.tsx msgid "Direct MCP" msgstr "Прямой MCP" @@ -3206,6 +3223,15 @@ msgstr "Отправлено сообщение {peer}" msgid "Messaging" msgstr "Обмен сообщениями" +#: src/lib/dictation.ts +msgid "Microphone access denied. Allow the microphone in your browser or system settings, then try again." +msgstr "" + +#: src/lib/dictation.ts +msgid "Microphone access is not available in this browser. Use Chrome, the desktop app, or check system settings." +msgstr "" + +#: src/lib/dictation.ts #: src/pages/CallView.tsx msgid "Microphone failed" msgstr "Ошибка микрофона" @@ -3518,6 +3544,10 @@ msgstr "Серверов MCP пока нет." msgid "No messages with {peerBotName} yet." msgstr "Сообщений с {peerBotName} пока нет." +#: src/lib/dictation.ts +msgid "No microphone found. Connect a microphone and try again." +msgstr "" + #: src/pages/ModelSettingsOverlay.tsx msgid "No model catalog is available." msgstr "Каталога моделей нет." @@ -3751,6 +3781,10 @@ msgstr "Ключ API организации" msgid "Other model…" msgstr "Другая модель…" +#: src/pages/ModelSettingsOverlay.tsx +msgid "Other providers" +msgstr "" + #: src/components/browser/InAppBrowser.tsx msgid "Page" msgstr "" @@ -4737,6 +4771,10 @@ msgstr "" msgid "Species" msgstr "" +#: src/lib/dictation.ts +msgid "Speech recognition is blocked. Allow the microphone and turn on dictation in your system settings." +msgstr "" + #: src/lib/model-tier-label.ts msgid "Standard" msgstr "" @@ -4965,6 +5003,14 @@ msgstr "рассуждение" msgid "This bot runs on this computer, not a Linux desktop. Shell and files use your home folder." msgstr "Этот бот работает на этом компьютере, а не на рабочем столе Linux. Оболочка и файлы используют вашу домашнюю папку." +#: src/lib/dictation.ts +msgid "This browser can't detect when you stop talking. Use Chrome, the desktop app, or hold-to-talk in the composer." +msgstr "" + +#: src/lib/dictation.ts +msgid "This browser can't recognize speech itself. Connect a voice provider with transcription, or use Safari, Chrome or the desktop app." +msgstr "" + #: src/pages/shell/dialogs.tsx msgid "This cannot be undone." msgstr "Это невозможно отменить." @@ -5099,6 +5145,10 @@ msgstr "" msgid "Transcription" msgstr "" +#: src/lib/dictation.ts +msgid "Transcription request timed out." +msgstr "" + #: src/pages/ExternalConversationSettings.tsx msgid "Treat like a person" msgstr "Считать человеком" @@ -5298,6 +5348,10 @@ msgstr "Голос" msgid "Voice \"{0}\" not found." msgstr "" +#: src/lib/dictation.ts +msgid "Voice input needs a secure connection. Open this address with https://." +msgstr "" + #: src/components/onboarding/OnboardingVoiceStep.tsx msgid "Voice is not available yet. You can turn it on later in settings." msgstr "" diff --git a/apps/web/src/locales/tr/messages.po b/apps/web/src/locales/tr/messages.po index 76f5229..b12147a 100644 --- a/apps/web/src/locales/tr/messages.po +++ b/apps/web/src/locales/tr/messages.po @@ -1352,6 +1352,7 @@ msgstr "Parolayı doğrulayın" msgid "Connect" msgstr "Bağlan" +#: src/pages/ModelSettingsOverlay.tsx #: src/pages/shell/composer.tsx msgid "Connect a model so your Oo can reply." msgstr "" @@ -1778,6 +1779,10 @@ msgstr "Bu yanıt gönderilemedi" msgid "Could not take control" msgstr "Kontrol alınamadı" +#: src/lib/dictation.ts +msgid "Could not transcribe that recording." +msgstr "" + #: src/pages/ScratchpadSection.tsx msgid "Could not update" msgstr "Güncellenemedi" @@ -2089,6 +2094,18 @@ msgstr "" msgid "Developer tools" msgstr "" +#: src/lib/dictation.ts +msgid "Dictation ended unexpectedly." +msgstr "" + +#: src/lib/dictation.ts +msgid "Dictation failed: {code}" +msgstr "" + +#: src/lib/dictation.ts +msgid "Dictation failed." +msgstr "" + #: src/components/integrations/IntegrationSetup.tsx msgid "Direct MCP" msgstr "" @@ -3206,6 +3223,15 @@ msgstr "{peer} botuna mesaj gönderildi" msgid "Messaging" msgstr "" +#: src/lib/dictation.ts +msgid "Microphone access denied. Allow the microphone in your browser or system settings, then try again." +msgstr "" + +#: src/lib/dictation.ts +msgid "Microphone access is not available in this browser. Use Chrome, the desktop app, or check system settings." +msgstr "" + +#: src/lib/dictation.ts #: src/pages/CallView.tsx msgid "Microphone failed" msgstr "Mikrofon hatası" @@ -3518,6 +3544,10 @@ msgstr "Henüz MCP sunucusu yok." msgid "No messages with {peerBotName} yet." msgstr "{peerBotName} ile henüz mesaj yok." +#: src/lib/dictation.ts +msgid "No microphone found. Connect a microphone and try again." +msgstr "" + #: src/pages/ModelSettingsOverlay.tsx msgid "No model catalog is available." msgstr "Kullanılabilir model kataloğu yok." @@ -3751,6 +3781,10 @@ msgstr "Kuruluş API anahtarı" msgid "Other model…" msgstr "Başka model…" +#: src/pages/ModelSettingsOverlay.tsx +msgid "Other providers" +msgstr "" + #: src/components/browser/InAppBrowser.tsx msgid "Page" msgstr "" @@ -4737,6 +4771,10 @@ msgstr "" msgid "Species" msgstr "" +#: src/lib/dictation.ts +msgid "Speech recognition is blocked. Allow the microphone and turn on dictation in your system settings." +msgstr "" + #: src/lib/model-tier-label.ts msgid "Standard" msgstr "" @@ -4965,6 +5003,14 @@ msgstr "Düşünüyor" msgid "This bot runs on this computer, not a Linux desktop. Shell and files use your home folder." msgstr "Bu bot bir Linux masaüstünde değil, bu bilgisayarda çalışır. Kabuk ve dosyalar senin kullanıcı klasörünü kullanır." +#: src/lib/dictation.ts +msgid "This browser can't detect when you stop talking. Use Chrome, the desktop app, or hold-to-talk in the composer." +msgstr "" + +#: src/lib/dictation.ts +msgid "This browser can't recognize speech itself. Connect a voice provider with transcription, or use Safari, Chrome or the desktop app." +msgstr "" + #: src/pages/shell/dialogs.tsx msgid "This cannot be undone." msgstr "Bu işlem geri alınamaz." @@ -5099,6 +5145,10 @@ msgstr "" msgid "Transcription" msgstr "" +#: src/lib/dictation.ts +msgid "Transcription request timed out." +msgstr "" + #: src/pages/ExternalConversationSettings.tsx msgid "Treat like a person" msgstr "" @@ -5298,6 +5348,10 @@ msgstr "Ses" msgid "Voice \"{0}\" not found." msgstr "" +#: src/lib/dictation.ts +msgid "Voice input needs a secure connection. Open this address with https://." +msgstr "" + #: src/components/onboarding/OnboardingVoiceStep.tsx msgid "Voice is not available yet. You can turn it on later in settings." msgstr "" diff --git a/apps/web/src/locales/zh-CN/messages.po b/apps/web/src/locales/zh-CN/messages.po index a9899f1..4fd390f 100644 --- a/apps/web/src/locales/zh-CN/messages.po +++ b/apps/web/src/locales/zh-CN/messages.po @@ -1352,6 +1352,7 @@ msgstr "确认密码" msgid "Connect" msgstr "连接" +#: src/pages/ModelSettingsOverlay.tsx #: src/pages/shell/composer.tsx msgid "Connect a model so your Oo can reply." msgstr "" @@ -1778,6 +1779,10 @@ msgstr "无法提交此回答" msgid "Could not take control" msgstr "无法接管" +#: src/lib/dictation.ts +msgid "Could not transcribe that recording." +msgstr "" + #: src/pages/ScratchpadSection.tsx msgid "Could not update" msgstr "无法更新" @@ -2089,6 +2094,18 @@ msgstr "" msgid "Developer tools" msgstr "" +#: src/lib/dictation.ts +msgid "Dictation ended unexpectedly." +msgstr "" + +#: src/lib/dictation.ts +msgid "Dictation failed: {code}" +msgstr "" + +#: src/lib/dictation.ts +msgid "Dictation failed." +msgstr "" + #: src/components/integrations/IntegrationSetup.tsx msgid "Direct MCP" msgstr "" @@ -3206,6 +3223,15 @@ msgstr "已发给 {peer}" msgid "Messaging" msgstr "消息" +#: src/lib/dictation.ts +msgid "Microphone access denied. Allow the microphone in your browser or system settings, then try again." +msgstr "" + +#: src/lib/dictation.ts +msgid "Microphone access is not available in this browser. Use Chrome, the desktop app, or check system settings." +msgstr "" + +#: src/lib/dictation.ts #: src/pages/CallView.tsx msgid "Microphone failed" msgstr "麦克风失败" @@ -3518,6 +3544,10 @@ msgstr "还没有 MCP 服务器。" msgid "No messages with {peerBotName} yet." msgstr "还没有与 {peerBotName} 的消息。" +#: src/lib/dictation.ts +msgid "No microphone found. Connect a microphone and try again." +msgstr "" + #: src/pages/ModelSettingsOverlay.tsx msgid "No model catalog is available." msgstr "没有可用的模型目录。" @@ -3751,6 +3781,10 @@ msgstr "组织 API 密钥" msgid "Other model…" msgstr "其他模型…" +#: src/pages/ModelSettingsOverlay.tsx +msgid "Other providers" +msgstr "" + #: src/components/browser/InAppBrowser.tsx msgid "Page" msgstr "" @@ -4737,6 +4771,10 @@ msgstr "" msgid "Species" msgstr "" +#: src/lib/dictation.ts +msgid "Speech recognition is blocked. Allow the microphone and turn on dictation in your system settings." +msgstr "" + #: src/lib/model-tier-label.ts msgid "Standard" msgstr "" @@ -4965,6 +5003,14 @@ msgstr "思考" msgid "This bot runs on this computer, not a Linux desktop. Shell and files use your home folder." msgstr "此 Bot 运行在这台电脑上,而不是 Linux 桌面。Shell 和文件使用你的主目录。" +#: src/lib/dictation.ts +msgid "This browser can't detect when you stop talking. Use Chrome, the desktop app, or hold-to-talk in the composer." +msgstr "" + +#: src/lib/dictation.ts +msgid "This browser can't recognize speech itself. Connect a voice provider with transcription, or use Safari, Chrome or the desktop app." +msgstr "" + #: src/pages/shell/dialogs.tsx msgid "This cannot be undone." msgstr "此操作无法撤销。" @@ -5099,6 +5145,10 @@ msgstr "" msgid "Transcription" msgstr "" +#: src/lib/dictation.ts +msgid "Transcription request timed out." +msgstr "" + #: src/pages/ExternalConversationSettings.tsx msgid "Treat like a person" msgstr "" @@ -5298,6 +5348,10 @@ msgstr "语音" msgid "Voice \"{0}\" not found." msgstr "" +#: src/lib/dictation.ts +msgid "Voice input needs a secure connection. Open this address with https://." +msgstr "" + #: src/components/onboarding/OnboardingVoiceStep.tsx msgid "Voice is not available yet. You can turn it on later in settings." msgstr "" diff --git a/apps/web/src/pages/ModelSettingsOverlay.tsx b/apps/web/src/pages/ModelSettingsOverlay.tsx index 6ea570d..07f641e 100644 --- a/apps/web/src/pages/ModelSettingsOverlay.tsx +++ b/apps/web/src/pages/ModelSettingsOverlay.tsx @@ -74,6 +74,7 @@ export function ModelSettingsOverlay({ const [pending, setPending] = useState<"connect" | "default" | null>(null); const [error, setError] = useState(null); const [notice, setNotice] = useState(null); + const [showProviders, setShowProviders] = useState(false); const detailScrollRef = useRef(null); const refreshRevisionRef = useRef(0); const selectionRevisionRef = useRef(0); @@ -192,6 +193,9 @@ export function ModelSettingsOverlay({ const subscriptionSignIn = selected?.signIn !== undefined; const busy = pending !== null || oauthPending; const effectiveBaseUrl = baseUrl.trim(); + // Until something is connected, only the preselected recommendation's sign-in is shown. + const setupOnly = + !showProviders && Boolean(me?.needsModel) && credentials.length === 0 && !isOpenAiCompatible; const openAiCompatibleReady = openAiCompatibleConnectReady({ baseUrl: effectiveBaseUrl, modelId, @@ -356,6 +360,8 @@ export function ModelSettingsOverlay({ const description = loading ? ( Loading model catalog… + ) : setupOnly ? ( + Connect a model so your Oo can reply. ) : localOwner ? ( Models for the server owner’s default space. ) : ( @@ -399,10 +405,10 @@ export function ModelSettingsOverlay({ )} - + {setupOnly ? null : }
-
+
Providers
@@ -603,7 +609,7 @@ export function ModelSettingsOverlay({ maxImagesLabel={t`Maximum images per request`} /> - ) : ( + ) : setupOnly ? null : ( <> Model @@ -622,13 +628,13 @@ export function ModelSettingsOverlay({ )}
- {!isOpenAiCompatible && selected.billing ? ( + {!isOpenAiCompatible && !setupOnly && selected.billing ? (

{selected.billing}

) : null} - {!isOpenAiCompatible ? ( + {!isOpenAiCompatible && !setupOnly ? (
Personal credential @@ -651,7 +657,7 @@ export function ModelSettingsOverlay({ ) : null} {subscriptionSignIn ? ( -
+
{oauth ? (
{oauth.mode === "auth-url" ? ( @@ -800,6 +806,17 @@ export function ModelSettingsOverlay({
) : null} + {setupOnly ? ( + + ) : null} + {selected.auth === "oauth" && !subscriptionSignIn ? (

diff --git a/apps/web/src/pages/SettingsOverlay.tsx b/apps/web/src/pages/SettingsOverlay.tsx index 5d6c601..d757994 100644 --- a/apps/web/src/pages/SettingsOverlay.tsx +++ b/apps/web/src/pages/SettingsOverlay.tsx @@ -56,6 +56,7 @@ export function SettingsOverlay({ usage, initialSection = "general", isDeploymentOwner = false, + modelsManaged = false, sandboxProvider, messagingEnabled = false, onOpenMessaging, @@ -71,6 +72,7 @@ export function SettingsOverlay({ usage?: { runs: number; inputTokens: number; outputTokens: number } | null; initialSection?: SettingsSection; isDeploymentOwner?: boolean; + modelsManaged?: boolean; sandboxProvider?: string | null; messagingEnabled?: boolean; onOpenMessaging?: () => void; @@ -103,7 +105,7 @@ export function SettingsOverlay({ const navItems: NavItem[] = [ { id: "general", label: t`General`, icon: Settings }, { id: "integrations", label: t`Integrations`, icon: LayoutGrid }, - { id: "models", label: t`Models`, icon: Cpu }, + ...(modelsManaged ? [] : [{ id: "models" as const, label: t`Models`, icon: Cpu }]), { id: "memory", label: t`Memory`, icon: Brain }, { id: "voice", label: t`Voice`, icon: Volume2 }, ...(isDeploymentOwner ? [{ id: "media" as const, label: t`Media`, icon: Clapperboard }] : []), @@ -252,7 +254,7 @@ export function SettingsOverlay({ {section === "updates" ? ( ) : null} - {section === "models" ? ( + {section === "models" && !modelsManaged ? ( ) : null} {section === "memory" ? ( diff --git a/apps/web/src/pages/Shell.tsx b/apps/web/src/pages/Shell.tsx index 3f5cbcc..e9afcb3 100644 --- a/apps/web/src/pages/Shell.tsx +++ b/apps/web/src/pages/Shell.tsx @@ -4134,6 +4134,7 @@ export function ShellPage() { usage={usage} initialSection={settingsSection} isDeploymentOwner={bootstrapMe?.isDeploymentOwner === true} + modelsManaged={bootstrapMe?.modelsManaged === true} sandboxProvider={bootstrapMe?.sandboxProvider} messagingEnabled={messagingSurfaceEnabled} onOpenMessaging={() => { diff --git a/apps/web/src/pages/shell/bot-panel.tsx b/apps/web/src/pages/shell/bot-panel.tsx index 6532f1d..786b1f7 100644 --- a/apps/web/src/pages/shell/bot-panel.tsx +++ b/apps/web/src/pages/shell/bot-panel.tsx @@ -561,66 +561,70 @@ export function BotSettings({ ) : null} - + {thinkingOptions.length ? ( + ) : null} - {modelKey && - !selectedTier && - !connectedOptions.some((option) => option.key === modelKey) ? ( - - {parseModelOptionKey(modelKey)?.modelId ?? modelKey} - - ) : null} - {connectedOptions.map((option) => ( - - {option.label} - - ))} - - - {thinkingOptions.length ? ( - - ) : null} + + )} {memoryProviderConfigured ? (

Memory scope diff --git a/docs/self-host.md b/docs/self-host.md index a0e44d0..0b145c8 100644 --- a/docs/self-host.md +++ b/docs/self-host.md @@ -233,6 +233,19 @@ way by default. Only configure an endpoint you control: prompts, attachments, and tool results sent to that model leave evOo through this URL. Leave `RAKAZO_LOCAL_MODELS` blank to disable the provider. +To run every space on these models without anyone connecting one, make them the deployment's +model, and optionally assign them to the tiers the agent delegates to. An entry can name its own +endpoint when the models run behind different servers: + +```env +PI_DEFAULT_PROVIDER=local +PI_DEFAULT_MODEL=qwen3-vl # the main agent's model; defaults to the first id +RAKAZO_LOCAL_MODELS=qwen3-vl,llama3.1:8b,qwen3:4b=http://127.0.0.1:11435/v1 +RAKAZO_LOCAL_TIERS=fast=qwen3:4b,standard=llama3.1:8b,code=qwen3-vl +``` + +A tier a space maps in **Settings → Models** still wins, and a tier left out keeps the run's model. + Each user can also connect their own OpenAI-compatible endpoint from **Connect a model** / **Settings → Models** on web and mobile. Choose **OpenAI-compatible**, enter the server base URL (for example `http://127.0.0.1:8000/v1`), the exact model id, and an optional API key. diff --git a/packages/adapters/package.json b/packages/adapters/package.json index 701d625..0011fd2 100644 --- a/packages/adapters/package.json +++ b/packages/adapters/package.json @@ -19,8 +19,8 @@ "@composio/core": "0.18.1", "@daytona/sdk": "0.211.2", "@e2b/desktop": "2.4.0", - "@earendil-works/pi-agent-core": "0.85.1", - "@earendil-works/pi-ai": "0.85.1", + "@earendil-works/pi-agent-core": "0.87.1", + "@earendil-works/pi-ai": "0.87.1", "@modelcontextprotocol/sdk": "1.30.0", "@mozilla/readability": "0.6.0", "@observablehq/plot": "0.6.17", diff --git a/packages/adapters/src/auto-review.ts b/packages/adapters/src/auto-review.ts index f90b830..248f2d3 100644 --- a/packages/adapters/src/auto-review.ts +++ b/packages/adapters/src/auto-review.ts @@ -3,7 +3,7 @@ import type { ActionApprovalRule } from "@rakazo/core"; import { type AutoReviewJudgeDecision, redactSecrets } from "@rakazo/core"; import { formatCurrentTimeInstruction } from "./current-time.js"; import { resolveDeploymentModel } from "./deployment-model.js"; -import { LOCAL_PROVIDER_ID } from "./pi-local-provider.js"; +import { LOCAL_PROVIDER_ID, localModelIds } from "./pi-local-provider.js"; const DEFAULT_TIMEOUT_MS = 1_500; const MAX_TASK_CHARS = 400; @@ -29,13 +29,6 @@ function envFlag(env: NodeJS.ProcessEnv, name: string): boolean { return value === "1" || value === "true" || value === "yes" || value === "on"; } -function localModelIds(env: NodeJS.ProcessEnv): string[] { - return (env.RAKAZO_LOCAL_MODELS ?? "") - .split(",") - .map((id) => id.trim()) - .filter((id) => id.length > 0); -} - /** Deployment default for the user toggle when no preference row exists. */ export function deploymentAutoReviewDefault(env: NodeJS.ProcessEnv = process.env): boolean { return envFlag(env, "RAKAZO_AUTO_REVIEW"); diff --git a/packages/adapters/src/deployment-model.test.ts b/packages/adapters/src/deployment-model.test.ts index 319f7e8..a98ac3a 100644 --- a/packages/adapters/src/deployment-model.test.ts +++ b/packages/adapters/src/deployment-model.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; import { resolveDeploymentModel } from "./deployment-model.js"; +import { LOCAL_API_KEY } from "./pi-local-provider.js"; describe("resolveDeploymentModel", () => { it("pairs the deployment model key with the provider it belongs to", () => { @@ -12,12 +13,24 @@ describe("resolveDeploymentModel", () => { // The whole point: switching the provider switches the key with it. expect(resolveDeploymentModel({ ...both, PI_DEFAULT_PROVIDER: "anthropic" })).toEqual({ provider: "anthropic", - model: "claude-sonnet-5", + model: "claude-opus-5-5", key: "sk-ant-key", }); // A provider with no key configured yields no key — never another vendor's. expect( resolveDeploymentModel({ OPENROUTER_API_KEY: "or-key", PI_DEFAULT_PROVIDER: "anthropic" }), - ).toEqual({ provider: "anthropic", model: "claude-sonnet-5", key: undefined }); + ).toEqual({ provider: "anthropic", model: "claude-opus-5-5", key: undefined }); + }); + + it("treats configured local models as the deployment's keyless model", () => { + const local = { PI_DEFAULT_PROVIDER: "local", RAKAZO_LOCAL_MODELS: "strong,weak" }; + expect(resolveDeploymentModel(local)).toEqual({ + provider: "local", + model: "strong", + key: LOCAL_API_KEY, + }); + expect(resolveDeploymentModel({ ...local, PI_DEFAULT_MODEL: "weak" }).model).toBe("weak"); + // Without local models there is nothing to run, so no key either. + expect(resolveDeploymentModel({ PI_DEFAULT_PROVIDER: "local" }).key).toBeUndefined(); }); }); diff --git a/packages/adapters/src/deployment-model.ts b/packages/adapters/src/deployment-model.ts index fe4ea8f..a6e9dec 100644 --- a/packages/adapters/src/deployment-model.ts +++ b/packages/adapters/src/deployment-model.ts @@ -1,7 +1,9 @@ +import { LOCAL_API_KEY, localModelIds } from "./pi-local-provider.js"; + export const DEFAULT_OPENROUTER_MODEL_ID = "openai/gpt-5.6-luna"; /** What the connect flow preselects while no model is connected: the user only adds a key or signs in. */ -export const RECOMMENDED_MODEL = { provider: "anthropic", model: "claude-opus-5" } as const; +export const RECOMMENDED_MODEL = { provider: "anthropic", model: "claude-opus-5-5" } as const; /** * The deployment-wide model default: which provider a run falls back to when no user @@ -14,13 +16,17 @@ export function resolveDeploymentModel(env: NodeJS.ProcessEnv = process.env) { // A row per provider that ships a deployment key. A third one adds a row here, not a // branch at each call site — and an unknown provider gets no key rather than another // vendor's, which a ternary on one provider would not give. + const localIds = localModelIds(env); const keys: Record = { openrouter: env.OPENROUTER_API_KEY, anthropic: env.ANTHROPIC_API_KEY, + // Keyless: configured local models are the deployment's model on their own. + local: localIds.length ? LOCAL_API_KEY : undefined, }; const models: Record = { openrouter: DEFAULT_OPENROUTER_MODEL_ID, - anthropic: "claude-sonnet-5", + anthropic: RECOMMENDED_MODEL.model, + ...(localIds[0] ? { local: localIds[0] } : {}), }; return { provider, diff --git a/packages/adapters/src/executor.test.ts b/packages/adapters/src/executor.test.ts index c4a885e..72656e9 100644 --- a/packages/adapters/src/executor.test.ts +++ b/packages/adapters/src/executor.test.ts @@ -18,6 +18,7 @@ import { settleSteeringAttachmentLoads, } from "./executor-attachments.js"; import { threadContextForRun } from "./executor-completion.js"; +import { LOCAL_API_KEY } from "./pi-local-provider.js"; import { serializeModelSecret } from "./pi-oauth.js"; describe("tool completion audit", () => { @@ -1706,7 +1707,11 @@ description: Prepare standup notes }); // One router connection serves several local models; tiers pick among them. - function tierPrisma(bot: Record | null, tierRow: unknown) { + function tierPrisma( + bot: Record | null, + tierRow: unknown, + deploymentModelKey?: string, + ) { const plaintext = serializeModelSecret({ kind: "openai_compatible", baseUrl: "http://127.0.0.1:8080/v1", @@ -1740,6 +1745,7 @@ description: Prepare standup notes const executor = createRunExecutor({ prisma, secretStore: { load: vi.fn(() => plaintext), put: vi.fn() }, + deploymentModelKey, } as unknown as Parameters[0]); return { executor, findUnique }; } @@ -1847,4 +1853,27 @@ description: Prepare standup notes unmapped.executor.resolveTierModel({ userId: "user-1", spaceId: "ws-1" }, "fast"), ).resolves.toBeNull(); }); + + it("falls back to the tiers a local-model deployment assigns", async () => { + vi.stubEnv("PI_DEFAULT_PROVIDER", "local"); + vi.stubEnv("RAKAZO_LOCAL_MODELS", "strong,weak=http://127.0.0.1:1239/v1"); + vi.stubEnv("RAKAZO_LOCAL_TIERS", "fast=weak,code=strong"); + try { + const { executor } = tierPrisma(null, null, LOCAL_API_KEY); + await expect( + executor.resolveTierModel({ userId: "user-1", spaceId: "ws-1" }, "fast"), + ).resolves.toMatchObject({ provider: "local", id: "weak", apiKey: LOCAL_API_KEY }); + // A tier the deployment leaves out keeps the run's own model. + await expect( + executor.resolveTierModel({ userId: "user-1", spaceId: "ws-1" }, "standard"), + ).resolves.toBeNull(); + // A space's own mapping still wins over the deployment's. + const mapped = tierPrisma(null, routerTier, LOCAL_API_KEY); + await expect( + mapped.executor.resolveTierModel({ userId: "user-1", spaceId: "ws-1" }, "code"), + ).resolves.toMatchObject({ provider: "openai-compatible", id: "router-code" }); + } finally { + vi.unstubAllEnvs(); + } + }); }); diff --git a/packages/adapters/src/executor.ts b/packages/adapters/src/executor.ts index f1faf42..43bc0da 100644 --- a/packages/adapters/src/executor.ts +++ b/packages/adapters/src/executor.ts @@ -332,6 +332,7 @@ import { } from "./model-vision.js"; import { notifyUserFromTool } from "./notify-user-tool.js"; import { toOAuthCredential } from "./pi-credentials.js"; +import { LOCAL_PROVIDER_ID, localTierModels } from "./pi-local-provider.js"; import { parseModelSecret, resolveModelAuth, @@ -782,7 +783,12 @@ export function createRunExecutor(deps: ExecutorDeps) { registerSecrets?: (values: string[]) => void, ): Promise => { const mapped = await findSpaceModelTier(deps.prisma, scope, tier); - if (!mapped) return null; + if (!mapped) { + // A deployment serving local models may assign them to tiers for every space. + const deploymentTier = deps.deploymentModelKey ? localTierModels()[tier] : undefined; + if (!deploymentTier || resolveDeploymentModel().provider !== LOCAL_PROVIDER_ID) return null; + return connectedModel(scope, null, LOCAL_PROVIDER_ID, deploymentTier, registerSecrets); + } return connectedModel( scope, mapped.credential, diff --git a/packages/adapters/src/pi-local-provider.test.ts b/packages/adapters/src/pi-local-provider.test.ts index f74fd67..645d19f 100644 --- a/packages/adapters/src/pi-local-provider.test.ts +++ b/packages/adapters/src/pi-local-provider.test.ts @@ -4,6 +4,7 @@ import { LOCAL_PROVIDER_ID, localBaseUrl, localProvider, + localTierModels, registerLocalProvider, } from "./pi-local-provider.js"; @@ -12,6 +13,7 @@ const ENV_KEYS = [ "RAKAZO_LOCAL_MODELS_URL", "RAKAZO_LOCAL_CONTEXT_WINDOW", "RAKAZO_LOCAL_MAX_TOKENS", + "RAKAZO_LOCAL_TIERS", ] as const; const saved = new Map(); @@ -114,4 +116,21 @@ describe("local model provider", () => { const withLocal = registerLocalProvider(builtinModels()); expect(withLocal.getModel(LOCAL_PROVIDER_ID, "qwen3:4b")).toBeDefined(); }); + + it("serves each model from its own server when the entry names one", () => { + process.env.RAKAZO_LOCAL_MODELS_URL = "http://127.0.0.1:1234/v1"; + setModels("strong, weak=http://127.0.0.1:1239/v1"); + const models = registerLocalProvider(builtinModels()); + expect(models.getModel(LOCAL_PROVIDER_ID, "strong")?.baseUrl).toBe("http://127.0.0.1:1234/v1"); + expect(models.getModel(LOCAL_PROVIDER_ID, "weak")?.baseUrl).toBe("http://127.0.0.1:1239/v1"); + + setModels("weak=not a url"); + expect(() => localProvider()).toThrow(/URL for weak must be an absolute HTTP\(S\) URL/); + }); + + it("maps tiers only to configured local models", () => { + setModels("strong,weak=http://127.0.0.1:1239/v1"); + process.env.RAKAZO_LOCAL_TIERS = "fast=weak, code=strong, standard=missing, huge=strong"; + expect(localTierModels()).toEqual({ fast: "weak", code: "strong" }); + }); }); diff --git a/packages/adapters/src/pi-local-provider.ts b/packages/adapters/src/pi-local-provider.ts index 50aeb47..d50c4a7 100644 --- a/packages/adapters/src/pi-local-provider.ts +++ b/packages/adapters/src/pi-local-provider.ts @@ -6,6 +6,7 @@ import { } from "@earendil-works/pi-ai"; import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy"; +import { type ModelTier, ModelTierSchema } from "@rakazo/contracts"; import { declaredVisionModelIds, inputModalities } from "./model-modalities.js"; /** * Local OpenAI-compatible model server (Ollama, LM Studio, llama.cpp, MLX). @@ -19,6 +20,9 @@ import { declaredVisionModelIds, inputModalities } from "./model-modalities.js"; */ export const LOCAL_PROVIDER_ID = "local"; +/** Placeholder sent as the key: local servers ignore it, and it is distinct enough to redact safely. */ +export const LOCAL_API_KEY = "rakazo-local-model-server"; + /** Model ids the local server serves with vision, declared by the operator. */ export const LOCAL_VISION_MODELS_ENV = "RAKAZO_LOCAL_VISION_MODELS"; @@ -31,15 +35,18 @@ const DEFAULT_CONTEXT_WINDOW = 32_768; const DEFAULT_MAX_TOKENS = 4_096; export function localBaseUrl(): string { - const value = process.env.RAKAZO_LOCAL_MODELS_URL?.trim() || DEFAULT_BASE_URL; + return checkedUrl(process.env.RAKAZO_LOCAL_MODELS_URL?.trim() || DEFAULT_BASE_URL); +} + +function checkedUrl(value: string, name = "RAKAZO_LOCAL_MODELS_URL"): string { let url: URL; try { url = new URL(value); } catch { - throw new Error("RAKAZO_LOCAL_MODELS_URL must be an absolute HTTP(S) URL"); + throw new Error(`${name} must be an absolute HTTP(S) URL`); } if (url.protocol !== "http:" && url.protocol !== "https:") { - throw new Error("RAKAZO_LOCAL_MODELS_URL must be an absolute HTTP(S) URL"); + throw new Error(`${name} must be an absolute HTTP(S) URL`); } return value; } @@ -61,21 +68,53 @@ function tokenLimit(name: string, fallback: number): number { return value; } -/** Comma-separated model ids exactly as the local server names them. */ -function localModelIds(): string[] { - return (process.env.RAKAZO_LOCAL_MODELS ?? "") +/** + * Comma-separated model ids exactly as the local server names them. An entry may name its own + * server as `id=url` when the models run behind different endpoints. + */ +function localModelEntries(env: NodeJS.ProcessEnv): { id: string; baseUrl?: string }[] { + return (env.RAKAZO_LOCAL_MODELS ?? "") .split(",") - .map((id) => id.trim()) - .filter((id) => id.length > 0); + .map((entry) => entry.trim()) + .filter((entry) => entry.length > 0) + .map((entry) => { + const separator = entry.indexOf("="); + if (separator < 0) return { id: entry }; + return { id: entry.slice(0, separator).trim(), baseUrl: entry.slice(separator + 1).trim() }; + }); } -function localModel(id: string): Model<"openai-completions"> { +export function localModelIds(env: NodeJS.ProcessEnv = process.env): string[] { + return localModelEntries(env).map((entry) => entry.id); +} + +/** + * The deployment's tiers served by local models, from `RAKAZO_LOCAL_TIERS` (`tier=id,…`). + * Entries naming an unknown tier or a model outside `RAKAZO_LOCAL_MODELS` are ignored. + */ +export function localTierModels( + env: NodeJS.ProcessEnv = process.env, +): Partial> { + const ids = new Set(localModelIds(env)); + const tiers: Partial> = {}; + for (const entry of (env.RAKAZO_LOCAL_TIERS ?? "").split(",")) { + const [tier, id] = entry.split("=").map((part) => part.trim()); + const parsed = ModelTierSchema.safeParse(tier); + if (parsed.success && id && ids.has(id)) tiers[parsed.data] = id; + } + return tiers; +} + +function localModel(entry: { id: string; baseUrl?: string }): Model<"openai-completions"> { + const { id } = entry; return { id, name: id, api: "openai-completions", provider: LOCAL_PROVIDER_ID, - baseUrl: localBaseUrl(), + baseUrl: entry.baseUrl + ? checkedUrl(entry.baseUrl, `The RAKAZO_LOCAL_MODELS URL for ${id}`) + : localBaseUrl(), reasoning: false, compat: { supportsDeveloperRole: false, supportsReasoningEffort: false }, input: inputModalities(localVisionModelIds().has(id)), @@ -88,8 +127,8 @@ function localModel(id: string): Model<"openai-completions"> { /** The provider, or undefined when no local models are configured. */ export function localProvider(): Provider | undefined { - const ids = localModelIds(); - if (!ids.length) return undefined; + const entries = localModelEntries(process.env); + if (!entries.length) return undefined; return createProvider({ id: LOCAL_PROVIDER_ID, name: "Local (Ollama / LM Studio)", @@ -98,12 +137,12 @@ export function localProvider(): Provider | undefined { apiKey: { name: "Local model server", resolve: async () => ({ - auth: { apiKey: "local", baseUrl: localBaseUrl() }, + auth: { apiKey: LOCAL_API_KEY, baseUrl: localBaseUrl() }, source: "local model server", }), }, }, - models: ids.map(localModel), + models: entries.map(localModel), api: openAICompletionsApi(), }); } diff --git a/packages/adapters/src/pi-models.test.ts b/packages/adapters/src/pi-models.test.ts index 37adc56..f3c27c6 100644 --- a/packages/adapters/src/pi-models.test.ts +++ b/packages/adapters/src/pi-models.test.ts @@ -127,6 +127,11 @@ describe("catalogModelLabel", () => { ["gemini-flash-latest", "Gemini Flash Latest", "Gemini Flash (auto-updates)"], ["foo-latest", "foo-latest", "foo (auto-updates)"], ["foo/latest", "foo/latest", "foo (auto-updates)"], + [ + "accounts/fireworks/routers/qwen-max-latest", + "Qwen Max Latest (Qwen3.8 Max)", + "Qwen Max (auto-updates)", + ], // Pinned: `-preview` is its own model and a dated id is already a snapshot, so promise nothing. ["foo", "Foo Latest", "Foo"], ["claude-opus-4-5-20251101", "Claude Opus 4.5 (latest)", "Claude Opus 4.5"], diff --git a/packages/adapters/src/pi-models.ts b/packages/adapters/src/pi-models.ts index 20b00cc..f07c4b8 100644 --- a/packages/adapters/src/pi-models.ts +++ b/packages/adapters/src/pi-models.ts @@ -98,6 +98,8 @@ function buildPiCatalog(): PiCatalogEntry[] { /** Trailing upstream "latest" marker: "Claude Opus 4.5 (latest)", "Gemini Flash Latest", "foo-latest". */ const LATEST_MARKER = /[\s(/-]*\blatest\b\s*\)?\s*$/i; +/** Router aliases name their current target after the marker: "Qwen Max Latest (Qwen3.8 Max)". */ +const LATEST_ROUTER_MARKER = /\s+\blatest\b\s*\([^)]*\)\s*$/i; /** * Upstream marks auto-updating alias ids with a trailing "latest". That is an alias marker, not a @@ -111,8 +113,9 @@ export function catalogModelLabel( providerModelIds: readonly string[] = [], ): string { const label = name || id; - if (!LATEST_MARKER.test(label)) return label; - const base = label.replace(LATEST_MARKER, "").trim(); + const marker = [LATEST_MARKER, LATEST_ROUTER_MARKER].find((pattern) => pattern.test(label)); + if (!marker) return label; + const base = label.replace(marker, "").trim(); if (!base) return label; return isAliasModelId(id, providerModelIds) ? `${base} (auto-updates)` : base; } diff --git a/packages/contracts/src/domain.ts b/packages/contracts/src/domain.ts index 50253cf..56f9d4b 100644 --- a/packages/contracts/src/domain.ts +++ b/packages/contracts/src/domain.ts @@ -1277,6 +1277,8 @@ export const MeSchema = z.object({ spaceId: Id, isDeploymentOwner: z.boolean(), needsModel: z.boolean(), + /** The deployment picks and routes models itself, so nobody chooses one. */ + modelsManaged: z.boolean(), defaultProvider: z.string().nullable(), defaultModel: z.string().nullable(), computerHost: z.enum(["docker", "this-mac"]).nullable(), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 74db6ac..a1f402a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -559,11 +559,11 @@ importers: specifier: 2.4.0 version: 2.4.0 '@earendil-works/pi-agent-core': - specifier: 0.85.1 - version: 0.85.1(@modelcontextprotocol/sdk@1.30.0(@cfworker/json-schema@4.1.1)(zod@4.6.2))(ws@8.21.3)(zod@4.6.2) + specifier: 0.87.1 + version: 0.87.1(@modelcontextprotocol/sdk@1.30.0(@cfworker/json-schema@4.1.1)(zod@4.6.2))(ws@8.21.3)(zod@4.6.2) '@earendil-works/pi-ai': - specifier: 0.85.1 - version: 0.85.1(@modelcontextprotocol/sdk@1.30.0(@cfworker/json-schema@4.1.1)(zod@4.6.2))(ws@8.21.3)(zod@4.6.2) + specifier: 0.87.1 + version: 0.87.1(@modelcontextprotocol/sdk@1.30.0(@cfworker/json-schema@4.1.1)(zod@4.6.2))(ws@8.21.3)(zod@4.6.2) '@modelcontextprotocol/sdk': specifier: 1.30.0 version: 1.30.0(@cfworker/json-schema@4.1.1)(zod@4.6.2) @@ -931,8 +931,8 @@ importers: packages: - '@anthropic-ai/sdk@0.123.0': - resolution: {integrity: sha512-Y9oX9mPNGZClHQOFqrWRk43Srcu/UHuPq3rfxxOq7JgW0gi+lJA2MAOK4Ul3k/+AUrwRWFJvd0tK3oC0Pw25dw==} + '@anthropic-ai/sdk@0.124.0': + resolution: {integrity: sha512-cN5O8i9UVxHeOQAzj/XjshWXG8KiibJDw9OGpH2Z/eR3n/RBxdoLxDJOcfqAJWvjaMDFfHTBADU04hWRJVkDyA==} hasBin: true peerDependencies: zod: ^3.25.0 || ^4.0.0 @@ -1062,25 +1062,12 @@ packages: '@astrojs/yaml2ts@0.2.4': resolution: {integrity: sha512-8oddpOae35pJsXPQXhTkM0ypfKPskVsh2bCxRtbf7e+/Epw2nReakFYpLKjZMEr75CsoF203PMnCocpfz0s69A==} - '@aws-crypto/sha256-browser@5.2.0': - resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} - - '@aws-crypto/sha256-js@5.2.0': - resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} - engines: {node: '>=16.0.0'} - - '@aws-crypto/supports-web-crypto@5.2.0': - resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} - - '@aws-crypto/util@5.2.0': - resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - '@aws-sdk/checksums@3.1000.29': resolution: {integrity: sha512-Dtu0gr4dnATZAPwEYbpCsG+MpLM7OAliy2gTepEFQwl1vZ6DL3QMH2FveMa3HLvPsOdhJsPRB3KtxVhph9T75A==} engines: {node: '>=20.0.0'} - '@aws-sdk/client-bedrock-runtime@3.1048.0': - resolution: {integrity: sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ==} + '@aws-sdk/client-bedrock-runtime@3.1127.0': + resolution: {integrity: sha512-IDl/lrPb90aH+pZFHGNDmgH9nAUQj5PlZH1sJ3w7RikctyjHSnY3oNjZhrLoaBoQn/rNK0zsP6OHEqEhj2tdLA==} engines: {node: '>=20.0.0'} '@aws-sdk/client-s3@3.1127.0': @@ -1153,20 +1140,16 @@ packages: resolution: {integrity: sha512-L+2xZTye/2T96f3lwCws0Zw6GG2JHZW9e8FpVgGBeeExSKyeoZ6CWRpBml/7DNiK/O26jrgPM9F+Ay8VkgzUWQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/token-providers@3.1048.0': - resolution: {integrity: sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA==} - engines: {node: '>=20.0.0'} - '@aws-sdk/token-providers@3.1116.0': resolution: {integrity: sha512-ygIivKqh8aHzNkucOCXHyIBgBpLPfrSI0mCqXF+vLBsPTUKqj0VSqAY0GFPe7lQl4HntjOcQ+KSyS7oUV2C54Q==} engines: {node: '>=20.0.0'} - '@aws-sdk/types@3.974.5': - resolution: {integrity: sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ==} + '@aws-sdk/token-providers@3.1127.0': + resolution: {integrity: sha512-Dv2TMWBshJ+tF6ahs2Sy5bh4Iabsd4GAQqVvE9XZmYmnoaVbpS2QKIKE/HRacc7bTtbjEEvP+laGzHvHlf1CiQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/util-locate-window@3.965.10': - resolution: {integrity: sha512-ycwH6Zd2GhuSqdXX9ihbCjeGTB6xOJs+O3+Jb8/zDG9978XU80qs75dfkPJRMNKe5MvBZPuNeFpd4JZKPoUF4g==} + '@aws-sdk/types@3.974.5': + resolution: {integrity: sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ==} engines: {node: '>=20.0.0'} '@aws-sdk/xml-builder@3.972.40': @@ -1932,21 +1915,21 @@ packages: resolution: {integrity: sha512-eeY4p/lz7kkDJMidREe7sp2nln16P1M1p64Fb+csn4SssxC2EjKBGqP0NEIlVDVd6iCuPbM7cVlkE/qbwJX4jg==} engines: {node: '>=20.18.1 <21 || >=22'} - '@earendil-works/chord@0.85.1': - resolution: {integrity: sha512-VDlkEC3dhCzQ5fcyH1OhG19dq+6jCn+rqc/iXFivwDYGR5anwo2RCiXij9PpHhqNR5GuhhE+Er69Zi1Sn4eY6w==} + '@earendil-works/chord@0.87.1': + resolution: {integrity: sha512-bg7IkJGFcEaMqqYgOGUiq5Ky9RghpRfrlZ8I/v/1b4bBZ02A7t3E+6uhPRbadwWb/kWsnVFbZsqOKRN4a3LLCg==} engines: {node: '>=22.19.0'} - '@earendil-works/pi-agent-core@0.85.1': - resolution: {integrity: sha512-hIXIP3eAWueAYiAl8aMvWCvvZ8Q5gT3Dip5bE5uJyIGh4+YlWRjtMLI4BaeoXoSs93zndjue61u1B/vhefLnuA==} + '@earendil-works/pi-agent-core@0.87.1': + resolution: {integrity: sha512-Zev3B0HK7YS5A4EZQ2XnEqiJuirx6QBiltJ+LpmjV5a/+2IU0cfKtIfnkNkORK707XOvKBY2WRtk7cAwHpbh2Q==} engines: {node: '>=22.19.0'} - '@earendil-works/pi-ai@0.85.1': - resolution: {integrity: sha512-+VgVIJDkDO2efYJKEEqvPTH4zmnIaXdAppGbO+vKFA9qy5PdhFiAenuFAkU+oiCSfOC4dMHDyrjdQeL4ZoC5CQ==} + '@earendil-works/pi-ai@0.87.1': + resolution: {integrity: sha512-X/3PfQBnnoeVdO9Cv8zHghUMglzlgNZYGNzoPnbRoGnHl3Rw3TlA2UKSUB7BRHUOxMryHXYa8dnjWZlbRheDZA==} engines: {node: '>=22.19.0'} hasBin: true - '@earendil-works/pi-telemetry@0.85.1': - resolution: {integrity: sha512-Bg/YN6kA7Swja/NQxka8xFdecb4E/auIEGF2G5A25EaQXhRnPj300/7/KpgsDDMYUzHTDAv4RyUxaQPJKW81Rw==} + '@earendil-works/pi-telemetry@0.87.1': + resolution: {integrity: sha512-MC6TRQH5lgMXpcN+Vku2WMI2T8BsiUPzMQHGo81uqFZD3/9O79WWJAysEDGuzduP6R4tvtgwMLwmqIxynM10JQ==} engines: {node: '>=22.19.0'} '@egjs/hammerjs@2.0.17': @@ -2047,312 +2030,156 @@ packages: '@epic-web/invariant@1.0.0': resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} - '@esbuild/aix-ppc64@0.28.1': - resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.28.2': resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.28.1': - resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.28.2': resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.28.1': - resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.28.2': resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.28.1': - resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.28.2': resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.28.1': - resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-arm64@0.28.2': resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.28.1': - resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.28.2': resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.28.1': - resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.28.2': resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.28.1': - resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.28.2': resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.28.1': - resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.28.2': resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.28.1': - resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.28.2': resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.28.1': - resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.28.2': resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.28.1': - resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.28.2': resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.28.1': - resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.28.2': resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.28.1': - resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.28.2': resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.28.1': - resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.28.2': resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.28.1': - resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.28.2': resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.28.1': - resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.28.2': resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.28.1': - resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - '@esbuild/netbsd-arm64@0.28.2': resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.28.1': - resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.28.2': resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.28.1': - resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-arm64@0.28.2': resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.28.1': - resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.28.2': resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.28.1': - resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - '@esbuild/openharmony-arm64@0.28.2': resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.28.1': - resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.28.2': resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.28.1': - resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.28.2': resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.28.1': - resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.28.2': resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.28.1': - resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.28.2': resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} engines: {node: '>=18'} @@ -2577,8 +2404,8 @@ packages: '@fontsource-variable/inter@5.3.0': resolution: {integrity: sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA==} - '@google/genai@1.52.0': - resolution: {integrity: sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==} + '@google/genai@2.21.0': + resolution: {integrity: sha512-+PDtco2/Z0ONdzCGekCoCT+O1VJS9xJQNN4XzQpXG/t3El/SWWMkCWlFRO1KmivOHPa4Q0VjUYu1HBKCZ/v33Q==} engines: {node: '>=20.0.0'} peerDependencies: '@modelcontextprotocol/sdk': ^1.25.2 @@ -4000,18 +3827,10 @@ packages: resolution: {integrity: sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==} engines: {node: '>=18.0.0'} - '@smithy/is-array-buffer@2.2.0': - resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} - engines: {node: '>=14.0.0'} - '@smithy/node-http-handler@4.12.1': resolution: {integrity: sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw==} engines: {node: '>=18.0.0'} - '@smithy/node-http-handler@4.7.3': - resolution: {integrity: sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==} - engines: {node: '>=18.0.0'} - '@smithy/signature-v4@5.7.3': resolution: {integrity: sha512-7ImGm+FkHRLcBaRttIAMZ6bzJZWb2cJGoYjq46F2UjycujWzrL9GEN9h4w7eQyXJYnltrUhxbbieBAIRrdqpow==} engines: {node: '>=18.0.0'} @@ -4020,14 +3839,6 @@ packages: resolution: {integrity: sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A==} engines: {node: '>=18.0.0'} - '@smithy/util-buffer-from@2.2.0': - resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} - engines: {node: '>=14.0.0'} - - '@smithy/util-utf8@2.3.0': - resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} - engines: {node: '>=14.0.0'} - '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -4670,6 +4481,10 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + agent-base@9.0.0: + resolution: {integrity: sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==} + engines: {node: '>= 20'} + agent-cli-detector@0.1.7: resolution: {integrity: sha512-d8OWDVdZMgjhLUT9ZPgSv/BdFFF9pVuscC0JdUSz3bjwE15gcp6u/o0/JooM2yyAWC49KThFhXlgTXRa9B7yng==} engines: {node: '>=18.18'} @@ -6016,11 +5831,6 @@ packages: es6-error@4.1.1: resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} - esbuild@0.28.1: - resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} - engines: {node: '>=18'} - hasBin: true - esbuild@0.28.2: resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} engines: {node: '>=18'} @@ -6772,6 +6582,10 @@ packages: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} + http-proxy-agent@9.1.0: + resolution: {integrity: sha512-2NxoveTT58mjYT4n3RPTEfCZGLMbidoO8XEieXfpSYxu+PQJ1qpx4ypwH6N+uF9twBPIvRRgvkvW5HUTYWENig==} + engines: {node: '>= 20'} + http2-wrapper@1.0.3: resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} engines: {node: '>=10.19.0'} @@ -6788,6 +6602,10 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + https-proxy-agent@9.1.0: + resolution: {integrity: sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA==} + engines: {node: '>= 20'} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -6810,8 +6628,8 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + ignore@7.0.8: + resolution: {integrity: sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q==} engines: {node: '>= 4'} import-fresh@3.3.1: @@ -8307,6 +8125,15 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + proxy-agent-negotiate@1.1.0: + resolution: {integrity: sha512-N8IBcM3UgCVzz2L2Lqv8DVntDnnC8/hiV4nEDUPkqq72TPUgYWjQc+bdZlBPZK9LzPAvOY//gAt0S0DApoOXWQ==} + engines: {node: '>= 20'} + peerDependencies: + kerberos: ^2.0.0 + peerDependenciesMeta: + kerberos: + optional: true + proxy-from-env@2.1.0: resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} engines: {node: '>=10'} @@ -9317,8 +9144,8 @@ packages: resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} engines: {node: '>= 18'} - typebox@1.3.7: - resolution: {integrity: sha512-meKuifc33Pccx0O6PdIzYMq3Og8zvP4TIi/a+Bw3AEMZMxOD0+RHGQvpglEe6Zdy3wZ8nqn/j95h8LUZLk/6Hg==} + typebox@1.3.27: + resolution: {integrity: sha512-zu+jc1pcy4UiNThxikUr36f0Rybk9PEeCg/NE6adeWr/SKsdNO4EzZHYRDlv2YCVAfj3Odq3dESSo/jNyoBXzA==} typesafe-path@0.2.2: resolution: {integrity: sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==} @@ -10016,7 +9843,7 @@ packages: snapshots: - '@anthropic-ai/sdk@0.123.0(zod@4.6.2)': + '@anthropic-ai/sdk@0.124.0(zod@4.6.2)': dependencies: json-schema-to-ts: 3.1.1 standardwebhooks: 1.1.1 @@ -10196,32 +10023,6 @@ snapshots: dependencies: yaml: 2.9.0 - '@aws-crypto/sha256-browser@5.2.0': - dependencies: - '@aws-crypto/sha256-js': 5.2.0 - '@aws-crypto/supports-web-crypto': 5.2.0 - '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.974.5 - '@aws-sdk/util-locate-window': 3.965.10 - '@smithy/util-utf8': 2.3.0 - tslib: 2.8.1 - - '@aws-crypto/sha256-js@5.2.0': - dependencies: - '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.974.5 - tslib: 2.8.1 - - '@aws-crypto/supports-web-crypto@5.2.0': - dependencies: - tslib: 2.8.1 - - '@aws-crypto/util@5.2.0': - dependencies: - '@aws-sdk/types': 3.974.5 - '@smithy/util-utf8': 2.3.0 - tslib: 2.8.1 - '@aws-sdk/checksums@3.1000.29': dependencies: '@aws-sdk/core': 3.977.9 @@ -10230,16 +10031,14 @@ snapshots: '@smithy/types': 4.18.0 tslib: 2.8.1 - '@aws-sdk/client-bedrock-runtime@3.1048.0': + '@aws-sdk/client-bedrock-runtime@3.1127.0': dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 '@aws-sdk/core': 3.977.9 '@aws-sdk/credential-provider-node': 3.972.82 '@aws-sdk/eventstream-handler-node': 3.972.34 '@aws-sdk/middleware-eventstream': 3.972.29 '@aws-sdk/middleware-websocket': 3.972.52 - '@aws-sdk/token-providers': 3.1048.0 + '@aws-sdk/token-providers': 3.1127.0 '@aws-sdk/types': 3.974.5 '@smithy/core': 3.33.3 '@smithy/fetch-http-handler': 5.8.0 @@ -10417,7 +10216,7 @@ snapshots: '@smithy/types': 4.18.0 tslib: 2.8.1 - '@aws-sdk/token-providers@3.1048.0': + '@aws-sdk/token-providers@3.1116.0': dependencies: '@aws-sdk/core': 3.977.9 '@aws-sdk/nested-clients': 3.997.44 @@ -10426,7 +10225,7 @@ snapshots: '@smithy/types': 4.18.0 tslib: 2.8.1 - '@aws-sdk/token-providers@3.1116.0': + '@aws-sdk/token-providers@3.1127.0': dependencies: '@aws-sdk/core': 3.977.9 '@aws-sdk/nested-clients': 3.997.44 @@ -10440,10 +10239,6 @@ snapshots: '@smithy/types': 4.18.0 tslib: 2.8.1 - '@aws-sdk/util-locate-window@3.965.10': - dependencies: - tslib: 2.8.1 - '@aws-sdk/xml-builder@3.972.40': dependencies: '@smithy/types': 4.18.0 @@ -11329,48 +11124,50 @@ snapshots: dependencies: e2b: 2.49.1 - '@earendil-works/chord@0.85.1': + '@earendil-works/chord@0.87.1': dependencies: - esbuild: 0.28.1 + esbuild: 0.28.2 - '@earendil-works/pi-agent-core@0.85.1(@modelcontextprotocol/sdk@1.30.0(@cfworker/json-schema@4.1.1)(zod@4.6.2))(ws@8.21.3)(zod@4.6.2)': + '@earendil-works/pi-agent-core@0.87.1(@modelcontextprotocol/sdk@1.30.0(@cfworker/json-schema@4.1.1)(zod@4.6.2))(ws@8.21.3)(zod@4.6.2)': dependencies: - '@earendil-works/chord': 0.85.1 - '@earendil-works/pi-ai': 0.85.1(@modelcontextprotocol/sdk@1.30.0(@cfworker/json-schema@4.1.1)(zod@4.6.2))(ws@8.21.3)(zod@4.6.2) - '@earendil-works/pi-telemetry': 0.85.1 + '@earendil-works/chord': 0.87.1 + '@earendil-works/pi-ai': 0.87.1(@modelcontextprotocol/sdk@1.30.0(@cfworker/json-schema@4.1.1)(zod@4.6.2))(ws@8.21.3)(zod@4.6.2) + '@earendil-works/pi-telemetry': 0.87.1 diff: 8.0.4 - ignore: 7.0.5 - typebox: 1.3.7 + ignore: 7.0.8 + typebox: 1.3.27 yaml: 2.9.0 transitivePeerDependencies: - '@modelcontextprotocol/sdk' - bufferutil + - kerberos - supports-color - utf-8-validate - ws - zod - '@earendil-works/pi-ai@0.85.1(@modelcontextprotocol/sdk@1.30.0(@cfworker/json-schema@4.1.1)(zod@4.6.2))(ws@8.21.3)(zod@4.6.2)': + '@earendil-works/pi-ai@0.87.1(@modelcontextprotocol/sdk@1.30.0(@cfworker/json-schema@4.1.1)(zod@4.6.2))(ws@8.21.3)(zod@4.6.2)': dependencies: - '@anthropic-ai/sdk': 0.123.0(zod@4.6.2) - '@aws-sdk/client-bedrock-runtime': 3.1048.0 - '@earendil-works/pi-telemetry': 0.85.1 - '@google/genai': 1.52.0(@modelcontextprotocol/sdk@1.30.0(@cfworker/json-schema@4.1.1)(zod@4.6.2)) - '@smithy/node-http-handler': 4.7.3 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 + '@anthropic-ai/sdk': 0.124.0(zod@4.6.2) + '@aws-sdk/client-bedrock-runtime': 3.1127.0 + '@earendil-works/pi-telemetry': 0.87.1 + '@google/genai': 2.21.0(@modelcontextprotocol/sdk@1.30.0(@cfworker/json-schema@4.1.1)(zod@4.6.2)) + '@smithy/node-http-handler': 4.12.1 + http-proxy-agent: 9.1.0 + https-proxy-agent: 9.1.0 openai: 6.40.0(ws@8.21.3)(zod@4.6.2) partial-json: 0.1.7 - typebox: 1.3.7 + typebox: 1.3.27 transitivePeerDependencies: - '@modelcontextprotocol/sdk' - bufferutil + - kerberos - supports-color - utf-8-validate - ws - zod - '@earendil-works/pi-telemetry@0.85.1': {} + '@earendil-works/pi-telemetry@0.87.1': {} '@egjs/hammerjs@2.0.17': dependencies: @@ -11526,159 +11323,81 @@ snapshots: '@epic-web/invariant@1.0.0': {} - '@esbuild/aix-ppc64@0.28.1': - optional: true - '@esbuild/aix-ppc64@0.28.2': optional: true - '@esbuild/android-arm64@0.28.1': - optional: true - '@esbuild/android-arm64@0.28.2': optional: true - '@esbuild/android-arm@0.28.1': - optional: true - '@esbuild/android-arm@0.28.2': optional: true - '@esbuild/android-x64@0.28.1': - optional: true - '@esbuild/android-x64@0.28.2': optional: true - '@esbuild/darwin-arm64@0.28.1': - optional: true - '@esbuild/darwin-arm64@0.28.2': optional: true - '@esbuild/darwin-x64@0.28.1': - optional: true - '@esbuild/darwin-x64@0.28.2': optional: true - '@esbuild/freebsd-arm64@0.28.1': - optional: true - '@esbuild/freebsd-arm64@0.28.2': optional: true - '@esbuild/freebsd-x64@0.28.1': - optional: true - '@esbuild/freebsd-x64@0.28.2': optional: true - '@esbuild/linux-arm64@0.28.1': - optional: true - '@esbuild/linux-arm64@0.28.2': optional: true - '@esbuild/linux-arm@0.28.1': - optional: true - '@esbuild/linux-arm@0.28.2': optional: true - '@esbuild/linux-ia32@0.28.1': - optional: true - '@esbuild/linux-ia32@0.28.2': optional: true - '@esbuild/linux-loong64@0.28.1': - optional: true - '@esbuild/linux-loong64@0.28.2': optional: true - '@esbuild/linux-mips64el@0.28.1': - optional: true - '@esbuild/linux-mips64el@0.28.2': optional: true - '@esbuild/linux-ppc64@0.28.1': - optional: true - '@esbuild/linux-ppc64@0.28.2': optional: true - '@esbuild/linux-riscv64@0.28.1': - optional: true - '@esbuild/linux-riscv64@0.28.2': optional: true - '@esbuild/linux-s390x@0.28.1': - optional: true - '@esbuild/linux-s390x@0.28.2': optional: true - '@esbuild/linux-x64@0.28.1': - optional: true - '@esbuild/linux-x64@0.28.2': optional: true - '@esbuild/netbsd-arm64@0.28.1': - optional: true - '@esbuild/netbsd-arm64@0.28.2': optional: true - '@esbuild/netbsd-x64@0.28.1': - optional: true - '@esbuild/netbsd-x64@0.28.2': optional: true - '@esbuild/openbsd-arm64@0.28.1': - optional: true - '@esbuild/openbsd-arm64@0.28.2': optional: true - '@esbuild/openbsd-x64@0.28.1': - optional: true - '@esbuild/openbsd-x64@0.28.2': optional: true - '@esbuild/openharmony-arm64@0.28.1': - optional: true - '@esbuild/openharmony-arm64@0.28.2': optional: true - '@esbuild/sunos-x64@0.28.1': - optional: true - '@esbuild/sunos-x64@0.28.2': optional: true - '@esbuild/win32-arm64@0.28.1': - optional: true - '@esbuild/win32-arm64@0.28.2': optional: true - '@esbuild/win32-ia32@0.28.1': - optional: true - '@esbuild/win32-ia32@0.28.2': optional: true - '@esbuild/win32-x64@0.28.1': - optional: true - '@esbuild/win32-x64@0.28.2': optional: true @@ -12120,7 +11839,7 @@ snapshots: '@fontsource-variable/inter@5.3.0': {} - '@google/genai@1.52.0(@modelcontextprotocol/sdk@1.30.0(@cfworker/json-schema@4.1.1)(zod@4.6.2))': + '@google/genai@2.21.0(@modelcontextprotocol/sdk@1.30.0(@cfworker/json-schema@4.1.1)(zod@4.6.2))': dependencies: google-auth-library: 10.9.1 p-retry: 4.6.2 @@ -13769,22 +13488,12 @@ snapshots: '@smithy/types': 4.18.0 tslib: 2.8.1 - '@smithy/is-array-buffer@2.2.0': - dependencies: - tslib: 2.8.1 - '@smithy/node-http-handler@4.12.1': dependencies: '@smithy/core': 3.33.3 '@smithy/types': 4.18.0 tslib: 2.8.1 - '@smithy/node-http-handler@4.7.3': - dependencies: - '@smithy/core': 3.33.3 - '@smithy/types': 4.18.0 - tslib: 2.8.1 - '@smithy/signature-v4@5.7.3': dependencies: '@smithy/core': 3.33.3 @@ -13795,16 +13504,6 @@ snapshots: dependencies: tslib: 2.8.1 - '@smithy/util-buffer-from@2.2.0': - dependencies: - '@smithy/is-array-buffer': 2.2.0 - tslib: 2.8.1 - - '@smithy/util-utf8@2.3.0': - dependencies: - '@smithy/util-buffer-from': 2.2.0 - tslib: 2.8.1 - '@socket.io/component-emitter@3.1.2': {} '@stablelib/base64@1.0.1': {} @@ -14505,6 +14204,8 @@ snapshots: agent-base@7.1.4: {} + agent-base@9.0.0: {} + agent-cli-detector@0.1.7: {} ajv-draft-04@1.0.0(ajv@8.20.0): @@ -16071,35 +15772,6 @@ snapshots: es6-error@4.1.1: optional: true - esbuild@0.28.1: - optionalDependencies: - '@esbuild/aix-ppc64': 0.28.1 - '@esbuild/android-arm': 0.28.1 - '@esbuild/android-arm64': 0.28.1 - '@esbuild/android-x64': 0.28.1 - '@esbuild/darwin-arm64': 0.28.1 - '@esbuild/darwin-x64': 0.28.1 - '@esbuild/freebsd-arm64': 0.28.1 - '@esbuild/freebsd-x64': 0.28.1 - '@esbuild/linux-arm': 0.28.1 - '@esbuild/linux-arm64': 0.28.1 - '@esbuild/linux-ia32': 0.28.1 - '@esbuild/linux-loong64': 0.28.1 - '@esbuild/linux-mips64el': 0.28.1 - '@esbuild/linux-ppc64': 0.28.1 - '@esbuild/linux-riscv64': 0.28.1 - '@esbuild/linux-s390x': 0.28.1 - '@esbuild/linux-x64': 0.28.1 - '@esbuild/netbsd-arm64': 0.28.1 - '@esbuild/netbsd-x64': 0.28.1 - '@esbuild/openbsd-arm64': 0.28.1 - '@esbuild/openbsd-x64': 0.28.1 - '@esbuild/openharmony-arm64': 0.28.1 - '@esbuild/sunos-x64': 0.28.1 - '@esbuild/win32-arm64': 0.28.1 - '@esbuild/win32-ia32': 0.28.1 - '@esbuild/win32-x64': 0.28.1 - esbuild@0.28.2: optionalDependencies: '@esbuild/aix-ppc64': 0.28.2 @@ -17060,6 +16732,15 @@ snapshots: transitivePeerDependencies: - supports-color + http-proxy-agent@9.1.0: + dependencies: + agent-base: 9.0.0 + debug: 4.4.3 + proxy-agent-negotiate: 1.1.0 + transitivePeerDependencies: + - kerberos + - supports-color + http2-wrapper@1.0.3: dependencies: quick-lru: 5.1.1 @@ -17081,6 +16762,15 @@ snapshots: transitivePeerDependencies: - supports-color + https-proxy-agent@9.1.0: + dependencies: + agent-base: 9.0.0 + debug: 4.4.3 + proxy-agent-negotiate: 1.1.0 + transitivePeerDependencies: + - kerberos + - supports-color + human-signals@2.1.0: {} hyphenate-style-name@1.1.0: {} @@ -17097,7 +16787,7 @@ snapshots: ignore@5.3.2: {} - ignore@7.0.5: {} + ignore@7.0.8: {} import-fresh@3.3.1: dependencies: @@ -18800,6 +18490,8 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + proxy-agent-negotiate@1.1.0: {} + proxy-from-env@2.1.0: {} pseudolocale@2.3.0: @@ -20008,7 +19700,7 @@ snapshots: media-typer: 1.1.1 mime-types: 3.0.2 - typebox@1.3.7: {} + typebox@1.3.27: {} typesafe-path@0.2.2: {}