@sixb/sqlite 0.1.1 → 0.1.3

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 (123) hide show
  1. package/README.md +18 -13
  2. package/dist/action-run-storage.d.ts +5 -2
  3. package/dist/action-run-storage.d.ts.map +1 -1
  4. package/dist/agents/agent-storage.d.ts +9 -5
  5. package/dist/agents/agent-storage.d.ts.map +1 -1
  6. package/dist/agents/checkpoints.d.ts +14 -0
  7. package/dist/agents/checkpoints.d.ts.map +1 -0
  8. package/dist/agents/index.d.ts +1 -0
  9. package/dist/agents/index.d.ts.map +1 -1
  10. package/dist/agents/messages.d.ts +2 -1
  11. package/dist/agents/messages.d.ts.map +1 -1
  12. package/dist/agents/rows.d.ts +21 -11
  13. package/dist/agents/rows.d.ts.map +1 -1
  14. package/dist/agents/runs.d.ts +3 -2
  15. package/dist/agents/runs.d.ts.map +1 -1
  16. package/dist/ai-cost-storage.d.ts +15 -0
  17. package/dist/ai-cost-storage.d.ts.map +1 -0
  18. package/dist/ai-usage-storage.d.ts +22 -0
  19. package/dist/ai-usage-storage.d.ts.map +1 -0
  20. package/dist/auth-storage/service-accounts.d.ts.map +1 -1
  21. package/dist/auth-storage/shared.d.ts +1 -10
  22. package/dist/auth-storage/shared.d.ts.map +1 -1
  23. package/dist/connector-connection-storage.d.ts +9 -0
  24. package/dist/connector-connection-storage.d.ts.map +1 -0
  25. package/dist/execution-storage.d.ts +14 -0
  26. package/dist/execution-storage.d.ts.map +1 -0
  27. package/dist/index.d.ts +14 -9
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +4633 -1066
  30. package/dist/index.js.map +33 -28
  31. package/dist/latest-run-query.d.ts.map +1 -1
  32. package/dist/migrations.d.ts +0 -1
  33. package/dist/migrations.d.ts.map +1 -1
  34. package/dist/ontology-storage/materialization-session.d.ts +50 -8
  35. package/dist/ontology-storage/materialization-session.d.ts.map +1 -1
  36. package/dist/ontology-storage/materialization-state.d.ts +4 -5
  37. package/dist/ontology-storage/materialization-state.d.ts.map +1 -1
  38. package/dist/ontology-storage/materialization-writer.d.ts.map +1 -1
  39. package/dist/ontology-storage/materializations.d.ts +0 -2
  40. package/dist/ontology-storage/materializations.d.ts.map +1 -1
  41. package/dist/ontology-storage/outbox.d.ts.map +1 -1
  42. package/dist/ontology-storage/shared.d.ts +3 -3
  43. package/dist/ontology-storage/shared.d.ts.map +1 -1
  44. package/dist/ontology-storage/sources.d.ts.map +1 -1
  45. package/dist/pipeline-run-storage.d.ts +7 -2
  46. package/dist/pipeline-run-storage.d.ts.map +1 -1
  47. package/dist/projection-run-storage.d.ts +9 -2
  48. package/dist/projection-run-storage.d.ts.map +1 -1
  49. package/dist/run-list-query.d.ts +1 -1
  50. package/dist/run-list-query.d.ts.map +1 -1
  51. package/dist/sync-run-storage.d.ts +7 -2
  52. package/dist/sync-run-storage.d.ts.map +1 -1
  53. package/dist/testing.d.ts +6 -0
  54. package/dist/testing.d.ts.map +1 -0
  55. package/dist/transactions.d.ts +3 -0
  56. package/dist/transactions.d.ts.map +1 -1
  57. package/dist/webhook-run-storage.d.ts +9 -2
  58. package/dist/webhook-run-storage.d.ts.map +1 -1
  59. package/dist/workflow-run-storage.d.ts +7 -3
  60. package/dist/workflow-run-storage.d.ts.map +1 -1
  61. package/package.json +3 -3
  62. package/src/action-run-storage.ts +51 -74
  63. package/src/agents/agent-storage.ts +11 -6
  64. package/src/agents/checkpoints.ts +175 -0
  65. package/src/agents/index.ts +1 -0
  66. package/src/agents/messages.ts +59 -0
  67. package/src/agents/rows.ts +52 -40
  68. package/src/agents/runs.ts +27 -21
  69. package/src/ai-cost-storage.ts +744 -0
  70. package/src/ai-usage-storage.ts +324 -0
  71. package/src/auth-storage/service-accounts.ts +14 -21
  72. package/src/auth-storage/shared.ts +0 -22
  73. package/src/connector-connection-storage.ts +784 -0
  74. package/src/execution-storage.ts +158 -0
  75. package/src/index.ts +69 -22
  76. package/src/latest-run-query.ts +7 -1
  77. package/src/migrations/004-executions.sql +142 -0
  78. package/src/migrations/005-workflow-executions.sql +7 -0
  79. package/src/migrations/006-narrow-ontology-source-root-index.sql +4 -0
  80. package/src/migrations/007-split-overrides.sql +154 -0
  81. package/src/migrations/008-action-executions.sql +4 -0
  82. package/src/migrations/009-agent-executions.sql +115 -0
  83. package/src/migrations/010-ai-usage-accounting-foundation.sql +74 -0
  84. package/src/migrations/011-sync-failure-record.sql +22 -0
  85. package/src/migrations/012-pipeline-failure-record.sql +45 -0
  86. package/src/migrations/013-workflow-failure-record.sql +45 -0
  87. package/src/migrations/014-agent-failure-record.sql +59 -0
  88. package/src/migrations/015-projection-failure-record.sql +20 -0
  89. package/src/migrations/016-webhook-run-failure-record.sql +19 -0
  90. package/src/migrations/017-action-failure-record.sql +65 -0
  91. package/src/migrations/018-ontology-outbox-failure-record.sql +80 -0
  92. package/src/migrations/019-webhook-delivery-failure-record.sql +62 -0
  93. package/src/migrations/020-drop-run-usage-projections.sql +7 -0
  94. package/src/migrations/021-sync-pipeline-executions.sql +101 -0
  95. package/src/migrations/022-projection-executions.sql +244 -0
  96. package/src/migrations/023-webhook-executions.sql +51 -0
  97. package/src/migrations/024-ontology-commit-executions.sql +58 -0
  98. package/src/migrations/025-connector-connections.sql +257 -0
  99. package/src/migrations/026-ai-cost-accounting.sql +57 -0
  100. package/src/migrations/027-agent-context-checkpoints.sql +28 -0
  101. package/src/migrations/028-object-override-edit-times.sql +3 -0
  102. package/src/migrations.ts +129 -4
  103. package/src/ontology-storage/materialization-session.ts +317 -143
  104. package/src/ontology-storage/materialization-state.ts +323 -112
  105. package/src/ontology-storage/materialization-writer.ts +145 -67
  106. package/src/ontology-storage/materializations.ts +67 -141
  107. package/src/ontology-storage/outbox.ts +10 -3
  108. package/src/ontology-storage/shared.ts +10 -4
  109. package/src/ontology-storage/sources.ts +4 -1
  110. package/src/pipeline-run-storage.ts +128 -43
  111. package/src/projection-run-storage.ts +209 -97
  112. package/src/run-list-query.ts +9 -4
  113. package/src/sync-run-storage.ts +118 -32
  114. package/src/testing.ts +20 -0
  115. package/src/transactions.ts +10 -3
  116. package/src/webhook-run-storage.ts +118 -74
  117. package/src/workflow-run-storage.ts +117 -131
  118. package/dist/validation.d.ts +0 -5
  119. package/dist/validation.d.ts.map +0 -1
  120. package/dist/webhook-delivery-storage.d.ts +0 -27
  121. package/dist/webhook-delivery-storage.d.ts.map +0 -1
  122. package/src/validation.ts +0 -30
  123. package/src/webhook-delivery-storage.ts +0 -272
