@sixb/sqlite 0.0.1

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 (152) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -0
  3. package/dist/action-run-storage.d.ts +29 -0
  4. package/dist/action-run-storage.d.ts.map +1 -0
  5. package/dist/agents/agent-storage.d.ts +25 -0
  6. package/dist/agents/agent-storage.d.ts.map +1 -0
  7. package/dist/agents/index.d.ts +6 -0
  8. package/dist/agents/index.d.ts.map +1 -0
  9. package/dist/agents/messages.d.ts +14 -0
  10. package/dist/agents/messages.d.ts.map +1 -0
  11. package/dist/agents/rows.d.ts +77 -0
  12. package/dist/agents/rows.d.ts.map +1 -0
  13. package/dist/agents/runs.d.ts +26 -0
  14. package/dist/agents/runs.d.ts.map +1 -0
  15. package/dist/agents/threads.d.ts +14 -0
  16. package/dist/agents/threads.d.ts.map +1 -0
  17. package/dist/auth-storage/access-tokens.d.ts +33 -0
  18. package/dist/auth-storage/access-tokens.d.ts.map +1 -0
  19. package/dist/auth-storage/auth-storage.d.ts +46 -0
  20. package/dist/auth-storage/auth-storage.d.ts.map +1 -0
  21. package/dist/auth-storage/group-memberships.d.ts +21 -0
  22. package/dist/auth-storage/group-memberships.d.ts.map +1 -0
  23. package/dist/auth-storage/identities.d.ts +17 -0
  24. package/dist/auth-storage/identities.d.ts.map +1 -0
  25. package/dist/auth-storage/index.d.ts +3 -0
  26. package/dist/auth-storage/index.d.ts.map +1 -0
  27. package/dist/auth-storage/invitations.d.ts +28 -0
  28. package/dist/auth-storage/invitations.d.ts.map +1 -0
  29. package/dist/auth-storage/magic-links.d.ts +28 -0
  30. package/dist/auth-storage/magic-links.d.ts.map +1 -0
  31. package/dist/auth-storage/oidc-attempts.d.ts +18 -0
  32. package/dist/auth-storage/oidc-attempts.d.ts.map +1 -0
  33. package/dist/auth-storage/rows.d.ts +134 -0
  34. package/dist/auth-storage/rows.d.ts.map +1 -0
  35. package/dist/auth-storage/service-account-group-memberships.d.ts +17 -0
  36. package/dist/auth-storage/service-account-group-memberships.d.ts.map +1 -0
  37. package/dist/auth-storage/service-accounts.d.ts +14 -0
  38. package/dist/auth-storage/service-accounts.d.ts.map +1 -0
  39. package/dist/auth-storage/sessions.d.ts +55 -0
  40. package/dist/auth-storage/sessions.d.ts.map +1 -0
  41. package/dist/auth-storage/shared.d.ts +131 -0
  42. package/dist/auth-storage/shared.d.ts.map +1 -0
  43. package/dist/auth-storage/users.d.ts +19 -0
  44. package/dist/auth-storage/users.d.ts.map +1 -0
  45. package/dist/index.d.ts +71 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +10204 -0
  48. package/dist/index.js.map +54 -0
  49. package/dist/latest-run-query.d.ts +22 -0
  50. package/dist/latest-run-query.d.ts.map +1 -0
  51. package/dist/migrations.d.ts +18 -0
  52. package/dist/migrations.d.ts.map +1 -0
  53. package/dist/object-query-compiler.d.ts +44 -0
  54. package/dist/object-query-compiler.d.ts.map +1 -0
  55. package/dist/object-storage.d.ts +93 -0
  56. package/dist/object-storage.d.ts.map +1 -0
  57. package/dist/ontology-storage/commits.d.ts +14 -0
  58. package/dist/ontology-storage/commits.d.ts.map +1 -0
  59. package/dist/ontology-storage/index.d.ts +22 -0
  60. package/dist/ontology-storage/index.d.ts.map +1 -0
  61. package/dist/ontology-storage/materialization-session.d.ts +49 -0
  62. package/dist/ontology-storage/materialization-session.d.ts.map +1 -0
  63. package/dist/ontology-storage/materialization-state.d.ts +63 -0
  64. package/dist/ontology-storage/materialization-state.d.ts.map +1 -0
  65. package/dist/ontology-storage/materialization-writer.d.ts +15 -0
  66. package/dist/ontology-storage/materialization-writer.d.ts.map +1 -0
  67. package/dist/ontology-storage/materializations.d.ts +32 -0
  68. package/dist/ontology-storage/materializations.d.ts.map +1 -0
  69. package/dist/ontology-storage/outbox.d.ts +14 -0
  70. package/dist/ontology-storage/outbox.d.ts.map +1 -0
  71. package/dist/ontology-storage/shared.d.ts +91 -0
  72. package/dist/ontology-storage/shared.d.ts.map +1 -0
  73. package/dist/ontology-storage/sources.d.ts +26 -0
  74. package/dist/ontology-storage/sources.d.ts.map +1 -0
  75. package/dist/pipeline-run-storage.d.ts +26 -0
  76. package/dist/pipeline-run-storage.d.ts.map +1 -0
  77. package/dist/projection-run-storage.d.ts +32 -0
  78. package/dist/projection-run-storage.d.ts.map +1 -0
  79. package/dist/rules-storage.d.ts +33 -0
  80. package/dist/rules-storage.d.ts.map +1 -0
  81. package/dist/run-list-query.d.ts +27 -0
  82. package/dist/run-list-query.d.ts.map +1 -0
  83. package/dist/storage-errors.d.ts +2 -0
  84. package/dist/storage-errors.d.ts.map +1 -0
  85. package/dist/sync-run-storage.d.ts +23 -0
  86. package/dist/sync-run-storage.d.ts.map +1 -0
  87. package/dist/timeseries-storage.d.ts +41 -0
  88. package/dist/timeseries-storage.d.ts.map +1 -0
  89. package/dist/transactions.d.ts +15 -0
  90. package/dist/transactions.d.ts.map +1 -0
  91. package/dist/tsconfig.json +5 -0
  92. package/dist/validation.d.ts +5 -0
  93. package/dist/validation.d.ts.map +1 -0
  94. package/dist/webhook-delivery-storage.d.ts +27 -0
  95. package/dist/webhook-delivery-storage.d.ts.map +1 -0
  96. package/dist/webhook-run-storage.d.ts +22 -0
  97. package/dist/webhook-run-storage.d.ts.map +1 -0
  98. package/dist/workflow-intervention-storage.d.ts +26 -0
  99. package/dist/workflow-intervention-storage.d.ts.map +1 -0
  100. package/dist/workflow-run-storage.d.ts +63 -0
  101. package/dist/workflow-run-storage.d.ts.map +1 -0
  102. package/package.json +58 -0
  103. package/src/action-run-storage.ts +761 -0
  104. package/src/agents/agent-storage.ts +46 -0
  105. package/src/agents/index.ts +5 -0
  106. package/src/agents/messages.ts +162 -0
  107. package/src/agents/rows.ts +205 -0
  108. package/src/agents/runs.ts +389 -0
  109. package/src/agents/threads.ts +133 -0
  110. package/src/auth-storage/access-tokens.ts +289 -0
  111. package/src/auth-storage/auth-storage.ts +784 -0
  112. package/src/auth-storage/group-memberships.ts +52 -0
  113. package/src/auth-storage/identities.ts +98 -0
  114. package/src/auth-storage/index.ts +2 -0
  115. package/src/auth-storage/invitations.ts +258 -0
  116. package/src/auth-storage/magic-links.ts +149 -0
  117. package/src/auth-storage/oidc-attempts.ts +109 -0
  118. package/src/auth-storage/rows.ts +338 -0
  119. package/src/auth-storage/service-account-group-memberships.ts +161 -0
  120. package/src/auth-storage/service-accounts.ts +168 -0
  121. package/src/auth-storage/sessions.ts +230 -0
  122. package/src/auth-storage/shared.ts +707 -0
  123. package/src/auth-storage/users.ts +226 -0
  124. package/src/index.ts +292 -0
  125. package/src/latest-run-query.ts +49 -0
  126. package/src/migrations/001-initial-schema.sql +1068 -0
  127. package/src/migrations.ts +245 -0
  128. package/src/object-query-compiler.ts +1198 -0
  129. package/src/object-storage.ts +605 -0
  130. package/src/ontology-storage/commits.ts +127 -0
  131. package/src/ontology-storage/index.ts +35 -0
  132. package/src/ontology-storage/materialization-session.ts +393 -0
  133. package/src/ontology-storage/materialization-state.ts +901 -0
  134. package/src/ontology-storage/materialization-writer.ts +394 -0
  135. package/src/ontology-storage/materializations.ts +838 -0
  136. package/src/ontology-storage/outbox.ts +237 -0
  137. package/src/ontology-storage/shared.ts +269 -0
  138. package/src/ontology-storage/sources.ts +653 -0
  139. package/src/pipeline-run-storage.ts +506 -0
  140. package/src/projection-run-storage.ts +592 -0
  141. package/src/rules-storage.ts +375 -0
  142. package/src/run-list-query.ts +98 -0
  143. package/src/sql.d.ts +4 -0
  144. package/src/storage-errors.ts +3 -0
  145. package/src/sync-run-storage.ts +299 -0
  146. package/src/timeseries-storage.ts +212 -0
  147. package/src/transactions.ts +96 -0
  148. package/src/validation.ts +30 -0
  149. package/src/webhook-delivery-storage.ts +272 -0
  150. package/src/webhook-run-storage.ts +240 -0
  151. package/src/workflow-intervention-storage.ts +398 -0
  152. package/src/workflow-run-storage.ts +1148 -0
