@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
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
-- A sub-agent works in a thread of its own: its task and its work are that thread's messages, and
|
|
2
|
+
-- each of its runs has the thread and a trigger message, as a turn does. The thread's parent is the
|
|
3
|
+
-- conversation, which every read of it goes through, and its spawning turn and key name it.
|
|
4
|
+
--
|
|
5
|
+
-- SQLite adds neither a foreign key nor a check with ALTER TABLE: both tables are rebuilt, as 033
|
|
6
|
+
-- rebuilt `agent_runs`. Their references use the final names.
|
|
7
|
+
|
|
8
|
+
-- The children admitted before this have no thread. Each gets one named as a new child's is, with
|
|
9
|
+
-- its task as the only message: what it answered stays in its parent's `wait_agent` output.
|
|
10
|
+
CREATE TEMP TABLE legacy_subagent_threads AS
|
|
11
|
+
SELECT
|
|
12
|
+
child.project_id,
|
|
13
|
+
child.id AS run_id,
|
|
14
|
+
'agt_thr_child_' || CASE
|
|
15
|
+
WHEN substr(child.id, 1, 14) = 'agt_run_child_' THEN substr(child.id, 15)
|
|
16
|
+
ELSE child.id
|
|
17
|
+
END AS thread_id,
|
|
18
|
+
'agt_msg_child_' || CASE
|
|
19
|
+
WHEN substr(child.id, 1, 14) = 'agt_run_child_' THEN substr(child.id, 15)
|
|
20
|
+
ELSE child.id
|
|
21
|
+
END AS message_id
|
|
22
|
+
FROM agent_runs AS child
|
|
23
|
+
WHERE child.kind = 'subagent' AND child.thread_id IS NULL;
|
|
24
|
+
|
|
25
|
+
CREATE TABLE agent_threads_v2 (
|
|
26
|
+
project_id TEXT NOT NULL,
|
|
27
|
+
id TEXT NOT NULL,
|
|
28
|
+
kind TEXT NOT NULL DEFAULT 'conversation' CHECK (kind IN ('conversation', 'subagent')),
|
|
29
|
+
parent_thread_id TEXT,
|
|
30
|
+
parent_run_id TEXT,
|
|
31
|
+
spawn_key TEXT,
|
|
32
|
+
owner_principal_type TEXT NOT NULL,
|
|
33
|
+
owner_principal_id TEXT NOT NULL,
|
|
34
|
+
title TEXT,
|
|
35
|
+
status TEXT NOT NULL CHECK (status IN ('active', 'archived')),
|
|
36
|
+
active_run_id TEXT,
|
|
37
|
+
last_message_at TEXT,
|
|
38
|
+
message_count INTEGER NOT NULL DEFAULT 0 CHECK (message_count >= 0),
|
|
39
|
+
created_at TEXT NOT NULL,
|
|
40
|
+
updated_at TEXT NOT NULL,
|
|
41
|
+
sandbox_params TEXT CHECK (
|
|
42
|
+
sandbox_params IS NULL
|
|
43
|
+
OR (json_valid(sandbox_params) AND json_type(sandbox_params) = 'object')
|
|
44
|
+
),
|
|
45
|
+
sandbox_state TEXT CHECK (
|
|
46
|
+
sandbox_state IS NULL
|
|
47
|
+
OR (json_valid(sandbox_state) AND json_type(sandbox_state) = 'object')
|
|
48
|
+
),
|
|
49
|
+
PRIMARY KEY (project_id, id),
|
|
50
|
+
UNIQUE (project_id, parent_run_id, spawn_key),
|
|
51
|
+
-- A conversation's sub-agent threads go with it.
|
|
52
|
+
FOREIGN KEY (project_id, parent_thread_id)
|
|
53
|
+
REFERENCES agent_threads (project_id, id)
|
|
54
|
+
ON DELETE CASCADE,
|
|
55
|
+
FOREIGN KEY (project_id, parent_run_id)
|
|
56
|
+
REFERENCES agent_runs (project_id, id)
|
|
57
|
+
ON DELETE RESTRICT,
|
|
58
|
+
CHECK (
|
|
59
|
+
(
|
|
60
|
+
kind = 'conversation'
|
|
61
|
+
AND parent_thread_id IS NULL
|
|
62
|
+
AND parent_run_id IS NULL
|
|
63
|
+
AND spawn_key IS NULL
|
|
64
|
+
)
|
|
65
|
+
OR
|
|
66
|
+
(
|
|
67
|
+
kind = 'subagent'
|
|
68
|
+
AND parent_thread_id IS NOT NULL
|
|
69
|
+
AND parent_run_id IS NOT NULL
|
|
70
|
+
AND spawn_key IS NOT NULL
|
|
71
|
+
AND sandbox_params IS NULL
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
INSERT INTO agent_threads_v2 (
|
|
77
|
+
project_id, id, owner_principal_type, owner_principal_id, title, status, active_run_id,
|
|
78
|
+
last_message_at, message_count, created_at, updated_at, sandbox_params, sandbox_state
|
|
79
|
+
)
|
|
80
|
+
SELECT
|
|
81
|
+
project_id, id, owner_principal_type, owner_principal_id, title, status, active_run_id,
|
|
82
|
+
last_message_at, message_count, created_at, updated_at, sandbox_params, sandbox_state
|
|
83
|
+
FROM agent_threads;
|
|
84
|
+
|
|
85
|
+
INSERT INTO agent_threads_v2 (
|
|
86
|
+
project_id, id, kind, parent_thread_id, parent_run_id, spawn_key, owner_principal_type,
|
|
87
|
+
owner_principal_id, status, active_run_id, last_message_at, message_count, created_at,
|
|
88
|
+
updated_at
|
|
89
|
+
)
|
|
90
|
+
SELECT
|
|
91
|
+
child.project_id,
|
|
92
|
+
legacy.thread_id,
|
|
93
|
+
'subagent',
|
|
94
|
+
parent.thread_id,
|
|
95
|
+
child.parent_run_id,
|
|
96
|
+
child.spawn_key,
|
|
97
|
+
conversation.owner_principal_type,
|
|
98
|
+
conversation.owner_principal_id,
|
|
99
|
+
'active',
|
|
100
|
+
CASE WHEN child.status IN ('queued', 'running') THEN child.id END,
|
|
101
|
+
child.created_at,
|
|
102
|
+
1,
|
|
103
|
+
child.created_at,
|
|
104
|
+
COALESCE(child.completed_at, child.created_at)
|
|
105
|
+
FROM legacy_subagent_threads AS legacy
|
|
106
|
+
JOIN agent_runs AS child ON child.project_id = legacy.project_id AND child.id = legacy.run_id
|
|
107
|
+
JOIN agent_runs AS parent
|
|
108
|
+
ON parent.project_id = child.project_id AND parent.id = child.parent_run_id
|
|
109
|
+
JOIN agent_threads AS conversation
|
|
110
|
+
ON conversation.project_id = parent.project_id AND conversation.id = parent.thread_id;
|
|
111
|
+
|
|
112
|
+
DROP TABLE agent_threads;
|
|
113
|
+
ALTER TABLE agent_threads_v2 RENAME TO agent_threads;
|
|
114
|
+
|
|
115
|
+
-- Lists read conversations only, so a conversation's children never weigh on them.
|
|
116
|
+
CREATE INDEX idx_agent_threads_project_activity
|
|
117
|
+
ON agent_threads(project_id, COALESCE(last_message_at, created_at) DESC)
|
|
118
|
+
WHERE kind = 'conversation';
|
|
119
|
+
CREATE INDEX idx_agent_threads_project_status_activity
|
|
120
|
+
ON agent_threads(project_id, status, COALESCE(last_message_at, created_at) DESC)
|
|
121
|
+
WHERE kind = 'conversation';
|
|
122
|
+
CREATE INDEX idx_agent_threads_project_owner_activity
|
|
123
|
+
ON agent_threads(
|
|
124
|
+
project_id,
|
|
125
|
+
owner_principal_type,
|
|
126
|
+
owner_principal_id,
|
|
127
|
+
COALESCE(last_message_at, created_at) DESC
|
|
128
|
+
)
|
|
129
|
+
WHERE kind = 'conversation';
|
|
130
|
+
|
|
131
|
+
-- A conversation's children, in the order they were spawned. Only a child has a parent, so the
|
|
132
|
+
-- index holds children only, and a read by `parent_thread_id` can use it.
|
|
133
|
+
CREATE INDEX idx_agent_threads_children
|
|
134
|
+
ON agent_threads(project_id, parent_thread_id, created_at, id)
|
|
135
|
+
WHERE parent_thread_id IS NOT NULL;
|
|
136
|
+
|
|
137
|
+
INSERT INTO agent_messages (
|
|
138
|
+
project_id, id, thread_id, run_id, role, seq, parts, content_version, created_at
|
|
139
|
+
)
|
|
140
|
+
SELECT
|
|
141
|
+
child.project_id,
|
|
142
|
+
legacy.message_id,
|
|
143
|
+
legacy.thread_id,
|
|
144
|
+
child.id,
|
|
145
|
+
'user',
|
|
146
|
+
1,
|
|
147
|
+
json_array(json_object('type', 'text', 'text', json_extract(child.spec, '$.task'))),
|
|
148
|
+
2,
|
|
149
|
+
child.created_at
|
|
150
|
+
FROM legacy_subagent_threads AS legacy
|
|
151
|
+
JOIN agent_runs AS child ON child.project_id = legacy.project_id AND child.id = legacy.run_id;
|
|
152
|
+
|
|
153
|
+
CREATE TABLE agent_runs_v6 (
|
|
154
|
+
project_id TEXT NOT NULL,
|
|
155
|
+
id TEXT NOT NULL,
|
|
156
|
+
execution_id TEXT NOT NULL,
|
|
157
|
+
kind TEXT NOT NULL DEFAULT 'conversation' CHECK (kind IN ('conversation', 'subagent')),
|
|
158
|
+
thread_id TEXT,
|
|
159
|
+
trigger_message_id TEXT,
|
|
160
|
+
parent_run_id TEXT,
|
|
161
|
+
spawn_key TEXT,
|
|
162
|
+
spec TEXT CHECK (spec IS NULL OR json_valid(spec)),
|
|
163
|
+
result TEXT CHECK (result IS NULL OR json_valid(result)),
|
|
164
|
+
status TEXT NOT NULL CHECK (
|
|
165
|
+
status IN ('queued', 'running', 'succeeded', 'failed', 'cancelled')
|
|
166
|
+
),
|
|
167
|
+
model_id TEXT,
|
|
168
|
+
finish_reason TEXT,
|
|
169
|
+
error TEXT CHECK (error IS NULL OR json_valid(error)),
|
|
170
|
+
diagnostics TEXT,
|
|
171
|
+
attempt INTEGER NOT NULL DEFAULT 0 CHECK (attempt >= 0),
|
|
172
|
+
execution_token TEXT,
|
|
173
|
+
execution_queue_lease_expires_at TEXT,
|
|
174
|
+
created_at TEXT NOT NULL,
|
|
175
|
+
started_at TEXT,
|
|
176
|
+
completed_at TEXT,
|
|
177
|
+
PRIMARY KEY (project_id, id),
|
|
178
|
+
UNIQUE (project_id, execution_id),
|
|
179
|
+
UNIQUE (project_id, parent_run_id, spawn_key),
|
|
180
|
+
FOREIGN KEY (project_id, execution_id)
|
|
181
|
+
REFERENCES executions (project_id, id)
|
|
182
|
+
ON DELETE RESTRICT,
|
|
183
|
+
FOREIGN KEY (project_id, parent_run_id)
|
|
184
|
+
REFERENCES agent_runs (project_id, id)
|
|
185
|
+
ON DELETE RESTRICT,
|
|
186
|
+
CHECK ((execution_token IS NULL) = (execution_queue_lease_expires_at IS NULL)),
|
|
187
|
+
CHECK (
|
|
188
|
+
(
|
|
189
|
+
kind = 'conversation'
|
|
190
|
+
AND thread_id IS NOT NULL
|
|
191
|
+
AND trigger_message_id IS NOT NULL
|
|
192
|
+
AND parent_run_id IS NULL
|
|
193
|
+
AND spawn_key IS NULL
|
|
194
|
+
AND result IS NULL
|
|
195
|
+
)
|
|
196
|
+
OR
|
|
197
|
+
(
|
|
198
|
+
kind = 'subagent'
|
|
199
|
+
AND thread_id IS NOT NULL
|
|
200
|
+
AND trigger_message_id IS NOT NULL
|
|
201
|
+
AND parent_run_id IS NOT NULL
|
|
202
|
+
AND spawn_key IS NOT NULL
|
|
203
|
+
AND spec IS NOT NULL
|
|
204
|
+
)
|
|
205
|
+
),
|
|
206
|
+
CHECK (kind <> 'subagent' OR status <> 'succeeded' OR result IS NOT NULL)
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
INSERT INTO agent_runs_v6 (
|
|
210
|
+
project_id, id, execution_id, kind, thread_id, trigger_message_id, parent_run_id, spawn_key,
|
|
211
|
+
spec, result, status, model_id, finish_reason, error, diagnostics, attempt, execution_token,
|
|
212
|
+
execution_queue_lease_expires_at, created_at, started_at, completed_at
|
|
213
|
+
)
|
|
214
|
+
SELECT
|
|
215
|
+
run.project_id, run.id, run.execution_id, run.kind,
|
|
216
|
+
COALESCE(run.thread_id, legacy.thread_id),
|
|
217
|
+
COALESCE(run.trigger_message_id, legacy.message_id),
|
|
218
|
+
run.parent_run_id, run.spawn_key, run.spec, run.result, run.status, run.model_id,
|
|
219
|
+
run.finish_reason, run.error, run.diagnostics, run.attempt, run.execution_token,
|
|
220
|
+
run.execution_queue_lease_expires_at, run.created_at, run.started_at, run.completed_at
|
|
221
|
+
FROM agent_runs AS run
|
|
222
|
+
LEFT JOIN legacy_subagent_threads AS legacy
|
|
223
|
+
ON legacy.project_id = run.project_id AND legacy.run_id = run.id;
|
|
224
|
+
|
|
225
|
+
DROP TABLE agent_runs;
|
|
226
|
+
ALTER TABLE agent_runs_v6 RENAME TO agent_runs;
|
|
227
|
+
|
|
228
|
+
CREATE INDEX idx_agent_runs_project_thread_started
|
|
229
|
+
ON agent_runs (project_id, thread_id, COALESCE(started_at, created_at) DESC, id DESC);
|
|
230
|
+
CREATE INDEX idx_agent_runs_project_status_created
|
|
231
|
+
ON agent_runs (project_id, status, created_at, id);
|
|
232
|
+
CREATE INDEX idx_agent_runs_project_parent_started
|
|
233
|
+
ON agent_runs (
|
|
234
|
+
project_id,
|
|
235
|
+
parent_run_id,
|
|
236
|
+
status,
|
|
237
|
+
COALESCE(started_at, created_at) DESC,
|
|
238
|
+
id DESC
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
DROP TABLE legacy_subagent_threads;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
-- A child that pauses on its question waits for a later turn of its parent's conversation to
|
|
2
|
+
-- answer it. Each turn reads which of the conversation's children wait so: few do, so a partial
|
|
3
|
+
-- index keeps that read small however many children the conversation had.
|
|
4
|
+
CREATE INDEX idx_agent_runs_paused_on_question
|
|
5
|
+
ON agent_runs (project_id, parent_run_id)
|
|
6
|
+
WHERE kind = 'subagent' AND finish_reason = 'awaiting_answer';
|
package/src/migrations.ts
CHANGED
|
@@ -278,6 +278,39 @@ export const sqliteStorageMigrations = defineMigrations({
|
|
|
278
278
|
sqliteSql("064-activity", activitySql),
|
|
279
279
|
sqliteSql("065-activity-timeline", activityTimelineSql),
|
|
280
280
|
sqliteSql("066-activity-interactions", activityInteractionsSql),
|
|
281
|
+
sqliteSql("067-artifact-kinds", artifactKindsSql),
|
|
282
|
+
sqliteSql("068-artifact-state", artifactStateSql),
|
|
283
|
+
sqliteSql("069-agent-thread-participants", agentThreadParticipantsSql),
|
|
284
|
+
sqliteSql("070-forked-context-checkpoints", forkedContextCheckpointsSql),
|
|
285
|
+
sqliteSql("071-unique-values", uniqueValuesSql),
|
|
286
|
+
sqliteSql("072-generated-media-usage", generatedMediaUsageSql),
|
|
287
|
+
sqliteSql("073-model-call-files", modelCallFilesSql),
|
|
288
|
+
sqliteSql("074-agent-memory", agentMemorySql),
|
|
289
|
+
sqliteSql("075-artifact-threads", artifactThreadsSql),
|
|
290
|
+
sqliteSql("076-agent-approvals", agentApprovalsSql),
|
|
291
|
+
sqliteSql("077-agent-approval-resume", agentApprovalResumeSql),
|
|
292
|
+
sqliteStep(
|
|
293
|
+
"078-subagent-threads",
|
|
294
|
+
(db) => {
|
|
295
|
+
// Bun runs a script past a statement that fails and reports only the last one's error, so
|
|
296
|
+
// a refused copy would go unnoticed and the rebuild would drop the rows: count around it.
|
|
297
|
+
const before = subagentThreadCounts(db)
|
|
298
|
+
db.run(subagentThreadsSql)
|
|
299
|
+
const after = subagentThreadCounts(db)
|
|
300
|
+
if (
|
|
301
|
+
after.runs !== before.runs ||
|
|
302
|
+
after.threads !== before.threads + before.threadless ||
|
|
303
|
+
after.messages !== before.messages + before.threadless ||
|
|
304
|
+
after.threadless !== 0
|
|
305
|
+
) {
|
|
306
|
+
throw new Error(
|
|
307
|
+
"[SixbSqliteStorage] Agent runs, threads or messages were lost while giving sub-agents their threads."
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
{ checksum: checksum(subagentThreadsSql) }
|
|
312
|
+
),
|
|
313
|
+
sqliteSql("079-subagent-questions", subagentQuestionsSql),
|
|
281
314
|
],
|
|
282
315
|
})
|
|
283
316
|
|
|
@@ -323,7 +356,7 @@ export function freshSqliteDatabase(): Database {
|
|
|
323
356
|
return Database.deserialize(freshSchema)
|
|
324
357
|
}
|
|
325
358
|
|
|
326
|
-
|
|
359
|
+
function installFreshSqliteSchema(db: Database): void {
|
|
327
360
|
for (const migration of sqliteStorageMigrations.steps) {
|
|
328
361
|
const installed = migration.up(db)
|
|
329
362
|
if (installed instanceof Promise) {
|
|
@@ -468,6 +501,26 @@ function sourceStorageCounts(db: Database): {
|
|
|
468
501
|
.get() as { readonly versions: number; readonly roots: number; readonly rows: number }
|
|
469
502
|
}
|
|
470
503
|
|
|
504
|
+
function subagentThreadCounts(db: Database): {
|
|
505
|
+
readonly runs: number
|
|
506
|
+
readonly threads: number
|
|
507
|
+
readonly messages: number
|
|
508
|
+
/** Child runs without a thread. */
|
|
509
|
+
readonly threadless: number
|
|
510
|
+
} {
|
|
511
|
+
return db
|
|
512
|
+
.query(`SELECT (SELECT count(*) FROM agent_runs) AS runs,
|
|
513
|
+
(SELECT count(*) FROM agent_threads) AS threads,
|
|
514
|
+
(SELECT count(*) FROM agent_messages) AS messages,
|
|
515
|
+
(SELECT count(*) FROM agent_runs WHERE kind = 'subagent' AND thread_id IS NULL) AS threadless`)
|
|
516
|
+
.get() as {
|
|
517
|
+
readonly runs: number
|
|
518
|
+
readonly threads: number
|
|
519
|
+
readonly messages: number
|
|
520
|
+
readonly threadless: number
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
471
524
|
/**
|
|
472
525
|
* Stored events that would not rebuild exactly from their commit and execution. Who asked and what
|
|
473
526
|
* wrote are derived from the execution exactly as 047 derived the commit's copies; both sides were
|
|
@@ -635,3 +688,24 @@ import activityTimelineSql from "./migrations/065-activity-timeline.sql" with {
|
|
|
635
688
|
import activityInteractionsSql from "./migrations/066-activity-interactions.sql" with {
|
|
636
689
|
type: "text",
|
|
637
690
|
}
|
|
691
|
+
import artifactKindsSql from "./migrations/067-artifact-kinds.sql" with { type: "text" }
|
|
692
|
+
import artifactStateSql from "./migrations/068-artifact-state.sql" with { type: "text" }
|
|
693
|
+
import agentThreadParticipantsSql from "./migrations/069-agent-thread-participants.sql" with {
|
|
694
|
+
type: "text",
|
|
695
|
+
}
|
|
696
|
+
import forkedContextCheckpointsSql from "./migrations/070-forked-context-checkpoints.sql" with {
|
|
697
|
+
type: "text",
|
|
698
|
+
}
|
|
699
|
+
import uniqueValuesSql from "./migrations/071-unique-values.sql" with { type: "text" }
|
|
700
|
+
import generatedMediaUsageSql from "./migrations/072-generated-media-usage.sql" with {
|
|
701
|
+
type: "text",
|
|
702
|
+
}
|
|
703
|
+
import modelCallFilesSql from "./migrations/073-model-call-files.sql" with { type: "text" }
|
|
704
|
+
import agentMemorySql from "./migrations/074-agent-memory.sql" with { type: "text" }
|
|
705
|
+
import artifactThreadsSql from "./migrations/075-artifact-threads.sql" with { type: "text" }
|
|
706
|
+
import agentApprovalsSql from "./migrations/076-agent-approvals.sql" with { type: "text" }
|
|
707
|
+
import agentApprovalResumeSql from "./migrations/077-agent-approval-resume.sql" with {
|
|
708
|
+
type: "text",
|
|
709
|
+
}
|
|
710
|
+
import subagentThreadsSql from "./migrations/078-subagent-threads.sql" with { type: "text" }
|
|
711
|
+
import subagentQuestionsSql from "./migrations/079-subagent-questions.sql" with { type: "text" }
|
package/src/objects/README.md
CHANGED
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
paths. The selected reader holds its traversal-budget probe and terminal statements on one WAL
|
|
5
5
|
snapshot. SQL must filter by those relations before ranking or limiting results.
|
|
6
6
|
|
|
7
|
+
A conditional scope needs no traversal: `_sixb_scope_objects` keeps an object of a conditioned type
|
|
8
|
+
while its condition matches, and `_sixb_scope_links` a link while each conditioned endpoint is such an
|
|
9
|
+
object. Both are `NOT MATERIALIZED`, so statements keep their index seeks and `LIMIT`. A statement
|
|
10
|
+
that reads one type (`start`, `list` of one type, `getByPrimaryId`) reads the stored table with that
|
|
11
|
+
type's own condition instead: the disjunction over every conditioned type would keep unconditioned
|
|
12
|
+
types off covering indexes and the condition out of reach of an index.
|
|
13
|
+
|
|
7
14
|
SQLite flattens CTEs. Expose identity columns from the grant/visible relation and put authorized
|
|
8
15
|
parents before indexed lookups with `CROSS JOIN`; exposing stored identities can cause a full
|
|
9
16
|
grant scan per traversed edge. The query-plan regression test checks those lookups.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite"
|
|
2
|
+
import type {
|
|
3
|
+
CompiledConditionalObjectReadScope,
|
|
4
|
+
ObjectQueryCapabilities,
|
|
5
|
+
ObjectReadStorage,
|
|
6
|
+
} from "@sixb/core/storage"
|
|
7
|
+
import { compileSqliteConditionalObjectReadSource } from "./read-scope"
|
|
8
|
+
import { createSqliteScopedReader } from "./scoped-reader"
|
|
9
|
+
|
|
10
|
+
/** Read objects and links through relations that apply the scope's conditions as they are read. */
|
|
11
|
+
export function createSqliteConditionalReader(
|
|
12
|
+
db: Database,
|
|
13
|
+
params: {
|
|
14
|
+
projectId: string
|
|
15
|
+
scope: CompiledConditionalObjectReadScope
|
|
16
|
+
},
|
|
17
|
+
queryCapabilities: () => ObjectQueryCapabilities
|
|
18
|
+
): ObjectReadStorage {
|
|
19
|
+
return createSqliteScopedReader(
|
|
20
|
+
db,
|
|
21
|
+
{
|
|
22
|
+
projectId: params.projectId,
|
|
23
|
+
source: compileSqliteConditionalObjectReadSource(params.projectId, params.scope),
|
|
24
|
+
},
|
|
25
|
+
queryCapabilities
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -118,6 +118,12 @@ export interface SqliteObjectQuerySource {
|
|
|
118
118
|
readonly objectsTable: string
|
|
119
119
|
readonly linksTable: string
|
|
120
120
|
readonly objectPropertyPermissionsTable?: string
|
|
121
|
+
/**
|
|
122
|
+
* Where to read one concrete type's objects, when the source can say it more precisely than
|
|
123
|
+
* `objectsTable`: a table and that type's own filter over its `properties`. A statement that
|
|
124
|
+
* reads a single type then holds the filter as a top-level condition, which an index can serve.
|
|
125
|
+
*/
|
|
126
|
+
objectsOfType?(objectTypeId: string): SqliteTypedObjects
|
|
121
127
|
/** Wrap a terminal SELECT without its own WITH clause; the selected source owns the only WITH. */
|
|
122
128
|
wrapStatement(
|
|
123
129
|
sql: string,
|
|
@@ -129,6 +135,26 @@ export interface SqliteObjectQuerySource {
|
|
|
129
135
|
wrapQuery(query: CompiledObjectQuery): CompiledObjectQuery
|
|
130
136
|
}
|
|
131
137
|
|
|
138
|
+
export interface SqliteTypedObjects {
|
|
139
|
+
readonly table: string
|
|
140
|
+
readonly filter?: { readonly sql: string; readonly args: readonly SqliteValue[] }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** The table and `AND ...` filter a statement reads one type's objects with. */
|
|
144
|
+
export function sqliteObjectsOfType(
|
|
145
|
+
source: SqliteObjectQuerySource,
|
|
146
|
+
objectTypeId: string
|
|
147
|
+
): { table: string; filter: string; args: readonly SqliteValue[] } {
|
|
148
|
+
const typed = source.objectsOfType?.(objectTypeId)
|
|
149
|
+
if (!typed) return { table: source.objectsTable, filter: "", args: [] }
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
table: typed.table,
|
|
153
|
+
filter: typed.filter ? `AND (${typed.filter.sql})` : "",
|
|
154
|
+
args: typed.filter?.args ?? [],
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
132
158
|
export const DEFAULT_OBJECT_QUERY_SOURCE: SqliteObjectQuerySource = {
|
|
133
159
|
objectsTable: "objects",
|
|
134
160
|
linksTable: "links",
|
|
@@ -298,19 +324,20 @@ function compileStart(
|
|
|
298
324
|
}
|
|
299
325
|
|
|
300
326
|
const order = compileOrder(identityOrderFields())
|
|
327
|
+
const objects = sqliteObjectsOfType(ctx.source, query.objectTypeId)
|
|
301
328
|
const sql = `
|
|
302
329
|
SELECT *, properties AS _cursor_properties
|
|
303
|
-
FROM ${
|
|
304
|
-
WHERE project_id = ? AND object_type_id = ?
|
|
330
|
+
FROM ${objects.table}
|
|
331
|
+
WHERE project_id = ? AND object_type_id = ? ${objects.filter}
|
|
305
332
|
ORDER BY ${order.sql}
|
|
306
333
|
`
|
|
307
|
-
const args = [projectId, query.objectTypeId, ...order.args]
|
|
334
|
+
const args = [projectId, query.objectTypeId, ...objects.args, ...order.args]
|
|
308
335
|
|
|
309
336
|
return {
|
|
310
337
|
sql,
|
|
311
338
|
args,
|
|
312
|
-
totalSql: `SELECT COUNT(*) as total FROM ${
|
|
313
|
-
totalArgs: [projectId, query.objectTypeId],
|
|
339
|
+
totalSql: `SELECT COUNT(*) as total FROM ${objects.table} WHERE project_id = ? AND object_type_id = ? ${objects.filter}`,
|
|
340
|
+
totalArgs: [projectId, query.objectTypeId, ...objects.args],
|
|
314
341
|
order,
|
|
315
342
|
hasMore: () => false,
|
|
316
343
|
trimRows: identityRows,
|
|
@@ -974,7 +1001,8 @@ export function sqliteJsonEachValue(alias: string): string {
|
|
|
974
1001
|
END`
|
|
975
1002
|
}
|
|
976
1003
|
|
|
977
|
-
|
|
1004
|
+
/** Compile a predicate over the unqualified `properties` column of the relation it filters. */
|
|
1005
|
+
export function compilePredicate(predicate: ObjectQueryPredicate): CompiledPredicate {
|
|
978
1006
|
if (
|
|
979
1007
|
"scalarKind" in predicate &&
|
|
980
1008
|
predicate.scalarKind === "decimal" &&
|
|
@@ -1004,6 +1032,8 @@ function compilePredicate(predicate: ObjectQueryPredicate): CompiledPredicate {
|
|
|
1004
1032
|
const item = compilePredicate(predicate.item)
|
|
1005
1033
|
return negatePredicate(item)
|
|
1006
1034
|
}
|
|
1035
|
+
case "constant":
|
|
1036
|
+
return { sql: predicate.value ? "1" : "0", args: [] }
|
|
1007
1037
|
case "eq":
|
|
1008
1038
|
return predicate.scalarKind === "userRef"
|
|
1009
1039
|
? compileUserRefInPredicate(predicate.propertyId, [predicate.value])
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
CompiledConditionalObjectReadScope,
|
|
3
|
+
CompiledSelectedObjectReadScope,
|
|
4
|
+
} from "@sixb/core/storage"
|
|
2
5
|
import {
|
|
3
6
|
type CompiledObjectQuery,
|
|
7
|
+
compilePredicate,
|
|
4
8
|
type SqliteObjectQuerySource,
|
|
5
9
|
type SqliteValue,
|
|
6
10
|
sqliteJsonEachValue,
|
|
@@ -35,16 +39,75 @@ export function compileSqliteSelectedObjectReadSource(
|
|
|
35
39
|
objects: scope.objects,
|
|
36
40
|
steps: scope.steps,
|
|
37
41
|
})
|
|
38
|
-
const
|
|
42
|
+
const source = scopedQuerySource(compileSelectedReadScopeCte(scopeJson, projectId))
|
|
43
|
+
|
|
44
|
+
return Object.freeze({
|
|
45
|
+
...source,
|
|
46
|
+
objectPropertyPermissionsTable: "_sixb_scope_object_permissions",
|
|
47
|
+
traversalProbe: source.wrapStatement(
|
|
48
|
+
`
|
|
49
|
+
SELECT COUNT(*) AS total
|
|
50
|
+
FROM (
|
|
51
|
+
SELECT 1 AS traversal_fact
|
|
52
|
+
FROM _sixb_scope_root_facts
|
|
53
|
+
|
|
54
|
+
UNION ALL
|
|
55
|
+
|
|
56
|
+
SELECT 1 AS traversal_fact
|
|
57
|
+
FROM _sixb_scope_reached_links
|
|
58
|
+
|
|
59
|
+
LIMIT ?
|
|
60
|
+
) AS bounded_traversal_facts
|
|
61
|
+
`,
|
|
62
|
+
[BigInt(maxTraversalFacts) + 1n]
|
|
63
|
+
),
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Prepare a conditional scope as SQLite relations that filter stored rows as they are read.
|
|
69
|
+
*
|
|
70
|
+
* Both relations are NOT MATERIALIZED, so SQLite flattens them into each statement: its filters,
|
|
71
|
+
* index seeks and LIMIT reach the stored tables, and the conditions become one more filter. An
|
|
72
|
+
* object of a conditioned type is visible while its condition matches; a link while each of its
|
|
73
|
+
* conditioned endpoints is a visible object.
|
|
74
|
+
*/
|
|
75
|
+
export function compileSqliteConditionalObjectReadSource(
|
|
76
|
+
projectId: string,
|
|
77
|
+
scope: CompiledConditionalObjectReadScope
|
|
78
|
+
): SqliteObjectQuerySource {
|
|
79
|
+
const filters = new Map(
|
|
80
|
+
scope.conditions.map((condition) => [
|
|
81
|
+
condition.objectTypeId,
|
|
82
|
+
compilePredicate(condition.predicate),
|
|
83
|
+
])
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
return Object.freeze({
|
|
87
|
+
...scopedQuerySource(compileConditionalReadScopeCte(projectId, filters)),
|
|
88
|
+
// One type's visible objects are the stored ones its own condition matches.
|
|
89
|
+
objectsOfType: (objectTypeId: string) => {
|
|
90
|
+
const filter = filters.get(objectTypeId)
|
|
91
|
+
return filter ? { table: "objects", filter } : { table: "objects" }
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface CompiledReadScopeCte {
|
|
97
|
+
readonly sql: string
|
|
98
|
+
readonly args: readonly SqliteValue[]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Prefix every statement of a query with the scope's relations, ahead of its own arguments. */
|
|
102
|
+
function scopedQuerySource(compiled: CompiledReadScopeCte): SqliteObjectQuerySource {
|
|
39
103
|
const wrapStatement = (sql: string, args: readonly SqliteValue[] = []) => ({
|
|
40
104
|
sql: `${compiled.sql}\n${sql}`,
|
|
41
105
|
args: [...compiled.args, ...args],
|
|
42
106
|
})
|
|
43
107
|
|
|
44
|
-
return
|
|
108
|
+
return {
|
|
45
109
|
objectsTable: "_sixb_scope_objects",
|
|
46
110
|
linksTable: "_sixb_scope_links",
|
|
47
|
-
objectPropertyPermissionsTable: "_sixb_scope_object_permissions",
|
|
48
111
|
wrapStatement,
|
|
49
112
|
wrapQuery: (query: CompiledObjectQuery): CompiledObjectQuery => ({
|
|
50
113
|
...query,
|
|
@@ -63,29 +126,83 @@ export function compileSqliteSelectedObjectReadSource(
|
|
|
63
126
|
}
|
|
64
127
|
: {}),
|
|
65
128
|
}),
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
SELECT COUNT(*) AS total
|
|
69
|
-
FROM (
|
|
70
|
-
SELECT 1 AS traversal_fact
|
|
71
|
-
FROM _sixb_scope_root_facts
|
|
72
|
-
|
|
73
|
-
UNION ALL
|
|
129
|
+
}
|
|
130
|
+
}
|
|
74
131
|
|
|
75
|
-
|
|
76
|
-
|
|
132
|
+
/** Conditions keyed by their concrete type, each compiled over that type's `properties`. */
|
|
133
|
+
type ConditionFilters = ReadonlyMap<
|
|
134
|
+
string,
|
|
135
|
+
{ readonly sql: string; readonly args: readonly SqliteValue[] }
|
|
136
|
+
>
|
|
77
137
|
|
|
78
|
-
|
|
79
|
-
|
|
138
|
+
function compileConditionalReadScopeCte(
|
|
139
|
+
projectId: string,
|
|
140
|
+
filters: ConditionFilters
|
|
141
|
+
): CompiledReadScopeCte {
|
|
142
|
+
if (filters.size === 0) {
|
|
143
|
+
return {
|
|
144
|
+
sql: `
|
|
145
|
+
WITH
|
|
146
|
+
_sixb_scope_objects AS NOT MATERIALIZED (
|
|
147
|
+
SELECT * FROM objects WHERE project_id = ?
|
|
148
|
+
),
|
|
149
|
+
_sixb_scope_links AS NOT MATERIALIZED (
|
|
150
|
+
SELECT * FROM links WHERE project_id = ?
|
|
151
|
+
)
|
|
80
152
|
`,
|
|
81
|
-
[
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
}
|
|
153
|
+
args: [projectId, projectId],
|
|
154
|
+
}
|
|
155
|
+
}
|
|
85
156
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
157
|
+
const conditionedTypeArgs = [...filters.keys()]
|
|
158
|
+
const conditionedTypes = conditionedTypeArgs.map(() => "?").join(", ")
|
|
159
|
+
|
|
160
|
+
const matches: string[] = []
|
|
161
|
+
const matchArgs: SqliteValue[] = []
|
|
162
|
+
for (const [objectTypeId, filter] of filters) {
|
|
163
|
+
matches.push(`(object_type_id = ? AND ${filter.sql})`)
|
|
164
|
+
matchArgs.push(objectTypeId, ...filter.args)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const visibleEndpoint = (end: "source" | "target") => `
|
|
168
|
+
(
|
|
169
|
+
stored.${end}_type_id NOT IN (${conditionedTypes})
|
|
170
|
+
OR EXISTS (
|
|
171
|
+
SELECT 1
|
|
172
|
+
FROM _sixb_scope_objects AS endpoint
|
|
173
|
+
WHERE endpoint.project_id = stored.project_id
|
|
174
|
+
AND endpoint.object_type_id = stored.${end}_type_id
|
|
175
|
+
AND endpoint.primary_id = stored.${end}_id
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
`
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
sql: `
|
|
182
|
+
WITH
|
|
183
|
+
_sixb_scope_objects AS NOT MATERIALIZED (
|
|
184
|
+
SELECT *
|
|
185
|
+
FROM objects
|
|
186
|
+
WHERE project_id = ?
|
|
187
|
+
AND (object_type_id NOT IN (${conditionedTypes}) OR ${matches.join(" OR ")})
|
|
188
|
+
),
|
|
189
|
+
_sixb_scope_links AS NOT MATERIALIZED (
|
|
190
|
+
SELECT stored.*
|
|
191
|
+
FROM links AS stored
|
|
192
|
+
WHERE stored.project_id = ?
|
|
193
|
+
AND ${visibleEndpoint("source")}
|
|
194
|
+
AND ${visibleEndpoint("target")}
|
|
195
|
+
)
|
|
196
|
+
`,
|
|
197
|
+
args: [
|
|
198
|
+
projectId,
|
|
199
|
+
...conditionedTypeArgs,
|
|
200
|
+
...matchArgs,
|
|
201
|
+
projectId,
|
|
202
|
+
...conditionedTypeArgs,
|
|
203
|
+
...conditionedTypeArgs,
|
|
204
|
+
],
|
|
205
|
+
}
|
|
89
206
|
}
|
|
90
207
|
|
|
91
208
|
function compileSelectedReadScopeCte(scopeJson: string, projectId: string): CompiledReadScopeCte {
|