@ixo/oracle-runtime-workers 0.12.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/README.md +32 -10
  2. package/package.json +14 -9
  3. package/src/artifacts/config.ts +158 -0
  4. package/src/artifacts/crypto.ts +94 -0
  5. package/src/artifacts/routes.ts +73 -0
  6. package/src/artifacts/store.ts +238 -0
  7. package/src/artifacts/sweep.ts +200 -0
  8. package/src/artifacts/tool.ts +103 -0
  9. package/src/artifacts/viewer-page.ts +614 -0
  10. package/src/attachments/download.ts +168 -45
  11. package/src/attachments/index.ts +9 -0
  12. package/src/attachments/magic.ts +33 -0
  13. package/src/attachments/pipeline.ts +105 -49
  14. package/src/attachments/sandbox-archive.ts +9 -8
  15. package/src/attachments/view-cache.ts +142 -0
  16. package/src/attachments/view.ts +53 -12
  17. package/src/channels/auth.ts +267 -0
  18. package/src/channels/contract.ts +137 -0
  19. package/src/channels/turns.ts +270 -0
  20. package/src/core/budgeted-llm.ts +223 -0
  21. package/src/core/capability-router.ts +273 -0
  22. package/src/core/context-budget.ts +97 -10
  23. package/src/core/env.ts +111 -0
  24. package/src/core/execution-profile.ts +17 -0
  25. package/src/core/index.ts +267 -9
  26. package/src/core/llm.ts +30 -9
  27. package/src/core/main-agent-types.ts +41 -1
  28. package/src/core/main-agent.ts +367 -65
  29. package/src/core/manifest.ts +34 -0
  30. package/src/core/meta-tools.ts +128 -18
  31. package/src/core/middlewares/capability-gate.ts +130 -12
  32. package/src/core/middlewares/context-guard.ts +55 -24
  33. package/src/core/middlewares/index.ts +20 -2
  34. package/src/core/middlewares/page-context.ts +46 -2
  35. package/src/core/middlewares/return-direct-first.ts +47 -0
  36. package/src/core/middlewares/summarization.ts +152 -10
  37. package/src/core/middlewares/tool-execution.ts +323 -0
  38. package/src/core/middlewares/tool-repetition-guard.ts +202 -35
  39. package/src/core/middlewares/turn-boundary.ts +162 -0
  40. package/src/core/openrouter-pricing.ts +155 -22
  41. package/src/core/prompt-composer.ts +112 -7
  42. package/src/core/registries.ts +253 -48
  43. package/src/core/request-admission.ts +159 -0
  44. package/src/core/runtime-context.ts +107 -8
  45. package/src/core/state.ts +1 -1
  46. package/src/core/sub-agent-fallback.ts +21 -2
  47. package/src/core/subagent-as-tool.ts +78 -43
  48. package/src/core/tool-access.ts +189 -0
  49. package/src/core/tool-scheduler.ts +125 -0
  50. package/src/core/turn-budget.ts +205 -0
  51. package/src/core/turn-time-note.ts +43 -0
  52. package/src/core/user-kv.ts +446 -0
  53. package/src/core/wrap-plugin-tool.ts +20 -0
  54. package/src/delivery/plan.ts +278 -0
  55. package/src/delivery/profile.ts +161 -0
  56. package/src/delivery/prompt.ts +27 -0
  57. package/src/delivery/schema.ts +61 -0
  58. package/src/delivery/shaper.ts +389 -0
  59. package/src/delivery/types.ts +84 -0
  60. package/src/do/ambient.ts +51 -15
  61. package/src/do/contracts.ts +198 -6
  62. package/src/do/idle-eviction.ts +38 -0
  63. package/src/do/matrix-turn-ledger.ts +24 -5
  64. package/src/do/result-store-test-do.ts +137 -3
  65. package/src/do/result-store.ts +239 -58
  66. package/src/do/room-mirror.ts +29 -0
  67. package/src/do/run-buffer.ts +64 -9
  68. package/src/do/run-coordinator.ts +451 -76
  69. package/src/do/run-request.ts +2 -0
  70. package/src/do/run-store-test-do.ts +301 -1
  71. package/src/do/run-store.ts +385 -43
  72. package/src/do/secrets-adapter.ts +22 -3
  73. package/src/do/sse-stream.ts +60 -9
  74. package/src/do/transcript.ts +48 -5
  75. package/src/do/turn-body.ts +105 -5
  76. package/src/do/ucan-service.ts +204 -34
  77. package/src/do/user-oracle-do.ts +1883 -264
  78. package/src/feedback/config.ts +127 -0
  79. package/src/feedback/contract.ts +148 -0
  80. package/src/feedback/linear-sink.ts +285 -0
  81. package/src/feedback/privacy.ts +150 -0
  82. package/src/feedback/reservation.ts +298 -0
  83. package/src/feedback/submit.ts +247 -0
  84. package/src/index.ts +138 -6
  85. package/src/llm/byo-adapter.ts +27 -6
  86. package/src/llm/byo-catalog.ts +13 -0
  87. package/src/llm/byo-model-fallback.ts +248 -0
  88. package/src/llm/byo-service.ts +45 -8
  89. package/src/llm/provider-error.ts +139 -2
  90. package/src/matrix/gateway-do.ts +523 -111
  91. package/src/matrix/group-chat.ts +66 -23
  92. package/src/matrix/inbox-store.ts +16 -1
  93. package/src/matrix/ingest.ts +79 -14
  94. package/src/matrix/media-deadline.ts +130 -0
  95. package/src/matrix/replay-format.ts +5 -3
  96. package/src/matrix/reply-parts.ts +92 -0
  97. package/src/matrix/room-alias.ts +96 -0
  98. package/src/matrix/user-homeserver.ts +166 -7
  99. package/src/owner-store/ixo-vfs-store.ts +170 -43
  100. package/src/owner-store/migrating-store.ts +31 -15
  101. package/src/owner-store/types.ts +18 -3
  102. package/src/plugin-api/index.ts +43 -2
  103. package/src/plugin-api/oracle-plugin.ts +35 -0
  104. package/src/plugin-api/request-admission.ts +41 -0
  105. package/src/plugin-api/tool-helper.ts +25 -1
  106. package/src/plugin-api/tool-plane.ts +92 -0
  107. package/src/plugin-api/types.ts +194 -6
  108. package/src/plugins/agui/agui.plugin.ts +31 -16
  109. package/src/plugins/composio/composio-tools.ts +57 -6
  110. package/src/plugins/composio/composio-ucan.ts +3 -2
  111. package/src/plugins/composio/composio.plugin.ts +2 -0
  112. package/src/plugins/delegated-capabilities.ts +25 -0
  113. package/src/plugins/editor/content-session.ts +173 -83
  114. package/src/plugins/editor/content-tools.ts +76 -17
  115. package/src/plugins/editor/editor-mx.ts +28 -9
  116. package/src/plugins/editor/editor.plugin.ts +3 -1
  117. package/src/plugins/editor/failures.ts +28 -7
  118. package/src/plugins/editor/prompts.ts +2 -1
  119. package/src/plugins/editor/provider.ts +553 -40
  120. package/src/plugins/editor/standalone-editor-tool.ts +61 -18
  121. package/src/plugins/firecrawl/firecrawl-tools.ts +13 -5
  122. package/src/plugins/flows/action-metadata.ts +111 -11
  123. package/src/plugins/flows/actions.ts +18 -0
  124. package/src/plugins/flows/decision-context.ts +31 -0
  125. package/src/plugins/flows/edit.ts +223 -52
  126. package/src/plugins/flows/errors.ts +22 -1
  127. package/src/plugins/flows/flow-doc.ts +92 -9
  128. package/src/plugins/flows/flows.plugin.ts +11 -5
  129. package/src/plugins/flows/forms.ts +34 -0
  130. package/src/plugins/flows/index.ts +2 -0
  131. package/src/plugins/flows/input-policy.ts +140 -0
  132. package/src/plugins/flows/prompts.ts +14 -11
  133. package/src/plugins/flows/read.ts +49 -9
  134. package/src/plugins/flows/ref-syntax.ts +129 -0
  135. package/src/plugins/flows/test-homeserver.ts +245 -0
  136. package/src/plugins/flows/tools/authoring.ts +216 -112
  137. package/src/plugins/flows/tools/discovery.ts +4 -1
  138. package/src/plugins/flows/tools/forms.ts +21 -2
  139. package/src/plugins/flows/tools/settings.ts +34 -13
  140. package/src/plugins/flows/translator.ts +13 -10
  141. package/src/plugins/flows/types.ts +167 -42
  142. package/src/plugins/index.ts +11 -1
  143. package/src/plugins/ixo-transaction/chain-ids.ts +43 -0
  144. package/src/plugins/ixo-transaction/index.ts +2 -0
  145. package/src/plugins/ixo-transaction/ixo-transaction.plugin.ts +119 -0
  146. package/src/plugins/ixo-transaction/receipts.ts +99 -0
  147. package/src/plugins/ixo-transaction/tools.ts +329 -0
  148. package/src/plugins/ixo-transaction/wallet-signing.ts +202 -0
  149. package/src/plugins/mcp-tool-adapter.ts +23 -2
  150. package/src/plugins/memory/memory-tools.ts +7 -2
  151. package/src/plugins/memory/memory-ucan.ts +3 -2
  152. package/src/plugins/memory/memory.plugin.ts +2 -0
  153. package/src/plugins/pod-creator/blueprint-store.ts +163 -0
  154. package/src/plugins/pod-creator/blueprint-types.ts +61 -0
  155. package/src/plugins/pod-creator/bounded-map.ts +87 -0
  156. package/src/plugins/pod-creator/capsule-content-client.ts +285 -0
  157. package/src/plugins/pod-creator/chain-gateway.ts +110 -0
  158. package/src/plugins/pod-creator/config.ts +85 -0
  159. package/src/plugins/pod-creator/create-session-store.ts +180 -0
  160. package/src/plugins/pod-creator/create-tools.ts +538 -0
  161. package/src/plugins/pod-creator/design-pod-roles.ts +111 -0
  162. package/src/plugins/pod-creator/index.ts +65 -0
  163. package/src/plugins/pod-creator/orchestration-tools.ts +172 -0
  164. package/src/plugins/pod-creator/pod-creator.plugin.ts +190 -0
  165. package/src/plugins/pod-creator/stage.ts +201 -0
  166. package/src/plugins/pod-creator/sub-agents.ts +189 -0
  167. package/src/plugins/portal/action-log.ts +21 -1
  168. package/src/plugins/portal/declared-tools.ts +90 -0
  169. package/src/plugins/portal/portal.plugin.ts +42 -16
  170. package/src/plugins/sandbox/sandbox-bridge.ts +79 -10
  171. package/src/plugins/sandbox/sandbox-mcp.ts +3 -2
  172. package/src/plugins/sandbox/sandbox-write-blob.ts +11 -1
  173. package/src/plugins/sandbox/sandbox.plugin.ts +8 -16
  174. package/src/plugins/tasks/manifest.ts +1 -1
  175. package/src/plugins/tasks/middleware.ts +20 -7
  176. package/src/plugins/tasks/tasks-tools.ts +30 -3
  177. package/src/plugins/tasks/tasks.plugin.ts +64 -11
  178. package/src/plugins/user-preferences/schema.ts +12 -1
  179. package/src/plugins/user-preferences/user-preferences-store.ts +38 -13
  180. package/src/plugins/user-preferences/user-preferences-tool.ts +3 -2
  181. package/src/plugins/vfs/vfs-client.ts +250 -71
  182. package/src/plugins/vfs/vfs-content.ts +30 -11
  183. package/src/plugins/vfs/vfs-errors.ts +22 -0
  184. package/src/plugins/vfs/vfs-sandbox-tools.ts +12 -5
  185. package/src/plugins/vfs/vfs-tools.ts +60 -20
  186. package/src/realtime/frontend-call-registry.ts +228 -81
  187. package/src/realtime/realtime-endpoint.ts +215 -44
  188. package/src/realtime/session-socket-hub.ts +47 -2
  189. package/src/secrets/secrets-service.ts +9 -3
  190. package/src/shell/app.ts +529 -82
  191. package/src/shell/auth.ts +131 -37
  192. package/src/shell/turn-body-cap.ts +11 -13
  193. package/src/sqlite/blob-compactor.ts +61 -28
  194. package/src/sqlite/database.ts +156 -90
  195. package/src/sqlite/do-vfs.ts +416 -129
  196. package/src/sqlite/page-tier.ts +133 -40
  197. package/src/sqlite/serialization.ts +40 -0
  198. package/src/sqlite/sessions-store.ts +10 -2
  199. package/src/sqlite/sqlite-saver.ts +139 -19
  200. package/src/sqlite/user-kv-store.ts +317 -0
  201. package/src/tasks/schedule.ts +201 -12
  202. package/src/tasks/scheduler.ts +586 -98
  203. package/src/tasks/spec.ts +67 -3
  204. package/src/tasks/store.ts +292 -27
  205. package/src/tasks/topic-deliverables.ts +70 -0
