diff --git a/apps/cli/src/__tests__/command-output/thread-spawn.test.ts b/apps/cli/src/__tests__/command-output/thread-spawn.test.ts index a4d02129287..a96f1cddd62 100644 --- a/apps/cli/src/__tests__/command-output/thread-spawn.test.ts +++ b/apps/cli/src/__tests__/command-output/thread-spawn.test.ts @@ -27,6 +27,42 @@ describe("bb thread spawn command output", () => { return vi.spyOn(process.stderr, "write").mockImplementation(() => true); } + it("saves the first message with the shipped Drafts submission", async () => { + const post = vi.fn(async ({ json }: { json: unknown }) => { + createThreadRequestSchema.parse(json); + return fixtures.makeThread({ + id: "thread-draft", + projectId: "proj-1", + providerId: "codex", + status: "pending", + }); + }); + stubServerApi({ "v1.threads.$post": post }); + + await runCommand( + [ + "thread", + "spawn", + "--project", + "proj-1", + "--prompt", + "Save this", + "--draft", + ], + register, + ); + + expect(post).toHaveBeenCalledWith({ + json: expect.objectContaining({ + pluginSubmission: { pluginId: "drafts", data: { kind: "draft" } }, + input: [{ type: "text", text: "Save this", mentions: [] }], + }), + }); + expect(collectLogLines(vi.mocked(console.log))[0]).toBe( + "Draft saved: thread-draft", + ); + }); + it("rejects explicitly empty lifecycle ownership instead of creating an independent thread", async () => { const post = vi.fn(async ({ json }: { json: unknown }) => { createThreadRequestSchema.parse(json); diff --git a/apps/cli/src/__tests__/command-output/thread-tell.test.ts b/apps/cli/src/__tests__/command-output/thread-tell.test.ts index b94badaed04..a0e7eb34b30 100644 --- a/apps/cli/src/__tests__/command-output/thread-tell.test.ts +++ b/apps/cli/src/__tests__/command-output/thread-tell.test.ts @@ -18,6 +18,60 @@ describe("bb thread tell command output", () => { const register: CommandRegistrar = (program) => registerThreadCommands(program, () => "http://server"); + it("saves a follow-up draft without steering the active turn", async () => { + const post = vi.fn(async () => ({ + ok: true, + delivery: "queued", + queuedMessage: { + id: "qm-draft", + waitingOn: { kind: "plugin", pluginId: "drafts", reason: "Draft" }, + sendAt: null, + }, + })); + stubServerApi({ "v1.threads.:id.send.$post": post }); + + await runCommand( + ["thread", "tell", "thread-draft", "Save this", "--draft"], + register, + ); + + expect(post).toHaveBeenCalledWith({ + param: { id: "thread-draft" }, + json: expect.objectContaining({ + mode: "queue-if-active", + pluginSubmission: { pluginId: "drafts", data: { kind: "draft" } }, + }), + }); + expect(vi.mocked(console.log).mock.calls[0]?.[0]).toBe( + "Thread thread-draft draft saved; send it with bb thread queue send", + ); + }); + + it("rejects conflicting draft and steer requests before sending", async () => { + const post = vi.fn(); + stubServerApi({ "v1.threads.:id.send.$post": post }); + + await expect( + runCommand( + [ + "thread", + "tell", + "thread-draft", + "Save this", + "--draft", + "--mode", + "steer", + ], + register, + ), + ).rejects.toThrow("process.exit:1"); + + expect(post).not.toHaveBeenCalled(); + expect(vi.mocked(console.error).mock.calls[0]?.[0]).toBe( + "Error: --draft cannot be combined with --mode steer or auto.", + ); + }); + it("bb thread tell --json prints the raw response plus thread id", async () => { const post = vi.fn(async () => ({ ok: true, delivery: "sent" })); stubServerApi({ "v1.threads.:id.send.$post": post }); diff --git a/apps/cli/src/commands/thread/actions.ts b/apps/cli/src/commands/thread/actions.ts index f7a4aff90b0..e9d17133b3a 100644 --- a/apps/cli/src/commands/thread/actions.ts +++ b/apps/cli/src/commands/thread/actions.ts @@ -69,6 +69,7 @@ interface ThreadDeleteCommandOptions { } interface ThreadTellCommandOptions { + draft?: boolean; json?: boolean; messageFile?: string; model?: string; @@ -106,6 +107,7 @@ interface ThreadEditMessageCommandOptions { type ThreadTellDeliveryMode = "auto" | "queue" | "steer"; interface PostThreadMessageArgs { + draft?: boolean; getUrl: () => string; threadId: string; message: string; @@ -440,6 +442,7 @@ export function registerActionsCommands( .command("tell [message]") .aliases(["message", "send"]) .description("Send a follow-up message to a thread") + .option("--draft", "Save the message as a draft until you send it manually") .option( "--message-file ", `Read the message from a file instead of [message]; ${TEXT_FILE_HELP_SUFFIX}`, @@ -483,11 +486,18 @@ export function registerActionsCommands( inline: inlineMessage, inlineLabel: "", }); + const mode = resolveThreadMessageMode(opts.mode); + if (opts.draft && opts.mode !== undefined && mode !== "queue") { + throw new Error( + "--draft cannot be combined with --mode steer or auto.", + ); + } const response = await postThreadMessage({ getUrl, threadId: id, message, - mode: resolveThreadMessageMode(opts.mode), + mode: opts.draft ? "queue" : mode, + draft: opts.draft, model: opts.model, permissionMode: parsePermissionMode(opts.permissionMode), reasoningLevel: parseReasoningLevel(opts.reasoningLevel), @@ -618,6 +628,9 @@ async function postThreadMessage( ...(args.serviceTier ? { serviceTier: args.serviceTier } : {}), ...(args.senderThreadId ? { senderThreadId: args.senderThreadId } : {}), ...(args.sendAt === undefined ? {} : { sendAt: args.sendAt }), + ...(args.draft + ? { pluginSubmission: { pluginId: "drafts", data: { kind: "draft" } } } + : {}), }); return { ...response, mode: args.mode }; } @@ -627,6 +640,12 @@ function describeThreadTellOutcome( response: PostThreadMessageResult, ): string { if (response.delivery === "queued") { + if ( + response.queuedMessage.waitingOn?.kind === "plugin" && + response.queuedMessage.waitingOn.pluginId === "drafts" + ) { + return `Thread ${threadId} draft saved; send it with bb thread queue send`; + } // The server says WHY it is waiting, so the CLI does not have to guess // from the flags it happened to send. `bb thread queue list` shows the // same reason for the row afterwards. diff --git a/apps/cli/src/commands/thread/spawn.ts b/apps/cli/src/commands/thread/spawn.ts index afb92217144..786dfadf2ca 100644 --- a/apps/cli/src/commands/thread/spawn.ts +++ b/apps/cli/src/commands/thread/spawn.ts @@ -73,6 +73,7 @@ interface ThreadSpawnCommandOptions { sourceSeqEnd?: string; visibility?: string; sendAt?: string; + draft?: boolean; } export function looksLikePath(value: string): boolean { @@ -392,6 +393,10 @@ export function registerSpawnCommand( "JSON value for an --environment-provider that declares inputs (`bb environment providers --json` shows the schema)", ) .option("--send-at ", SEND_AT_HELP) + .option( + "--draft", + "Save the first message as a draft until you send it manually", + ) .option("--origin-kind ", "Thread origin: fork") .option("--source-thread ", "Source thread for a fork") .option( @@ -596,14 +601,24 @@ export function registerSpawnCommand( ...(opts.sourceThread ? { sourceThreadId: opts.sourceThread } : {}), ...(sourceSeqEnd !== undefined ? { sourceSeqEnd } : {}), ...(sendAt !== undefined ? { sendAt } : {}), + ...(opts.draft + ? { + pluginSubmission: { + pluginId: "drafts", + data: { kind: "draft" }, + }, + } + : {}), }); } catch (err: unknown) { throw prependErrorContext("Failed to create thread", err); } if (outputJson(opts, thread)) return; - console.log(`Thread spawned: ${thread.id}`); - if (sendAt !== undefined) { + console.log( + `${opts.draft ? "Draft saved" : "Thread spawned"}: ${thread.id}`, + ); + if (sendAt !== undefined && !opts.draft) { console.log( `First message scheduled for ${new Date(sendAt).toLocaleString()}; the thread stays pending until then.`, ); diff --git a/apps/server/src/services/plugins/plugin-hook-registry.ts b/apps/server/src/services/plugins/plugin-hook-registry.ts index 12e62a32718..7831745f53c 100644 --- a/apps/server/src/services/plugins/plugin-hook-registry.ts +++ b/apps/server/src/services/plugins/plugin-hook-registry.ts @@ -52,6 +52,7 @@ export async function invokeBridgedProvider( * seams — and because it is the seam a test substitutes fake handlers through. */ export interface PluginHookProvider { + isPluginRunning?(pluginId: string): boolean; /** Registered handlers for a hook, in plugin install order. */ listHooks(hook: K): PluginHookRegistration[]; /** @@ -77,7 +78,9 @@ export interface PluginHookProvider { */ let provider: PluginHookProvider | undefined; -export function setPluginHookProvider(next: PluginHookProvider | undefined): void { +export function setPluginHookProvider( + next: PluginHookProvider | undefined, +): void { provider = next; } diff --git a/apps/server/src/services/plugins/plugin-service.ts b/apps/server/src/services/plugins/plugin-service.ts index f4f905949de..0b65a696c4a 100644 --- a/apps/server/src/services/plugins/plugin-service.ts +++ b/apps/server/src/services/plugins/plugin-service.ts @@ -1266,6 +1266,15 @@ export function createPluginService(deps: PluginServiceDeps): PluginService { }, hooks: { + isPluginRunning(pluginId) { + const row = getInstalledPlugin(deps.db, pluginId); + return ( + row !== undefined && + row.enabled && + loaded.has(pluginId) && + getStatus(row).status === "running" + ); + }, listHooks: listPluginHooks, invokeHook: invokeIsolated, decisionTimeoutMs: DEFAULT_PLUGIN_HOOK_TIMEOUT_MS, diff --git a/apps/server/src/services/threads/dispatch-attempt.ts b/apps/server/src/services/threads/dispatch-attempt.ts index 6b62d8380b1..27ffeb04dd2 100644 --- a/apps/server/src/services/threads/dispatch-attempt.ts +++ b/apps/server/src/services/threads/dispatch-attempt.ts @@ -43,6 +43,8 @@ import { dispatchExecutionSources, dispatchWaitReasonForPass, hasMessageDispatchHooks, + isDraftSubmission, + requireDraftSubmissionAvailable, noteDispatchRequeued, runMessageDispatchHookPass, type DispatchAttemptKind, @@ -293,6 +295,7 @@ async function runDispatchAttempt( reattempted: boolean, ): Promise { const { payload, thread } = args; + requireDraftSubmissionAvailable(args.pluginSubmission); // A stopping thread is writable HERE and nowhere upstream: the checkpoint // below turns it into a core wait, which is a truthful "not yet" the row can // recover from, rather than the 409 that used to make a stop a dead end for @@ -505,7 +508,10 @@ async function runDispatchAttempt( } }; - if (!sendNow && hasMessageDispatchHooks()) { + if ( + !sendNow && + (isDraftSubmission(args.pluginSubmission) || hasMessageDispatchHooks()) + ) { const outcome = await runMessageDispatchHookPass(deps, { thread, threadResponse: toThreadResponseFromThread(deps, { thread }), diff --git a/apps/server/src/services/threads/dispatch-hooks.ts b/apps/server/src/services/threads/dispatch-hooks.ts index 554d9fe1999..e1a7fe457dd 100644 --- a/apps/server/src/services/threads/dispatch-hooks.ts +++ b/apps/server/src/services/threads/dispatch-hooks.ts @@ -166,6 +166,38 @@ export function hasMessageDispatchHooks(): boolean { ); } +export function isDraftSubmission( + submission: MessageDispatchHookContext["experimental_submission"] | undefined, +): boolean { + return ( + submission?.pluginId === "drafts" && + submission.data !== null && + typeof submission.data === "object" && + !Array.isArray(submission.data) && + submission.data.kind === "draft" + ); +} + +export function requireDraftSubmissionAvailable( + submission: MessageDispatchHookContext["experimental_submission"] | undefined, +): void { + if (!isDraftSubmission(submission)) return; + const provider = pluginHookProvider(); + if ( + provider?.isPluginRunning?.("drafts") !== true || + !provider + .listHooks("message.dispatch") + .some((hook) => hook.pluginId === "drafts") + ) { + throw new ApiError( + 409, + "drafts_unavailable", + "Drafts must be installed, enabled, and running to save a draft.", + { details: { pluginId: "drafts" } }, + ); + } +} + /** * Server-wide evaluation lock. * @@ -393,6 +425,7 @@ export async function runMessageDispatchHookPass( deps: DispatchHookDeps, request: MessageDispatchHookPassRequest, ): Promise { + requireDraftSubmissionAvailable(request.pluginSubmission); const provider = pluginHookProvider(); if (provider === undefined) { return { kind: "proceed" }; @@ -403,6 +436,7 @@ export async function runMessageDispatchHookPass( } return withEvaluationLock(async () => { + requireDraftSubmissionAvailable(request.pluginSubmission); const context = buildHookContext(deps, request); const waits: MessageDispatchWaitDecision[] = []; @@ -447,12 +481,32 @@ export async function runMessageDispatchHookPass( } } - const waiter = waits[0]; + const draftWait = waits.find((wait) => wait.pluginId === "drafts"); + if ( + isDraftSubmission(request.pluginSubmission) && + (draftWait === undefined || draftWait.sendAt !== null) + ) { + throw messageDispatchHookFailure( + "drafts", + "did not hold the draft for manual dispatch", + ); + } + const firstQueuedWait = request.queuedMessages[0]?.waitingOn; + const preserveDraftHold = + isDraftSubmission(request.pluginSubmission) || + (firstQueuedWait?.kind === "plugin" && + firstQueuedWait.pluginId === "drafts"); + const waiter = + preserveDraftHold && draftWait !== undefined ? draftWait : waits[0]; if (waiter === undefined) { await request.continueAfterHooks?.(); return { kind: "proceed" }; } - return { kind: "wait", waiter, additionalWaiters: waits.slice(1) }; + return { + kind: "wait", + waiter, + additionalWaiters: waits.filter((wait) => wait !== waiter), + }; }); } diff --git a/apps/server/src/services/threads/thread-create.ts b/apps/server/src/services/threads/thread-create.ts index 0914d5b04a3..0e3052714e4 100644 --- a/apps/server/src/services/threads/thread-create.ts +++ b/apps/server/src/services/threads/thread-create.ts @@ -18,6 +18,7 @@ import type { LoggedPendingInteractionWorkSessionDeps, } from "../../types.js"; import { ApiError } from "../../errors.js"; +import { requireDraftSubmissionAvailable } from "./dispatch-hooks.js"; import { ensureHostSessionReadyForWork } from "../hosts/host-lifecycle.js"; import { buildExecutionOptions } from "./thread-commands.js"; import { @@ -525,6 +526,7 @@ export async function createThreadFromRequest( forkSourceEnvironmentId?: string; } = {}, ) { + requireDraftSubmissionAvailable(rawRequestInput.pluginSubmission); const project = requirePublicProjectForThreadCreate( deps, rawRequestInput.projectId, diff --git a/apps/server/src/services/threads/thread-send-request.ts b/apps/server/src/services/threads/thread-send-request.ts index 132257c3c4f..332a73f980d 100644 --- a/apps/server/src/services/threads/thread-send-request.ts +++ b/apps/server/src/services/threads/thread-send-request.ts @@ -5,6 +5,10 @@ import type { } from "@bb/server-contract"; import type { LoggedPendingInteractionWorkSessionDeps } from "../../types.js"; import { attemptDispatch } from "./dispatch-attempt.js"; +import { + isDraftSubmission, + requireDraftSubmissionAvailable, +} from "./dispatch-hooks.js"; import { requireThreadCommandEnvironment } from "./thread-command-environment.js"; import { sendThreadMessage } from "./thread-send.js"; @@ -17,7 +21,11 @@ export async function acceptThreadSendRequest( deps: LoggedPendingInteractionWorkSessionDeps, args: AcceptThreadSendRequestArgs, ): Promise { - if (isStandaloneBuiltinClearCommand(args.payload.input)) { + requireDraftSubmissionAvailable(args.payload.pluginSubmission); + if ( + !isDraftSubmission(args.payload.pluginSubmission) && + isStandaloneBuiltinClearCommand(args.payload.input) + ) { const environment = await requireThreadCommandEnvironment(deps, { thread: args.thread, }); diff --git a/apps/server/test/public/public-thread-search.test.ts b/apps/server/test/public/public-thread-search.test.ts index 63f425d13cb..e20f02e608d 100644 --- a/apps/server/test/public/public-thread-search.test.ts +++ b/apps/server/test/public/public-thread-search.test.ts @@ -1,4 +1,4 @@ -import { archiveThread } from "@bb/db"; +import { archiveThread, createQueuedThreadMessage } from "@bb/db"; import { threadSearchResponseSchema } from "@bb/server-contract"; import { describe, expect, it } from "vitest"; import { readJson } from "../helpers/json.js"; @@ -10,6 +10,44 @@ import { import { withTestHarness } from "../helpers/test-app.js"; describe("public thread search route", () => { + it("finds saved first messages and follow-ups in the existing thread groups", async () => { + await withTestHarness(async (harness) => { + const { host } = seedHostSession(harness.deps); + const { project } = seedProjectWithSource(harness.deps, { hostId: host.id }); + const first = seedThread(harness.deps, { projectId: project.id, status: "pending" }); + const followup = seedThread(harness.deps, { projectId: project.id }); + const archived = seedThread(harness.deps, { projectId: project.id }); + const hidden = seedThread(harness.deps, { projectId: project.id, visibility: "hidden" }); + for (const thread of [first, followup, archived, hidden]) { + createQueuedThreadMessage(harness.db, harness.deps.hub, { + threadId: thread.id, + content: [{ type: "text", text: "Juniper saved message", mentions: [] }], + model: "gpt-5", + reasoningLevel: "medium", + permissionMode: "full", + serviceTier: "default", + waitingOn: { kind: "plugin", pluginId: "drafts", reason: "Draft" }, + sendAt: null, + payload: { kind: "inline" }, + systemNotice: null, + }); + } + archiveThread(harness.db, harness.deps.hub, archived.id); + const response = await harness.app.request("/api/v1/threads/search?query=Juniper"); + expect(response.status).toBe(200); + const body = threadSearchResponseSchema.parse(await readJson(response)); + expect(Object.keys(body)).toEqual(["active", "archived"]); + expect(new Set(body.active.results.map((result) => result.thread.id))).toEqual(new Set([first.id, followup.id])); + expect(body.archived.results.map((result) => result.thread.id)).toEqual([archived.id]); + for (const result of [...body.active.results, ...body.archived.results]) { + expect(result.thread).not.toHaveProperty("lifecycle"); + expect(result.matches).toEqual(expect.arrayContaining([ + expect.objectContaining({ text: "Juniper saved message", sourceSeq: null }), + ])); + } + }); + }); + it("returns active and archived search result groups", async () => { await withTestHarness(async (harness) => { const { host } = seedHostSession(harness.deps); diff --git a/apps/server/test/threads/dispatch-hooks.test.ts b/apps/server/test/threads/dispatch-hooks.test.ts index 9953b894e97..89ddc629324 100644 --- a/apps/server/test/threads/dispatch-hooks.test.ts +++ b/apps/server/test/threads/dispatch-hooks.test.ts @@ -1,5 +1,7 @@ import { createQueuedThreadMessage, + environments, + threads, getThread, listEvents, listQueuedThreadMessages, @@ -72,9 +74,12 @@ function emptyRegistry(): HookRegistry { */ function installHooks( registry: HookRegistry, - options: { decisionTimeoutMs?: number } = {}, + options: { decisionTimeoutMs?: number; running?: boolean } = {}, ): void { setPluginHookProvider({ + isPluginRunning: (pluginId) => + options.running ?? + registry["message.dispatch"].some((hook) => hook.pluginId === pluginId), listHooks: (hook) => registry[hook], // Mirrors the plugin service's failure isolation: a throw is reported, not // propagated, and the runner is what turns it into a failed dispatch. @@ -197,6 +202,256 @@ async function expectApiError(run: () => Promise): Promise { throw new Error("expected the operation to fail"); } +describe("built-in Drafts save-only admission", () => { + const pluginSubmission = { pluginId: "drafts", data: { kind: "draft" } }; + + it.each(["missing", "disabled", "missing-handler"] as const)( + "rejects %s Drafts before creating a thread, environment, or turn", + async (state) => { + await withTestHarness(async (harness) => { + const hostId = `host-drafts-${state}`; + const { project, thread } = seedRunnableThread(harness, { + hostId, + status: "active", + }); + if (state === "missing") { + setPluginHookProvider(undefined); + } else { + installHooks( + { + "message.dispatch": + state === "missing-handler" + ? [] + : [ + { + pluginId: "drafts", + handler: () => ({ action: "wait", reason: "Draft" }), + }, + ], + }, + { running: state === "missing-handler" }, + ); + } + const beforeThreads = harness.db.select().from(threads).all().length; + const beforeEnvironments = harness.db + .select() + .from(environments) + .all().length; + const beforeTurns = turnRequests(harness, thread.id).length; + const createError = await expectApiError(() => + createThreadFromRequest(harness.deps, { + projectId: project.id, + providerId: "codex", + environment: { + type: "host", + hostId, + workspace: { + type: "unmanaged", + path: "/tmp/drafts-new-environment", + }, + }, + origin: "sdk", + startedOnBehalfOf: null, + input: textInput("save only"), + pluginSubmission, + }), + ); + expect(createError.body.code).toBe("drafts_unavailable"); + const sendError = await expectApiError(() => + acceptThreadSendRequest(harness.deps, { + thread, + payload: { + input: textInput("/clear"), + mode: "steer-if-active", + pluginSubmission, + }, + }), + ); + expect(sendError.body.code).toBe("drafts_unavailable"); + expect(harness.db.select().from(threads).all()).toHaveLength( + beforeThreads, + ); + expect(harness.db.select().from(environments).all()).toHaveLength( + beforeEnvironments, + ); + expect(turnRequests(harness, thread.id)).toHaveLength(beforeTurns); + expect(queuedRows(harness, thread.id)).toEqual([]); + }); + }, + ); + + it("keeps active steering, a future schedule, and /clear behind the Drafts hold", async () => { + await withTestHarness(async (harness) => { + const { thread } = seedRunnableThread(harness, { + hostId: "host-save-only-steer", + status: "active", + }); + installHooks({ + "message.dispatch": [ + { + pluginId: "drafts", + handler: () => ({ action: "wait", reason: "Draft" }), + }, + ], + }); + const beforeTurns = turnRequests(harness, thread.id).length; + const response = await acceptThreadSendRequest(harness.deps, { + thread, + payload: { + input: textInput("/clear"), + mode: "steer-if-active", + sendAt: Date.now() + 60_000, + pluginSubmission, + }, + }); + expect(response.delivery).toBe("queued"); + expect(onlyQueuedRow(harness, thread.id)).toMatchObject({ + waitingOn: { kind: "plugin", pluginId: "drafts", reason: "Draft" }, + sendAt: null, + }); + expect(turnRequests(harness, thread.id)).toHaveLength(beforeTurns); + expect(getThread(harness.db, thread.id)?.status).toBe("active"); + }); + }); + + it.each(["throws", "proceeds", "schedules"] as const)( + "fails closed when Drafts %s", + async (behavior) => { + await withTestHarness(async (harness) => { + const { host, project } = seedDispatchFixture( + harness, + `host-draft-${behavior}`, + ); + installHooks({ + "message.dispatch": [ + { + pluginId: "drafts", + handler: () => { + if (behavior === "throws") throw new Error("Drafts failed"); + return behavior === "proceeds" + ? { action: "proceed" } + : { + action: "wait", + reason: "Draft", + sendAt: Date.now() + 60_000, + }; + }, + }, + ], + }); + const beforeEnvironments = harness.db + .select() + .from(environments) + .all().length; + const error = await expectApiError(() => + createThreadFromRequest(harness.deps, { + projectId: project.id, + providerId: "codex", + environment: { + type: "host", + hostId: host.id, + workspace: { + type: "unmanaged", + path: "/tmp/drafts-failure-environment", + }, + }, + origin: "sdk", + startedOnBehalfOf: null, + input: textInput("save only"), + pluginSubmission, + }), + ); + expect(error.body.code).toBe("dispatch_hook_failed"); + expect(harness.db.select().from(environments).all()).toHaveLength( + beforeEnvironments, + ); + for (const row of harness.db.select().from(threads).all()) { + expect(turnRequests(harness, row.id)).toEqual([]); + } + }); + }, + ); + + it("keeps the durable Drafts owner when another plugin also waits", async () => { + await withTestHarness(async (harness) => { + const { thread } = seedRunnableThread(harness, { + hostId: "host-drafts-owner", + status: "idle", + }); + installHooks({ + "message.dispatch": [ + { + pluginId: "capacity", + handler: () => ({ action: "wait", reason: "Busy" }), + }, + { + pluginId: "drafts", + handler: (context) => { + const wait = context.queuedMessages[0]?.waitingOn; + return context.experimental_submission?.pluginId === "drafts" || + (wait?.kind === "plugin" && wait.pluginId === "drafts") + ? { action: "wait", reason: "Draft" } + : { action: "proceed" }; + }, + }, + ], + }); + const beforeTurns = turnRequests(harness, thread.id).length; + await acceptThreadSendRequest(harness.deps, { + thread, + payload: { + input: textInput("save only"), + mode: "auto", + pluginSubmission, + }, + }); + const saved = onlyQueuedRow(harness, thread.id); + expect(saved.waitingOn).toEqual({ + kind: "plugin", + pluginId: "drafts", + reason: "Draft (also waiting on capacity: Busy)", + }); + await runQueuedMessageDispatch(harness.deps, { kind: "plugin-recheck" }); + expect(onlyQueuedRow(harness, thread.id)).toMatchObject({ + id: saved.id, + waitingOn: { kind: "plugin", pluginId: "drafts" }, + }); + expect(turnRequests(harness, thread.id)).toHaveLength(beforeTurns); + }); + }); + + it.each([ + { pluginId: "other", data: { kind: "draft" } }, + { pluginId: "drafts", data: { kind: "other" } }, + { pluginId: "drafts", data: null }, + { pluginId: "drafts", data: [{ kind: "draft" }] }, + ])( + "preserves opaque submission behavior for $pluginId / $data", + async (opaqueSubmission) => { + await withTestHarness(async (harness) => { + const { thread } = seedRunnableThread(harness, { + hostId: "host-opaque-draft", + status: "idle", + }); + setPluginHookProvider(undefined); + const response = await acceptThreadSendRequest(harness.deps, { + thread, + payload: { + input: textInput("scheduled opaque request"), + mode: "auto", + sendAt: Date.now() + 60_000, + pluginSubmission: opaqueSubmission, + }, + }); + expect(response.delivery).toBe("queued"); + expect(onlyQueuedRow(harness, thread.id).waitingOn).toEqual({ + kind: "time", + }); + }); + }, + ); +}); + describe("message.dispatch hook context", () => { it("passes plugin submission data through a new thread's first dispatch", async () => { await withTestHarness(async (harness) => { @@ -207,7 +462,7 @@ describe("message.dispatch hook context", () => { pluginId: "drafts", handler: (context) => { seen.push(context.experimental_submission); - return { action: "proceed" }; + return { action: "wait", reason: "Draft" }; }, }, ], @@ -221,7 +476,7 @@ describe("message.dispatch hook context", () => { data: { kind: "draft" }, }; - await createThreadFromRequest(harness.deps, { + const created = await createThreadFromRequest(harness.deps, { environment: { type: "host", hostId: host.id, @@ -236,6 +491,17 @@ describe("message.dispatch hook context", () => { }); expect(seen).toEqual([pluginSubmission]); + expect(getThread(harness.db, created.id)?.status).toBe("pending"); + const saved = onlyQueuedRow(harness, created.id); + await sendQueuedMessage(harness.deps, { + threadId: created.id, + queuedMessageId: saved.id, + mode: "auto", + claimPolicy: { kind: "explicit-send" }, + }); + expect(seen).toEqual([pluginSubmission]); + expect(getThread(harness.db, created.id)?.status).not.toBe("pending"); + expect(queuedRows(harness, created.id)).toEqual([]); }); }); diff --git a/packages/db/drizzle/0128_queued_message_search.sql b/packages/db/drizzle/0128_queued_message_search.sql new file mode 100644 index 00000000000..1045cdee965 --- /dev/null +++ b/packages/db/drizzle/0128_queued_message_search.sql @@ -0,0 +1,86 @@ +INSERT INTO thread_search_segments (`id`, `thread_id`, `source_kind`, `source_key`, `source_seq`, `text`, `created_at`, `updated_at`) +SELECT + q.thread_id || ':user_message:queued:' || q.id, + q.thread_id, + 'user_message', + 'queued:' || q.id, + NULL, + trim(COALESCE(( + SELECT group_concat(json_extract(part.value, '$.text'), char(10)) + FROM json_each(CASE WHEN json_valid(q.content) THEN q.content ELSE '[]' END) AS part + WHERE json_extract(part.value, '$.type') = 'text' + AND COALESCE(json_extract(part.value, '$.visibility'), '') <> 'agent-only' + ), '')), + q.created_at, + q.updated_at +FROM queued_thread_messages AS q +WHERE q.system_notice IS NULL + AND EXISTS ( + SELECT 1 FROM json_each(CASE WHEN json_valid(q.content) THEN q.content ELSE '[]' END) AS part + WHERE json_extract(part.value, '$.type') = 'text' + AND COALESCE(json_extract(part.value, '$.visibility'), '') <> 'agent-only' + AND trim(json_extract(part.value, '$.text')) <> '' + ); +--> statement-breakpoint +CREATE TRIGGER queued_thread_messages_search_insert +AFTER INSERT ON queued_thread_messages +WHEN NEW.system_notice IS NULL +BEGIN + INSERT INTO thread_search_segments (`id`, `thread_id`, `source_kind`, `source_key`, `source_seq`, `text`, `created_at`, `updated_at`) + SELECT + NEW.thread_id || ':user_message:queued:' || NEW.id, + NEW.thread_id, + 'user_message', + 'queued:' || NEW.id, + NULL, + trim(COALESCE(( + SELECT group_concat(json_extract(part.value, '$.text'), char(10)) + FROM json_each(CASE WHEN json_valid(NEW.content) THEN NEW.content ELSE '[]' END) AS part + WHERE json_extract(part.value, '$.type') = 'text' + AND COALESCE(json_extract(part.value, '$.visibility'), '') <> 'agent-only' + ), '')), + NEW.created_at, + NEW.updated_at + WHERE EXISTS ( + SELECT 1 FROM json_each(CASE WHEN json_valid(NEW.content) THEN NEW.content ELSE '[]' END) AS part + WHERE json_extract(part.value, '$.type') = 'text' + AND COALESCE(json_extract(part.value, '$.visibility'), '') <> 'agent-only' + AND trim(json_extract(part.value, '$.text')) <> '' + ); +END; +--> statement-breakpoint +CREATE TRIGGER queued_thread_messages_search_update +AFTER UPDATE OF content, system_notice ON queued_thread_messages +BEGIN + DELETE FROM thread_search_segments + WHERE id = OLD.thread_id || ':user_message:queued:' || OLD.id; + INSERT INTO thread_search_segments (`id`, `thread_id`, `source_kind`, `source_key`, `source_seq`, `text`, `created_at`, `updated_at`) + SELECT + NEW.thread_id || ':user_message:queued:' || NEW.id, + NEW.thread_id, + 'user_message', + 'queued:' || NEW.id, + NULL, + trim(COALESCE(( + SELECT group_concat(json_extract(part.value, '$.text'), char(10)) + FROM json_each(CASE WHEN json_valid(NEW.content) THEN NEW.content ELSE '[]' END) AS part + WHERE json_extract(part.value, '$.type') = 'text' + AND COALESCE(json_extract(part.value, '$.visibility'), '') <> 'agent-only' + ), '')), + NEW.created_at, + NEW.updated_at + WHERE NEW.system_notice IS NULL + AND EXISTS ( + SELECT 1 FROM json_each(CASE WHEN json_valid(NEW.content) THEN NEW.content ELSE '[]' END) AS part + WHERE json_extract(part.value, '$.type') = 'text' + AND COALESCE(json_extract(part.value, '$.visibility'), '') <> 'agent-only' + AND trim(json_extract(part.value, '$.text')) <> '' + ); +END; +--> statement-breakpoint +CREATE TRIGGER queued_thread_messages_search_delete +AFTER DELETE ON queued_thread_messages +BEGIN + DELETE FROM thread_search_segments + WHERE id = OLD.thread_id || ':user_message:queued:' || OLD.id; +END; diff --git a/packages/db/drizzle/meta/0127_snapshot.json b/packages/db/drizzle/meta/0127_snapshot.json index d12fb2c9281..e8fcfee0a02 100644 --- a/packages/db/drizzle/meta/0127_snapshot.json +++ b/packages/db/drizzle/meta/0127_snapshot.json @@ -5065,4 +5065,4 @@ "internal": { "indexes": {} } -} \ No newline at end of file +} diff --git a/packages/db/drizzle/meta/0128_snapshot.json b/packages/db/drizzle/meta/0128_snapshot.json new file mode 100644 index 00000000000..00b6627723c --- /dev/null +++ b/packages/db/drizzle/meta/0128_snapshot.json @@ -0,0 +1,5068 @@ +{ + "id": "0ac2b12c-b1b5-4ab6-997b-8e24d40c0ba9", + "prevId": "6c59f5c6-5254-4535-8f08-708287d8c133", + "version": "6", + "dialect": "sqlite", + "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_settings_values": { + "name": "app_settings_values", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "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": {}, + "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", + "columnsFrom": [ + "referenceId" + ], + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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": {} + }, + "environment_hook_operations": { + "name": "environment_hook_operations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "operation_id": { + "name": "operation_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": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "finished_at": { + "name": "finished_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "environment_variables": { + "name": "environment_variables", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ciphertext": { + "name": "ciphertext", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encryption_version": { + "name": "encryption_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "environment_variables_global_name": { + "name": "environment_variables_global_name", + "columns": [ + "name" + ], + "where": "\"environment_variables\".\"project_id\" IS NULL", + "isUnique": true + }, + "environment_variables_project_name": { + "name": "environment_variables_project_name", + "columns": [ + "project_id", + "name" + ], + "where": "\"environment_variables\".\"project_id\" IS NOT NULL", + "isUnique": true + } + }, + "foreignKeys": { + "environment_variables_project_id_projects_id_fk": { + "name": "environment_variables_project_id_projects_id_fk", + "tableFrom": "environment_variables", + "columnsFrom": [ + "project_id" + ], + "tableTo": "projects", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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 + }, + "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 + }, + "environment_provider_id": { + "name": "environment_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "environment_provider_plugin_id": { + "name": "environment_provider_plugin_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider_owns_path": { + "name": "provider_owns_path", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "environment_provider_selection": { + "name": "environment_provider_selection", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "environment_provider_instance_key": { + "name": "environment_provider_instance_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "retire_at": { + "name": "retire_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "teardown_attempt": { + "name": "teardown_attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "teardown_status": { + "name": "teardown_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "teardown_message": { + "name": "teardown_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "owner_thread_id": { + "name": "owner_thread_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "attempt": { + "name": "attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "status_message": { + "name": "status_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pending_log": { + "name": "pending_log", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "claim_path": { + "name": "claim_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "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_owner_thread_idx": { + "name": "environments_owner_thread_idx", + "columns": [ + "owner_thread_id" + ], + "isUnique": true + }, + "environments_claim_idx": { + "name": "environments_claim_idx", + "columns": [ + "host_id", + "claim_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 + }, + "environments_provider_instance_idx": { + "name": "environments_provider_instance_idx", + "columns": [ + "environment_provider_id", + "environment_provider_instance_key" + ], + "isUnique": false + } + }, + "foreignKeys": { + "environments_project_id_projects_id_fk": { + "name": "environments_project_id_projects_id_fk", + "tableFrom": "environments", + "columnsFrom": [ + "project_id" + ], + "tableTo": "projects", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "environments_host_id_hosts_id_fk": { + "name": "environments_host_id_hosts_id_fk", + "tableFrom": "environments", + "columnsFrom": [ + "host_id" + ], + "tableTo": "hosts", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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 + }, + "parent_tool_call_id": { + "name": "parent_tool_call_id", + "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_delegating_item_lookup_idx": { + "name": "events_delegating_item_lookup_idx", + "columns": [ + "thread_id", + "item_id", + "sequence", + "item_kind" + ], + "where": "\"events\".\"item_kind\" IN ('toolCall', 'delegation')", + "isUnique": false + }, + "events_plan_steps_thread_sequence_idx": { + "name": "events_plan_steps_thread_sequence_idx", + "columns": [ + "thread_id", + "sequence" + ], + "where": "(\"events\".\"item_kind\" = 'planSteps' AND \"events\".\"type\" = 'item/completed') OR \"events\".\"type\" = 'turn/plan/updated'", + "isUnique": false + }, + "events_parent_tool_call_thread_parent_sequence_idx": { + "name": "events_parent_tool_call_thread_parent_sequence_idx", + "columns": [ + "thread_id", + "parent_tool_call_id", + "sequence" + ], + "where": "\"events\".\"parent_tool_call_id\" IS NOT NULL", + "isUnique": false + }, + "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_background_task_thread_type_item_sequence_idx": { + "name": "events_background_task_thread_type_item_sequence_idx", + "columns": [ + "thread_id", + "type", + "item_id", + "sequence" + ], + "where": "\"events\".\"item_kind\" = 'backgroundTask'", + "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_item_lifecycle_thread_item_sequence_idx": { + "name": "events_item_lifecycle_thread_item_sequence_idx", + "columns": [ + "thread_id", + "item_id", + "sequence" + ], + "where": "\"events\".\"type\" IN ('item/started', 'item/completed', 'item/backgroundTask/completed')", + "isUnique": false + }, + "events_environment_idx": { + "name": "events_environment_idx", + "columns": [ + "environment_id" + ], + "isUnique": false + }, + "events_provider_identity_idx": { + "name": "events_provider_identity_idx", + "columns": [ + "provider_thread_id", + "created_at" + ], + "where": "\"events\".\"type\" = 'thread/identity'", + "isUnique": false + }, + "events_completed_item_truncation_idx": { + "name": "events_completed_item_truncation_idx", + "columns": [ + "item_kind", + "created_at", + "id" + ], + "where": "\"events\".\"type\" = 'item/completed'", + "isUnique": false + }, + "events_thread_state_thread_sequence_idx": { + "name": "events_thread_state_thread_sequence_idx", + "columns": [ + "thread_id", + "sequence" + ], + "where": "\"events\".\"type\" IN ('thread/goal/updated', 'thread/goal/cleared', 'thread/extensionState/updated')", + "isUnique": false + } + }, + "foreignKeys": { + "events_thread_id_threads_id_fk": { + "name": "events_thread_id_threads_id_fk", + "tableFrom": "events", + "columnsFrom": [ + "thread_id" + ], + "tableTo": "threads", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "events_environment_id_environments_id_fk": { + "name": "events_environment_id_environments_id_fk", + "tableFrom": "events", + "columnsFrom": [ + "environment_id" + ], + "tableTo": "environments", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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 + }, + "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", + "columnsFrom": [ + "host_id" + ], + "tableTo": "hosts", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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 + }, + "machine_provider_id": { + "name": "machine_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "launch_key": { + "name": "launch_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "machine_inputs": { + "name": "machine_inputs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "machine_attempt": { + "name": "machine_attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "pending_log": { + "name": "pending_log", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "machine_operation_id": { + "name": "machine_operation_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "server_access_provider_id": { + "name": "server_access_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "server_access_grant_id": { + "name": "server_access_grant_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "suspended_at": { + "name": "suspended_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_message": { + "name": "status_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "suspend_retry_at": { + "name": "suspend_retry_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "remove_retry_at": { + "name": "remove_retry_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "teardown_attempt": { + "name": "teardown_attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "teardown_status": { + "name": "teardown_status", + "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 + }, + "hosts_live_launch_key_idx": { + "name": "hosts_live_launch_key_idx", + "columns": [ + "launch_key" + ], + "where": "\"hosts\".\"destroyed_at\" is null", + "isUnique": true + } + }, + "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 + }, + "catalog_marketplace_name": { + "name": "catalog_marketplace_name", + "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 + }, + "source_git_range": { + "name": "source_git_range", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source_git_tag_prefix": { + "name": "source_git_tag_prefix", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source_git_resolved_tag": { + "name": "source_git_resolved_tag", + "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", + "columnsFrom": [ + "active_artifact_id" + ], + "tableTo": "plugin_artifacts", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "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", + "columnsFrom": [ + "thread_id" + ], + "tableTo": "threads", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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 + }, + "git_checkout_root": { + "name": "git_checkout_root", + "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_marketplace_icons": { + "name": "plugin_marketplace_icons", + "columns": { + "marketplace_name": { + "name": "marketplace_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entry_id": { + "name": "entry_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "etag": { + "name": "etag", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bytes": { + "name": "bytes", + "type": "blob", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "plugin_marketplace_icons_marketplace_name_entry_id_pk": { + "columns": [ + "marketplace_name", + "entry_id" + ], + "name": "plugin_marketplace_icons_marketplace_name_entry_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_marketplaces": { + "name": "plugin_marketplaces", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'https'" + }, + "manifest_url": { + "name": "manifest_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_git_ref": { + "name": "source_git_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source_git_commit": { + "name": "source_git_commit", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "manifest_json": { + "name": "manifest_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "stats_json": { + "name": "stats_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "etag": { + "name": "etag", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_modified": { + "name": "last_modified", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_successful_refresh_at": { + "name": "last_successful_refresh_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_attempted_refresh_at": { + "name": "last_attempted_refresh_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_error": { + "name": "last_error", + "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": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "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_attachment_backfills": { + "name": "project_attachment_backfills", + "columns": { + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "thread_cursor": { + "name": "thread_cursor", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "input_cursor": { + "name": "input_cursor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "input_id": { + "name": "input_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "input_sequence": { + "name": "input_sequence", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "attempted_at": { + "name": "attempted_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "project_attachment_backfills_project_id_projects_id_fk": { + "name": "project_attachment_backfills_project_id_projects_id_fk", + "tableFrom": "project_attachment_backfills", + "columnsFrom": [ + "project_id" + ], + "tableTo": "projects", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "project_attachment_threads": { + "name": "project_attachment_threads", + "columns": { + "attachment_id": { + "name": "attachment_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "project_attachment_threads_thread_idx": { + "name": "project_attachment_threads_thread_idx", + "columns": [ + "thread_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "project_attachment_threads_attachment_id_project_attachments_id_fk": { + "name": "project_attachment_threads_attachment_id_project_attachments_id_fk", + "tableFrom": "project_attachment_threads", + "columnsFrom": [ + "attachment_id" + ], + "tableTo": "project_attachments", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "project_attachment_threads_thread_id_threads_id_fk": { + "name": "project_attachment_threads_thread_id_threads_id_fk", + "tableFrom": "project_attachment_threads", + "columnsFrom": [ + "thread_id" + ], + "tableTo": "threads", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "project_attachment_threads_attachment_id_thread_id_pk": { + "columns": [ + "attachment_id", + "thread_id" + ], + "name": "project_attachment_threads_attachment_id_thread_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "project_attachments": { + "name": "project_attachments", + "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 + }, + "stored_path": { + "name": "stored_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "original_name": { + "name": "original_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ready_at": { + "name": "ready_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deletion_claimed_at": { + "name": "deletion_claimed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "project_attachments_project_path_idx": { + "name": "project_attachments_project_path_idx", + "columns": [ + "project_id", + "stored_path" + ], + "isUnique": true + }, + "project_attachments_project_created_idx": { + "name": "project_attachments_project_created_idx", + "columns": [ + "project_id", + "created_at" + ], + "isUnique": false + }, + "project_attachments_deletion_idx": { + "name": "project_attachments_deletion_idx", + "columns": [ + "project_id", + "deletion_claimed_at", + "id" + ], + "where": "\"project_attachments\".\"deletion_claimed_at\" IS NOT NULL", + "isUnique": false + } + }, + "foreignKeys": { + "project_attachments_project_id_projects_id_fk": { + "name": "project_attachments_project_id_projects_id_fk", + "tableFrom": "project_attachments", + "columnsFrom": [ + "project_id" + ], + "tableTo": "projects", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": { + "project_attachments_size_check": { + "name": "project_attachments_size_check", + "value": "\"project_attachments\".\"size_bytes\" >= 0" + } + } + }, + "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", + "columnsFrom": [ + "project_id" + ], + "tableTo": "projects", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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 + }, + "owns_path": { + "name": "owns_path", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 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", + "columnsFrom": [ + "project_id" + ], + "tableTo": "projects", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "project_sources_host_id_hosts_id_fk": { + "name": "project_sources_host_id_hosts_id_fk", + "tableFrom": "project_sources", + "columnsFrom": [ + "host_id" + ], + "tableTo": "hosts", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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" + ], + "where": "\"projects\".\"kind\" = 'personal'", + "isUnique": true + } + }, + "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", + "columnsFrom": [ + "project_id" + ], + "tableTo": "projects", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "prompt_history_entries_thread_id_threads_id_fk": { + "name": "prompt_history_entries_thread_id_threads_id_fk", + "tableFrom": "prompt_history_entries", + "columnsFrom": [ + "thread_id" + ], + "tableTo": "threads", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "provider_model_catalogs": { + "name": "provider_model_catalogs", + "columns": { + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope_key": { + "name": "scope_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "models_json": { + "name": "models_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "selected_only_models_json": { + "name": "selected_only_models_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fetched_at": { + "name": "fetched_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "provider_model_catalogs_host_id_hosts_id_fk": { + "name": "provider_model_catalogs_host_id_hosts_id_fk", + "tableFrom": "provider_model_catalogs", + "columnsFrom": [ + "host_id" + ], + "tableTo": "hosts", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "provider_model_catalogs_host_id_provider_id_scope_key_pk": { + "columns": [ + "host_id", + "provider_id", + "scope_key" + ], + "name": "provider_model_catalogs_host_id_provider_id_scope_key_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "queued_thread_messages": { + "name": "queued_thread_messages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "system_notice": { + "name": "system_notice", + "type": "text", + "primaryKey": false, + "notNull": false, + "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 + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "origin_plugin_id": { + "name": "origin_plugin_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "requested_by_initiator": { + "name": "requested_by_initiator", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "requested_by_thread_id": { + "name": "requested_by_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 + }, + "send_at": { + "name": "send_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "waiting_on": { + "name": "waiting_on", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "wait_holder": { + "name": "wait_holder", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payload_kind": { + "name": "payload_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'inline'" + }, + "retry_of_turn_request_id": { + "name": "retry_of_turn_request_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "retry_attempt": { + "name": "retry_attempt", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "retry_reason": { + "name": "retry_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": 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 + }, + "queued_thread_messages_due_idx": { + "name": "queued_thread_messages_due_idx", + "columns": [ + "send_at", + "id" + ], + "where": "\"queued_thread_messages\".\"send_at\" IS NOT NULL AND \"queued_thread_messages\".\"claimed_at\" IS NULL AND \"queued_thread_messages\".\"claim_token\" IS NULL", + "isUnique": false + }, + "queued_thread_messages_wait_holder_idx": { + "name": "queued_thread_messages_wait_holder_idx", + "columns": [ + "wait_holder", + "id" + ], + "where": "\"queued_thread_messages\".\"wait_holder\" IS NOT NULL", + "isUnique": false + } + }, + "foreignKeys": { + "queued_thread_messages_thread_id_threads_id_fk": { + "name": "queued_thread_messages_thread_id_threads_id_fk", + "tableFrom": "queued_thread_messages", + "columnsFrom": [ + "thread_id" + ], + "tableTo": "threads", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "retained_event_outputs": { + "name": "retained_event_outputs", + "columns": { + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "output_path": { + "name": "output_path", + "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 + } + }, + "indexes": { + "retained_event_outputs_expiry_idx": { + "name": "retained_event_outputs_expiry_idx", + "columns": [ + "expires_at", + "event_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "retained_event_outputs_event_id_events_id_fk": { + "name": "retained_event_outputs_event_id_events_id_fk", + "tableFrom": "retained_event_outputs", + "columnsFrom": [ + "event_id" + ], + "tableTo": "events", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "system_experiments": { + "name": "system_experiments", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": 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", + "columnsFrom": [ + "thread_id" + ], + "tableTo": "threads", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "terminal_sessions_environment_id_environments_id_fk": { + "name": "terminal_sessions_environment_id_environments_id_fk", + "tableFrom": "terminal_sessions", + "columnsFrom": [ + "environment_id" + ], + "tableTo": "environments", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "terminal_sessions_host_id_hosts_id_fk": { + "name": "terminal_sessions_host_id_hosts_id_fk", + "tableFrom": "terminal_sessions", + "columnsFrom": [ + "host_id" + ], + "tableTo": "hosts", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "terminal_sessions_daemon_session_id_host_daemon_sessions_id_fk": { + "name": "terminal_sessions_daemon_session_id_host_daemon_sessions_id_fk", + "tableFrom": "terminal_sessions", + "columnsFrom": [ + "daemon_session_id" + ], + "tableTo": "host_daemon_sessions", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "thread_conversation_outlines": { + "name": "thread_conversation_outlines", + "columns": { + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "projection_key": { + "name": "projection_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "items_json": { + "name": "items_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "thread_conversation_outlines_thread_id_threads_id_fk": { + "name": "thread_conversation_outlines_thread_id_threads_id_fk", + "tableFrom": "thread_conversation_outlines", + "columnsFrom": [ + "thread_id" + ], + "tableTo": "threads", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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", + "columnsFrom": [ + "thread_id" + ], + "tableTo": "threads", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "thread_image_metadata": { + "name": "thread_image_metadata", + "columns": { + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "etag": { + "name": "etag", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "thread_image_metadata_thread_id_threads_id_fk": { + "name": "thread_image_metadata_thread_id_threads_id_fk", + "tableFrom": "thread_image_metadata", + "columnsFrom": [ + "thread_id" + ], + "tableTo": "threads", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "thread_image_metadata_thread_id_source_pk": { + "columns": [ + "thread_id", + "source" + ], + "name": "thread_image_metadata_thread_id_source_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "thread_plugin_metadata": { + "name": "thread_plugin_metadata", + "columns": { + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "metadata_json": { + "name": "metadata_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "thread_plugin_metadata_thread_id_threads_id_fk": { + "name": "thread_plugin_metadata_thread_id_threads_id_fk", + "tableFrom": "thread_plugin_metadata", + "columnsFrom": [ + "thread_id" + ], + "tableTo": "threads", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "thread_plugin_metadata_thread_id_plugin_id_pk": { + "columns": [ + "thread_id", + "plugin_id" + ], + "name": "thread_plugin_metadata_thread_id_plugin_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "thread_pruning_cursors": { + "name": "thread_pruning_cursors", + "columns": { + "policy": { + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_thread_id": { + "name": "last_thread_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "current_thread_id": { + "name": "current_thread_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "step": { + "name": "step", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "sequence": { + "name": "sequence", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "upper_sequence": { + "name": "upper_sequence", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "cycle": { + "name": "cycle", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "latest_root_sequence": { + "name": "latest_root_sequence", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "latest_context_sequence": { + "name": "latest_context_sequence", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "probe_event_id": { + "name": "probe_event_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "probe_phase": { + "name": "probe_phase", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "probe_sequence": { + "name": "probe_sequence", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "probe_witness_id": { + "name": "probe_witness_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "thread_pruning_cursors_thread_idx": { + "name": "thread_pruning_cursors_thread_idx", + "columns": [ + "thread_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "thread_pruning_cursors_thread_id_threads_id_fk": { + "name": "thread_pruning_cursors_thread_id_threads_id_fk", + "tableFrom": "thread_pruning_cursors", + "columnsFrom": [ + "thread_id" + ], + "tableTo": "threads", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "thread_pruning_cursors_policy_scope_pk": { + "columns": [ + "policy", + "scope" + ], + "name": "thread_pruning_cursors_policy_scope_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": { + "thread_pruning_cursors_scope_check": { + "name": "thread_pruning_cursors_scope_check", + "value": "\"thread_pruning_cursors\".\"scope\" = coalesce(\"thread_pruning_cursors\".\"thread_id\", '')" + } + } + }, + "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_source_seq_idx": { + "name": "thread_search_segments_thread_source_seq_idx", + "columns": [ + "thread_id", + "source_seq" + ], + "isUnique": false + } + }, + "foreignKeys": { + "thread_search_segments_thread_id_threads_id_fk": { + "name": "thread_search_segments_thread_id_threads_id_fk", + "tableFrom": "thread_search_segments", + "columnsFrom": [ + "thread_id" + ], + "tableTo": "threads", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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", + "columnsFrom": [ + "thread_id" + ], + "tableTo": "threads", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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'" + }, + "startup_context": { + "name": "startup_context", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_thread_id": { + "name": "parent_thread_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "lifecycle_owner_thread_id": { + "name": "lifecycle_owner_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 + }, + "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 + }, + "storage_deleted_at": { + "name": "storage_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_id_idx": { + "name": "threads_project_id_idx", + "columns": [ + "project_id", + "id" + ], + "isUnique": false + }, + "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" + ], + "where": "\"threads\".\"pinned_at\" IS NOT NULL", + "isUnique": false + }, + "threads_environment_idx": { + "name": "threads_environment_idx", + "columns": [ + "environment_id" + ], + "isUnique": false + }, + "threads_lifecycle_owner_idx": { + "name": "threads_lifecycle_owner_idx", + "columns": [ + "lifecycle_owner_thread_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" + ], + "where": "\"threads\".\"deleted_at\" IS NULL", + "isUnique": false + } + }, + "foreignKeys": { + "threads_project_id_projects_id_fk": { + "name": "threads_project_id_projects_id_fk", + "tableFrom": "threads", + "columnsFrom": [ + "project_id" + ], + "tableTo": "projects", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "threads_environment_id_environments_id_fk": { + "name": "threads_environment_id_environments_id_fk", + "tableFrom": "threads", + "columnsFrom": [ + "environment_id" + ], + "tableTo": "environments", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "threads_section_id_thread_sections_id_fk": { + "name": "threads_section_id_thread_sections_id_fk", + "tableFrom": "threads", + "columnsFrom": [ + "section_id" + ], + "tableTo": "thread_sections", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "threads_parent_thread_id_threads_id_fk": { + "name": "threads_parent_thread_id_threads_id_fk", + "tableFrom": "threads", + "columnsFrom": [ + "parent_thread_id" + ], + "tableTo": "threads", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "threads_lifecycle_owner_thread_id_threads_id_fk": { + "name": "threads_lifecycle_owner_thread_id_threads_id_fk", + "tableFrom": "threads", + "columnsFrom": [ + "lifecycle_owner_thread_id" + ], + "tableTo": "threads", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + }, + "threads_source_thread_id_threads_id_fk": { + "name": "threads_source_thread_id_threads_id_fk", + "tableFrom": "threads", + "columnsFrom": [ + "source_thread_id" + ], + "tableTo": "threads", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ui_preferences": { + "name": "ui_preferences", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value_json": { + "name": "value_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": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + }, + "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 0b470d2ea80..b507d14229a 100644 --- a/packages/db/drizzle/meta/_journal.json +++ b/packages/db/drizzle/meta/_journal.json @@ -897,6 +897,13 @@ "when": 1789925496816, "tag": "0127_thread_image_metadata", "breakpoints": true + }, + { + "idx": 128, + "version": "6", + "when": 1789956746482, + "tag": "0128_queued_message_search", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/test/data/queued-message-search.test.ts b/packages/db/test/data/queued-message-search.test.ts new file mode 100644 index 00000000000..70b0499ebe5 --- /dev/null +++ b/packages/db/test/data/queued-message-search.test.ts @@ -0,0 +1,123 @@ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { describe, expect, it } from "vitest"; +import { type PromptInput } from "@bb/domain"; +import { noopNotifier } from "../../src/notifier.js"; +import { createMigratedConnection } from "../helpers/migrated-connection.js"; +import { upsertHost } from "../../src/data/hosts.js"; +import { createProject } from "../../src/data/projects.js"; +import { + archiveThread, + createThread, + deleteThread, + searchThreadsWithPendingInteractionState, +} from "../../src/data/threads.js"; +import { + createQueuedThreadMessage, + deleteQueuedThreadMessage, + updateQueuedThreadMessage, +} from "../../src/data/queued-thread-messages.js"; + +function text(text: string, visibility?: "agent-only"): PromptInput { + return { type: "text", text, mentions: [], ...(visibility ? { visibility } : {}) }; +} + +function setup() { + const db = createMigratedConnection(); + const host = upsertHost(db, noopNotifier, { name: "search-host" }); + const { project } = createProject(db, noopNotifier, { + name: "search-project", + source: { type: "local_path", hostId: host.id, path: "/tmp/search" }, + }); + const thread = (status: "pending" | "idle" = "pending", visibility: "visible" | "hidden" = "visible") => + createThread(db, noopNotifier, { projectId: project.id, providerId: "codex", title: "Conversation", status, visibility }); + const save = (threadId: string, content: PromptInput[]) => + createQueuedThreadMessage(db, noopNotifier, { + threadId, content, model: "gpt-5", reasoningLevel: "medium", + permissionMode: "full", serviceTier: "default", + waitingOn: { kind: "plugin", pluginId: "drafts", reason: "Draft" }, + sendAt: null, payload: { kind: "inline" }, systemNotice: null, + }); + const search = (query: string, limitPerGroup = 20) => + searchThreadsWithPendingInteractionState(db, { query, limitPerGroup }); + return { db, thread, save, search }; +} + +describe("saved message thread search", () => { + it("finds both first messages and follow-ups once per thread with normal snippets and no event anchor", () => { + const { db, thread, save, search } = setup(); + try { + const pending = thread(); + const existing = thread("idle"); + save(pending.id, [text("violet launch plan"), text("privatecode", "agent-only")]); + save(existing.id, [text("violet launch follow-up")]); + save(existing.id, [text("another violet launch note")]); + const result = search("violet launch"); + expect(result.active.total).toBe(2); + expect(new Set(result.active.results.map((row) => row.thread.id))).toEqual(new Set([pending.id, existing.id])); + for (const row of result.active.results) { + expect(row.matches[0]).toMatchObject({ sourceKind: "user_message", sourceSeq: null }); + expect(row.matches[0]?.highlightRanges.length).toBeGreaterThan(0); + } + expect(search("privatecode").active.total).toBe(0); + expect(search("violet", 1).active.results).toHaveLength(1); + expect(search("violet", 1).active.total).toBe(2); + archiveThread(db, noopNotifier, pending.id); + expect(search("violet").archived.results[0]?.thread.id).toBe(pending.id); + const hidden = thread("idle", "hidden"); + save(hidden.id, [text("violet launch hidden")]); + deleteThread(db, noopNotifier, existing.id); + expect(search("violet").active.total).toBe(0); + } finally { + db.$client.close(); + } + }); + + it("replaces edited content and removes deleted or emptied messages from the index", () => { + const { db, thread, save, search } = setup(); + try { + const owner = thread(); + const message = save(owner.id, [text("oldword")]); + const edited = updateQueuedThreadMessage(db, noopNotifier, { + id: message.id, threadId: owner.id, expectedUpdatedAt: message.updatedAt, + content: [text("newword")], + }); + expect(edited.kind).toBe("updated"); + expect(search("oldword").active.total).toBe(0); + expect(search("newword").active.total).toBe(1); + if (edited.kind !== "updated") throw new Error("Expected edited message"); + updateQueuedThreadMessage(db, noopNotifier, { + id: message.id, threadId: owner.id, expectedUpdatedAt: edited.queuedMessage.updatedAt, + content: [text("privateword", "agent-only")], + }); + expect(search("newword").active.total).toBe(0); + expect(search("privateword").active.total).toBe(0); + const removed = save(owner.id, [text("removedword")]); + deleteQueuedThreadMessage(db, noopNotifier, removed.id); + expect(search("removedword").active.total).toBe(0); + } finally { + db.$client.close(); + } + }); + + it("backfills previously saved messages without changing their queue rows", () => { + const { db, thread, save, search } = setup(); + try { + db.$client.exec(` + DROP TRIGGER queued_thread_messages_search_insert; + DROP TRIGGER queued_thread_messages_search_update; + DROP TRIGGER queued_thread_messages_search_delete; + `); + const owner = thread(); + const saved = save(owner.id, [text("preexistingmessage")]); + expect(search("preexistingmessage").active.total).toBe(0); + const migration = readFileSync(resolve(__dirname, "../../drizzle/0128_queued_message_search.sql"), "utf8"); + db.$client.exec(migration); + expect(search("preexistingmessage").active.results[0]?.thread.id).toBe(owner.id); + const persisted = db.$client.prepare("SELECT content, waiting_on FROM queued_thread_messages WHERE id = ?").get(saved.id); + expect(persisted).toEqual({ content: saved.content, waiting_on: saved.waitingOn }); + } finally { + db.$client.close(); + } + }); +}); diff --git a/packages/db/test/migrate.test.ts b/packages/db/test/migrate.test.ts index 55ad087a00b..b721e8fea36 100644 --- a/packages/db/test/migrate.test.ts +++ b/packages/db/test/migrate.test.ts @@ -736,7 +736,22 @@ function dropMarketplaceStatsColumn(db: DbConnection): void { * nothing here. Every rewind that clears 0110's journal row also clears * 0108's, so the replay recreates the table before 0110 drops it again. */ +function dropQueuedMessageSearchTriggers(db: DbConnection): void { + db.$client.exec(` + DROP TRIGGER IF EXISTS queued_thread_messages_search_insert; + DROP TRIGGER IF EXISTS queued_thread_messages_search_update; + DROP TRIGGER IF EXISTS queued_thread_messages_search_delete; + `); + if (readTableNames(db).includes("thread_search_segments")) { + db.$client.exec(` + DELETE FROM thread_search_segments + WHERE source_kind = 'user_message' AND source_key LIKE 'queued:%'; + `); + } +} + function rewindEnvironmentProvisioningMigration(db: DbConnection): void { + dropQueuedMessageSearchTriggers(db); db.$client.exec("DROP TRIGGER IF EXISTS threads_lifecycle_owner_insert"); db.$client.exec("DROP TRIGGER IF EXISTS threads_lifecycle_owner_immutable"); db.$client.exec("DROP INDEX IF EXISTS threads_lifecycle_owner_idx"); @@ -858,6 +873,7 @@ function rewindEnvironmentRowFactsMigration(db: DbConnection): void { } function rewindMachineProvidersMigration(db: DbConnection): void { + dropQueuedMessageSearchTriggers(db); const queuedDispatchOrigin = db.$client .prepare<[], TableInfoRow>("PRAGMA table_info(queued_thread_messages)") .all(); diff --git a/packages/templates/src/templates/bb-guide-threads.md b/packages/templates/src/templates/bb-guide-threads.md index e4853f55c1e..d079cdf2cfc 100644 --- a/packages/templates/src/templates/bb-guide-threads.md +++ b/packages/templates/src/templates/bb-guide-threads.md @@ -51,6 +51,7 @@ Spawning: --section Create the thread in a section --visibility visible or hidden; a child inherits its parent by default --send-at Dispatch the first message at an ISO 8601 timestamp or a duration from now (30s, 10m, 2h, 7d) + --draft Save the first message until you send it manually --file CLI-local absolute path, file: URL, or uploaded file path --image CLI-local absolute path, file: URL, or uploaded image path --origin-kind Create a fork thread @@ -82,6 +83,11 @@ Spawning: machine resolution is unchanged. Omit --base-branch for bb's default. Explicit values are exact; use origin/ for a remote ref. + --draft uses the built-in Drafts plugin to hold the first queued message. + The thread stays pending without starting a turn or provisioning its workspace. + Missing, disabled, or unavailable Drafts rejects the save instead of starting work. + Inspect or edit it with bb thread queue list/update; send it with queue send. + Deleting the queued message preserves its thread and any fork history. Before selecting a provider, run `bb environment providers --project --machine ` to see whether it is available, needs setup, or is unavailable and why. The first-party providers are Project checkout, @@ -261,6 +267,7 @@ Messaging: --reasoning-level Reasoning level override --plan Send the message as the provider's /plan action --send-at Dispatch at an ISO 8601 timestamp or a duration from now (30s, 10m, 2h, 7d) + --draft Save a follow-up until you send it manually; implies queue mode --file CLI-local absolute path, file: URL, or uploaded file path --image CLI-local absolute path, file: URL, or uploaded image path @@ -277,6 +284,13 @@ Messaging: its `id`, `waitingOn`, and `sendAt`. A deferred message waits for a thread that failed while it was deferred, and delivers when the thread is retried. + --draft saves a held queue row even while a turn is running. It cannot be + combined with --mode steer or auto. SDK callers pass + pluginSubmission: { pluginId: "drafts", data: { kind: "draft" } } to + threads.spawn or threads.send; follow-up saves use mode: "queue-if-active". + Save-only requests require the Drafts plugin to be available. Saved messages + are searchable through their owning threads with bb thread search. + --plan sends the same structured /plan command the composer's plan action sends, so the agent proposes a plan for approval before executing (Claude Code and Codex threads). Plain "/plan ..." text is not recognized; it reaches diff --git a/plugins/bb-guide/skills/bb-cli/references/thread-creation.md b/plugins/bb-guide/skills/bb-cli/references/thread-creation.md index bbe137475bd..c35436392ba 100644 --- a/plugins/bb-guide/skills/bb-cli/references/thread-creation.md +++ b/plugins/bb-guide/skills/bb-cli/references/thread-creation.md @@ -10,6 +10,10 @@ current thread's project ID to add. Omitted execution flags use remembered project defaults; without a remembered model, bb resolves the selected provider and its reported default model on the target machine. +- Add `--draft` to save the first message without starting a turn or provisioning + its environment. Drafts must be available; a failed save never falls back to + starting work. Use `bb thread queue list/update/send` to inspect, edit, or send + it. Queue deletion removes only the message, preserving the owning thread. - Select a target with `--environment`, `--new-environment`, `--base-branch`, or `--machine`. Select execution with `--provider`, `--model`, `--reasoning-level`, `--service-tier`, and `--permission-mode`. diff --git a/plugins/bb-guide/skills/bb-cli/references/thread-operation.md b/plugins/bb-guide/skills/bb-cli/references/thread-operation.md index 88984cd9adf..af7b852c319 100644 --- a/plugins/bb-guide/skills/bb-cli/references/thread-operation.md +++ b/plugins/bb-guide/skills/bb-cli/references/thread-operation.md @@ -42,6 +42,12 @@ scheduled tell neither sends nor runs. Both report `delivery: "queued"` and dispatch on the sweep after the requested time. The SDK equivalent is `sendAt` (epoch ms) on `threads.spawn` / `threads.send`. +- Add `--draft` to `bb thread spawn` or `bb thread tell` to save a message until + manual Send now. Follow-up saves imply queue mode and reject explicit steer + or auto mode. SDK callers use `pluginSubmission: { pluginId: "drafts", data: + { kind: "draft" } }` on `threads.spawn` or `threads.send`; use + `mode: "queue-if-active"` for a follow-up. Drafts must be installed, enabled, + and available or the server rejects the save before starting any work. - `bb thread queue list` shows a Sender for agent threads and system notices. SDK queue rows and `--json` include `initiator` and nullable `senderThreadId`. - A send that cannot run right now does not fail: it joins the thread's queue