subagent-extension-for-codex 0.2.0 → 0.3.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.
Files changed (45) hide show
  1. package/README.md +5 -5
  2. package/assets/icons/arrow-up-circle.svg +14 -0
  3. package/assets/icons/check-circle.svg +14 -0
  4. package/assets/icons/file-code.svg +17 -0
  5. package/assets/icons/file-diff.svg +17 -0
  6. package/assets/icons/file-text.svg +17 -0
  7. package/assets/icons/note.svg +16 -0
  8. package/dist/THIRD_PARTY_LICENSES.txt +21 -1
  9. package/dist/app.html +644 -132
  10. package/dist/bundle/chunks/{chunk-MOWBM7MZ.js → chunk-36LESPFG.js} +1752 -196
  11. package/dist/bundle/chunks/chunk-36LESPFG.js.map +6 -0
  12. package/dist/bundle/chunks/{chunk-5TX3SEV2.js → chunk-FT2Y2DFZ.js} +137 -1
  13. package/dist/bundle/chunks/chunk-FT2Y2DFZ.js.map +6 -0
  14. package/dist/bundle/chunks/{chunk-DGCGXY3T.js → chunk-L5ILPS3O.js} +1722 -687
  15. package/dist/bundle/chunks/chunk-L5ILPS3O.js.map +6 -0
  16. package/dist/bundle/chunks/chunk-TPCNCQM7.js +970 -0
  17. package/dist/bundle/chunks/chunk-TPCNCQM7.js.map +6 -0
  18. package/dist/bundle/chunks/{v4-LDJUK5DU.js → v4-NCRHZ3K4.js} +2 -2
  19. package/dist/bundle/main.js +14481 -11021
  20. package/dist/bundle/main.js.map +3 -3
  21. package/dist/bundle/projects-cli.js +6 -3
  22. package/dist/bundle/projects-cli.js.map +1 -1
  23. package/dist/bundle/stdio.js +106 -8
  24. package/dist/bundle/stdio.js.map +2 -2
  25. package/migrations/20261107064009_claude-sdk-settings/migration.sql +39 -0
  26. package/migrations/20261107064009_claude-sdk-settings/snapshot.json +615 -0
  27. package/migrations/20261107203218_task-read-indexes/migration.sql +9 -0
  28. package/migrations/20261107203218_task-read-indexes/snapshot.json +762 -0
  29. package/migrations/20261107210532_chat-events-backfill/migration.sql +23 -0
  30. package/migrations/20261107210532_chat-events-backfill/snapshot.json +762 -0
  31. package/migrations/20261108005345_task-messages/migration.sql +9 -0
  32. package/migrations/20261108005345_task-messages/snapshot.json +841 -0
  33. package/migrations/20261108005348_task-messages-backfill/migration.sql +14 -0
  34. package/migrations/20261108005348_task-messages-backfill/snapshot.json +841 -0
  35. package/migrations/20261110025216_task-attachments/migration.sql +21 -0
  36. package/migrations/20261110025216_task-attachments/snapshot.json +1040 -0
  37. package/package.json +1 -1
  38. package/plugin.json +1 -1
  39. package/skills/delegation/SKILL.md +14 -7
  40. package/dist/bundle/chunks/chunk-5TX3SEV2.js.map +0 -6
  41. package/dist/bundle/chunks/chunk-DGCGXY3T.js.map +0 -6
  42. package/dist/bundle/chunks/chunk-MOWBM7MZ.js.map +0 -6
  43. package/dist/bundle/chunks/chunk-ZSLXLKU2.js +0 -1702
  44. package/dist/bundle/chunks/chunk-ZSLXLKU2.js.map +0 -6
  45. /package/dist/bundle/chunks/{v4-LDJUK5DU.js.map → v4-NCRHZ3K4.js.map} +0 -0