@@ -9,11 +9,13 @@
9
9
  import type { RunSummary, TurnRequest } from './contracts';
10
10
  import type { RunRecord } from './run-store';
11
11
  import type { TurnBody } from './turn-body';
12
+ import type { RequestDisposition } from '../plugin-api/request-admission';
12
13
 
13
14
  /** The client-declared surface of a turn, as the body carries it. */
14
15
  export type ClientSurfaceBody = Pick<TurnBody, 'tools' | 'agActions'>;
15
16
 
16
17
  export interface StoredRunRequest extends ClientSurfaceBody {
18
+ disposition?: RequestDisposition;
17
19
  turn: Omit<TurnRequest, 'attachments'> & {
18
20
  attachments?: TurnRequest['attachments'];
19
21
  };
@@ -5,8 +5,19 @@
5
5
  */
6
6
  import { DurableObject } from 'cloudflare:workers';
7
7
  import { DoSqliteDatabase } from '../sqlite/database';
8
+ import type { RunSummary } from './contracts';
8
9
  import type { PackedSegment } from './run-buffer';
9
- import { RunStore, type RunRecord, type ToolMark } from './run-store';
10
+ import { RunCoordinator } from './run-coordinator';
11
+ import { runSummaryOf } from './run-request';
12
+ import {
13
+ RUN_RETENTION_MS,
14
+ RunStore,
15
+ STALE_RUN_FILTER,
16
+ runDurabilityConfig,
17
+ type RunRecord,
18
+ type ToolMark,
19
+ type WriteClaimRecord,
20
+ } from './run-store';
10
21
 
11
22
  export class RunStoreTestDO extends DurableObject {
12
23
  private db: DoSqliteDatabase | undefined;
@@ -56,6 +67,96 @@ export class RunStoreTestDO extends DurableObject {
56
67
  return (await this.runStore()).get(runId);
57
68
  }
58
69
 
70
+ async wasChannelRunPruned(runId: string): Promise<boolean> {
71
+ return (await this.runStore()).wasChannelRunPruned(runId);
72
+ }
73
+
74
+ /**
75
+ * Begin a run through a `RunCoordinator` over this SQLite store, let its
76
+ * one attempt finish with `reply`, and report what the coordinator handed
77
+ * `onRunEnded` and what the row holds afterwards.
78
+ */
79
+ async coordinateRun(input: {
80
+ runId: string;
81
+ client: RunRecord['client'];
82
+ reply: string;
83
+ }): Promise<{
84
+ endedClient: RunRecord['client'];
85
+ /** The record `onRunEnded` was handed (what the object delivers from). */
86
+ ended: RunRecord;
87
+ stored: RunRecord | undefined;
88
+ summary: RunSummary | undefined;
89
+ /** Store work from begin to the run's end: transactions and row reads. */
90
+ work: { transactions: number; reads: number };
91
+ }> {
92
+ const store = await this.runStore();
93
+ await store.setup();
94
+ const db = this.db!;
95
+ const work = { transactions: 0, reads: 0 };
96
+ const transaction = db.transaction.bind(db);
97
+ db.transaction = <T>(fn: () => Promise<T>): Promise<T> => {
98
+ work.transactions += 1;
99
+ return transaction(fn);
100
+ };
101
+ const get = store.get.bind(store);
102
+ store.get = (runId: string) => {
103
+ work.reads += 1;
104
+ return get(runId);
105
+ };
106
+ const readSegments = store.readSegments.bind(store);
107
+ store.readSegments = (runId: string, after?: number) => {
108
+ work.reads += 1;
109
+ return readSegments(runId, after);
110
+ };
111
+ let ended!: (record: RunRecord) => void;
112
+ const endedRecord = new Promise<RunRecord>((resolve) => {
113
+ ended = resolve;
114
+ });
115
+ const runs = new RunCoordinator({
116
+ store,
117
+ config: runDurabilityConfig({}),
118
+ instanceId: 'coordinator-test',
119
+ log: {
120
+ log: () => undefined,
121
+ warn: () => undefined,
122
+ error: () => undefined,
123
+ },
124
+ now: () => this.nowMs,
125
+ requestAlarm: () => undefined,
126
+ runAttempt: async () => ({
127
+ status: 'finished',
128
+ text: input.reply,
129
+ messageId: `msg-${input.runId}`,
130
+ }),
131
+ checkpointIdOf: async () => null,
132
+ onRunEnded: async (record) => ended(record),
133
+ });
134
+ await runs.begin({
135
+ runId: input.runId,
136
+ sessionId: `$session-${input.runId}`,
137
+ requestId: `req-${input.runId}`,
138
+ client: input.client,
139
+ request: '{}',
140
+ multitask: 'enqueue',
141
+ });
142
+ const record = await endedRecord;
143
+ db.transaction = transaction;
144
+ store.get = get;
145
+ store.readSegments = readSegments;
146
+ const stored = await store.get(input.runId);
147
+ return {
148
+ endedClient: record.client,
149
+ ended: record,
150
+ stored,
151
+ summary: stored ? runSummaryOf(stored) : undefined,
152
+ work,
153
+ };
154
+ }
155
+
156
+ async listRecent(): Promise<RunRecord[]> {
157
+ return (await this.runStore()).listRecent();
158
+ }
159
+
59
160
  async update(
60
161
  runId: string,
61
162
  patch: Parameters<RunStore['update']>[1],
@@ -113,6 +214,20 @@ export class RunStoreTestDO extends DurableObject {
113
214
  return (await this.runStore()).listMarks(runId);
114
215
  }
115
216
 
217
+ async claimWrite(
218
+ input: Parameters<RunStore['claimWrite']>[0],
219
+ ): Promise<Awaited<ReturnType<RunStore['claimWrite']>>> {
220
+ return (await this.runStore()).claimWrite(input);
221
+ }
222
+
223
+ async releaseWrite(fingerprint: string, runId: string): Promise<void> {
224
+ await (await this.runStore()).releaseWrite(fingerprint, runId);
225
+ }
226
+
227
+ async listClaims(): Promise<WriteClaimRecord[]> {
228
+ return (await this.runStore()).listClaims();
229
+ }
230
+
116
231
  /** Close the database so the next call re-opens it (a fresh `RunStore`, setup again). */
117
232
  async reopen(): Promise<void> {
118
233
  await this.db?.close();
@@ -120,6 +235,144 @@ export class RunStoreTestDO extends DurableObject {
120
235
  this.store = undefined;
121
236
  }
122
237
 
238
+ /**
239
+ * Seed two databases with the same mix of runs, prune one with the
240
+ * per-run pruning the store used to do and the other by opening a
241
+ * `RunStore` on it, and hand back both databases' contents and how many
242
+ * transactions the store's boot opened.
243
+ */
244
+ async pruneEquivalence(): Promise<{
245
+ legacy: PrunedTables;
246
+ current: PrunedTables;
247
+ transactions: number;
248
+ receiptPlan: string[];
249
+ }> {
250
+ const t0 = this.nowMs;
251
+ const later = t0 + RUN_RETENTION_MS + 60_000;
252
+ const seed = async (name: string): Promise<DoSqliteDatabase> => {
253
+ const db = await DoSqliteDatabase.open(this.ctx, name);
254
+ await new RunStore(db, () => t0).setup();
255
+ await db.run(`CREATE TABLE IF NOT EXISTS channel_requests (
256
+ binding_id TEXT NOT NULL, request_id TEXT NOT NULL, request_hash TEXT NOT NULL,
257
+ run_id TEXT NOT NULL UNIQUE, session_id TEXT,
258
+ reply_mirrored INTEGER NOT NULL DEFAULT 0,
259
+ PRIMARY KEY (binding_id, request_id)
260
+ )`);
261
+ const old = new Date(t0).toISOString();
262
+ const fresh = new Date(later - 1000).toISOString();
263
+ const statuses = ['finished', 'aborted', 'interrupted', 'failed'];
264
+ const runs: Array<[string, string, string, string]> = [];
265
+ for (let i = 0; i < 30; i += 1)
266
+ runs.push([`ch-stale-${i}`, 'channel', statuses[i % 4]!, old]);
267
+ for (let i = 0; i < 5; i += 1)
268
+ runs.push([`portal-stale-${i}`, 'portal', statuses[i % 4]!, old]);
269
+ for (let i = 0; i < 5; i += 1)
270
+ runs.push([`ch-fresh-${i}`, 'channel', 'finished', fresh]);
271
+ for (const status of ['running', 'queued', 'recovering'])
272
+ runs.push([`ch-active-${status}`, 'channel', status, old]);
273
+ for (const [runId, client, status, at] of runs) {
274
+ await db.run(
275
+ `INSERT INTO turn_runs (run_id, session_id, request_id, client, status, started_at, updated_at, request, instance_id)
276
+ VALUES (?, 's', ?, ?, ?, ?, ?, '{}', 'i')`,
277
+ [runId, `req-${runId}`, client, status, at, at],
278
+ );
279
+ await db.run(
280
+ `INSERT INTO turn_tool_marks (run_id, tool_call_id, tool_name, effect, started_at) VALUES (?, 'c1', 't', 'read', ?)`,
281
+ [runId, at],
282
+ );
283
+ await db.run(
284
+ `INSERT INTO turn_run_plans (run_id, plan) VALUES (?, '{}')`,
285
+ [runId],
286
+ );
287
+ await db.run(
288
+ `INSERT INTO turn_run_segments (run_id, seq_from, seq_to, payload) VALUES (?, 1, 1, '[]')`,
289
+ [runId],
290
+ );
291
+ if (client === 'channel')
292
+ await db.run(
293
+ `INSERT INTO channel_requests (binding_id, request_id, request_hash, run_id) VALUES ('b', ?, 'h', ?)`,
294
+ [`req-${runId}`, runId],
295
+ );
296
+ }
297
+ // Left by an earlier boot: tombstoned runs whose receipts survived,
298
+ // and a receipt whose run was never begun.
299
+ for (const runId of ['ch-tombstoned-0', 'ch-tombstoned-1']) {
300
+ await db.run(
301
+ `INSERT INTO channel_run_tombstones (run_id, status) VALUES (?, 'finished')`,
302
+ [runId],
303
+ );
304
+ await db.run(
305
+ `INSERT INTO channel_requests (binding_id, request_id, request_hash, run_id) VALUES ('b', ?, 'h', ?)`,
306
+ [`req-${runId}`, runId],
307
+ );
308
+ }
309
+ await db.run(
310
+ `INSERT INTO channel_requests (binding_id, request_id, request_hash, run_id) VALUES ('b', 'req-never', 'h', 'ch-never-begun')`,
311
+ );
312
+ await db.run(
313
+ `INSERT INTO turn_write_claims (fingerprint, tool_name, run_id, session_id, started_at) VALUES ('old', 't', 'x', 's', ?), ('new', 't', 'y', 's', ?)`,
314
+ [old, fresh],
315
+ );
316
+ return db;
317
+ };
318
+ const dump = async (db: DoSqliteDatabase): Promise<PrunedTables> => {
319
+ const rows = (table: keyof PrunedTables) =>
320
+ db.exec<PrunedRow>(`SELECT * FROM ${table} ORDER BY 1, 2`);
321
+ return {
322
+ turn_runs: await rows('turn_runs'),
323
+ turn_tool_marks: await rows('turn_tool_marks'),
324
+ turn_run_plans: await rows('turn_run_plans'),
325
+ turn_run_segments: await rows('turn_run_segments'),
326
+ turn_write_claims: await rows('turn_write_claims'),
327
+ channel_run_tombstones: await rows('channel_run_tombstones'),
328
+ channel_requests: await rows('channel_requests'),
329
+ };
330
+ };
331
+
332
+ const legacyDb = await seed('prune-legacy.db');
333
+ await legacyPrune(legacyDb, later);
334
+ const legacy = await dump(legacyDb);
335
+
336
+ const currentDb = await seed('prune-current.db');
337
+ let transactions = 0;
338
+ const transaction = currentDb.transaction.bind(currentDb);
339
+ currentDb.transaction = <T>(fn: () => Promise<T>): Promise<T> => {
340
+ transactions += 1;
341
+ return transaction(fn);
342
+ };
343
+ await new RunStore(currentDb, () => later).setup();
344
+ const current = await dump(currentDb);
345
+ const receiptPlan = (
346
+ await currentDb.exec<{ detail: string }>(
347
+ `EXPLAIN QUERY PLAN DELETE FROM channel_requests WHERE EXISTS (
348
+ SELECT 1 FROM channel_run_tombstones t WHERE t.run_id = channel_requests.run_id
349
+ )`,
350
+ )
351
+ ).map((row) => row.detail);
352
+ await legacyDb.close();
353
+ await currentDb.close();
354
+ return { legacy, current, transactions, receiptPlan };
355
+ }
356
+
357
+ /** How SQLite runs the statements boot pruning issues on every boot. */
358
+ async prunePlans(): Promise<{ staleRuns: string[]; oldClaims: string[] }> {
359
+ await (await this.runStore()).setup();
360
+ const plan = async (sql: string) =>
361
+ (
362
+ await this.db!.exec<{ detail: string }>(`EXPLAIN QUERY PLAN ${sql}`, [
363
+ '2026-01-01T00:00:00.000Z',
364
+ ])
365
+ ).map((row) => row.detail);
366
+ return {
367
+ staleRuns: await plan(
368
+ `SELECT run_id FROM turn_runs WHERE ${STALE_RUN_FILTER} LIMIT 1`,
369
+ ),
370
+ oldClaims: await plan(
371
+ `DELETE FROM turn_write_claims WHERE started_at < ?`,
372
+ ),
373
+ };
374
+ }
375
+
123
376
  async rowCount(table: string): Promise<number> {
124
377
  await this.runStore();
125
378
  const row = await this.db!.get<{ n: number }>(
@@ -128,3 +381,50 @@ export class RunStoreTestDO extends DurableObject {
128
381
  return Number(row?.n ?? 0);
129
382
  }
130
383
  }
384
+
385
+ /** A row of the run tables (text and integer columns only). */
386
+ type PrunedRow = Record<string, string | number | null>;
387
+
388
+ /** Every table boot pruning touches, row by row. */
389
+ interface PrunedTables {
390
+ turn_runs: PrunedRow[];
391
+ turn_tool_marks: PrunedRow[];
392
+ turn_run_plans: PrunedRow[];
393
+ turn_run_segments: PrunedRow[];
394
+ turn_write_claims: PrunedRow[];
395
+ channel_run_tombstones: PrunedRow[];
396
+ channel_requests: PrunedRow[];
397
+ }
398
+
399
+ /** The store's boot pruning as it was: one transaction per stale run. */
400
+ async function legacyPrune(db: DoSqliteDatabase, now: number): Promise<void> {
401
+ const cutoff = new Date(now - RUN_RETENTION_MS).toISOString();
402
+ await db.run(`DELETE FROM turn_write_claims WHERE started_at < ?`, [cutoff]);
403
+ const stale = await db.exec<{
404
+ run_id: string;
405
+ client: string;
406
+ status: string;
407
+ }>(
408
+ `SELECT run_id, client, status FROM turn_runs WHERE status IN ('finished','aborted','interrupted','failed') AND updated_at < ?`,
409
+ [cutoff],
410
+ );
411
+ for (const row of stale) {
412
+ await db.transaction(async () => {
413
+ if (row.client === 'channel')
414
+ await db.run(
415
+ 'INSERT OR IGNORE INTO channel_run_tombstones (run_id, status) VALUES (?, ?)',
416
+ [row.run_id, row.status],
417
+ );
418
+ for (const table of [
419
+ 'turn_tool_marks',
420
+ 'turn_run_plans',
421
+ 'turn_run_segments',
422
+ 'turn_runs',
423
+ ])
424
+ await db.run(`DELETE FROM ${table} WHERE run_id = ?`, [row.run_id]);
425
+ });
426
+ }
427
+ await db.run(
428
+ `DELETE FROM channel_requests WHERE run_id IN (SELECT run_id FROM channel_run_tombstones)`,
429
+ );
430
+ }