@@ -1,5 +1,10 @@
1
1
  import type { Database } from "bun:sqlite"
2
2
  import type { ActionSubject, JsonValue } from "@sixb/core"
3
+ import {
4
+ parseActionRunFailure,
5
+ serializeActionRunFailure,
6
+ } from "@sixb/core/internal/action-run-storage"
7
+ import { assertActionRunExecution } from "@sixb/core/internal/action-run-storage-provider"
3
8
  import type {
4
9
  ActionRunEffectsRecord,
5
10
  ActionRunFailure,
@@ -9,6 +14,7 @@ import type {
9
14
  ActionRunStorage,
10
15
  ActionRunWritebackRecord,
11
16
  EnterActionRunPhaseInput,
17
+ ExecutionStorage,
12
18
  FinishActionRunInput,
13
19
  ListActionRunsInput,
14
20
  ListActionRunsResult,
@@ -33,6 +39,8 @@ import {
33
39
  } from "./transactions"
34
40
 
35
41
  export interface SqliteActionRunStorageOptions {
42
+ /** Execution lookup sharing the same provider transaction. */
43
+ executions: ExecutionStorage
36
44
  /** Path to SQLite database file. Defaults to ':memory:' for in-memory database. */
37
45
  path?: string
38
46
  /** Internal shared connection used by bundled SqliteStorage. */
@@ -43,9 +51,12 @@ export class SqliteActionRunStorage implements ActionRunStorage {
43
51
  private readonly connection: SqliteStoreConnection
44
52
  private readonly db: Database
45
53
 
46
- constructor(options: SqliteActionRunStorageOptions = {}) {
54
+ private readonly executions: ExecutionStorage
55
+
56
+ constructor(options: SqliteActionRunStorageOptions) {
47
57
  this.connection = openSqliteStoreConnection(options)
48
58
  this.db = this.connection.db
59
+ this.executions = options.executions
49
60
 
50
61
  if (this.connection.installFreshSchema) {
51
62
  installFreshSqliteSchema(this.db)
@@ -85,6 +96,13 @@ export class SqliteActionRunStorage implements ActionRunStorage {
85
96
 
86
97
  async queue(input: QueueActionRunInput): Promise<ActionRunRecord> {
87
98
  const queuedAt = input.queuedAt ?? new Date()
99
+ await assertActionRunExecution({
100
+ executions: this.executions,
101
+ projectId: input.projectId,
102
+ executionId: input.executionId,
103
+ runId: input.id,
104
+ actionId: input.actionId,
105
+ })
88
106
 
89
107
  try {
90
108
  this.db
@@ -93,6 +111,7 @@ export class SqliteActionRunStorage implements ActionRunStorage {
93
111
  INSERT INTO action_runs (
94
112
  project_id,
95
113
  id,
114
+ execution_id,
96
115
  action_id,
97
116
  subject_kind,
98
117
  object_type_id,
@@ -107,28 +126,23 @@ export class SqliteActionRunStorage implements ActionRunStorage {
107
126
  writeback_status,
108
127
  writeback_completed_at,
109
128
  writeback_result,
110
- writeback_error_name,
111
- writeback_error_message,
112
- writeback_error_phase,
129
+ writeback_error,
113
130
  effects_status,
114
131
  effects_completed_at,
115
- effects_error_name,
116
- effects_error_message,
117
- effects_error_phase,
118
- error_name,
119
- error_message,
120
- error_phase
132
+ effects_error,
133
+ error
121
134
  ) VALUES (
122
- ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, ?, ?,
123
- NULL, NULL, NULL, NULL, NULL, NULL,
124
- NULL, NULL, NULL, NULL, NULL,
125
- NULL, NULL, NULL
135
+ ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, ?, ?,
136
+ NULL, NULL, NULL, NULL,
137
+ NULL, NULL, NULL,
138
+ NULL
126
139
  )
127
140
  `
128
141
  )
129
142
  .run(
130
143
  input.projectId,
131
144
  input.id,
145
+ input.executionId,
132
146
  input.actionId,
133
147
  input.subject.kind,
134
148
  input.subject.kind === "object" ? input.subject.objectTypeId : null,
@@ -188,17 +202,11 @@ export class SqliteActionRunStorage implements ActionRunStorage {
188
202
  writeback_status = NULL,
189
203
  writeback_completed_at = NULL,
190
204
  writeback_result = NULL,
191
- writeback_error_name = NULL,
192
- writeback_error_message = NULL,
193
- writeback_error_phase = NULL,
205
+ writeback_error = NULL,
194
206
  effects_status = NULL,
195
207
  effects_completed_at = NULL,
196
- effects_error_name = NULL,
197
- effects_error_message = NULL,
198
- effects_error_phase = NULL,
199
- error_name = NULL,
200
- error_message = NULL,
201
- error_phase = NULL
208
+ effects_error = NULL,
209
+ error = NULL
202
210
  WHERE project_id = ? AND id = ?
203
211
  `
204
212
  )
@@ -238,9 +246,7 @@ export class SqliteActionRunStorage implements ActionRunStorage {
238
246
  status = ?,
239
247
  phase = ?,
240
248
  started_at = ?,
241
- error_name = NULL,
242
- error_message = NULL,
243
- error_phase = NULL
249
+ error = NULL
244
250
  WHERE project_id = ? AND id = ?
245
251
  `
246
252
  )
@@ -307,9 +313,7 @@ export class SqliteActionRunStorage implements ActionRunStorage {
307
313
  writeback_status = ?,
308
314
  writeback_completed_at = ?,
309
315
  writeback_result = ?,
310
- writeback_error_name = ?,
311
- writeback_error_message = ?,
312
- writeback_error_phase = ?
316
+ writeback_error = ?
313
317
  WHERE project_id = ? AND id = ?
314
318
  `
315
319
  )
@@ -318,9 +322,7 @@ export class SqliteActionRunStorage implements ActionRunStorage {
318
322
  input.status,
319
323
  nextWriteback.completedAt.toISOString(),
320
324
  input.status === "succeeded" ? serializeJsonValue(input.result) : null,
321
- input.status === "failed" ? (input.error.name ?? null) : null,
322
- input.status === "failed" ? input.error.message : null,
323
- input.status === "failed" ? (input.error.phase ?? "writeback") : null,
325
+ input.status === "failed" ? serializeActionRunFailure(input.error, "writeback") : null,
324
326
  input.projectId,
325
327
  input.id
326
328
  )
@@ -357,9 +359,7 @@ export class SqliteActionRunStorage implements ActionRunStorage {
357
359
  phase = ?,
358
360
  effects_status = ?,
359
361
  effects_completed_at = ?,
360
- effects_error_name = ?,
361
- effects_error_message = ?,
362
- effects_error_phase = ?
362
+ effects_error = ?
363
363
  WHERE project_id = ? AND id = ?
364
364
  `
365
365
  )
@@ -367,9 +367,7 @@ export class SqliteActionRunStorage implements ActionRunStorage {
367
367
  "effects",
368
368
  input.status,
369
369
  nextEffects.completedAt.toISOString(),
370
- input.status === "failed" ? (input.error.name ?? null) : null,
371
- input.status === "failed" ? input.error.message : null,
372
- input.status === "failed" ? (input.error.phase ?? "effects") : null,
370
+ input.status === "failed" ? serializeActionRunFailure(input.error, "effects") : null,
373
371
  input.projectId,
374
372
  input.id
375
373
  )
@@ -410,9 +408,7 @@ export class SqliteActionRunStorage implements ActionRunStorage {
410
408
  status = ?,
411
409
  phase = ?,
412
410
  finished_at = ?,
413
- error_name = ?,
414
- error_message = ?,
415
- error_phase = ?
411
+ error = ?
416
412
  WHERE project_id = ? AND id = ?
417
413
  `
418
414
  )
@@ -420,9 +416,7 @@ export class SqliteActionRunStorage implements ActionRunStorage {
420
416
  input.status,
421
417
  phase,
422
418
  (input.finishedAt ?? new Date()).toISOString(),
423
- input.status === "succeeded" ? null : (input.error?.name ?? null),
424
- input.status === "succeeded" ? null : (input.error?.message ?? null),
425
- input.status === "succeeded" ? null : (input.error?.phase ?? phase),
419
+ input.status === "succeeded" ? null : serializeActionRunFailure(input.error),
426
420
  input.projectId,
427
421
  input.id
428
422
  )
@@ -583,23 +577,14 @@ function serializeJsonValue(value: JsonValue): string {
583
577
  }
584
578
 
585
579
  function toActionRunFailure(row: DatabaseRow): ActionRunFailure | undefined {
586
- return toFailure(row.error_name, row.error_message, row.error_phase)
580
+ return row.error === null ? undefined : parseActionRunFailure(row.error)
587
581
  }
588
582
 
589
- function toFailure(
590
- name: string | null,
591
- message: string | null,
592
- phase: ActionRunPhase | null
593
- ): ActionRunFailure | undefined {
594
- if (!message) {
595
- return undefined
596
- }
597
-
598
- return {
599
- name: name ?? undefined,
600
- message,
601
- phase: phase ?? undefined,
602
- }
583
+ function parsePhaseFailure<TPhase extends Extract<ActionRunPhase, "writeback" | "effects">>(
584
+ value: string | null,
585
+ expectedPhase: TPhase
586
+ ): ActionRunFailure<TPhase> {
587
+ return parseActionRunFailure(value, expectedPhase)
603
588
  }
604
589
 
605
590
  function toActionRunWritebackRecord(row: DatabaseRow): ActionRunWritebackRecord | undefined {
@@ -623,11 +608,7 @@ function toActionRunWritebackRecord(row: DatabaseRow): ActionRunWritebackRecord
623
608
  return {
624
609
  status: "failed",
625
610
  completedAt,
626
- error: toFailure(
627
- row.writeback_error_name,
628
- row.writeback_error_message,
629
- row.writeback_error_phase
630
- ),
611
+ error: parsePhaseFailure(row.writeback_error, "writeback"),
631
612
  }
632
613
  }
633
614
 
@@ -650,7 +631,7 @@ function toActionRunEffectsRecord(row: DatabaseRow): ActionRunEffectsRecord | un
650
631
  return {
651
632
  status: "failed",
652
633
  completedAt,
653
- error: toFailure(row.effects_error_name, row.effects_error_message, row.effects_error_phase),
634
+ error: parsePhaseFailure(row.effects_error, "effects"),
654
635
  }
655
636
  }
656
637
 
@@ -695,6 +676,7 @@ function rowToActionRunRecord(row: DatabaseRow): ActionRunRecord {
695
676
  return {
696
677
  id: row.id,
697
678
  projectId: row.project_id,
679
+ executionId: row.execution_id,
698
680
  actionId: row.action_id,
699
681
  subject: rowToActionSubject(row),
700
682
  status: row.status,
@@ -733,6 +715,7 @@ function isUniqueConstraintError(error: unknown): boolean {
733
715
  interface DatabaseRow {
734
716
  project_id: string
735
717
  id: string
718
+ execution_id: string
736
719
  action_id: string
737
720
  subject_kind: ActionSubject["kind"]
738
721
  object_type_id: string | null
@@ -747,15 +730,9 @@ interface DatabaseRow {
747
730
  writeback_status: ActionRunWritebackRecord["status"] | null
748
731
  writeback_completed_at: string | null
749
732
  writeback_result: string | null
750
- writeback_error_name: string | null
751
- writeback_error_message: string | null
752
- writeback_error_phase: ActionRunPhase | null
733
+ writeback_error: string | null
753
734
  effects_status: ActionRunEffectsRecord["status"] | null
754
735
  effects_completed_at: string | null
755
- effects_error_name: string | null
756
- effects_error_message: string | null
757
- effects_error_phase: ActionRunPhase | null
758
- error_name: string | null
759
- error_message: string | null
760
- error_phase: ActionRunPhase | null
736
+ effects_error: string | null
737
+ error: string | null
761
738
  }
@@ -1,10 +1,11 @@
1
- import type { AgentStorage } from "@sixb/core/storage"
1
+ import type { AgentStorage, ExecutionStorage } from "@sixb/core/storage"
2
2
  import { installFreshSqliteSchema } from "../migrations"
3
3
  import {
4
4
  closeSqliteStoreConnection,
5
5
  openSqliteStoreConnection,
6
6
  type SqliteStoreConnection,
7
7
  } from "../transactions"
8
+ import { SqliteAgentContextCheckpointStore } from "./checkpoints"
8
9
  import { SqliteAgentMessageStore } from "./messages"
9
10
  import { SqliteAgentRunStore } from "./runs"
10
11
  import { SqliteAgentThreadStore } from "./threads"
@@ -14,21 +15,24 @@ export interface SqliteAgentStorageOptions {
14
15
  path?: string
15
16
  /** Internal shared connection used by bundled SqliteStorage. */
16
17
  connection?: SqliteStoreConnection
18
+ /** Execution ledger used to validate immutable run ownership. */
19
+ executions: ExecutionStorage
17
20
  }
18
21
 
19
22
  /**
20
- * SQLite-backed agent persistence: thread / run / message sub-stores sharing one connection. The
21
- * cross-table writes (reserve + finish touch the thread anchor, append bumps thread stats) run in
22
- * SQLite transactions; the bundled {@link SqliteStorage} serializes all of them on one connection.
23
+ * SQLite-backed agent persistence: thread, run, message, and context-checkpoint sub-stores sharing
24
+ * one connection. Cross-table writes run in SQLite transactions; the bundled {@link SqliteStorage}
25
+ * serializes them for single-flight and checkpoint compare-and-swap safety.
23
26
  */
24
27
  export class SqliteAgentStorage implements AgentStorage {
25
28
  readonly threads: SqliteAgentThreadStore
26
29
  readonly runs: SqliteAgentRunStore
27
30
  readonly messages: SqliteAgentMessageStore
31
+ readonly checkpoints: SqliteAgentContextCheckpointStore
28
32
 
29
33
  private readonly connection: SqliteStoreConnection
30
34
 
31
- constructor(options: SqliteAgentStorageOptions = {}) {
35
+ constructor(options: SqliteAgentStorageOptions) {
32
36
  this.connection = openSqliteStoreConnection(options)
33
37
 
34
38
  if (this.connection.installFreshSchema) {
@@ -36,8 +40,9 @@ export class SqliteAgentStorage implements AgentStorage {
36
40
  }
37
41
 
38
42
  this.threads = new SqliteAgentThreadStore(this.connection.db)
39
- this.runs = new SqliteAgentRunStore(this.connection.db)
43
+ this.runs = new SqliteAgentRunStore(this.connection.db, options.executions)
40
44
  this.messages = new SqliteAgentMessageStore(this.connection.db)
45
+ this.checkpoints = new SqliteAgentContextCheckpointStore(this.connection.db)
41
46
  }
42
47
 
43
48
  close(): void {
@@ -0,0 +1,175 @@
1
+ import type { Database } from "bun:sqlite"
2
+ import {
3
+ agentContextCheckpointMatchesCreateInput,
4
+ assertAgentContextCheckpointAnchors,
5
+ assertAgentContextCheckpointAuthority,
6
+ assertAgentContextCheckpointReplayState,
7
+ assertCreateAgentContextCheckpointInput,
8
+ } from "@sixb/core/internal/agent-run-storage-provider"
9
+ import {
10
+ type AgentContextCheckpointRecord,
11
+ type AgentContextCheckpointStore,
12
+ AgentStorageError,
13
+ type CreateAgentContextCheckpointInput,
14
+ } from "@sixb/core/storage"
15
+ import {
16
+ type AgentContextCheckpointRow,
17
+ type AgentMessageRow,
18
+ type AgentRunRow,
19
+ type AgentThreadRow,
20
+ rowToContextCheckpointRecord,
21
+ rowToMessageRecord,
22
+ rowToRunRecord,
23
+ rowToThreadRecord,
24
+ } from "./rows"
25
+
26
+ export class SqliteAgentContextCheckpointStore implements AgentContextCheckpointStore {
27
+ constructor(private readonly db: Database) {}
28
+
29
+ async create(input: CreateAgentContextCheckpointInput): Promise<AgentContextCheckpointRecord> {
30
+ assertCreateAgentContextCheckpointInput(input, "SixbSqlite")
31
+
32
+ return this.db.transaction(() => {
33
+ const runRow = this.db
34
+ .query("SELECT * FROM agent_runs WHERE project_id = ? AND id = ?")
35
+ .get(input.projectId, input.createdByRunId) as AgentRunRow | null
36
+ const threadRow = this.db
37
+ .query("SELECT * FROM agent_threads WHERE project_id = ? AND id = ?")
38
+ .get(input.projectId, input.threadId) as AgentThreadRow | null
39
+ assertAgentContextCheckpointAuthority({
40
+ create: input,
41
+ run: runRow ? rowToRunRecord(runRow) : null,
42
+ thread: threadRow ? rowToThreadRecord(threadRow) : null,
43
+ prefix: "SixbSqlite",
44
+ })
45
+
46
+ const latest = this.findLatest(input.projectId, input.threadId)
47
+ const headRow = this.db
48
+ .query(
49
+ "SELECT COALESCE(MAX(seq), 0) AS seq FROM agent_messages WHERE project_id = ? AND thread_id = ?"
50
+ )
51
+ .get(input.projectId, input.threadId) as { seq: number }
52
+
53
+ const existingForRun = this.db
54
+ .query(
55
+ "SELECT * FROM agent_context_checkpoints WHERE project_id = ? AND created_by_run_id = ?"
56
+ )
57
+ .get(input.projectId, input.createdByRunId) as AgentContextCheckpointRow | null
58
+ if (existingForRun) {
59
+ const record = rowToContextCheckpointRecord(existingForRun)
60
+ if (agentContextCheckpointMatchesCreateInput(record, input)) {
61
+ assertAgentContextCheckpointReplayState({
62
+ create: input,
63
+ existing: record,
64
+ latest,
65
+ headSeq: headRow.seq,
66
+ prefix: "SixbSqlite",
67
+ })
68
+ return record
69
+ }
70
+ throw new AgentStorageError(
71
+ "invalid_state",
72
+ `[SixbSqlite] Agent run '${input.createdByRunId}' already created a different context checkpoint.`
73
+ )
74
+ }
75
+
76
+ const duplicateId = this.db
77
+ .query("SELECT 1 FROM agent_context_checkpoints WHERE project_id = ? AND id = ?")
78
+ .get(input.projectId, input.id)
79
+ if (duplicateId) {
80
+ throw new AgentStorageError(
81
+ "duplicate_id",
82
+ `[SixbSqlite] Agent context checkpoint '${input.id}' already exists for project '${input.projectId}'.`
83
+ )
84
+ }
85
+
86
+ const firstRetainedRow = this.db
87
+ .query(
88
+ "SELECT * FROM agent_messages WHERE project_id = ? AND thread_id = ? AND seq > ? ORDER BY seq ASC LIMIT 1"
89
+ )
90
+ .get(input.projectId, input.threadId, input.summarizedThroughSeq) as AgentMessageRow | null
91
+ assertAgentContextCheckpointAnchors({
92
+ create: input,
93
+ latest,
94
+ headSeq: headRow.seq,
95
+ firstRetained: firstRetainedRow ? rowToMessageRecord(firstRetainedRow) : null,
96
+ prefix: "SixbSqlite",
97
+ })
98
+
99
+ const createdAt = input.createdAt ?? new Date()
100
+ this.db
101
+ .query(
102
+ `
103
+ INSERT INTO agent_context_checkpoints (
104
+ project_id,
105
+ id,
106
+ thread_id,
107
+ created_by_run_id,
108
+ previous_checkpoint_id,
109
+ reason,
110
+ summary,
111
+ summary_format_version,
112
+ summarized_through_seq,
113
+ observed_head_seq,
114
+ estimated_input_tokens_before,
115
+ estimated_input_tokens_after,
116
+ summary_model_id,
117
+ created_at
118
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
119
+ `
120
+ )
121
+ .run(
122
+ input.projectId,
123
+ input.id,
124
+ input.threadId,
125
+ input.createdByRunId,
126
+ input.expectedPreviousCheckpointId,
127
+ input.reason,
128
+ input.summary,
129
+ input.summaryFormatVersion,
130
+ input.summarizedThroughSeq,
131
+ input.observedHeadSeq,
132
+ input.estimatedInputTokensBefore,
133
+ input.estimatedInputTokensAfter,
134
+ input.summaryModelId,
135
+ createdAt.toISOString()
136
+ )
137
+
138
+ return this.requireById(input.projectId, input.id)
139
+ })()
140
+ }
141
+
142
+ async getLatest(input: {
143
+ readonly projectId: string
144
+ readonly threadId: string
145
+ }): Promise<AgentContextCheckpointRecord | null> {
146
+ return this.findLatest(input.projectId, input.threadId)
147
+ }
148
+
149
+ private findLatest(projectId: string, threadId: string): AgentContextCheckpointRecord | null {
150
+ const row = this.db
151
+ .query(
152
+ `
153
+ SELECT * FROM agent_context_checkpoints
154
+ WHERE project_id = ? AND thread_id = ?
155
+ ORDER BY summarized_through_seq DESC, created_at DESC, id DESC
156
+ LIMIT 1
157
+ `
158
+ )
159
+ .get(projectId, threadId) as AgentContextCheckpointRow | null
160
+ return row ? rowToContextCheckpointRecord(row) : null
161
+ }
162
+
163
+ private requireById(projectId: string, id: string): AgentContextCheckpointRecord {
164
+ const row = this.db
165
+ .query("SELECT * FROM agent_context_checkpoints WHERE project_id = ? AND id = ?")
166
+ .get(projectId, id) as AgentContextCheckpointRow | null
167
+ if (!row) {
168
+ throw new AgentStorageError(
169
+ "invalid_state",
170
+ `[SixbSqlite] Failed to load agent context checkpoint '${id}' for project '${projectId}'.`
171
+ )
172
+ }
173
+ return rowToContextCheckpointRecord(row)
174
+ }
175
+ }
@@ -1,5 +1,6 @@
1
1
  export type { SqliteAgentStorageOptions } from "./agent-storage"
2
2
  export { SqliteAgentStorage } from "./agent-storage"
3
+ export { SqliteAgentContextCheckpointStore } from "./checkpoints"
3
4
  export { SqliteAgentMessageStore } from "./messages"
4
5
  export { SqliteAgentRunStore } from "./runs"
5
6
  export { SqliteAgentThreadStore } from "./threads"
@@ -5,6 +5,7 @@ import {
5
5
  type AgentMessageStore,
6
6
  AgentStorageError,
7
7
  type AppendAgentMessageInput,
8
+ type DeleteAgentMessagesByRunInput,
8
9
  type ListAgentMessagesInput,
9
10
  type ListAgentMessagesResult,
10
11
  } from "@sixb/core/storage"
@@ -110,6 +111,59 @@ export class SqliteAgentMessageStore implements AgentMessageStore {
110
111
  })()
111
112
  }
112
113
 
114
+ async deleteByRunId(input: DeleteAgentMessagesByRunInput): Promise<number> {
115
+ return this.db.transaction(() => {
116
+ const thread = this.db
117
+ .query("SELECT 1 AS present FROM agent_threads WHERE project_id = ? AND id = ?")
118
+ .get(input.projectId, input.threadId) as { present: number } | null
119
+
120
+ if (!thread) {
121
+ throw new AgentStorageError(
122
+ "thread_not_found",
123
+ `[SixbSqlite] Agent thread '${input.threadId}' not found for project '${input.projectId}'.`
124
+ )
125
+ }
126
+
127
+ const deleted = this.db
128
+ .query("DELETE FROM agent_messages WHERE project_id = ? AND thread_id = ? AND run_id = ?")
129
+ .run(input.projectId, input.threadId, input.runId)
130
+ if (deleted.changes === 0) {
131
+ return 0
132
+ }
133
+
134
+ const updatedAt = new Date().toISOString()
135
+ this.db
136
+ .query(
137
+ `
138
+ UPDATE agent_threads
139
+ SET
140
+ message_count = (
141
+ SELECT COUNT(*) FROM agent_messages WHERE project_id = ? AND thread_id = ?
142
+ ),
143
+ last_message_at = (
144
+ SELECT created_at FROM agent_messages
145
+ WHERE project_id = ? AND thread_id = ?
146
+ ORDER BY seq DESC
147
+ LIMIT 1
148
+ ),
149
+ updated_at = ?
150
+ WHERE project_id = ? AND id = ?
151
+ `
152
+ )
153
+ .run(
154
+ input.projectId,
155
+ input.threadId,
156
+ input.projectId,
157
+ input.threadId,
158
+ updatedAt,
159
+ input.projectId,
160
+ input.threadId
161
+ )
162
+
163
+ return deleted.changes
164
+ })()
165
+ }
166
+
113
167
  async getById(params: { projectId: string; id: string }): Promise<AgentMessageRecord | null> {
114
168
  const row = this.db
115
169
  .query("SELECT * FROM agent_messages WHERE project_id = ? AND id = ?")
@@ -126,6 +180,11 @@ export class SqliteAgentMessageStore implements AgentMessageStore {
126
180
  const whereClauses = ["project_id = ?", "thread_id = ?"]
127
181
  const args: SqliteValue[] = [input.projectId, input.threadId]
128
182
 
183
+ if (input.afterSeq !== undefined) {
184
+ whereClauses.push("seq > ?")
185
+ args.push(input.afterSeq)
186
+ }
187
+
129
188
  if (input.roles) {
130
189
  whereClauses.push(`role IN (${input.roles.map(() => "?").join(", ")})`)
131
190
  args.push(...input.roles)