@frontmcp/storage-sqlite 1.0.4 → 1.1.0-beta.1

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/esm/index.mjs CHANGED
@@ -371,6 +371,278 @@ var SqliteElicitationStore = class {
371
371
  }
372
372
  };
373
373
 
374
+ // libs/storage-sqlite/src/sqlite-task.store.ts
375
+ import { EventEmitter as EventEmitter2 } from "node:events";
376
+ var defaultLivenessProbe = (pid) => {
377
+ try {
378
+ process.kill(pid, 0);
379
+ return true;
380
+ } catch {
381
+ return false;
382
+ }
383
+ };
384
+ var DEFAULT_PAGE_SIZE = 50;
385
+ var SqliteTaskStore = class {
386
+ db;
387
+ emitter = new EventEmitter2();
388
+ logger;
389
+ liveness;
390
+ cleanupTimer;
391
+ /**
392
+ * AES-256-GCM key derived from `options.encryption.secret`, applied to the
393
+ * `record_json` column on write and reversed on read. Other columns
394
+ * (`task_id`, `session_id`, `status`, `expires_at`, `executor_pid`) are kept
395
+ * in plaintext so the indexes remain usable.
396
+ */
397
+ encryptionKey = null;
398
+ stmts = null;
399
+ constructor(options) {
400
+ const BetterSqlite3 = __require("better-sqlite3");
401
+ try {
402
+ this.db = new BetterSqlite3(options.path);
403
+ } catch (err) {
404
+ const message = err instanceof Error ? err.message : String(err);
405
+ throw new Error(`SqliteTaskStore: failed to open database at "${options.path}": ${message}`);
406
+ }
407
+ if (options.walMode !== false) {
408
+ this.db.pragma("journal_mode = WAL");
409
+ }
410
+ this.logger = options.logger;
411
+ this.liveness = options.livenessProbe ?? defaultLivenessProbe;
412
+ if (options.encryption?.secret) {
413
+ this.encryptionKey = deriveEncryptionKey(options.encryption.secret);
414
+ }
415
+ this.emitter.setMaxListeners(200);
416
+ this.initSchema();
417
+ this.prepareStatements();
418
+ const cleanupInterval = options.ttlCleanupIntervalMs ?? 6e4;
419
+ if (cleanupInterval > 0) {
420
+ this.cleanupTimer = setInterval(() => this.purgeExpired(), cleanupInterval);
421
+ this.cleanupTimer.unref?.();
422
+ } else {
423
+ this.cleanupTimer = null;
424
+ }
425
+ }
426
+ initSchema() {
427
+ this.db.exec(`
428
+ CREATE TABLE IF NOT EXISTS mcp_tasks (
429
+ task_id TEXT PRIMARY KEY,
430
+ session_id TEXT NOT NULL,
431
+ status TEXT NOT NULL,
432
+ expires_at INTEGER NOT NULL,
433
+ created_at INTEGER NOT NULL,
434
+ updated_at INTEGER NOT NULL,
435
+ executor_pid INTEGER,
436
+ record_json TEXT NOT NULL
437
+ );
438
+ CREATE INDEX IF NOT EXISTS idx_mcp_tasks_session ON mcp_tasks (session_id);
439
+ CREATE INDEX IF NOT EXISTS idx_mcp_tasks_status ON mcp_tasks (status);
440
+ CREATE INDEX IF NOT EXISTS idx_mcp_tasks_expires ON mcp_tasks (expires_at);
441
+ `);
442
+ }
443
+ prepareStatements() {
444
+ this.stmts = {
445
+ insert: this.db.prepare(
446
+ "INSERT INTO mcp_tasks (task_id, session_id, status, expires_at, created_at, updated_at, executor_pid, record_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
447
+ ),
448
+ get: this.db.prepare("SELECT * FROM mcp_tasks WHERE task_id = ? AND session_id = ?"),
449
+ update: this.db.prepare(
450
+ "UPDATE mcp_tasks SET status = ?, expires_at = ?, updated_at = ?, executor_pid = ?, record_json = ? WHERE task_id = ? AND session_id = ?"
451
+ ),
452
+ del: this.db.prepare("DELETE FROM mcp_tasks WHERE task_id = ? AND session_id = ?"),
453
+ listBySession: this.db.prepare(
454
+ "SELECT * FROM mcp_tasks WHERE session_id = ? AND expires_at > ? ORDER BY created_at ASC, task_id ASC LIMIT ? OFFSET ?"
455
+ ),
456
+ countBySession: this.db.prepare("SELECT COUNT(*) as n FROM mcp_tasks WHERE session_id = ? AND expires_at > ?"),
457
+ cleanup: this.db.prepare("DELETE FROM mcp_tasks WHERE expires_at <= ?")
458
+ };
459
+ }
460
+ prepared() {
461
+ if (!this.stmts) throw new Error("SqliteTaskStore: prepared statements not initialized");
462
+ return this.stmts;
463
+ }
464
+ // ───────────────────── CRUD ─────────────────────
465
+ async create(record) {
466
+ if (record.expiresAt <= Date.now()) {
467
+ this.logger?.warn?.("[SqliteTaskStore] create: record already expired", { taskId: record.taskId });
468
+ return;
469
+ }
470
+ this.prepared().insert.run(
471
+ record.taskId,
472
+ record.sessionId,
473
+ record.status,
474
+ record.expiresAt,
475
+ new Date(record.createdAt).getTime(),
476
+ new Date(record.lastUpdatedAt).getTime(),
477
+ record.executor?.pid ?? null,
478
+ this.serializeRecord(record)
479
+ );
480
+ }
481
+ async get(taskId, sessionId) {
482
+ const row = this.prepared().get.get(taskId, sessionId);
483
+ if (!row) return null;
484
+ if (row.expires_at <= Date.now()) {
485
+ this.prepared().del.run(taskId, sessionId);
486
+ return null;
487
+ }
488
+ const record = this.rowToRecord(row);
489
+ if ((record.status === "working" || record.status === "input_required") && record.executor?.host === "cli" && typeof record.executor.pid === "number" && !this.liveness(record.executor.pid)) {
490
+ const patched = await this.update(taskId, sessionId, {
491
+ status: "failed",
492
+ statusMessage: "Task runner exited before completing the task"
493
+ });
494
+ if (patched) {
495
+ await this.publishTerminal(patched);
496
+ return patched;
497
+ }
498
+ }
499
+ return record;
500
+ }
501
+ async update(taskId, sessionId, patch) {
502
+ const row = this.prepared().get.get(taskId, sessionId);
503
+ if (!row) return null;
504
+ if (row.expires_at <= Date.now()) {
505
+ this.prepared().del.run(taskId, sessionId);
506
+ return null;
507
+ }
508
+ const existing = this.rowToRecord(row);
509
+ const merged = {
510
+ ...existing,
511
+ ...patch,
512
+ taskId: existing.taskId,
513
+ sessionId: existing.sessionId,
514
+ createdAt: existing.createdAt,
515
+ lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
516
+ };
517
+ if (merged.expiresAt <= Date.now()) {
518
+ this.prepared().del.run(taskId, sessionId);
519
+ return null;
520
+ }
521
+ this.prepared().update.run(
522
+ merged.status,
523
+ merged.expiresAt,
524
+ new Date(merged.lastUpdatedAt).getTime(),
525
+ merged.executor?.pid ?? null,
526
+ this.serializeRecord(merged),
527
+ taskId,
528
+ sessionId
529
+ );
530
+ return merged;
531
+ }
532
+ async delete(taskId, sessionId) {
533
+ this.prepared().del.run(taskId, sessionId);
534
+ }
535
+ async list(sessionId, opts = {}) {
536
+ const pageSize = Math.max(1, Math.min(opts.pageSize ?? DEFAULT_PAGE_SIZE, 500));
537
+ const offset = opts.cursor ? decodeCursor(opts.cursor) : 0;
538
+ const rows = this.prepared().listBySession.all(sessionId, Date.now(), pageSize, offset);
539
+ const tasks = [];
540
+ for (const row of rows) {
541
+ let record = this.rowToRecord(row);
542
+ if ((record.status === "working" || record.status === "input_required") && record.executor?.host === "cli" && typeof record.executor.pid === "number" && !this.liveness(record.executor.pid)) {
543
+ const patched = await this.update(record.taskId, sessionId, {
544
+ status: "failed",
545
+ statusMessage: "Task runner exited before completing the task"
546
+ });
547
+ if (patched) {
548
+ await this.publishTerminal(patched);
549
+ record = patched;
550
+ }
551
+ }
552
+ tasks.push(record);
553
+ }
554
+ const total = this.prepared().countBySession.get(sessionId, Date.now()).n;
555
+ const page = { tasks };
556
+ if (offset + rows.length < total) {
557
+ page.nextCursor = encodeCursor(offset + rows.length);
558
+ }
559
+ return page;
560
+ }
561
+ // ───────────────────── Pub/Sub (in-process) ─────────────────────
562
+ async subscribeTerminal(taskId, _sessionId, cb) {
563
+ const channel = `terminal:${taskId}`;
564
+ const handler = (record) => cb(record);
565
+ this.emitter.on(channel, handler);
566
+ return async () => {
567
+ this.emitter.removeListener(channel, handler);
568
+ };
569
+ }
570
+ async publishTerminal(record) {
571
+ this.emitter.emit(`terminal:${record.taskId}`, record);
572
+ }
573
+ async subscribeCancel(taskId, _sessionId, cb) {
574
+ const channel = `cancel:${taskId}`;
575
+ const handler = () => cb();
576
+ this.emitter.on(channel, handler);
577
+ return async () => {
578
+ this.emitter.removeListener(channel, handler);
579
+ };
580
+ }
581
+ async publishCancel(taskId, _sessionId) {
582
+ this.emitter.emit(`cancel:${taskId}`);
583
+ }
584
+ // ───────────────────── Misc ─────────────────────
585
+ async destroy() {
586
+ if (this.cleanupTimer) clearInterval(this.cleanupTimer);
587
+ this.emitter.removeAllListeners();
588
+ try {
589
+ this.db.close();
590
+ } catch {
591
+ }
592
+ }
593
+ purgeExpired() {
594
+ try {
595
+ const res = this.prepared().cleanup.run(Date.now());
596
+ return res.changes;
597
+ } catch (err) {
598
+ this.logger?.warn?.("[SqliteTaskStore] purgeExpired failed", {
599
+ error: err instanceof Error ? err.message : String(err)
600
+ });
601
+ return 0;
602
+ }
603
+ }
604
+ getDatabase() {
605
+ return this.db;
606
+ }
607
+ rowToRecord(row) {
608
+ const plaintext = this.encryptionKey ? decryptValue(this.encryptionKey, row.record_json) : row.record_json;
609
+ return JSON.parse(plaintext);
610
+ }
611
+ /**
612
+ * Serialize + optionally encrypt a TaskRecord before it lands in the DB.
613
+ * Only the `record_json` column is encrypted; indexed columns stay plaintext.
614
+ */
615
+ serializeRecord(record) {
616
+ const json = JSON.stringify(record);
617
+ return this.encryptionKey ? encryptValue(this.encryptionKey, json) : json;
618
+ }
619
+ };
620
+ function encodeCursor(offset) {
621
+ return toB64Url(new TextEncoder().encode(JSON.stringify({ offset })));
622
+ }
623
+ function decodeCursor(cursor) {
624
+ try {
625
+ const bytes = fromB64Url(cursor);
626
+ const parsed = JSON.parse(new TextDecoder().decode(bytes));
627
+ if (typeof parsed?.offset === "number" && parsed.offset >= 0 && Number.isInteger(parsed.offset)) {
628
+ return parsed.offset;
629
+ }
630
+ } catch {
631
+ }
632
+ return 0;
633
+ }
634
+ function toB64Url(data) {
635
+ const b64 = typeof Buffer !== "undefined" ? Buffer.from(data).toString("base64") : btoa(Array.from(data, (b) => String.fromCodePoint(b)).join(""));
636
+ return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
637
+ }
638
+ function fromB64Url(data) {
639
+ let b64 = data.replace(/-/g, "+").replace(/_/g, "/");
640
+ while (b64.length % 4) b64 += "=";
641
+ if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(b64, "base64"));
642
+ const bin = atob(b64);
643
+ return Uint8Array.from(bin, (c) => c.codePointAt(0) ?? 0);
644
+ }
645
+
374
646
  // libs/storage-sqlite/src/sqlite-event.store.ts