@@ -0,0 +1,970 @@
1
+ import { createRequire as __bundleCreateRequire } from "node:module"; const require = __bundleCreateRequire(import.meta.url);
2
+ import {
3
+ DefaultLogger,
4
+ DrizzleQueryError,
5
+ NoopLogger,
6
+ SQLiteAsyncDatabase,
7
+ SQLiteAsyncPreparedQuery,
8
+ SQLiteAsyncSession,
9
+ SQLiteAsyncTransaction,
10
+ SQLiteDialect,
11
+ and,
12
+ asc,
13
+ booleanFromInteger,
14
+ bufferFromBytes,
15
+ castToText,
16
+ count,
17
+ dateFromStoredMilliseconds,
18
+ dateFromStoredSeconds,
19
+ decodeEvent,
20
+ desc,
21
+ entityKind,
22
+ eq,
23
+ eventColumns,
24
+ events,
25
+ formatToMillis,
26
+ gt,
27
+ gte,
28
+ inArray,
29
+ insertEvent,
30
+ jitCompatCheck,
31
+ latestEvents,
32
+ logError,
33
+ logEvent,
34
+ lt,
35
+ lte,
36
+ max,
37
+ now,
38
+ packagePath,
39
+ readCompletedFlags,
40
+ readOriginalResponse,
41
+ readStates,
42
+ readTasks,
43
+ refineSqliteCodecs,
44
+ sql,
45
+ taskFilters,
46
+ taskMessages,
47
+ taskRead,
48
+ tasks,
49
+ withLogContext
50
+ } from "./chunk-36LESPFG.js";
51
+ import {
52
+ init_define_EXTENSION_BUILD_INFO
53
+ } from "./chunk-FT2Y2DFZ.js";
54
+
55
+ // src/service/features/tasks/storage/store.ts
56
+ init_define_EXTENSION_BUILD_INFO();
57
+ import { randomUUID } from "node:crypto";
58
+ import { EventEmitter } from "node:events";
59
+
60
+ // src/service/platform/database/database.ts
61
+ init_define_EXTENSION_BUILD_INFO();
62
+ import { mkdirSync as mkdirSync2 } from "node:fs";
63
+ import { dirname as dirname2 } from "node:path";
64
+ import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
65
+
66
+ // node_modules/drizzle-orm/node-sqlite/index.js
67
+ init_define_EXTENSION_BUILD_INFO();
68
+
69
+ // node_modules/drizzle-orm/node-sqlite/codecs.js
70
+ init_define_EXTENSION_BUILD_INFO();
71
+ var nodeSQLiteCodecs = refineSqliteCodecs({
72
+ "integer:boolean": { normalize: booleanFromInteger },
73
+ "integer:timestamp": { normalize: dateFromStoredSeconds },
74
+ "integer:timestamp_ms": { normalize: dateFromStoredMilliseconds },
75
+ "text:json": { normalize: JSON.parse },
76
+ blob: { normalize: bufferFromBytes },
77
+ "blob:json": {
78
+ cast: castToText,
79
+ normalize: JSON.parse
80
+ },
81
+ "blob:bigint": {
82
+ cast: castToText,
83
+ normalize: BigInt
84
+ },
85
+ numeric: {
86
+ cast: castToText,
87
+ normalize: String
88
+ },
89
+ "numeric:number": { normalize: Number },
90
+ "numeric:bigint": {
91
+ cast: castToText,
92
+ normalize: BigInt
93
+ }
94
+ });
95
+
96
+ // node_modules/drizzle-orm/node-sqlite/session.js
97
+ init_define_EXTENSION_BUILD_INFO();
98
+ var NodeSQLiteSession = class extends SQLiteAsyncSession {
99
+ client;
100
+ relations;
101
+ options;
102
+ static [entityKind] = "SQLJsSession";
103
+ logger;
104
+ constructor(client, dialect, relations, options = {}) {
105
+ super(dialect, "sync");
106
+ this.client = client;
107
+ this.relations = relations;
108
+ this.options = options;
109
+ this.logger = options.logger ?? new NoopLogger();
110
+ }
111
+ prepareQuery(query, mode, _prepare, executeMethod, mapper, queryMetadata) {
112
+ let stmt;
113
+ try {
114
+ stmt = this.client.prepare(query.sql);
115
+ } catch (e) {
116
+ throw new DrizzleQueryError(query.sql, this.options.paramsInErrors ? query.params : void 0, e);
117
+ }
118
+ return new SQLiteAsyncPreparedQuery("sync", executeMethod, {
119
+ all: (params) => {
120
+ stmt.setReturnArrays(mode === "arrays");
121
+ const res = stmt.all(...params);
122
+ if (mode === "objects") return res.map((row) => ({ ...row }));
123
+ return res;
124
+ },
125
+ get: (params) => {
126
+ stmt.setReturnArrays(mode === "arrays");
127
+ const res = stmt.get(...params);
128
+ if (res && mode === "objects") return { ...res };
129
+ return res;
130
+ },
131
+ run: (params) => {
132
+ stmt.setReturnArrays(false);
133
+ return stmt.run(...params);
134
+ }
135
+ }, query, mapper, mode, this.logger, void 0, queryMetadata, void 0, this.options.paramsInErrors);
136
+ }
137
+ transaction(transaction, config = {}) {
138
+ if (config?.behavior === "concurrent") throw new Error("Concurrent transactions are not supported by driver");
139
+ const tx = new NodeSQLiteTransaction("sync", this.dialect, this, this.relations);
140
+ this.run(sql.raw(`begin${config.behavior ? ` ${config.behavior}` : ""}`));
141
+ try {
142
+ const result = transaction(tx);
143
+ this.run(sql`commit`);
144
+ return result;
145
+ } catch (err) {
146
+ try {
147
+ this.run(sql`rollback`);
148
+ } catch {
149
+ }
150
+ throw err;
151
+ }
152
+ }
153
+ };
154
+ var NodeSQLiteTransaction = class NodeSQLiteTransaction2 extends SQLiteAsyncTransaction {
155
+ static [entityKind] = "SQLJsTransaction";
156
+ transaction(transaction) {
157
+ const savepointName = `sp${this.nestedIndex + 1}`;
158
+ const tx = new NodeSQLiteTransaction2("sync", this.dialect, this.session, this._.relations, this.nestedIndex + 1);
159
+ tx.run(sql.raw(`savepoint ${savepointName}`));
160
+ try {
161
+ const result = transaction(tx);
162
+ tx.run(sql.raw(`release savepoint ${savepointName}`));
163
+ return result;
164
+ } catch (err) {
165
+ try {
166
+ tx.run(sql.raw(`rollback to savepoint ${savepointName}`));
167
+ } catch {
168
+ }
169
+ throw err;
170
+ }
171
+ }
172
+ };
173
+
174
+ // node_modules/drizzle-orm/node-sqlite/driver.js
175
+ init_define_EXTENSION_BUILD_INFO();
176
+ import { DatabaseSync } from "node:sqlite";
177
+ var NodeSQLiteDatabase = class extends SQLiteAsyncDatabase {
178
+ static [entityKind] = "NodeSQLiteDatabase";
179
+ };
180
+ function construct(client, config = {}) {
181
+ const dialect = new SQLiteDialect({
182
+ codecs: config.codecs ?? nodeSQLiteCodecs,
183
+ useJitMappers: jitCompatCheck(config.jit)
184
+ });
185
+ let logger;
186
+ if (config.logger === true) logger = new DefaultLogger();
187
+ else if (config.logger !== false) logger = config.logger;
188
+ const relations = config.relations ?? {};
189
+ const db = new NodeSQLiteDatabase("sync", dialect, new NodeSQLiteSession(client, dialect, relations, {
190
+ logger,
191
+ paramsInErrors: config.paramsInErrors
192
+ }), relations);
193
+ db.$client = client;
194
+ return db;
195
+ }
196
+ function drizzle(...params) {
197
+ if (params[0] === void 0 || typeof params[0] === "string") return construct(params[0] === void 0 ? new DatabaseSync(":memory:") : new DatabaseSync(params[0]), params[1]);
198
+ const { connection, client, ...config } = params[0];
199
+ if (client) return construct(client, config);
200
+ if (typeof connection === "object") {
201
+ const { path, ...options } = connection;
202
+ return construct(new DatabaseSync(path ?? ":memory:", options), config);
203
+ }
204
+ return construct(new DatabaseSync(connection ?? ":memory:"), config);
205
+ }
206
+ (function(_drizzle) {
207
+ function mock(config) {
208
+ return construct({}, config);
209
+ }
210
+ _drizzle.mock = mock;
211
+ })(drizzle || (drizzle = {}));
212
+
213
+ // src/service/platform/database/migrate.ts
214
+ init_define_EXTENSION_BUILD_INFO();
215
+
216
+ // node_modules/drizzle-orm/migrator.js
217
+ init_define_EXTENSION_BUILD_INFO();
218
+ import crypto from "node:crypto";
219
+ import fs, { existsSync, readdirSync } from "node:fs";
220
+ import { join } from "node:path";
221
+ function readMigrationFiles(config) {
222
+ if (fs.existsSync(`${config.migrationsFolder}/meta/_journal.json`)) throw Error('We detected that you have old drizzle-kit migration folders. You must upgrade drizzle-kit and run "drizzle-kit up"');
223
+ const migrationFolderTo = config.migrationsFolder;
224
+ const migrationQueries = [];
225
+ const migrations = readdirSync(migrationFolderTo).map((subdir) => ({
226
+ path: join(migrationFolderTo, subdir, "migration.sql"),
227
+ name: subdir
228
+ })).filter((it) => existsSync(it.path));
229
+ migrations.sort((a, b) => a.name.localeCompare(b.name));
230
+ for (const migration of migrations) {
231
+ const migrationPath = migration.path;
232
+ const migrationDate = migration.name.slice(0, 14);
233
+ const query = fs.readFileSync(migrationPath).toString();
234
+ const result = query.split("--> statement-breakpoint").map((it) => {
235
+ return it;
236
+ });
237
+ const millis = formatToMillis(migrationDate);
238
+ migrationQueries.push({
239
+ sql: result,
240
+ bps: true,
241
+ folderMillis: millis,
242
+ hash: crypto.createHash("sha256").update(query).digest("hex"),
243
+ name: migration.name
244
+ });
245
+ }
246
+ return migrationQueries;
247
+ }
248
+
249
+ // src/service/platform/database/backup.ts
250
+ init_define_EXTENSION_BUILD_INFO();
251
+ import { mkdirSync, readdirSync as readdirSync2, rmSync } from "node:fs";
252
+ import { basename, dirname, extname, join as join2 } from "node:path";
253
+ var KEEP = 3;
254
+ function backUpDatabase(client, path, lastMigration) {
255
+ const directory = join2(dirname(path), "backups");
256
+ mkdirSync(directory, { recursive: true, mode: 448 });
257
+ const name = basename(path, extname(path));
258
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
259
+ const backup = join2(directory, `${name}-${lastMigration}-${stamp}.sqlite`);
260
+ client.prepare("VACUUM INTO ?").run(backup);
261
+ const stamped = new RegExp(`^${name}-.+-(\\d{4}-[\\dT-]+Z)\\.sqlite$`);
262
+ const backups = readdirSync2(directory).flatMap((file) => {
263
+ const time = file.match(stamped)?.[1];
264
+ return time ? [{ file, time }] : [];
265
+ }).sort(
266
+ (a, b) => b.time.localeCompare(a.time) || b.file.localeCompare(a.file)
267
+ );
268
+ for (const { file } of backups.slice(KEEP)) {
269
+ rmSync(join2(directory, file), { force: true });
270
+ }
271
+ return backup;
272
+ }
273
+
274
+ // src/service/platform/database/migrate.ts
275
+ var LEDGER = "__drizzle_migrations";
276
+ function migrate(client, folder, path) {
277
+ const migrations = readMigrationFiles({ migrationsFolder: folder });
278
+ if (!migrations.length) throw new Error(`No migrations found in ${folder}`);
279
+ const before = ledger(client);
280
+ if (before.length && path !== ":memory:" && migrations.some((m) => !before.includes(m.name))) {
281
+ backUpDatabase(client, path, before.at(-1));
282
+ }
283
+ client.exec("BEGIN IMMEDIATE");
284
+ try {
285
+ const versioned = client.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=?").get(LEDGER);
286
+ if (!versioned) {
287
+ const tables = client.prepare(
288
+ "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
289
+ ).all();
290
+ if (tables.length) {
291
+ throw new Error(
292
+ `${path} was created by an unsupported earlier version. Delete its data directory to start over.`
293
+ );
294
+ }
295
+ client.exec(
296
+ `CREATE TABLE ${LEDGER}(id INTEGER PRIMARY KEY, hash TEXT NOT NULL, created_at NUMERIC, name TEXT, applied_at TEXT)`
297
+ );
298
+ }
299
+ const applied = new Set(ledger(client));
300
+ const known = new Set(migrations.map((m) => m.name));
301
+ const unknown = [...applied].filter((name) => !known.has(name));
302
+ if (unknown.length) {
303
+ throw new Error(
304
+ `${path} has migration history this build does not recognize (${unknown.join(", ")}). Use a build that includes those migrations, or delete its data directory to start over.`
305
+ );
306
+ }
307
+ const record = client.prepare(
308
+ `INSERT INTO ${LEDGER}(hash, created_at, name, applied_at) VALUES(?,?,?,?)`
309
+ );
310
+ for (const migration of migrations) {
311
+ if (applied.has(migration.name)) continue;
312
+ for (const statement of migration.sql) {
313
+ if (statement.trim()) client.exec(statement);
314
+ }
315
+ record.run(
316
+ migration.hash,
317
+ migration.folderMillis,
318
+ migration.name,
319
+ (/* @__PURE__ */ new Date()).toISOString()
320
+ );
321
+ }
322
+ client.exec("COMMIT");
323
+ } catch (error) {
324
+ client.exec("ROLLBACK");
325
+ throw error;
326
+ }
327
+ }
328
+ function ledger(client) {
329
+ const exists = client.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=?").get(LEDGER);
330
+ if (!exists) return [];
331
+ return client.prepare(`SELECT name FROM ${LEDGER} ORDER BY id`).all().map((row) => String(row.name));
332
+ }
333
+
334
+ // src/service/platform/database/database.ts
335
+ var migrationsFolder = packagePath("migrations");
336
+ for (const method of ["run", "all", "get", "values"]) {
337
+ const prototype = SQLiteAsyncPreparedQuery.prototype;
338
+ const execute = prototype[method];
339
+ if (!execute) {
340
+ throw new Error(
341
+ `Drizzle's prepared queries have no ${method} method to wrap; check the drizzle-orm upgrade.`
342
+ );
343
+ }
344
+ prototype[method] = function(...args) {
345
+ try {
346
+ return execute.apply(this, args);
347
+ } catch (error) {
348
+ throw error instanceof DrizzleQueryError && error.cause instanceof Error ? error.cause : error;
349
+ }
350
+ };
351
+ }
352
+ var BUSY_TIMEOUT_MS = 5e3;
353
+ var SQLITE_BUSY = 5;
354
+ function busy(error) {
355
+ return (error.errcode ?? 0) % 256 === SQLITE_BUSY;
356
+ }
357
+ function useWal(client) {
358
+ const deadline = Date.now() + BUSY_TIMEOUT_MS;
359
+ for (; ; ) {
360
+ try {
361
+ if (client.prepare("PRAGMA journal_mode").get()?.journal_mode === "wal") {
362
+ return;
363
+ }
364
+ client.exec("PRAGMA journal_mode=WAL");
365
+ return;
366
+ } catch (error) {
367
+ if (!busy(error) || Date.now() > deadline) throw error;
368
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
369
+ }
370
+ }
371
+ }
372
+ function thenable(value) {
373
+ return typeof value?.then === "function";
374
+ }
375
+ function transactions(client) {
376
+ let queued;
377
+ return {
378
+ transaction(fn) {
379
+ if (queued) {
380
+ throw new Error(
381
+ "A transaction is already open; run this work inside it"
382
+ );
383
+ }
384
+ const notifications = [];
385
+ queued = notifications;
386
+ let result;
387
+ client.exec("BEGIN IMMEDIATE");
388
+ try {
389
+ result = fn();
390
+ if (thenable(result)) {
391
+ throw new Error("Database transactions must be synchronous");
392
+ }
393
+ client.exec("COMMIT");
394
+ } catch (error) {
395
+ client.exec("ROLLBACK");
396
+ throw error;
397
+ } finally {
398
+ queued = void 0;
399
+ }
400
+ for (const notify of notifications) notify();
401
+ return result;
402
+ },
403
+ afterCommit(notify) {
404
+ if (queued) queued.push(notify);
405
+ else notify();
406
+ }
407
+ };
408
+ }
409
+ function openDatabase(path, migrations = migrationsFolder) {
410
+ mkdirSync2(dirname2(path), { recursive: true, mode: 448 });
411
+ const client = new DatabaseSync2(path, { timeout: BUSY_TIMEOUT_MS });
412
+ try {
413
+ useWal(client);
414
+ client.exec("PRAGMA foreign_keys=ON");
415
+ migrate(client, migrations, path);
416
+ client.exec("PRAGMA optimize=0x10002");
417
+ } catch (error) {
418
+ client.close();
419
+ throw error;
420
+ }
421
+ return {
422
+ client,
423
+ orm: drizzle({ client }),
424
+ ...transactions(client),
425
+ close: () => client.close()
426
+ };
427
+ }
428
+ var INCREMENTAL = 2;
429
+ function useIncrementalVacuum(database) {
430
+ const { client } = database;
431
+ const mode = client.prepare("PRAGMA auto_vacuum").get()?.auto_vacuum;
432
+ if (mode === INCREMENTAL) return;
433
+ const started = performance.now();
434
+ logEvent("info", "database.vacuum_started");
435
+ client.exec("PRAGMA auto_vacuum=INCREMENTAL");
436
+ client.exec("VACUUM");
437
+ logEvent("info", "database.vacuum_completed", {
438
+ duration_ms: Math.round(performance.now() - started)
439
+ });
440
+ }
441
+
442
+ // src/service/features/tasks/storage/chat-events.ts
443
+ init_define_EXTENSION_BUILD_INFO();
444
+ function chatEvents(orm, id, after = 0, types) {
445
+ const chat = and(eq(events.task_id, id), eq(events.chat, true));
446
+ if (after > 0) {
447
+ const newest2 = orm.select({ id: max(events.id) }).from(events).where(chat).get();
448
+ if ((newest2?.id ?? 0) <= after) return [];
449
+ }
450
+ return orm.select(eventColumns).from(events).where(chat).orderBy(events.id).all().filter((row) => !types || types.includes(row.type)).map(decodeEvent);
451
+ }
452
+
453
+ // src/service/features/tasks/storage/event-storage-metrics.ts
454
+ init_define_EXTENSION_BUILD_INFO();
455
+ var EventStorageMetrics = class {
456
+ active = /* @__PURE__ */ new Map();
457
+ begin(taskId, turn) {
458
+ const counters = {
459
+ event_count: 0,
460
+ json_bytes: 0,
461
+ original_response_event_count: 0
462
+ };
463
+ this.active.set(taskId, counters);
464
+ return (status) => {
465
+ if (this.active.get(taskId) !== counters) return;
466
+ this.active.delete(taskId);
467
+ try {
468
+ logEvent("info", "task.event_storage", {
469
+ task_id: taskId,
470
+ turn,
471
+ status,
472
+ ...counters
473
+ });
474
+ } catch {
475
+ }
476
+ };
477
+ }
478
+ committed(database, taskId, serialized, originalResponse) {
479
+ const counters = this.active.get(taskId);
480
+ if (!counters) return;
481
+ const bytes = Buffer.byteLength(serialized, "utf8") + (originalResponse ? Buffer.byteLength(originalResponse, "utf8") : 0);
482
+ database.afterCommit(() => {
483
+ if (this.active.get(taskId) !== counters) return;
484
+ counters.event_count++;
485
+ counters.json_bytes += bytes;
486
+ counters.original_response_event_count += Number(
487
+ originalResponse !== void 0
488
+ );
489
+ });
490
+ }
491
+ };
492
+
493
+ // src/service/features/tasks/storage/message-store.ts
494
+ init_define_EXTENSION_BUILD_INFO();
495
+ var MessageStore = class {
496
+ constructor(database) {
497
+ this.database = database;
498
+ }
499
+ database;
500
+ get orm() {
501
+ return this.database.orm;
502
+ }
503
+ /** Adds messages after a task's others. */
504
+ add(taskId, ...messages) {
505
+ if (!messages.length) return;
506
+ const next = this.count(taskId);
507
+ this.orm.insert(taskMessages).values(
508
+ messages.map((m, index) => ({
509
+ task_id: taskId,
510
+ seq: next + index,
511
+ message_id: m.id,
512
+ message: m.message,
513
+ sender: m.from
514
+ }))
515
+ ).run();
516
+ }
517
+ /** How many messages a task has. */
518
+ count(taskId) {
519
+ return this.orm.select({ count: count() }).from(taskMessages).where(eq(taskMessages.task_id, taskId)).get()?.count ?? 0;
520
+ }
521
+ /** A task's messages from position `from`, and before `to` when given. */
522
+ list(taskId, from = 0, to) {
523
+ return this.orm.select({
524
+ id: taskMessages.message_id,
525
+ message: taskMessages.message,
526
+ from: taskMessages.sender
527
+ }).from(taskMessages).where(
528
+ and(
529
+ eq(taskMessages.task_id, taskId),
530
+ gte(taskMessages.seq, from),
531
+ to === void 0 ? void 0 : lt(taskMessages.seq, to)
532
+ )
533
+ ).orderBy(taskMessages.seq).all();
534
+ }
535
+ /** Which of `ids` name a task's messages. */
536
+ existing(taskId, ids) {
537
+ const rows = this.orm.select({ id: taskMessages.message_id }).from(taskMessages).where(
538
+ and(
539
+ eq(taskMessages.task_id, taskId),
540
+ inArray(taskMessages.message_id, ids)
541
+ )
542
+ ).all();
543
+ return new Set(rows.map((row) => row.id));
544
+ }
545
+ };
546
+
547
+ // src/service/features/tasks/storage/publish-listeners.ts
548
+ init_define_EXTENSION_BUILD_INFO();
549
+ function publishListeners(bus, name, args) {
550
+ for (const listener of bus.listeners(name)) {
551
+ try {
552
+ listener(...args);
553
+ } catch (error) {
554
+ logError("store.listener_failed", error, { listener: name });
555
+ }
556
+ }
557
+ }
558
+
559
+ // src/service/features/tasks/storage/read-metrics.ts
560
+ init_define_EXTENSION_BUILD_INFO();
561
+ var WINDOW_MS = 6e4;
562
+ var SLOW_MS = 50;
563
+ var ReadMetrics = class {
564
+ summaries = /* @__PURE__ */ new Map();
565
+ timer;
566
+ started = 0;
567
+ measure(operation, run) {
568
+ const started = performance.now();
569
+ const work = { rows: 0, tally_rows: 0 };
570
+ let outcome = "error";
571
+ try {
572
+ const result = run(work);
573
+ outcome = "success";
574
+ return result;
575
+ } finally {
576
+ const duration = performance.now() - started;
577
+ try {
578
+ logEvent("debug", "storage.read", {
579
+ operation,
580
+ duration_ms: duration,
581
+ outcome,
582
+ ...work
583
+ });
584
+ } catch {
585
+ }
586
+ const summary = this.summaries.get(operation) ?? {
587
+ count: 0,
588
+ total_ms: 0,
589
+ max_ms: 0,
590
+ rows: 0,
591
+ tally_rows: 0,
592
+ slow_count: 0,
593
+ error_count: 0
594
+ };
595
+ summary.count++;
596
+ summary.total_ms += duration;
597
+ summary.max_ms = Math.max(summary.max_ms, duration);
598
+ summary.rows += work.rows;
599
+ summary.tally_rows += work.tally_rows;
600
+ summary.slow_count += Number(duration >= SLOW_MS);
601
+ summary.error_count += Number(outcome === "error");
602
+ this.summaries.set(operation, summary);
603
+ if (!this.timer) {
604
+ this.started = performance.now();
605
+ this.timer = setTimeout(() => this.flush(), WINDOW_MS);
606
+ this.timer.unref();
607
+ }
608
+ }
609
+ }
610
+ rows(operation, run) {
611
+ return this.measure(operation, (work) => {
612
+ const rows = run();
613
+ work.rows = rows.length;
614
+ return rows;
615
+ });
616
+ }
617
+ flush() {
618
+ clearTimeout(this.timer);
619
+ this.timer = void 0;
620
+ try {
621
+ withLogContext(
622
+ {},
623
+ () => {
624
+ for (const [operation, summary] of this.summaries) {
625
+ logEvent("info", "storage.read_summary", {
626
+ operation,
627
+ ...summary,
628
+ window_ms: performance.now() - this.started,
629
+ slow_threshold_ms: SLOW_MS
630
+ });
631
+ }
632
+ },
633
+ { inherit: false }
634
+ );
635
+ } catch {
636
+ } finally {
637
+ this.summaries.clear();
638
+ }
639
+ }
640
+ };
641
+
642
+ // src/service/features/tasks/storage/store.ts
643
+ var newest = desc(tasks.created_at);
644
+ var PAGE = 1e3;
645
+ var Store = class {
646
+ /** The shared connection, which other features' storage also uses. */
647
+ database;
648
+ messages;
649
+ reads = new ReadMetrics();
650
+ eventStorage = new EventStorageMetrics();
651
+ /** The tasks saved in the open transaction, with the `updated_at` to restore on rollback. */
652
+ saved;
653
+ /** The owners whose tasks the open transaction changed. */
654
+ changed;
655
+ /** Distinguishes this process's revisions from a restarted service's. */
656
+ bootId = randomUUID();
657
+ revisions = /* @__PURE__ */ new Map();
658
+ projectRevision = 0;
659
+ // Long-poll reads each listen while they wait, so there can be many.
660
+ bus = new EventEmitter().setMaxListeners(0);
661
+ constructor(path) {
662
+ this.database = openDatabase(path);
663
+ this.messages = new MessageStore(this.database);
664
+ }
665
+ beginEventStorage(taskId, turn) {
666
+ return this.eventStorage.begin(taskId, turn);
667
+ }
668
+ get orm() {
669
+ return this.database.orm;
670
+ }
671
+ /**
672
+ * Runs `fn` in one transaction, so the task state and events it writes
673
+ * commit together. Listeners hear its events only after the commit; on a
674
+ * rollback they hear nothing, and saved tasks get back their `updated_at`.
675
+ */
676
+ transaction(fn) {
677
+ if (this.saved) {
678
+ throw new Error("A transaction is already open; run this work inside it");
679
+ }
680
+ const saved = [];
681
+ this.saved = saved;
682
+ this.changed = /* @__PURE__ */ new Set();
683
+ try {
684
+ return this.database.transaction(() => {
685
+ try {
686
+ return fn();
687
+ } finally {
688
+ this.saved = void 0;
689
+ this.changed = void 0;
690
+ }
691
+ });
692
+ } catch (error) {
693
+ for (const [t, updated] of saved.reverse()) t.updated_at = updated;
694
+ throw error;
695
+ }
696
+ }
697
+ save(t) {
698
+ const updated = t.updated_at;
699
+ t.updated_at = now();
700
+ const body = JSON.stringify(t);
701
+ try {
702
+ this.orm.insert(tasks).values({ id: t.id, owner: t.owner, body }).onConflictDoUpdate({ target: tasks.id, set: { body } }).run();
703
+ } catch (error) {
704
+ t.updated_at = updated;
705
+ throw error;
706
+ }
707
+ this.saved?.push([t, updated]);
708
+ this.markChanged(t.owner);
709
+ }
710
+ /**
711
+ * Deletes a task. Its events and pending forms
712
+ * reference it and are deleted with it, and the service's database returns
713
+ * their space to the file system.
714
+ */
715
+ delete(id, owner) {
716
+ this.get(id, owner);
717
+ this.orm.delete(tasks).where(and(eq(tasks.id, id), eq(tasks.owner, owner))).run();
718
+ this.database.client.exec("PRAGMA incremental_vacuum");
719
+ this.markChanged(owner);
720
+ }
721
+ /**
722
+ * Announces `change` for an owner once the open transaction commits, once
723
+ * per transaction however many writes it holds; at once outside one.
724
+ * Other task storage built on this connection announces its writes here.
725
+ */
726
+ markChanged(owner) {
727
+ const changed = this.changed;
728
+ if (!changed) {
729
+ this.announce([owner]);
730
+ return;
731
+ }
732
+ if (!changed.size) this.database.afterCommit(() => this.announce(changed));
733
+ changed.add(owner);
734
+ }
735
+ announce(owners) {
736
+ for (const owner of owners) {
737
+ this.revisions.set(owner, (this.revisions.get(owner) ?? 0) + 1);
738
+ this.publish("change", owner);
739
+ }
740
+ }
741
+ /** Announces a change to the project list, which every owner shares. */
742
+ projectsChanged() {
743
+ this.projectRevision++;
744
+ this.publish("change", void 0);
745
+ }
746
+ /**
747
+ * A token for the state of an owner's tasks and the project list,
748
+ * `bootId:count`. It differs whenever either has changed, including across
749
+ * a restart, and the count only grows within a boot.
750
+ */
751
+ revision(owner) {
752
+ const count2 = (this.revisions.get(owner) ?? 0) + this.projectRevision;
753
+ return `${this.bootId}:${count2}`;
754
+ }
755
+ /**
756
+ * An owner's tasks, newest first, in a conversation or repository when
757
+ * given.
758
+ */
759
+ list(owner, filters) {
760
+ return this.reads.rows(
761
+ "task_list",
762
+ () => readTasks(this.orm, taskFilters(owner, filters), newest)
763
+ );
764
+ }
765
+ /**
766
+ * An owner's tasks matching `filters`, newest first, with their repository
767
+ * and whether each is a completed task the owner has read.
768
+ */
769
+ readCompletedFlags(owner, filters) {
770
+ return readCompletedFlags(this.orm, taskFilters(owner, filters), newest);
771
+ }
772
+ /** Every owner's tasks, newest first. */
773
+ all() {
774
+ return readTasks(this.orm, void 0, newest);
775
+ }
776
+ /** An owner's newest task in a conversation. */
777
+ latestInConversation(owner, conversation) {
778
+ return readTasks(
779
+ this.orm,
780
+ taskFilters(owner, { conversation }),
781
+ newest,
782
+ 1
783
+ )[0];
784
+ }
785
+ /** Every owner's tasks with one of `statuses`, oldest first. */
786
+ withStatus(statuses) {
787
+ return readTasks(
788
+ this.orm,
789
+ inArray(tasks.status, statuses),
790
+ asc(tasks.created_at)
791
+ );
792
+ }
793
+ get(id, owner) {
794
+ const task = this.find(id, owner);
795
+ if (!task) throw new Error("Task not found");
796
+ return task;
797
+ }
798
+ /** An owner's task, or undefined when it has none with this id. */
799
+ find(id, owner) {
800
+ return readTasks(
801
+ this.orm,
802
+ and(eq(tasks.id, id), eq(tasks.owner, owner))
803
+ )[0];
804
+ }
805
+ event(t, type, ...args) {
806
+ const data = args[0] ?? {};
807
+ return this.writeEvent(t, type, data);
808
+ }
809
+ /** Records a provider-owned event through the explicit opaque extension path. */
810
+ eventOpaque(t, type, data = {}) {
811
+ return this.writeEvent(t, type, data);
812
+ }
813
+ writeEvent(t, type, data) {
814
+ const event = insertEvent(
815
+ this.database,
816
+ this.eventStorage,
817
+ t.id,
818
+ type,
819
+ data
820
+ );
821
+ this.database.afterCommit(() => this.publish("event", event, t.owner));
822
+ this.markChanged(t.owner);
823
+ return event;
824
+ }
825
+ /** Isolate listener failures after commit so consumers can still catch up. */
826
+ publish(name, ...args) {
827
+ publishListeners(this.bus, name, args);
828
+ }
829
+ events(id, owner, after = 0) {
830
+ this.get(id, owner);
831
+ return this.orm.select(eventColumns).from(events).where(and(eq(events.task_id, id), gt(events.id, after))).orderBy(events.id).limit(PAGE).all().map(decodeEvent);
832
+ }
833
+ /** Resolves an original response after checking task ownership. */
834
+ originalResponse(id, owner, responseRef) {
835
+ this.get(id, owner);
836
+ return readOriginalResponse(this.database, id, responseRef);
837
+ }
838
+ /**
839
+ * The id of a task's event `depth` places from its newest, so the newest
840
+ * `depth` events are those from it on; 0 when there are fewer than that.
841
+ * `depth` is at least 1: the query offsets by `depth - 1`.
842
+ */
843
+ eventWindowStart(id, depth) {
844
+ return this.orm.select({ id: events.id }).from(events).where(eq(events.task_id, id)).orderBy(desc(events.id)).limit(1).offset(depth - 1).get()?.id ?? 0;
845
+ }
846
+ /**
847
+ * The id of the `depth`th newest of a task's events after `after` and
848
+ * through `through`, so those events from it on are the newest `depth`;
849
+ * `after + 1` when there are fewer than that.
850
+ */
851
+ rangeWindowStart(id, after, through, depth) {
852
+ return this.orm.select({ id: events.id }).from(events).where(
853
+ and(
854
+ eq(events.task_id, id),
855
+ gt(events.id, after),
856
+ lte(events.id, through)
857
+ )
858
+ ).orderBy(desc(events.id)).limit(1).offset(depth - 1).get()?.id ?? after + 1;
859
+ }
860
+ /**
861
+ * What the owner has read of a task: whether it has a reply, outcome or
862
+ * request they haven't read, and the newest event they had read.
863
+ */
864
+ readState(id) {
865
+ return readStates(this.orm, eq(tasks.id, id)).get(id);
866
+ }
867
+ /** The read state of each of an owner's tasks matching `filters`, by id. */
868
+ readStates(owner, filters) {
869
+ return readStates(this.orm, taskFilters(owner, filters));
870
+ }
871
+ /**
872
+ * Records that the owner has read a task through event `through`, or its
873
+ * newest event without one. It never moves back, never passes the newest
874
+ * event and clears the task's unread mark.
875
+ */
876
+ markRead(t, through) {
877
+ const newest2 = this.lastEventId(t.id);
878
+ const read = Math.min(through ?? newest2, newest2);
879
+ this.orm.insert(taskRead).values({ task_id: t.id, through: read, unread: false }).onConflictDoUpdate({
880
+ target: taskRead.task_id,
881
+ set: { through: sql`max(${taskRead.through}, ${read})`, unread: false }
882
+ }).run();
883
+ this.markChanged(t.owner);
884
+ }
885
+ /** Marks a task unread from event `from` on; unlike `markRead`, it can move the read point back. */
886
+ markUnreadFrom(t, from) {
887
+ const through = Math.min(from - 1, this.lastEventId(t.id));
888
+ const mark = { through, unread: false };
889
+ this.orm.insert(taskRead).values({ task_id: t.id, ...mark }).onConflictDoUpdate({ target: taskRead.task_id, set: mark }).run();
890
+ this.markChanged(t.owner);
891
+ }
892
+ /** Marks a task unread until the owner next sees it. */
893
+ markUnread(t) {
894
+ this.orm.insert(taskRead).values({ task_id: t.id, through: 0, unread: true }).onConflictDoUpdate({ target: taskRead.task_id, set: { unread: true } }).run();
895
+ this.markChanged(t.owner);
896
+ }
897
+ /** The id of a task's `task.turn_ended` event for a turn; absent until it ends. */
898
+ turnEnded(id, turn) {
899
+ return this.orm.select({ id: events.id }).from(events).where(
900
+ and(
901
+ eq(events.task_id, id),
902
+ eq(events.type, "task.turn_ended"),
903
+ eq(sql`json_extract(${events.data}, '$.turn')`, turn)
904
+ )
905
+ ).orderBy(events.id).limit(1).get()?.id;
906
+ }
907
+ /** A task's first event after `after` with one of `types`. */
908
+ nextEventOfType(id, after, types) {
909
+ return this.orm.select({ id: events.id, type: events.type }).from(events).where(
910
+ and(
911
+ eq(events.task_id, id),
912
+ gt(events.id, after),
913
+ inArray(events.type, types)
914
+ )
915
+ ).orderBy(events.id).limit(1).get();
916
+ }
917
+ /**
918
+ * A task's events after `after`, and before `before` when given, a page at a
919
+ * time. Internal: callers check the task's owner first.
920
+ */
921
+ taskEvents(id, after, before) {
922
+ return this.orm.select(eventColumns).from(events).where(
923
+ and(
924
+ eq(events.task_id, id),
925
+ gt(events.id, after),
926
+ before === void 0 ? void 0 : lt(events.id, before)
927
+ )
928
+ ).orderBy(events.id).limit(PAGE).all().map(decodeEvent);
929
+ }
930
+ recentEvents(id, owner) {
931
+ this.get(id, owner);
932
+ return this.orm.select(eventColumns).from(events).where(eq(events.task_id, id)).orderBy(desc(events.id)).limit(200).all().reverse().map(decodeEvent);
933
+ }
934
+ /** The id of the latest event, or of a task's latest one; 0 before any. */
935
+ lastEventId(id) {
936
+ const row = this.orm.select({ id: max(events.id) }).from(events).where(id === void 0 ? void 0 : eq(events.task_id, id)).get();
937
+ return row?.id ?? 0;
938
+ }
939
+ /** A task's latest event of a type, such as its latest `agent.reply`. */
940
+ latestEvent(id, type) {
941
+ const row = this.orm.select(eventColumns).from(events).where(and(eq(events.task_id, id), eq(events.type, type))).orderBy(desc(events.id)).limit(1).get();
942
+ return row && decodeEvent(row);
943
+ }
944
+ /** The latest event of `type` in each of an owner's tasks matching `filters`. */
945
+ latestEvents(owner, type, filters) {
946
+ return latestEvents(this.orm, taskFilters(owner, filters), type);
947
+ }
948
+ /** Every event of the given types for a task, in order, with no page limit. */
949
+ eventsOfTypes(id, types) {
950
+ return this.orm.select(eventColumns).from(events).where(and(eq(events.task_id, id), inArray(events.type, types))).orderBy(events.id).all().map(decodeEvent);
951
+ }
952
+ /** The events Chat shows, in order, or none when none is newer than `after`. */
953
+ chatEvents(id, after = 0) {
954
+ return this.reads.rows("chat", () => chatEvents(this.orm, id, after));
955
+ }
956
+ /** A task's Chat events of `types`, in order, for its conversation. */
957
+ conversationEvents(id, types) {
958
+ return chatEvents(this.orm, id, 0, types);
959
+ }
960
+ close() {
961
+ this.reads.flush();
962
+ this.database.close();
963
+ }
964
+ };
965
+
966
+ export {
967
+ useIncrementalVacuum,
968
+ Store
969
+ };
970
+ //# sourceMappingURL=chunk-TPCNCQM7.js.map