diff --git a/apps/app/src/components/promptbox/banner/QueuedMessagesList.test.tsx b/apps/app/src/components/promptbox/banner/QueuedMessagesList.test.tsx
index 551312a5d54..18904dbf077 100644
--- a/apps/app/src/components/promptbox/banner/QueuedMessagesList.test.tsx
+++ b/apps/app/src/components/promptbox/banner/QueuedMessagesList.test.tsx
@@ -169,6 +169,41 @@ afterEach(() => {
});
describe("QueuedMessagesList", () => {
+ it.each([
+ { kind: "host-offline", hostName: "M4" },
+ { kind: "provisioning" },
+ { kind: "interaction" },
+ { kind: "turn-starting" },
+ { kind: "stopping" },
+ ] as const)("offers Send now for a failed $kind row", (waitingOn) => {
+ const onSend = vi.fn();
+ const { getByRole, getByText } = render(
+ ,
+ );
+ expect(getByText("Provider unavailable")).toBeTruthy();
+ const button = getByRole("button", { name: "Send queued message 1 now" });
+ expect(button.hasAttribute("disabled")).toBe(false);
+ fireEvent.click(button);
+ expect(onSend).toHaveBeenCalledWith("failed-row");
+ });
+
it("labels non-user senders and refreshes their names from the thread cache", async () => {
const queryClient = new QueryClient();
const messages = [
diff --git a/apps/app/src/components/promptbox/banner/QueuedMessagesList.tsx b/apps/app/src/components/promptbox/banner/QueuedMessagesList.tsx
index 3357fa7e79d..7231e0923ce 100644
--- a/apps/app/src/components/promptbox/banner/QueuedMessagesList.tsx
+++ b/apps/app/src/components/promptbox/banner/QueuedMessagesList.tsx
@@ -803,7 +803,7 @@ const QueuedMessageRow = memo(function QueuedMessageRow({
const hasWaitLine = queuedMessageHasWaitLine(queuedMessage);
const sendAllowed =
sendAction === "steer-when-ready" ||
- isQueuedMessageSendNowAllowed(queuedMessage.waitingOn);
+ isQueuedMessageSendNowAllowed(queuedMessage);
const sendAriaLabel =
sendAction === "steer-when-ready"
? `Steer queued message ${index + 1} when ready`
diff --git a/apps/app/src/lib/queued-message-wait.test.ts b/apps/app/src/lib/queued-message-wait.test.ts
index 4427009392a..03951a75eb2 100644
--- a/apps/app/src/lib/queued-message-wait.test.ts
+++ b/apps/app/src/lib/queued-message-wait.test.ts
@@ -201,27 +201,33 @@ describe("queuedMessageFallbackTitle", () => {
});
describe("isQueuedMessageSendNowAllowed", () => {
- it("hides send-now only for the waits a re-attempt cannot clear", () => {
- expect(isQueuedMessageSendNowAllowed({ kind: "time" })).toBe(true);
- expect(
- isQueuedMessageSendNowAllowed({
- kind: "plugin",
- pluginId: "limiter",
- reason: "busy",
- }),
- ).toBe(true);
- expect(isQueuedMessageSendNowAllowed({ kind: "thread-busy" })).toBe(true);
- expect(isQueuedMessageSendNowAllowed({ kind: "stopping" })).toBe(false);
- expect(isQueuedMessageSendNowAllowed({ kind: "turn-starting" })).toBe(
- false,
- );
- expect(isQueuedMessageSendNowAllowed(null)).toBe(true);
- expect(isQueuedMessageSendNowAllowed({ kind: "provisioning" })).toBe(false);
- expect(isQueuedMessageSendNowAllowed({ kind: "interaction" })).toBe(false);
- expect(
- isQueuedMessageSendNowAllowed({ kind: "host-offline", hostName: "M4" }),
- ).toBe(false);
- });
+ it.each([
+ { waitingOn: null, allowed: true },
+ { waitingOn: { kind: "time" }, allowed: true },
+ {
+ waitingOn: { kind: "plugin", pluginId: "limiter", reason: "busy" },
+ allowed: true,
+ },
+ { waitingOn: { kind: "thread-busy" }, allowed: true },
+ { waitingOn: { kind: "stopping" }, allowed: false },
+ { waitingOn: { kind: "turn-starting" }, allowed: false },
+ { waitingOn: { kind: "provisioning" }, allowed: false },
+ { waitingOn: { kind: "interaction" }, allowed: false },
+ { waitingOn: { kind: "host-offline", hostName: "M4" }, allowed: false },
+ ] as const)(
+ "allows manual recovery for $waitingOn",
+ ({ waitingOn, allowed }) => {
+ expect(
+ isQueuedMessageSendNowAllowed({ waitingOn, failureReason: null }),
+ ).toBe(allowed);
+ expect(
+ isQueuedMessageSendNowAllowed({
+ waitingOn,
+ failureReason: "Provider unavailable",
+ }),
+ ).toBe(true);
+ },
+ );
});
describe("formatQueuedMessageCountdown", () => {
diff --git a/apps/app/src/lib/queued-message-wait.ts b/apps/app/src/lib/queued-message-wait.ts
index a3aac025938..dea6a2a4db3 100644
--- a/apps/app/src/lib/queued-message-wait.ts
+++ b/apps/app/src/lib/queued-message-wait.ts
@@ -22,9 +22,14 @@ export function formatQueuedMessageCountdown(
return `in ${Math.floor(remainingMs / DAY_MS)}d`;
}
-export function isQueuedMessageSendNowAllowed(
- waitingOn: QueuedMessageWaitingOn | null,
-): boolean {
+export function isQueuedMessageSendNowAllowed({
+ waitingOn,
+ failureReason,
+}: {
+ waitingOn: QueuedMessageWaitingOn | null;
+ failureReason: string | null;
+}): boolean {
+ if (failureReason !== null) return true;
if (waitingOn === null) return true;
switch (waitingOn.kind) {
case "provisioning":
diff --git a/apps/cli/src/__tests__/command-output/thread-organization.test.ts b/apps/cli/src/__tests__/command-output/thread-organization.test.ts
index 5c2edcab641..fe27735580d 100644
--- a/apps/cli/src/__tests__/command-output/thread-organization.test.ts
+++ b/apps/cli/src/__tests__/command-output/thread-organization.test.ts
@@ -105,6 +105,36 @@ describe("bb thread organization commands", () => {
expect(output).toContain("System");
});
+ it.each([false, true])(
+ "shows failures and recovery commands in queue list (scoped: %s)",
+ async (scoped) => {
+ const failureReason =
+ "The provider bridge is unavailable while the plugin is still building";
+ const list = vi.fn(async () => [
+ queuedMessage({
+ waitingOn: { kind: "host-offline", hostName: "Michael-M4" },
+ failureReason,
+ }),
+ ]);
+ stubServerApi({
+ [scoped
+ ? "v1.threads.:id.queued-messages.$get"
+ : "v1.queued-messages.$get"]: list,
+ });
+ await runCommand(
+ ["thread", "queue", "list", ...(scoped ? ["thread-1"] : [])],
+ register,
+ );
+ const output = vi
+ .mocked(console.log)
+ .mock.calls.map((args) => args.join(" "))
+ .join("\n");
+ expect(output).toContain(`Failed queued-1: ${failureReason}`);
+ expect(output).toContain("bb thread queue send thread-1 queued-1");
+ expect(output).not.toContain("waiting for Michael-M4 to reconnect");
+ },
+ );
+
it("updates a queued message in place", async () => {
const list = vi.fn(async () => [
{ id: "queued-1", updatedAt: 42 },
diff --git a/apps/cli/src/commands/thread/organization.ts b/apps/cli/src/commands/thread/organization.ts
index f5e53141b51..d6ddadcf1b0 100644
--- a/apps/cli/src/commands/thread/organization.ts
+++ b/apps/cli/src/commands/thread/organization.ts
@@ -120,7 +120,12 @@ function printQueueTable(rows: ThreadQueuedMessagesResult): void {
? "System"
: (row.senderThreadId ?? "Agent"),
truncateCell(queuedMessagePreview(row.content), MAX_QUEUE_TEXT_WIDTH),
- truncateCell(describeQueueWait(row), MAX_QUEUE_TEXT_WIDTH),
+ truncateCell(
+ row.failureReason === null
+ ? describeQueueWait(row)
+ : `Failed: ${row.failureReason}`,
+ MAX_QUEUE_TEXT_WIDTH,
+ ),
formatQueueSendCountdown(row.sendAt, now),
]);
printBorderlessTable(
@@ -130,6 +135,11 @@ function printQueueTable(rows: ThreadQueuedMessagesResult): void {
},
table,
);
+ for (const row of rows) {
+ if (row.failureReason === null) continue;
+ console.log(`Failed ${row.id}: ${row.failureReason}`);
+ console.log(`Retry: bb thread queue send ${row.threadId} ${row.id}`);
+ }
}
function queuedMessagePreview(content: PromptInput[]): string {
diff --git a/apps/server/src/services/system/periodic-sweeps.ts b/apps/server/src/services/system/periodic-sweeps.ts
index 5028ae72e5c..5f213bd25d7 100644
--- a/apps/server/src/services/system/periodic-sweeps.ts
+++ b/apps/server/src/services/system/periodic-sweeps.ts
@@ -591,6 +591,13 @@ const PERIODIC_SWEEP_JOBS: PeriodicSweepJob[] = [
run: (deps, now) =>
runQueuedMessageDispatch(deps, { kind: "time-reached", now }),
},
+ {
+ cadenceMs: 0,
+ category: "durable-intent-retry",
+ name: "failed-queue-message-retry",
+ run: (deps, now) =>
+ runQueuedMessageDispatch(deps, { kind: "failed-retry", now }),
+ },
{
cadenceMs: 0,
category: "durable-intent-retry",
diff --git a/apps/server/src/services/threads/queue-drain-failure.ts b/apps/server/src/services/threads/queue-drain-failure.ts
index 583c61038ab..392e4fc88bd 100644
--- a/apps/server/src/services/threads/queue-drain-failure.ts
+++ b/apps/server/src/services/threads/queue-drain-failure.ts
@@ -12,6 +12,10 @@ import { dispatchEnvironmentAndHost } from "./dispatch-hooks.js";
type QueueDrainFailureDeps = Pick;
+export const QUEUED_MESSAGE_RETRY_DELAYS_MS: readonly number[] = [
+ 15_000, 60_000, 300_000,
+];
+
/**
* What a failed dispatch says to the person whose message did not go.
*
@@ -39,7 +43,10 @@ export function describeDispatchFailure(error: unknown): string {
* host-reconnect drain clears when the machine comes back. Any other failure
* is recorded as the row's failure reason, leaving its existing wait alone —
* the row is still waiting on whatever it was waiting on, and what went wrong
- * last time is a different fact from what it is waiting for.
+ * last time is a different fact from what it is waiting for — and spends one
+ * of the row's attempts, booking the next on
+ * {@link QUEUED_MESSAGE_RETRY_DELAYS_MS}. The row gives up only once that
+ * budget runs out.
*
* Only the drain calls this. An inline attempt has a caller still listening
* and surfaces its error to them instead, which is why a queued row never
@@ -49,6 +56,7 @@ export function recordQueuedMessageDrainFailure(
deps: QueueDrainFailureDeps,
args: {
error: unknown;
+ now: number;
row: { id: string; threadId: string };
thread: Thread;
},
@@ -71,5 +79,7 @@ export function recordQueuedMessageDrainFailure(
id: args.row.id,
threadId: args.row.threadId,
failureReason: describeDispatchFailure(args.error),
+ now: args.now,
+ retryDelaysMs: QUEUED_MESSAGE_RETRY_DELAYS_MS,
});
}
diff --git a/apps/server/src/services/threads/queued-message-dispatch.ts b/apps/server/src/services/threads/queued-message-dispatch.ts
index 12a86f9d332..499675377b3 100644
--- a/apps/server/src/services/threads/queued-message-dispatch.ts
+++ b/apps/server/src/services/threads/queued-message-dispatch.ts
@@ -10,6 +10,7 @@ import {
listQueuedThreadMessagePluginWaitRefs,
listQueuedThreadMessagesByWaitHolder,
listQueuedThreadMessagesWaitingOnKind,
+ listRetryableFailedQueuedThreadMessages,
listThreadIdsWithHostOfflineQueueWaits,
} from "@bb/db";
import {
@@ -47,6 +48,7 @@ export type QueuedMessageDispatchWake =
| { kind: "plugin-recheck" }
| { kind: "plugin-unregistered"; pluginId: string }
| { kind: "idle-recovery"; now: number }
+ | { kind: "failed-retry"; now: number }
| {
kind: "orphaned-plugin-recovery";
plugins: QueueWaitPluginDirectory;
@@ -116,6 +118,7 @@ function dispatchWakeContext(
case "interaction-settled":
return { threadId: wake.threadId, wake: wake.kind };
case "idle-recovery":
+ case "failed-retry":
return { now: wake.now, wake: wake.kind };
case "orphaned-plugin-recovery":
return { wake: wake.kind };
@@ -231,6 +234,7 @@ async function executePreparedQueuedMessageDispatch(
await attemptAutomaticQueuedMessage(deps, row, {
now: Date.now(),
respectRequeuePacing: false,
+ retryingFailure: false,
});
}
}
@@ -260,6 +264,9 @@ async function executePreparedQueuedMessageDispatch(
releaseStaleQueuedMessageDispatchClaims(deps, wake.now);
await runIdleThreadRecovery(deps);
return;
+ case "failed-retry":
+ await runFailedRetryDispatch(deps, wake.now);
+ return;
case "orphaned-plugin-recovery":
await runOrphanedPluginWaitRecovery(deps, wake.plugins);
return;
@@ -319,6 +326,7 @@ async function runTurnStartedDispatch(
await attemptAutomaticQueuedMessage(deps, row, {
now: Date.now(),
respectRequeuePacing: false,
+ retryingFailure: false,
});
}
}
@@ -343,6 +351,7 @@ async function runWorkspaceReadyDispatch(
await attemptAutomaticQueuedMessage(deps, row, {
now: Date.now(),
respectRequeuePacing: false,
+ retryingFailure: false,
});
}
}
@@ -364,7 +373,11 @@ async function runInteractionSettledDispatch(
async function attemptAutomaticQueuedMessage(
deps: QueueDispatchDeps,
row: QueuedMessageDispatchRef,
- args: { now: number; respectRequeuePacing: boolean },
+ args: {
+ now: number;
+ respectRequeuePacing: boolean;
+ retryingFailure: boolean;
+ },
): Promise {
if (args.respectRequeuePacing && isDispatchRequeuedRecently(row.threadId))
return;
@@ -377,8 +390,10 @@ async function attemptAutomaticQueuedMessage(
kind: "automatic",
isGroupEligible: createAutomaticQueuedMessageGroupEligibility(deps, {
now: args.now,
+ retryingFailure: args.retryingFailure,
thread,
}),
+ retryingFailure: args.retryingFailure,
},
mode: "auto",
queuedMessageId: row.id,
@@ -396,7 +411,12 @@ async function attemptAutomaticQueuedMessage(
);
return;
}
- recordQueuedMessageDrainFailure(deps, { error, row, thread });
+ recordQueuedMessageDrainFailure(deps, {
+ error,
+ now: args.now,
+ row,
+ thread,
+ });
deps.logger.warn(
{
queuedMessageId: row.id,
@@ -416,6 +436,7 @@ async function runPluginRecheckDispatch(
await attemptAutomaticQueuedMessage(deps, row, {
now,
respectRequeuePacing: true,
+ retryingFailure: false,
});
}
}
@@ -449,6 +470,40 @@ async function runDueScheduledDispatch(
await attemptAutomaticQueuedMessage(deps, row, {
now,
respectRequeuePacing: true,
+ retryingFailure: false,
+ });
+ }
+}
+
+/**
+ * Re-attempts rows whose booked retry has come due.
+ *
+ * This is the only automatic path that may claim a row with a recorded
+ * failure, and it exists because a failure is not a verdict about the message:
+ * it is what the server was able to do at one instant, usually an instant
+ * during a restart. Every other wake asks "did the thing this row waits for
+ * happen?", which a row that failed can no longer be asked — its wait may have
+ * gone stale while it sat there, and the edge that would have cleared it has
+ * passed. So this one re-asks the whole question instead, and the row's
+ * remaining attempts are what stop it asking forever.
+ */
+async function runFailedRetryDispatch(
+ deps: QueueDispatchDeps,
+ now: number,
+): Promise {
+ for (const row of listRetryableFailedQueuedThreadMessages(deps.db, now)) {
+ deps.logger.info(
+ {
+ failureCount: row.failureCount,
+ queuedMessageId: row.id,
+ threadId: row.threadId,
+ },
+ "Retrying a queued message whose dispatch failed",
+ );
+ await attemptAutomaticQueuedMessage(deps, row, {
+ now,
+ respectRequeuePacing: true,
+ retryingFailure: true,
});
}
}
diff --git a/apps/server/src/services/threads/queued-messages.ts b/apps/server/src/services/threads/queued-messages.ts
index e2fa2fb4689..a2fab087d9e 100644
--- a/apps/server/src/services/threads/queued-messages.ts
+++ b/apps/server/src/services/threads/queued-messages.ts
@@ -130,12 +130,12 @@ interface SendClaimedQueuedMessageForThreadArgs {
export function createAutomaticQueuedMessageGroupEligibility(
deps: Pick,
- args: { now: number; thread: Thread },
+ args: { now: number; retryingFailure: boolean; thread: Thread },
): QueuedThreadMessageGroupEligibility {
const activeTurnId = getActiveTurnId(deps, args.thread.id);
return (group) =>
group.every((member) => {
- if (member.failureReason !== null) return false;
+ if (member.failureReason !== null && !args.retryingFailure) return false;
const waitingOn = parseStoredQueuedThreadMessageWaitingOn(member);
switch (waitingOn?.kind) {
case undefined:
@@ -855,6 +855,7 @@ export async function sendNextQueuedMessageIfPresent(
args.threadId,
createAutomaticQueuedMessageGroupEligibility(deps, {
now: Date.now(),
+ retryingFailure: false,
thread: initialThread,
}),
);
@@ -896,6 +897,7 @@ export async function sendNextQueuedMessageIfPresent(
if (!isCommandTimeoutError(error)) {
recordQueuedMessageDrainFailure(deps, {
error,
+ now: Date.now(),
row: nextQueuedMessages[0]!,
thread,
});
diff --git a/apps/server/test/public/public-thread-fork.test.ts b/apps/server/test/public/public-thread-fork.test.ts
index 3133aedd85a..859075a46b0 100644
--- a/apps/server/test/public/public-thread-fork.test.ts
+++ b/apps/server/test/public/public-thread-fork.test.ts
@@ -808,6 +808,7 @@ describe("public thread fork route", () => {
claimPolicy: {
kind: "automatic",
isGroupEligible: () => true,
+ retryingFailure: false,
},
threadId: fork.id,
queuedMessageId: first.id,
diff --git a/apps/server/test/services/plugins/plugin-mention-providers.test.ts b/apps/server/test/services/plugins/plugin-mention-providers.test.ts
index 6ed8452ac1b..389ccc08e53 100644
--- a/apps/server/test/services/plugins/plugin-mention-providers.test.ts
+++ b/apps/server/test/services/plugins/plugin-mention-providers.test.ts
@@ -460,6 +460,7 @@ describe("plugin mention providers (bb.ui.registerMentionProvider)", () => {
claimPolicy: {
kind: "automatic",
isGroupEligible: () => true,
+ retryingFailure: false,
},
threadId: thread.id,
queuedMessageId: queued.id,
diff --git a/apps/server/test/services/threads/thread-runtime-display.test.ts b/apps/server/test/services/threads/thread-runtime-display.test.ts
index 2deaaf650c0..6d70a3b39f7 100644
--- a/apps/server/test/services/threads/thread-runtime-display.test.ts
+++ b/apps/server/test/services/threads/thread-runtime-display.test.ts
@@ -474,6 +474,8 @@ describe("thread runtime display", () => {
id: failedRow.id,
threadId: failed.thread.id,
failureReason: "The message could not be sent.",
+ now: Date.now(),
+ retryDelaysMs: [],
});
const entries = toThreadListEntryResponses(
diff --git a/apps/server/test/threads/dispatch-hooks.test.ts b/apps/server/test/threads/dispatch-hooks.test.ts
index 9953b894e97..ef587475d9d 100644
--- a/apps/server/test/threads/dispatch-hooks.test.ts
+++ b/apps/server/test/threads/dispatch-hooks.test.ts
@@ -1202,8 +1202,9 @@ describe("message.dispatch grouped authors", () => {
kind: "automatic",
isGroupEligible: createAutomaticQueuedMessageGroupEligibility(
harness.deps,
- { now: Date.now(), thread },
+ { now: Date.now(), retryingFailure: false, thread },
),
+ retryingFailure: false,
},
});
expect(seen).toHaveLength(2);
diff --git a/apps/server/test/threads/queue-drain-failure.test.ts b/apps/server/test/threads/queue-drain-failure.test.ts
index 47f1e96413b..924ee4c4332 100644
--- a/apps/server/test/threads/queue-drain-failure.test.ts
+++ b/apps/server/test/threads/queue-drain-failure.test.ts
@@ -12,7 +12,10 @@ import {
type PluginHookRegistration,
} from "../../src/services/plugins/plugin-hook-registry.js";
import { noteDispatchRequeued } from "../../src/services/threads/dispatch-hooks.js";
-import { recordQueuedMessageDrainFailure } from "../../src/services/threads/queue-drain-failure.js";
+import {
+ QUEUED_MESSAGE_RETRY_DELAYS_MS,
+ recordQueuedMessageDrainFailure,
+} from "../../src/services/threads/queue-drain-failure.js";
import { runQueuedMessageDispatch } from "../../src/services/threads/queued-message-dispatch.js";
import { toThreadQueuedMessage } from "../../src/services/threads/thread-queued-messages.js";
import { textInput } from "../helpers/prompt-input.js";
@@ -71,10 +74,14 @@ function seedQueuedRow(
return { host, thread, row };
}
-function reread(harness: TestAppHarness, queuedMessageId: string) {
+function rereadRow(harness: TestAppHarness, queuedMessageId: string) {
const row = getQueuedThreadMessage(harness.db, queuedMessageId);
if (row === null) throw new Error("the queued row vanished");
- return toThreadQueuedMessage(row);
+ return row;
+}
+
+function reread(harness: TestAppHarness, queuedMessageId: string) {
+ return toThreadQueuedMessage(rereadRow(harness, queuedMessageId));
}
describe("host-connected queue dispatch", () => {
@@ -91,6 +98,7 @@ describe("host-connected queue dispatch", () => {
for (const seeded of [away, otherAway]) {
recordQueuedMessageDrainFailure(harness.deps, {
error: new ApiError(502, "host_unavailable", "Host is not connected"),
+ now: Date.now(),
row: seeded.row,
thread: seeded.thread,
});
@@ -124,7 +132,7 @@ describe("host-connected queue dispatch", () => {
});
describe("recordQueuedMessageDrainFailure", () => {
- it("does not automatically re-attempt a terminally failed row", async () => {
+ it("hides a failed row from the wakes that are not its booked retry", async () => {
await withTestHarness(async (harness) => {
let attempts = 0;
const registry: HookRegistry = { "message.dispatch": [] };
@@ -256,6 +264,7 @@ describe("recordQueuedMessageDrainFailure", () => {
recordQueuedMessageDrainFailure(harness.deps, {
error: new ApiError(502, "host_unavailable", "Host is not connected"),
+ now: Date.now(),
row,
thread,
});
@@ -283,6 +292,7 @@ describe("recordQueuedMessageDrainFailure", () => {
recordQueuedMessageDrainFailure(harness.deps, {
error: new ApiError(409, "thread_not_writable", "Thread is archived"),
+ now: Date.now(),
row,
thread,
});
@@ -306,6 +316,7 @@ describe("recordQueuedMessageDrainFailure", () => {
recordQueuedMessageDrainFailure(harness.deps, {
error: new Error("Cannot read properties of undefined (reading 'id')"),
+ now: Date.now(),
row,
thread,
});
@@ -329,10 +340,13 @@ describe("recordQueuedMessageDrainFailure", () => {
id: row.id,
threadId: row.threadId,
failureReason: "Thread is archived",
+ now: Date.now(),
+ retryDelaysMs: [],
});
recordQueuedMessageDrainFailure(harness.deps, {
error: new ApiError(502, "host_unavailable", "Host is not connected"),
+ now: Date.now(),
row,
thread,
});
@@ -349,3 +363,225 @@ describe("recordQueuedMessageDrainFailure", () => {
});
});
});
+
+describe("a failed row's booked retry", () => {
+ const [FIRST_DELAY_MS, SECOND_DELAY_MS] = QUEUED_MESSAGE_RETRY_DELAYS_MS;
+
+ /**
+ * A dispatch hook that refuses everything, counting the attempts. Rejection
+ * is the shortest route to a recorded failure that is not the host being
+ * away, which is the one failure the drain turns into a wait instead.
+ */
+ function installRejector(): { attempts: () => number; dispose(): void } {
+ let attempts = 0;
+ const registry: HookRegistry = { "message.dispatch": [] };
+ registry["message.dispatch"].push({
+ pluginId: "rejector",
+ handler: () => {
+ attempts += 1;
+ return { action: "reject", message: "Rejected for testing" } as const;
+ },
+ });
+ setPluginHookProvider({
+ listHooks: (hook) => registry[hook],
+ invokeHook: async (_pluginId, _label, run) => ({
+ ok: true,
+ value: await run(),
+ }),
+ decisionTimeoutMs: 10_000,
+ });
+ return {
+ attempts: () => attempts,
+ dispose: () => setPluginHookProvider(undefined),
+ };
+ }
+
+ it("books the next attempt rather than giving up on the first failure", async () => {
+ await withTestHarness(async (harness) => {
+ const { thread, row } = seedQueuedRow(harness, {
+ hostConnected: true,
+ hostName: "M4",
+ });
+ const now = Date.now();
+
+ recordQueuedMessageDrainFailure(harness.deps, {
+ error: new ApiError(409, "thread_not_writable", "Thread is archived"),
+ now,
+ row,
+ thread,
+ });
+
+ const failed = rereadRow(harness, row.id);
+ expect(failed.failureReason).toBe("Thread is archived");
+ expect(failed.failureCount).toBe(1);
+ expect(failed.nextAttemptAt).toBe(now + FIRST_DELAY_MS!);
+ });
+ });
+
+ it("waits for the booked instant before trying again", async () => {
+ await withTestHarness(async (harness) => {
+ const rejector = installRejector();
+ try {
+ const { thread, row } = seedQueuedRow(harness, {
+ hostConnected: true,
+ hostName: "M4",
+ });
+
+ await runQueuedMessageDispatch(harness.deps, {
+ kind: "thread-ready",
+ threadId: thread.id,
+ });
+ expect(rejector.attempts()).toBe(1);
+ const booked = rereadRow(harness, row.id).nextAttemptAt!;
+
+ await runQueuedMessageDispatch(harness.deps, {
+ kind: "failed-retry",
+ now: booked - 1,
+ });
+ expect(rejector.attempts()).toBe(1);
+
+ await runQueuedMessageDispatch(harness.deps, {
+ kind: "failed-retry",
+ now: booked,
+ });
+ expect(rejector.attempts()).toBe(2);
+
+ // A second failure spends a second attempt and books a later one, so a
+ // row that keeps failing backs off instead of spinning on every tick.
+ const retried = rereadRow(harness, row.id);
+ expect(retried.failureCount).toBe(2);
+ expect(retried.nextAttemptAt).toBe(booked + SECOND_DELAY_MS!);
+ } finally {
+ rejector.dispose();
+ }
+ });
+ });
+
+ it("sends a row whose wait went stale while it sat failed", async () => {
+ await withTestHarness(async (harness) => {
+ const { host, thread, row } = seedQueuedRow(harness, {
+ hostConnected: false,
+ hostName: "M4",
+ });
+ const now = Date.now();
+
+ // How the stuck row is actually made: the host is away, so the drain
+ // parks the row on `host-offline`; the machine comes back, and the
+ // attempt that follows fails for its own reason and leaves that wait in
+ // place. From then on the wait describes a condition that has already
+ // cleared, and the host-reconnect wake it names has been and gone.
+ recordQueuedMessageDrainFailure(harness.deps, {
+ error: new ApiError(502, "host_unavailable", "Host is not connected"),
+ now,
+ row,
+ thread,
+ });
+ expect(reread(harness, row.id).waitingOn).toEqual({
+ kind: "host-offline",
+ hostName: "M4",
+ });
+
+ seedHostSession(harness.deps, { id: host.id, name: "M4" });
+ seedThreadRuntimeState(harness.deps, {
+ environmentId: thread.environmentId,
+ providerThreadId: "returning-machine-thread",
+ threadId: thread.id,
+ });
+ recordQueuedMessageDrainFailure(harness.deps, {
+ error: new ApiError(
+ 409,
+ "provider_bridge_unavailable",
+ 'Provider "claude-code" has no bridge to run on.',
+ ),
+ now,
+ row,
+ thread,
+ });
+ const stale = rereadRow(harness, row.id);
+ expect(stale.waitingOn).toContain("host-offline");
+ expect(stale.nextAttemptAt).toBe(now + FIRST_DELAY_MS!);
+
+ await runQueuedMessageDispatch(harness.deps, {
+ kind: "failed-retry",
+ now: stale.nextAttemptAt!,
+ });
+
+ // The retry re-asks the whole question instead of waiting on an edge
+ // that already passed, so the row goes out.
+ expect(getQueuedThreadMessage(harness.db, row.id)).toBeNull();
+ });
+ });
+
+ it("stops once the row's attempts are spent", async () => {
+ await withTestHarness(async (harness) => {
+ const rejector = installRejector();
+ try {
+ const { thread, row } = seedQueuedRow(harness, {
+ hostConnected: true,
+ hostName: "M4",
+ });
+
+ await runQueuedMessageDispatch(harness.deps, {
+ kind: "thread-ready",
+ threadId: thread.id,
+ });
+ for (const _delay of QUEUED_MESSAGE_RETRY_DELAYS_MS) {
+ const booked = rereadRow(harness, row.id).nextAttemptAt;
+ if (booked === null) break;
+ await runQueuedMessageDispatch(harness.deps, {
+ kind: "failed-retry",
+ now: booked,
+ });
+ }
+
+ const spent = rereadRow(harness, row.id);
+ expect(spent.failureCount).toBe(4);
+ expect(spent.nextAttemptAt).toBeNull();
+ expect(rejector.attempts()).toBe(4);
+
+ // Nothing automatic can reach it now: it is a row for a person, and
+ // the thread list has been showing it as failed the whole time.
+ await runQueuedMessageDispatch(harness.deps, {
+ kind: "failed-retry",
+ now: Date.now() + 86_400_000,
+ });
+ expect(rejector.attempts()).toBe(4);
+ } finally {
+ rejector.dispose();
+ }
+ });
+ });
+
+ it("gives the attempts back when the row queues again", async () => {
+ await withTestHarness(async (harness) => {
+ const { thread, row } = seedQueuedRow(harness, {
+ hostConnected: false,
+ hostName: "M4",
+ });
+
+ setQueuedThreadMessageFailureReason(harness.db, harness.deps.hub, {
+ id: row.id,
+ threadId: row.threadId,
+ failureReason: "Thread is archived",
+ now: Date.now(),
+ retryDelaysMs: QUEUED_MESSAGE_RETRY_DELAYS_MS,
+ });
+ expect(rereadRow(harness, row.id).failureCount).toBe(1);
+
+ recordQueuedMessageDrainFailure(harness.deps, {
+ error: new ApiError(502, "host_unavailable", "Host is not connected"),
+ now: Date.now(),
+ row,
+ thread,
+ });
+
+ // Re-queueing is a fresh, successful statement of why the row waits, so
+ // the row starts its budget over rather than carrying attempts it spent
+ // against a condition it has since got past.
+ const requeued = rereadRow(harness, row.id);
+ expect(requeued.failureReason).toBeNull();
+ expect(requeued.failureCount).toBe(0);
+ expect(requeued.nextAttemptAt).toBeNull();
+ });
+ });
+});
diff --git a/apps/server/test/threads/requested-queue-drain.test.ts b/apps/server/test/threads/requested-queue-drain.test.ts
index 284c555e894..04356a16f0d 100644
--- a/apps/server/test/threads/requested-queue-drain.test.ts
+++ b/apps/server/test/threads/requested-queue-drain.test.ts
@@ -534,6 +534,8 @@ describe("the requested queue drain", () => {
id: failed.id,
threadId: thread.id,
failureReason: "Terminal failure",
+ now: Date.now(),
+ retryDelaysMs: [],
});
if (drain === "scheduled") await runTimeWake(harness, Date.now());
diff --git a/apps/server/test/threads/thread-send-dispatch.test.ts b/apps/server/test/threads/thread-send-dispatch.test.ts
index 87246e4a001..acab5bdc3a1 100644
--- a/apps/server/test/threads/thread-send-dispatch.test.ts
+++ b/apps/server/test/threads/thread-send-dispatch.test.ts
@@ -163,8 +163,9 @@ describe("queued message dispatch hook", () => {
kind: "automatic",
isGroupEligible: createAutomaticQueuedMessageGroupEligibility(
harness.deps,
- { now: Date.now(), thread },
+ { now: Date.now(), retryingFailure: false, thread },
),
+ retryingFailure: false,
},
threadId: thread.id,
queuedMessageId: queued.id,
@@ -206,8 +207,9 @@ describe("queued message dispatch hook", () => {
kind: "automatic",
isGroupEligible: createAutomaticQueuedMessageGroupEligibility(
harness.deps,
- { now: Date.now(), thread },
+ { now: Date.now(), retryingFailure: false, thread },
),
+ retryingFailure: false,
},
threadId: thread.id,
queuedMessageId: queued.id,
@@ -245,8 +247,9 @@ describe("queued message auto-send notification", () => {
kind: "automatic",
isGroupEligible: createAutomaticQueuedMessageGroupEligibility(
harness.deps,
- { now: Date.now(), thread },
+ { now: Date.now(), retryingFailure: false, thread },
),
+ retryingFailure: false,
},
threadId: thread.id,
queuedMessageId: queued.id,
@@ -620,6 +623,8 @@ describe("startup queue waits", () => {
id: failed.id,
threadId: thread.id,
failureReason: "Terminal failure",
+ now: Date.now(),
+ retryDelaysMs: [],
});
setQueuedThreadMessageGroupBoundary({
db: harness.db,
@@ -1385,8 +1390,9 @@ describe("service tier execution lifecycle", () => {
kind: "automatic",
isGroupEligible: createAutomaticQueuedMessageGroupEligibility(
harness.deps,
- { now: Date.now(), thread },
+ { now: Date.now(), retryingFailure: false, thread },
),
+ retryingFailure: false,
},
threadId: thread.id,
queuedMessageId: older.id,
diff --git a/packages/db/drizzle/0128_burly_jazinda.sql b/packages/db/drizzle/0128_burly_jazinda.sql
new file mode 100644
index 00000000000..c0b6f976183
--- /dev/null
+++ b/packages/db/drizzle/0128_burly_jazinda.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `queued_thread_messages` ADD `failure_count` integer DEFAULT 0 NOT NULL;--> statement-breakpoint
+ALTER TABLE `queued_thread_messages` ADD `next_attempt_at` integer;
\ 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..21abfa9eb20
--- /dev/null
+++ b/packages/db/drizzle/meta/0128_snapshot.json
@@ -0,0 +1,5083 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "4c665fb9-6b38-402a-99fc-4cda889c9090",
+ "prevId": "6c59f5c6-5254-4535-8f08-708287d8c133",
+ "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",
+ "tableTo": "user",
+ "columnsFrom": [
+ "referenceId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user": {
+ "name": "user",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "emailVerified": {
+ "name": "emailVerified",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "columns": [
+ "email"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "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"
+ ],
+ "isUnique": true,
+ "where": "\"environment_variables\".\"project_id\" IS NULL"
+ },
+ "environment_variables_project_name": {
+ "name": "environment_variables_project_name",
+ "columns": [
+ "project_id",
+ "name"
+ ],
+ "isUnique": true,
+ "where": "\"environment_variables\".\"project_id\" IS NOT NULL"
+ }
+ },
+ "foreignKeys": {
+ "environment_variables_project_id_projects_id_fk": {
+ "name": "environment_variables_project_id_projects_id_fk",
+ "tableFrom": "environment_variables",
+ "tableTo": "projects",
+ "columnsFrom": [
+ "project_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "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",
+ "tableTo": "projects",
+ "columnsFrom": [
+ "project_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "environments_host_id_hosts_id_fk": {
+ "name": "environments_host_id_hosts_id_fk",
+ "tableFrom": "environments",
+ "tableTo": "hosts",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "events": {
+ "name": "events",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "scope_kind": {
+ "name": "scope_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "turn_id": {
+ "name": "turn_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "provider_thread_id": {
+ "name": "provider_thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sequence": {
+ "name": "sequence",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "item_id": {
+ "name": "item_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "item_kind": {
+ "name": "item_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "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"
+ ],
+ "isUnique": false,
+ "where": "\"events\".\"item_kind\" IN ('toolCall', 'delegation')"
+ },
+ "events_plan_steps_thread_sequence_idx": {
+ "name": "events_plan_steps_thread_sequence_idx",
+ "columns": [
+ "thread_id",
+ "sequence"
+ ],
+ "isUnique": false,
+ "where": "(\"events\".\"item_kind\" = 'planSteps' AND \"events\".\"type\" = 'item/completed') OR \"events\".\"type\" = 'turn/plan/updated'"
+ },
+ "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"
+ ],
+ "isUnique": false,
+ "where": "\"events\".\"parent_tool_call_id\" IS NOT NULL"
+ },
+ "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"
+ ],
+ "isUnique": false,
+ "where": "\"events\".\"item_kind\" = 'backgroundTask'"
+ },
+ "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"
+ ],
+ "isUnique": false,
+ "where": "\"events\".\"type\" IN ('item/started', 'item/completed', 'item/backgroundTask/completed')"
+ },
+ "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"
+ ],
+ "isUnique": false,
+ "where": "\"events\".\"type\" = 'thread/identity'"
+ },
+ "events_completed_item_truncation_idx": {
+ "name": "events_completed_item_truncation_idx",
+ "columns": [
+ "item_kind",
+ "created_at",
+ "id"
+ ],
+ "isUnique": false,
+ "where": "\"events\".\"type\" = 'item/completed'"
+ },
+ "events_thread_state_thread_sequence_idx": {
+ "name": "events_thread_state_thread_sequence_idx",
+ "columns": [
+ "thread_id",
+ "sequence"
+ ],
+ "isUnique": false,
+ "where": "\"events\".\"type\" IN ('thread/goal/updated', 'thread/goal/cleared', 'thread/extensionState/updated')"
+ }
+ },
+ "foreignKeys": {
+ "events_thread_id_threads_id_fk": {
+ "name": "events_thread_id_threads_id_fk",
+ "tableFrom": "events",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "events_environment_id_environments_id_fk": {
+ "name": "events_environment_id_environments_id_fk",
+ "tableFrom": "events",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {
+ "events_scope_shape_check": {
+ "name": "events_scope_shape_check",
+ "value": "(\n (\"events\".\"scope_kind\" = 'turn' AND \"events\".\"turn_id\" IS NOT NULL)\n OR\n (\"events\".\"scope_kind\" = 'thread' AND \"events\".\"turn_id\" IS NULL)\n )"
+ }
+ }
+ },
+ "host_daemon_sessions": {
+ "name": "host_daemon_sessions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "instance_id": {
+ "name": "instance_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_name": {
+ "name": "host_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "data_dir": {
+ "name": "data_dir",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "protocol_version": {
+ "name": "protocol_version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "heartbeat_interval_ms": {
+ "name": "heartbeat_interval_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "lease_timeout_ms": {
+ "name": "lease_timeout_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "lease_expires_at": {
+ "name": "lease_expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "closed_at": {
+ "name": "closed_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "close_reason": {
+ "name": "close_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "host_daemon_sessions_host_status_idx": {
+ "name": "host_daemon_sessions_host_status_idx",
+ "columns": [
+ "host_id",
+ "status"
+ ],
+ "isUnique": false
+ },
+ "host_daemon_sessions_host_latest_idx": {
+ "name": "host_daemon_sessions_host_latest_idx",
+ "columns": [
+ "host_id",
+ "updated_at",
+ "created_at",
+ "id"
+ ],
+ "isUnique": false
+ },
+ "host_daemon_sessions_closed_prune_idx": {
+ "name": "host_daemon_sessions_closed_prune_idx",
+ "columns": [
+ "status",
+ "closed_at",
+ "id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "host_daemon_sessions_host_id_hosts_id_fk": {
+ "name": "host_daemon_sessions_host_id_hosts_id_fk",
+ "tableFrom": "host_daemon_sessions",
+ "tableTo": "hosts",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "hosts": {
+ "name": "hosts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "connect_machine_id": {
+ "name": "connect_machine_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "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"
+ ],
+ "isUnique": true,
+ "where": "\"hosts\".\"destroyed_at\" is null"
+ }
+ },
+ "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",
+ "tableTo": "plugin_artifacts",
+ "columnsFrom": [
+ "active_artifact_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "maintenance_scan_cursors": {
+ "name": "maintenance_scan_cursors",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "policy": {
+ "name": "policy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "item_kind": {
+ "name": "item_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "output_path": {
+ "name": "output_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "last_created_at": {
+ "name": "last_created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "last_event_id": {
+ "name": "last_event_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "maintenance_scan_cursors_path_idx": {
+ "name": "maintenance_scan_cursors_path_idx",
+ "columns": [
+ "policy",
+ "version",
+ "item_kind",
+ "output_path"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "pending_interactions": {
+ "name": "pending_interactions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "origin_kind": {
+ "name": "origin_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'provider'"
+ },
+ "turn_id": {
+ "name": "turn_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "provider_thread_id": {
+ "name": "provider_thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "provider_request_id": {
+ "name": "provider_request_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "plugin_id": {
+ "name": "plugin_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "renderer_id": {
+ "name": "renderer_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "payload": {
+ "name": "payload",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "resolution": {
+ "name": "resolution",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status_reason": {
+ "name": "status_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "pending_interactions_provider_request_idx": {
+ "name": "pending_interactions_provider_request_idx",
+ "columns": [
+ "provider_id",
+ "provider_thread_id",
+ "provider_request_id"
+ ],
+ "isUnique": true
+ },
+ "pending_interactions_thread_created_idx": {
+ "name": "pending_interactions_thread_created_idx",
+ "columns": [
+ "thread_id",
+ "created_at"
+ ],
+ "isUnique": false
+ },
+ "pending_interactions_thread_status_created_idx": {
+ "name": "pending_interactions_thread_status_created_idx",
+ "columns": [
+ "thread_id",
+ "status",
+ "created_at"
+ ],
+ "isUnique": false
+ },
+ "pending_interactions_status_created_idx": {
+ "name": "pending_interactions_status_created_idx",
+ "columns": [
+ "status",
+ "created_at"
+ ],
+ "isUnique": false
+ },
+ "pending_interactions_plugin_status_created_idx": {
+ "name": "pending_interactions_plugin_status_created_idx",
+ "columns": [
+ "plugin_id",
+ "status",
+ "created_at"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "pending_interactions_thread_id_threads_id_fk": {
+ "name": "pending_interactions_thread_id_threads_id_fk",
+ "tableFrom": "pending_interactions",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "plugin_artifacts": {
+ "name": "plugin_artifacts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "plugin_id": {
+ "name": "plugin_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "source_kind": {
+ "name": "source_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "npm_resolved_version": {
+ "name": "npm_resolved_version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "git_resolved_commit": {
+ "name": "git_resolved_commit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "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",
+ "tableTo": "projects",
+ "columnsFrom": [
+ "project_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "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",
+ "tableTo": "project_attachments",
+ "columnsFrom": [
+ "attachment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "project_attachment_threads_thread_id_threads_id_fk": {
+ "name": "project_attachment_threads_thread_id_threads_id_fk",
+ "tableFrom": "project_attachment_threads",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "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"
+ ],
+ "isUnique": false,
+ "where": "\"project_attachments\".\"deletion_claimed_at\" IS NOT NULL"
+ }
+ },
+ "foreignKeys": {
+ "project_attachments_project_id_projects_id_fk": {
+ "name": "project_attachments_project_id_projects_id_fk",
+ "tableFrom": "project_attachments",
+ "tableTo": "projects",
+ "columnsFrom": [
+ "project_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "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",
+ "tableTo": "projects",
+ "columnsFrom": [
+ "project_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "project_sources": {
+ "name": "project_sources",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "host_id": {
+ "name": "host_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "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",
+ "tableTo": "projects",
+ "columnsFrom": [
+ "project_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "project_sources_host_id_hosts_id_fk": {
+ "name": "project_sources_host_id_hosts_id_fk",
+ "tableFrom": "project_sources",
+ "tableTo": "hosts",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {
+ "project_sources_shape_check": {
+ "name": "project_sources_shape_check",
+ "value": "(\n \"project_sources\".\"type\" = 'local_path' AND \"project_sources\".\"host_id\" IS NOT NULL AND \"project_sources\".\"path\" IS NOT NULL\n )"
+ }
+ }
+ },
+ "projects": {
+ "name": "projects",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'standard'"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "git_remote_url": {
+ "name": "git_remote_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sort_key": {
+ "name": "sort_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'V'"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "projects_updated_idx": {
+ "name": "projects_updated_idx",
+ "columns": [
+ "updated_at"
+ ],
+ "isUnique": false
+ },
+ "projects_deleted_idx": {
+ "name": "projects_deleted_idx",
+ "columns": [
+ "deleted_at"
+ ],
+ "isUnique": false
+ },
+ "projects_sort_idx": {
+ "name": "projects_sort_idx",
+ "columns": [
+ "sort_key",
+ "id"
+ ],
+ "isUnique": false
+ },
+ "projects_personal_singleton_idx": {
+ "name": "projects_personal_singleton_idx",
+ "columns": [
+ "kind"
+ ],
+ "isUnique": true,
+ "where": "\"projects\".\"kind\" = 'personal'"
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "prompt_history_entries": {
+ "name": "prompt_history_entries",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "request_sequence": {
+ "name": "request_sequence",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "input": {
+ "name": "input",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "prompt_history_entries_thread_request_idx": {
+ "name": "prompt_history_entries_thread_request_idx",
+ "columns": [
+ "thread_id",
+ "request_sequence"
+ ],
+ "isUnique": true
+ },
+ "prompt_history_entries_project_scope_created_idx": {
+ "name": "prompt_history_entries_project_scope_created_idx",
+ "columns": [
+ "project_id",
+ "scope",
+ "created_at",
+ "request_sequence",
+ "id"
+ ],
+ "isUnique": false
+ },
+ "prompt_history_entries_thread_scope_created_idx": {
+ "name": "prompt_history_entries_thread_scope_created_idx",
+ "columns": [
+ "thread_id",
+ "scope",
+ "created_at",
+ "request_sequence",
+ "id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "prompt_history_entries_project_id_projects_id_fk": {
+ "name": "prompt_history_entries_project_id_projects_id_fk",
+ "tableFrom": "prompt_history_entries",
+ "tableTo": "projects",
+ "columnsFrom": [
+ "project_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "prompt_history_entries_thread_id_threads_id_fk": {
+ "name": "prompt_history_entries_thread_id_threads_id_fk",
+ "tableFrom": "prompt_history_entries",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "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",
+ "tableTo": "hosts",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "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
+ },
+ "failure_count": {
+ "name": "failure_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "next_attempt_at": {
+ "name": "next_attempt_at",
+ "type": "integer",
+ "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"
+ ],
+ "isUnique": false,
+ "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"
+ },
+ "queued_thread_messages_wait_holder_idx": {
+ "name": "queued_thread_messages_wait_holder_idx",
+ "columns": [
+ "wait_holder",
+ "id"
+ ],
+ "isUnique": false,
+ "where": "\"queued_thread_messages\".\"wait_holder\" IS NOT NULL"
+ }
+ },
+ "foreignKeys": {
+ "queued_thread_messages_thread_id_threads_id_fk": {
+ "name": "queued_thread_messages_thread_id_threads_id_fk",
+ "tableFrom": "queued_thread_messages",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "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",
+ "tableTo": "events",
+ "columnsFrom": [
+ "event_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "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",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "terminal_sessions_environment_id_environments_id_fk": {
+ "name": "terminal_sessions_environment_id_environments_id_fk",
+ "tableFrom": "terminal_sessions",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "terminal_sessions_host_id_hosts_id_fk": {
+ "name": "terminal_sessions_host_id_hosts_id_fk",
+ "tableFrom": "terminal_sessions",
+ "tableTo": "hosts",
+ "columnsFrom": [
+ "host_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "terminal_sessions_daemon_session_id_host_daemon_sessions_id_fk": {
+ "name": "terminal_sessions_daemon_session_id_host_daemon_sessions_id_fk",
+ "tableFrom": "terminal_sessions",
+ "tableTo": "host_daemon_sessions",
+ "columnsFrom": [
+ "daemon_session_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "thread_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",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "thread_dynamic_context_file_states": {
+ "name": "thread_dynamic_context_file_states",
+ "columns": {
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "file_key": {
+ "name": "file_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content_status": {
+ "name": "content_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content_hash": {
+ "name": "content_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "shown_at": {
+ "name": "shown_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "thread_dynamic_context_file_states_thread_file_idx": {
+ "name": "thread_dynamic_context_file_states_thread_file_idx",
+ "columns": [
+ "thread_id",
+ "file_key"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "thread_dynamic_context_file_states_thread_id_threads_id_fk": {
+ "name": "thread_dynamic_context_file_states_thread_id_threads_id_fk",
+ "tableFrom": "thread_dynamic_context_file_states",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "thread_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",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "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",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "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",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "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",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "thread_sections": {
+ "name": "thread_sections",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "thread_sections_name_idx": {
+ "name": "thread_sections_name_idx",
+ "columns": [
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "thread_tabs": {
+ "name": "thread_tabs",
+ "columns": {
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "tabs_json": {
+ "name": "tabs_json",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "revision": {
+ "name": "revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "thread_tabs_thread_id_threads_id_fk": {
+ "name": "thread_tabs_thread_id_threads_id_fk",
+ "tableFrom": "thread_tabs",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "threads": {
+ "name": "threads",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "environment_id": {
+ "name": "environment_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "model_override": {
+ "name": "model_override",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "reasoning_level_override": {
+ "name": "reasoning_level_override",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "title_fallback": {
+ "name": "title_fallback",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "section_id": {
+ "name": "section_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'starting'"
+ },
+ "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"
+ ],
+ "isUnique": false,
+ "where": "\"threads\".\"pinned_at\" IS NOT NULL"
+ },
+ "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"
+ ],
+ "isUnique": false,
+ "where": "\"threads\".\"deleted_at\" IS NULL"
+ }
+ },
+ "foreignKeys": {
+ "threads_project_id_projects_id_fk": {
+ "name": "threads_project_id_projects_id_fk",
+ "tableFrom": "threads",
+ "tableTo": "projects",
+ "columnsFrom": [
+ "project_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "threads_environment_id_environments_id_fk": {
+ "name": "threads_environment_id_environments_id_fk",
+ "tableFrom": "threads",
+ "tableTo": "environments",
+ "columnsFrom": [
+ "environment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "threads_section_id_thread_sections_id_fk": {
+ "name": "threads_section_id_thread_sections_id_fk",
+ "tableFrom": "threads",
+ "tableTo": "thread_sections",
+ "columnsFrom": [
+ "section_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "threads_parent_thread_id_threads_id_fk": {
+ "name": "threads_parent_thread_id_threads_id_fk",
+ "tableFrom": "threads",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "parent_thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "threads_lifecycle_owner_thread_id_threads_id_fk": {
+ "name": "threads_lifecycle_owner_thread_id_threads_id_fk",
+ "tableFrom": "threads",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "lifecycle_owner_thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "threads_source_thread_id_threads_id_fk": {
+ "name": "threads_source_thread_id_threads_id_fk",
+ "tableFrom": "threads",
+ "tableTo": "threads",
+ "columnsFrom": [
+ "source_thread_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "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": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+}
\ No newline at end of file
diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json
index 0b470d2ea80..8a18070fc12 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": 1790008317938,
+ "tag": "0128_burly_jazinda",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/packages/db/src/data/index.ts b/packages/db/src/data/index.ts
index 288f68fbff5..507db715316 100644
--- a/packages/db/src/data/index.ts
+++ b/packages/db/src/data/index.ts
@@ -431,6 +431,7 @@ export {
listQueuedThreadMessagesForApi,
listQueuedThreadMessagesByWaitHolder,
listQueuedThreadMessagesWaitingOnKind,
+ listRetryableFailedQueuedThreadMessages,
listThreadIdsWithHostOfflineQueueWaits,
releaseQueuedMessageClaim,
requeueClaimedQueuedThreadMessages,
diff --git a/packages/db/src/data/queued-thread-messages.ts b/packages/db/src/data/queued-thread-messages.ts
index 79a029c6a60..05147f2d1c9 100644
--- a/packages/db/src/data/queued-thread-messages.ts
+++ b/packages/db/src/data/queued-thread-messages.ts
@@ -957,15 +957,23 @@ export type QueuedThreadMessageGroupClaimPolicy =
| {
kind: "automatic";
isGroupEligible: QueuedThreadMessageGroupEligibility;
+ /**
+ * True only for the retry the row's own `next_attempt_at` booked. A recorded
+ * failure hides a row from every other automatic claim — otherwise the
+ * idle drain would re-run a failing send every sweep tick — and the one
+ * claim that must see through it is the retry of that failure.
+ */
+ retryingFailure: boolean;
}
| { kind: "explicit-send" };
function isAutomaticQueuedThreadMessageGroupClaimAllowed(
rows: readonly QueuedThreadMessageRow[],
pauseOrdinaryMessages: boolean,
+ retryingFailure: boolean,
): boolean {
return (
- rows.every((row) => row.failureReason === null) &&
+ (retryingFailure || rows.every((row) => row.failureReason === null)) &&
(!pauseOrdinaryMessages ||
rows.every((row) => !isOrdinaryTurnEndQueuedMessage(row)))
);
@@ -997,6 +1005,7 @@ export function claimQueuedThreadMessageGroup(
!isAutomaticQueuedThreadMessageGroupClaimAllowed(
group,
isThreadQueueAutoSendPaused(tx, existing.threadId),
+ policy.retryingFailure,
)) ||
(policy.kind === "automatic" && !policy.isGroupEligible(group))
) {
@@ -1049,6 +1058,7 @@ export function claimNextQueuedThreadMessageGroup(
isAutomaticQueuedThreadMessageGroupClaimAllowed(
rows,
pauseOrdinaryMessages,
+ false,
)
);
}) ?? null;
@@ -1301,6 +1311,8 @@ export function requeueClaimedQueuedThreadMessages(
waitHolder: waitHolderFor(args.waitingOn),
sendAt: args.sendAt,
failureReason: null,
+ failureCount: 0,
+ nextAttemptAt: null,
updatedAt: now,
})
.where(
@@ -1321,8 +1333,12 @@ export function requeueClaimedQueuedThreadMessages(
// A re-queue is a fresh, successful statement of why this row is
// waiting, which supersedes whatever the previous attempt failed
// with. Leaving a stale failure next to a current wait would show
- // the user two contradictory explanations of the same row.
+ // the user two contradictory explanations of the same row, and
+ // would spend the row's remaining attempts against a failure it
+ // has since got past.
failureReason: null,
+ failureCount: 0,
+ nextAttemptAt: null,
updatedAt: now,
})
.where(
@@ -1674,8 +1690,11 @@ export function setQueuedThreadMessageWaitingOn(
// successful statement of why this row is waiting supersedes whatever
// a previous attempt failed with. Leaving a stale failure beside a
// current wait would show the reader two contradictory explanations of
- // one row.
+ // one row, and would spend the row's remaining attempts against a
+ // failure it has since got past.
failureReason: null,
+ failureCount: 0,
+ nextAttemptAt: null,
updatedAt: Date.now(),
})
.where(
@@ -1698,6 +1717,14 @@ export interface SetQueuedThreadMessageFailureReasonArgs {
id: string;
threadId: string;
failureReason: string;
+ now: number;
+ /**
+ * How long to wait before each further automatic attempt, indexed by the
+ * failures already recorded. Running off the end is what makes a failure
+ * terminal, so the caller decides how many attempts a row gets and how far
+ * apart — the policy is the server's, the counting is this row's.
+ */
+ retryDelaysMs: readonly number[];
}
/**
@@ -1708,28 +1735,52 @@ export interface SetQueuedThreadMessageFailureReasonArgs {
* is still waiting on whatever it was waiting on, and the failure is a separate
* fact about the last attempt rather than a new reason to wait. A later
* successful re-queue clears it (see `requeueClaimedQueuedThreadMessages`).
+ *
+ * Recording a failure also spends one of the row's attempts and books the next
+ * one. What failed a dispatch is usually a condition with an end — a provider
+ * whose plugin is still loading, a workspace mid-rebuild — so the row is owed
+ * another try before anybody is asked to look at it. It is terminal only once
+ * `retryDelaysMs` runs out, which is the state `next_attempt_at` NULL records.
*/
export function setQueuedThreadMessageFailureReason(
db: DbConnection,
notifier: DbNotifier,
args: SetQueuedThreadMessageFailureReasonArgs,
): QueuedThreadMessageRow | null {
- const updated =
- db
- .update(queuedThreadMessages)
- .set({
- failureReason: args.failureReason,
- updatedAt: Date.now(),
- })
- .where(
- and(
- eq(queuedThreadMessages.id, args.id),
- eq(queuedThreadMessages.threadId, args.threadId),
- liveQueuedThreadMessage(),
- ),
- )
- .returning()
- .get() ?? null;
+ const updated = db.transaction(
+ (tx): QueuedThreadMessageRow | null => {
+ const existing = getQueuedThreadMessage(tx, args.id);
+ if (
+ !existing ||
+ existing.threadId !== args.threadId ||
+ isQueuedThreadMessageClaimed(existing)
+ ) {
+ return null;
+ }
+ const failureCount = existing.failureCount + 1;
+ const delayMs = args.retryDelaysMs[failureCount - 1];
+ return (
+ tx
+ .update(queuedThreadMessages)
+ .set({
+ failureReason: args.failureReason,
+ failureCount,
+ nextAttemptAt: delayMs === undefined ? null : args.now + delayMs,
+ updatedAt: args.now,
+ })
+ .where(
+ and(
+ eq(queuedThreadMessages.id, args.id),
+ eq(queuedThreadMessages.threadId, args.threadId),
+ liveQueuedThreadMessage(),
+ ),
+ )
+ .returning()
+ .get() ?? null
+ );
+ },
+ { behavior: "immediate" },
+ );
if (updated) {
notifier.notifyThread(args.threadId, ["queue-changed"]);
@@ -1737,6 +1788,47 @@ export function setQueuedThreadMessageFailureReason(
return updated;
}
+/**
+ * Every live row whose booked retry has come due, oldest first.
+ *
+ * Deliberately not filtered by wait: the retry is not the wait's wake firing
+ * again, it is core re-asking the whole question from scratch, which is the
+ * only thing that can move a row whose wait went stale while it sat failed
+ * (a `host-offline` row whose host came back during the failure, say). Rows
+ * on archived or deleted threads are excluded for the same reason the due
+ * sweep excludes them: nobody is waiting for those to send.
+ */
+export function listRetryableFailedQueuedThreadMessages(
+ db: DbQueryConnection,
+ now: number,
+): QueuedThreadMessageRow[] {
+ return db
+ .select()
+ .from(queuedThreadMessages)
+ .where(
+ and(
+ isNotNull(queuedThreadMessages.failureReason),
+ isNotNull(queuedThreadMessages.nextAttemptAt),
+ lte(queuedThreadMessages.nextAttemptAt, now),
+ liveQueuedThreadMessage(),
+ exists(
+ db
+ .select({ live: sql`1` })
+ .from(threads)
+ .where(
+ and(
+ eq(threads.id, queuedThreadMessages.threadId),
+ isNull(threads.archivedAt),
+ isNull(threads.deletedAt),
+ ),
+ ),
+ ),
+ ),
+ )
+ .orderBy(asc(queuedThreadMessages.nextAttemptAt), asc(queuedThreadMessages.id))
+ .all();
+}
+
/**
* Drop a live row's wait, leaving it an ordinary queued row eligible at the
* next drain. `sendAt` is cleared with it: a row with no wait is not waiting
diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts
index 5ff43ebcb9e..857d59806de 100644
--- a/packages/db/src/schema.ts
+++ b/packages/db/src/schema.ts
@@ -1030,6 +1030,17 @@ export const queuedThreadMessages = sqliteTable(
// row stays waiting on whatever it was waiting on; this only says what went
// wrong the last time the drain tried to send it.
failureReason: text("failure_reason"),
+ // How many drain attempts in a row have failed, and when the next
+ // automatic one may run. Together they make a failure a bounded retry
+ // instead of a terminal state: the condition that failed a dispatch is
+ // usually the one a restart just created, so the row goes again on a
+ // widening delay and only stops when the budget is spent. `next_attempt_at`
+ // NULL beside a non-NULL `failure_reason` IS that spent budget — the row
+ // now waits for a person. A fresh, successful statement of the row's wait
+ // resets both, because the attempt that wrote it learned something newer
+ // than the failure did.
+ failureCount: integer("failure_count").notNull().default(0),
+ nextAttemptAt: integer("next_attempt_at"),
payloadKind: text("payload_kind")
.$type()
.notNull()
diff --git a/packages/db/test/data/queued-thread-messages.test.ts b/packages/db/test/data/queued-thread-messages.test.ts
index c50ff78b906..dd294c42c6d 100644
--- a/packages/db/test/data/queued-thread-messages.test.ts
+++ b/packages/db/test/data/queued-thread-messages.test.ts
@@ -868,6 +868,7 @@ describe("queued thread messages", () => {
claimQueuedThreadMessageGroup(db, noopNotifier, ordinary.id, {
kind: "automatic",
isGroupEligible: () => true,
+ retryingFailure: false,
}),
).toBeNull();
expect(
@@ -931,6 +932,7 @@ describe("queued thread messages", () => {
claimQueuedThreadMessageGroup(db, noopNotifier, heldBack.id, {
kind: "automatic",
isGroupEligible: () => true,
+ retryingFailure: false,
}),
).toBeNull();
expect(
diff --git a/packages/db/test/migrate.test.ts b/packages/db/test/migrate.test.ts
index 55ad087a00b..32bab3f4480 100644
--- a/packages/db/test/migrate.test.ts
+++ b/packages/db/test/migrate.test.ts
@@ -780,7 +780,20 @@ function rewindEnvironmentProvisioningMigration(db: DbConnection): void {
);
}
+function dropQueuedMessageAttemptColumns(db: DbConnection): void {
+ const columns = db.$client
+ .prepare<[], TableInfoRow>("PRAGMA table_info(queued_thread_messages)")
+ .all();
+ for (const name of ["failure_count", "next_attempt_at"]) {
+ if (!columns.some((column) => column.name === name)) continue;
+ db.$client
+ .prepare(`ALTER TABLE queued_thread_messages DROP COLUMN ${name}`)
+ .run();
+ }
+}
+
function dropQueueReworkSchema(db: DbConnection): void {
+ dropQueuedMessageAttemptColumns(db);
rewindEnvironmentProvisioningMigration(db);
// Indexes first: SQLite refuses to drop a column an existing index names.
for (const index of [
@@ -5708,6 +5721,7 @@ describe("environment providers migration", () => {
db.$client.prepare("DROP TABLE retained_event_outputs").run();
rewindEnvironmentRowFactsMigration(db);
rewindEnvironmentProvidersMigration(db);
+ dropQueuedMessageAttemptColumns(db);
db.$client
.prepare<[number]>(
"DELETE FROM __drizzle_migrations WHERE created_at >= ?",
@@ -6121,6 +6135,7 @@ describe("environment and thread startup ownership migration", () => {
try {
rewindMachineProvidersMigration(db);
rewindEnvironmentProvisioningMigration(db);
+ dropQueuedMessageAttemptColumns(db);
const legacySchema = readFileSync(
resolve(
dirname(fileURLToPath(import.meta.url)),
diff --git a/packages/templates/src/templates/bb-guide-threads.md b/packages/templates/src/templates/bb-guide-threads.md
index e4853f55c1e..027dbd8da5e 100644
--- a/packages/templates/src/templates/bb-guide-threads.md
+++ b/packages/templates/src/templates/bb-guide-threads.md
@@ -367,6 +367,11 @@ Queued messages:
queued row in the workspace; `--wait-holder plugin:` narrows it to
the rows one plugin is holding.
+ Failed rows show their failure reason instead of their previous wait, followed
+ by a recovery command: `bb thread queue send `.
+ Use it to retry immediately, including after automatic retries are exhausted.
+ Editing the message does not clear its failure or trigger a retry.
+
`queue send` dispatches a row now, bypassing every plugin wait and its own
schedule — the invariants (a running turn, an unfinished workspace, an
unanswered interaction) still apply, and a message that hits one simply queues
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..f96de70f6af 100644
--- a/plugins/bb-guide/skills/bb-cli/references/thread-operation.md
+++ b/plugins/bb-guide/skills/bb-cli/references/thread-operation.md
@@ -58,6 +58,10 @@
and `Send at` columns. Several queued rows on one thread are normal. The SDK
equivalents are `threads.queue.list` (cross-thread) and
`threads.queuedMessages.list/send/update/delete` (one thread).
+- Failed queue rows show the failure reason and an exact recovery command.
+ Use `bb thread queue send ` to retry immediately,
+ including after automatic retries are exhausted. Editing does not clear a
+ failure or trigger a retry; send still respects core readiness requirements.
- `bb thread queue send --mode steer` re-attempts the
row as a steer with the same send-now behavior: it bypasses the row's schedule
and plugin waits, while core waits still apply. During provisioning it reports