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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
cynarAI 2026-10-05 23:18:40 +02:00 committed by GitHub
parent 9062717eee
commit b78eff1f7b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 979 additions and 551 deletions

View File

@ -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).

View File

@ -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",
}),
});
});

View File

@ -5543,6 +5543,7 @@ async function meDto(deps: RouterDeps, actor: Actor): Promise<Me> {
spaceId: actor.spaceId,
isDeploymentOwner: actor.isDeploymentOwner,
needsModel: setup.needsModel,
modelsManaged: Boolean(deps.env.deploymentModelKey),
defaultProvider:
setup.credential?.provider ?? setup.settings?.defaultModelProvider ?? fallback.provider,
defaultModel:

View File

@ -353,15 +353,17 @@ export default function Account() {
</View>
) : null}
<Pressable
accessibilityRole="button"
disabled={pending}
onPress={() => router.push("/models")}
style={({ pressed }) => [styles.settingsButton, pressed && styles.pressed]}
>
<Text style={styles.settingsTitle}>{t("Models")}</Text>
<Text style={styles.chevron}>›</Text>
</Pressable>
{me?.modelsManaged ? null : (
<Pressable
accessibilityRole="button"
disabled={pending}
onPress={() => router.push("/models")}
style={({ pressed }) => [styles.settingsButton, pressed && styles.pressed]}
>
<Text style={styles.settingsTitle}>{t("Models")}</Text>
<Text style={styles.chevron}>›</Text>
</Pressable>
)}
<Pressable
accessibilityRole="button"

View File

@ -353,25 +353,27 @@ export default function BotSettingsScreen() {
<OoAtelier value={color} identity={bot?.id ?? ""} onChange={setColor} />
</View>
<ComputerModePicker value={computerMode} onChange={setComputerMode} />
<Pressable
accessibilityRole="button"
accessibilityLabel={t("Advanced")}
accessibilityState={{ expanded: advancedOpen }}
onPress={() => setAdvancedOpen((open) => !open)}
style={{
marginTop: 20,
minHeight: 44,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
}}
>
<Text style={{ color: tokens.mutedForeground, fontSize: 14 }}>{t("Advanced")}</Text>
<Text style={{ color: tokens.mutedForeground, fontSize: 18 }}>
{advancedOpen ? "⌃" : "⌄"}
</Text>
</Pressable>
{advancedOpen ? (
{me?.modelsManaged ? null : (
<Pressable
accessibilityRole="button"
accessibilityLabel={t("Advanced")}
accessibilityState={{ expanded: advancedOpen }}
onPress={() => setAdvancedOpen((open) => !open)}
style={{
marginTop: 20,
minHeight: 44,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
}}
>
<Text style={{ color: tokens.mutedForeground, fontSize: 14 }}>{t("Advanced")}</Text>
<Text style={{ color: tokens.mutedForeground, fontSize: 18 }}>
{advancedOpen ? "⌃" : "⌄"}
</Text>
</Pressable>
)}
{advancedOpen && !me?.modelsManaged ? (
<View>
<Text
style={{

View File

@ -740,6 +740,7 @@ export type MobileMe = Pick<
| "defaultProvider"
| "defaultModel"
| "needsModel"
| "modelsManaged"
| "avatarStyle"
| "isDeploymentOwner"
>;

View File

@ -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(

View File

@ -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<string, unknown>), ...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");
});

View File

@ -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<string, unknown>), 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");
});

View File

@ -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();
});
});

View File