@@ -0,0 +1,389 @@
1
+ import type { Database } from "bun:sqlite"
2
+ import {
3
+ type AgentRunRecord,
4
+ type AgentRunStore,
5
+ AgentStorageError,
6
+ type ConfirmAgentRunExecutionOwnershipInput,
7
+ type CreateAgentRunInput,
8
+ type FinishAgentRunInput,
9
+ type FinishQueuedAgentRunInput,
10
+ type ListAgentRunsInput,
11
+ type ListAgentRunsResult,
12
+ type ReclaimAgentRunInput,
13
+ type StartAgentRunInput,
14
+ } from "@sixb/core/storage"
15
+ import {
16
+ appendRunListFilters,
17
+ hasEmptyStatuses,
18
+ queryRunList,
19
+ type SqliteValue,
20
+ } from "../run-list-query"
21
+ import { isUniqueConstraintError } from "../storage-errors"
22
+ import { type AgentRunRow, rowToRunRecord } from "./rows"
23
+
24
+ const SQLITE_RUN_ID_BATCH_SIZE = 500
25
+
26
+ export class SqliteAgentRunStore implements AgentRunStore {
27
+ constructor(private readonly db: Database) {}
28
+
29
+ async create(input: CreateAgentRunInput): Promise<AgentRunRecord> {
30
+ const createdAt = input.createdAt ?? new Date()
31
+
32
+ return this.db.transaction(() => {
33
+ const thread = this.db
34
+ .query("SELECT active_run_id FROM agent_threads WHERE project_id = ? AND id = ?")
35
+ .get(input.projectId, input.threadId) as { active_run_id: string | null } | null
36
+
37
+ if (!thread) {
38
+ throw new AgentStorageError(
39
+ "thread_not_found",
40
+ `[SixbSqlite] Agent thread '${input.threadId}' not found for project '${input.projectId}'.`
41
+ )
42
+ }
43
+
44
+ if (thread.active_run_id !== null) {
45
+ throw new AgentStorageError(
46
+ "active_run_exists",
47
+ `[SixbSqlite] Agent thread '${input.threadId}' already has an active run '${thread.active_run_id}'.`
48
+ )
49
+ }
50
+
51
+ try {
52
+ this.db
53
+ .query(
54
+ `
55
+ INSERT INTO agent_runs (
56
+ project_id,
57
+ id,
58
+ thread_id,
59
+ agent_id,
60
+ trigger_message_id,
61
+ requested_by_principal_type,
62
+ requested_by_principal_id,
63
+ status,
64
+ attempt,
65
+ created_at
66
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, 'queued', 0, ?)
67
+ `
68
+ )
69
+ .run(
70
+ input.projectId,
71
+ input.id,
72
+ input.threadId,
73
+ input.agentId,
74
+ input.triggerMessageId,
75
+ input.requestedByPrincipal.type,
76
+ input.requestedByPrincipal.id,
77
+ createdAt.toISOString()
78
+ )
79
+ } catch (error) {
80
+ if (isUniqueConstraintError(error)) {
81
+ throw new AgentStorageError(
82
+ "duplicate_id",
83
+ `[SixbSqlite] Agent run '${input.id}' already exists for project '${input.projectId}'.`
84
+ )
85
+ }
86
+
87
+ throw error
88
+ }
89
+
90
+ this.db
91
+ .query(
92
+ "UPDATE agent_threads SET active_run_id = ?, updated_at = ? WHERE project_id = ? AND id = ?"
93
+ )
94
+ .run(input.id, createdAt.toISOString(), input.projectId, input.threadId)
95
+
96
+ return this.requireRun(input.projectId, input.id)
97
+ })()
98
+ }
99
+
100
+ async start(input: StartAgentRunInput): Promise<AgentRunRecord> {
101
+ const startedAt = input.startedAt ?? new Date()
102
+ return this.db.transaction(() => {
103
+ this.requireStatus(input.projectId, input.id, "queued")
104
+ this.db
105
+ .query(
106
+ `
107
+ UPDATE agent_runs
108
+ SET
109
+ status = 'running',
110
+ execution_principal_type = ?,
111
+ execution_principal_id = ?,
112
+ model_id = ?,
113
+ attempt = 1,
114
+ execution_token = ?,
115
+ execution_queue_lease_expires_at = ?,
116
+ started_at = ?
117
+ WHERE project_id = ? AND id = ?
118
+ `
119
+ )
120
+ .run(
121
+ input.executionPrincipal?.type ?? null,
122
+ input.executionPrincipal?.id ?? null,
123
+ input.modelId ?? null,
124
+ input.execution.token,
125
+ input.execution.queueLeaseExpiresAt.toISOString(),
126
+ startedAt.toISOString(),
127
+ input.projectId,
128
+ input.id
129
+ )
130
+
131
+ return this.requireRun(input.projectId, input.id)
132
+ })()
133
+ }
134
+
135
+ async finishQueued(input: FinishQueuedAgentRunInput): Promise<AgentRunRecord> {
136
+ const completedAt = input.completedAt ?? new Date()
137
+ return this.db.transaction(() => {
138
+ const run = this.requireStatus(input.projectId, input.id, "queued")
139
+ this.db
140
+ .query(
141
+ `
142
+ UPDATE agent_runs
143
+ SET status = ?, error = ?, completed_at = ?
144
+ WHERE project_id = ? AND id = ?
145
+ `
146
+ )
147
+ .run(
148
+ input.status,
149
+ input.error ?? null,
150
+ completedAt.toISOString(),
151
+ input.projectId,
152
+ input.id
153
+ )
154
+ this.releaseThread(run, completedAt)
155
+ return this.requireRun(input.projectId, input.id)
156
+ })()
157
+ }
158
+
159
+ async reclaim(input: ReclaimAgentRunInput): Promise<AgentRunRecord> {
160
+ return this.db.transaction(() => {
161
+ this.requireRunning(input.projectId, input.id)
162
+ this.db
163
+ .query(
164
+ `
165
+ UPDATE agent_runs
166
+ SET
167
+ attempt = attempt + 1,
168
+ execution_token = ?,
169
+ execution_queue_lease_expires_at = ?
170
+ WHERE project_id = ? AND id = ?
171
+ `
172
+ )
173
+ .run(
174
+ input.execution.token,
175
+ input.execution.queueLeaseExpiresAt.toISOString(),
176
+ input.projectId,
177
+ input.id
178
+ )
179
+
180
+ return this.requireRun(input.projectId, input.id)
181
+ })()
182
+ }
183
+
184
+ async confirmExecutionOwnership(
185
+ input: ConfirmAgentRunExecutionOwnershipInput
186
+ ): Promise<AgentRunRecord> {
187
+ return this.db.transaction(() => {
188
+ const run = this.requireRunning(input.projectId, input.id)
189
+ if (run.execution_token !== input.executionToken) {
190
+ throw new AgentStorageError(
191
+ "execution_lost",
192
+ `[SixbSqlite] Execution token is no longer current on agent run '${input.id}'.`
193
+ )
194
+ }
195
+
196
+ this.db
197
+ .query(
198
+ `
199
+ UPDATE agent_runs
200
+ SET execution_queue_lease_expires_at = MAX(execution_queue_lease_expires_at, ?)
201
+ WHERE project_id = ? AND id = ?
202
+ `
203
+ )
204
+ .run(input.queueLeaseExpiresAt.toISOString(), input.projectId, input.id)
205
+ return this.requireRun(input.projectId, input.id)
206
+ })()
207
+ }
208
+
209
+ async finish(input: FinishAgentRunInput): Promise<AgentRunRecord> {
210
+ const completedAt = input.completedAt ?? new Date()
211
+ const errorValue = input.status === "succeeded" ? null : (input.error ?? null)
212
+
213
+ return this.db.transaction(() => {
214
+ const run = this.requireRunning(input.projectId, input.id)
215
+
216
+ if (run.execution_token !== input.executionToken) {
217
+ throw new AgentStorageError(
218
+ "execution_lost",
219
+ `[SixbSqlite] Execution token is no longer current on agent run '${input.id}'.`
220
+ )
221
+ }
222
+
223
+ this.db
224
+ .query(
225
+ `
226
+ UPDATE agent_runs
227
+ SET
228
+ status = ?,
229
+ model_id = COALESCE(?, model_id),
230
+ finish_reason = ?,
231
+ usage_input_tokens = ?,
232
+ usage_output_tokens = ?,
233
+ usage_total_tokens = ?,
234
+ usage_reasoning_tokens = ?,
235
+ usage_cached_input_tokens = ?,
236
+ error = ?,
237
+ diagnostics = ?,
238
+ execution_token = NULL,
239
+ execution_queue_lease_expires_at = NULL,
240
+ completed_at = ?
241
+ WHERE project_id = ? AND id = ?
242
+ `
243
+ )
244
+ .run(
245
+ input.status,
246
+ input.modelId ?? null,
247
+ input.finishReason ?? null,
248
+ input.usage?.inputTokens ?? null,
249
+ input.usage?.outputTokens ?? null,
250
+ input.usage?.totalTokens ?? null,
251
+ input.usage?.reasoningTokens ?? null,
252
+ input.usage?.cachedInputTokens ?? null,
253
+ errorValue,
254
+ input.diagnostics === undefined ? null : JSON.stringify(input.diagnostics),
255
+ completedAt.toISOString(),
256
+ input.projectId,
257
+ input.id
258
+ )
259
+
260
+ this.releaseThread(run, completedAt)
261
+
262
+ return this.requireRun(input.projectId, input.id)
263
+ })()
264
+ }
265
+
266
+ async getById(params: { projectId: string; id: string }): Promise<AgentRunRecord | null> {
267
+ const row = this.db
268
+ .query("SELECT * FROM agent_runs WHERE project_id = ? AND id = ?")
269
+ .get(params.projectId, params.id) as AgentRunRow | null
270
+
271
+ return row ? rowToRunRecord(row) : null
272
+ }
273
+
274
+ async getByIds(params: {
275
+ projectId: string
276
+ ids: readonly string[]
277
+ }): Promise<readonly AgentRunRecord[]> {
278
+ const ids = [...new Set(params.ids)]
279
+ if (ids.length === 0) {
280
+ return []
281
+ }
282
+
283
+ const rows: AgentRunRow[] = []
284
+ for (let offset = 0; offset < ids.length; offset += SQLITE_RUN_ID_BATCH_SIZE) {
285
+ const batch = ids.slice(offset, offset + SQLITE_RUN_ID_BATCH_SIZE)
286
+ const placeholders = batch.map(() => "?").join(", ")
287
+ rows.push(
288
+ ...(this.db
289
+ .query(`SELECT * FROM agent_runs WHERE project_id = ? AND id IN (${placeholders})`)
290
+ .all(params.projectId, ...batch) as AgentRunRow[])
291
+ )
292
+ }
293
+ const byId = new Map(rows.map((row) => [row.id, rowToRunRecord(row)]))
294
+ return ids.flatMap((id) => {
295
+ const run = byId.get(id)
296
+ return run ? [run] : []
297
+ })
298
+ }
299
+
300
+ async list(input: ListAgentRunsInput): Promise<ListAgentRunsResult> {
301
+ if (hasEmptyStatuses(input)) {
302
+ return { runs: [], hasMore: false, total: 0 }
303
+ }
304
+
305
+ const whereClauses = ["project_id = ?"]
306
+ const args: SqliteValue[] = [input.projectId]
307
+
308
+ if (input.threadId) {
309
+ whereClauses.push("thread_id = ?")
310
+ args.push(input.threadId)
311
+ }
312
+
313
+ if (input.agentId) {
314
+ whereClauses.push("agent_id = ?")
315
+ args.push(input.agentId)
316
+ }
317
+
318
+ appendRunListFilters(whereClauses, args, input, "COALESCE(started_at, created_at)")
319
+
320
+ const { rows, total, hasMore } = queryRunList<AgentRunRow>({
321
+ db: this.db,
322
+ tableName: "agent_runs",
323
+ whereClauses,
324
+ args,
325
+ order: input.order,
326
+ limit: input.limit,
327
+ offset: input.offset,
328
+ })
329
+
330
+ return { runs: rows.map(rowToRunRecord), hasMore, total }
331
+ }
332
+
333
+ private requireRunning(projectId: string, id: string): AgentRunRow {
334
+ return this.requireStatus(projectId, id, "running")
335
+ }
336
+
337
+ private requireStatus(
338
+ projectId: string,
339
+ id: string,
340
+ status: AgentRunRecord["status"]
341
+ ): AgentRunRow {
342
+ const row = this.db
343
+ .query("SELECT * FROM agent_runs WHERE project_id = ? AND id = ?")
344
+ .get(projectId, id) as AgentRunRow | null
345
+
346
+ if (!row) {
347
+ throw new AgentStorageError(
348
+ "run_not_found",
349
+ `[SixbSqlite] Agent run '${id}' not found for project '${projectId}'.`
350
+ )
351
+ }
352
+
353
+ if (row.status !== status) {
354
+ throw new AgentStorageError(
355
+ "invalid_state",
356
+ `[SixbSqlite] Agent run '${id}' is not ${status} (status '${row.status}').`
357
+ )
358
+ }
359
+
360
+ return row
361
+ }
362
+
363
+ private releaseThread(run: AgentRunRow, completedAt: Date): void {
364
+ this.db
365
+ .query(
366
+ `
367
+ UPDATE agent_threads
368
+ SET active_run_id = NULL, updated_at = ?
369
+ WHERE project_id = ? AND id = ? AND active_run_id = ?
370
+ `
371
+ )
372
+ .run(completedAt.toISOString(), run.project_id, run.thread_id, run.id)
373
+ }
374
+
375
+ private requireRun(projectId: string, id: string): AgentRunRecord {
376
+ const row = this.db
377
+ .query("SELECT * FROM agent_runs WHERE project_id = ? AND id = ?")
378
+ .get(projectId, id) as AgentRunRow | null
379
+
380
+ if (!row) {
381
+ throw new AgentStorageError(
382
+ "run_not_found",
383
+ `[SixbSqlite] Agent run '${id}' not found for project '${projectId}'.`
384
+ )
385
+ }
386
+
387
+ return rowToRunRecord(row)
388
+ }
389
+ }
@@ -0,0 +1,133 @@
1
+ import type { Database } from "bun:sqlite"
2
+ import {
3
+ AgentStorageError,
4
+ type AgentThreadRecord,
5
+ type AgentThreadStore,
6
+ type CreateAgentThreadInput,
7
+ type ListAgentThreadsInput,
8
+ type ListAgentThreadsResult,
9
+ } from "@sixb/core/storage"
10
+ import type { SqliteValue } from "../run-list-query"
11
+ import { isUniqueConstraintError } from "../storage-errors"
12
+ import { type AgentThreadRow, queryAgentList, rowToThreadRecord } from "./rows"
13
+
14
+ export class SqliteAgentThreadStore implements AgentThreadStore {
15
+ constructor(private readonly db: Database) {}
16
+
17
+ async create(input: CreateAgentThreadInput): Promise<AgentThreadRecord> {
18
+ const createdAt = input.createdAt ?? new Date()
19
+ const updatedAt = input.updatedAt ?? createdAt
20
+
21
+ try {
22
+ this.db
23
+ .query(
24
+ `
25
+ INSERT INTO agent_threads (
26
+ project_id,
27
+ id,
28
+ agent_id,
29
+ owner_principal_type,
30
+ owner_principal_id,
31
+ title,
32
+ status,
33
+ active_run_id,
34
+ last_message_at,
35
+ message_count,
36
+ created_at,
37
+ updated_at
38
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, NULL, NULL, 0, ?, ?)
39
+ `
40
+ )
41
+ .run(
42
+ input.projectId,
43
+ input.id,
44
+ input.agentId,
45
+ input.ownerPrincipal.type,
46
+ input.ownerPrincipal.id,
47
+ input.title ?? null,
48
+ input.status ?? "active",
49
+ createdAt.toISOString(),
50
+ updatedAt.toISOString()
51
+ )
52
+ } catch (error) {
53
+ if (isUniqueConstraintError(error)) {
54
+ throw new AgentStorageError(
55
+ "duplicate_id",
56
+ `[SixbSqlite] Agent thread '${input.id}' already exists for project '${input.projectId}'.`
57
+ )
58
+ }
59
+
60
+ throw error
61
+ }
62
+
63
+ return this.requireThread(input.projectId, input.id)
64
+ }
65
+
66
+ async getById(params: { projectId: string; id: string }): Promise<AgentThreadRecord | null> {
67
+ const row = this.db
68
+ .query("SELECT * FROM agent_threads WHERE project_id = ? AND id = ?")
69
+ .get(params.projectId, params.id) as AgentThreadRow | null
70
+
71
+ return row ? rowToThreadRecord(row) : null
72
+ }
73
+
74
+ async list(input: ListAgentThreadsInput): Promise<ListAgentThreadsResult> {
75
+ if (
76
+ (input.statuses !== undefined && input.statuses.length === 0) ||
77
+ (input.agentIds !== undefined && input.agentIds.length === 0)
78
+ ) {
79
+ return { threads: [], hasMore: false, total: 0 }
80
+ }
81
+
82
+ const whereClauses = ["project_id = ?"]
83
+ const args: SqliteValue[] = [input.projectId]
84
+
85
+ if (input.agentId) {
86
+ whereClauses.push("agent_id = ?")
87
+ args.push(input.agentId)
88
+ }
89
+
90
+ if (input.agentIds) {
91
+ whereClauses.push(`agent_id IN (${input.agentIds.map(() => "?").join(", ")})`)
92
+ args.push(...input.agentIds)
93
+ }
94
+
95
+ if (input.statuses) {
96
+ whereClauses.push(`status IN (${input.statuses.map(() => "?").join(", ")})`)
97
+ args.push(...input.statuses)
98
+ }
99
+
100
+ if (input.ownerPrincipal) {
101
+ whereClauses.push("owner_principal_type = ? AND owner_principal_id = ?")
102
+ args.push(input.ownerPrincipal.type, input.ownerPrincipal.id)
103
+ }
104
+
105
+ const dir = input.order === "asc" ? "ASC" : "DESC"
106
+ const { rows, total, hasMore } = queryAgentList<AgentThreadRow>({
107
+ db: this.db,
108
+ table: "agent_threads",
109
+ whereClauses,
110
+ args,
111
+ orderBy: `COALESCE(last_message_at, created_at) ${dir}, id ${dir}`,
112
+ limit: input.limit,
113
+ offset: input.offset,
114
+ })
115
+
116
+ return { threads: rows.map(rowToThreadRecord), hasMore, total }
117
+ }
118
+
119
+ private requireThread(projectId: string, id: string): AgentThreadRecord {
120
+ const row = this.db
121
+ .query("SELECT * FROM agent_threads WHERE project_id = ? AND id = ?")
122
+ .get(projectId, id) as AgentThreadRow | null
123
+
124
+ if (!row) {
125
+ throw new AgentStorageError(
126
+ "invalid_state",
127
+ `[SixbSqlite] Failed to load agent thread '${id}' for project '${projectId}'.`
128
+ )
129
+ }
130
+
131
+ return rowToThreadRecord(row)
132
+ }
133
+ }