@pasko70/pibo 1.13.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apps/chat/static-assets.js +3 -1
- package/dist/apps/chat/web-app.js +10 -0
- package/dist/apps/chat-ui/assets/{dist-B_8EcoB6.js → dist-1eQpSfYj.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DVPXynJE.js → dist-BG1J2hGI.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-UYBI2F8E.js → dist-BIwXuAMQ.js} +1 -1
- package/dist/apps/{context-files-ui/assets/dist-D6pYe6d_.js → chat-ui/assets/dist-BeCHnL7P.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CVLKu7NV.js → dist-CtsdsIpj.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DugRsLaG.js → dist-DfdQQObC.js} +1 -1
- package/dist/apps/{context-files-ui/assets/dist-BFRDpzcv.js → chat-ui/assets/dist-DmqwP_tM.js} +1 -1
- package/dist/apps/{context-files-ui/assets/dist-B5mpygOZ.js → chat-ui/assets/dist-LWXXqiGT.js} +1 -1
- package/dist/apps/{context-files-ui/assets/dist-B_zXPNMb.js → chat-ui/assets/dist-hoDORUlL.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-XmG4qG-A.js → dist-ur4yXrlj.js} +1 -1
- package/dist/apps/chat-ui/assets/index-CoCl8FG8.css +1 -0
- package/dist/apps/chat-ui/assets/index-DLNTzhdh.js +237 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-R_7kgvvD.js +41 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/apps/{chat-ui/assets/dist-CnX234jg.js → context-files-ui/assets/dist-B7gXtTZC.js} +1 -1
- package/dist/apps/{chat-ui/assets/dist-CELRUR8b.js → context-files-ui/assets/dist-BD262Ouw.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-Em3LWmNi.js → dist-BKuGdcnX.js} +1 -1
- package/dist/apps/{chat-ui/assets/dist-CVmpu9FK.js → context-files-ui/assets/dist-C-Jm45P4.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-DM6yvMZv.js → dist-CHdx1Jri.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-BLI6sQZG.js → dist-CULMH-fB.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-CLrlJjPP.js → dist-Cjq6RWEt.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-D8ppb7gk.js → dist-CntqFySz.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-SEP88Pg7.js → dist-DxEGebCR.js} +1 -1
- package/dist/apps/{chat-ui/assets/dist-KOXty1JB.js → context-files-ui/assets/dist-kPuQud7t.js} +1 -1
- package/dist/apps/context-files-ui/assets/index-DZWsbuvg.js +202 -0
- package/dist/apps/context-files-ui/index.html +1 -1
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.14.0.vsix +0 -0
- package/dist/cli-session/localSessionSource.js +2 -0
- package/dist/core/session-router.js +3 -0
- package/dist/gateway/client.js +8 -0
- package/dist/gateway/server.js +18 -2
- package/dist/gateway/web.js +1 -0
- package/dist/local/extension.js +2 -0
- package/dist/loops/plugin.js +34 -1
- package/dist/loops/service.js +85 -0
- package/dist/mcp/config-command.js +5 -3
- package/dist/plugins/context-files-store.js +287 -101
- package/dist/plugins/context-files.js +102 -154
- package/dist/shared/trace-live-reducer.js +6 -1
- package/dist/web/channel.js +105 -16
- package/dist/web/http.js +14 -2
- package/package.json +11 -7
- package/dist/apps/chat-ui/assets/index-DhFQ1th-.js +0 -237
- package/dist/apps/chat-ui/assets/index-hjygk9Ol.css +0 -1
- package/dist/apps/chat-vscode-web/assets/index-1wWonouX.js +0 -41
- package/dist/apps/context-files-ui/assets/index-C7X2UvWe.js +0 -202
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.13.0.vsix +0 -0
|
@@ -1,7 +1,49 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
4
|
import { DatabaseSync } from "node:sqlite";
|
|
5
|
+
const CONTEXT_FILE_SCHEMA_VERSION_KEY = "schema-version";
|
|
6
|
+
const CONTEXT_FILE_SCHEMA_VERSION = "2";
|
|
7
|
+
const MANUAL_REVISION_MIGRATION_KEY = "manual-revisions-v2";
|
|
8
|
+
const CONTEXT_FILE_BASE_COLUMNS = [
|
|
9
|
+
"key",
|
|
10
|
+
"label",
|
|
11
|
+
"managed_path",
|
|
12
|
+
"scope",
|
|
13
|
+
"source_type",
|
|
14
|
+
"agent_profile_name",
|
|
15
|
+
"active_revision_id",
|
|
16
|
+
"source_ref",
|
|
17
|
+
"source_hash",
|
|
18
|
+
"created_at",
|
|
19
|
+
"updated_at",
|
|
20
|
+
];
|
|
21
|
+
const CONTEXT_FILE_COLUMNS = [
|
|
22
|
+
...CONTEXT_FILE_BASE_COLUMNS,
|
|
23
|
+
"working_content",
|
|
24
|
+
"source_content",
|
|
25
|
+
];
|
|
26
|
+
const AUTOMATIC_REVISION_COLUMNS = [
|
|
27
|
+
"id",
|
|
28
|
+
"context_file_key",
|
|
29
|
+
"kind",
|
|
30
|
+
"content_hash",
|
|
31
|
+
"content",
|
|
32
|
+
"created_at",
|
|
33
|
+
"actor_id",
|
|
34
|
+
"based_on_revision_id",
|
|
35
|
+
"source_hash_at_creation",
|
|
36
|
+
"note",
|
|
37
|
+
];
|
|
38
|
+
const MANUAL_REVISION_COLUMNS = [
|
|
39
|
+
"id",
|
|
40
|
+
"context_file_key",
|
|
41
|
+
"name",
|
|
42
|
+
"content_hash",
|
|
43
|
+
"content",
|
|
44
|
+
"created_at",
|
|
45
|
+
"actor_id",
|
|
46
|
+
];
|
|
5
47
|
function readLegacyManagedStore(storePath) {
|
|
6
48
|
if (!existsSync(storePath))
|
|
7
49
|
return { files: [] };
|
|
@@ -40,9 +82,10 @@ function fileRowToRecord(row) {
|
|
|
40
82
|
scope: row.scope,
|
|
41
83
|
sourceType: row.source_type,
|
|
42
84
|
...(row.agent_profile_name ? { agentProfileName: row.agent_profile_name } : {}),
|
|
43
|
-
|
|
85
|
+
workingContent: row.working_content ?? "",
|
|
44
86
|
...(row.source_ref ? { sourceRef: row.source_ref } : {}),
|
|
45
87
|
...(row.source_hash ? { sourceHash: row.source_hash } : {}),
|
|
88
|
+
...(row.source_content !== null ? { sourceContent: row.source_content } : {}),
|
|
46
89
|
createdAt: row.created_at,
|
|
47
90
|
updatedAt: row.updated_at,
|
|
48
91
|
};
|
|
@@ -51,14 +94,11 @@ function revisionRowToRecord(row) {
|
|
|
51
94
|
return {
|
|
52
95
|
id: row.id,
|
|
53
96
|
contextFileKey: row.context_file_key,
|
|
54
|
-
|
|
97
|
+
name: row.name,
|
|
55
98
|
contentHash: row.content_hash,
|
|
56
99
|
content: row.content,
|
|
57
100
|
createdAt: row.created_at,
|
|
58
101
|
...(row.actor_id ? { actorId: row.actor_id } : {}),
|
|
59
|
-
...(row.based_on_revision_id ? { basedOnRevisionId: row.based_on_revision_id } : {}),
|
|
60
|
-
...(row.source_hash_at_creation ? { sourceHashAtCreation: row.source_hash_at_creation } : {}),
|
|
61
|
-
...(row.note ? { note: row.note } : {}),
|
|
62
102
|
};
|
|
63
103
|
}
|
|
64
104
|
export class ContextFileMetadataStore {
|
|
@@ -71,51 +111,22 @@ export class ContextFileMetadataStore {
|
|
|
71
111
|
this.db = new DatabaseSync(resolvedPath);
|
|
72
112
|
this.db.exec("PRAGMA busy_timeout = 5000");
|
|
73
113
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
74
|
-
this.
|
|
75
|
-
|
|
76
|
-
key TEXT PRIMARY KEY,
|
|
77
|
-
label TEXT NOT NULL,
|
|
78
|
-
managed_path TEXT NOT NULL,
|
|
79
|
-
scope TEXT NOT NULL,
|
|
80
|
-
source_type TEXT NOT NULL,
|
|
81
|
-
agent_profile_name TEXT,
|
|
82
|
-
active_revision_id TEXT,
|
|
83
|
-
source_ref TEXT,
|
|
84
|
-
source_hash TEXT,
|
|
85
|
-
created_at TEXT NOT NULL,
|
|
86
|
-
updated_at TEXT NOT NULL
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
CREATE INDEX IF NOT EXISTS idx_context_files_scope
|
|
90
|
-
ON context_files(scope, updated_at);
|
|
91
|
-
|
|
92
|
-
CREATE TABLE IF NOT EXISTS context_file_revisions (
|
|
93
|
-
id TEXT PRIMARY KEY,
|
|
94
|
-
context_file_key TEXT NOT NULL,
|
|
95
|
-
kind TEXT NOT NULL,
|
|
96
|
-
content_hash TEXT NOT NULL,
|
|
97
|
-
content TEXT NOT NULL,
|
|
98
|
-
created_at TEXT NOT NULL,
|
|
99
|
-
actor_id TEXT,
|
|
100
|
-
based_on_revision_id TEXT,
|
|
101
|
-
source_hash_at_creation TEXT,
|
|
102
|
-
note TEXT
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
CREATE INDEX IF NOT EXISTS idx_context_file_revisions_key
|
|
106
|
-
ON context_file_revisions(context_file_key, created_at DESC);
|
|
107
|
-
`);
|
|
114
|
+
this.migrateStorage();
|
|
115
|
+
this.assertCompatibleSchema();
|
|
108
116
|
this.migrateLegacyStore();
|
|
109
117
|
}
|
|
110
118
|
listFiles() {
|
|
119
|
+
this.assertCompatibleSchema();
|
|
111
120
|
const rows = this.db.prepare("SELECT * FROM context_files ORDER BY updated_at DESC").all();
|
|
112
121
|
return rows.map(fileRowToRecord);
|
|
113
122
|
}
|
|
114
123
|
getFile(key) {
|
|
124
|
+
this.assertCompatibleSchema();
|
|
115
125
|
const row = this.db.prepare("SELECT * FROM context_files WHERE key = ?").get(key);
|
|
116
126
|
return row ? fileRowToRecord(row) : undefined;
|
|
117
127
|
}
|
|
118
128
|
createFile(input) {
|
|
129
|
+
this.assertCompatibleSchema();
|
|
119
130
|
const createdAt = input.createdAt ?? new Date().toISOString();
|
|
120
131
|
const updatedAt = input.updatedAt ?? createdAt;
|
|
121
132
|
this.db
|
|
@@ -128,19 +139,22 @@ export class ContextFileMetadataStore {
|
|
|
128
139
|
source_type,
|
|
129
140
|
agent_profile_name,
|
|
130
141
|
active_revision_id,
|
|
142
|
+
working_content,
|
|
131
143
|
source_ref,
|
|
132
144
|
source_hash,
|
|
145
|
+
source_content,
|
|
133
146
|
created_at,
|
|
134
147
|
updated_at
|
|
135
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
148
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
136
149
|
`)
|
|
137
|
-
.run(input.key, input.label, input.managedPath, input.scope, "managed", input.agentProfileName ?? null, null, input.sourceRef ?? null, input.sourceHash ?? null, createdAt, updatedAt);
|
|
150
|
+
.run(input.key, input.label, input.managedPath, input.scope, "managed", input.agentProfileName ?? null, null, input.workingContent, input.sourceRef ?? null, input.sourceHash ?? null, input.sourceContent ?? null, createdAt, updatedAt);
|
|
138
151
|
const record = this.getFile(input.key);
|
|
139
152
|
if (!record)
|
|
140
153
|
throw new Error(`Failed to create context file record "${input.key}"`);
|
|
141
154
|
return record;
|
|
142
155
|
}
|
|
143
156
|
updateFile(input) {
|
|
157
|
+
this.assertCompatibleSchema();
|
|
144
158
|
this.db
|
|
145
159
|
.prepare(`
|
|
146
160
|
UPDATE context_files SET
|
|
@@ -149,22 +163,26 @@ export class ContextFileMetadataStore {
|
|
|
149
163
|
scope = ?,
|
|
150
164
|
source_type = ?,
|
|
151
165
|
agent_profile_name = ?,
|
|
152
|
-
active_revision_id =
|
|
166
|
+
active_revision_id = NULL,
|
|
167
|
+
working_content = ?,
|
|
153
168
|
source_ref = ?,
|
|
154
169
|
source_hash = ?,
|
|
170
|
+
source_content = ?,
|
|
155
171
|
created_at = ?,
|
|
156
172
|
updated_at = ?
|
|
157
173
|
WHERE key = ?
|
|
158
174
|
`)
|
|
159
|
-
.run(input.label, input.managedPath, input.scope, "managed", input.agentProfileName ?? null, input.
|
|
175
|
+
.run(input.label, input.managedPath, input.scope, "managed", input.agentProfileName ?? null, input.workingContent, input.sourceRef ?? null, input.sourceHash ?? null, input.sourceContent ?? null, input.createdAt, input.updatedAt, input.key);
|
|
160
176
|
const record = this.getFile(input.key);
|
|
161
177
|
if (!record)
|
|
162
178
|
throw new Error(`Failed to update context file record "${input.key}"`);
|
|
163
179
|
return record;
|
|
164
180
|
}
|
|
165
181
|
deleteFile(key) {
|
|
182
|
+
this.assertCompatibleSchema();
|
|
166
183
|
this.db.exec("BEGIN");
|
|
167
184
|
try {
|
|
185
|
+
this.db.prepare("DELETE FROM context_file_manual_revisions WHERE context_file_key = ?").run(key);
|
|
168
186
|
this.db.prepare("DELETE FROM context_file_revisions WHERE context_file_key = ?").run(key);
|
|
169
187
|
this.db.prepare("DELETE FROM context_files WHERE key = ?").run(key);
|
|
170
188
|
this.db.exec("COMMIT");
|
|
@@ -175,62 +193,250 @@ export class ContextFileMetadataStore {
|
|
|
175
193
|
}
|
|
176
194
|
}
|
|
177
195
|
appendRevision(input) {
|
|
196
|
+
this.assertCompatibleSchema();
|
|
178
197
|
const id = `rev_${randomUUID()}`;
|
|
179
198
|
const createdAt = input.createdAt ?? new Date().toISOString();
|
|
180
199
|
this.db
|
|
181
200
|
.prepare(`
|
|
182
|
-
INSERT INTO
|
|
201
|
+
INSERT INTO context_file_manual_revisions (
|
|
183
202
|
id,
|
|
184
203
|
context_file_key,
|
|
185
|
-
|
|
204
|
+
name,
|
|
186
205
|
content_hash,
|
|
187
206
|
content,
|
|
188
207
|
created_at,
|
|
189
|
-
actor_id
|
|
190
|
-
|
|
191
|
-
source_hash_at_creation,
|
|
192
|
-
note
|
|
193
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
208
|
+
actor_id
|
|
209
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
194
210
|
`)
|
|
195
|
-
.run(id, input.contextFileKey, input.
|
|
211
|
+
.run(id, input.contextFileKey, input.name, input.contentHash, input.content, createdAt, input.actorId ?? null);
|
|
196
212
|
const revision = this.getRevision(id);
|
|
197
213
|
if (!revision)
|
|
198
214
|
throw new Error(`Failed to create context file revision "${id}"`);
|
|
199
215
|
return revision;
|
|
200
216
|
}
|
|
201
217
|
getRevision(id) {
|
|
202
|
-
|
|
218
|
+
this.assertCompatibleSchema();
|
|
219
|
+
const row = this.db.prepare("SELECT * FROM context_file_manual_revisions WHERE id = ?").get(id);
|
|
203
220
|
return row ? revisionRowToRecord(row) : undefined;
|
|
204
221
|
}
|
|
205
222
|
listRevisions(contextFileKey) {
|
|
223
|
+
this.assertCompatibleSchema();
|
|
206
224
|
const rows = this.db
|
|
207
|
-
.prepare("SELECT * FROM
|
|
225
|
+
.prepare("SELECT * FROM context_file_manual_revisions WHERE context_file_key = ? ORDER BY created_at DESC")
|
|
208
226
|
.all(contextFileKey);
|
|
209
227
|
return rows.map(revisionRowToRecord);
|
|
210
228
|
}
|
|
211
|
-
findLatestSourceSnapshot(contextFileKey, sourceHash) {
|
|
212
|
-
const row = sourceHash
|
|
213
|
-
? this.db
|
|
214
|
-
.prepare(`
|
|
215
|
-
SELECT * FROM context_file_revisions
|
|
216
|
-
WHERE context_file_key = ? AND kind = 'source-snapshot' AND content_hash = ?
|
|
217
|
-
ORDER BY created_at DESC
|
|
218
|
-
LIMIT 1
|
|
219
|
-
`)
|
|
220
|
-
.get(contextFileKey, sourceHash)
|
|
221
|
-
: this.db
|
|
222
|
-
.prepare(`
|
|
223
|
-
SELECT * FROM context_file_revisions
|
|
224
|
-
WHERE context_file_key = ? AND kind = 'source-snapshot'
|
|
225
|
-
ORDER BY created_at DESC
|
|
226
|
-
LIMIT 1
|
|
227
|
-
`)
|
|
228
|
-
.get(contextFileKey);
|
|
229
|
-
return row ? revisionRowToRecord(row) : undefined;
|
|
230
|
-
}
|
|
231
229
|
close() {
|
|
232
230
|
this.db.close();
|
|
233
231
|
}
|
|
232
|
+
columnsFor(table) {
|
|
233
|
+
if (!this.tableExists(table))
|
|
234
|
+
return new Set();
|
|
235
|
+
return new Set(this.db.prepare(`PRAGMA table_info(${table})`).all().map((column) => column.name));
|
|
236
|
+
}
|
|
237
|
+
hasColumns(table, required) {
|
|
238
|
+
const columns = this.columnsFor(table);
|
|
239
|
+
return required.every((column) => columns.has(column));
|
|
240
|
+
}
|
|
241
|
+
ensureColumn(table, column, type) {
|
|
242
|
+
if (this.columnsFor(table).has(column))
|
|
243
|
+
return;
|
|
244
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${type}`);
|
|
245
|
+
}
|
|
246
|
+
tableExists(table) {
|
|
247
|
+
return Boolean(this.db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(table));
|
|
248
|
+
}
|
|
249
|
+
ensureAutomaticRevisionTable() {
|
|
250
|
+
this.db.exec(`
|
|
251
|
+
CREATE TABLE IF NOT EXISTS context_file_revisions (
|
|
252
|
+
id TEXT PRIMARY KEY,
|
|
253
|
+
context_file_key TEXT NOT NULL,
|
|
254
|
+
kind TEXT NOT NULL,
|
|
255
|
+
content_hash TEXT NOT NULL,
|
|
256
|
+
content TEXT NOT NULL,
|
|
257
|
+
created_at TEXT NOT NULL,
|
|
258
|
+
actor_id TEXT,
|
|
259
|
+
based_on_revision_id TEXT,
|
|
260
|
+
source_hash_at_creation TEXT,
|
|
261
|
+
note TEXT
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
CREATE INDEX IF NOT EXISTS idx_context_file_revisions_key
|
|
265
|
+
ON context_file_revisions(context_file_key, created_at DESC);
|
|
266
|
+
`);
|
|
267
|
+
}
|
|
268
|
+
ensureManualRevisionTable() {
|
|
269
|
+
this.db.exec(`
|
|
270
|
+
CREATE TABLE IF NOT EXISTS context_file_manual_revisions (
|
|
271
|
+
id TEXT PRIMARY KEY,
|
|
272
|
+
context_file_key TEXT NOT NULL,
|
|
273
|
+
name TEXT NOT NULL,
|
|
274
|
+
content_hash TEXT NOT NULL,
|
|
275
|
+
content TEXT NOT NULL,
|
|
276
|
+
created_at TEXT NOT NULL,
|
|
277
|
+
actor_id TEXT
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
CREATE INDEX IF NOT EXISTS idx_context_file_manual_revisions_key
|
|
281
|
+
ON context_file_manual_revisions(context_file_key, created_at DESC);
|
|
282
|
+
`);
|
|
283
|
+
}
|
|
284
|
+
assertCompatibleSchema() {
|
|
285
|
+
const version = this.db.prepare("SELECT value FROM context_file_store_meta WHERE key = ?").get(CONTEXT_FILE_SCHEMA_VERSION_KEY);
|
|
286
|
+
const compatible = version?.value === CONTEXT_FILE_SCHEMA_VERSION
|
|
287
|
+
&& this.hasColumns("context_files", CONTEXT_FILE_COLUMNS)
|
|
288
|
+
&& this.hasColumns("context_file_revisions", AUTOMATIC_REVISION_COLUMNS)
|
|
289
|
+
&& this.hasColumns("context_file_manual_revisions", MANUAL_REVISION_COLUMNS);
|
|
290
|
+
if (compatible)
|
|
291
|
+
return;
|
|
292
|
+
throw new Error(`Context Files metadata schema is incompatible with this Pibo version (expected ${CONTEXT_FILE_SCHEMA_VERSION}, found ${version?.value ?? "unversioned"}). ` +
|
|
293
|
+
"Activate a matching package version or use an isolated PIBO_HOME before accessing Context Files storage.");
|
|
294
|
+
}
|
|
295
|
+
migrateStorage() {
|
|
296
|
+
let recovered = [];
|
|
297
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
298
|
+
try {
|
|
299
|
+
this.db.exec(`
|
|
300
|
+
CREATE TABLE IF NOT EXISTS context_file_store_meta (
|
|
301
|
+
key TEXT PRIMARY KEY,
|
|
302
|
+
value TEXT NOT NULL
|
|
303
|
+
);
|
|
304
|
+
`);
|
|
305
|
+
const version = this.db.prepare("SELECT value FROM context_file_store_meta WHERE key = ?").get(CONTEXT_FILE_SCHEMA_VERSION_KEY);
|
|
306
|
+
if (version?.value === CONTEXT_FILE_SCHEMA_VERSION) {
|
|
307
|
+
this.db.exec("COMMIT");
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (version?.value !== undefined && version.value !== "1") {
|
|
311
|
+
throw new Error(`Unsupported Context Files metadata schema version ${version.value}; expected ${CONTEXT_FILE_SCHEMA_VERSION}`);
|
|
312
|
+
}
|
|
313
|
+
this.db.exec(`
|
|
314
|
+
CREATE TABLE IF NOT EXISTS context_files (
|
|
315
|
+
key TEXT PRIMARY KEY,
|
|
316
|
+
label TEXT NOT NULL,
|
|
317
|
+
managed_path TEXT NOT NULL,
|
|
318
|
+
scope TEXT NOT NULL,
|
|
319
|
+
source_type TEXT NOT NULL,
|
|
320
|
+
agent_profile_name TEXT,
|
|
321
|
+
active_revision_id TEXT,
|
|
322
|
+
working_content TEXT,
|
|
323
|
+
source_ref TEXT,
|
|
324
|
+
source_hash TEXT,
|
|
325
|
+
source_content TEXT,
|
|
326
|
+
created_at TEXT NOT NULL,
|
|
327
|
+
updated_at TEXT NOT NULL
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
CREATE INDEX IF NOT EXISTS idx_context_files_scope
|
|
331
|
+
ON context_files(scope, updated_at);
|
|
332
|
+
`);
|
|
333
|
+
if (!this.hasColumns("context_files", CONTEXT_FILE_BASE_COLUMNS)) {
|
|
334
|
+
throw new Error("Context Files metadata has an unsupported context_files table shape");
|
|
335
|
+
}
|
|
336
|
+
this.ensureColumn("context_files", "working_content", "TEXT");
|
|
337
|
+
this.ensureColumn("context_files", "source_content", "TEXT");
|
|
338
|
+
const revisionTableExists = this.tableExists("context_file_revisions");
|
|
339
|
+
const revisionColumns = this.columnsFor("context_file_revisions");
|
|
340
|
+
const hasAutomaticRevisionSchema = revisionTableExists
|
|
341
|
+
&& AUTOMATIC_REVISION_COLUMNS.every((column) => revisionColumns.has(column));
|
|
342
|
+
const hasManualOnlyRevisionSchema = revisionTableExists
|
|
343
|
+
&& !hasAutomaticRevisionSchema
|
|
344
|
+
&& MANUAL_REVISION_COLUMNS.every((column) => revisionColumns.has(column));
|
|
345
|
+
if (revisionTableExists && !hasAutomaticRevisionSchema && !hasManualOnlyRevisionSchema) {
|
|
346
|
+
throw new Error("Context Files metadata has an unsupported context_file_revisions table shape");
|
|
347
|
+
}
|
|
348
|
+
const files = this.db.prepare(`
|
|
349
|
+
SELECT key, managed_path, active_revision_id, working_content, source_content
|
|
350
|
+
FROM context_files
|
|
351
|
+
`).all();
|
|
352
|
+
recovered = files.map((file) => {
|
|
353
|
+
const fileExists = existsSync(file.managed_path);
|
|
354
|
+
const activeContent = !fileExists && revisionTableExists && file.active_revision_id
|
|
355
|
+
? this.db.prepare("SELECT content FROM context_file_revisions WHERE id = ?").get(file.active_revision_id)?.content
|
|
356
|
+
: undefined;
|
|
357
|
+
const workingContent = fileExists
|
|
358
|
+
? readFileSync(file.managed_path, "utf8")
|
|
359
|
+
: file.working_content ?? activeContent ?? "";
|
|
360
|
+
const sourceContent = file.source_content ?? (hasAutomaticRevisionSchema
|
|
361
|
+
? this.db.prepare(`
|
|
362
|
+
SELECT content FROM context_file_revisions
|
|
363
|
+
WHERE context_file_key = ? AND kind = 'source-snapshot'
|
|
364
|
+
ORDER BY created_at DESC
|
|
365
|
+
LIMIT 1
|
|
366
|
+
`).get(file.key)?.content
|
|
367
|
+
: undefined);
|
|
368
|
+
return {
|
|
369
|
+
key: file.key,
|
|
370
|
+
managedPath: file.managed_path,
|
|
371
|
+
workingContent,
|
|
372
|
+
sourceContent,
|
|
373
|
+
restoreManagedFile: !fileExists && (file.working_content !== null || activeContent !== undefined),
|
|
374
|
+
};
|
|
375
|
+
});
|
|
376
|
+
if (hasManualOnlyRevisionSchema) {
|
|
377
|
+
this.db.exec("DROP INDEX IF EXISTS idx_context_file_revisions_key");
|
|
378
|
+
if (this.tableExists("context_file_manual_revisions")) {
|
|
379
|
+
if (!this.hasColumns("context_file_manual_revisions", MANUAL_REVISION_COLUMNS)) {
|
|
380
|
+
throw new Error("Context Files metadata has an unsupported context_file_manual_revisions table shape");
|
|
381
|
+
}
|
|
382
|
+
this.db.exec(`
|
|
383
|
+
INSERT OR IGNORE INTO context_file_manual_revisions (
|
|
384
|
+
id, context_file_key, name, content_hash, content, created_at, actor_id
|
|
385
|
+
)
|
|
386
|
+
SELECT id, context_file_key, name, content_hash, content, created_at, actor_id
|
|
387
|
+
FROM context_file_revisions;
|
|
388
|
+
DROP TABLE context_file_revisions;
|
|
389
|
+
`);
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
this.db.exec("ALTER TABLE context_file_revisions RENAME TO context_file_manual_revisions");
|
|
393
|
+
}
|
|
394
|
+
this.db.exec(`
|
|
395
|
+
CREATE INDEX IF NOT EXISTS idx_context_file_manual_revisions_key
|
|
396
|
+
ON context_file_manual_revisions(context_file_key, created_at DESC);
|
|
397
|
+
`);
|
|
398
|
+
}
|
|
399
|
+
this.ensureAutomaticRevisionTable();
|
|
400
|
+
this.ensureManualRevisionTable();
|
|
401
|
+
if (hasAutomaticRevisionSchema && revisionColumns.has("name")) {
|
|
402
|
+
this.db.exec(`
|
|
403
|
+
INSERT OR IGNORE INTO context_file_manual_revisions (
|
|
404
|
+
id, context_file_key, name, content_hash, content, created_at, actor_id
|
|
405
|
+
)
|
|
406
|
+
SELECT id, context_file_key, name, content_hash, content, created_at, actor_id
|
|
407
|
+
FROM context_file_revisions
|
|
408
|
+
WHERE name IS NOT NULL;
|
|
409
|
+
`);
|
|
410
|
+
}
|
|
411
|
+
for (const file of recovered) {
|
|
412
|
+
this.db.prepare(`
|
|
413
|
+
UPDATE context_files
|
|
414
|
+
SET active_revision_id = NULL, working_content = ?, source_content = ?
|
|
415
|
+
WHERE key = ?
|
|
416
|
+
`).run(file.workingContent, file.sourceContent ?? null, file.key);
|
|
417
|
+
}
|
|
418
|
+
const migratedAt = new Date().toISOString();
|
|
419
|
+
this.db.prepare(`
|
|
420
|
+
INSERT INTO context_file_store_meta (key, value) VALUES (?, ?)
|
|
421
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
422
|
+
`).run(CONTEXT_FILE_SCHEMA_VERSION_KEY, CONTEXT_FILE_SCHEMA_VERSION);
|
|
423
|
+
this.db.prepare(`
|
|
424
|
+
INSERT INTO context_file_store_meta (key, value) VALUES (?, ?)
|
|
425
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
426
|
+
`).run(MANUAL_REVISION_MIGRATION_KEY, migratedAt);
|
|
427
|
+
this.db.exec("COMMIT");
|
|
428
|
+
}
|
|
429
|
+
catch (error) {
|
|
430
|
+
this.db.exec("ROLLBACK");
|
|
431
|
+
throw error;
|
|
432
|
+
}
|
|
433
|
+
for (const file of recovered) {
|
|
434
|
+
if (!file.restoreManagedFile)
|
|
435
|
+
continue;
|
|
436
|
+
mkdirSync(dirname(file.managedPath), { recursive: true });
|
|
437
|
+
writeFileSync(file.managedPath, file.workingContent, "utf8");
|
|
438
|
+
}
|
|
439
|
+
}
|
|
234
440
|
migrateLegacyStore() {
|
|
235
441
|
if (!this.legacyStorePath)
|
|
236
442
|
return;
|
|
@@ -244,9 +450,11 @@ export class ContextFileMetadataStore {
|
|
|
244
450
|
try {
|
|
245
451
|
for (const file of legacyStore.files) {
|
|
246
452
|
const resolvedPath = resolve(file.path);
|
|
247
|
-
const
|
|
453
|
+
const fileExists = existsSync(resolvedPath);
|
|
454
|
+
const createdAt = fileExists
|
|
248
455
|
? statSync(resolvedPath).mtime.toISOString()
|
|
249
456
|
: new Date().toISOString();
|
|
457
|
+
const workingContent = fileExists ? readFileSync(resolvedPath, "utf8") : "";
|
|
250
458
|
this.db
|
|
251
459
|
.prepare(`
|
|
252
460
|
INSERT INTO context_files (
|
|
@@ -257,37 +465,15 @@ export class ContextFileMetadataStore {
|
|
|
257
465
|
source_type,
|
|
258
466
|
agent_profile_name,
|
|
259
467
|
active_revision_id,
|
|
468
|
+
working_content,
|
|
260
469
|
source_ref,
|
|
261
470
|
source_hash,
|
|
471
|
+
source_content,
|
|
262
472
|
created_at,
|
|
263
473
|
updated_at
|
|
264
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
474
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
265
475
|
`)
|
|
266
|
-
.run(file.key, file.label, resolvedPath, file.scope, "managed", file.agentProfileName ?? null, null, null, null, createdAt, createdAt);
|
|
267
|
-
if (!existsSync(resolvedPath))
|
|
268
|
-
continue;
|
|
269
|
-
const content = readFileSync(resolvedPath, "utf8");
|
|
270
|
-
const contentHash = hashContextFileContent(content);
|
|
271
|
-
const revisionId = `rev_${randomUUID()}`;
|
|
272
|
-
this.db
|
|
273
|
-
.prepare(`
|
|
274
|
-
INSERT INTO context_file_revisions (
|
|
275
|
-
id,
|
|
276
|
-
context_file_key,
|
|
277
|
-
kind,
|
|
278
|
-
content_hash,
|
|
279
|
-
content,
|
|
280
|
-
created_at,
|
|
281
|
-
actor_id,
|
|
282
|
-
based_on_revision_id,
|
|
283
|
-
source_hash_at_creation,
|
|
284
|
-
note
|
|
285
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
286
|
-
`)
|
|
287
|
-
.run(revisionId, file.key, "working", contentHash, content, createdAt, null, null, null, "Legacy managed file import");
|
|
288
|
-
this.db
|
|
289
|
-
.prepare("UPDATE context_files SET active_revision_id = ?, updated_at = ? WHERE key = ?")
|
|
290
|
-
.run(revisionId, createdAt, file.key);
|
|
476
|
+
.run(file.key, file.label, resolvedPath, file.scope, "managed", file.agentProfileName ?? null, null, workingContent, null, null, null, createdAt, createdAt);
|
|
291
477
|
}
|
|
292
478
|
this.db.exec("COMMIT");
|
|
293
479
|
}
|