@ -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<MediaStream | null> {
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();

View File

@ -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."

View File

@ -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."

View File

@ -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 ""

View File

@ -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 ""

View File

@ -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 ""

View File

@ -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 ""

View File

@ -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 ""

View File

@ -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 ""

View File

@ -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 ""

View File

@ -74,6 +74,7 @@ export function ModelSettingsOverlay({
const [pending, setPending] = useState<"connect" | "default" | null>(null);
const [error, setError] = useState<string | null>(null);
const [notice, setNotice] = useState<string | null>(null);
const [showProviders, setShowProviders] = useState(false);
const detailScrollRef = useRef<HTMLDivElement>(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 ? (
<Trans>Loading model catalog…</Trans>
) : setupOnly ? (
<Trans>Connect a model so your Oo can reply.</Trans>
) : localOwner ? (
<Trans>Models for the server owner’s default space.</Trans>
) : (
@ -399,10 +405,10 @@ export function ModelSettingsOverlay({
)}
</div>
</div>
<ModelTierSettings credentials={credentials} catalog={catalog} />
{setupOnly ? null : <ModelTierSettings credentials={credentials} catalog={catalog} />}
<div className="flex min-h-0 flex-1 flex-col gap-6 overflow-hidden px-6 py-6 sm:px-8 md:flex-row">
<div className="flex min-h-0 shrink-0 flex-col md:w-[310px]">
<div className={setupOnly ? "hidden" : "flex min-h-0 shrink-0 flex-col md:w-[310px]"}>
<div className="mb-3 text-ui-body text-muted-foreground">
<Trans>Providers</Trans>
</div>
@ -603,7 +609,7 @@ export function ModelSettingsOverlay({
maxImagesLabel={t`Maximum images per request`}
/>
</>
) : (
) : setupOnly ? null : (
<>
<span>
<Trans>Model</Trans>
@ -622,13 +628,13 @@ export function ModelSettingsOverlay({
</>
)}
</div>
{!isOpenAiCompatible && selected.billing ? (
{!isOpenAiCompatible && !setupOnly && selected.billing ? (
<p className="mt-2 text-ui-body-sm leading-[1.5] text-muted-foreground">
{selected.billing}
</p>
) : null}
{!isOpenAiCompatible ? (
{!isOpenAiCompatible && !setupOnly ? (
<div className="mt-5 rounded-xl border border-border px-4 py-3">
<div className="text-ui-body-sm uppercase tracking-[0.08em] text-muted-foreground/80">
<Trans>Personal credential</Trans>
@ -651,7 +657,7 @@ export function ModelSettingsOverlay({
) : null}
{subscriptionSignIn ? (
<div className="mt-5">
<div className={setupOnly ? undefined : "mt-5"}>
{oauth ? (
<div className="rounded-xl border border-border px-4 py-3">
{oauth.mode === "auth-url" ? (
@ -800,6 +806,17 @@ export function ModelSettingsOverlay({
</div>
) : null}
{setupOnly ? (
<Button
type="button"
variant="link"
className="mt-5 h-auto px-0 text-ui-body-sm text-muted-foreground underline"
onClick={() => setShowProviders(true)}
>
<Trans>Other providers</Trans>
</Button>
) : null}
{selected.auth === "oauth" && !subscriptionSignIn ? (
<p className="mt-5 text-sm leading-[1.5] text-muted-foreground">
<Trans>

View File

@ -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" ? (
<UpdatesSettingsPanel isDeploymentOwner={isDeploymentOwner} />
) : null}
{section === "models" ? (
{section === "models" && !modelsManaged ? (
<ModelSettingsOverlay embedded onClose={requestClose} />
) : null}
{section === "memory" ? (

View File

@ -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={() => {

View File

@ -561,66 +561,70 @@ export function BotSettings({
<KnowledgeSection botId={bot.id} onSkillsChange={onSkillsChange} />
) : null}
</Suspense>
<label htmlFor={`${ids}-model`} className={fieldLabelClass}>
<Trans>Model</Trans>
<NativeSelect
id={`${ids}-model`}
className="mt-2 w-full"
value={modelKey}
onChange={(event) => {
setModelKey(event.target.value);
setThinkingLevel("");
}}
>
<NativeSelectOption value="">
{t`Space default`}
{me?.defaultModel
? ` (${catalogLabel(catalog, me.defaultProvider, me.defaultModel) ?? me.defaultModel})`
: ""}
</NativeSelectOption>
{tierOptions.length ? (
<NativeSelectOptGroup label={t`Tiers`}>
{tierOptions.map((option) => (
{me?.modelsManaged ? null : (
<>
<label htmlFor={`${ids}-model`} className={fieldLabelClass}>
<Trans>Model</Trans>
<NativeSelect
id={`${ids}-model`}
className="mt-2 w-full"
value={modelKey}
onChange={(event) => {
setModelKey(event.target.value);
setThinkingLevel("");
}}
>
<NativeSelectOption value="">
{t`Space default`}
{me?.defaultModel
? ` (${catalogLabel(catalog, me.defaultProvider, me.defaultModel) ?? me.defaultModel})`
: ""}
</NativeSelectOption>
{tierOptions.length ? (
<NativeSelectOptGroup label={t`Tiers`}>
{tierOptions.map((option) => (
<NativeSelectOption key={option.key} value={option.key}>
{option.label}
</NativeSelectOption>
))}
</NativeSelectOptGroup>
) : null}
{modelKey &&
!selectedTier &&
!connectedOptions.some((option) => option.key === modelKey) ? (
<NativeSelectOption value={modelKey}>
{parseModelOptionKey(modelKey)?.modelId ?? modelKey}
</NativeSelectOption>
) : null}
{connectedOptions.map((option) => (
<NativeSelectOption key={option.key} value={option.key}>
{option.label}
</NativeSelectOption>
))}
</NativeSelectOptGroup>
</NativeSelect>
</label>
{thinkingOptions.length ? (
<label htmlFor={`${ids}-thinking`} className={fieldLabelClass}>
<Trans>Thinking</Trans>
<NativeSelect
id={`${ids}-thinking`}
className="mt-2 w-full"
value={thinkingLevel}
onChange={(event) => setThinkingLevel(event.target.value)}
>
<NativeSelectOption value="">
{t`Default (${thinkingLevelLabel(defaultThinkingLevel)})`}
</NativeSelectOption>
{thinkingOptions.map((level) => (
<NativeSelectOption key={level} value={level}>
{thinkingLevelLabel(level)}
</NativeSelectOption>
))}
</NativeSelect>
</label>
) : null}
{modelKey &&
!selectedTier &&
!connectedOptions.some((option) => option.key === modelKey) ? (
<NativeSelectOption value={modelKey}>
{parseModelOptionKey(modelKey)?.modelId ?? modelKey}
</NativeSelectOption>
) : null}
{connectedOptions.map((option) => (
<NativeSelectOption key={option.key} value={option.key}>
{option.label}
</NativeSelectOption>
))}
</NativeSelect>
</label>
{thinkingOptions.length ? (
<label htmlFor={`${ids}-thinking`} className={fieldLabelClass}>
<Trans>Thinking</Trans>
<NativeSelect
id={`${ids}-thinking`}
className="mt-2 w-full"
value={thinkingLevel}
onChange={(event) => setThinkingLevel(event.target.value)}
>
<NativeSelectOption value="">
{t`Default (${thinkingLevelLabel(defaultThinkingLevel)})`}
</NativeSelectOption>
{thinkingOptions.map((level) => (
<NativeSelectOption key={level} value={level}>
{thinkingLevelLabel(level)}
</NativeSelectOption>
))}
</NativeSelect>
</label>
) : null}
</>
)}
{memoryProviderConfigured ? (
<div className="mt-4 text-ui-body text-muted-foreground">
<Trans>Memory scope</Trans>

View File

@ -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.

View File

@ -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",

View File

@ -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");

View File

@ -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();
});
});

View File

@ -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<string, string | undefined> = {
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<string, string> = {
openrouter: DEFAULT_OPENROUTER_MODEL_ID,
anthropic: "claude-sonnet-5",
anthropic: RECOMMENDED_MODEL.model,
...(localIds[0] ? { local: localIds[0] } : {}),
};
return {
provider,

View File

@ -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<string, unknown> | null, tierRow: unknown) {
function tierPrisma(
bot: Record<string, unknown> | 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<typeof createRunExecutor>[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();
}
});
});

View File

@ -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<AgentRunRequest["model"] | null> => {
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,

View File

@ -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<string, string | undefined>();
@ -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" });
});
});

View File

@ -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<Record<ModelTier, string>> {
const ids = new Set(localModelIds(env));
const tiers: Partial<Record<ModelTier, string>> = {};
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(),
});
}

View File

@ -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"],

View File

@ -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;
}

View File

@ -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(),

502
pnpm-lock.yaml generated
View File

@ -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: {}