diff --git a/apps/app/src/components/layout/AppLayout.plugin-panel-header.test.tsx b/apps/app/src/components/layout/AppLayout.plugin-panel-header.test.tsx
index 7dff45cb33..ba54eb2796 100644
--- a/apps/app/src/components/layout/AppLayout.plugin-panel-header.test.tsx
+++ b/apps/app/src/components/layout/AppLayout.plugin-panel-header.test.tsx
@@ -28,6 +28,7 @@ vi.mock("@/hooks/queries/system-queries", () => ({
data: {
experiments: {
claudeCodeMockCliTraffic: false,
+ cloudAi: false,
newOnboarding: false,
toolsHub: true,
},
diff --git a/apps/app/src/components/layout/AppLayout.root-compose-project.test.tsx b/apps/app/src/components/layout/AppLayout.root-compose-project.test.tsx
index b2f91fa6c8..98e8cebdf2 100644
--- a/apps/app/src/components/layout/AppLayout.root-compose-project.test.tsx
+++ b/apps/app/src/components/layout/AppLayout.root-compose-project.test.tsx
@@ -24,6 +24,7 @@ vi.mock("@/hooks/queries/system-queries", () => ({
data: {
experiments: {
claudeCodeMockCliTraffic: false,
+ cloudAi: false,
newOnboarding: false,
toolsHub: true,
},
diff --git a/apps/app/src/components/plugin/PluginSettings.test.tsx b/apps/app/src/components/plugin/PluginSettings.test.tsx
index 68d1ddbdaa..60973357d7 100644
--- a/apps/app/src/components/plugin/PluginSettings.test.tsx
+++ b/apps/app/src/components/plugin/PluginSettings.test.tsx
@@ -1,6 +1,7 @@
// @vitest-environment jsdom
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
+import { MemoryRouter } from "react-router-dom";
import { afterEach, describe, expect, it, vi } from "vitest";
import { createQueryClientTestHarness } from "@/test/queryClientTestHarness";
import {
@@ -283,14 +284,16 @@ describe("PluginSettingsDetail settings gating", () => {
});
const { wrapper } = createQueryClientTestHarness();
render(
-
- {section.description} -
- ) : null} -+ {section.description} +
+ ) : null} + --server https://.getbb.app` pairs this bb
- server for browser access at `.getbb.app` (get the code from
+- `bb connect --code --server https://.getbb.app` connects this
+ bb server to bb Cloud (getbb.app): browser access at `.getbb.app`,
+ port shares, and AI features through the account (get the code from
https://getbb.app). Pairing returns immediately — the
server itself holds the tunnel and reconnects on restart, so there is no
foreground process.
- `bb connect status` / `bb connect off` report and clear the pairing.
+ `bb connect status` reports the connection; `bb connect off` unlinks this bb
+ from Cloud and clears the pairing.
+ After `bb settings experiment cloudAi true`, AI features (thread titles,
+ commit messages, voice transcription) can route through bb Cloud with
+ automatic fallback to locally configured providers. `bb connect ai
+ [on|off]` shows or sets the plugin's inner preference; the experiment and
+ preference must both be on.
Port sharing works from a thread on any enrolled host. `bb connect expose
` resolves that thread's environment host and returns its public URL;
outside a thread it defaults to the server host. Pass `--host
@@ -173,10 +184,12 @@ isolated|reuse`, or anchor with `--source-seq-end`. Permission mode inherits
name, url, live) so callers can discover siblings; `--json` includes
`selfHandle` for deduping this server. When you start a local server the user
should open remotely, expose the port and give them the share URL. Remote
- access is owned by the builtin `connect` plugin: `bb plugin disable connect`
- cuts it off entirely; with bb connect still enabled, `bb plugin enable
- connect` restores the command. Plugins → Connect shows the current URL, QR
- code, shared ports, re-pair form, and disconnect control.
+ bb Cloud is provided by the builtin Cloud plugin (internal id `connect`):
+ `bb plugin disable connect` cuts it off entirely (remote access and AI
+ features alike);
+ `bb plugin enable connect` restores it. Settings → Cloud always contains
+ Remote access (URL, QR code, shared ports, pairing, and disconnect) and adds
+ a separate Cloud AI section when the `cloudAi` experiment is enabled.
- Add remote execution machines from Settings → Machines. Its one-line
installer stores the bb connect machine credential locally and configures
both the daemon protocol and agent-launched `bb` CLI to traverse the account
diff --git a/apps/server/src/services/skills/builtin-skills/bb-cli/references/app-settings.md b/apps/server/src/services/skills/builtin-skills/bb-cli/references/app-settings.md
index bab61c7147..d17a61fb5a 100644
--- a/apps/server/src/services/skills/builtin-skills/bb-cli/references/app-settings.md
+++ b/apps/server/src/services/skills/builtin-skills/bb-cli/references/app-settings.md
@@ -55,3 +55,14 @@ every window and client sees the same value.
- Enable it with `bb settings experiment newOnboarding true`.
- Use `bb settings replay-onboarding` to enable the experiment and show the
agent and project setup guide again.
+
+## Cloud AI
+
+- The `cloudAi` experiment defaults to false. Enable it with
+ `bb settings experiment cloudAi true`.
+- It is the outer product gate for cloud-backed thread titles, commit messages,
+ and voice transcription. While off, Cloud AI is hidden in the Cloud plugin
+ and the server does not send requests to its registered provider.
+- Remote access, pairing, and port sharing remain available while it is off.
+- Once enabled, `bb connect ai [on|off]` controls the plugin's separate inner
+ routing preference. Both the experiment and preference must be on.
diff --git a/apps/server/src/services/skills/builtin-skills/bb-plugin-authoring/SKILL.md b/apps/server/src/services/skills/builtin-skills/bb-plugin-authoring/SKILL.md
index 7cf0a97c30..be751ce158 100644
--- a/apps/server/src/services/skills/builtin-skills/bb-plugin-authoring/SKILL.md
+++ b/apps/server/src/services/skills/builtin-skills/bb-plugin-authoring/SKILL.md
@@ -731,6 +731,22 @@ the same cleanup without a replacement. A
captured `bb` from a previous load throws `PluginContextStaleError` on use
— never stash the API object in module-level state that outlives a load.
+### bb.experimental_registerCloudAiProvider
+
+`bb.experimental_registerCloudAiProvider(provider)` (EXPERIMENTAL — see
+docs/api_to_audit.md) registers a cloud route for bb's small AI tasks:
+thread-title inference, commit-message inference, and voice transcription.
+The provider is `{ isAvailable(): boolean; complete({prompt, schema, signal});
+transcribe({file, prompt?, signal}) }` where the async methods resolve to
+`{ ok: true, value }` or `{ ok: false, code, message }` (codes: `unauthorized`,
+`quota_exhausted`, `unavailable`) — result-shaped, never thrown classes. When
+the server's `cloudAi` experiment is enabled and `isAvailable()` is true, the
+host tries the provider before the locally configured AI providers and falls
+back on `ok: false`. While the experiment is off, the provider may remain
+registered but is never called. Single host-wide slot; the most recent
+registration wins and is unregistered automatically with the plugin's dispose
+hooks. Used by the builtin Cloud plugin (internal id `connect`) for bb Cloud.
+
## Frontend (`bb.app` entry)
`app.tsx` default-exports `definePluginApp` from `@bb/plugin-sdk/app`.
@@ -821,11 +837,16 @@ export default definePluginApp((app) => {
id: "credentials",
component: CredentialForm,
});
+ app.slots.settingsSection({
+ id: "remote-access",
+ title: "Remote access",
+ component: RemoteAccessSettings,
+ });
app.slots.sidebarFooterAction({
- id: "remote",
+ id: "remote-access",
title: "Remote access",
icon: "Smartphone",
- run: ({ openSettings }) => openSettings(),
+ run: ({ openSettings }) => openSettings({ sectionId: "remote-access" }),
});
app.slots.messageDirective({ id: "inline-vis", component: InlineVis });
app.slots.experimental_threadList({
@@ -1079,8 +1100,10 @@ Slot props contracts (versioned, additive-only):
the chrome so icons stay consistent. Registration:
`{ id, title, icon, run }`. Activating it calls
`run({ openSettings })` — use `openSettings()` to open this plugin's
- detail page in Tools, or do anything else (rpc, toast). Errors from `run`
- (sync or async) are contained and logged,
+ detail page in Tools, or `openSettings({ sectionId: "my-section" })` to
+ target one of its registered settings sections. The action's `icon` is its
+ own glyph rather than the plugin branding icon. The callback can also do
+ anything else (rpc, toast). Errors from `run` (sync or async) are contained and logged,
never breaking the sidebar. `title` is the tooltip + accessible label;
`icon` is a BB icon-name hint (unknown names fall back to a generic bolt).
- `fileOpener` → `{ path: string, source }` — register as a viewer/editor
diff --git a/apps/server/test/ai/inference.test.ts b/apps/server/test/ai/inference.test.ts
index 6e8c89a4b3..6c8f3668b0 100644
--- a/apps/server/test/ai/inference.test.ts
+++ b/apps/server/test/ai/inference.test.ts
@@ -1,5 +1,12 @@
+import { setExperiments, type DbConnection } from "@bb/db";
+import { defaultExperiments } from "@bb/domain";
+import type { CloudAiProvider, CloudAiResult, JsonValue } from "@bb/plugin-sdk";
import { Type } from "@earendil-works/pi-ai";
-import { describe, expect, it } from "vitest";
+import { afterEach, describe, expect, it } from "vitest";
+import {
+ registerCloudAiProvider,
+ resetCloudAiProviderForTests,
+} from "../../src/services/ai/cloud-ai-provider.js";
import {
InferenceTimeoutError,
inferenceComplete,
@@ -16,6 +23,10 @@ const titleSchema = Type.Object({
title: Type.String(),
});
+function enableCloudAiExperiment(db: DbConnection): void {
+ setExperiments(db, { ...defaultExperiments, cloudAi: true });
+}
+
describe("inferenceComplete", () => {
it("surfaces missing host for codex inference", async () => {
await withTestHarness({
@@ -127,3 +138,178 @@ describe("inferenceComplete", () => {
});
});
});
+
+describe("inferenceComplete cloud provider routing", () => {
+ afterEach(() => {
+ resetCloudAiProviderForTests();
+ });
+
+ function stubProvider(over: {
+ available?: boolean;
+ complete?: CloudAiProvider["complete"];
+ }): CloudAiProvider & { completeCalls: number } {
+ const provider = {
+ completeCalls: 0,
+ isAvailable: () => over.available ?? true,
+ async complete(
+ args: Parameters[0],
+ ): Promise> {
+ provider.completeCalls += 1;
+ if (over.complete) return over.complete(args);
+ return { ok: true, value: { title: "Cloud title" } };
+ },
+ async transcribe(): Promise> {
+ throw new Error("not under test");
+ },
+ };
+ return provider;
+ }
+
+ it("does not use a registered provider while the experiment is off", async () => {
+ await withTestHarness(async (harness) => {
+ const provider = stubProvider({});
+ registerCloudAiProvider("connect", provider);
+
+ await expect(
+ inferenceComplete(harness.deps, {
+ prompt: "Generate a title",
+ schema: titleSchema,
+ }),
+ ).resolves.toBeNull();
+ expect(provider.completeCalls).toBe(0);
+ });
+ });
+
+ it("routes through an available provider instead of the codex daemon", async () => {
+ // codex model configured and NO host connected: success proves the daemon
+ // path was never consulted (it would throw host_unavailable).
+ await withTestHarness({
+ inferenceModel: "codex/gpt-5.4-mini",
+ }, async (harness) => {
+ enableCloudAiExperiment(harness.db);
+ const provider = stubProvider({});
+ registerCloudAiProvider("connect", provider);
+ let sentSchema: JsonValue | null = null;
+ provider.complete = async (args) => {
+ provider.completeCalls += 1;
+ sentSchema = args.schema;
+ return { ok: true, value: { title: "Cloud title" } };
+ };
+
+ await expect(
+ inferenceComplete(harness.deps, {
+ prompt: "Generate a title",
+ schema: titleSchema,
+ timeoutMs: 5000,
+ }),
+ ).resolves.toEqual({ title: "Cloud title" });
+ expect(sentSchema).toMatchObject({ type: "object" });
+ });
+ });
+
+ it("skips an unavailable provider and returns null on invalid results", async () => {
+ await withTestHarness(async (harness) => {
+ enableCloudAiExperiment(harness.db);
+ const unavailable = stubProvider({ available: false });
+ registerCloudAiProvider("connect", unavailable);
+ // Local config is test/mock-model (unsupported) → local path yields null.
+ await expect(
+ inferenceComplete(harness.deps, {
+ prompt: "Generate a title",
+ schema: titleSchema,
+ }),
+ ).resolves.toBeNull();
+ expect(unavailable.completeCalls).toBe(0);
+
+ const invalidResult = stubProvider({
+ complete: async () => ({ ok: true, value: { wrong: 1 } }),
+ });
+ registerCloudAiProvider("connect", invalidResult);
+ await expect(
+ inferenceComplete(harness.deps, {
+ prompt: "Generate a title",
+ schema: titleSchema,
+ }),
+ ).resolves.toBeNull();
+ });
+ });
+
+ it("falls through to local on provider failure or a provider bug", async () => {
+ await withTestHarness(async (harness) => {
+ enableCloudAiExperiment(harness.db);
+ const failing = stubProvider({
+ complete: async () => ({
+ ok: false,
+ code: "quota_exhausted",
+ message: "budget reached",
+ }),
+ });
+ registerCloudAiProvider("connect", failing);
+ await expect(
+ inferenceComplete(harness.deps, {
+ prompt: "Generate a title",
+ schema: titleSchema,
+ }),
+ ).resolves.toBeNull();
+ expect(failing.completeCalls).toBe(1);
+
+ const throwing = stubProvider({
+ complete: async () => {
+ throw new Error("plugin bug");
+ },
+ });
+ registerCloudAiProvider("connect", throwing);
+ await expect(
+ inferenceComplete(harness.deps, {
+ prompt: "Generate a title",
+ schema: titleSchema,
+ }),
+ ).resolves.toBeNull();
+ });
+ });
+
+ it("turns a provider timeout into InferenceTimeoutError without local fallback", async () => {
+ await withTestHarness(async (harness) => {
+ enableCloudAiExperiment(harness.db);
+ registerCloudAiProvider(
+ "connect",
+ stubProvider({
+ complete: (args) =>
+ new Promise((_, reject) => {
+ args.signal.addEventListener("abort", () => {
+ reject(new DOMException("aborted", "AbortError"));
+ });
+ }),
+ }),
+ );
+
+ await expect(
+ inferenceComplete(harness.deps, {
+ prompt: "Generate a title",
+ schema: titleSchema,
+ timeoutMs: 50,
+ }),
+ ).rejects.toBeInstanceOf(InferenceTimeoutError);
+ });
+ });
+
+ it("stale unregister hooks cannot tear down a newer registration", async () => {
+ await withTestHarness(async (harness) => {
+ enableCloudAiExperiment(harness.db);
+ const first = stubProvider({});
+ const unregisterFirst = registerCloudAiProvider("connect", first);
+ const second = stubProvider({});
+ registerCloudAiProvider("connect", second);
+ unregisterFirst();
+
+ await expect(
+ inferenceComplete(harness.deps, {
+ prompt: "Generate a title",
+ schema: titleSchema,
+ }),
+ ).resolves.toEqual({ title: "Cloud title" });
+ expect(second.completeCalls).toBe(1);
+ expect(first.completeCalls).toBe(0);
+ });
+ });
+});
diff --git a/apps/server/test/ai/voice-transcription.test.ts b/apps/server/test/ai/voice-transcription.test.ts
index 930d4915d4..cb1da547f0 100644
--- a/apps/server/test/ai/voice-transcription.test.ts
+++ b/apps/server/test/ai/voice-transcription.test.ts
@@ -1,8 +1,18 @@
import { Buffer } from "node:buffer";
+import { setExperiments, type DbConnection } from "@bb/db";
+import { defaultExperiments } from "@bb/domain";
import type { HostDaemonOnlineRpcRequestMessage } from "@bb/host-daemon-contract";
-import { describe, expect, it, vi } from "vitest";
+import type { CloudAiProvider, CloudAiResult } from "@bb/plugin-sdk";
+import { afterEach, describe, expect, it, vi } from "vitest";
import { ApiError } from "../../src/errors.js";
-import { transcribeVoiceInput } from "../../src/services/ai/voice-transcription.js";
+import {
+ registerCloudAiProvider,
+ resetCloudAiProviderForTests,
+} from "../../src/services/ai/cloud-ai-provider.js";
+import {
+ resolveVoiceTranscriptionEnabled,
+ transcribeVoiceInput,
+} from "../../src/services/ai/voice-transcription.js";
import {
registerHostRpcResponder,
type HostRpcResponder,
@@ -44,6 +54,10 @@ function emptyVoiceFile(): File {
return new File([], "prompt.webm", { type: "audio/webm" });
}
+function enableCloudAiExperiment(db: DbConnection): void {
+ setExperiments(db, { ...defaultExperiments, cloudAi: true });
+}
+
function requireCodexVoiceTranscribeCommand(
request: HostDaemonOnlineRpcRequestMessage,
): CodexVoiceTranscribeCommand {
@@ -271,3 +285,162 @@ describe("voice transcription", () => {
}
});
});
+
+describe("voice transcription cloud provider routing", () => {
+ afterEach(() => {
+ resetCloudAiProviderForTests();
+ });
+
+ function stubProvider(over: {
+ available?: boolean;
+ transcribe?: CloudAiProvider["transcribe"];
+ }): CloudAiProvider {
+ return {
+ isAvailable: () => over.available ?? true,
+ async complete(): Promise> {
+ throw new Error("not under test");
+ },
+ async transcribe(
+ args: Parameters[0],
+ ): Promise> {
+ if (over.transcribe) return over.transcribe(args);
+ return { ok: true, value: "hello cloud" };
+ },
+ };
+ }
+
+ it("transcribes through an available provider with the trimmed prompt", async () => {
+ const harness = await createTestAppHarness({});
+ try {
+ enableCloudAiExperiment(harness.deps.db);
+ let sentPrompt: string | undefined;
+ registerCloudAiProvider(
+ "connect",
+ stubProvider({
+ transcribe: async (args) => {
+ sentPrompt = args.prompt;
+ return { ok: true, value: "hello cloud" };
+ },
+ }),
+ );
+ await expect(
+ transcribeVoiceInput(harness.deps, {
+ file: voiceFile(),
+ prompt: " context ",
+ }),
+ ).resolves.toBe("hello cloud");
+ expect(sentPrompt).toBe("context");
+ } finally {
+ await harness.cleanup();
+ }
+ });
+
+ it("falls back to the codex daemon when the provider fails", async () => {
+ const harness = await createCodexTranscriptionHarness({
+ handle(request) {
+ const command = requireCodexVoiceTranscribeCommand(request);
+ return {
+ ok: true,
+ result: { model: command.model, text: "hello daemon" },
+ };
+ },
+ });
+ try {
+ enableCloudAiExperiment(harness.deps.db);
+ registerCloudAiProvider(
+ "connect",
+ stubProvider({
+ transcribe: async () => ({
+ ok: false,
+ code: "quota_exhausted",
+ message: "budget reached",
+ }),
+ }),
+ );
+ await expect(
+ transcribeVoiceInput(harness.deps, { file: voiceFile() }),
+ ).resolves.toBe("hello daemon");
+ expect(harness.requests).toHaveLength(1);
+ } finally {
+ await harness.cleanup();
+ }
+ });
+
+ it("maps quota exhaustion to a retryable 503 when no local provider exists", async () => {
+ const harness = await createTestAppHarness({});
+ try {
+ enableCloudAiExperiment(harness.deps.db);
+ registerCloudAiProvider(
+ "connect",
+ stubProvider({
+ transcribe: async () => ({
+ ok: false,
+ code: "quota_exhausted",
+ message: "budget reached",
+ }),
+ }),
+ );
+ await transcribeVoiceInput(harness.deps, { file: voiceFile() }).then(
+ () => {
+ throw new Error("Expected transcription to fail");
+ },
+ (error: unknown) => {
+ expectRetryableApiError(error, {
+ code: "transcription_unavailable",
+ status: 503,
+ });
+ expect((error as ApiError).body.message).toContain("budget");
+ },
+ );
+ } finally {
+ await harness.cleanup();
+ }
+ });
+
+ it("maps a provider timeout to the standard 504 without a local attempt", async () => {
+ const harness = await createTestAppHarness({});
+ try {
+ enableCloudAiExperiment(harness.deps.db);
+ registerCloudAiProvider(
+ "connect",
+ stubProvider({
+ transcribe: (args) =>
+ new Promise((_, reject) => {
+ args.signal.addEventListener("abort", () => {
+ reject(new DOMException("aborted", "AbortError"));
+ });
+ }),
+ }),
+ );
+ vi.useFakeTimers();
+ const attempt = transcribeVoiceInput(harness.deps, {
+ file: voiceFile(),
+ });
+ const expectation = expect(attempt).rejects.toMatchObject({
+ status: 504,
+ body: { code: "transcription_timeout" },
+ });
+ await vi.advanceTimersByTimeAsync(20_000);
+ await expectation;
+ } finally {
+ vi.useRealTimers();
+ await harness.cleanup();
+ }
+ });
+
+ it("reports voice transcription enabled only when the experiment and provider are available", async () => {
+ const harness = await createTestAppHarness({});
+ try {
+ // Default harness config: unsupported local transcription provider.
+ expect(resolveVoiceTranscriptionEnabled(harness.deps)).toBe(false);
+ registerCloudAiProvider("connect", stubProvider({ available: false }));
+ expect(resolveVoiceTranscriptionEnabled(harness.deps)).toBe(false);
+ registerCloudAiProvider("connect", stubProvider({}));
+ expect(resolveVoiceTranscriptionEnabled(harness.deps)).toBe(false);
+ enableCloudAiExperiment(harness.deps.db);
+ expect(resolveVoiceTranscriptionEnabled(harness.deps)).toBe(true);
+ } finally {
+ await harness.cleanup();
+ }
+ });
+});
diff --git a/apps/server/test/services/plugins/builtin-plugins.test.ts b/apps/server/test/services/plugins/builtin-plugins.test.ts
index 83b901d4eb..a530fdafec 100644
--- a/apps/server/test/services/plugins/builtin-plugins.test.ts
+++ b/apps/server/test/services/plugins/builtin-plugins.test.ts
@@ -198,7 +198,7 @@ describe("builtin plugin reconciliation", () => {
const expectedIcons = new Map([
["ask-user-question", "MessageQuestion"],
["automations", "Clock"],
- ["connect", "Smartphone"],
+ ["connect", "Cloud"],
["custom-instructions", "EditFile"],
["inline-vis", "AppWindow"],
["secrets", "Lock"],
@@ -217,6 +217,17 @@ describe("builtin plugin reconciliation", () => {
}
});
+ it("presents the compatibility-id connect plugin as Cloud", async () => {
+ const manifest = await readPluginManifest(
+ resolveBuiltinPluginRootPath("connect"),
+ );
+ expect(manifest).toMatchObject({
+ id: "connect",
+ name: "Cloud",
+ branding: { icon: "Cloud" },
+ });
+ });
+
afterEach(async () => {
await service?.stop();
db.$client.close();
diff --git a/apps/server/test/services/plugins/plugin-authoring-docs.test.ts b/apps/server/test/services/plugins/plugin-authoring-docs.test.ts
index 00bf8aba10..4d96599791 100644
--- a/apps/server/test/services/plugins/plugin-authoring-docs.test.ts
+++ b/apps/server/test/services/plugins/plugin-authoring-docs.test.ts
@@ -67,6 +67,7 @@ const BB_PLUGIN_API_KEYS = [
"hosts",
"sdk",
"onDispose",
+ "experimental_registerCloudAiProvider",
] as const satisfies readonly (keyof BbPluginApi)[];
type MissingApiKey = Exclude<
diff --git a/apps/server/test/system/experiments.test.ts b/apps/server/test/system/experiments.test.ts
index f6806306cf..00504041dc 100644
--- a/apps/server/test/system/experiments.test.ts
+++ b/apps/server/test/system/experiments.test.ts
@@ -13,6 +13,7 @@ describe("experiments settings", () => {
const body = systemConfigResponseSchema.parse(await readJson(response));
expect(body.experiments).toEqual({
claudeCodeMockCliTraffic: false,
+ cloudAi: false,
newOnboarding: false,
toolsHub: false,
});
@@ -26,6 +27,7 @@ describe("experiments settings", () => {
headers: { "content-type": "application/json" },
body: JSON.stringify({
claudeCodeMockCliTraffic: true,
+ cloudAi: true,
newOnboarding: true,
toolsHub: true,
}),
@@ -33,11 +35,13 @@ describe("experiments settings", () => {
expect(put.status).toBe(200);
expect(experimentsSchema.parse(await readJson(put))).toEqual({
claudeCodeMockCliTraffic: true,
+ cloudAi: true,
newOnboarding: true,
toolsHub: true,
});
expect(getExperiments(harness.db)).toEqual({
claudeCodeMockCliTraffic: true,
+ cloudAi: true,
newOnboarding: true,
toolsHub: true,
});
@@ -47,6 +51,7 @@ describe("experiments settings", () => {
systemConfigResponseSchema.parse(await readJson(config)).experiments,
).toEqual({
claudeCodeMockCliTraffic: true,
+ cloudAi: true,
newOnboarding: true,
toolsHub: true,
});
@@ -63,6 +68,7 @@ describe("experiments settings", () => {
headers: { "content-type": "application/json" },
body: JSON.stringify({
claudeCodeMockCliTraffic: false,
+ cloudAi: false,
newOnboarding: false,
toolsHub: false,
}),
diff --git a/apps/server/test/threads/thread-runtime-config.test.ts b/apps/server/test/threads/thread-runtime-config.test.ts
index 9d8cf245c5..af436ba495 100644
--- a/apps/server/test/threads/thread-runtime-config.test.ts
+++ b/apps/server/test/threads/thread-runtime-config.test.ts
@@ -849,6 +849,7 @@ describe("thread runtime config", () => {
setExperiments(harness.db, {
claudeCodeMockCliTraffic: true,
+ cloudAi: false,
newOnboarding: false,
toolsHub: false,
});
diff --git a/docs/api_to_audit.md b/docs/api_to_audit.md
index 17b8fc7f01..cdd3c1398d 100644
--- a/docs/api_to_audit.md
+++ b/docs/api_to_audit.md
@@ -44,7 +44,6 @@ Each label is capped at 80 characters and rendered as a truncating segment.
only for non-MCP native plugin tools. Confirm that distinction stays sound
as provider adapters and dynamic-tool provenance evolve.
-
## `experimental_NewThreadComposer` (`@bb/plugin-sdk/app`)
**What it does.** The host-owned new-thread compose surface, the create-side
@@ -213,6 +212,7 @@ reimplementing it, and `indicatorLabel` carries the matching accessible string.
a sidebar of many distinct worktrees does not stampede the git host; and
returning `null` for "lookup failed" (rather than an error) is the right
failure for a row that should simply show nothing.
+
## `app.slots.experimental_threadHeaderAction` (`@bb/plugin-sdk/app`)
**What it does.** Renders a plugin component in the thread header's action row.
@@ -253,3 +253,35 @@ other pane's copy (or release its owned state). The thread-list slot omits it
deliberately: it mounts once, and a crash there should disable it everywhere.
Confirm that split before stabilizing, and decide whether other multi-mount
slots need the same treatment.
+
+## `bb.experimental_registerCloudAiProvider`
+
+**What it does.** Registers a cloud route for bb's small AI tasks (thread-title
+inference, commit-message inference, voice transcription). Per call, when the
+server's `cloudAi` experiment is enabled and the provider's synchronous
+`isAvailable()` is true, the host tries it before the locally configured
+`BB_INFERENCE`/`BB_TRANSCRIPTION` providers; an `ok: false` result falls
+through to those, and host-side timeouts keep the host's existing timeout
+semantics. While the experiment is off, a provider may remain registered but
+is never called. Single slot host-wide: the most recent registration wins,
+and the registration is torn down with the plugin's dispose hooks. The
+contract is result-shaped (no thrown error classes) so it survives plugin
+bundling. Shipped for the builtin Cloud plugin's bb Cloud routing (the
+plugin's compatibility id remains `connect`).
+
+**Audit before stabilizing.**
+
+1. **Registration policy.** Any plugin may currently claim the slot, which
+ reroutes prompt/diff/audio content to plugin-chosen infrastructure. Decide
+ whether the slot needs a permission gate, builtin-only restriction, or a
+ user-visible indicator of which plugin holds it.
+2. **Single-slot semantics.** Last-registration-wins is the simplest rule;
+ confirm it against multiple plugins wanting the slot (priority? explicit
+ user choice?) before freezing.
+3. **Failure vocabulary.** `unauthorized | quota_exhausted | unavailable`
+ drives fallback routing. Confirm the set is sufficient (rate limiting?
+ partial availability per capability?) — codes are additive-only after
+ stabilization.
+4. **Capability granularity.** One provider currently covers completion and
+ transcription together; decide whether providers should be able to offer
+ one without the other.
diff --git a/docs/configuration.md b/docs/configuration.md
index 37f843980d..95bcabc9c8 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -428,12 +428,15 @@ SDK request, plus ephemeral maximize/restore delivery through
target thread is already open in a multi-pane app window; the response reports
how many connected clients received the broadcast.
-## bb connect
-
-`bb connect --code --server https://.getbb.app` pairs this bb
-server for browser access at `.getbb.app` (claim a handle and copy the
-command at https://getbb.app). Remote access is owned by the builtin
-**connect plugin** (`plugins/connect/`): pairing redeems the code and stores
+## bb Cloud (bb connect)
+
+`bb connect --code --server https://.getbb.app` connects this
+bb server to **bb Cloud** — the getbb.app account relationship (claim a handle
+and copy the command at https://getbb.app). Once connected: the bb is
+reachable from any browser at `.getbb.app`, port shares and machine
+pairing work through the account, and AI features run through it (below).
+bb Cloud is provided by the builtin **Cloud plugin** (internal id `connect`,
+implemented in `plugins/connect/`): pairing redeems the code and stores
the durable credential in the plugin's kv storage (in `bb.db`), and the
plugin's background service holds the connect tunnel — dialing the gate,
proxying relayed requests to the server's own loopback (which serves the SPA
@@ -443,9 +446,9 @@ proxying relayed requests to the server's own loopback (which serves the SPA
re-establishes on restart; there is no foreground client. Pair from a machine
without an installed bb via `npx -p bb-app@latest bb connect …`.
`bb connect status` shows the connect state and every share's host and URL;
- `bb connect off` disconnects and clears the pairing. After pairing,
- `bb connect expose ` run from a thread shares that thread environment's
- enrolled host. Server-host URLs remain
+ `bb connect off` unlinks this bb from Cloud and clears the pairing. After
+ pairing, `bb connect expose ` run from a thread shares that thread
+ environment's enrolled host. Server-host URLs remain
`https://--.getbb.app`; other machines use
`https://--.getbb.app` and proxy directly through the
owning daemon. Outside a thread the command defaults to the server host;
@@ -456,18 +459,41 @@ proxying relayed requests to the server's own loopback (which serves the SPA
`url`; `shares --json` also includes the resolved `host`. A machine without
a live Connect enrollment fails fast with instructions to remove and re-add
it in Settings → Machines. Disabling the plugin
- (`bb plugin disable connect`) cuts off all remote access;
- `bb plugin enable connect` restores it.
-
-The tunnel client lives in `plugins/connect/`; the CLI command is proxied to
-the plugin, and Settings → Connect drives the plugin's rpc (including shared
-ports).
+ (`bb plugin disable connect`) cuts off all bb Cloud capabilities — remote
+ access and AI features alike; `bb plugin enable connect` restores them.
+
+When the default-off `cloudAi` experiment is enabled, **AI features** —
+thread-title inference, commit-message inference, and voice transcription —
+can route through the account's bb Cloud AI proxy while connected, with no
+local AI credentials needed. Enable the experiment in Settings → Experiments
+or with `bb settings experiment cloudAi true`. The cloud picks the models;
+`BB_INFERENCE`/`BB_TRANSCRIPTION` govern only the local fallback, which bb uses
+automatically when a cloud call fails. Usage is capped by a generous
+per-account daily cost budget (an abuse ceiling, not a usage limit — normal
+use never approaches it).
+
+The experiment is the outer product gate: while it is off, Cloud AI is hidden
+from the Cloud plugin settings and the server never sends it inference or
+voice requests. Once enabled, Settings → Cloud → Cloud AI and
+`bb connect ai [on|off]` control the paired plugin's inner routing preference;
+both switches must be on for Cloud AI to be used. `bb connect status` includes
+the inner preference. With both enabled, prompts, commit diffs, and voice audio
+transit getbb.app and its upstream model provider; they are processed for the
+request and not stored.
+
+The tunnel client lives in `plugins/connect/`, and the `bb connect` CLI command
+is proxied to the plugin. Settings → Cloud always contains Remote access
+(pairing, URL, QR code, and shared ports) and adds a separate Cloud AI section
+when its experiment is enabled, preserving one account connection underneath.
## Experiments
Experimental surfaces are off by default and can be changed in Settings →
Experiments or with `bb settings experiment `. The
`newOnboarding` experiment exposes the first-run agent and project setup guide.
+The `cloudAi` experiment permits server AI tasks to use a registered cloud
+provider and reveals the Cloud AI section of the builtin Cloud plugin. It does
+not affect Remote access, pairing, or port sharing.
The `toolsHub` experiment exposes Extensions for managing skills and plugins,
while Automations stays in the Plugins section beside threads. The `toolsHub`
gate only controls the UI. Installed skills, automation execution, plugin
diff --git a/packages/connect-client/src/ai.ts b/packages/connect-client/src/ai.ts
new file mode 100644
index 0000000000..3a553e0673
--- /dev/null
+++ b/packages/connect-client/src/ai.ts
@@ -0,0 +1,152 @@
+import { z } from "zod";
+import type { ConnectCredential } from "./credential.js";
+
+// bb Cloud AI proxy calls for a paired bb (`/api/connect/ai/*` on the gate).
+// The client sends a prompt+schema or an audio file — never a model name;
+// models are a gate-side choice so they can change without a client release.
+
+export type ConnectAiErrorCode =
+ | "unauthorized"
+ | "quota_exhausted"
+ | "server_error"
+ | "network"
+ | "invalid_response";
+
+/**
+ * Typed AI-proxy failure. `code` is stable for routing decisions (fall back to
+ * a local provider, latch a dead credential); `message` carries detail for
+ * logs. Aborts are re-thrown untouched so callers keep their own timeout
+ * semantics.
+ */
+export class ConnectAiError extends Error {
+ constructor(
+ readonly code: ConnectAiErrorCode,
+ message: string,
+ readonly status?: number,
+ ) {
+ super(message);
+ this.name = "ConnectAiError";
+ }
+}
+
+const aiInferenceResponseSchema = z.object({
+ value: z.record(z.string(), z.unknown()),
+});
+const aiTranscriptionResponseSchema = z.object({ text: z.string() });
+
+function isAbortError(error: unknown): boolean {
+ return error instanceof DOMException && error.name === "AbortError";
+}
+
+async function postAi(
+ credential: ConnectCredential,
+ path: string,
+ init: RequestInit,
+ fetchImpl: typeof fetch,
+): Promise {
+ const url = `${credential.serverUrl.replace(/\/$/, "")}${path}`;
+ let response: Response;
+ try {
+ response = await fetchImpl(url, {
+ ...init,
+ method: "POST",
+ headers: {
+ ...init.headers,
+ "x-bb-connect-machine": credential.credential,
+ },
+ });
+ } catch (error) {
+ if (isAbortError(error)) throw error;
+ throw new ConnectAiError(
+ "network",
+ error instanceof Error ? error.message : String(error),
+ );
+ }
+ if (response.status === 401 || response.status === 403) {
+ throw new ConnectAiError(
+ "unauthorized",
+ "bb Cloud AI request not authorized",
+ response.status,
+ );
+ }
+ if (response.status === 429) {
+ throw new ConnectAiError(
+ "quota_exhausted",
+ "Daily bb Cloud AI budget reached",
+ response.status,
+ );
+ }
+ if (!response.ok) {
+ throw new ConnectAiError(
+ "server_error",
+ `bb Cloud AI request failed (${response.status})`,
+ response.status,
+ );
+ }
+ return response;
+}
+
+/**
+ * Structured completion through the gate. Returns the raw value for
+ * caller-side schema validation (bb validates against its TypeBox schema).
+ */
+export async function fetchAiInference(
+ credential: ConnectCredential,
+ args: {
+ prompt: string;
+ schema: Record;
+ signal?: AbortSignal;
+ },
+ fetchImpl: typeof fetch = globalThis.fetch,
+): Promise {
+ const response = await postAi(
+ credential,
+ "/api/connect/ai/inference",
+ {
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify({ prompt: args.prompt, schema: args.schema }),
+ ...(args.signal ? { signal: args.signal } : {}),
+ },
+ fetchImpl,
+ );
+ const parsed = aiInferenceResponseSchema.safeParse(
+ await response.json().catch(() => null),
+ );
+ if (!parsed.success) {
+ throw new ConnectAiError(
+ "invalid_response",
+ "bb Cloud AI inference response failed schema validation",
+ );
+ }
+ return parsed.data.value;
+}
+
+/** Voice transcription through the gate (multipart file + optional prompt). */
+export async function fetchAiTranscription(
+ credential: ConnectCredential,
+ args: { file: File; prompt?: string; signal?: AbortSignal },
+ fetchImpl: typeof fetch = globalThis.fetch,
+): Promise {
+ const form = new FormData();
+ form.set("file", args.file, args.file.name);
+ if (args.prompt !== undefined) form.set("prompt", args.prompt);
+ const response = await postAi(
+ credential,
+ "/api/connect/ai/transcription",
+ {
+ body: form,
+ ...(args.signal ? { signal: args.signal } : {}),
+ },
+ fetchImpl,
+ );
+ const parsed = aiTranscriptionResponseSchema.safeParse(
+ await response.json().catch(() => null),
+ );
+ if (!parsed.success) {
+ throw new ConnectAiError(
+ "invalid_response",
+ "bb Cloud AI transcription response failed schema validation",
+ );
+ }
+ return parsed.data.text;
+}
diff --git a/packages/connect-client/src/index.ts b/packages/connect-client/src/index.ts
index 3248e4d174..34f1b20f22 100644
--- a/packages/connect-client/src/index.ts
+++ b/packages/connect-client/src/index.ts
@@ -9,6 +9,12 @@ export {
isRevokedCredentialError,
type ConnectListErrorCode,
} from "./errors.js";
+export {
+ ConnectAiError,
+ fetchAiInference,
+ fetchAiTranscription,
+ type ConnectAiErrorCode,
+} from "./ai.js";
export {
accountServerSchema,
accountServersResponseSchema,
diff --git a/packages/connect-client/test/connect-client.test.ts b/packages/connect-client/test/connect-client.test.ts
index e3e51866e1..218104da01 100644
--- a/packages/connect-client/test/connect-client.test.ts
+++ b/packages/connect-client/test/connect-client.test.ts
@@ -3,6 +3,8 @@ import {
ConnectListError,
ConnectMachineRedeemError,
deriveConnectBaseUrl,
+ fetchAiInference,
+ fetchAiTranscription,
listAccountServers,
redeemMachineCredential,
serverUrlForHandle,
@@ -183,3 +185,108 @@ describe("redeemMachineCredential", () => {
).resolves.toMatchObject({ handle: "laptop" });
});
});
+
+describe("fetchAiInference", () => {
+ it("posts prompt+schema with the credential header and no model name", async () => {
+ const fetchImpl = vi.fn(
+ async (_url: string, _init: RequestInit) =>
+ new Response(JSON.stringify({ value: { title: "Hi" } })),
+ );
+ await expect(
+ fetchAiInference(
+ CREDENTIAL,
+ {
+ prompt: "Summarize",
+ schema: { type: "object", properties: {} },
+ },
+ fetchImpl as unknown as typeof fetch,
+ ),
+ ).resolves.toEqual({ title: "Hi" });
+
+ const [url, init] = fetchImpl.mock.calls[0];
+ expect(url).toBe("https://laptop.getbb.app/api/connect/ai/inference");
+ expect(init.method).toBe("POST");
+ expect(
+ (init.headers as Record)["x-bb-connect-machine"],
+ ).toBe("bbcm_desktop");
+ expect(JSON.parse(String(init.body))).toEqual({
+ prompt: "Summarize",
+ schema: { type: "object", properties: {} },
+ });
+ });
+
+ it("maps statuses to stable codes", async () => {
+ const cases: Array<[number, string]> = [
+ [401, "unauthorized"],
+ [403, "unauthorized"],
+ [429, "quota_exhausted"],
+ [500, "server_error"],
+ ];
+ for (const [status, code] of cases) {
+ await expect(
+ fetchAiInference(
+ CREDENTIAL,
+ { prompt: "p", schema: {} },
+ async () => new Response("{}", { status }),
+ ),
+ ).rejects.toMatchObject({ name: "ConnectAiError", code, status });
+ }
+ });
+
+ it("rejects malformed bodies and network failures with typed codes", async () => {
+ await expect(
+ fetchAiInference(
+ CREDENTIAL,
+ { prompt: "p", schema: {} },
+ async () => new Response(JSON.stringify({ value: "not-an-object" })),
+ ),
+ ).rejects.toMatchObject({ code: "invalid_response" });
+ await expect(
+ fetchAiInference(CREDENTIAL, { prompt: "p", schema: {} }, async () => {
+ throw new Error("socket hang up");
+ }),
+ ).rejects.toMatchObject({ code: "network" });
+ });
+
+ it("re-throws aborts untouched so callers keep timeout semantics", async () => {
+ await expect(
+ fetchAiInference(CREDENTIAL, { prompt: "p", schema: {} }, async () => {
+ throw new DOMException("aborted", "AbortError");
+ }),
+ ).rejects.toMatchObject({ name: "AbortError" });
+ });
+});
+
+describe("fetchAiTranscription", () => {
+ it("posts multipart with filename and optional prompt", async () => {
+ const fetchImpl = vi.fn(
+ async (_url: string, _init: RequestInit) =>
+ new Response(JSON.stringify({ text: "hello" })),
+ );
+ const file = new File(["audio"], "recording.webm", { type: "audio/webm" });
+ await expect(
+ fetchAiTranscription(
+ CREDENTIAL,
+ { file, prompt: "ctx" },
+ fetchImpl as unknown as typeof fetch,
+ ),
+ ).resolves.toBe("hello");
+
+ const [url, init] = fetchImpl.mock.calls[0];
+ expect(url).toBe("https://laptop.getbb.app/api/connect/ai/transcription");
+ const form = init.body as FormData;
+ expect((form.get("file") as File).name).toBe("recording.webm");
+ expect(form.get("prompt")).toBe("ctx");
+ });
+
+ it("maps a 429 to quota_exhausted", async () => {
+ const file = new File(["audio"], "recording.webm", { type: "audio/webm" });
+ await expect(
+ fetchAiTranscription(
+ CREDENTIAL,
+ { file },
+ async () => new Response("{}", { status: 429 }),
+ ),
+ ).rejects.toMatchObject({ code: "quota_exhausted" });
+ });
+});
diff --git a/packages/connect-db/migrations/0006_ai_usage.sql b/packages/connect-db/migrations/0006_ai_usage.sql
new file mode 100644
index 0000000000..83a403ab15
--- /dev/null
+++ b/packages/connect-db/migrations/0006_ai_usage.sql
@@ -0,0 +1,7 @@
+CREATE TABLE `ai_usage` (
+ `user_id` text NOT NULL,
+ `day` text NOT NULL,
+ `cost_micro_usd` integer DEFAULT 0 NOT NULL,
+ PRIMARY KEY(`user_id`, `day`),
+ FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
+);
diff --git a/packages/connect-db/migrations/meta/0006_snapshot.json b/packages/connect-db/migrations/meta/0006_snapshot.json
new file mode 100644
index 0000000000..826087bc60
--- /dev/null
+++ b/packages/connect-db/migrations/meta/0006_snapshot.json
@@ -0,0 +1,920 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "863cc45a-b627-4837-9f67-071eceb5a2c3",
+ "prevId": "7ff3fbcf-840d-4547-8d1f-94d571998114",
+ "tables": {
+ "account": {
+ "name": "account",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "account_user_id_idx": {
+ "name": "account_user_id_idx",
+ "columns": [
+ "user_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "account_user_id_user_id_fk": {
+ "name": "account_user_id_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "ai_usage": {
+ "name": "ai_usage",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "day": {
+ "name": "day",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cost_micro_usd": {
+ "name": "cost_micro_usd",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "ai_usage_user_id_user_id_fk": {
+ "name": "ai_usage_user_id_user_id_fk",
+ "tableFrom": "ai_usage",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "ai_usage_user_id_day_pk": {
+ "columns": [
+ "user_id",
+ "day"
+ ],
+ "name": "ai_usage_user_id_day_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "audit_log": {
+ "name": "audit_log",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "detail": {
+ "name": "detail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(unixepoch() * 1000)"
+ }
+ },
+ "indexes": {
+ "audit_log_user_id_idx": {
+ "name": "audit_log_user_id_idx",
+ "columns": [
+ "user_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "audit_log_user_id_user_id_fk": {
+ "name": "audit_log_user_id_user_id_fk",
+ "tableFrom": "audit_log",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "connect_code": {
+ "name": "connect_code",
+ "columns": {
+ "code": {
+ "name": "code",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "purpose": {
+ "name": "purpose",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "consumed_at": {
+ "name": "consumed_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "connect_code_user_id_idx": {
+ "name": "connect_code_user_id_idx",
+ "columns": [
+ "user_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "connect_code_user_id_user_id_fk": {
+ "name": "connect_code_user_id_user_id_fk",
+ "tableFrom": "connect_code",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "connect_code_server_id_server_id_fk": {
+ "name": "connect_code_server_id_server_id_fk",
+ "tableFrom": "connect_code",
+ "tableTo": "server",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "label_claim": {
+ "name": "label_claim",
+ "columns": {
+ "label": {
+ "name": "label",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "owner_id": {
+ "name": "owner_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "generation": {
+ "name": "generation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "label_claim_user_id_idx": {
+ "name": "label_claim_user_id_idx",
+ "columns": [
+ "user_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "label_claim_user_id_user_id_fk": {
+ "name": "label_claim_user_id_user_id_fk",
+ "tableFrom": "label_claim",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "machine": {
+ "name": "machine",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "subdomain": {
+ "name": "subdomain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "credential_hash": {
+ "name": "credential_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "last_seen_at": {
+ "name": "last_seen_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "machine_subdomain_unique": {
+ "name": "machine_subdomain_unique",
+ "columns": [
+ "subdomain"
+ ],
+ "isUnique": true
+ },
+ "machine_user_id_idx": {
+ "name": "machine_user_id_idx",
+ "columns": [
+ "user_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "machine_user_id_user_id_fk": {
+ "name": "machine_user_id_user_id_fk",
+ "tableFrom": "machine",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "profile": {
+ "name": "profile",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "handle": {
+ "name": "handle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "profile_handle_unique": {
+ "name": "profile_handle_unique",
+ "columns": [
+ "handle"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "profile_user_id_user_id_fk": {
+ "name": "profile_user_id_user_id_fk",
+ "tableFrom": "profile",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "server": {
+ "name": "server",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'default'"
+ },
+ "subdomain": {
+ "name": "subdomain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "credential_hash": {
+ "name": "credential_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "version": {
+ "name": "version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_seen_at": {
+ "name": "last_seen_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "server_subdomain_unique": {
+ "name": "server_subdomain_unique",
+ "columns": [
+ "subdomain"
+ ],
+ "isUnique": true
+ },
+ "server_user_name_idx": {
+ "name": "server_user_name_idx",
+ "columns": [
+ "user_id",
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "server_user_id_user_id_fk": {
+ "name": "server_user_id_user_id_fk",
+ "tableFrom": "server",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "session": {
+ "name": "session",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "session_token_unique": {
+ "name": "session_token_unique",
+ "columns": [
+ "token"
+ ],
+ "isUnique": true
+ },
+ "session_user_id_idx": {
+ "name": "session_user_id_idx",
+ "columns": [
+ "user_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "session_user_id_user_id_fk": {
+ "name": "session_user_id_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user": {
+ "name": "user",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "github_login": {
+ "name": "github_login",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "columns": [
+ "email"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "verification": {
+ "name": "verification",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "verification_identifier_idx": {
+ "name": "verification_identifier_idx",
+ "columns": [
+ "identifier"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+}
\ No newline at end of file
diff --git a/packages/connect-db/migrations/meta/_journal.json b/packages/connect-db/migrations/meta/_journal.json
index 1b9234becf..4c9416b652 100644
--- a/packages/connect-db/migrations/meta/_journal.json
+++ b/packages/connect-db/migrations/meta/_journal.json
@@ -43,6 +43,13 @@
"when": 1783989596863,
"tag": "0005_label_claim_triggers",
"breakpoints": true
+ },
+ {
+ "idx": 6,
+ "version": "6",
+ "when": 1785967822801,
+ "tag": "0006_ai_usage",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/packages/connect-db/src/constants.ts b/packages/connect-db/src/constants.ts
index 7e8c168844..9e61a27cd2 100644
--- a/packages/connect-db/src/constants.ts
+++ b/packages/connect-db/src/constants.ts
@@ -77,6 +77,21 @@ export const SERVER_OFFLINE_AFTER_MS = 90 * 1000;
/** Token prefixes (mirrors bb's host-key convention; distinct namespaces). */
export const CLOUD_PAT_PREFIX = "bbc_";
+/**
+ * bb Cloud AI proxy policy. The daily budget is an abuse ceiling, not a usage
+ * limit: it is sized an order of magnitude above heavy legitimate use (a big
+ * day of titles, commit messages, and voice notes costs well under $0.50) so
+ * no real user ever hits it, while bounding worst-case spend from a leaked
+ * credential or runaway loop. Usage is metered as estimated upstream cost in
+ * micro-USD; the per-request estimate rates live in the gate worker beside the
+ * model choices.
+ */
+export const AI_DAILY_BUDGET_MICROUSD = 2_000_000;
+export const AI_INFERENCE_MAX_PROMPT_BYTES = 64 * 1024;
+export const AI_INFERENCE_MAX_SCHEMA_BYTES = 16 * 1024;
+export const AI_TRANSCRIPTION_MAX_FILE_BYTES = 25 * 1024 * 1024;
+export const AI_TRANSCRIPTION_MAX_PROMPT_BYTES = 4 * 1024;
+
export type HandleValidationError =
| "too-short"
| "too-long"
diff --git a/packages/connect-db/src/schema.ts b/packages/connect-db/src/schema.ts
index b350ca3a7f..d82ba07466 100644
--- a/packages/connect-db/src/schema.ts
+++ b/packages/connect-db/src/schema.ts
@@ -14,6 +14,7 @@ import { sql } from "drizzle-orm";
import {
index,
integer,
+ primaryKey,
sqliteTable,
text,
uniqueIndex,
@@ -221,6 +222,25 @@ export const machine = sqliteTable(
(table) => [index("machine_user_id_idx").on(table.userId)],
);
+/**
+ * Daily bb Cloud AI usage per account, metered as estimated upstream cost in
+ * micro-USD (the abuse ceiling is dollars, so dollars are what we count). One
+ * row per user per UTC day (`day` is "YYYY-MM-DD"); rollover is implicit via
+ * the primary key, no sweeper. Metering only — request/response content is
+ * never stored.
+ */
+export const aiUsage = sqliteTable(
+ "ai_usage",
+ {
+ userId: text("user_id")
+ .notNull()
+ .references(() => user.id, { onDelete: "cascade" }),
+ day: text("day").notNull(),
+ costMicroUsd: integer("cost_micro_usd").notNull().default(0),
+ },
+ (table) => [primaryKey({ columns: [table.userId, table.day] })],
+);
+
/** Append-only audit of security-relevant events (connect, revoke, pair). */
export const auditLog = sqliteTable(
"audit_log",
@@ -254,5 +274,6 @@ export const schema = {
server,
machine,
connectCode,
+ aiUsage,
auditLog,
};
diff --git a/packages/db/drizzle/0088_minor_juggernaut.sql b/packages/db/drizzle/0088_minor_juggernaut.sql
new file mode 100644
index 0000000000..db86b945e6
--- /dev/null
+++ b/packages/db/drizzle/0088_minor_juggernaut.sql
@@ -0,0 +1 @@
+ALTER TABLE `system_experiments` ADD `cloud_ai` integer DEFAULT false NOT NULL;
\ No newline at end of file
diff --git a/packages/db/drizzle/meta/0088_snapshot.json b/packages/db/drizzle/meta/0088_snapshot.json
new file mode 100644
index 0000000000..34395c263c
--- /dev/null
+++ b/packages/db/drizzle/meta/0088_snapshot.json
@@ -0,0 +1,3436 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "293c24ad-bd5d-41ad-bb55-0c114528b4be",
+ "prevId": "3a348e16-0592-4d7b-8f90-b6afcafffe48",
+ "tables": {
+ "app_settings": {
+ "name": "app_settings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "caffeinate": {
+ "name": "caffeinate",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "show_keyboard_hints": {
+ "name": "show_keyboard_hints",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "steer_active_thread_on_enter": {
+ "name": "steer_active_thread_on_enter",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "show_unhandled_provider_events": {
+ "name": "show_unhandled_provider_events",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "codex_memory_enabled": {
+ "name": "codex_memory_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "claude_code_memory_enabled": {
+ "name": "claude_code_memory_enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "codex_subagents_disabled": {
+ "name": "codex_subagents_disabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "claude_code_subagents_disabled": {
+ "name": "claude_code_subagents_disabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "claude_code_workflows_disabled": {
+ "name": "claude_code_workflows_disabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "keybinding_overrides": {
+ "name": "keybinding_overrides",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'[]'"
+ },
+ "onboarding_completed_at": {
+ "name": "onboarding_completed_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "app_theme": {
+ "name": "app_theme",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "theme_id": {
+ "name": "theme_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "favicon_color": {
+ "name": "favicon_color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'default'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "apikey": {
+ "name": "apikey",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "start": {
+ "name": "start",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "prefix": {
+ "name": "prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "referenceId": {
+ "name": "referenceId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "refillInterval": {
+ "name": "refillInterval",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "refillAmount": {
+ "name": "refillAmount",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "lastRefillAt": {
+ "name": "lastRefillAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "rateLimitEnabled": {
+ "name": "rateLimitEnabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "rateLimitTimeWindow": {
+ "name": "rateLimitTimeWindow",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "rateLimitMax": {
+ "name": "rateLimitMax",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "requestCount": {
+ "name": "requestCount",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "remaining": {
+ "name": "remaining",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "lastRequest": {
+ "name": "lastRequest",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "expiresAt": {
+ "name": "expiresAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "permissions": {
+ "name": "permissions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "configId": {
+ "name": "configId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "apikey_key_unique": {
+ "name": "apikey_key_unique",
+ "columns": [
+ "key"
+ ],
+ "isUnique": true
+ },
+ "apikey_reference_id_idx": {
+ "name": "apikey_reference_id_idx",
+ "columns": [
+ "referenceId"
+ ],
+ "isUnique": false
+ },
+ "apikey_config_id_idx": {
+ "name": "apikey_config_id_idx",
+ "columns": [
+ "configId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "apikey_referenceId_user_id_fk": {
+ "name": "apikey_referenceId_user_id_fk",
+ "tableFrom": "apikey",
+ "tableTo": "user",
+ "columnsFrom": [
+ "referenceId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user": {
+ "name": "user",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "emailVerified": {
+ "name": "emailVerified",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "columns": [
+ "email"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "environments": {
+ "name": "environments",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "managed": {
+ "name": "managed",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "is_git_repo": {
+ "name": "is_git_repo",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "is_worktree": {
+ "name": "is_worktree",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "branch_name": {
+ "name": "branch_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "base_branch": {
+ "name": "base_branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "default_branch": {
+ "name": "default_branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "merge_base_branch": {
+ "name": "merge_base_branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "destroy_attempt_id": {
+ "name": "destroy_attempt_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "workspace_provision_type": {
+ "name": "workspace_provision_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'provisioning'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "environments_project_host_path_idx": {
+ "name": "environments_project_host_path_idx",
+ "columns": [
+ "project_id",
+ "host_id",
+ "path"
+ ],
+ "isUnique": true
+ },
+ "environments_host_path_lookup_idx": {
+ "name": "environments_host_path_lookup_idx",
+ "columns": [
+ "host_id",
+ "path"
+ ],
+ "isUnique": false
+ },
+ "environments_project_idx": {
+ "name": "environments_project_idx",
+ "columns": [
+ "project_id"
+ ],
+ "isUnique": false
+ },
+ "environments_status_idx": {
+ "name": "environments_status_idx",
+ "columns": [
+ "status"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "environments_project_id_projects_id_fk": {
+ "name": "environments_project_id_projects_id_fk",
+ "tableFrom": "environments",
+ "tableTo": "projects",
+ "columnsFrom": [
+ "project_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "environments_host_id_hosts_id_fk": {
+ "name": "environments_host_id_hosts_id_fk",
+ "tableFrom": "environments",
+ "tableTo": "hosts",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "events": {
+ "name": "events",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "scope_kind": {
+ "name": "scope_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "turn_id": {
+ "name": "turn_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "provider_thread_id": {
+ "name": "provider_thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sequence": {
+ "name": "sequence",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "item_id": {
+ "name": "item_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "item_kind": {
+ "name": "item_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "data": {
+ "name": "data",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'{}'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "events_thread_sequence_idx": {
+ "name": "events_thread_sequence_idx",
+ "columns": [
+ "thread_id",
+ "sequence"
+ ],
+ "isUnique": true
+ },
+ "events_thread_type_item_kind_sequence_idx": {
+ "name": "events_thread_type_item_kind_sequence_idx",
+ "columns": [
+ "thread_id",
+ "type",
+ "item_kind",
+ "sequence"
+ ],
+ "isUnique": false
+ },
+ "events_thread_type_sequence_idx": {
+ "name": "events_thread_type_sequence_idx",
+ "columns": [
+ "thread_id",
+ "type",
+ "sequence"
+ ],
+ "isUnique": false
+ },
+ "events_thread_turn_type_item_sequence_idx": {
+ "name": "events_thread_turn_type_item_sequence_idx",
+ "columns": [
+ "thread_id",
+ "turn_id",
+ "type",
+ "item_id",
+ "sequence"
+ ],
+ "isUnique": false
+ },
+ "events_environment_idx": {
+ "name": "events_environment_idx",
+ "columns": [
+ "environment_id"
+ ],
+ "isUnique": false
+ },
+ "events_completed_item_truncation_idx": {
+ "name": "events_completed_item_truncation_idx",
+ "columns": [
+ "item_kind",
+ "created_at",
+ "id"
+ ],
+ "isUnique": false,
+ "where": "\"events\".\"type\" = 'item/completed'"
+ }
+ },
+ "foreignKeys": {
+ "events_thread_id_threads_id_fk": {
+ "name": "events_thread_id_threads_id_fk",
+ "tableFrom": "events",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "events_environment_id_environments_id_fk": {
+ "name": "events_environment_id_environments_id_fk",
+ "tableFrom": "events",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {
+ "events_scope_shape_check": {
+ "name": "events_scope_shape_check",
+ "value": "(\n (\"events\".\"scope_kind\" = 'turn' AND \"events\".\"turn_id\" IS NOT NULL)\n OR\n (\"events\".\"scope_kind\" = 'thread' AND \"events\".\"turn_id\" IS NULL)\n )"
+ }
+ }
+ },
+ "host_daemon_sessions": {
+ "name": "host_daemon_sessions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "instance_id": {
+ "name": "instance_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_name": {
+ "name": "host_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_type": {
+ "name": "host_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "data_dir": {
+ "name": "data_dir",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "protocol_version": {
+ "name": "protocol_version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "heartbeat_interval_ms": {
+ "name": "heartbeat_interval_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "lease_timeout_ms": {
+ "name": "lease_timeout_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "lease_expires_at": {
+ "name": "lease_expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "closed_at": {
+ "name": "closed_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "close_reason": {
+ "name": "close_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "host_daemon_sessions_host_status_idx": {
+ "name": "host_daemon_sessions_host_status_idx",
+ "columns": [
+ "host_id",
+ "status"
+ ],
+ "isUnique": false
+ },
+ "host_daemon_sessions_host_latest_idx": {
+ "name": "host_daemon_sessions_host_latest_idx",
+ "columns": [
+ "host_id",
+ "updated_at",
+ "created_at",
+ "id"
+ ],
+ "isUnique": false
+ },
+ "host_daemon_sessions_closed_prune_idx": {
+ "name": "host_daemon_sessions_closed_prune_idx",
+ "columns": [
+ "status",
+ "closed_at",
+ "id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "host_daemon_sessions_host_id_hosts_id_fk": {
+ "name": "host_daemon_sessions_host_id_hosts_id_fk",
+ "tableFrom": "host_daemon_sessions",
+ "tableTo": "hosts",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "hosts": {
+ "name": "hosts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "connect_machine_id": {
+ "name": "connect_machine_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "max_permission_mode": {
+ "name": "max_permission_mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'full'"
+ },
+ "destroyed_at": {
+ "name": "destroyed_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_seen_at": {
+ "name": "last_seen_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_rejected_protocol_version": {
+ "name": "last_rejected_protocol_version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "hosts_last_seen_idx": {
+ "name": "hosts_last_seen_idx",
+ "columns": [
+ "last_seen_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "plugins": {
+ "name": "plugins",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provenance": {
+ "name": "provenance",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'direct'"
+ },
+ "catalog_entry_id": {
+ "name": "catalog_entry_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "source_kind": {
+ "name": "source_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'path'"
+ },
+ "source_path": {
+ "name": "source_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "source_builtin_name": {
+ "name": "source_builtin_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "source_npm_package": {
+ "name": "source_npm_package",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "source_npm_registry": {
+ "name": "source_npm_registry",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "source_npm_requested_spec": {
+ "name": "source_npm_requested_spec",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "source_npm_spec_kind": {
+ "name": "source_npm_spec_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "source_git_url": {
+ "name": "source_git_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "source_git_subdirectory": {
+ "name": "source_git_subdirectory",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "source_git_requested_ref": {
+ "name": "source_git_requested_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "source_git_ref_kind": {
+ "name": "source_git_ref_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "npm_resolved_version": {
+ "name": "npm_resolved_version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "npm_integrity": {
+ "name": "npm_integrity",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "git_resolved_commit": {
+ "name": "git_resolved_commit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_update_check_at": {
+ "name": "last_update_check_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "available_compatible_version": {
+ "name": "available_compatible_version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "newest_incompatible_version": {
+ "name": "newest_incompatible_version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "update_status_detail": {
+ "name": "update_status_detail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_failure_version": {
+ "name": "last_failure_version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_failure_at": {
+ "name": "last_failure_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_failure_detail": {
+ "name": "last_failure_detail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "active_artifact_id": {
+ "name": "active_artifact_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "normalization_version": {
+ "name": "normalization_version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "root_dir": {
+ "name": "root_dir",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "version": {
+ "name": "version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "removed_at": {
+ "name": "removed_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "installed_at": {
+ "name": "installed_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "plugins_active_artifact_id_plugin_artifacts_id_fk": {
+ "name": "plugins_active_artifact_id_plugin_artifacts_id_fk",
+ "tableFrom": "plugins",
+ "tableTo": "plugin_artifacts",
+ "columnsFrom": [
+ "active_artifact_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "maintenance_scan_cursors": {
+ "name": "maintenance_scan_cursors",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "policy": {
+ "name": "policy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "item_kind": {
+ "name": "item_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "output_path": {
+ "name": "output_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "last_created_at": {
+ "name": "last_created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "last_event_id": {
+ "name": "last_event_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "maintenance_scan_cursors_path_idx": {
+ "name": "maintenance_scan_cursors_path_idx",
+ "columns": [
+ "policy",
+ "version",
+ "item_kind",
+ "output_path"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "pending_interactions": {
+ "name": "pending_interactions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "origin_kind": {
+ "name": "origin_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'provider'"
+ },
+ "turn_id": {
+ "name": "turn_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "provider_thread_id": {
+ "name": "provider_thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "provider_request_id": {
+ "name": "provider_request_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "plugin_id": {
+ "name": "plugin_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "renderer_id": {
+ "name": "renderer_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "payload": {
+ "name": "payload",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "resolution": {
+ "name": "resolution",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status_reason": {
+ "name": "status_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "pending_interactions_provider_request_idx": {
+ "name": "pending_interactions_provider_request_idx",
+ "columns": [
+ "provider_id",
+ "provider_thread_id",
+ "provider_request_id"
+ ],
+ "isUnique": true
+ },
+ "pending_interactions_thread_created_idx": {
+ "name": "pending_interactions_thread_created_idx",
+ "columns": [
+ "thread_id",
+ "created_at"
+ ],
+ "isUnique": false
+ },
+ "pending_interactions_thread_status_created_idx": {
+ "name": "pending_interactions_thread_status_created_idx",
+ "columns": [
+ "thread_id",
+ "status",
+ "created_at"
+ ],
+ "isUnique": false
+ },
+ "pending_interactions_status_created_idx": {
+ "name": "pending_interactions_status_created_idx",
+ "columns": [
+ "status",
+ "created_at"
+ ],
+ "isUnique": false
+ },
+ "pending_interactions_plugin_status_created_idx": {
+ "name": "pending_interactions_plugin_status_created_idx",
+ "columns": [
+ "plugin_id",
+ "status",
+ "created_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "pending_interactions_thread_id_threads_id_fk": {
+ "name": "pending_interactions_thread_id_threads_id_fk",
+ "tableFrom": "pending_interactions",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "plugin_artifacts": {
+ "name": "plugin_artifacts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "plugin_id": {
+ "name": "plugin_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "source_kind": {
+ "name": "source_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "npm_resolved_version": {
+ "name": "npm_resolved_version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "git_resolved_commit": {
+ "name": "git_resolved_commit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "integrity": {
+ "name": "integrity",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "content_hash": {
+ "name": "content_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "validation_result": {
+ "name": "validation_result",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "validated_at": {
+ "name": "validated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "plugin_artifacts_plugin_idx": {
+ "name": "plugin_artifacts_plugin_idx",
+ "columns": [
+ "plugin_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "plugin_kv": {
+ "name": "plugin_kv",
+ "columns": {
+ "plugin_id": {
+ "name": "plugin_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "plugin_kv_plugin_id_key_pk": {
+ "columns": [
+ "plugin_id",
+ "key"
+ ],
+ "name": "plugin_kv_plugin_id_key_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "plugin_schedules": {
+ "name": "plugin_schedules",
+ "columns": {
+ "plugin_id": {
+ "name": "plugin_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cron": {
+ "name": "cron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "next_run_at": {
+ "name": "next_run_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "last_run_at": {
+ "name": "last_run_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_status": {
+ "name": "last_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "plugin_schedules_plugin_id_name_pk": {
+ "columns": [
+ "plugin_id",
+ "name"
+ ],
+ "name": "plugin_schedules_plugin_id_name_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "plugin_settings": {
+ "name": "plugin_settings",
+ "columns": {
+ "plugin_id": {
+ "name": "plugin_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "plugin_settings_plugin_id_key_pk": {
+ "columns": [
+ "plugin_id",
+ "key"
+ ],
+ "name": "plugin_settings_plugin_id_key_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "plugin_state_snapshots": {
+ "name": "plugin_state_snapshots",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "plugin_id": {
+ "name": "plugin_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "from_artifact_id": {
+ "name": "from_artifact_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "to_artifact_id": {
+ "name": "to_artifact_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "snapshot_path": {
+ "name": "snapshot_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "database_path": {
+ "name": "database_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "state_path": {
+ "name": "state_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "secrets_path": {
+ "name": "secrets_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "registration_path": {
+ "name": "registration_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "rollback_candidate_version": {
+ "name": "rollback_candidate_version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "rollback_source_fingerprint": {
+ "name": "rollback_source_fingerprint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "rollback_bb_version": {
+ "name": "rollback_bb_version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "rollback_sdk_version": {
+ "name": "rollback_sdk_version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "rollback_detail": {
+ "name": "rollback_detail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "retained_until": {
+ "name": "retained_until",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "plugin_state_snapshots_plugin_idx": {
+ "name": "plugin_state_snapshots_plugin_idx",
+ "columns": [
+ "plugin_id"
+ ],
+ "isUnique": false
+ },
+ "plugin_state_snapshots_retention_idx": {
+ "name": "plugin_state_snapshots_retention_idx",
+ "columns": [
+ "retained_until"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "project_execution_defaults": {
+ "name": "project_execution_defaults",
+ "columns": {
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "service_tier": {
+ "name": "service_tier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "reasoning_level": {
+ "name": "reasoning_level",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "permission_mode": {
+ "name": "permission_mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "project_execution_defaults_project_idx": {
+ "name": "project_execution_defaults_project_idx",
+ "columns": [
+ "project_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "project_execution_defaults_project_id_projects_id_fk": {
+ "name": "project_execution_defaults_project_id_projects_id_fk",
+ "tableFrom": "project_execution_defaults",
+ "tableTo": "projects",
+ "columnsFrom": [
+ "project_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "project_sources": {
+ "name": "project_sources",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "is_default": {
+ "name": "is_default",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "project_sources_project_idx": {
+ "name": "project_sources_project_idx",
+ "columns": [
+ "project_id"
+ ],
+ "isUnique": false
+ },
+ "project_sources_host_idx": {
+ "name": "project_sources_host_idx",
+ "columns": [
+ "host_id"
+ ],
+ "isUnique": false
+ },
+ "project_sources_project_host_idx": {
+ "name": "project_sources_project_host_idx",
+ "columns": [
+ "project_id",
+ "host_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "project_sources_project_id_projects_id_fk": {
+ "name": "project_sources_project_id_projects_id_fk",
+ "tableFrom": "project_sources",
+ "tableTo": "projects",
+ "columnsFrom": [
+ "project_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "project_sources_host_id_hosts_id_fk": {
+ "name": "project_sources_host_id_hosts_id_fk",
+ "tableFrom": "project_sources",
+ "tableTo": "hosts",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {
+ "project_sources_shape_check": {
+ "name": "project_sources_shape_check",
+ "value": "(\n \"project_sources\".\"type\" = 'local_path' AND \"project_sources\".\"host_id\" IS NOT NULL AND \"project_sources\".\"path\" IS NOT NULL\n )"
+ }
+ }
+ },
+ "projects": {
+ "name": "projects",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'standard'"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "git_remote_url": {
+ "name": "git_remote_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sort_key": {
+ "name": "sort_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'V'"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "projects_updated_idx": {
+ "name": "projects_updated_idx",
+ "columns": [
+ "updated_at"
+ ],
+ "isUnique": false
+ },
+ "projects_deleted_idx": {
+ "name": "projects_deleted_idx",
+ "columns": [
+ "deleted_at"
+ ],
+ "isUnique": false
+ },
+ "projects_sort_idx": {
+ "name": "projects_sort_idx",
+ "columns": [
+ "sort_key",
+ "id"
+ ],
+ "isUnique": false
+ },
+ "projects_personal_singleton_idx": {
+ "name": "projects_personal_singleton_idx",
+ "columns": [
+ "kind"
+ ],
+ "isUnique": true,
+ "where": "\"projects\".\"kind\" = 'personal'"
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "prompt_history_entries": {
+ "name": "prompt_history_entries",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "request_sequence": {
+ "name": "request_sequence",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "input": {
+ "name": "input",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "prompt_history_entries_thread_request_idx": {
+ "name": "prompt_history_entries_thread_request_idx",
+ "columns": [
+ "thread_id",
+ "request_sequence"
+ ],
+ "isUnique": true
+ },
+ "prompt_history_entries_project_scope_created_idx": {
+ "name": "prompt_history_entries_project_scope_created_idx",
+ "columns": [
+ "project_id",
+ "scope",
+ "created_at",
+ "request_sequence",
+ "id"
+ ],
+ "isUnique": false
+ },
+ "prompt_history_entries_thread_scope_created_idx": {
+ "name": "prompt_history_entries_thread_scope_created_idx",
+ "columns": [
+ "thread_id",
+ "scope",
+ "created_at",
+ "request_sequence",
+ "id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "prompt_history_entries_project_id_projects_id_fk": {
+ "name": "prompt_history_entries_project_id_projects_id_fk",
+ "tableFrom": "prompt_history_entries",
+ "tableTo": "projects",
+ "columnsFrom": [
+ "project_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "prompt_history_entries_thread_id_threads_id_fk": {
+ "name": "prompt_history_entries_thread_id_threads_id_fk",
+ "tableFrom": "prompt_history_entries",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "queued_thread_messages": {
+ "name": "queued_thread_messages",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sender_thread_id": {
+ "name": "sender_thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "reasoning_level": {
+ "name": "reasoning_level",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "permission_mode": {
+ "name": "permission_mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "service_tier": {
+ "name": "service_tier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "group_with_next": {
+ "name": "group_with_next",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "claimed_at": {
+ "name": "claimed_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "claim_token": {
+ "name": "claim_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sort_key": {
+ "name": "sort_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "queued_thread_messages_thread_created_idx": {
+ "name": "queued_thread_messages_thread_created_idx",
+ "columns": [
+ "thread_id",
+ "created_at",
+ "id"
+ ],
+ "isUnique": false
+ },
+ "queued_thread_messages_thread_sort_idx": {
+ "name": "queued_thread_messages_thread_sort_idx",
+ "columns": [
+ "thread_id",
+ "sort_key",
+ "id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "queued_thread_messages_thread_id_threads_id_fk": {
+ "name": "queued_thread_messages_thread_id_threads_id_fk",
+ "tableFrom": "queued_thread_messages",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "system_experiments": {
+ "name": "system_experiments",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "claude_code_mock_cli_traffic": {
+ "name": "claude_code_mock_cli_traffic",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cloud_ai": {
+ "name": "cloud_ai",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "new_onboarding": {
+ "name": "new_onboarding",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "tools_hub": {
+ "name": "tools_hub",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "terminal_sessions": {
+ "name": "terminal_sessions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "daemon_session_id": {
+ "name": "daemon_session_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "initial_cwd": {
+ "name": "initial_cwd",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cols": {
+ "name": "cols",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "rows": {
+ "name": "rows",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "exit_code": {
+ "name": "exit_code",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "close_reason": {
+ "name": "close_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "last_user_input_at": {
+ "name": "last_user_input_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "terminal_sessions_thread_status_updated_idx": {
+ "name": "terminal_sessions_thread_status_updated_idx",
+ "columns": [
+ "thread_id",
+ "status",
+ "updated_at"
+ ],
+ "isUnique": false
+ },
+ "terminal_sessions_environment_status_idx": {
+ "name": "terminal_sessions_environment_status_idx",
+ "columns": [
+ "environment_id",
+ "status"
+ ],
+ "isUnique": false
+ },
+ "terminal_sessions_host_status_idx": {
+ "name": "terminal_sessions_host_status_idx",
+ "columns": [
+ "host_id",
+ "status"
+ ],
+ "isUnique": false
+ },
+ "terminal_sessions_daemon_session_idx": {
+ "name": "terminal_sessions_daemon_session_idx",
+ "columns": [
+ "daemon_session_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "terminal_sessions_thread_id_threads_id_fk": {
+ "name": "terminal_sessions_thread_id_threads_id_fk",
+ "tableFrom": "terminal_sessions",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "terminal_sessions_environment_id_environments_id_fk": {
+ "name": "terminal_sessions_environment_id_environments_id_fk",
+ "tableFrom": "terminal_sessions",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "terminal_sessions_host_id_hosts_id_fk": {
+ "name": "terminal_sessions_host_id_hosts_id_fk",
+ "tableFrom": "terminal_sessions",
+ "tableTo": "hosts",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "terminal_sessions_daemon_session_id_host_daemon_sessions_id_fk": {
+ "name": "terminal_sessions_daemon_session_id_host_daemon_sessions_id_fk",
+ "tableFrom": "terminal_sessions",
+ "tableTo": "host_daemon_sessions",
+ "columnsFrom": [
+ "daemon_session_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "thread_dynamic_context_file_states": {
+ "name": "thread_dynamic_context_file_states",
+ "columns": {
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "file_key": {
+ "name": "file_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content_status": {
+ "name": "content_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content_hash": {
+ "name": "content_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "shown_at": {
+ "name": "shown_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "thread_dynamic_context_file_states_thread_file_idx": {
+ "name": "thread_dynamic_context_file_states_thread_file_idx",
+ "columns": [
+ "thread_id",
+ "file_key"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "thread_dynamic_context_file_states_thread_id_threads_id_fk": {
+ "name": "thread_dynamic_context_file_states_thread_id_threads_id_fk",
+ "tableFrom": "thread_dynamic_context_file_states",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "thread_search_segments": {
+ "name": "thread_search_segments",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "source_kind": {
+ "name": "source_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "source_key": {
+ "name": "source_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "source_seq": {
+ "name": "source_seq",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "thread_search_segments_source_idx": {
+ "name": "thread_search_segments_source_idx",
+ "columns": [
+ "thread_id",
+ "source_kind",
+ "source_key"
+ ],
+ "isUnique": true
+ },
+ "thread_search_segments_thread_idx": {
+ "name": "thread_search_segments_thread_idx",
+ "columns": [
+ "thread_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "thread_search_segments_thread_id_threads_id_fk": {
+ "name": "thread_search_segments_thread_id_threads_id_fk",
+ "tableFrom": "thread_search_segments",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "thread_sections": {
+ "name": "thread_sections",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "thread_sections_name_idx": {
+ "name": "thread_sections_name_idx",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "thread_tabs": {
+ "name": "thread_tabs",
+ "columns": {
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "tabs_json": {
+ "name": "tabs_json",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "revision": {
+ "name": "revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "thread_tabs_thread_id_threads_id_fk": {
+ "name": "thread_tabs_thread_id_threads_id_fk",
+ "tableFrom": "thread_tabs",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "threads": {
+ "name": "threads",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "model_override": {
+ "name": "model_override",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "reasoning_level_override": {
+ "name": "reasoning_level_override",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "title_fallback": {
+ "name": "title_fallback",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "section_id": {
+ "name": "section_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'starting'"
+ },
+ "parent_thread_id": {
+ "name": "parent_thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "source_thread_id": {
+ "name": "source_thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "origin_kind": {
+ "name": "origin_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "child_origin": {
+ "name": "child_origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "origin_plugin_id": {
+ "name": "origin_plugin_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "visibility": {
+ "name": "visibility",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'visible'"
+ },
+ "archived_at": {
+ "name": "archived_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "pinned_at": {
+ "name": "pinned_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "pin_sort_key": {
+ "name": "pin_sort_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_read_at": {
+ "name": "last_read_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "latest_attention_at": {
+ "name": "latest_attention_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "threads_project_updated_idx": {
+ "name": "threads_project_updated_idx",
+ "columns": [
+ "project_id",
+ "updated_at"
+ ],
+ "isUnique": false
+ },
+ "threads_project_archived_deleted_idx": {
+ "name": "threads_project_archived_deleted_idx",
+ "columns": [
+ "project_id",
+ "archived_at",
+ "deleted_at",
+ "id"
+ ],
+ "isUnique": false
+ },
+ "threads_pin_sort_idx": {
+ "name": "threads_pin_sort_idx",
+ "columns": [
+ "archived_at",
+ "deleted_at",
+ "pin_sort_key",
+ "id"
+ ],
+ "isUnique": false,
+ "where": "\"threads\".\"pinned_at\" IS NOT NULL"
+ },
+ "threads_environment_idx": {
+ "name": "threads_environment_idx",
+ "columns": [
+ "environment_id"
+ ],
+ "isUnique": false
+ },
+ "threads_parent_idx": {
+ "name": "threads_parent_idx",
+ "columns": [
+ "parent_thread_id"
+ ],
+ "isUnique": false
+ },
+ "threads_source_origin_idx": {
+ "name": "threads_source_origin_idx",
+ "columns": [
+ "source_thread_id",
+ "origin_kind"
+ ],
+ "isUnique": false
+ },
+ "threads_origin_plugin_archived_idx": {
+ "name": "threads_origin_plugin_archived_idx",
+ "columns": [
+ "origin_plugin_id",
+ "archived_at"
+ ],
+ "isUnique": false
+ },
+ "threads_section_archived_deleted_idx": {
+ "name": "threads_section_archived_deleted_idx",
+ "columns": [
+ "section_id",
+ "archived_at",
+ "deleted_at",
+ "id"
+ ],
+ "isUnique": false
+ },
+ "threads_archived_status_idx": {
+ "name": "threads_archived_status_idx",
+ "columns": [
+ "archived_at",
+ "status"
+ ],
+ "isUnique": false
+ },
+ "threads_environment_archived_deleted_idx": {
+ "name": "threads_environment_archived_deleted_idx",
+ "columns": [
+ "environment_id",
+ "archived_at",
+ "deleted_at"
+ ],
+ "isUnique": false
+ },
+ "threads_active_maintenance_idx": {
+ "name": "threads_active_maintenance_idx",
+ "columns": [
+ "status"
+ ],
+ "isUnique": false,
+ "where": "\"threads\".\"deleted_at\" IS NULL"
+ }
+ },
+ "foreignKeys": {
+ "threads_project_id_projects_id_fk": {
+ "name": "threads_project_id_projects_id_fk",
+ "tableFrom": "threads",
+ "tableTo": "projects",
+ "columnsFrom": [
+ "project_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "threads_environment_id_environments_id_fk": {
+ "name": "threads_environment_id_environments_id_fk",
+ "tableFrom": "threads",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "threads_section_id_thread_sections_id_fk": {
+ "name": "threads_section_id_thread_sections_id_fk",
+ "tableFrom": "threads",
+ "tableTo": "thread_sections",
+ "columnsFrom": [
+ "section_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "threads_parent_thread_id_threads_id_fk": {
+ "name": "threads_parent_thread_id_threads_id_fk",
+ "tableFrom": "threads",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "parent_thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "threads_source_thread_id_threads_id_fk": {
+ "name": "threads_source_thread_id_threads_id_fk",
+ "tableFrom": "threads",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "source_thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+}
\ No newline at end of file
diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json
index fb688482de..b3119e72ba 100644
--- a/packages/db/drizzle/meta/_journal.json
+++ b/packages/db/drizzle/meta/_journal.json
@@ -617,6 +617,13 @@
"when": 1786043536668,
"tag": "0087_brief_khan",
"breakpoints": true
+ },
+ {
+ "idx": 88,
+ "version": "6",
+ "when": 1786124160418,
+ "tag": "0088_minor_juggernaut",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/packages/db/src/data/experiments.ts b/packages/db/src/data/experiments.ts
index 13a3262ff6..5f21bbfa27 100644
--- a/packages/db/src/data/experiments.ts
+++ b/packages/db/src/data/experiments.ts
@@ -9,6 +9,7 @@ export function getExperiments(db: DbConnection): Experiments {
const row = db
.select({
claudeCodeMockCliTraffic: systemExperiments.claudeCodeMockCliTraffic,
+ cloudAi: systemExperiments.cloudAi,
newOnboarding: systemExperiments.newOnboarding,
toolsHub: systemExperiments.toolsHub,
})
@@ -28,6 +29,7 @@ export function setExperiments(
.values({
id: SYSTEM_EXPERIMENTS_ROW_ID,
claudeCodeMockCliTraffic: experiments.claudeCodeMockCliTraffic,
+ cloudAi: experiments.cloudAi,
newOnboarding: experiments.newOnboarding,
toolsHub: experiments.toolsHub,
updatedAt,
@@ -36,6 +38,7 @@ export function setExperiments(
target: systemExperiments.id,
set: {
claudeCodeMockCliTraffic: experiments.claudeCodeMockCliTraffic,
+ cloudAi: experiments.cloudAi,
newOnboarding: experiments.newOnboarding,
toolsHub: experiments.toolsHub,
updatedAt,
diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts
index 6f066ccff3..37152da36e 100644
--- a/packages/db/src/schema.ts
+++ b/packages/db/src/schema.ts
@@ -152,6 +152,7 @@ export const systemExperiments = sqliteTable("system_experiments", {
claudeCodeMockCliTraffic: integer("claude_code_mock_cli_traffic", {
mode: "boolean",
}).notNull(),
+ cloudAi: integer("cloud_ai", { mode: "boolean" }).notNull().default(false),
newOnboarding: integer("new_onboarding", { mode: "boolean" })
.notNull()
.default(false),
diff --git a/packages/db/test/migrate.test.ts b/packages/db/test/migrate.test.ts
index b615bbe667..49ac9dc962 100644
--- a/packages/db/test/migrate.test.ts
+++ b/packages/db/test/migrate.test.ts
@@ -294,6 +294,7 @@ function dropRewindAddedTables(db: DbConnection): void {
dropSideChatPluginExperimentColumn(db);
dropToolsHubExperimentColumn(db);
dropNewOnboardingExperimentColumn(db);
+ dropCloudAiExperimentColumn(db);
dropSteerActiveThreadOnEnterColumn(db);
dropOnboardingCompletedAtColumn(db);
// Thread visibility was added after the legacy checkpoints these tests
@@ -475,6 +476,19 @@ function dropNewOnboardingExperimentColumn(db: DbConnection): void {
}
}
+// Migration 0088 adds the Cloud AI experiment column. Rewind scenarios that
+// clear its migration row must drop the column before replay.
+function dropCloudAiExperimentColumn(db: DbConnection): void {
+ const columns = db.$client
+ .prepare<[], TableInfoRow>("PRAGMA table_info(system_experiments)")
+ .all();
+ if (columns.some((column) => column.name === "cloud_ai")) {
+ db.$client
+ .prepare("ALTER TABLE system_experiments DROP COLUMN cloud_ai")
+ .run();
+ }
+}
+
// Migration 0083 adds the machine permission ceiling. Rewind scenarios that
// clear its migration row must drop the column before replay.
function dropHostMaxPermissionModeColumn(db: DbConnection): void {
@@ -1224,6 +1238,7 @@ describe("migrate", () => {
// exactly what an upgrading user's database looks like.
dropOnboardingCompletedAtColumn(db);
dropNewOnboardingExperimentColumn(db);
+ dropCloudAiExperimentColumn(db);
// Delete by the journal timestamp, not a hash substring: migration hashes
// are hex and can contain "0085" by coincidence.
db.$client
@@ -1510,6 +1525,7 @@ describe("migrate", () => {
dropSteerActiveThreadOnEnterColumn(db);
dropOnboardingCompletedAtColumn(db);
dropNewOnboardingExperimentColumn(db);
+ dropCloudAiExperimentColumn(db);
dropHostMaxPermissionModeColumn(db);
migrate(db);
@@ -1907,6 +1923,7 @@ describe("migrate", () => {
dropSteerActiveThreadOnEnterColumn(db);
dropOnboardingCompletedAtColumn(db);
dropNewOnboardingExperimentColumn(db);
+ dropCloudAiExperimentColumn(db);
dropHostMaxPermissionModeColumn(db);
expect(
@@ -2001,6 +2018,7 @@ describe("migrate", () => {
dropSteerActiveThreadOnEnterColumn(db);
dropOnboardingCompletedAtColumn(db);
dropNewOnboardingExperimentColumn(db);
+ dropCloudAiExperimentColumn(db);
dropHostMaxPermissionModeColumn(db);
expect(() => migrate(db)).not.toThrow();
diff --git a/packages/domain/src/experiments.ts b/packages/domain/src/experiments.ts
index 33711fc0cb..b5e4cc0f1f 100644
--- a/packages/domain/src/experiments.ts
+++ b/packages/domain/src/experiments.ts
@@ -14,6 +14,11 @@ export const experimentsSchema = z.object({
* local proxy so forwarded requests use CLI-shaped traffic.
*/
claudeCodeMockCliTraffic: z.boolean(),
+ /**
+ * Cloud AI: permits paired cloud providers to serve server-owned AI
+ * features such as metadata inference and voice transcription.
+ */
+ cloudAi: z.boolean(),
/**
* New onboarding: shows the first-run agent and project setup guide.
*/
@@ -29,6 +34,7 @@ export type Experiments = z.infer;
export const defaultExperiments: Experiments = {
claudeCodeMockCliTraffic: false,
+ cloudAi: false,
newOnboarding: false,
toolsHub: false,
};
diff --git a/packages/plugin-registry/r/icon.json b/packages/plugin-registry/r/icon.json
index 02e5c11d5c..92f40c68cc 100644
--- a/packages/plugin-registry/r/icon.json
+++ b/packages/plugin-registry/r/icon.json
@@ -14,7 +14,7 @@
"files": [
{
"path": "registry/components/ui/icon.tsx",
- "content": "import { HugeiconsIcon, type IconSvgElement } from \"@hugeicons/react\";\nimport {\n AiContentGenerator01Icon,\n Alert02Icon,\n AlertCircleIcon,\n Archive03Icon,\n ArrowDown01Icon,\n ArrowDown02Icon,\n ArrowDownDoubleIcon,\n ArrowLeft01Icon,\n ArrowMoveDownLeftIcon,\n ArrowMoveDownRightIcon,\n ArrowRight01Icon,\n ArrowRight02Icon,\n ArrowReloadHorizontalIcon,\n ArrowUp01Icon,\n ArrowUp02Icon,\n ArrowUpDoubleIcon,\n ArrowUpDownIcon,\n ArrowTurnBackwardIcon,\n ArrowTurnForwardIcon,\n ArrowUpRight01Icon,\n AttachmentIcon,\n AudioWaveIcon,\n BrowserIcon,\n BubbleChatAddIcon,\n BubbleChatIcon,\n BubbleChatQuestionIcon,\n Calendar03Icon,\n CalendarCheckOut02Icon,\n CalendarSyncIcon,\n Cancel01Icon,\n CancelCircleIcon,\n ChatFeedback01Icon,\n Bug01Icon,\n ChartColumnIcon,\n CheckListIcon,\n CheckmarkCircle02Icon,\n CircleArrowShrink01Icon,\n CircleIcon,\n Clock01Icon,\n CloudIcon,\n CollapseIcon,\n Book02Icon,\n BrainIcon,\n ComputerTerminal01Icon,\n Copy01Icon,\n DashedLine02Icon,\n DashedLineCircleIcon,\n DateTimeIcon,\n Delete02Icon,\n Download01Icon,\n DiscordIcon,\n GithubIcon,\n DragDropHorizontalIcon,\n DragDropVerticalIcon,\n Edit02Icon,\n Edit04Icon,\n ElectricPlugsIcon,\n ExpandIcon,\n ViewIcon,\n ViewOffIcon,\n File01Icon,\n FileAttachmentIcon,\n FileEmpty02Icon,\n FileQuestionMarkIcon,\n FileXIcon,\n Folder02Icon,\n FolderAddIcon,\n FolderGitTwoIcon,\n FolderIcon,\n FolderRemoveIcon,\n GitBranchIcon,\n GitForkIcon,\n GitMergeIcon,\n GitPullRequestArrow,\n GitPullRequestClosedIcon,\n GitPullRequestDraftIcon,\n GitPullRequestIcon,\n GridViewIcon,\n HelpCircleIcon,\n InformationCircleIcon,\n InternetIcon,\n LaptopIcon,\n Layers01Icon,\n ListViewIcon,\n LockIcon,\n Loading03Icon,\n LayoutThreeRowIcon,\n LayoutTwoColumnIcon,\n LayoutTwoRowIcon,\n LinkSquare02Icon,\n Mail02Icon,\n MailOpen01Icon,\n Menu02Icon,\n MessageAdd02Icon,\n MessageQuestionIcon,\n Mic02Icon,\n MoreHorizontalIcon,\n PackageReceiveIcon,\n PauseIcon,\n PinIcon,\n PinOffIcon,\n PlayIcon,\n PlusMinusSquare01Icon,\n PlusSignIcon,\n PuzzleIcon,\n Refresh01Icon,\n RepeatIcon,\n Search01Icon,\n SentIcon,\n Settings01Icon,\n SidebarBottomIcon,\n SidebarLeftIcon,\n SidebarRightIcon,\n SlidersHorizontalIcon,\n SmartPhone01Icon,\n Sorting01Icon,\n SourceCodeIcon,\n SquareIcon,\n StarIcon,\n Target02Icon,\n TestTube01Icon,\n TextWrapIcon,\n Tick02Icon,\n TimeScheduleIcon,\n ToolboxIcon,\n Unarchive03Icon,\n UserAdd01Icon,\n UserIcon,\n WorkflowCircle03Icon,\n ZapIcon,\n ZoomInAreaIcon,\n ZoomOutAreaIcon,\n} from \"@hugeicons/core-free-icons\";\nimport { cn } from \"../../lib/utils\";\n\n// The free hugeicons set ships no artist-palette glyph (its `Palette` export\n// is a pen nib), so this inlines the stroke-rounded palette artwork in the\n// same element format the set uses.\nconst PaletteStrokeRoundedIcon: IconSvgElement = [\n [\n \"path\",\n {\n d: \"M21.8205 10.4127C22.062 11.8519 22.1827 12.5715 21.2423 13.9326C21.1459 14.0722 20.8966 14.3713 20.777 14.4911C19.6103 15.6586 18.4308 15.6586 16.0716 15.6586H14.1392C13.5085 15.6586 13.1931 15.6586 12.9639 15.7142C11.9586 15.9581 11.3031 16.9391 11.453 17.9755C11.4872 18.2118 11.6043 18.5085 11.8386 19.102C11.9345 19.3449 11.9824 19.4664 12.0136 19.7304C12.1292 20.7084 11.0869 21.9508 10.1158 21.9926C9.85358 22.0039 9.83681 22.0002 9.80326 21.9926C7.66174 21.51 5.66204 20.3123 4.18389 18.4421C0.736789 14.0808 1.43146 7.71364 5.73548 4.22064C10.0395 0.727643 16.323 1.43156 19.7701 5.79289C20.868 7.1819 21.5457 8.77438 21.8205 10.4127Z\",\n fill: \"none\",\n fillRule: \"evenodd\",\n clipRule: \"evenodd\",\n stroke: \"currentColor\",\n strokeLinejoin: \"round\",\n strokeWidth: \"1.5\",\n key: \"0\",\n },\n ],\n [\n \"path\",\n {\n d: \"M7.36719 7.74976H7.24219M7.49219 7.74976C7.49219 7.88783 7.38026 7.99976 7.24219 7.99976C7.10412 7.99976 6.99219 7.88783 6.99219 7.74976C6.99219 7.61169 7.10412 7.49976 7.24219 7.49976C7.38026 7.49976 7.49219 7.61169 7.49219 7.74976Z\",\n stroke: \"currentColor\",\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n strokeWidth: \"1.5\",\n key: \"1\",\n },\n ],\n [\n \"path\",\n {\n d: \"M7.36719 15.7498H7.24219M7.49219 15.7498C7.49219 15.8878 7.38026 15.9998 7.24219 15.9998C7.10412 15.9998 6.99219 15.8878 6.99219 15.7498C6.99219 15.6117 7.10412 15.4998 7.24219 15.4998C7.38026 15.4998 7.49219 15.6117 7.49219 15.7498Z\",\n stroke: \"currentColor\",\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n strokeWidth: \"1.5\",\n key: \"2\",\n },\n ],\n [\n \"path\",\n {\n d: \"M11.8672 5.74976H11.7422M11.9922 5.74976C11.9922 5.88783 11.8803 5.99976 11.7422 5.99976C11.6041 5.99976 11.4922 5.88783 11.4922 5.74976C11.4922 5.61169 11.6041 5.49976 11.7422 5.49976C11.8803 5.49976 11.9922 5.61169 11.9922 5.74976Z\",\n stroke: \"currentColor\",\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n strokeWidth: \"1.5\",\n key: \"3\",\n },\n ],\n [\n \"path\",\n {\n d: \"M16.3672 7.74976H16.2422M16.4922 7.74976C16.4922 7.88783 16.3803 7.99976 16.2422 7.99976C16.1041 7.99976 15.9922 7.88783 15.9922 7.74976C15.9922 7.61169 16.1041 7.49976 16.2422 7.49976C16.3803 7.49976 16.4922 7.61169 16.4922 7.74976Z\",\n stroke: \"currentColor\",\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n strokeWidth: \"1.5\",\n key: \"4\",\n },\n ],\n [\n \"path\",\n {\n d: \"M18.3672 11.7498H18.2422M18.4922 11.7498C18.4922 11.8878 18.3803 11.9998 18.2422 11.9998C18.1041 11.9998 17.9922 11.8878 17.9922 11.7498C17.9922 11.6117 18.1041 11.4998 18.2422 11.4998C18.3803 11.4998 18.4922 11.6117 18.4922 11.7498Z\",\n stroke: \"currentColor\",\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n strokeWidth: \"1.5\",\n key: \"5\",\n },\n ],\n [\n \"path\",\n {\n d: \"M5.86719 11.7498H5.74219M5.99219 11.7498C5.99219 11.8878 5.88026 11.9998 5.74219 11.9998C5.60412 11.9998 5.49219 11.8878 5.49219 11.7498C5.49219 11.6117 5.60412 11.4998 5.74219 11.4998C5.88026 11.4998 5.99219 11.6117 5.99219 11.7498Z\",\n stroke: \"currentColor\",\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n strokeWidth: \"1.5\",\n key: \"6\",\n },\n ],\n];\n\n// Custom \"new section\" glyph: the set's ListView rows with the middle and\n// bottom rows shortened so the plus owns the lower-right quadrant, matching\n// FolderAdd's non-overlapping plus placement (same plus geometry). Hugeicons\n// has no list-with-plus variant that keeps the ListView row shape, so this\n// inlines the artwork in the same element format the set uses.\nconst SectionAddStrokeRoundedIcon: IconSvgElement = [\n [\n \"path\",\n {\n d: \"M2 3.4C2 2.24173 2.24173 2 3.4 2H20.6C21.7583 2 22 2.24173 22 3.4V4.6C22 5.75827 21.7583 6 20.6 6H3.4C2.24173 6 2 5.75827 2 4.6V3.4Z\",\n stroke: \"currentColor\",\n strokeLinecap: \"round\",\n strokeWidth: \"1.5\",\n key: \"0\",\n },\n ],\n [\n \"path\",\n {\n d: \"M2 11.4C2 10.2417 2.24173 10 3.4 10H10.6C11.7583 10 12 10.2417 12 11.4V12.6C12 13.7583 11.7583 14 10.6 14H3.4C2.24173 14 2 13.7583 2 12.6V11.4Z\",\n stroke: \"currentColor\",\n strokeLinecap: \"round\",\n strokeWidth: \"1.5\",\n key: \"1\",\n },\n ],\n [\n \"path\",\n {\n d: \"M2 19.4C2 18.2417 2.24173 18 3.4 18H10.6C11.7583 18 12 18.2417 12 19.4V20.6C12 21.7583 11.7583 22 10.6 22H3.4C2.24173 22 2 21.7583 2 20.6V19.4Z\",\n stroke: \"currentColor\",\n strokeLinecap: \"round\",\n strokeWidth: \"1.5\",\n key: \"2\",\n },\n ],\n [\n \"path\",\n {\n d: \"M18 13V21M22 17H14\",\n stroke: \"currentColor\",\n strokeLinecap: \"round\",\n strokeWidth: \"1.5\",\n key: \"3\",\n },\n ],\n];\n\nconst ICON_MAP = {\n AiContentGenerator01: AiContentGenerator01Icon,\n AlertCircle: AlertCircleIcon,\n AlertTriangle: Alert02Icon,\n AlignLeft: Menu02Icon,\n AppWindow: BrowserIcon,\n Archive: Archive03Icon,\n ArchiveRestore: Unarchive03Icon,\n ArrowDown: ArrowDown02Icon,\n ArrowRight: ArrowRight02Icon,\n ArrowReloadHorizontal: ArrowReloadHorizontalIcon,\n ArrowUp: ArrowUp02Icon,\n ArrowUpDown: ArrowUpDownIcon,\n ArrowTurnBackward: ArrowTurnBackwardIcon,\n ArrowTurnForward: ArrowTurnForwardIcon,\n ArrowUpRight: ArrowUpRight01Icon,\n AudioLines: AudioWaveIcon,\n Beaker: TestTube01Icon,\n BubbleChatQuestion: BubbleChatQuestionIcon,\n Browser: BrowserIcon,\n Brain: BrainIcon,\n Bug: Bug01Icon,\n Calendar: Calendar03Icon,\n CalendarCheckOut02: CalendarCheckOut02Icon,\n CalendarSync: CalendarSyncIcon,\n ChatFeedback: ChatFeedback01Icon,\n ChartColumn: ChartColumnIcon,\n Check: Tick02Icon,\n ChevronDown: ArrowDown01Icon,\n ChevronLeft: ArrowLeft01Icon,\n ChevronRight: ArrowRight01Icon,\n ChevronUp: ArrowUp01Icon,\n ChevronsDown: ArrowDownDoubleIcon,\n ChevronsUp: ArrowUpDoubleIcon,\n Circle: CircleIcon,\n CircleArrowShrink: CircleArrowShrink01Icon,\n CircleCheck: CheckmarkCircle02Icon,\n CircleDashed: DashedLineCircleIcon,\n CircleQuestion: HelpCircleIcon,\n CircleX: CancelCircleIcon,\n Clock: Clock01Icon,\n Code: SourceCodeIcon,\n ComputerTerminal01: ComputerTerminal01Icon,\n Columns2: LayoutTwoColumnIcon,\n Container: CloudIcon,\n Copy: Copy01Icon,\n CornerDownLeft: ArrowMoveDownLeftIcon,\n CornerDownRight: ArrowMoveDownRightIcon,\n Discord: DiscordIcon,\n DateTime: DateTimeIcon,\n Github: GithubIcon,\n DragDropHorizontal: DragDropHorizontalIcon,\n DragDropVertical: DragDropVerticalIcon,\n Download: Download01Icon,\n Edit: Edit02Icon,\n EditFile: Edit04Icon,\n ElectricPlugs: ElectricPlugsIcon,\n Eye: ViewIcon,\n EyeOff: ViewOffIcon,\n Explore: Book02Icon,\n ExternalLink: LinkSquare02Icon,\n FileDiff: PlusMinusSquare01Icon,\n File: FileEmpty02Icon,\n FileAttachment: FileAttachmentIcon,\n FileQuestion: FileQuestionMarkIcon,\n FileText: File01Icon,\n FileX2: FileXIcon,\n Folder: FolderIcon,\n FolderGit: FolderGitTwoIcon,\n FolderOpen: Folder02Icon,\n FolderMinus: FolderRemoveIcon,\n FolderPlus: FolderAddIcon,\n Fork: GitForkIcon,\n GitBranch: GitBranchIcon,\n GitMerge: GitMergeIcon,\n GitPullRequest: GitPullRequestIcon,\n GitPullRequestArrow: GitPullRequestArrow,\n GitPullRequestClosed: GitPullRequestClosedIcon,\n GitPullRequestDraft: GitPullRequestDraftIcon,\n Globe: InternetIcon,\n GridView: GridViewIcon,\n Info: InformationCircleIcon,\n Laptop: LaptopIcon,\n Layers: Layers01Icon,\n ListView: ListViewIcon,\n SectionAdd: SectionAddStrokeRoundedIcon,\n ListTodo: CheckListIcon,\n Loading: Loading03Icon,\n Lock: LockIcon,\n Mail: Mail02Icon,\n MailOpen: MailOpen01Icon,\n Maximize2: ExpandIcon,\n MessageQuestion: MessageQuestionIcon,\n MessageCirclePlus: BubbleChatAddIcon,\n MessageSquarePlus: BubbleChatAddIcon,\n MessageSquare: BubbleChatIcon,\n Mic: Mic02Icon,\n Minimize2: CollapseIcon,\n MoreHorizontal: MoreHorizontalIcon,\n NewTab: DashedLine02Icon,\n PackageReceive: PackageReceiveIcon,\n Palette: PaletteStrokeRoundedIcon,\n PanelBottom: SidebarBottomIcon,\n PanelLeft: SidebarLeftIcon,\n PanelRight: SidebarRightIcon,\n Paperclip: AttachmentIcon,\n Pause: PauseIcon,\n Pin: PinIcon,\n PinOff: PinOffIcon,\n Play: PlayIcon,\n Plus: PlusSignIcon,\n Puzzle: PuzzleIcon,\n Repeat: RepeatIcon,\n RotateCcw: Refresh01Icon,\n Rows2: LayoutTwoRowIcon,\n Rows3: LayoutThreeRowIcon,\n Search: Search01Icon,\n Sent: SentIcon,\n Settings: Settings01Icon,\n SideChat: MessageAdd02Icon,\n ClosePluginPane: Cancel01Icon,\n CloseThreadPane: Cancel01Icon,\n SlidersHorizontal: SlidersHorizontalIcon,\n Smartphone: SmartPhone01Icon,\n Sort: Sorting01Icon,\n Spinner: DashedLineCircleIcon,\n Square: SquareIcon,\n Star: StarIcon,\n Target: Target02Icon,\n Terminal: ComputerTerminal01Icon,\n TextWrap: TextWrapIcon,\n TimeSchedule: TimeScheduleIcon,\n Toolbox: ToolboxIcon,\n Trash2: Delete02Icon,\n UserRound: UserIcon,\n UserRoundPlus: UserAdd01Icon,\n Workflow: WorkflowCircle03Icon,\n X: Cancel01Icon,\n Zap: ZapIcon,\n ZoomIn: ZoomInAreaIcon,\n ZoomOut: ZoomOutAreaIcon,\n} as const satisfies Record;\n\nexport type IconName = keyof typeof ICON_MAP;\n\nexport const ICON_NAMES = Object.keys(ICON_MAP) as readonly IconName[];\n\nexport interface IconProps {\n name: IconName;\n className?: string;\n \"aria-hidden\"?: boolean | \"true\" | \"false\";\n \"aria-label\"?: string;\n}\n\nexport function Icon({\n name,\n className,\n \"aria-hidden\": ariaHidden,\n \"aria-label\": ariaLabel,\n}: IconProps) {\n return (\n \n );\n}\n",
+ "content": "import { HugeiconsIcon, type IconSvgElement } from \"@hugeicons/react\";\nimport {\n AiContentGenerator01Icon,\n Alert02Icon,\n AlertCircleIcon,\n Archive03Icon,\n ArrowDown01Icon,\n ArrowDown02Icon,\n ArrowDownDoubleIcon,\n ArrowLeft01Icon,\n ArrowMoveDownLeftIcon,\n ArrowMoveDownRightIcon,\n ArrowRight01Icon,\n ArrowRight02Icon,\n ArrowReloadHorizontalIcon,\n ArrowUp01Icon,\n ArrowUp02Icon,\n ArrowUpDoubleIcon,\n ArrowUpDownIcon,\n ArrowTurnBackwardIcon,\n ArrowTurnForwardIcon,\n ArrowUpRight01Icon,\n AttachmentIcon,\n AudioWaveIcon,\n BrowserIcon,\n BubbleChatAddIcon,\n BubbleChatIcon,\n BubbleChatQuestionIcon,\n Calendar03Icon,\n CalendarCheckOut02Icon,\n CalendarSyncIcon,\n Cancel01Icon,\n CancelCircleIcon,\n ChatFeedback01Icon,\n Bug01Icon,\n ChartColumnIcon,\n CheckListIcon,\n CheckmarkCircle02Icon,\n CircleArrowShrink01Icon,\n CircleIcon,\n Clock01Icon,\n CloudIcon,\n CollapseIcon,\n Book02Icon,\n BrainIcon,\n ComputerTerminal01Icon,\n Copy01Icon,\n DashedLine02Icon,\n DashedLineCircleIcon,\n DateTimeIcon,\n Delete02Icon,\n Download01Icon,\n DiscordIcon,\n GithubIcon,\n DragDropHorizontalIcon,\n DragDropVerticalIcon,\n Edit02Icon,\n Edit04Icon,\n ElectricPlugsIcon,\n ExpandIcon,\n ViewIcon,\n ViewOffIcon,\n File01Icon,\n FileAttachmentIcon,\n FileEmpty02Icon,\n FileQuestionMarkIcon,\n FileXIcon,\n Folder02Icon,\n FolderAddIcon,\n FolderGitTwoIcon,\n FolderIcon,\n FolderRemoveIcon,\n GitBranchIcon,\n GitForkIcon,\n GitMergeIcon,\n GitPullRequestArrow,\n GitPullRequestClosedIcon,\n GitPullRequestDraftIcon,\n GitPullRequestIcon,\n GridViewIcon,\n HelpCircleIcon,\n InformationCircleIcon,\n InternetIcon,\n LaptopIcon,\n Layers01Icon,\n ListViewIcon,\n LockIcon,\n Loading03Icon,\n LayoutThreeRowIcon,\n LayoutTwoColumnIcon,\n LayoutTwoRowIcon,\n LinkSquare02Icon,\n Mail02Icon,\n MailOpen01Icon,\n Menu02Icon,\n MessageAdd02Icon,\n MessageQuestionIcon,\n Mic02Icon,\n MoreHorizontalIcon,\n PackageReceiveIcon,\n PauseIcon,\n PinIcon,\n PinOffIcon,\n PlayIcon,\n PlusMinusSquare01Icon,\n PlusSignIcon,\n PuzzleIcon,\n Refresh01Icon,\n RepeatIcon,\n Search01Icon,\n SentIcon,\n Settings01Icon,\n SidebarBottomIcon,\n SidebarLeftIcon,\n SidebarRightIcon,\n SlidersHorizontalIcon,\n SmartPhone01Icon,\n Sorting01Icon,\n SourceCodeIcon,\n SquareIcon,\n StarIcon,\n Target02Icon,\n TestTube01Icon,\n TextWrapIcon,\n Tick02Icon,\n TimeScheduleIcon,\n ToolboxIcon,\n Unarchive03Icon,\n UserAdd01Icon,\n UserIcon,\n WorkflowCircle03Icon,\n ZapIcon,\n ZoomInAreaIcon,\n ZoomOutAreaIcon,\n} from \"@hugeicons/core-free-icons\";\nimport { cn } from \"../../lib/utils\";\n\n// The free hugeicons set ships no artist-palette glyph (its `Palette` export\n// is a pen nib), so this inlines the stroke-rounded palette artwork in the\n// same element format the set uses.\nconst PaletteStrokeRoundedIcon: IconSvgElement = [\n [\n \"path\",\n {\n d: \"M21.8205 10.4127C22.062 11.8519 22.1827 12.5715 21.2423 13.9326C21.1459 14.0722 20.8966 14.3713 20.777 14.4911C19.6103 15.6586 18.4308 15.6586 16.0716 15.6586H14.1392C13.5085 15.6586 13.1931 15.6586 12.9639 15.7142C11.9586 15.9581 11.3031 16.9391 11.453 17.9755C11.4872 18.2118 11.6043 18.5085 11.8386 19.102C11.9345 19.3449 11.9824 19.4664 12.0136 19.7304C12.1292 20.7084 11.0869 21.9508 10.1158 21.9926C9.85358 22.0039 9.83681 22.0002 9.80326 21.9926C7.66174 21.51 5.66204 20.3123 4.18389 18.4421C0.736789 14.0808 1.43146 7.71364 5.73548 4.22064C10.0395 0.727643 16.323 1.43156 19.7701 5.79289C20.868 7.1819 21.5457 8.77438 21.8205 10.4127Z\",\n fill: \"none\",\n fillRule: \"evenodd\",\n clipRule: \"evenodd\",\n stroke: \"currentColor\",\n strokeLinejoin: \"round\",\n strokeWidth: \"1.5\",\n key: \"0\",\n },\n ],\n [\n \"path\",\n {\n d: \"M7.36719 7.74976H7.24219M7.49219 7.74976C7.49219 7.88783 7.38026 7.99976 7.24219 7.99976C7.10412 7.99976 6.99219 7.88783 6.99219 7.74976C6.99219 7.61169 7.10412 7.49976 7.24219 7.49976C7.38026 7.49976 7.49219 7.61169 7.49219 7.74976Z\",\n stroke: \"currentColor\",\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n strokeWidth: \"1.5\",\n key: \"1\",\n },\n ],\n [\n \"path\",\n {\n d: \"M7.36719 15.7498H7.24219M7.49219 15.7498C7.49219 15.8878 7.38026 15.9998 7.24219 15.9998C7.10412 15.9998 6.99219 15.8878 6.99219 15.7498C6.99219 15.6117 7.10412 15.4998 7.24219 15.4998C7.38026 15.4998 7.49219 15.6117 7.49219 15.7498Z\",\n stroke: \"currentColor\",\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n strokeWidth: \"1.5\",\n key: \"2\",\n },\n ],\n [\n \"path\",\n {\n d: \"M11.8672 5.74976H11.7422M11.9922 5.74976C11.9922 5.88783 11.8803 5.99976 11.7422 5.99976C11.6041 5.99976 11.4922 5.88783 11.4922 5.74976C11.4922 5.61169 11.6041 5.49976 11.7422 5.49976C11.8803 5.49976 11.9922 5.61169 11.9922 5.74976Z\",\n stroke: \"currentColor\",\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n strokeWidth: \"1.5\",\n key: \"3\",\n },\n ],\n [\n \"path\",\n {\n d: \"M16.3672 7.74976H16.2422M16.4922 7.74976C16.4922 7.88783 16.3803 7.99976 16.2422 7.99976C16.1041 7.99976 15.9922 7.88783 15.9922 7.74976C15.9922 7.61169 16.1041 7.49976 16.2422 7.49976C16.3803 7.49976 16.4922 7.61169 16.4922 7.74976Z\",\n stroke: \"currentColor\",\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n strokeWidth: \"1.5\",\n key: \"4\",\n },\n ],\n [\n \"path\",\n {\n d: \"M18.3672 11.7498H18.2422M18.4922 11.7498C18.4922 11.8878 18.3803 11.9998 18.2422 11.9998C18.1041 11.9998 17.9922 11.8878 17.9922 11.7498C17.9922 11.6117 18.1041 11.4998 18.2422 11.4998C18.3803 11.4998 18.4922 11.6117 18.4922 11.7498Z\",\n stroke: \"currentColor\",\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n strokeWidth: \"1.5\",\n key: \"5\",\n },\n ],\n [\n \"path\",\n {\n d: \"M5.86719 11.7498H5.74219M5.99219 11.7498C5.99219 11.8878 5.88026 11.9998 5.74219 11.9998C5.60412 11.9998 5.49219 11.8878 5.49219 11.7498C5.49219 11.6117 5.60412 11.4998 5.74219 11.4998C5.88026 11.4998 5.99219 11.6117 5.99219 11.7498Z\",\n stroke: \"currentColor\",\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n strokeWidth: \"1.5\",\n key: \"6\",\n },\n ],\n];\n\n// Custom \"new section\" glyph: the set's ListView rows with the middle and\n// bottom rows shortened so the plus owns the lower-right quadrant, matching\n// FolderAdd's non-overlapping plus placement (same plus geometry). Hugeicons\n// has no list-with-plus variant that keeps the ListView row shape, so this\n// inlines the artwork in the same element format the set uses.\nconst SectionAddStrokeRoundedIcon: IconSvgElement = [\n [\n \"path\",\n {\n d: \"M2 3.4C2 2.24173 2.24173 2 3.4 2H20.6C21.7583 2 22 2.24173 22 3.4V4.6C22 5.75827 21.7583 6 20.6 6H3.4C2.24173 6 2 5.75827 2 4.6V3.4Z\",\n stroke: \"currentColor\",\n strokeLinecap: \"round\",\n strokeWidth: \"1.5\",\n key: \"0\",\n },\n ],\n [\n \"path\",\n {\n d: \"M2 11.4C2 10.2417 2.24173 10 3.4 10H10.6C11.7583 10 12 10.2417 12 11.4V12.6C12 13.7583 11.7583 14 10.6 14H3.4C2.24173 14 2 13.7583 2 12.6V11.4Z\",\n stroke: \"currentColor\",\n strokeLinecap: \"round\",\n strokeWidth: \"1.5\",\n key: \"1\",\n },\n ],\n [\n \"path\",\n {\n d: \"M2 19.4C2 18.2417 2.24173 18 3.4 18H10.6C11.7583 18 12 18.2417 12 19.4V20.6C12 21.7583 11.7583 22 10.6 22H3.4C2.24173 22 2 21.7583 2 20.6V19.4Z\",\n stroke: \"currentColor\",\n strokeLinecap: \"round\",\n strokeWidth: \"1.5\",\n key: \"2\",\n },\n ],\n [\n \"path\",\n {\n d: \"M18 13V21M22 17H14\",\n stroke: \"currentColor\",\n strokeLinecap: \"round\",\n strokeWidth: \"1.5\",\n key: \"3\",\n },\n ],\n];\n\nconst ICON_MAP = {\n AiContentGenerator01: AiContentGenerator01Icon,\n AlertCircle: AlertCircleIcon,\n AlertTriangle: Alert02Icon,\n AlignLeft: Menu02Icon,\n AppWindow: BrowserIcon,\n Archive: Archive03Icon,\n ArchiveRestore: Unarchive03Icon,\n ArrowDown: ArrowDown02Icon,\n ArrowRight: ArrowRight02Icon,\n ArrowReloadHorizontal: ArrowReloadHorizontalIcon,\n ArrowUp: ArrowUp02Icon,\n ArrowUpDown: ArrowUpDownIcon,\n ArrowTurnBackward: ArrowTurnBackwardIcon,\n ArrowTurnForward: ArrowTurnForwardIcon,\n ArrowUpRight: ArrowUpRight01Icon,\n AudioLines: AudioWaveIcon,\n Beaker: TestTube01Icon,\n BubbleChatQuestion: BubbleChatQuestionIcon,\n Browser: BrowserIcon,\n Brain: BrainIcon,\n Bug: Bug01Icon,\n Calendar: Calendar03Icon,\n CalendarCheckOut02: CalendarCheckOut02Icon,\n CalendarSync: CalendarSyncIcon,\n ChatFeedback: ChatFeedback01Icon,\n ChartColumn: ChartColumnIcon,\n Check: Tick02Icon,\n ChevronDown: ArrowDown01Icon,\n ChevronLeft: ArrowLeft01Icon,\n ChevronRight: ArrowRight01Icon,\n ChevronUp: ArrowUp01Icon,\n ChevronsDown: ArrowDownDoubleIcon,\n ChevronsUp: ArrowUpDoubleIcon,\n Circle: CircleIcon,\n CircleArrowShrink: CircleArrowShrink01Icon,\n CircleCheck: CheckmarkCircle02Icon,\n CircleDashed: DashedLineCircleIcon,\n CircleQuestion: HelpCircleIcon,\n CircleX: CancelCircleIcon,\n Clock: Clock01Icon,\n Cloud: CloudIcon,\n Code: SourceCodeIcon,\n ComputerTerminal01: ComputerTerminal01Icon,\n Columns2: LayoutTwoColumnIcon,\n Container: CloudIcon,\n Copy: Copy01Icon,\n CornerDownLeft: ArrowMoveDownLeftIcon,\n CornerDownRight: ArrowMoveDownRightIcon,\n Discord: DiscordIcon,\n DateTime: DateTimeIcon,\n Github: GithubIcon,\n DragDropHorizontal: DragDropHorizontalIcon,\n DragDropVertical: DragDropVerticalIcon,\n Download: Download01Icon,\n Edit: Edit02Icon,\n EditFile: Edit04Icon,\n ElectricPlugs: ElectricPlugsIcon,\n Eye: ViewIcon,\n EyeOff: ViewOffIcon,\n Explore: Book02Icon,\n ExternalLink: LinkSquare02Icon,\n FileDiff: PlusMinusSquare01Icon,\n File: FileEmpty02Icon,\n FileAttachment: FileAttachmentIcon,\n FileQuestion: FileQuestionMarkIcon,\n FileText: File01Icon,\n FileX2: FileXIcon,\n Folder: FolderIcon,\n FolderGit: FolderGitTwoIcon,\n FolderOpen: Folder02Icon,\n FolderMinus: FolderRemoveIcon,\n FolderPlus: FolderAddIcon,\n Fork: GitForkIcon,\n GitBranch: GitBranchIcon,\n GitMerge: GitMergeIcon,\n GitPullRequest: GitPullRequestIcon,\n GitPullRequestArrow: GitPullRequestArrow,\n GitPullRequestClosed: GitPullRequestClosedIcon,\n GitPullRequestDraft: GitPullRequestDraftIcon,\n Globe: InternetIcon,\n GridView: GridViewIcon,\n Info: InformationCircleIcon,\n Laptop: LaptopIcon,\n Layers: Layers01Icon,\n ListView: ListViewIcon,\n SectionAdd: SectionAddStrokeRoundedIcon,\n ListTodo: CheckListIcon,\n Loading: Loading03Icon,\n Lock: LockIcon,\n Mail: Mail02Icon,\n MailOpen: MailOpen01Icon,\n Maximize2: ExpandIcon,\n MessageQuestion: MessageQuestionIcon,\n MessageCirclePlus: BubbleChatAddIcon,\n MessageSquarePlus: BubbleChatAddIcon,\n MessageSquare: BubbleChatIcon,\n Mic: Mic02Icon,\n Minimize2: CollapseIcon,\n MoreHorizontal: MoreHorizontalIcon,\n NewTab: DashedLine02Icon,\n PackageReceive: PackageReceiveIcon,\n Palette: PaletteStrokeRoundedIcon,\n PanelBottom: SidebarBottomIcon,\n PanelLeft: SidebarLeftIcon,\n PanelRight: SidebarRightIcon,\n Paperclip: AttachmentIcon,\n Pause: PauseIcon,\n Pin: PinIcon,\n PinOff: PinOffIcon,\n Play: PlayIcon,\n Plus: PlusSignIcon,\n Puzzle: PuzzleIcon,\n Repeat: RepeatIcon,\n RotateCcw: Refresh01Icon,\n Rows2: LayoutTwoRowIcon,\n Rows3: LayoutThreeRowIcon,\n Search: Search01Icon,\n Sent: SentIcon,\n Settings: Settings01Icon,\n SideChat: MessageAdd02Icon,\n ClosePluginPane: Cancel01Icon,\n CloseThreadPane: Cancel01Icon,\n SlidersHorizontal: SlidersHorizontalIcon,\n Smartphone: SmartPhone01Icon,\n Sort: Sorting01Icon,\n Spinner: DashedLineCircleIcon,\n Square: SquareIcon,\n Star: StarIcon,\n Target: Target02Icon,\n Terminal: ComputerTerminal01Icon,\n TextWrap: TextWrapIcon,\n TimeSchedule: TimeScheduleIcon,\n Toolbox: ToolboxIcon,\n Trash2: Delete02Icon,\n UserRound: UserIcon,\n UserRoundPlus: UserAdd01Icon,\n Workflow: WorkflowCircle03Icon,\n X: Cancel01Icon,\n Zap: ZapIcon,\n ZoomIn: ZoomInAreaIcon,\n ZoomOut: ZoomOutAreaIcon,\n} as const satisfies Record;\n\nexport type IconName = keyof typeof ICON_MAP;\n\nexport const ICON_NAMES = Object.keys(ICON_MAP) as readonly IconName[];\n\nexport interface IconProps {\n name: IconName;\n className?: string;\n \"aria-hidden\"?: boolean | \"true\" | \"false\";\n \"aria-label\"?: string;\n}\n\nexport function Icon({\n name,\n className,\n \"aria-hidden\": ariaHidden,\n \"aria-label\": ariaLabel,\n}: IconProps) {\n return (\n \n );\n}\n",
"type": "registry:ui",
"target": "components/ui/icon.tsx"
}
diff --git a/packages/plugin-sdk/bundled-types/bb-plugin-sdk-app.d.ts b/packages/plugin-sdk/bundled-types/bb-plugin-sdk-app.d.ts
index 114493d0d4..e7109a4bd2 100644
--- a/packages/plugin-sdk/bundled-types/bb-plugin-sdk-app.d.ts
+++ b/packages/plugin-sdk/bundled-types/bb-plugin-sdk-app.d.ts
@@ -480,9 +480,12 @@ interface PluginPendingInteractionRegistration {
interface PluginSidebarFooterActionContext {
/**
* Navigate to this plugin's detail page in Tools, where declarative settings
- * and `settingsSection` slots render.
+ * and `settingsSection` slots render. Pass a section id to target one of
+ * this plugin's registered settings sections directly.
*/
- openSettings(): void;
+ openSettings(options?: {
+ sectionId?: string;
+ }): void;
}
/**
* An icon button in the app sidebar footer (next to Settings / bug report).
diff --git a/packages/plugin-sdk/bundled-types/bb-plugin-sdk.d.ts b/packages/plugin-sdk/bundled-types/bb-plugin-sdk.d.ts
index 5f273bcbba..9c156665e1 100644
--- a/packages/plugin-sdk/bundled-types/bb-plugin-sdk.d.ts
+++ b/packages/plugin-sdk/bundled-types/bb-plugin-sdk.d.ts
@@ -244,6 +244,7 @@ type Environment = z$1.infer;
*/
declare const experimentsSchema: z$1.ZodObject<{
claudeCodeMockCliTraffic: z$1.ZodBoolean;
+ cloudAi: z$1.ZodBoolean;
newOnboarding: z$1.ZodBoolean;
toolsHub: z$1.ZodBoolean;
}, z$1.core.$strip>;
@@ -700,8 +701,8 @@ declare const threadEventSchema: z$1.ZodPipe;
@@ -889,10 +890,10 @@ declare const threadEventSchema: z$1.ZodPipe;
taskStatus: z$1.ZodEnum<{
pending: "pending";
- running: "running";
- paused: "paused";
completed: "completed";
failed: "failed";
+ paused: "paused";
+ running: "running";
killed: "killed";
stopped: "stopped";
}>;
@@ -908,8 +909,8 @@ declare const threadEventSchema: z$1.ZodPipe;
taskStatus: z$1.ZodEnum<{
pending: "pending";
- running: "running";
- paused: "paused";
completed: "completed";
failed: "failed";
+ paused: "paused";
+ running: "running";
killed: "killed";
stopped: "stopped";
}>;
@@ -1140,8 +1141,8 @@ declare const threadEventSchema: z$1.ZodPipe;
taskStatus: z$1.ZodEnum<{
pending: "pending";
- running: "running";
- paused: "paused";
completed: "completed";
failed: "failed";
+ paused: "paused";
+ running: "running";
killed: "killed";
stopped: "stopped";
}>;
@@ -1269,8 +1270,8 @@ declare const threadEventSchema: z$1.ZodPipe;
taskStatus: z$1.ZodEnum<{
pending: "pending";
- running: "running";
- paused: "paused";
completed: "completed";
failed: "failed";
+ paused: "paused";
+ running: "running";
killed: "killed";
stopped: "stopped";
}>;
@@ -1341,8 +1342,8 @@ declare const threadEventSchema: z$1.ZodPipe;
}, z$1.core.$strip>>;
}, z$1.core.$strip>;
@@ -2530,8 +2531,8 @@ type SkillFilesResponse = z$1.infer;
declare const projectResponseSchema: z$1.ZodObject<{
id: z$1.ZodString;
kind: z$1.ZodEnum<{
- personal: "personal";
standard: "standard";
+ personal: "personal";
}>;
name: z$1.ZodString;
gitRemoteUrl: z$1.ZodNullable;
@@ -2552,8 +2553,8 @@ type ProjectResponse = z$1.infer;
declare const projectWithThreadsResponseSchema: z$1.ZodObject<{
id: z$1.ZodString;
kind: z$1.ZodEnum<{
- personal: "personal";
standard: "standard";
+ personal: "personal";
}>;
name: z$1.ZodString;
gitRemoteUrl: z$1.ZodNullable;
@@ -2653,8 +2654,8 @@ declare const projectWithThreadsResponseSchema: z$1.ZodObject<{
ultra: "ultra";
}>;
permissionMode: z$1.ZodEnum<{
- auto: "auto";
"accept-edits": "accept-edits";
+ auto: "auto";
full: "full";
}>;
}, z$1.core.$strip>>;
@@ -3517,8 +3518,8 @@ declare const hostDaemonCommandRegistry: {
}, z$1.core.$strict>], "kind">>;
disallowedTools: z$1.ZodOptional>;
instructionMode: z$1.ZodEnum<{
- append: "append";
replace: "replace";
+ append: "append";
}>;
type: z$1.ZodLiteral<"thread.start">;
requestId: z$1.ZodString;
@@ -4006,8 +4007,8 @@ declare const hostDaemonCommandRegistry: {
}>;
}, z$1.core.$strip>;
instructionMode: z$1.ZodEnum<{
- append: "append";
replace: "replace";
+ append: "append";
}>;
projectId: z$1.ZodString;
providerId: z$1.ZodString;
@@ -4302,8 +4303,8 @@ declare const hostDaemonCommandRegistry: {
}>;
}, z$1.core.$strip>;
instructionMode: z$1.ZodEnum<{
- append: "append";
replace: "replace";
+ append: "append";
}>;
projectId: z$1.ZodString;
providerId: z$1.ZodString;
@@ -5307,9 +5308,9 @@ declare const hostDaemonCommandRegistry: {
executablePath: z$1.ZodNullable;
installed: z$1.ZodBoolean;
installSource: z$1.ZodEnum<{
- external: "external";
notInstalled: "notInstalled";
npmGlobal: "npmGlobal";
+ external: "external";
}>;
currentVersion: z$1.ZodNullable;
latestVersion: z$1.ZodNullable;
@@ -5482,13 +5483,13 @@ declare const hostDaemonCommandRegistry: {
outcome: z$1.ZodLiteral<"unavailable">;
failure: z$1.ZodObject<{
code: z$1.ZodEnum<{
+ unknown: "unknown";
path_not_found: "path_not_found";
not_git_repo: "not_git_repo";
not_worktree: "not_worktree";
workspace_type_mismatch: "workspace_type_mismatch";
permission_denied: "permission_denied";
unknown_environment: "unknown_environment";
- unknown: "unknown";
}>;
workspacePath: z$1.ZodString;
message: z$1.ZodString;
@@ -5532,13 +5533,13 @@ declare const hostDaemonCommandRegistry: {
outcome: z$1.ZodLiteral<"unavailable">;
failure: z$1.ZodObject<{
code: z$1.ZodEnum<{
+ unknown: "unknown";
path_not_found: "path_not_found";
not_git_repo: "not_git_repo";
not_worktree: "not_worktree";
workspace_type_mismatch: "workspace_type_mismatch";
permission_denied: "permission_denied";
unknown_environment: "unknown_environment";
- unknown: "unknown";
}>;
workspacePath: z$1.ZodString;
message: z$1.ZodString;
@@ -5594,13 +5595,13 @@ declare const hostDaemonCommandRegistry: {
outcome: z$1.ZodLiteral<"unavailable">;
failure: z$1.ZodObject<{
code: z$1.ZodEnum<{
+ unknown: "unknown";
path_not_found: "path_not_found";
not_git_repo: "not_git_repo";
not_worktree: "not_worktree";
workspace_type_mismatch: "workspace_type_mismatch";
permission_denied: "permission_denied";
unknown_environment: "unknown_environment";
- unknown: "unknown";
}>;
workspacePath: z$1.ZodString;
message: z$1.ZodString;
@@ -5642,13 +5643,13 @@ declare const hostDaemonCommandRegistry: {
outcome: z$1.ZodLiteral<"unavailable">;
failure: z$1.ZodObject<{
code: z$1.ZodEnum<{
+ unknown: "unknown";
path_not_found: "path_not_found";
not_git_repo: "not_git_repo";
not_worktree: "not_worktree";
workspace_type_mismatch: "workspace_type_mismatch";
permission_denied: "permission_denied";
unknown_environment: "unknown_environment";
- unknown: "unknown";
}>;
workspacePath: z$1.ZodString;
message: z$1.ZodString;
@@ -5766,9 +5767,9 @@ declare const providerCliStatusResponseSchema: z$1.ZodRecord;
installed: z$1.ZodBoolean;
installSource: z$1.ZodEnum<{
- external: "external";
notInstalled: "notInstalled";
npmGlobal: "npmGlobal";
+ external: "external";
}>;
currentVersion: z$1.ZodNullable;
latestVersion: z$1.ZodNullable;
@@ -6358,8 +6359,8 @@ declare const systemExecutionOptionsResponseSchema: z$1.ZodObject<{
supportsUserQuestion: z$1.ZodBoolean;
supportsFork: z$1.ZodBoolean;
supportedPermissionModes: z$1.ZodArray>;
}, z$1.core.$strip>;
@@ -6390,8 +6391,8 @@ declare const systemExecutionOptionsResponseSchema: z$1.ZodObject<{
available: z$1.ZodBoolean;
}, z$1.core.$strip>>;
permissionCeiling: z$1.ZodEnum<{
- auto: "auto";
"accept-edits": "accept-edits";
+ auto: "auto";
full: "full";
}>;
models: z$1.ZodArray>;
experiments: z$1.ZodObject<{
claudeCodeMockCliTraffic: z$1.ZodBoolean;
+ cloudAi: z$1.ZodBoolean;
newOnboarding: z$1.ZodBoolean;
toolsHub: z$1.ZodBoolean;
}, z$1.core.$strip>;
@@ -7822,10 +7824,10 @@ declare const createThreadRequestSchema: z$1.ZodObject<{
ultra: "ultra";
}>>;
permissionMode: z$1.ZodOptional, z$1.ZodLiteral<"workspace-write">]>, z$1.ZodTransform<"auto" | "accept-edits" | "full", "auto" | "accept-edits" | "full" | "workspace-write">>>;
+ }>, z$1.ZodLiteral<"workspace-write">]>, z$1.ZodTransform<"accept-edits" | "auto" | "full", "accept-edits" | "auto" | "full" | "workspace-write">>>;
executionInputSources: z$1.ZodOptional>>>;
title: z$1.ZodOptional;
permissionMode: z$1.ZodOptional, z$1.ZodLiteral<"workspace-write">]>, z$1.ZodTransform<"auto" | "accept-edits" | "full", "auto" | "accept-edits" | "full" | "workspace-write">>>;
+ }>, z$1.ZodLiteral<"workspace-write">]>, z$1.ZodTransform<"accept-edits" | "auto" | "full", "accept-edits" | "auto" | "full" | "workspace-write">>>;
visibility: z$1.ZodDefault>;
permissionMode: z$1.ZodOptional, z$1.ZodLiteral<"workspace-write">]>, z$1.ZodTransform<"auto" | "accept-edits" | "full", "auto" | "accept-edits" | "full" | "workspace-write">>>;
+ }>, z$1.ZodLiteral<"workspace-write">]>, z$1.ZodTransform<"accept-edits" | "auto" | "full", "accept-edits" | "auto" | "full" | "workspace-write">>>;
executionInputSources: z$1.ZodOptional>;
permissionMode: z$1.ZodOptional, z$1.ZodLiteral<"workspace-write">]>, z$1.ZodTransform<"auto" | "accept-edits" | "full", "auto" | "accept-edits" | "full" | "workspace-write">>>;
+ }>, z$1.ZodLiteral<"workspace-write">]>, z$1.ZodTransform<"accept-edits" | "auto" | "full", "accept-edits" | "auto" | "full" | "workspace-write">>>;
executionInputSources: z$1.ZodOptional;
permissionMode: z$1.ZodEnum<{
- auto: "auto";
"accept-edits": "accept-edits";
+ auto: "auto";
full: "full";
}>;
serviceTier: z$1.ZodEnum<{
@@ -8910,9 +8912,9 @@ declare const threadWithIncludesResponseSchema: z$1.ZodObject<{
isGitRepo: z$1.ZodBoolean;
isWorktree: z$1.ZodBoolean;
workspaceProvisionType: z$1.ZodEnum<{
- unmanaged: "unmanaged";
- "managed-worktree": "managed-worktree";
personal: "personal";
+ "managed-worktree": "managed-worktree";
+ unmanaged: "unmanaged";
}>;
branchName: z$1.ZodNullable;
baseBranch: z$1.ZodNullable;
@@ -8940,8 +8942,8 @@ declare const threadWithIncludesResponseSchema: z$1.ZodObject<{
connected: "connected";
}>;
maxPermissionMode: z$1.ZodEnum<{
- auto: "auto";
"accept-edits": "accept-edits";
+ auto: "auto";
full: "full";
}>;
lastSeenAt: z$1.ZodNullable;
@@ -9211,8 +9213,8 @@ declare const threadQueuedMessageListResponseSchema: z$1.ZodArray;
permissionMode: z$1.ZodEnum<{
- auto: "auto";
"accept-edits": "accept-edits";
+ auto: "auto";
full: "full";
}>;
serviceTier: z$1.ZodEnum<{
@@ -9580,8 +9582,8 @@ declare const threadTimelineResponseSchema: z$1.ZodObject<{
originalModel: z$1.ZodString;
fallbackModel: z$1.ZodString;
reason: z$1.ZodEnum<{
- refusal: "refusal";
provider: "provider";
+ refusal: "refusal";
}>;
message: z$1.ZodString;
}, z$1.core.$strip>>;
@@ -10120,9 +10122,12 @@ interface PluginPendingInteractionRegistration {
interface PluginSidebarFooterActionContext {
/**
* Navigate to this plugin's detail page in Tools, where declarative settings
- * and `settingsSection` slots render.
+ * and `settingsSection` slots render. Pass a section id to target one of
+ * this plugin's registered settings sections directly.
*/
- openSettings(): void;
+ openSettings(options?: {
+ sectionId?: string;
+ }): void;
}
/**
* An icon button in the app sidebar footer (next to Settings / bug report).
@@ -12804,6 +12809,53 @@ interface PluginHosts {
*/
declareSharedPorts(hostId: string, ports: readonly number[]): void;
}
+/** Why a cloud AI call failed; drives the host's local-fallback routing. */
+type CloudAiFailureCode = "unauthorized" | "quota_exhausted" | "unavailable";
+/**
+ * Result-shaped (never a thrown class) so the contract survives plugin
+ * bundling — an error class would have a different identity in the plugin
+ * bundle than in the host. A provider may still reject on abort; the host
+ * checks its own signal and treats that as a timeout.
+ */
+type CloudAiResult = {
+ ok: true;
+ value: T;
+} | {
+ ok: false;
+ code: CloudAiFailureCode;
+ message: string;
+};
+interface CloudAiCompleteArgs {
+ prompt: string;
+ /** JSON Schema (top-level `type: "object"`) the structured result must match. */
+ schema: {
+ [key: string]: JsonValue;
+ };
+ /** Host-owned abort signal; the host maps aborts to its timeout semantics. */
+ signal: AbortSignal;
+}
+interface CloudAiTranscribeArgs {
+ file: File;
+ prompt?: string;
+ /** Host-owned abort signal; the host maps aborts to its timeout semantics. */
+ signal: AbortSignal;
+}
+/**
+ * A cloud route for bb's small AI tasks (thread titles, commit messages,
+ * voice transcription). When the server's Cloud AI experiment is enabled and
+ * a registered provider `isAvailable()`, the host tries it before the locally
+ * configured `BB_INFERENCE`/`BB_TRANSCRIPTION` providers and falls back to
+ * those on `ok: false`.
+ */
+interface CloudAiProvider {
+ /**
+ * Cheap synchronous probe consulted per call — pairing state, user setting,
+ * failure latches. The host never calls `complete`/`transcribe` when false.
+ */
+ isAvailable(): boolean;
+ complete(args: CloudAiCompleteArgs): Promise>;
+ transcribe(args: CloudAiTranscribeArgs): Promise>;
+}
interface PluginStatusApi {
/**
* Mark this plugin `needs-configuration` (with a message shown in
@@ -12865,7 +12917,13 @@ interface BbPluginApi {
* The sanctioned place to clear timers and close connections.
*/
onDispose(hook: () => void | Promise): void;
+ /**
+ * EXPERIMENTAL (see docs/api_to_audit.md). Register a cloud route for bb's
+ * small AI tasks. Single slot host-wide: the most recent registration wins;
+ * it is unregistered automatically when this plugin reloads or disables.
+ */
+ experimental_registerCloudAiProvider(provider: CloudAiProvider): void;
}
export { PLUGIN_CLI_OUTPUT_MAX_BYTES, defineRpcContract };
-export type { BbContext, BbNavigate, BbPluginApi, ComposerCustomization, ComposerPlusMenuItem, ComposerRichTextSpec, ComposerStructuredDraft, ComposerView, JsonValue, MarkdownProps, NewThreadComposerProps, NewThreadRequest, PluginAgentConfiguration, PluginAgentConfigurationContext, PluginAgentToolContentPart, PluginAgentToolContext, PluginAgentToolExperimentalStatusLabels, PluginAgentToolRegistrationBase, PluginAgentToolResult, PluginAgentToolSelection, PluginAgents, PluginAppBuilder, PluginAppComposer, PluginAppContentScripts, PluginAppDefinition, PluginAppSetup, PluginAppSlots, PluginBackground, PluginCli, PluginCliCommandInfo, PluginCliContext, PluginCliExecutionResult, PluginCliOutputLimitError, PluginCliRegistration, PluginCliResult, PluginComposerApi, PluginComposerMention, PluginComposerScope, PluginComposerTextEffect, PluginComposerThreadRowStatus, PluginContentScriptContext, PluginContentScriptDisposer, PluginContentScriptRegistration, PluginEvents, PluginFileOpenerProps, PluginFileOpenerRegistration, PluginFileOpenerSource, PluginHomepageSectionProps, PluginHomepageSectionRegistration, PluginHosts, PluginHttp, PluginHttpAuthMode, PluginHttpHandler, PluginInteractionCancelReason, PluginInteractionRequest, PluginInteractionResult, PluginKvStorage, PluginLogger, PluginMentionItem, PluginMentionProviderRegistration, PluginMentionSearchContext, PluginMentionTrigger, PluginMessageActionContext, PluginMessageActionRegistration, PluginMessageActionThreadPanelOptions, PluginMessageDirectiveMessage, PluginMessageDirectiveOpenWorkspaceFile, PluginMessageDirectiveProps, PluginMessageDirectiveRegistration, PluginNavPanelProps, PluginNavPanelRegistration, PluginPendingInteractionProps, PluginPendingInteractionRegistration, PluginPendingInteractionView, PluginRealtime, PluginRealtimeConnectionState, PluginRpc, PluginRpcCallArgs, PluginRpcClient, PluginRpcContract, PluginRpcError, PluginRpcErrorCode, PluginRpcHandlers, PluginRpcIssuePathSegment, PluginRpcMethodContract, PluginRpcResult, PluginRpcValidationIssue, PluginSdkApp, PluginServerApi, PluginSettingDescriptor, PluginSettingDescriptors, PluginSettingValue, PluginSettings, PluginSettingsHandle, PluginSettingsSectionProps, PluginSettingsSectionRegistration, PluginSettingsState, PluginSettingsValues, PluginSharedPortTunnelIdentity, PluginSidebarFooterActionContext, PluginSidebarFooterActionProps, PluginSidebarFooterActionRegistration, PluginSidebarProject, PluginSidebarPullRequest, PluginSidebarSplitPane, PluginSidebarThread, PluginSidebarThreadActions, PluginSidebarThreadActivity, PluginSidebarThreadIndicator, PluginSidebarThreadPullRequestState, PluginSidebarThreadSplit, PluginSidebarThreadsState, PluginSidebarWorkspaceKind, PluginStatusApi, PluginStorage, PluginThreadEventHandler, PluginThreadEventName, PluginThreadEventPayloads, PluginThreadHeaderActionProps, PluginThreadHeaderActionRegistration, PluginThreadListProps, PluginThreadListRegistration, PluginThreadPanelActionContext, PluginThreadPanelActionRegistration, PluginThreadPanelProps, PluginUi, StandardSchemaV1, StandardSchemaV1InferInput, StandardSchemaV1InferOutput, StandardSchemaV1Issue, StandardSchemaV1Result, ThreadChatMessageAction, ThreadChatMessageReference, ThreadChatProps };
+export type { BbContext, BbNavigate, BbPluginApi, CloudAiCompleteArgs, CloudAiFailureCode, CloudAiProvider, CloudAiResult, CloudAiTranscribeArgs, ComposerCustomization, ComposerPlusMenuItem, ComposerRichTextSpec, ComposerStructuredDraft, ComposerView, JsonValue, MarkdownProps, NewThreadComposerProps, NewThreadRequest, PluginAgentConfiguration, PluginAgentConfigurationContext, PluginAgentToolContentPart, PluginAgentToolContext, PluginAgentToolExperimentalStatusLabels, PluginAgentToolRegistrationBase, PluginAgentToolResult, PluginAgentToolSelection, PluginAgents, PluginAppBuilder, PluginAppComposer, PluginAppContentScripts, PluginAppDefinition, PluginAppSetup, PluginAppSlots, PluginBackground, PluginCli, PluginCliCommandInfo, PluginCliContext, PluginCliExecutionResult, PluginCliOutputLimitError, PluginCliRegistration, PluginCliResult, PluginComposerApi, PluginComposerMention, PluginComposerScope, PluginComposerTextEffect, PluginComposerThreadRowStatus, PluginContentScriptContext, PluginContentScriptDisposer, PluginContentScriptRegistration, PluginEvents, PluginFileOpenerProps, PluginFileOpenerRegistration, PluginFileOpenerSource, PluginHomepageSectionProps, PluginHomepageSectionRegistration, PluginHosts, PluginHttp, PluginHttpAuthMode, PluginHttpHandler, PluginInteractionCancelReason, PluginInteractionRequest, PluginInteractionResult, PluginKvStorage, PluginLogger, PluginMentionItem, PluginMentionProviderRegistration, PluginMentionSearchContext, PluginMentionTrigger, PluginMessageActionContext, PluginMessageActionRegistration, PluginMessageActionThreadPanelOptions, PluginMessageDirectiveMessage, PluginMessageDirectiveOpenWorkspaceFile, PluginMessageDirectiveProps, PluginMessageDirectiveRegistration, PluginNavPanelProps, PluginNavPanelRegistration, PluginPendingInteractionProps, PluginPendingInteractionRegistration, PluginPendingInteractionView, PluginRealtime, PluginRealtimeConnectionState, PluginRpc, PluginRpcCallArgs, PluginRpcClient, PluginRpcContract, PluginRpcError, PluginRpcErrorCode, PluginRpcHandlers, PluginRpcIssuePathSegment, PluginRpcMethodContract, PluginRpcResult, PluginRpcValidationIssue, PluginSdkApp, PluginServerApi, PluginSettingDescriptor, PluginSettingDescriptors, PluginSettingValue, PluginSettings, PluginSettingsHandle, PluginSettingsSectionProps, PluginSettingsSectionRegistration, PluginSettingsState, PluginSettingsValues, PluginSharedPortTunnelIdentity, PluginSidebarFooterActionContext, PluginSidebarFooterActionProps, PluginSidebarFooterActionRegistration, PluginSidebarProject, PluginSidebarPullRequest, PluginSidebarSplitPane, PluginSidebarThread, PluginSidebarThreadActions, PluginSidebarThreadActivity, PluginSidebarThreadIndicator, PluginSidebarThreadPullRequestState, PluginSidebarThreadSplit, PluginSidebarThreadsState, PluginSidebarWorkspaceKind, PluginStatusApi, PluginStorage, PluginThreadEventHandler, PluginThreadEventName, PluginThreadEventPayloads, PluginThreadHeaderActionProps, PluginThreadHeaderActionRegistration, PluginThreadListProps, PluginThreadListRegistration, PluginThreadPanelActionContext, PluginThreadPanelActionRegistration, PluginThreadPanelProps, PluginUi, StandardSchemaV1, StandardSchemaV1InferInput, StandardSchemaV1InferOutput, StandardSchemaV1Issue, StandardSchemaV1Result, ThreadChatMessageAction, ThreadChatMessageReference, ThreadChatProps };
diff --git a/packages/plugin-sdk/src/__tests__/public-types.test.ts b/packages/plugin-sdk/src/__tests__/public-types.test.ts
index d52dd12199..d9f9754ac0 100644
--- a/packages/plugin-sdk/src/__tests__/public-types.test.ts
+++ b/packages/plugin-sdk/src/__tests__/public-types.test.ts
@@ -7,6 +7,7 @@ type ExpectedBbPluginApiKey =
| "background"
| "cli"
| "events"
+ | "experimental_registerCloudAiProvider"
| "hosts"
| "http"
| "log"
@@ -23,6 +24,11 @@ type ExpectedBbPluginApiKey =
const EXPECTED_BACKEND_ROOT_TYPE_EXPORTS = [
"BbPluginApi",
+ "CloudAiCompleteArgs",
+ "CloudAiFailureCode",
+ "CloudAiProvider",
+ "CloudAiResult",
+ "CloudAiTranscribeArgs",
"PluginAgents",
"PluginAgentConfiguration",
"PluginAgentConfigurationContext",
diff --git a/packages/plugin-sdk/src/app-contract.ts b/packages/plugin-sdk/src/app-contract.ts
index 9b0a8efcf5..a0c9276119 100644
--- a/packages/plugin-sdk/src/app-contract.ts
+++ b/packages/plugin-sdk/src/app-contract.ts
@@ -283,9 +283,10 @@ export interface PluginPendingInteractionRegistration {
export interface PluginSidebarFooterActionContext {
/**
* Navigate to this plugin's detail page in Tools, where declarative settings
- * and `settingsSection` slots render.
+ * and `settingsSection` slots render. Pass a section id to target one of
+ * this plugin's registered settings sections directly.
*/
- openSettings(): void;
+ openSettings(options?: { sectionId?: string }): void;
}
/**
diff --git a/packages/plugin-sdk/src/backend-contract.ts b/packages/plugin-sdk/src/backend-contract.ts
index 57fde780e4..578eddca13 100644
--- a/packages/plugin-sdk/src/backend-contract.ts
+++ b/packages/plugin-sdk/src/backend-contract.ts
@@ -640,6 +640,58 @@ export interface PluginHosts {
declareSharedPorts(hostId: string, ports: readonly number[]): void;
}
+// ---------------------------------------------------------------------------
+// Cloud AI provider (experimental).
+// ---------------------------------------------------------------------------
+
+/** Why a cloud AI call failed; drives the host's local-fallback routing. */
+export type CloudAiFailureCode =
+ | "unauthorized"
+ | "quota_exhausted"
+ | "unavailable";
+
+/**
+ * Result-shaped (never a thrown class) so the contract survives plugin
+ * bundling — an error class would have a different identity in the plugin
+ * bundle than in the host. A provider may still reject on abort; the host
+ * checks its own signal and treats that as a timeout.
+ */
+export type CloudAiResult =
+ | { ok: true; value: T }
+ | { ok: false; code: CloudAiFailureCode; message: string };
+
+export interface CloudAiCompleteArgs {
+ prompt: string;
+ /** JSON Schema (top-level `type: "object"`) the structured result must match. */
+ schema: { [key: string]: JsonValue };
+ /** Host-owned abort signal; the host maps aborts to its timeout semantics. */
+ signal: AbortSignal;
+}
+
+export interface CloudAiTranscribeArgs {
+ file: File;
+ prompt?: string;
+ /** Host-owned abort signal; the host maps aborts to its timeout semantics. */
+ signal: AbortSignal;
+}
+
+/**
+ * A cloud route for bb's small AI tasks (thread titles, commit messages,
+ * voice transcription). When the server's Cloud AI experiment is enabled and
+ * a registered provider `isAvailable()`, the host tries it before the locally
+ * configured `BB_INFERENCE`/`BB_TRANSCRIPTION` providers and falls back to
+ * those on `ok: false`.
+ */
+export interface CloudAiProvider {
+ /**
+ * Cheap synchronous probe consulted per call — pairing state, user setting,
+ * failure latches. The host never calls `complete`/`transcribe` when false.
+ */
+ isAvailable(): boolean;
+ complete(args: CloudAiCompleteArgs): Promise>;
+ transcribe(args: CloudAiTranscribeArgs): Promise>;
+}
+
// ---------------------------------------------------------------------------
// Status + the API root.
// ---------------------------------------------------------------------------
@@ -706,4 +758,10 @@ export interface BbPluginApi {
* The sanctioned place to clear timers and close connections.
*/
onDispose(hook: () => void | Promise): void;
+ /**
+ * EXPERIMENTAL (see docs/api_to_audit.md). Register a cloud route for bb's
+ * small AI tasks. Single slot host-wide: the most recent registration wins;
+ * it is unregistered automatically when this plugin reloads or disables.
+ */
+ experimental_registerCloudAiProvider(provider: CloudAiProvider): void;
}
diff --git a/packages/plugin-sdk/src/testing/fake-plugin-host.ts b/packages/plugin-sdk/src/testing/fake-plugin-host.ts
index 8b8049701a..f5c1957116 100644
--- a/packages/plugin-sdk/src/testing/fake-plugin-host.ts
+++ b/packages/plugin-sdk/src/testing/fake-plugin-host.ts
@@ -1857,6 +1857,11 @@ function createFakePluginHostInternal(
assertLive();
disposeHooks.push(hook);
},
+ experimental_registerCloudAiProvider() {
+ assertLive();
+ // The fake host has no AI chokepoints to route; registration is
+ // accepted and torn down with the host like the real one.
+ },
};
async function disposeHost(cleanupStorage: boolean): Promise {
diff --git a/packages/shared-ui/src/components/ui/icon.tsx b/packages/shared-ui/src/components/ui/icon.tsx
index 0e7c1ca82c..6d5e71e93e 100644
--- a/packages/shared-ui/src/components/ui/icon.tsx
+++ b/packages/shared-ui/src/components/ui/icon.tsx
@@ -309,6 +309,7 @@ const ICON_MAP = {
CircleQuestion: HelpCircleIcon,
CircleX: CancelCircleIcon,
Clock: Clock01Icon,
+ Cloud: CloudIcon,
Code: SourceCodeIcon,
ComputerTerminal01: ComputerTerminal01Icon,
Columns2: LayoutTwoColumnIcon,
diff --git a/packages/templates/src/generated/plugin-sdk-dts.generated.ts b/packages/templates/src/generated/plugin-sdk-dts.generated.ts
index 93b0884a52..a9726eb360 100644
--- a/packages/templates/src/generated/plugin-sdk-dts.generated.ts
+++ b/packages/templates/src/generated/plugin-sdk-dts.generated.ts
@@ -2,6 +2,6 @@
// Generated by packages/templates/scripts/generate-templates.mjs from
// @bb/plugin-sdk/bundled-types. Do not edit directly.
-export const PLUGIN_SDK_DTS = "// Portable type declarations for `@bb/plugin-sdk`. Unpublished BB\n// workspace contracts are flattened; public subpaths may reuse the\n// package root without requiring any other @bb/* package.\n//\n// Confused by the API, or need a symbol that isn't here? Clone the BB repo\n// and read the real source: https://github.com/get-bb/bb\n\nimport * as react from 'react';\nimport { ComponentType, ReactNode } from 'react';\nimport * as z from 'zod';\nimport { z as z$1 } from 'zod';\nimport Database from 'better-sqlite3';\nimport { Context } from 'hono';\n\n/**\n * App-wide server-backed preferences.\n * Client-local settings stay in the frontend localStorage helpers instead.\n */\ndeclare const appSettingsSchema: z$1.ZodObject<{\n caffeinate: z$1.ZodBoolean;\n showKeyboardHints: z$1.ZodBoolean;\n steerActiveThreadOnEnter: z$1.ZodBoolean;\n showUnhandledProviderEvents: z$1.ZodBoolean;\n codexMemoryEnabled: z$1.ZodBoolean;\n claudeCodeMemoryEnabled: z$1.ZodBoolean;\n codexSubagentsDisabled: z$1.ZodBoolean;\n claudeCodeSubagentsDisabled: z$1.ZodBoolean;\n claudeCodeWorkflowsDisabled: z$1.ZodBoolean;\n onboardingCompletedAt: z$1.ZodNullable;\n}, z$1.core.$strict>;\ntype AppSettings = z$1.infer;\n\ndeclare const appKeybindingOverridesSchema: z$1.ZodArray;\n shortcut: z$1.ZodNullable>;\n}, z$1.core.$strict>>;\ntype AppKeybindingOverrides = z$1.infer;\n\ndeclare const appThemeSchema: z$1.ZodObject<{\n themeId: z$1.ZodString;\n customCss: z$1.ZodNullable;\n faviconColor: z$1.ZodEnum<{\n default: \"default\";\n red: \"red\";\n orange: \"orange\";\n yellow: \"yellow\";\n green: \"green\";\n teal: \"teal\";\n blue: \"blue\";\n purple: \"purple\";\n pink: \"pink\";\n }>;\n}, z$1.core.$strip>;\ntype AppTheme = z$1.infer;\n/**\n * The complete appearance selection a client sends when changing the palette\n * and/or favicon tint. The server validates `themeId` (built-in id or an\n * existing custom theme) and resolves the CSS from disk for custom themes.\n * Callers changing only one facet must carry the other facet forward explicitly.\n */\ndeclare const appThemeSelectionSchema: z$1.ZodObject<{\n themeId: z$1.ZodString;\n faviconColor: z$1.ZodEnum<{\n default: \"default\";\n red: \"red\";\n orange: \"orange\";\n yellow: \"yellow\";\n green: \"green\";\n teal: \"teal\";\n blue: \"blue\";\n purple: \"purple\";\n pink: \"pink\";\n }>;\n}, z$1.core.$strip>;\ntype AppThemeSelection = z$1.infer;\n\ndeclare const changedMessageSchema: z$1.ZodDiscriminatedUnion<[z$1.ZodObject<{\n type: z$1.ZodLiteral<\"changed\">;\n entity: z$1.ZodLiteral<\"thread\">;\n id: z$1.ZodOptional;\n metadata: z$1.ZodOptional;\n eventTypes: z$1.ZodOptional>>>>;\n hasPendingInteraction: z$1.ZodOptional;\n projectId: z$1.ZodOptional;\n }, z$1.core.$strict>>;\n changes: z$1.ZodReadonly>>;\n}, z$1.core.$strict>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"changed\">;\n entity: z$1.ZodLiteral<\"project\">;\n id: z$1.ZodOptional;\n changes: z$1.ZodReadonly>>;\n}, z$1.core.$strict>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"changed\">;\n entity: z$1.ZodLiteral<\"environment\">;\n id: z$1.ZodOptional;\n changes: z$1.ZodReadonly>>;\n}, z$1.core.$strict>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"changed\">;\n entity: z$1.ZodLiteral<\"host\">;\n id: z$1.ZodOptional;\n changes: z$1.ZodReadonly>>;\n}, z$1.core.$strict>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"changed\">;\n entity: z$1.ZodLiteral<\"system\">;\n changes: z$1.ZodReadonly>>;\n}, z$1.core.$strict>], \"entity\">;\ntype ChangedMessage = z$1.infer;\n\ndeclare const environmentSchema: z$1.ZodObject<{\n id: z$1.ZodString;\n name: z$1.ZodNullable;\n projectId: z$1.ZodString;\n hostId: z$1.ZodString;\n path: z$1.ZodNullable;\n managed: z$1.ZodBoolean;\n isGitRepo: z$1.ZodBoolean;\n isWorktree: z$1.ZodBoolean;\n workspaceProvisionType: z$1.ZodEnum<{\n unmanaged: \"unmanaged\";\n \"managed-worktree\": \"managed-worktree\";\n personal: \"personal\";\n }>;\n branchName: z$1.ZodNullable;\n baseBranch: z$1.ZodNullable;\n defaultBranch: z$1.ZodNullable;\n mergeBaseBranch: z$1.ZodNullable;\n status: z$1.ZodEnum<{\n error: \"error\";\n provisioning: \"provisioning\";\n ready: \"ready\";\n retiring: \"retiring\";\n destroying: \"destroying\";\n destroyed: \"destroyed\";\n }>;\n createdAt: z$1.ZodNumber;\n updatedAt: z$1.ZodNumber;\n}, z$1.core.$strip>;\ntype Environment = z$1.infer;\n\n/**\n * User-opt-in experiments (the Settings → Experiments toggles). Distinct from\n * `FeatureFlags`: flags are operator-set via env at server start, experiments\n * are user-toggled at runtime and persisted server-side so server-owned\n * policy (e.g. skill injection) can honor them.\n *\n * Every experiment defaults to off — opting in is the point.\n */\ndeclare const experimentsSchema: z$1.ZodObject<{\n claudeCodeMockCliTraffic: z$1.ZodBoolean;\n newOnboarding: z$1.ZodBoolean;\n toolsHub: z$1.ZodBoolean;\n}, z$1.core.$strip>;\ntype Experiments = z$1.infer;\n\ndeclare const hostSchema: z$1.ZodObject<{\n id: z$1.ZodString;\n name: z$1.ZodString;\n type: z$1.ZodEnum<{\n persistent: \"persistent\";\n }>;\n status: z$1.ZodEnum<{\n connected: \"connected\";\n disconnected: \"disconnected\";\n }>;\n maxPermissionMode: z$1.ZodEnum<{\n full: \"full\";\n auto: \"auto\";\n \"accept-edits\": \"accept-edits\";\n }>;\n lastSeenAt: z$1.ZodNullable;\n lastRejectedProtocolVersion: z$1.ZodNullable;\n createdAt: z$1.ZodNumber;\n updatedAt: z$1.ZodNumber;\n}, z$1.core.$strip>;\ntype Host = z$1.infer;\n\ninterface JsonObject {\n [key: string]: JsonValue$1;\n}\ntype JsonValue$1 = string | number | boolean | null | JsonValue$1[] | JsonObject;\n\ndeclare const pendingInteractionResolutionSchema: z$1.ZodUnion;\n grantedPermissions: z$1.ZodNullable;\n }, z$1.core.$strip>>;\n fileSystem: z$1.ZodNullable;\n write: z$1.ZodArray;\n }, z$1.core.$strip>>;\n }, z$1.core.$strict>>;\n}, z$1.core.$strip>, z$1.ZodObject<{\n decision: z$1.ZodLiteral<\"allow_for_session\">;\n grantedPermissions: z$1.ZodNullable;\n }, z$1.core.$strip>>;\n fileSystem: z$1.ZodNullable;\n write: z$1.ZodArray;\n }, z$1.core.$strip>>;\n }, z$1.core.$strict>>;\n}, z$1.core.$strip>, z$1.ZodObject<{\n decision: z$1.ZodLiteral<\"deny\">;\n}, z$1.core.$strip>], \"decision\">, z$1.ZodObject<{\n kind: z$1.ZodLiteral<\"user_answer\">;\n answers: z$1.ZodRecord;\n freeText: z$1.ZodOptional;\n }, z$1.core.$strip>>;\n}, z$1.core.$strip>, z$1.ZodObject<{\n kind: z$1.ZodLiteral<\"plugin_submitted\">;\n}, z$1.core.$strip>]>;\ntype PendingInteractionResolution = z$1.infer;\ndeclare const providerPendingInteractionSchema: z$1.ZodObject<{\n id: z$1.ZodString;\n threadId: z$1.ZodString;\n status: z$1.ZodEnum<{\n pending: \"pending\";\n interrupted: \"interrupted\";\n resolving: \"resolving\";\n resolved: \"resolved\";\n }>;\n statusReason: z$1.ZodNullable;\n createdAt: z$1.ZodNumber;\n expiresAt: z$1.ZodOptional>;\n resolvedAt: z$1.ZodNullable;\n turnId: z$1.ZodString;\n providerId: z$1.ZodString;\n providerThreadId: z$1.ZodString;\n providerRequestId: z$1.ZodString;\n origin: z$1.ZodOptional;\n providerId: z$1.ZodString;\n providerThreadId: z$1.ZodString;\n providerRequestId: z$1.ZodString;\n }, z$1.core.$strip>>;\n payload: z$1.ZodUnion;\n subject: z$1.ZodDiscriminatedUnion<[z$1.ZodObject<{\n kind: z$1.ZodLiteral<\"command\">;\n itemId: z$1.ZodString;\n command: z$1.ZodString;\n cwd: z$1.ZodNullable;\n actions: z$1.ZodArray;\n command: z$1.ZodString;\n name: z$1.ZodString;\n path: z$1.ZodString;\n }, z$1.core.$strip>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"listFiles\">;\n command: z$1.ZodString;\n path: z$1.ZodNullable;\n }, z$1.core.$strip>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"search\">;\n command: z$1.ZodString;\n query: z$1.ZodNullable;\n path: z$1.ZodNullable;\n }, z$1.core.$strip>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"unknown\">;\n command: z$1.ZodString;\n }, z$1.core.$strip>], \"type\">>;\n sessionGrant: z$1.ZodNullable;\n }, z$1.core.$strip>>;\n fileSystem: z$1.ZodNullable;\n write: z$1.ZodArray;\n }, z$1.core.$strip>>;\n }, z$1.core.$strict>>;\n }, z$1.core.$strip>, z$1.ZodObject<{\n kind: z$1.ZodLiteral<\"file_change\">;\n itemId: z$1.ZodString;\n writeScope: z$1.ZodNullable;\n sessionGrant: z$1.ZodNullable;\n }, z$1.core.$strip>>;\n fileSystem: z$1.ZodNullable;\n write: z$1.ZodArray;\n }, z$1.core.$strip>>;\n }, z$1.core.$strict>>;\n }, z$1.core.$strip>, z$1.ZodObject<{\n kind: z$1.ZodLiteral<\"permission_grant\">;\n itemId: z$1.ZodString;\n toolName: z$1.ZodNullable;\n permissions: z$1.ZodObject<{\n network: z$1.ZodNullable;\n }, z$1.core.$strip>>;\n fileSystem: z$1.ZodNullable;\n write: z$1.ZodArray;\n }, z$1.core.$strip>>;\n }, z$1.core.$strict>;\n }, z$1.core.$strip>], \"kind\">;\n reason: z$1.ZodNullable;\n availableDecisions: z$1.ZodArray>;\n }, z$1.core.$strip>, z$1.ZodObject<{\n kind: z$1.ZodLiteral<\"user_question\">;\n questions: z$1.ZodArray;\n multiSelect: z$1.ZodBoolean;\n options: z$1.ZodOptional;\n }, z$1.core.$strip>>>;\n allowFreeText: z$1.ZodBoolean;\n }, z$1.core.$strip>>;\n }, z$1.core.$strip>]>;\n resolution: z$1.ZodNullable;\n grantedPermissions: z$1.ZodNullable;\n }, z$1.core.$strip>>;\n fileSystem: z$1.ZodNullable;\n write: z$1.ZodArray;\n }, z$1.core.$strip>>;\n }, z$1.core.$strict>>;\n }, z$1.core.$strip>, z$1.ZodObject<{\n decision: z$1.ZodLiteral<\"allow_for_session\">;\n grantedPermissions: z$1.ZodNullable;\n }, z$1.core.$strip>>;\n fileSystem: z$1.ZodNullable;\n write: z$1.ZodArray;\n }, z$1.core.$strip>>;\n }, z$1.core.$strict>>;\n }, z$1.core.$strip>, z$1.ZodObject<{\n decision: z$1.ZodLiteral<\"deny\">;\n }, z$1.core.$strip>], \"decision\">, z$1.ZodObject<{\n kind: z$1.ZodLiteral<\"user_answer\">;\n answers: z$1.ZodRecord;\n freeText: z$1.ZodOptional;\n }, z$1.core.$strip>>;\n }, z$1.core.$strip>]>>;\n}, z$1.core.$strip>;\ntype ProviderPendingInteraction = z$1.infer;\ndeclare const pluginPendingInteractionSchema: z$1.ZodObject<{\n id: z$1.ZodString;\n threadId: z$1.ZodString;\n status: z$1.ZodEnum<{\n pending: \"pending\";\n interrupted: \"interrupted\";\n resolving: \"resolving\";\n resolved: \"resolved\";\n }>;\n statusReason: z$1.ZodNullable;\n createdAt: z$1.ZodNumber;\n expiresAt: z$1.ZodOptional>;\n resolvedAt: z$1.ZodNullable;\n turnId: z$1.ZodNullable;\n origin: z$1.ZodObject<{\n kind: z$1.ZodLiteral<\"plugin\">;\n pluginId: z$1.ZodString;\n rendererId: z$1.ZodString;\n }, z$1.core.$strip>;\n payload: z$1.ZodObject<{\n kind: z$1.ZodLiteral<\"plugin\">;\n title: z$1.ZodString;\n data: z$1.ZodType>;\n }, z$1.core.$strip>;\n resolution: z$1.ZodNullable;\n }, z$1.core.$strip>>;\n}, z$1.core.$strip>;\ntype PluginPendingInteraction = z$1.infer;\ntype PendingInteraction = ProviderPendingInteraction | PluginPendingInteraction;\n\ndeclare const projectSourceSchema: z$1.ZodObject<{\n id: z$1.ZodString;\n projectId: z$1.ZodString;\n isDefault: z$1.ZodBoolean;\n createdAt: z$1.ZodNumber;\n updatedAt: z$1.ZodNumber;\n type: z$1.ZodLiteral<\"local_path\">;\n hostId: z$1.ZodString;\n path: z$1.ZodString;\n}, z$1.core.$strip>;\ntype ProjectSource = z$1.infer;\n\ndeclare const reasoningLevelSchema: z$1.ZodEnum<{\n none: \"none\";\n low: \"low\";\n medium: \"medium\";\n high: \"high\";\n xhigh: \"xhigh\";\n ultracode: \"ultracode\";\n max: \"max\";\n ultra: \"ultra\";\n}>;\ntype ReasoningLevel = z$1.infer;\ndeclare const serviceTierSchema: z$1.ZodEnum<{\n default: \"default\";\n fast: \"fast\";\n}>;\ntype ServiceTier = z$1.infer;\ndeclare const permissionModeSchema: z$1.ZodEnum<{\n full: \"full\";\n auto: \"auto\";\n \"accept-edits\": \"accept-edits\";\n}>;\ntype PermissionMode = z$1.infer;\ndeclare const promptInputSchema: z$1.ZodDiscriminatedUnion<[z$1.ZodObject<{\n visibility: z$1.ZodOptional>;\n type: z$1.ZodLiteral<\"text\">;\n text: z$1.ZodString;\n mentions: z$1.ZodDefault, z$1.ZodDiscriminatedUnion<[z$1.ZodObject<{\n kind: z$1.ZodLiteral<\"thread\">;\n threadId: z$1.ZodString;\n projectId: z$1.ZodOptional;\n label: z$1.ZodString;\n }, z$1.core.$strip>, z$1.ZodObject<{\n kind: z$1.ZodLiteral<\"project\">;\n projectId: z$1.ZodString;\n label: z$1.ZodString;\n }, z$1.core.$strip>, z$1.ZodObject<{\n kind: z$1.ZodLiteral<\"section\">;\n sectionId: z$1.ZodString;\n label: z$1.ZodString;\n }, z$1.core.$strip>, z$1.ZodObject<{\n kind: z$1.ZodLiteral<\"path\">;\n source: z$1.ZodEnum<{\n workspace: \"workspace\";\n \"thread-storage\": \"thread-storage\";\n }>;\n entryKind: z$1.ZodEnum<{\n file: \"file\";\n directory: \"directory\";\n }>;\n path: z$1.ZodString;\n label: z$1.ZodString;\n }, z$1.core.$strip>, z$1.ZodObject<{\n kind: z$1.ZodLiteral<\"command\">;\n trigger: z$1.ZodEnum<{\n \"/\": \"/\";\n }>;\n name: z$1.ZodString;\n source: z$1.ZodEnum<{\n command: \"command\";\n skill: \"skill\";\n }>;\n origin: z$1.ZodEnum<{\n user: \"user\";\n project: \"project\";\n builtin: \"builtin\";\n }>;\n label: z$1.ZodString;\n argumentHint: z$1.ZodNullable;\n }, z$1.core.$strip>, z$1.ZodObject<{\n kind: z$1.ZodLiteral<\"plugin\">;\n pluginId: z$1.ZodString;\n icon: z$1.ZodOptional>;\n itemId: z$1.ZodString;\n label: z$1.ZodString;\n }, z$1.core.$strip>], \"kind\">>;\n }, z$1.core.$strip>>>;\n}, z$1.core.$strip>, z$1.ZodObject<{\n visibility: z$1.ZodOptional>;\n type: z$1.ZodLiteral<\"image\">;\n url: z$1.ZodString;\n}, z$1.core.$strip>, z$1.ZodObject<{\n visibility: z$1.ZodOptional>;\n type: z$1.ZodLiteral<\"localImage\">;\n path: z$1.ZodString;\n}, z$1.core.$strip>, z$1.ZodObject<{\n visibility: z$1.ZodOptional>;\n type: z$1.ZodLiteral<\"localFile\">;\n path: z$1.ZodString;\n name: z$1.ZodOptional;\n sizeBytes: z$1.ZodOptional;\n mimeType: z$1.ZodOptional;\n}, z$1.core.$strip>], \"type\">;\ntype PromptInput = z$1.infer;\ndeclare const resolvedThreadExecutionOptionsSchema: z$1.ZodObject<{\n seq: z$1.ZodOptional;\n model: z$1.ZodString;\n serviceTier: z$1.ZodEnum<{\n default: \"default\";\n fast: \"fast\";\n }>;\n reasoningLevel: z$1.ZodEnum<{\n none: \"none\";\n low: \"low\";\n medium: \"medium\";\n high: \"high\";\n xhigh: \"xhigh\";\n ultracode: \"ultracode\";\n max: \"max\";\n ultra: \"ultra\";\n }>;\n permissionMode: z$1.ZodEnum<{\n full: \"full\";\n auto: \"auto\";\n \"accept-edits\": \"accept-edits\";\n }>;\n source: z$1.ZodEnum<{\n \"client/thread/start\": \"client/thread/start\";\n \"client/turn/requested\": \"client/turn/requested\";\n \"client/turn/start\": \"client/turn/start\";\n }>;\n}, z$1.core.$strip>;\ntype ResolvedThreadExecutionOptions = z$1.infer;\ndeclare const projectExecutionDefaultsSchema: z$1.ZodObject<{\n providerId: z$1.ZodString;\n model: z$1.ZodString;\n serviceTier: z$1.ZodEnum<{\n default: \"default\";\n fast: \"fast\";\n }>;\n reasoningLevel: z$1.ZodEnum<{\n none: \"none\";\n low: \"low\";\n medium: \"medium\";\n high: \"high\";\n xhigh: \"xhigh\";\n ultracode: \"ultracode\";\n max: \"max\";\n ultra: \"ultra\";\n }>;\n permissionMode: z$1.ZodEnum<{\n full: \"full\";\n auto: \"auto\";\n \"accept-edits\": \"accept-edits\";\n }>;\n}, z$1.core.$strip>;\ntype ProjectExecutionDefaults = z$1.infer;\n\n/** All thread events — provider-originated or system-originated. */\ndeclare const threadEventSchema: z$1.ZodPipe;\n threadId: z$1.ZodString;\n}, z$1.core.$strip>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"thread/identity\">;\n threadId: z$1.ZodString;\n providerThreadId: z$1.ZodString;\n}, z$1.core.$strip>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"turn/started\">;\n threadId: z$1.ZodString;\n providerThreadId: z$1.ZodString;\n parentToolCallId: z$1.ZodOptional;\n}, z$1.core.$strip>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"turn/completed\">;\n threadId: z$1.ZodString;\n providerThreadId: z$1.ZodNullable;\n status: z$1.ZodEnum<{\n completed: \"completed\";\n failed: \"failed\";\n interrupted: \"interrupted\";\n }>;\n error: z$1.ZodOptional>;\n}, z$1.core.$strip>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"turn/input/accepted\">;\n threadId: z$1.ZodString;\n providerThreadId: z$1.ZodString;\n clientRequestId: z$1.ZodString;\n scope: z$1.ZodDiscriminatedUnion<[z$1.ZodObject<{\n kind: z$1.ZodLiteral<\"thread\">;\n }, z$1.core.$strip>, z$1.ZodObject<{\n kind: z$1.ZodLiteral<\"turn\">;\n turnId: z$1.ZodString;\n }, z$1.core.$strip>], \"kind\">;\n}, z$1.core.$strict>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"thread/name/updated\">;\n threadId: z$1.ZodString;\n providerThreadId: z$1.ZodString;\n threadName: z$1.ZodString;\n}, z$1.core.$strip>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"thread/compacted\">;\n threadId: z$1.ZodString;\n providerThreadId: z$1.ZodString;\n}, z$1.core.$strip>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"thread/goal/updated\">;\n threadId: z$1.ZodString;\n providerThreadId: z$1.ZodString;\n objective: z$1.ZodString;\n status: z$1.ZodEnum<{\n paused: \"paused\";\n active: \"active\";\n budgetLimited: \"budgetLimited\";\n complete: \"complete\";\n }>;\n tokenBudget: z$1.ZodNullable;\n tokensUsed: z$1.ZodNumber;\n timeUsedSeconds: z$1.ZodNumber;\n}, z$1.core.$strip>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"thread/goal/cleared\">;\n threadId: z$1.ZodString;\n providerThreadId: z$1.ZodString;\n}, z$1.core.$strip>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"item/started\">;\n threadId: z$1.ZodString;\n providerThreadId: z$1.ZodString;\n item: z$1.ZodDiscriminatedUnion<[z$1.ZodObject<{\n type: z$1.ZodLiteral<\"userMessage\">;\n id: z$1.ZodString;\n content: z$1.ZodArray;\n text: z$1.ZodString;\n }, z$1.core.$strip>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"image\">;\n url: z$1.ZodString;\n }, z$1.core.$strip>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"localImage\">;\n path: z$1.ZodString;\n }, z$1.core.$strip>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"localFile\">;\n path: z$1.ZodString;\n }, z$1.core.$strip>], \"type\">>;\n clientRequestId: z$1.ZodOptional;\n parentToolCallId: z$1.ZodOptional;\n }, z$1.core.$strict>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"agentMessage\">;\n id: z$1.ZodString;\n text: z$1.ZodString;\n parentToolCallId: z$1.ZodOptional;\n }, z$1.core.$strip>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"commandExecution\">;\n id: z$1.ZodString;\n command: z$1.ZodString;\n cwd: z$1.ZodString;\n status: z$1.ZodEnum<{\n pending: \"pending\";\n completed: \"completed\";\n failed: \"failed\";\n interrupted: \"interrupted\";\n }>;\n approvalStatus: z$1.ZodNullable>;\n aggregatedOutput: z$1.ZodOptional;\n exitCode: z$1.ZodOptional;\n durationMs: z$1.ZodOptional;\n truncation: z$1.ZodOptional>;\n result: z$1.ZodOptional>;\n resultText: z$1.ZodOptional>;\n }, z$1.core.$strip>>;\n parentToolCallId: z$1.ZodOptional;\n }, z$1.core.$strip>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"fileChange\">;\n id: z$1.ZodString;\n changes: z$1.ZodArray;\n movePath: z$1.ZodOptional;\n diff: z$1.ZodOptional;\n }, z$1.core.$strip>>;\n status: z$1.ZodEnum<{\n pending: \"pending\";\n completed: \"completed\";\n failed: \"failed\";\n interrupted: \"interrupted\";\n }>;\n approvalStatus: z$1.ZodNullable>;\n parentToolCallId: z$1.ZodOptional;\n }, z$1.core.$strip>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"webSearch\">;\n id: z$1.ZodString;\n queries: z$1.ZodArray;\n resultText: z$1.ZodNullable;\n parentToolCallId: z$1.ZodOptional;\n }, z$1.core.$strip>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"webFetch\">;\n id: z$1.ZodString;\n url: z$1.ZodString;\n prompt: z$1.ZodNullable;\n pattern: z$1.ZodNullable;\n resultText: z$1.ZodNullable;\n parentToolCallId: z$1.ZodOptional;\n }, z$1.core.$strip>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"imageView\">;\n id: z$1.ZodString;\n path: z$1.ZodString;\n parentToolCallId: z$1.ZodOptional;\n }, z$1.core.$strip>, z$1.ZodObject<{\n type: z$1.ZodLiteral<\"toolCall\">;\n id: z$1.ZodString;\n server: z$1.ZodOptional;\n tool: z$1.ZodString;\n arguments: z$1.ZodOptional