375
647
  var SqliteEventStore = class {
376
648
  db;
@@ -518,7 +790,7 @@ var SqliteEventStore = class {
518
790
  };
519
791
 
520
792
  // libs/storage-sqlite/src/sqlite.options.ts
521
- import { z } from "zod";
793
+ import { z } from "@frontmcp/lazy-zod";
522
794
  var sqliteStorageOptionsSchema = z.object({
523
795
  path: z.string().min(1),
524
796
  encryption: z.object({
@@ -532,6 +804,7 @@ export {
532
804
  SqliteEventStore,
533
805
  SqliteKvStore,
534
806
  SqliteSessionStore,
807
+ SqliteTaskStore,
535
808
  decryptValue,
536
809
  deriveEncryptionKey,
537
810
  encryptValue,
package/esm/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/storage-sqlite",
3
- "version": "1.0.4",
3
+ "version": "1.1.0-beta.1",
4
4
  "description": "SQLite storage backend for FrontMCP - local session, elicitation, and event persistence without Redis",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://docs.agentfront.dev",
@@ -44,11 +44,11 @@
44
44
  "node": ">=24.0.0"
45
45
  },
46
46
  "dependencies": {
47
- "@frontmcp/utils": "1.0.4",
47
+ "@frontmcp/utils": "1.1.0-beta.1",
48
48
  "better-sqlite3": "^12.6.2"
49
49
  },
50
50
  "peerDependencies": {
51
- "zod": "^4.0.0"
51
+ "@frontmcp/lazy-zod": "1.1.0-beta.1"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/better-sqlite3": "^7.6.13",
package/index.d.ts CHANGED
@@ -9,6 +9,8 @@ export { SqliteSessionStore } from './sqlite-session.store';
9
9
  export type { SqliteSessionStoreOptions, SessionStoreInterface, StoredSessionData } from './sqlite-session.store';
10
10
  export { SqliteElicitationStore } from './sqlite-elicitation.store';
11
11
  export type { SqliteElicitationStoreOptions, ElicitationStoreInterface } from './sqlite-elicitation.store';
12
+ export { SqliteTaskStore } from './sqlite-task.store';
13
+ export type { SqliteTaskStoreOptions, TaskStoreInterface, TaskRecord as SqliteTaskRecord, TaskStatus as SqliteTaskStatus, TaskOutcome as SqliteTaskOutcome, TaskListPage as SqliteTaskListPage, ProcessLivenessProbe, SqliteTaskLogger, } from './sqlite-task.store';
12
14
  export { SqliteEventStore } from './sqlite-event.store';
13
15
  export type { SqliteEventStoreOptions, EventStoreInterface } from './sqlite-event.store';
14
16
  export { deriveEncryptionKey, encryptValue, decryptValue } from './encryption';
package/index.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,YAAY,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAClH,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpE,YAAY,EAAE,6BAA6B,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAC3G,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,YAAY,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AACzF,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAC9D,YAAY,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,YAAY,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAClH,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpE,YAAY,EAAE,6BAA6B,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAC3G,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,YAAY,EACV,sBAAsB,EACtB,kBAAkB,EAClB,UAAU,IAAI,gBAAgB,EAC9B,UAAU,IAAI,gBAAgB,EAC9B,WAAW,IAAI,iBAAiB,EAChC,YAAY,IAAI,kBAAkB,EAClC,oBAAoB,EACpB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,YAAY,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AACzF,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAC9D,YAAY,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC"}
package/index.js CHANGED
@@ -24,6 +24,7 @@ __export(index_exports, {
24
24
  SqliteEventStore: () => SqliteEventStore,
25
25
  SqliteKvStore: () => SqliteKvStore,
26
26
  SqliteSessionStore: () => SqliteSessionStore,
27
+ SqliteTaskStore: () => SqliteTaskStore,
27
28
  decryptValue: () => decryptValue,
28
29
  deriveEncryptionKey: () => deriveEncryptionKey,
29
30
  encryptValue: () => encryptValue,
@@ -390,6 +391,278 @@ var SqliteElicitationStore = class {
390
391
  }
391
392
  };
392
393
 
394
+ // libs/storage-sqlite/src/sqlite-task.store.ts
395
+ var import_node_events2 = require("node:events");
396
+ var defaultLivenessProbe = (pid) => {
397
+ try {
398
+ process.kill(pid, 0);
399
+ return true;
400
+ } catch {
401
+ return false;
402
+ }
403
+ };
404
+ var DEFAULT_PAGE_SIZE = 50;
405
+ var SqliteTaskStore = class {
406
+ db;
407
+ emitter = new import_node_events2.EventEmitter();
408
+ logger;
409
+ liveness;
410
+ cleanupTimer;
411
+ /**
412
+ * AES-256-GCM key derived from `options.encryption.secret`, applied to the
413
+ * `record_json` column on write and reversed on read. Other columns
414
+ * (`task_id`, `session_id`, `status`, `expires_at`, `executor_pid`) are kept
415
+ * in plaintext so the indexes remain usable.
416
+ */
417
+ encryptionKey = null;
418
+ stmts = null;
419
+ constructor(options) {
420
+ const BetterSqlite3 = require("better-sqlite3");
421
+ try {
422
+ this.db = new BetterSqlite3(options.path);
423
+ } catch (err) {
424
+ const message = err instanceof Error ? err.message : String(err);
425
+ throw new Error(`SqliteTaskStore: failed to open database at "${options.path}": ${message}`);
426
+ }
427
+ if (options.walMode !== false) {
428
+ this.db.pragma("journal_mode = WAL");
429
+ }
430
+ this.logger = options.logger;
431
+ this.liveness = options.livenessProbe ?? defaultLivenessProbe;
432
+ if (options.encryption?.secret) {
433
+ this.encryptionKey = deriveEncryptionKey(options.encryption.secret);
434
+ }
435
+ this.emitter.setMaxListeners(200);
436
+ this.initSchema();
437
+ this.prepareStatements();
438
+ const cleanupInterval = options.ttlCleanupIntervalMs ?? 6e4;
439
+ if (cleanupInterval > 0) {
440
+ this.cleanupTimer = setInterval(() => this.purgeExpired(), cleanupInterval);
441
+ this.cleanupTimer.unref?.();
442
+ } else {
443
+ this.cleanupTimer = null;
444
+ }
445
+ }
446
+ initSchema() {
447
+ this.db.exec(`
448
+ CREATE TABLE IF NOT EXISTS mcp_tasks (
449
+ task_id TEXT PRIMARY KEY,
450
+ session_id TEXT NOT NULL,
451
+ status TEXT NOT NULL,
452
+ expires_at INTEGER NOT NULL,
453
+ created_at INTEGER NOT NULL,
454
+ updated_at INTEGER NOT NULL,
455
+ executor_pid INTEGER,
456
+ record_json TEXT NOT NULL
457
+ );
458
+ CREATE INDEX IF NOT EXISTS idx_mcp_tasks_session ON mcp_tasks (session_id);
459
+ CREATE INDEX IF NOT EXISTS idx_mcp_tasks_status ON mcp_tasks (status);
460
+ CREATE INDEX IF NOT EXISTS idx_mcp_tasks_expires ON mcp_tasks (expires_at);
461
+ `);
462
+ }
463
+ prepareStatements() {
464
+ this.stmts = {
465
+ insert: this.db.prepare(
466
+ "INSERT INTO mcp_tasks (task_id, session_id, status, expires_at, created_at, updated_at, executor_pid, record_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
467
+ ),
468
+ get: this.db.prepare("SELECT * FROM mcp_tasks WHERE task_id = ? AND session_id = ?"),
469
+ update: this.db.prepare(
470
+ "UPDATE mcp_tasks SET status = ?, expires_at = ?, updated_at = ?, executor_pid = ?, record_json = ? WHERE task_id = ? AND session_id = ?"
471
+ ),
472
+ del: this.db.prepare("DELETE FROM mcp_tasks WHERE task_id = ? AND session_id = ?"),
473
+ listBySession: this.db.prepare(
474
+ "SELECT * FROM mcp_tasks WHERE session_id = ? AND expires_at > ? ORDER BY created_at ASC, task_id ASC LIMIT ? OFFSET ?"
475
+ ),
476
+ countBySession: this.db.prepare("SELECT COUNT(*) as n FROM mcp_tasks WHERE session_id = ? AND expires_at > ?"),
477
+ cleanup: this.db.prepare("DELETE FROM mcp_tasks WHERE expires_at <= ?")
478
+ };
479
+ }
480
+ prepared() {
481
+ if (!this.stmts) throw new Error("SqliteTaskStore: prepared statements not initialized");
482
+ return this.stmts;
483
+ }
484
+ // ───────────────────── CRUD ─────────────────────
485
+ async create(record) {
486
+ if (record.expiresAt <= Date.now()) {
487
+ this.logger?.warn?.("[SqliteTaskStore] create: record already expired", { taskId: record.taskId });
488
+ return;
489
+ }
490
+ this.prepared().insert.run(
491
+ record.taskId,
492
+ record.sessionId,
493
+ record.status,
494
+ record.expiresAt,
495
+ new Date(record.createdAt).getTime(),
496
+ new Date(record.lastUpdatedAt).getTime(),
497
+ record.executor?.pid ?? null,
498
+ this.serializeRecord(record)
499
+ );
500
+ }
501
+ async get(taskId, sessionId) {
502
+ const row = this.prepared().get.get(taskId, sessionId);
503
+ if (!row) return null;
504
+ if (row.expires_at <= Date.now()) {
505
+ this.prepared().del.run(taskId, sessionId);
506
+ return null;
507
+ }
508
+ const record = this.rowToRecord(row);
509
+ if ((record.status === "working" || record.status === "input_required") && record.executor?.host === "cli" && typeof record.executor.pid === "number" && !this.liveness(record.executor.pid)) {
510
+ const patched = await this.update(taskId, sessionId, {
511
+ status: "failed",
512
+ statusMessage: "Task runner exited before completing the task"
513
+ });
514
+ if (patched) {
515
+ await this.publishTerminal(patched);
516
+ return patched;
517
+ }
518
+ }
519
+ return record;
520
+ }
521
+ async update(taskId, sessionId, patch) {
522
+ const row = this.prepared().get.get(taskId, sessionId);
523
+ if (!row) return null;
524
+ if (row.expires_at <= Date.now()) {
525
+ this.prepared().del.run(taskId, sessionId);
526
+ return null;
527
+ }
528
+ const existing = this.rowToRecord(row);
529
+ const merged = {
530
+ ...existing,
531
+ ...patch,
532
+ taskId: existing.taskId,
533
+ sessionId: existing.sessionId,
534
+ createdAt: existing.createdAt,
535
+ lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
536
+ };
537
+ if (merged.expiresAt <= Date.now()) {
538
+ this.prepared().del.run(taskId, sessionId);
539
+ return null;
540
+ }
541
+ this.prepared().update.run(
542
+ merged.status,
543
+ merged.expiresAt,
544
+ new Date(merged.lastUpdatedAt).getTime(),
545
+ merged.executor?.pid ?? null,
546
+ this.serializeRecord(merged),
547
+ taskId,
548
+ sessionId
549
+ );
550
+ return merged;
551
+ }
552
+ async delete(taskId, sessionId) {
553
+ this.prepared().del.run(taskId, sessionId);
554
+ }
555
+ async list(sessionId, opts = {}) {
556
+ const pageSize = Math.max(1, Math.min(opts.pageSize ?? DEFAULT_PAGE_SIZE, 500));
557
+ const offset = opts.cursor ? decodeCursor(opts.cursor) : 0;
558
+ const rows = this.prepared().listBySession.all(sessionId, Date.now(), pageSize, offset);
559
+ const tasks = [];
560
+ for (const row of rows) {
561
+ let record = this.rowToRecord(row);
562
+ if ((record.status === "working" || record.status === "input_required") && record.executor?.host === "cli" && typeof record.executor.pid === "number" && !this.liveness(record.executor.pid)) {
563
+ const patched = await this.update(record.taskId, sessionId, {
564
+ status: "failed",
565
+ statusMessage: "Task runner exited before completing the task"
566
+ });
567
+ if (patched) {
568
+ await this.publishTerminal(patched);
569
+ record = patched;
570
+ }
571
+ }
572
+ tasks.push(record);
573
+ }
574
+ const total = this.prepared().countBySession.get(sessionId, Date.now()).n;
575
+ const page = { tasks };
576
+ if (offset + rows.length < total) {
577
+ page.nextCursor = encodeCursor(offset + rows.length);
578
+ }
579
+ return page;
580
+ }
581
+ // ───────────────────── Pub/Sub (in-process) ─────────────────────
582
+ async subscribeTerminal(taskId, _sessionId, cb) {
583
+ const channel = `terminal:${taskId}`;
584
+ const handler = (record) => cb(record);
585
+ this.emitter.on(channel, handler);
586
+ return async () => {
587
+ this.emitter.removeListener(channel, handler);
588
+ };
589
+ }
590
+ async publishTerminal(record) {
591
+ this.emitter.emit(`terminal:${record.taskId}`, record);
592
+ }
593
+ async subscribeCancel(taskId, _sessionId, cb) {
594
+ const channel = `cancel:${taskId}`;
595
+ const handler = () => cb();
596
+ this.emitter.on(channel, handler);
597
+ return async () => {
598
+ this.emitter.removeListener(channel, handler);
599
+ };
600
+ }
601
+ async publishCancel(taskId, _sessionId) {
602
+ this.emitter.emit(`cancel:${taskId}`);
603
+ }
604
+ // ───────────────────── Misc ─────────────────────
605
+ async destroy() {
606
+ if (this.cleanupTimer) clearInterval(this.cleanupTimer);
607
+ this.emitter.removeAllListeners();
608
+ try {
609
+ this.db.close();
610
+ } catch {
611
+ }
612
+ }
613
+ purgeExpired() {
614
+ try {
615
+ const res = this.prepared().cleanup.run(Date.now());
616
+ return res.changes;
617
+ } catch (err) {
618
+ this.logger?.warn?.("[SqliteTaskStore] purgeExpired failed", {
619
+ error: err instanceof Error ? err.message : String(err)
620
+ });
621
+ return 0;
622
+ }
623
+ }
624
+ getDatabase() {
625
+ return this.db;
626
+ }
627
+ rowToRecord(row) {
628
+ const plaintext = this.encryptionKey ? decryptValue(this.encryptionKey, row.record_json) : row.record_json;
629
+ return JSON.parse(plaintext);
630
+ }
631
+ /**
632
+ * Serialize + optionally encrypt a TaskRecord before it lands in the DB.
633
+ * Only the `record_json` column is encrypted; indexed columns stay plaintext.
634
+ */
635
+ serializeRecord(record) {
636
+ const json = JSON.stringify(record);
637
+ return this.encryptionKey ? encryptValue(this.encryptionKey, json) : json;
638
+ }
639
+ };
640
+ function encodeCursor(offset) {
641
+ return toB64Url(new TextEncoder().encode(JSON.stringify({ offset })));
642
+ }
643
+ function decodeCursor(cursor) {
644
+ try {
645
+ const bytes = fromB64Url(cursor);
646
+ const parsed = JSON.parse(new TextDecoder().decode(bytes));
647
+ if (typeof parsed?.offset === "number" && parsed.offset >= 0 && Number.isInteger(parsed.offset)) {
648
+ return parsed.offset;
649
+ }
650
+ } catch {
651
+ }
652
+ return 0;
653
+ }
654
+ function toB64Url(data) {
655
+ const b64 = typeof Buffer !== "undefined" ? Buffer.from(data).toString("base64") : btoa(Array.from(data, (b) => String.fromCodePoint(b)).join(""));
656
+ return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
657
+ }
658
+ function fromB64Url(data) {
659
+ let b64 = data.replace(/-/g, "+").replace(/_/g, "/");
660
+ while (b64.length % 4) b64 += "=";
661
+ if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(b64, "base64"));
662
+ const bin = atob(b64);
663
+ return Uint8Array.from(bin, (c) => c.codePointAt(0) ?? 0);
664
+ }
665
+
393
666
  // libs/storage-sqlite/src/sqlite-event.store.ts
394
667
  var SqliteEventStore = class {
395
668
  db;
@@ -537,14 +810,14 @@ var SqliteEventStore = class {
537
810
  };
538
811
 
539
812
  // libs/storage-sqlite/src/sqlite.options.ts
540
- var import_zod = require("zod");
541
- var sqliteStorageOptionsSchema = import_zod.z.object({
542
- path: import_zod.z.string().min(1),
543
- encryption: import_zod.z.object({
544
- secret: import_zod.z.string().min(1)
813
+ var import_lazy_zod = require("@frontmcp/lazy-zod");
814
+ var sqliteStorageOptionsSchema = import_lazy_zod.z.object({
815
+ path: import_lazy_zod.z.string().min(1),
816
+ encryption: import_lazy_zod.z.object({
817
+ secret: import_lazy_zod.z.string().min(1)
545
818
  }).optional(),
546
- ttlCleanupIntervalMs: import_zod.z.number().int().nonnegative().optional().default(6e4),
547
- walMode: import_zod.z.boolean().optional().default(true)
819
+ ttlCleanupIntervalMs: import_lazy_zod.z.number().int().nonnegative().optional().default(6e4),
820
+ walMode: import_lazy_zod.z.boolean().optional().default(true)
548
821
  });
549
822
  // Annotate the CommonJS export names for ESM import in node:
550
823
  0 && (module.exports = {
@@ -552,6 +825,7 @@ var sqliteStorageOptionsSchema = import_zod.z.object({
552
825
  SqliteEventStore,
553
826
  SqliteKvStore,
554
827
  SqliteSessionStore,
828
+ SqliteTaskStore,
555
829
  decryptValue,
556
830
  deriveEncryptionKey,
557
831
  encryptValue,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/storage-sqlite",
3
- "version": "1.0.4",
3
+ "version": "1.1.0-beta.1",
4
4
  "description": "SQLite storage backend for FrontMCP - local session, elicitation, and event persistence without Redis",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://docs.agentfront.dev",
@@ -44,11 +44,11 @@
44
44
  "node": ">=24.0.0"
45
45
  },
46
46
  "dependencies": {
47
- "@frontmcp/utils": "1.0.4",
47
+ "@frontmcp/utils": "1.1.0-beta.1",
48
48
  "better-sqlite3": "^12.6.2"
49
49
  },
50
50
  "peerDependencies": {
51
- "zod": "^4.0.0"
51
+ "@frontmcp/lazy-zod": "1.1.0-beta.1"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/better-sqlite3": "^7.6.13",
@@ -0,0 +1,140 @@
1
+ /**
2
+ * SQLite Task Store
3
+ *
4
+ * Implements the FrontMCP `TaskStore` interface on top of better-sqlite3.
5
+ * Suitable for single-host deployments — including the CLI runner — where
6
+ * tasks outlive a single HTTP or stdio session but don't need cross-node
7
+ * pub/sub. Pub/sub is served by an in-process EventEmitter (same pattern as
8
+ * SqliteElicitationStore).
9
+ *
10
+ * Schema:
11
+ * ```sql
12
+ * CREATE TABLE IF NOT EXISTS mcp_tasks (
13
+ * task_id TEXT PRIMARY KEY,
14
+ * session_id TEXT NOT NULL,
15
+ * status TEXT NOT NULL,
16
+ * expires_at INTEGER NOT NULL,
17
+ * created_at INTEGER NOT NULL,
18
+ * updated_at INTEGER NOT NULL,
19
+ * executor_pid INTEGER,
20
+ * record_json TEXT NOT NULL
21
+ * );
22
+ * ```
23
+ *
24
+ * @module storage-sqlite/sqlite-task.store
25
+ */
26
+ import type Database from 'better-sqlite3';
27
+ import type { SqliteStorageOptions } from './sqlite.options';
28
+ export interface TaskJsonRpcError {
29
+ code: number;
30
+ message: string;
31
+ data?: unknown;
32
+ }
33
+ export type TaskOutcome = {
34
+ kind: 'ok';
35
+ data: unknown;
36
+ } | {
37
+ kind: 'error';
38
+ error: TaskJsonRpcError;
39
+ };
40
+ export type TaskStatus = 'working' | 'input_required' | 'completed' | 'failed' | 'cancelled';
41
+ export interface TaskRecord {
42
+ taskId: string;
43
+ sessionId: string;
44
+ status: TaskStatus;
45
+ statusMessage?: string;
46
+ createdAt: string;
47
+ lastUpdatedAt: string;
48
+ ttlMs: number | null;
49
+ pollIntervalMs?: number;
50
+ expiresAt: number;
51
+ request: {
52
+ method: 'tools/call';
53
+ params: Record<string, unknown>;
54
+ };
55
+ outcome?: TaskOutcome;
56
+ progressToken?: string | number;
57
+ executor?: {
58
+ host: 'in-process' | 'cli';
59
+ pid?: number;
60
+ spawnedAt?: string;
61
+ };
62
+ }
63
+ export type TaskTerminalCallback = (record: TaskRecord) => void;
64
+ export type TaskCancelCallback = () => void;
65
+ export type TaskUnsubscribe = () => Promise<void>;
66
+ export interface TaskListPage {
67
+ tasks: TaskRecord[];
68
+ nextCursor?: string;
69
+ }
70
+ export interface TaskStoreInterface {
71
+ create(record: TaskRecord): Promise<void>;
72
+ get(taskId: string, sessionId: string): Promise<TaskRecord | null>;
73
+ update(taskId: string, sessionId: string, patch: Partial<TaskRecord>): Promise<TaskRecord | null>;
74
+ delete(taskId: string, sessionId: string): Promise<void>;
75
+ list(sessionId: string, opts?: {
76
+ cursor?: string;
77
+ pageSize?: number;
78
+ }): Promise<TaskListPage>;
79
+ subscribeTerminal(taskId: string, sessionId: string, cb: TaskTerminalCallback): Promise<TaskUnsubscribe>;
80
+ publishTerminal(record: TaskRecord): Promise<void>;
81
+ subscribeCancel(taskId: string, sessionId: string, cb: TaskCancelCallback): Promise<TaskUnsubscribe>;
82
+ publishCancel(taskId: string, sessionId: string): Promise<void>;
83
+ destroy?(): Promise<void>;
84
+ }
85
+ export interface SqliteTaskLogger {
86
+ debug?: (message: string, meta?: Record<string, unknown>) => void;
87
+ warn?: (message: string, meta?: Record<string, unknown>) => void;
88
+ error?: (message: string, meta?: Record<string, unknown>) => void;
89
+ }
90
+ /** Returns `true` if the process with the given PID is alive. */
91
+ export type ProcessLivenessProbe = (pid: number) => boolean;
92
+ export interface SqliteTaskStoreOptions extends SqliteStorageOptions {
93
+ logger?: SqliteTaskLogger;
94
+ /**
95
+ * Override the PID liveness check (primarily for tests).
96
+ * Defaults to `process.kill(pid, 0)` semantics.
97
+ */
98
+ livenessProbe?: ProcessLivenessProbe;
99
+ }
100
+ export declare class SqliteTaskStore implements TaskStoreInterface {
101
+ private readonly db;
102
+ private readonly emitter;
103
+ private readonly logger?;
104
+ private readonly liveness;
105
+ private readonly cleanupTimer;
106
+ /**
107
+ * AES-256-GCM key derived from `options.encryption.secret`, applied to the
108
+ * `record_json` column on write and reversed on read. Other columns
109
+ * (`task_id`, `session_id`, `status`, `expires_at`, `executor_pid`) are kept
110
+ * in plaintext so the indexes remain usable.
111
+ */
112
+ private readonly encryptionKey;
113
+ private stmts;
114
+ constructor(options: SqliteTaskStoreOptions);
115
+ private initSchema;
116
+ private prepareStatements;
117
+ private prepared;
118
+ create(record: TaskRecord): Promise<void>;
119
+ get(taskId: string, sessionId: string): Promise<TaskRecord | null>;
120
+ update(taskId: string, sessionId: string, patch: Partial<TaskRecord>): Promise<TaskRecord | null>;
121
+ delete(taskId: string, sessionId: string): Promise<void>;
122
+ list(sessionId: string, opts?: {
123
+ cursor?: string;
124
+ pageSize?: number;
125
+ }): Promise<TaskListPage>;
126
+ subscribeTerminal(taskId: string, _sessionId: string, cb: TaskTerminalCallback): Promise<TaskUnsubscribe>;
127
+ publishTerminal(record: TaskRecord): Promise<void>;
128
+ subscribeCancel(taskId: string, _sessionId: string, cb: TaskCancelCallback): Promise<TaskUnsubscribe>;
129
+ publishCancel(taskId: string, _sessionId: string): Promise<void>;
130
+ destroy(): Promise<void>;
131
+ purgeExpired(): number;
132
+ getDatabase(): Database.Database;
133
+ private rowToRecord;
134
+ /**
135
+ * Serialize + optionally encrypt a TaskRecord before it lands in the DB.
136
+ * Only the `record_json` column is encrypted; indexed columns stay plaintext.
137
+ */
138
+ private serializeRecord;
139
+ }
140
+ //# sourceMappingURL=sqlite-task.store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-task.store.d.ts","sourceRoot":"","sources":["../src/sqlite-task.store.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Configuration types and Zod schema for SQLite-based storage.
5
5
  */
6
- import { z } from 'zod';
6
+ import { z } from '@frontmcp/lazy-zod';
7
7
  /**
8
8
  * SQLite storage configuration options.
9
9
  */
@@ -32,14 +32,14 @@ export interface SqliteStorageOptions {
32
32
  /**
33
33
  * Zod schema for SQLite storage configuration.
34
34
  */
35
- export declare const sqliteStorageOptionsSchema: z.ZodObject<{
36
- path: z.ZodString;
37
- encryption: z.ZodOptional<z.ZodObject<{
38
- secret: z.ZodString;
39
- }, z.core.$strip>>;
40
- ttlCleanupIntervalMs: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
41
- walMode: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
42
- }, z.core.$strip>;
35
+ export declare const sqliteStorageOptionsSchema: import("@frontmcp/lazy-zod").ZodObject<{
36
+ path: import("@frontmcp/lazy-zod").ZodString;
37
+ encryption: import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodObject<{
38
+ secret: import("@frontmcp/lazy-zod").ZodString;
39
+ }, import("zod/v4/core").$strip>>;
40
+ ttlCleanupIntervalMs: import("@frontmcp/lazy-zod").ZodDefault<import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodNumber>>;
41
+ walMode: import("@frontmcp/lazy-zod").ZodDefault<import("@frontmcp/lazy-zod").ZodOptional<import("@frontmcp/lazy-zod").ZodBoolean>>;
42
+ }, import("zod/v4/core").$strip>;
43
43
  /**
44
44
  * SQLite storage input type (before Zod defaults).
45
45
  */
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite.options.d.ts","sourceRoot":"","sources":["../src/sqlite.options.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,UAAU,CAAC,EAAE;QACX,qEAAqE;QACrE,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IAEF;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,eAAO,MAAM,0BAA0B;;;;;;;iBASrC,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEnF;;GAEG;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC"}
1
+ {"version":3,"file":"sqlite.options.d.ts","sourceRoot":"","sources":["../src/sqlite.options.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,oBAAoB,CAAC;AAEvC;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,UAAU,CAAC,EAAE;QACX,qEAAqE;QACrE,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IAEF;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,eAAO,MAAM,0BAA0B;;;;;;;gCASrC,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEnF;;GAEG;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC"}