@sixb/sqlite 0.1.21 → 0.1.22
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.
- package/dist/agents/agent-storage.d.ts +8 -2
- package/dist/agents/agent-storage.d.ts.map +1 -1
- package/dist/agents/approvals.d.ts +51 -0
- package/dist/agents/approvals.d.ts.map +1 -0
- package/dist/agents/checkpoints.d.ts +1 -0
- package/dist/agents/checkpoints.d.ts.map +1 -1
- package/dist/agents/index.d.ts +1 -0
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/memory.d.ts +21 -0
- package/dist/agents/memory.d.ts.map +1 -0
- package/dist/agents/messages.d.ts.map +1 -1
- package/dist/agents/rows.d.ts +15 -1
- package/dist/agents/rows.d.ts.map +1 -1
- package/dist/agents/runs.d.ts +14 -1
- package/dist/agents/runs.d.ts.map +1 -1
- package/dist/agents/settings.d.ts +29 -0
- package/dist/agents/settings.d.ts.map +1 -0
- package/dist/agents/threads.d.ts +12 -1
- package/dist/agents/threads.d.ts.map +1 -1
- package/dist/ai-cost-groups.d.ts.map +1 -1
- package/dist/ai-cost-storage.d.ts.map +1 -1
- package/dist/ai-usage-storage.d.ts +4 -1
- package/dist/ai-usage-storage.d.ts.map +1 -1
- package/dist/artifact-storage.d.ts +10 -1
- package/dist/artifact-storage.d.ts.map +1 -1
- package/dist/auth-storage/auth-storage.d.ts +3 -2
- package/dist/auth-storage/auth-storage.d.ts.map +1 -1
- package/dist/auth-storage/users.d.ts +7 -3
- package/dist/auth-storage/users.d.ts.map +1 -1
- package/dist/execution-storage.d.ts +2 -1
- package/dist/execution-storage.d.ts.map +1 -1
- package/dist/index.js +3348 -1515
- package/dist/index.js.map +32 -26
- package/dist/migrations.d.ts +0 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/objects/conditional-reader.d.ts +8 -0
- package/dist/objects/conditional-reader.d.ts.map +1 -0
- package/dist/objects/query-compiler.d.ts +26 -1
- package/dist/objects/query-compiler.d.ts.map +1 -1
- package/dist/objects/read-scope.d.ts +10 -1
- package/dist/objects/read-scope.d.ts.map +1 -1
- package/dist/objects/reader.d.ts +2 -0
- package/dist/objects/reader.d.ts.map +1 -1
- package/dist/objects/scoped-reader.d.ts +15 -0
- package/dist/objects/scoped-reader.d.ts.map +1 -0
- package/dist/objects/selected-reader.d.ts.map +1 -1
- package/dist/objects/storage.d.ts +6 -1
- package/dist/objects/storage.d.ts.map +1 -1
- package/dist/ontology-storage/index.d.ts +2 -0
- package/dist/ontology-storage/index.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-writer.d.ts +2 -0
- package/dist/ontology-storage/materialization-writer.d.ts.map +1 -1
- package/dist/ontology-storage/materializations.d.ts.map +1 -1
- package/dist/ontology-storage/unique-values.d.ts +42 -0
- package/dist/ontology-storage/unique-values.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/agents/agent-storage.ts +11 -2
- package/src/agents/approvals.ts +456 -0
- package/src/agents/checkpoints.ts +9 -4
- package/src/agents/index.ts +1 -0
- package/src/agents/memory.ts +195 -0
- package/src/agents/messages.ts +21 -18
- package/src/agents/rows.ts +46 -1
- package/src/agents/runs.ts +202 -21
- package/src/agents/settings.ts +112 -0
- package/src/agents/threads.ts +385 -1
- package/src/ai-cost-groups.ts +11 -1
- package/src/ai-cost-storage.ts +54 -12
- package/src/ai-usage-storage.ts +79 -2
- package/src/artifact-storage.ts +206 -9
- package/src/auth-storage/auth-storage.ts +58 -26
- package/src/auth-storage/users.ts +106 -60
- package/src/execution-storage.ts +39 -0
- package/src/migrations/067-artifact-kinds.sql +32 -0
- package/src/migrations/068-artifact-state.sql +15 -0
- package/src/migrations/069-agent-thread-participants.sql +16 -0
- package/src/migrations/070-forked-context-checkpoints.sql +45 -0
- package/src/migrations/071-unique-values.sql +22 -0
- package/src/migrations/072-generated-media-usage.sql +32 -0
- package/src/migrations/073-model-call-files.sql +11 -0
- package/src/migrations/074-agent-memory.sql +32 -0
- package/src/migrations/075-artifact-threads.sql +21 -0
- package/src/migrations/076-agent-approvals.sql +59 -0
- package/src/migrations/077-agent-approval-resume.sql +20 -0
- package/src/migrations/078-subagent-threads.sql +241 -0
- package/src/migrations/079-subagent-questions.sql +6 -0
- package/src/migrations.ts +75 -1
- package/src/objects/README.md +7 -0
- package/src/objects/conditional-reader.ts +27 -0
- package/src/objects/query-compiler.ts +36 -6
- package/src/objects/read-scope.ts +140 -23
- package/src/objects/reader.ts +47 -14
- package/src/objects/scoped-reader.ts +103 -0
- package/src/objects/selected-reader.ts +12 -75
- package/src/objects/storage.ts +16 -0
- package/src/ontology-storage/index.ts +3 -0
- package/src/ontology-storage/materialization-writer.ts +14 -0
- package/src/ontology-storage/materializations.ts +16 -1
- package/src/ontology-storage/unique-values.ts +187 -0
- package/src/transactions.ts +1 -1
package/src/agents/messages.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Database } from "bun:sqlite"
|
|
2
|
+
import { countsAsThreadMessage } from "@sixb/core/internal/agent-run-storage-provider"
|
|
2
3
|
import { AGENT_MESSAGE_CONTENT_VERSION } from "@sixb/core/internal/agents"
|
|
3
4
|
import {
|
|
4
5
|
type AgentMessageRecord,
|
|
@@ -101,11 +102,17 @@ export class SqliteAgentMessageStore implements AgentMessageStore {
|
|
|
101
102
|
.query(
|
|
102
103
|
`
|
|
103
104
|
UPDATE agent_threads
|
|
104
|
-
SET message_count = message_count +
|
|
105
|
+
SET message_count = message_count + ?, last_message_at = ?, updated_at = ?
|
|
105
106
|
WHERE project_id = ? AND id = ?
|
|
106
107
|
`
|
|
107
108
|
)
|
|
108
|
-
.run(
|
|
109
|
+
.run(
|
|
110
|
+
countsAsThreadMessage(input) ? 1 : 0,
|
|
111
|
+
createdAt.toISOString(),
|
|
112
|
+
createdAt.toISOString(),
|
|
113
|
+
input.projectId,
|
|
114
|
+
input.threadId
|
|
115
|
+
)
|
|
109
116
|
|
|
110
117
|
return this.requireMessage(input.projectId, input.id)
|
|
111
118
|
})()
|
|
@@ -125,11 +132,17 @@ export class SqliteAgentMessageStore implements AgentMessageStore {
|
|
|
125
132
|
}
|
|
126
133
|
|
|
127
134
|
const deleted = this.db
|
|
128
|
-
.query(
|
|
129
|
-
|
|
130
|
-
|
|
135
|
+
.query(
|
|
136
|
+
"DELETE FROM agent_messages WHERE project_id = ? AND thread_id = ? AND run_id = ? RETURNING parts"
|
|
137
|
+
)
|
|
138
|
+
.all(input.projectId, input.threadId, input.runId) as { parts: string }[]
|
|
139
|
+
if (deleted.length === 0) {
|
|
131
140
|
return 0
|
|
132
141
|
}
|
|
142
|
+
// The thread counts only the messages the conversation shows: take back the ones it counted.
|
|
143
|
+
const counted = deleted.filter((row) =>
|
|
144
|
+
countsAsThreadMessage({ parts: JSON.parse(row.parts) })
|
|
145
|
+
).length
|
|
133
146
|
|
|
134
147
|
const updatedAt = new Date().toISOString()
|
|
135
148
|
this.db
|
|
@@ -137,9 +150,7 @@ export class SqliteAgentMessageStore implements AgentMessageStore {
|
|
|
137
150
|
`
|
|
138
151
|
UPDATE agent_threads
|
|
139
152
|
SET
|
|
140
|
-
message_count = (
|
|
141
|
-
SELECT COUNT(*) FROM agent_messages WHERE project_id = ? AND thread_id = ?
|
|
142
|
-
),
|
|
153
|
+
message_count = MAX(message_count - ?, 0),
|
|
143
154
|
last_message_at = (
|
|
144
155
|
SELECT created_at FROM agent_messages
|
|
145
156
|
WHERE project_id = ? AND thread_id = ?
|
|
@@ -150,17 +161,9 @@ export class SqliteAgentMessageStore implements AgentMessageStore {
|
|
|
150
161
|
WHERE project_id = ? AND id = ?
|
|
151
162
|
`
|
|
152
163
|
)
|
|
153
|
-
.run(
|
|
154
|
-
input.projectId,
|
|
155
|
-
input.threadId,
|
|
156
|
-
input.projectId,
|
|
157
|
-
input.threadId,
|
|
158
|
-
updatedAt,
|
|
159
|
-
input.projectId,
|
|
160
|
-
input.threadId
|
|
161
|
-
)
|
|
164
|
+
.run(counted, input.projectId, input.threadId, updatedAt, input.projectId, input.threadId)
|
|
162
165
|
|
|
163
|
-
return deleted.
|
|
166
|
+
return deleted.length
|
|
164
167
|
})()
|
|
165
168
|
}
|
|
166
169
|
|
package/src/agents/rows.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type AgentMessageRecord,
|
|
9
9
|
type AgentRunDiagnostic,
|
|
10
10
|
type AgentRunRecord,
|
|
11
|
+
type AgentThreadParticipantRecord,
|
|
11
12
|
type AgentThreadRecord,
|
|
12
13
|
type AgentThreadSandboxState,
|
|
13
14
|
type ConversationAgentRunSpec,
|
|
@@ -22,6 +23,10 @@ import type { SqliteValue } from "../run-list-query"
|
|
|
22
23
|
export interface AgentThreadRow {
|
|
23
24
|
project_id: string
|
|
24
25
|
id: string
|
|
26
|
+
kind: AgentThreadRecord["kind"]
|
|
27
|
+
parent_thread_id: string | null
|
|
28
|
+
parent_run_id: string | null
|
|
29
|
+
spawn_key: string | null
|
|
25
30
|
owner_principal_type: Principal["type"]
|
|
26
31
|
owner_principal_id: string
|
|
27
32
|
title: string | null
|
|
@@ -35,6 +40,16 @@ export interface AgentThreadRow {
|
|
|
35
40
|
updated_at: string
|
|
36
41
|
}
|
|
37
42
|
|
|
43
|
+
export interface AgentThreadParticipantRow {
|
|
44
|
+
project_id: string
|
|
45
|
+
thread_id: string
|
|
46
|
+
principal_type: "user"
|
|
47
|
+
principal_id: string
|
|
48
|
+
added_at: string
|
|
49
|
+
added_by_type: Principal["type"]
|
|
50
|
+
added_by_id: string
|
|
51
|
+
}
|
|
52
|
+
|
|
38
53
|
export interface AgentRunRow {
|
|
39
54
|
project_id: string
|
|
40
55
|
id: string
|
|
@@ -98,6 +113,16 @@ export function rowToThreadRecord(row: AgentThreadRow): AgentThreadRecord {
|
|
|
98
113
|
return {
|
|
99
114
|
id: row.id,
|
|
100
115
|
projectId: row.project_id,
|
|
116
|
+
kind: row.kind,
|
|
117
|
+
...(row.parent_thread_id && row.parent_run_id && row.spawn_key
|
|
118
|
+
? {
|
|
119
|
+
parent: {
|
|
120
|
+
threadId: row.parent_thread_id,
|
|
121
|
+
spawnedBy: row.parent_run_id,
|
|
122
|
+
key: row.spawn_key,
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
: {}),
|
|
101
126
|
ownerPrincipal: { type: row.owner_principal_type, id: row.owner_principal_id },
|
|
102
127
|
title: row.title ?? undefined,
|
|
103
128
|
...(row.sandbox_state == null
|
|
@@ -154,12 +179,20 @@ export function rowToRunRecord(row: AgentRunRow): AgentRunRecord {
|
|
|
154
179
|
...(row.spec === null ? {} : { spec: JSON.parse(row.spec) as ConversationAgentRunSpec }),
|
|
155
180
|
}
|
|
156
181
|
}
|
|
157
|
-
if (
|
|
182
|
+
if (
|
|
183
|
+
!row.thread_id ||
|
|
184
|
+
!row.trigger_message_id ||
|
|
185
|
+
!row.parent_run_id ||
|
|
186
|
+
!row.spawn_key ||
|
|
187
|
+
row.spec === null
|
|
188
|
+
) {
|
|
158
189
|
throw new Error(`[SixbSqlite] Subagent run '${row.id}' has invalid persisted fields.`)
|
|
159
190
|
}
|
|
160
191
|
return {
|
|
161
192
|
...base,
|
|
162
193
|
kind: "subagent",
|
|
194
|
+
threadId: row.thread_id,
|
|
195
|
+
triggerMessageId: row.trigger_message_id,
|
|
163
196
|
parentRunId: row.parent_run_id,
|
|
164
197
|
spawnKey: row.spawn_key,
|
|
165
198
|
spec: JSON.parse(row.spec) as SubagentRunSpec,
|
|
@@ -212,6 +245,18 @@ export function rowToContextCheckpointRecord(
|
|
|
212
245
|
}
|
|
213
246
|
}
|
|
214
247
|
|
|
248
|
+
export function rowToParticipantRecord(
|
|
249
|
+
row: AgentThreadParticipantRow
|
|
250
|
+
): AgentThreadParticipantRecord {
|
|
251
|
+
return {
|
|
252
|
+
projectId: row.project_id,
|
|
253
|
+
threadId: row.thread_id,
|
|
254
|
+
principal: { type: "user", id: row.principal_id },
|
|
255
|
+
addedAt: new Date(row.added_at),
|
|
256
|
+
addedBy: { type: row.added_by_type, id: row.added_by_id },
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
215
260
|
function principalFromColumns(
|
|
216
261
|
type: Principal["type"] | null | undefined,
|
|
217
262
|
id: string | null | undefined
|
package/src/agents/runs.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import type { Database } from "bun:sqlite"
|
|
2
2
|
import {
|
|
3
|
+
agentRunMayHoldCalls,
|
|
3
4
|
assertAgentRunExecution,
|
|
5
|
+
assertAgentRunFinishPayload,
|
|
6
|
+
assertAwaitingApprovalFinish,
|
|
4
7
|
assertConversationAgentRunSpec,
|
|
8
|
+
assertConversationThreadRow,
|
|
5
9
|
assertCreateSubagentRunInput,
|
|
6
|
-
|
|
10
|
+
assertSubagentThreadTakesRun,
|
|
11
|
+
newSubagentThreadRecord,
|
|
7
12
|
subagentRunMatchesCreateInput,
|
|
8
13
|
} from "@sixb/core/internal/agent-run-storage-provider"
|
|
14
|
+
import { AGENT_MESSAGE_CONTENT_VERSION } from "@sixb/core/internal/agents"
|
|
9
15
|
import { serializeSixbFailure } from "@sixb/core/internal/errors"
|
|
10
16
|
import {
|
|
11
17
|
AGENT_RUN_FAILURE_CODES,
|
|
@@ -21,6 +27,7 @@ import {
|
|
|
21
27
|
type FinishQueuedAgentRunInput,
|
|
22
28
|
type ListAgentRunsInput,
|
|
23
29
|
type ListAgentRunsResult,
|
|
30
|
+
type ListThreadSubagentsInput,
|
|
24
31
|
type ReclaimAgentRunInput,
|
|
25
32
|
type StartAgentRunInput,
|
|
26
33
|
type SubagentRunRecord,
|
|
@@ -32,7 +39,8 @@ import {
|
|
|
32
39
|
type SqliteValue,
|
|
33
40
|
} from "../run-list-query"
|
|
34
41
|
import { isUniqueConstraintError } from "../storage-errors"
|
|
35
|
-
import {
|
|
42
|
+
import { sqliteHoldsRun } from "./approvals"
|
|
43
|
+
import { type AgentRunRow, type AgentThreadRow, rowToRunRecord, rowToThreadRecord } from "./rows"
|
|
36
44
|
|
|
37
45
|
const SQLITE_RUN_ID_BATCH_SIZE = 500
|
|
38
46
|
|
|
@@ -54,8 +62,11 @@ export class SqliteAgentRunStore implements AgentRunStore {
|
|
|
54
62
|
|
|
55
63
|
return this.db.transaction(() => {
|
|
56
64
|
const thread = this.db
|
|
57
|
-
.query("SELECT active_run_id FROM agent_threads WHERE project_id = ? AND id = ?")
|
|
58
|
-
.get(input.projectId, input.threadId) as
|
|
65
|
+
.query("SELECT id, kind, active_run_id FROM agent_threads WHERE project_id = ? AND id = ?")
|
|
66
|
+
.get(input.projectId, input.threadId) as Pick<
|
|
67
|
+
AgentThreadRow,
|
|
68
|
+
"id" | "kind" | "active_run_id"
|
|
69
|
+
> | null
|
|
59
70
|
|
|
60
71
|
if (!thread) {
|
|
61
72
|
throw new AgentStorageError(
|
|
@@ -63,6 +74,7 @@ export class SqliteAgentRunStore implements AgentRunStore {
|
|
|
63
74
|
`[SixbSqlite] Agent thread '${input.threadId}' not found for project '${input.projectId}'.`
|
|
64
75
|
)
|
|
65
76
|
}
|
|
77
|
+
assertConversationThreadRow(thread, "takes turns", "SixbSqlite")
|
|
66
78
|
if (thread.active_run_id !== null) {
|
|
67
79
|
throw new AgentStorageError(
|
|
68
80
|
"active_run_exists",
|
|
@@ -188,24 +200,54 @@ export class SqliteAgentRunStore implements AgentRunStore {
|
|
|
188
200
|
)
|
|
189
201
|
}
|
|
190
202
|
|
|
203
|
+
const createdAt = input.createdAt ?? new Date()
|
|
191
204
|
try {
|
|
205
|
+
this.claimChildThread(input, parentRow, createdAt)
|
|
192
206
|
this.db
|
|
193
207
|
.query(
|
|
194
208
|
`
|
|
195
209
|
INSERT INTO agent_runs (
|
|
196
|
-
project_id, id, execution_id, kind,
|
|
197
|
-
status, attempt, created_at
|
|
198
|
-
) VALUES (?, ?, ?, 'subagent', ?, ?, ?, 'queued', 0, ?)
|
|
210
|
+
project_id, id, execution_id, kind, thread_id, trigger_message_id, parent_run_id,
|
|
211
|
+
spawn_key, spec, status, attempt, created_at
|
|
212
|
+
) VALUES (?, ?, ?, 'subagent', ?, ?, ?, ?, ?, 'queued', 0, ?)
|
|
199
213
|
`
|
|
200
214
|
)
|
|
201
215
|
.run(
|
|
202
216
|
input.projectId,
|
|
203
217
|
input.id,
|
|
204
218
|
input.executionId,
|
|
219
|
+
input.thread.id,
|
|
220
|
+
input.trigger.id,
|
|
205
221
|
input.parentRunId,
|
|
206
222
|
input.spawnKey,
|
|
207
223
|
JSON.stringify(input.spec),
|
|
208
|
-
|
|
224
|
+
createdAt.toISOString()
|
|
225
|
+
)
|
|
226
|
+
this.db
|
|
227
|
+
.query(
|
|
228
|
+
`
|
|
229
|
+
INSERT INTO agent_messages (
|
|
230
|
+
project_id, id, thread_id, run_id, role, seq, parts, metadata, content_version,
|
|
231
|
+
created_at
|
|
232
|
+
) VALUES (
|
|
233
|
+
?, ?, ?, ?, 'user',
|
|
234
|
+
(SELECT COALESCE(MAX(seq), 0) + 1 FROM agent_messages
|
|
235
|
+
WHERE project_id = ? AND thread_id = ?),
|
|
236
|
+
?, ?, ?, ?
|
|
237
|
+
)
|
|
238
|
+
`
|
|
239
|
+
)
|
|
240
|
+
.run(
|
|
241
|
+
input.projectId,
|
|
242
|
+
input.trigger.id,
|
|
243
|
+
input.thread.id,
|
|
244
|
+
input.id,
|
|
245
|
+
input.projectId,
|
|
246
|
+
input.thread.id,
|
|
247
|
+
JSON.stringify(input.trigger.parts),
|
|
248
|
+
input.trigger.metadata === undefined ? null : JSON.stringify(input.trigger.metadata),
|
|
249
|
+
AGENT_MESSAGE_CONTENT_VERSION,
|
|
250
|
+
createdAt.toISOString()
|
|
209
251
|
)
|
|
210
252
|
} catch (error) {
|
|
211
253
|
if (isUniqueConstraintError(error)) {
|
|
@@ -347,18 +389,14 @@ export class SqliteAgentRunStore implements AgentRunStore {
|
|
|
347
389
|
`[SixbSqlite] Execution token is no longer current on agent run '${input.id}'.`
|
|
348
390
|
)
|
|
349
391
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
throw new AgentStorageError(
|
|
359
|
-
"invalid_input",
|
|
360
|
-
`[SixbSqlite] Conversational Agent run '${run.id}' cannot persist a subagent result.`
|
|
361
|
-
)
|
|
392
|
+
assertAgentRunFinishPayload(run.kind, input, run.id, "SixbSqlite")
|
|
393
|
+
if (input.finishReason === "awaiting_approval") {
|
|
394
|
+
assertAwaitingApprovalFinish({
|
|
395
|
+
runId: input.id,
|
|
396
|
+
status: input.status,
|
|
397
|
+
holdsApproval: sqliteHoldsRun(this.db, input.projectId, input.id),
|
|
398
|
+
prefix: "SixbSqlite",
|
|
399
|
+
})
|
|
362
400
|
}
|
|
363
401
|
|
|
364
402
|
this.db
|
|
@@ -393,11 +431,43 @@ export class SqliteAgentRunStore implements AgentRunStore {
|
|
|
393
431
|
)
|
|
394
432
|
|
|
395
433
|
this.releaseThread(run, completedAt)
|
|
434
|
+
if (agentRunMayHoldCalls(rowToRunRecord(run)))
|
|
435
|
+
this.settleApprovals(run.kind, input, completedAt)
|
|
396
436
|
|
|
397
437
|
return this.requireRun(input.projectId, input.id)
|
|
398
438
|
})()
|
|
399
439
|
}
|
|
400
440
|
|
|
441
|
+
/**
|
|
442
|
+
* A turn that pauses keeps its calls for their decision. A turn that ends any other way cancels
|
|
443
|
+
* the ones still waiting, a late one included, and never resumes on the rest. A child that fails
|
|
444
|
+
* or is cancelled takes its own pending calls with it.
|
|
445
|
+
*/
|
|
446
|
+
private settleApprovals(
|
|
447
|
+
kind: AgentRunRecord["kind"],
|
|
448
|
+
input: FinishAgentRunInput,
|
|
449
|
+
completedAt: Date
|
|
450
|
+
): void {
|
|
451
|
+
if (input.finishReason === "awaiting_approval") return
|
|
452
|
+
const turn = kind === "conversation"
|
|
453
|
+
if (!turn && input.status === "succeeded") return
|
|
454
|
+
|
|
455
|
+
this.db
|
|
456
|
+
.query(
|
|
457
|
+
`UPDATE agent_approvals SET status = 'cancelled'
|
|
458
|
+
WHERE project_id = ? AND status = 'pending' AND (run_id = ? OR caller_run_id = ?)`
|
|
459
|
+
)
|
|
460
|
+
.run(input.projectId, input.id, input.id)
|
|
461
|
+
if (!turn) return
|
|
462
|
+
this.db
|
|
463
|
+
.query(
|
|
464
|
+
`UPDATE agent_approvals SET turn_stopped_at = ?
|
|
465
|
+
WHERE project_id = ? AND run_id = ?
|
|
466
|
+
AND reported_in_message_id IS NULL AND turn_stopped_at IS NULL`
|
|
467
|
+
)
|
|
468
|
+
.run(completedAt.toISOString(), input.projectId, input.id)
|
|
469
|
+
}
|
|
470
|
+
|
|
401
471
|
async getById(params: { projectId: string; id: string }): Promise<AgentRunRecord | null> {
|
|
402
472
|
const row = this.db
|
|
403
473
|
.query("SELECT * FROM agent_runs WHERE project_id = ? AND id = ?")
|
|
@@ -432,6 +502,41 @@ export class SqliteAgentRunStore implements AgentRunStore {
|
|
|
432
502
|
})
|
|
433
503
|
}
|
|
434
504
|
|
|
505
|
+
async listThreadSubagents(
|
|
506
|
+
input: ListThreadSubagentsInput
|
|
507
|
+
): Promise<readonly SubagentRunRecord[]> {
|
|
508
|
+
// Reached through the turns of the conversation, by thread, then each turn's children by key
|
|
509
|
+
// or, for a finish reason, by `idx_agent_runs_paused_on_question`.
|
|
510
|
+
const filters: string[] = []
|
|
511
|
+
const args: SqliteValue[] = [input.projectId, input.threadId]
|
|
512
|
+
if (input.spawnKey !== undefined) {
|
|
513
|
+
filters.push("AND child.spawn_key = ?")
|
|
514
|
+
args.push(input.spawnKey)
|
|
515
|
+
}
|
|
516
|
+
if (input.finishReason !== undefined) {
|
|
517
|
+
filters.push("AND child.finish_reason = ?")
|
|
518
|
+
args.push(input.finishReason)
|
|
519
|
+
}
|
|
520
|
+
args.push(input.limit ?? 100)
|
|
521
|
+
|
|
522
|
+
const rows = this.db
|
|
523
|
+
.query(
|
|
524
|
+
`SELECT child.* FROM agent_runs turn
|
|
525
|
+
JOIN agent_runs child
|
|
526
|
+
ON child.project_id = turn.project_id AND child.parent_run_id = turn.id
|
|
527
|
+
WHERE turn.project_id = ? AND turn.thread_id = ? AND child.kind = 'subagent'
|
|
528
|
+
${filters.join(" ")}
|
|
529
|
+
ORDER BY COALESCE(turn.started_at, turn.created_at) DESC, turn.id DESC,
|
|
530
|
+
child.created_at DESC, child.id DESC
|
|
531
|
+
LIMIT ?`
|
|
532
|
+
)
|
|
533
|
+
.all(...args) as AgentRunRow[]
|
|
534
|
+
return rows.flatMap((row) => {
|
|
535
|
+
const run = rowToRunRecord(row)
|
|
536
|
+
return run.kind === "subagent" ? [run] : []
|
|
537
|
+
})
|
|
538
|
+
}
|
|
539
|
+
|
|
435
540
|
async list(input: ListAgentRunsInput): Promise<ListAgentRunsResult> {
|
|
436
541
|
if (hasEmptyStatuses(input) || input.kinds?.length === 0) {
|
|
437
542
|
return { runs: [], hasMore: false, total: 0 }
|
|
@@ -500,8 +605,84 @@ export class SqliteAgentRunStore implements AgentRunStore {
|
|
|
500
605
|
return row
|
|
501
606
|
}
|
|
502
607
|
|
|
608
|
+
/**
|
|
609
|
+
* The child's thread: its first run creates it, owned by the conversation's owner; a later run
|
|
610
|
+
* takes it while it is idle. Either way the run is its active run.
|
|
611
|
+
*/
|
|
612
|
+
private claimChildThread(
|
|
613
|
+
input: CreateSubagentRunInput,
|
|
614
|
+
parent: AgentRunRow,
|
|
615
|
+
createdAt: Date
|
|
616
|
+
): void {
|
|
617
|
+
if (!input.thread.create) {
|
|
618
|
+
const row = this.db
|
|
619
|
+
.query("SELECT * FROM agent_threads WHERE project_id = ? AND id = ?")
|
|
620
|
+
.get(input.projectId, input.thread.id) as AgentThreadRow | null
|
|
621
|
+
assertSubagentThreadTakesRun(
|
|
622
|
+
row ? rowToThreadRecord(row) : null,
|
|
623
|
+
input,
|
|
624
|
+
parent.thread_id ?? "",
|
|
625
|
+
"SixbSqlite"
|
|
626
|
+
)
|
|
627
|
+
this.db
|
|
628
|
+
.query(
|
|
629
|
+
`
|
|
630
|
+
UPDATE agent_threads
|
|
631
|
+
SET active_run_id = ?, message_count = message_count + 1, last_message_at = ?,
|
|
632
|
+
updated_at = ?
|
|
633
|
+
WHERE project_id = ? AND id = ?
|
|
634
|
+
`
|
|
635
|
+
)
|
|
636
|
+
.run(
|
|
637
|
+
input.id,
|
|
638
|
+
createdAt.toISOString(),
|
|
639
|
+
createdAt.toISOString(),
|
|
640
|
+
input.projectId,
|
|
641
|
+
input.thread.id
|
|
642
|
+
)
|
|
643
|
+
return
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const conversation = this.db
|
|
647
|
+
.query("SELECT * FROM agent_threads WHERE project_id = ? AND id = ?")
|
|
648
|
+
.get(input.projectId, parent.thread_id) as AgentThreadRow | null
|
|
649
|
+
if (!conversation) {
|
|
650
|
+
throw new AgentStorageError(
|
|
651
|
+
"thread_not_found",
|
|
652
|
+
`[SixbSqlite] Agent thread '${parent.thread_id}' not found for project '${input.projectId}'.`
|
|
653
|
+
)
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const thread = newSubagentThreadRecord(input, rowToThreadRecord(conversation), createdAt)
|
|
657
|
+
this.db
|
|
658
|
+
.query(
|
|
659
|
+
`
|
|
660
|
+
INSERT INTO agent_threads (
|
|
661
|
+
project_id, id, kind, parent_thread_id, parent_run_id, spawn_key, owner_principal_type,
|
|
662
|
+
owner_principal_id, status, active_run_id, last_message_at, message_count, created_at,
|
|
663
|
+
updated_at
|
|
664
|
+
) VALUES (?, ?, 'subagent', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
665
|
+
`
|
|
666
|
+
)
|
|
667
|
+
.run(
|
|
668
|
+
thread.projectId,
|
|
669
|
+
thread.id,
|
|
670
|
+
conversation.id,
|
|
671
|
+
input.parentRunId,
|
|
672
|
+
input.spawnKey,
|
|
673
|
+
thread.ownerPrincipal.type,
|
|
674
|
+
thread.ownerPrincipal.id,
|
|
675
|
+
thread.status,
|
|
676
|
+
input.id,
|
|
677
|
+
createdAt.toISOString(),
|
|
678
|
+
thread.messageCount,
|
|
679
|
+
createdAt.toISOString(),
|
|
680
|
+
createdAt.toISOString()
|
|
681
|
+
)
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/** A turn releases its conversation, a child run its own thread. */
|
|
503
685
|
private releaseThread(run: AgentRunRow, completedAt: Date): void {
|
|
504
|
-
if (run.kind !== "conversation") return
|
|
505
686
|
this.db
|
|
506
687
|
.query(
|
|
507
688
|
`
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite"
|
|
2
|
+
import { assertAgentApprovalModeInput } from "@sixb/core/internal/agent-run-storage-provider"
|
|
3
|
+
import {
|
|
4
|
+
type AgentApprovalMode,
|
|
5
|
+
type AgentApprovalModeStore,
|
|
6
|
+
AgentStorageError,
|
|
7
|
+
type Principal,
|
|
8
|
+
} from "@sixb/core/storage"
|
|
9
|
+
|
|
10
|
+
export class SqliteAgentApprovalModeStore implements AgentApprovalModeStore {
|
|
11
|
+
constructor(private readonly db: Database) {}
|
|
12
|
+
|
|
13
|
+
async getDefault(input: {
|
|
14
|
+
readonly projectId: string
|
|
15
|
+
readonly principal: Principal
|
|
16
|
+
}): Promise<AgentApprovalMode | null> {
|
|
17
|
+
const row = this.db
|
|
18
|
+
.query(
|
|
19
|
+
`SELECT approval_mode FROM agent_user_settings
|
|
20
|
+
WHERE project_id = ? AND principal_type = ? AND principal_id = ?`
|
|
21
|
+
)
|
|
22
|
+
.get(input.projectId, input.principal.type, input.principal.id) as {
|
|
23
|
+
approval_mode: AgentApprovalMode
|
|
24
|
+
} | null
|
|
25
|
+
return row?.approval_mode ?? null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async setDefault(input: {
|
|
29
|
+
readonly projectId: string
|
|
30
|
+
readonly principal: Principal
|
|
31
|
+
readonly mode: AgentApprovalMode
|
|
32
|
+
readonly updatedAt?: Date
|
|
33
|
+
}): Promise<void> {
|
|
34
|
+
assertAgentApprovalModeInput(input.mode, "SixbSqlite")
|
|
35
|
+
this.db
|
|
36
|
+
.query(
|
|
37
|
+
`INSERT INTO agent_user_settings (
|
|
38
|
+
project_id, principal_type, principal_id, approval_mode, updated_at
|
|
39
|
+
) VALUES (?, ?, ?, ?, ?)
|
|
40
|
+
ON CONFLICT (project_id, principal_type, principal_id)
|
|
41
|
+
DO UPDATE SET approval_mode = excluded.approval_mode, updated_at = excluded.updated_at`
|
|
42
|
+
)
|
|
43
|
+
.run(
|
|
44
|
+
input.projectId,
|
|
45
|
+
input.principal.type,
|
|
46
|
+
input.principal.id,
|
|
47
|
+
input.mode,
|
|
48
|
+
(input.updatedAt ?? new Date()).toISOString()
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async getForThread(input: {
|
|
53
|
+
readonly projectId: string
|
|
54
|
+
readonly threadId: string
|
|
55
|
+
readonly principal: Principal
|
|
56
|
+
}): Promise<AgentApprovalMode | null> {
|
|
57
|
+
const row = this.db
|
|
58
|
+
.query(
|
|
59
|
+
`SELECT approval_mode FROM agent_thread_user_settings
|
|
60
|
+
WHERE project_id = ? AND thread_id = ? AND principal_type = ? AND principal_id = ?`
|
|
61
|
+
)
|
|
62
|
+
.get(input.projectId, input.threadId, input.principal.type, input.principal.id) as {
|
|
63
|
+
approval_mode: AgentApprovalMode
|
|
64
|
+
} | null
|
|
65
|
+
return row?.approval_mode ?? null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async setForThread(input: {
|
|
69
|
+
readonly projectId: string
|
|
70
|
+
readonly threadId: string
|
|
71
|
+
readonly principal: Principal
|
|
72
|
+
readonly mode: AgentApprovalMode | null
|
|
73
|
+
readonly updatedAt?: Date
|
|
74
|
+
}): Promise<void> {
|
|
75
|
+
if (input.mode !== null) assertAgentApprovalModeInput(input.mode, "SixbSqlite")
|
|
76
|
+
const thread = this.db
|
|
77
|
+
.query("SELECT id FROM agent_threads WHERE project_id = ? AND id = ?")
|
|
78
|
+
.get(input.projectId, input.threadId)
|
|
79
|
+
if (!thread) {
|
|
80
|
+
throw new AgentStorageError(
|
|
81
|
+
"thread_not_found",
|
|
82
|
+
`[SixbSqlite] Agent thread '${input.threadId}' not found for project '${input.projectId}'.`
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (input.mode === null) {
|
|
87
|
+
this.db
|
|
88
|
+
.query(
|
|
89
|
+
`DELETE FROM agent_thread_user_settings
|
|
90
|
+
WHERE project_id = ? AND thread_id = ? AND principal_type = ? AND principal_id = ?`
|
|
91
|
+
)
|
|
92
|
+
.run(input.projectId, input.threadId, input.principal.type, input.principal.id)
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
this.db
|
|
96
|
+
.query(
|
|
97
|
+
`INSERT INTO agent_thread_user_settings (
|
|
98
|
+
project_id, thread_id, principal_type, principal_id, approval_mode, updated_at
|
|
99
|
+
) VALUES (?, ?, ?, ?, ?, ?)
|
|
100
|
+
ON CONFLICT (project_id, thread_id, principal_type, principal_id)
|
|
101
|
+
DO UPDATE SET approval_mode = excluded.approval_mode, updated_at = excluded.updated_at`
|
|
102
|
+
)
|
|
103
|
+
.run(
|
|
104
|
+
input.projectId,
|
|
105
|
+
input.threadId,
|
|
106
|
+
input.principal.type,
|
|
107
|
+
input.principal.id,
|
|
108
|
+
input.mode,
|
|
109
|
+
(input.updatedAt ?? new Date()).toISOString()
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
}
|