gsd-pi 2.80.0-dev.c5c38454b → 2.80.0-dev.e146beb20
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/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +2 -2
- package/dist/resources/extensions/gsd/auto/loop.js +32 -1
- package/dist/resources/extensions/gsd/auto/phases.js +37 -30
- package/dist/resources/extensions/gsd/auto-post-unit.js +10 -10
- package/dist/resources/extensions/gsd/auto-prompts.js +111 -1
- package/dist/resources/extensions/gsd/auto.js +9 -1
- package/dist/resources/extensions/gsd/clean-root-preflight.js +42 -4
- package/dist/resources/extensions/gsd/commands/dispatcher.js +5 -0
- package/dist/resources/extensions/gsd/crash-recovery.js +56 -10
- package/dist/resources/extensions/gsd/detection.js +106 -0
- package/dist/resources/extensions/gsd/guided-flow.js +47 -10
- package/dist/resources/extensions/gsd/planning-path-scope.js +26 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/safety/evidence-collector.js +10 -2
- package/dist/resources/extensions/gsd/tools/plan-slice.js +9 -0
- package/dist/resources/extensions/gsd/tools/plan-task.js +9 -0
- package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +16 -14
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +16 -16
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +16 -16
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +30 -0
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts +15 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.js +66 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.js +24 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js +10 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js +3 -2
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js.map +1 -1
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +36 -0
- package/packages/pi-coding-agent/src/core/db-snapshot.test.ts +32 -0
- package/packages/pi-coding-agent/src/core/db-snapshot.ts +66 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +2 -0
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage-safety-guard.test.ts +14 -0
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts +3 -2
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/src/resources/GSD-WORKFLOW.md +2 -2
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/loop.ts +50 -8
- package/src/resources/extensions/gsd/auto/phases.ts +42 -28
- package/src/resources/extensions/gsd/auto-post-unit.ts +10 -10
- package/src/resources/extensions/gsd/auto-prompts.ts +116 -1
- package/src/resources/extensions/gsd/auto.ts +12 -1
- package/src/resources/extensions/gsd/clean-root-preflight.ts +41 -3
- package/src/resources/extensions/gsd/commands/dispatcher.ts +6 -0
- package/src/resources/extensions/gsd/crash-recovery.ts +67 -10
- package/src/resources/extensions/gsd/detection.ts +128 -0
- package/src/resources/extensions/gsd/guided-flow.ts +47 -10
- package/src/resources/extensions/gsd/planning-path-scope.ts +35 -0
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/safety/evidence-collector.ts +11 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +88 -2
- package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/detection.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/right-sized-workflow-prompts.test.ts +192 -0
- package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +46 -2
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +37 -6
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +9 -2
- package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +10 -0
- package/src/resources/extensions/gsd/unit-runtime.ts +14 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +15 -4
- /package/dist/web/standalone/.next/static/{TCSim36ZpcPu2WgeoC45g → y73quA-XdLo9n41nxphjW}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{TCSim36ZpcPu2WgeoC45g → y73quA-XdLo9n41nxphjW}/_ssgManifest.js +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../../../../src/resources/extensions/memory/storage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SQLite storage for the memory extraction pipeline.\n *\n * Tables:\n * - threads: tracks session files and their processing state\n * - stage1_outputs: stores per-thread extraction results\n * - jobs: lease-based job queue for pipeline phases\n */\n\nimport initSqlJs, { type Database as SqlJsDatabase } from \"sql.js\";\nimport { randomUUID } from \"crypto\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { dirname } from \"path\";\n\nexport interface ThreadRow {\n\tthread_id: string;\n\tfile_path: string;\n\tfile_size: number;\n\tfile_mtime: number;\n\tcwd: string;\n\tstatus: \"pending\" | \"processing\" | \"done\" | \"error\";\n\terror_message: string | null;\n\tcreated_at: string;\n\tupdated_at: string;\n}\n\nexport interface Stage1OutputRow {\n\tthread_id: string;\n\textraction_json: string;\n\tcreated_at: string;\n}\n\nexport interface JobRow {\n\tid: string;\n\tphase: \"stage1\" | \"stage2\";\n\tthread_id: string | null;\n\tstatus: \"pending\" | \"claimed\" | \"done\" | \"error\";\n\tworker_id: string | null;\n\townership_token: string | null;\n\tlease_expires_at: string | null;\n\terror_message: string | null;\n\tcreated_at: string;\n\tupdated_at: string;\n}\n\nexport class MemoryStorage {\n\tprivate db: SqlJsDatabase;\n\tprivate dbPath: string;\n\tprivate persistTimer: ReturnType<typeof setTimeout> | null = null;\n\n\tprivate constructor(db: SqlJsDatabase, dbPath: string) {\n\t\tthis.db = db;\n\t\tthis.dbPath = dbPath;\n\t}\n\n\tstatic async create(dbPath: string): Promise<MemoryStorage> {\n\t\tconst dir = dirname(dbPath);\n\t\tif (!existsSync(dir)) {\n\t\t\tmkdirSync(dir, { recursive: true });\n\t\t}\n\n\t\tconst SQL = await initSqlJs();\n\t\tconst buffer = existsSync(dbPath) ? readFileSync(dbPath) : undefined;\n\t\tconst db = buffer ? new SQL.Database(buffer) : new SQL.Database();\n\n\t\tdb.run(\"PRAGMA journal_mode = WAL\");\n\t\tdb.run(\"PRAGMA synchronous = NORMAL\");\n\t\tdb.run(\"PRAGMA busy_timeout = 5000\");\n\n\t\tconst storage = new MemoryStorage(db, dbPath);\n\t\tstorage.initSchema();\n\t\treturn storage;\n\t}\n\n\tprivate persist(): void {\n\t\tconst data = this.db.export();\n\t\twriteFileSync(this.dbPath, Buffer.from(data));\n\t}\n\n\tprivate schedulePersist(): void {\n\t\tif (this.persistTimer) {\n\t\t\tclearTimeout(this.persistTimer);\n\t\t}\n\t\tthis.persistTimer = setTimeout(() => {\n\t\t\tthis.persistTimer = null;\n\t\t\tthis.persist();\n\t\t}, 500);\n\t}\n\n\tprivate initSchema(): void {\n\t\tthis.db.run(`\n\t\t\tCREATE TABLE IF NOT EXISTS threads (\n\t\t\t\tthread_id TEXT PRIMARY KEY,\n\t\t\t\tfile_path TEXT NOT NULL,\n\t\t\t\tfile_size INTEGER NOT NULL DEFAULT 0,\n\t\t\t\tfile_mtime INTEGER NOT NULL DEFAULT 0,\n\t\t\t\tcwd TEXT NOT NULL DEFAULT '',\n\t\t\t\tstatus TEXT NOT NULL DEFAULT 'pending',\n\t\t\t\terror_message TEXT,\n\t\t\t\tcreated_at TEXT NOT NULL DEFAULT (datetime('now')),\n\t\t\t\tupdated_at TEXT NOT NULL DEFAULT (datetime('now'))\n\t\t\t)\n\t\t`);\n\t\tthis.db.run(`\n\t\t\tCREATE TABLE IF NOT EXISTS stage1_outputs (\n\t\t\t\tthread_id TEXT PRIMARY KEY,\n\t\t\t\textraction_json TEXT NOT NULL,\n\t\t\t\tcreated_at TEXT NOT NULL DEFAULT (datetime('now')),\n\t\t\t\tFOREIGN KEY (thread_id) REFERENCES threads(thread_id) ON DELETE CASCADE\n\t\t\t)\n\t\t`);\n\t\tthis.db.run(`\n\t\t\tCREATE TABLE IF NOT EXISTS jobs (\n\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\tphase TEXT NOT NULL,\n\t\t\t\tthread_id TEXT,\n\t\t\t\tstatus TEXT NOT NULL DEFAULT 'pending',\n\t\t\t\tworker_id TEXT,\n\t\t\t\townership_token TEXT,\n\t\t\t\tlease_expires_at TEXT,\n\t\t\t\terror_message TEXT,\n\t\t\t\tcreated_at TEXT NOT NULL DEFAULT (datetime('now')),\n\t\t\t\tupdated_at TEXT NOT NULL DEFAULT (datetime('now'))\n\t\t\t)\n\t\t`);\n\t\tthis.db.run(\"CREATE INDEX IF NOT EXISTS idx_jobs_phase_status ON jobs(phase, status)\");\n\t\tthis.db.run(\"CREATE INDEX IF NOT EXISTS idx_threads_status ON threads(status)\");\n\t\tthis.db.run(\"CREATE INDEX IF NOT EXISTS idx_threads_cwd ON threads(cwd)\");\n\t\tthis.persist();\n\t}\n\n\tprivate queryAll<T>(sql: string, params: unknown[] = []): T[] {\n\t\tconst stmt = this.db.prepare(sql);\n\t\tstmt.bind(params as (string | number | null | Uint8Array)[]);\n\t\tconst rows: T[] = [];\n\t\twhile (stmt.step()) {\n\t\t\trows.push(stmt.getAsObject() as T);\n\t\t}\n\t\tstmt.free();\n\t\treturn rows;\n\t}\n\n\tprivate queryOne<T>(sql: string, params: unknown[] = []): T | undefined {\n\t\tconst rows = this.queryAll<T>(sql, params);\n\t\treturn rows[0];\n\t}\n\n\t/**\n\t * Insert or update thread records. Skips threads whose file hasn't changed\n\t * (same size + mtime = watermark match).\n\t */\n\tupsertThreads(\n\t\tthreads: Array<{\n\t\t\tthreadId: string;\n\t\t\tfilePath: string;\n\t\t\tfileSize: number;\n\t\t\tfileMtime: number;\n\t\t\tcwd: string;\n\t\t}>,\n\t): { inserted: number; updated: number; skipped: number } {\n\t\tlet inserted = 0;\n\t\tlet updated = 0;\n\t\tlet skipped = 0;\n\n\t\tfor (const t of threads) {\n\t\t\tconst existing = this.queryOne<{ file_size: number; file_mtime: number; status: string }>(\n\t\t\t\t\"SELECT file_size, file_mtime, status FROM threads WHERE thread_id = ?\",\n\t\t\t\t[t.threadId],\n\t\t\t);\n\n\t\t\tif (!existing) {\n\t\t\t\tthis.db.run(\n\t\t\t\t\t\"INSERT INTO threads (thread_id, file_path, file_size, file_mtime, cwd, status) VALUES (?, ?, ?, ?, ?, 'pending')\",\n\t\t\t\t\t[t.threadId, t.filePath, t.fileSize, t.fileMtime, t.cwd],\n\t\t\t\t);\n\t\t\t\tthis.db.run(\n\t\t\t\t\t\"INSERT OR IGNORE INTO jobs (id, phase, thread_id, status) VALUES (?, 'stage1', ?, 'pending')\",\n\t\t\t\t\t[randomUUID(), t.threadId],\n\t\t\t\t);\n\t\t\t\tinserted++;\n\t\t\t} else if (existing.file_size !== t.fileSize || existing.file_mtime !== t.fileMtime) {\n\t\t\t\tthis.db.run(\n\t\t\t\t\t\"UPDATE threads SET file_path = ?, file_size = ?, file_mtime = ?, cwd = ?, status = 'pending', updated_at = datetime('now') WHERE thread_id = ?\",\n\t\t\t\t\t[t.filePath, t.fileSize, t.fileMtime, t.cwd, t.threadId],\n\t\t\t\t);\n\t\t\t\tif (existing.status === \"done\" || existing.status === \"error\") {\n\t\t\t\t\tthis.db.run(\n\t\t\t\t\t\t\"INSERT OR IGNORE INTO jobs (id, phase, thread_id, status) VALUES (?, 'stage1', ?, 'pending')\",\n\t\t\t\t\t\t[randomUUID(), t.threadId],\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tupdated++;\n\t\t\t} else {\n\t\t\t\tskipped++;\n\t\t\t}\n\t\t}\n\n\t\tthis.schedulePersist();\n\t\treturn { inserted, updated, skipped };\n\t}\n\n\t/**\n\t * Claim up to `limit` stage1 jobs for the given worker.\n\t * Uses lease-based ownership with an ownership_token UUID.\n\t */\n\tclaimStage1Jobs(\n\t\tworkerId: string,\n\t\tlimit: number,\n\t\tleaseSeconds: number,\n\t): Array<{ jobId: string; threadId: string; ownershipToken: string }> {\n\t\tconst token = randomUUID();\n\t\tconst expiresAt = new Date(Date.now() + leaseSeconds * 1000).toISOString();\n\n\t\tthis.db.run(\n\t\t\t`UPDATE jobs SET\n\t\t\t\tstatus = 'claimed',\n\t\t\t\tworker_id = ?,\n\t\t\t\townership_token = ?,\n\t\t\t\tlease_expires_at = ?,\n\t\t\t\tupdated_at = datetime('now')\n\t\t\tWHERE id IN (\n\t\t\t\tSELECT id FROM jobs\n\t\t\t\tWHERE phase = 'stage1'\n\t\t\t\t\tAND (status = 'pending' OR (status = 'claimed' AND lease_expires_at < datetime('now')))\n\t\t\t\tLIMIT ?\n\t\t\t)`,\n\t\t\t[workerId, token, expiresAt, limit],\n\t\t);\n\n\t\tconst rows = this.queryAll<{ id: string; thread_id: string }>(\n\t\t\t\"SELECT id, thread_id FROM jobs WHERE ownership_token = ? AND status = 'claimed'\",\n\t\t\t[token],\n\t\t);\n\n\t\tthis.schedulePersist();\n\n\t\treturn rows.map((r) => ({\n\t\t\tjobId: r.id,\n\t\t\tthreadId: r.thread_id,\n\t\t\townershipToken: token,\n\t\t}));\n\t}\n\n\t/**\n\t * Mark a stage1 job as complete and store the extraction output.\n\t */\n\tcompleteStage1Job(threadId: string, output: string): void {\n\t\tthis.db.run(\n\t\t\t\"UPDATE jobs SET status = 'done', updated_at = datetime('now') WHERE thread_id = ? AND phase = 'stage1' AND status = 'claimed'\",\n\t\t\t[threadId],\n\t\t);\n\t\tthis.db.run(\n\t\t\t\"INSERT OR REPLACE INTO stage1_outputs (thread_id, extraction_json, created_at) VALUES (?, ?, datetime('now'))\",\n\t\t\t[threadId, output],\n\t\t);\n\t\tthis.db.run(\n\t\t\t\"UPDATE threads SET status = 'done', updated_at = datetime('now') WHERE thread_id = ?\",\n\t\t\t[threadId],\n\t\t);\n\t\tthis.schedulePersist();\n\t}\n\n\t/**\n\t * Mark a stage1 job as errored.\n\t */\n\tfailStage1Job(threadId: string, errorMessage: string): void {\n\t\tthis.db.run(\n\t\t\t\"UPDATE jobs SET status = 'error', error_message = ?, updated_at = datetime('now') WHERE thread_id = ? AND phase = 'stage1' AND status = 'claimed'\",\n\t\t\t[errorMessage, threadId],\n\t\t);\n\t\tthis.db.run(\n\t\t\t\"UPDATE threads SET status = 'error', error_message = ?, updated_at = datetime('now') WHERE thread_id = ?\",\n\t\t\t[errorMessage, threadId],\n\t\t);\n\t\tthis.schedulePersist();\n\t}\n\n\t/**\n\t * Try to claim the global phase 2 consolidation job.\n\t * Only one worker can hold this at a time.\n\t */\n\ttryClaimGlobalPhase2Job(\n\t\tworkerId: string,\n\t\tleaseSeconds: number,\n\t): { jobId: string; ownershipToken: string } | null {\n\t\tconst token = randomUUID();\n\t\tconst expiresAt = new Date(Date.now() + leaseSeconds * 1000).toISOString();\n\n\t\tconst pendingStage1 = this.queryOne<{ cnt: number }>(\n\t\t\t\"SELECT COUNT(*) as cnt FROM jobs WHERE phase = 'stage1' AND status IN ('pending', 'claimed')\",\n\t\t);\n\n\t\tif (pendingStage1 && pendingStage1.cnt > 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst existingPhase2 = this.queryOne<{ id: string }>(\n\t\t\t\"SELECT id FROM jobs WHERE phase = 'stage2' AND status = 'claimed' AND lease_expires_at > datetime('now')\",\n\t\t);\n\n\t\tif (existingPhase2) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst outputCount = this.queryOne<{ cnt: number }>(\n\t\t\t\"SELECT COUNT(*) as cnt FROM stage1_outputs\",\n\t\t);\n\n\t\tif (!outputCount || outputCount.cnt === 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst jobId = randomUUID();\n\t\tthis.db.run(\n\t\t\t\"INSERT INTO jobs (id, phase, status, worker_id, ownership_token, lease_expires_at) VALUES (?, 'stage2', 'claimed', ?, ?, ?)\",\n\t\t\t[jobId, workerId, token, expiresAt],\n\t\t);\n\n\t\tthis.schedulePersist();\n\t\treturn { jobId, ownershipToken: token };\n\t}\n\n\t/**\n\t * Complete the phase 2 consolidation job.\n\t */\n\tcompletePhase2Job(jobId: string): void {\n\t\tthis.db.run(\n\t\t\t\"UPDATE jobs SET status = 'done', updated_at = datetime('now') WHERE id = ? AND phase = 'stage2'\",\n\t\t\t[jobId],\n\t\t);\n\t\tthis.schedulePersist();\n\t}\n\n\t/**\n\t * Get all stage1 extraction outputs.\n\t */\n\tgetStage1Outputs(): Array<{ threadId: string; extractionJson: string }> {\n\t\tconst rows = this.queryAll<{ thread_id: string; extraction_json: string }>(\n\t\t\t\"SELECT thread_id, extraction_json FROM stage1_outputs\",\n\t\t);\n\n\t\treturn rows.map((r) => ({\n\t\t\tthreadId: r.thread_id,\n\t\t\textractionJson: r.extraction_json,\n\t\t}));\n\t}\n\n\t/**\n\t * Get all stage1 outputs for a specific cwd.\n\t */\n\tgetStage1OutputsForCwd(cwd: string): Array<{ threadId: string; extractionJson: string }> {\n\t\tconst rows = this.queryAll<{ thread_id: string; extraction_json: string }>(\n\t\t\t`SELECT s.thread_id, s.extraction_json FROM stage1_outputs s\n\t\t\tINNER JOIN threads t ON t.thread_id = s.thread_id\n\t\t\tWHERE t.cwd = ?`,\n\t\t\t[cwd],\n\t\t);\n\n\t\treturn rows.map((r) => ({\n\t\t\tthreadId: r.thread_id,\n\t\t\textractionJson: r.extraction_json,\n\t\t}));\n\t}\n\n\t/**\n\t * Get thread info by ID.\n\t */\n\tgetThread(threadId: string): ThreadRow | undefined {\n\t\treturn this.queryOne<ThreadRow>(\n\t\t\t\"SELECT * FROM threads WHERE thread_id = ?\",\n\t\t\t[threadId],\n\t\t);\n\t}\n\n\t/**\n\t * Get pipeline statistics.\n\t */\n\tgetStats(): {\n\t\ttotalThreads: number;\n\t\tpendingThreads: number;\n\t\tdoneThreads: number;\n\t\terrorThreads: number;\n\t\ttotalStage1Outputs: number;\n\t\tpendingStage1Jobs: number;\n\t} {\n\t\tconst threads = this.queryOne<{ total: number; pending: number; done: number; errors: number }>(`\n\t\t\tSELECT\n\t\t\t\tCOUNT(*) as total,\n\t\t\t\tSUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending,\n\t\t\t\tSUM(CASE WHEN status = 'done' THEN 1 ELSE 0 END) as done,\n\t\t\t\tSUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) as errors\n\t\t\tFROM threads\n\t\t`)!;\n\n\t\tconst outputs = this.queryOne<{ cnt: number }>(\n\t\t\t\"SELECT COUNT(*) as cnt FROM stage1_outputs\",\n\t\t)!;\n\n\t\tconst pendingJobs = this.queryOne<{ cnt: number }>(\n\t\t\t\"SELECT COUNT(*) as cnt FROM jobs WHERE phase = 'stage1' AND status IN ('pending', 'claimed')\",\n\t\t)!;\n\n\t\treturn {\n\t\t\ttotalThreads: threads.total,\n\t\t\tpendingThreads: threads.pending,\n\t\t\tdoneThreads: threads.done,\n\t\t\terrorThreads: threads.errors,\n\t\t\ttotalStage1Outputs: outputs.cnt,\n\t\t\tpendingStage1Jobs: pendingJobs.cnt,\n\t\t};\n\t}\n\n\t/**\n\t * Clear all data (for /memory clear).\n\t */\n\tclearAll(): void {\n\t\tthis.db.run(\"DELETE FROM stage1_outputs\");\n\t\tthis.db.run(\"DELETE FROM jobs\");\n\t\tthis.db.run(\"DELETE FROM threads\");\n\t\tthis.schedulePersist();\n\t}\n\n\t/**\n\t * Clear data for a specific cwd (for /memory clear in project scope).\n\t */\n\tclearForCwd(cwd: string): void {\n\t\tthis.db.run(\n\t\t\t\"DELETE FROM stage1_outputs WHERE thread_id IN (SELECT thread_id FROM threads WHERE cwd = ?)\",\n\t\t\t[cwd],\n\t\t);\n\t\tthis.db.run(\n\t\t\t\"DELETE FROM jobs WHERE thread_id IN (SELECT thread_id FROM threads WHERE cwd = ?)\",\n\t\t\t[cwd],\n\t\t);\n\t\tthis.db.run(\"DELETE FROM threads WHERE cwd = ?\", [cwd]);\n\t\tthis.schedulePersist();\n\t}\n\n\t/**\n\t * Reset all threads to pending (for /memory rebuild).\n\t */\n\tresetAllForCwd(cwd: string): void {\n\t\tthis.db.run(\n\t\t\t\"DELETE FROM stage1_outputs WHERE thread_id IN (SELECT thread_id FROM threads WHERE cwd = ?)\",\n\t\t\t[cwd],\n\t\t);\n\t\tthis.db.run(\n\t\t\t\"DELETE FROM jobs WHERE thread_id IN (SELECT thread_id FROM threads WHERE cwd = ?)\",\n\t\t\t[cwd],\n\t\t);\n\t\tthis.db.run(\n\t\t\t\"UPDATE threads SET status = 'pending', updated_at = datetime('now') WHERE cwd = ?\",\n\t\t\t[cwd],\n\t\t);\n\n\t\tconst threads = this.queryAll<{ thread_id: string }>(\n\t\t\t\"SELECT thread_id FROM threads WHERE cwd = ?\",\n\t\t\t[cwd],\n\t\t);\n\n\t\tfor (const t of threads) {\n\t\t\tthis.db.run(\n\t\t\t\t\"INSERT INTO jobs (id, phase, thread_id, status) VALUES (?, 'stage1', ?, 'pending')\",\n\t\t\t\t[randomUUID(), t.thread_id],\n\t\t\t);\n\t\t}\n\t\tthis.schedulePersist();\n\t}\n\n\tclose(): void {\n\t\tif (this.persistTimer) {\n\t\t\tclearTimeout(this.persistTimer);\n\t\t\tthis.persistTimer = null;\n\t\t}\n\t\tthis.persist();\n\t\tthis.db.close();\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../../../../src/resources/extensions/memory/storage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SQLite storage for the memory extraction pipeline.\n *\n * Tables:\n * - threads: tracks session files and their processing state\n * - stage1_outputs: stores per-thread extraction results\n * - jobs: lease-based job queue for pipeline phases\n */\n\nimport initSqlJs, { type Database as SqlJsDatabase } from \"sql.js\";\nimport { randomUUID } from \"crypto\";\nimport { existsSync, mkdirSync, readFileSync } from \"fs\";\nimport { dirname } from \"path\";\nimport { atomicWriteDbSnapshotSync } from \"../../../core/db-snapshot.js\";\n\nexport interface ThreadRow {\n\tthread_id: string;\n\tfile_path: string;\n\tfile_size: number;\n\tfile_mtime: number;\n\tcwd: string;\n\tstatus: \"pending\" | \"processing\" | \"done\" | \"error\";\n\terror_message: string | null;\n\tcreated_at: string;\n\tupdated_at: string;\n}\n\nexport interface Stage1OutputRow {\n\tthread_id: string;\n\textraction_json: string;\n\tcreated_at: string;\n}\n\nexport interface JobRow {\n\tid: string;\n\tphase: \"stage1\" | \"stage2\";\n\tthread_id: string | null;\n\tstatus: \"pending\" | \"claimed\" | \"done\" | \"error\";\n\tworker_id: string | null;\n\townership_token: string | null;\n\tlease_expires_at: string | null;\n\terror_message: string | null;\n\tcreated_at: string;\n\tupdated_at: string;\n}\n\nexport class MemoryStorage {\n\tprivate db: SqlJsDatabase;\n\tprivate dbPath: string;\n\tprivate persistTimer: ReturnType<typeof setTimeout> | null = null;\n\n\tprivate constructor(db: SqlJsDatabase, dbPath: string) {\n\t\tthis.db = db;\n\t\tthis.dbPath = dbPath;\n\t}\n\n\tstatic async create(dbPath: string): Promise<MemoryStorage> {\n\t\tconst dir = dirname(dbPath);\n\t\tif (!existsSync(dir)) {\n\t\t\tmkdirSync(dir, { recursive: true });\n\t\t}\n\n\t\tconst SQL = await initSqlJs();\n\t\tconst buffer = existsSync(dbPath) ? readFileSync(dbPath) : undefined;\n\t\tconst db = buffer ? new SQL.Database(buffer) : new SQL.Database();\n\n\t\tdb.run(\"PRAGMA journal_mode = WAL\");\n\t\tdb.run(\"PRAGMA synchronous = NORMAL\");\n\t\tdb.run(\"PRAGMA busy_timeout = 5000\");\n\n\t\tconst storage = new MemoryStorage(db, dbPath);\n\t\tstorage.initSchema();\n\t\treturn storage;\n\t}\n\n\tprivate persist(): void {\n\t\tconst data = this.db.export();\n\t\tatomicWriteDbSnapshotSync(this.dbPath, data);\n\t}\n\n\tprivate schedulePersist(): void {\n\t\tif (this.persistTimer) {\n\t\t\tclearTimeout(this.persistTimer);\n\t\t}\n\t\tthis.persistTimer = setTimeout(() => {\n\t\t\tthis.persistTimer = null;\n\t\t\tthis.persist();\n\t\t}, 500);\n\t}\n\n\tprivate initSchema(): void {\n\t\tthis.db.run(`\n\t\t\tCREATE TABLE IF NOT EXISTS threads (\n\t\t\t\tthread_id TEXT PRIMARY KEY,\n\t\t\t\tfile_path TEXT NOT NULL,\n\t\t\t\tfile_size INTEGER NOT NULL DEFAULT 0,\n\t\t\t\tfile_mtime INTEGER NOT NULL DEFAULT 0,\n\t\t\t\tcwd TEXT NOT NULL DEFAULT '',\n\t\t\t\tstatus TEXT NOT NULL DEFAULT 'pending',\n\t\t\t\terror_message TEXT,\n\t\t\t\tcreated_at TEXT NOT NULL DEFAULT (datetime('now')),\n\t\t\t\tupdated_at TEXT NOT NULL DEFAULT (datetime('now'))\n\t\t\t)\n\t\t`);\n\t\tthis.db.run(`\n\t\t\tCREATE TABLE IF NOT EXISTS stage1_outputs (\n\t\t\t\tthread_id TEXT PRIMARY KEY,\n\t\t\t\textraction_json TEXT NOT NULL,\n\t\t\t\tcreated_at TEXT NOT NULL DEFAULT (datetime('now')),\n\t\t\t\tFOREIGN KEY (thread_id) REFERENCES threads(thread_id) ON DELETE CASCADE\n\t\t\t)\n\t\t`);\n\t\tthis.db.run(`\n\t\t\tCREATE TABLE IF NOT EXISTS jobs (\n\t\t\t\tid TEXT PRIMARY KEY,\n\t\t\t\tphase TEXT NOT NULL,\n\t\t\t\tthread_id TEXT,\n\t\t\t\tstatus TEXT NOT NULL DEFAULT 'pending',\n\t\t\t\tworker_id TEXT,\n\t\t\t\townership_token TEXT,\n\t\t\t\tlease_expires_at TEXT,\n\t\t\t\terror_message TEXT,\n\t\t\t\tcreated_at TEXT NOT NULL DEFAULT (datetime('now')),\n\t\t\t\tupdated_at TEXT NOT NULL DEFAULT (datetime('now'))\n\t\t\t)\n\t\t`);\n\t\tthis.db.run(\"CREATE INDEX IF NOT EXISTS idx_jobs_phase_status ON jobs(phase, status)\");\n\t\tthis.db.run(\"CREATE INDEX IF NOT EXISTS idx_threads_status ON threads(status)\");\n\t\tthis.db.run(\"CREATE INDEX IF NOT EXISTS idx_threads_cwd ON threads(cwd)\");\n\t\tthis.persist();\n\t}\n\n\tprivate queryAll<T>(sql: string, params: unknown[] = []): T[] {\n\t\tconst stmt = this.db.prepare(sql);\n\t\tstmt.bind(params as (string | number | null | Uint8Array)[]);\n\t\tconst rows: T[] = [];\n\t\twhile (stmt.step()) {\n\t\t\trows.push(stmt.getAsObject() as T);\n\t\t}\n\t\tstmt.free();\n\t\treturn rows;\n\t}\n\n\tprivate queryOne<T>(sql: string, params: unknown[] = []): T | undefined {\n\t\tconst rows = this.queryAll<T>(sql, params);\n\t\treturn rows[0];\n\t}\n\n\t/**\n\t * Insert or update thread records. Skips threads whose file hasn't changed\n\t * (same size + mtime = watermark match).\n\t */\n\tupsertThreads(\n\t\tthreads: Array<{\n\t\t\tthreadId: string;\n\t\t\tfilePath: string;\n\t\t\tfileSize: number;\n\t\t\tfileMtime: number;\n\t\t\tcwd: string;\n\t\t}>,\n\t): { inserted: number; updated: number; skipped: number } {\n\t\tlet inserted = 0;\n\t\tlet updated = 0;\n\t\tlet skipped = 0;\n\n\t\tfor (const t of threads) {\n\t\t\tconst existing = this.queryOne<{ file_size: number; file_mtime: number; status: string }>(\n\t\t\t\t\"SELECT file_size, file_mtime, status FROM threads WHERE thread_id = ?\",\n\t\t\t\t[t.threadId],\n\t\t\t);\n\n\t\t\tif (!existing) {\n\t\t\t\tthis.db.run(\n\t\t\t\t\t\"INSERT INTO threads (thread_id, file_path, file_size, file_mtime, cwd, status) VALUES (?, ?, ?, ?, ?, 'pending')\",\n\t\t\t\t\t[t.threadId, t.filePath, t.fileSize, t.fileMtime, t.cwd],\n\t\t\t\t);\n\t\t\t\tthis.db.run(\n\t\t\t\t\t\"INSERT OR IGNORE INTO jobs (id, phase, thread_id, status) VALUES (?, 'stage1', ?, 'pending')\",\n\t\t\t\t\t[randomUUID(), t.threadId],\n\t\t\t\t);\n\t\t\t\tinserted++;\n\t\t\t} else if (existing.file_size !== t.fileSize || existing.file_mtime !== t.fileMtime) {\n\t\t\t\tthis.db.run(\n\t\t\t\t\t\"UPDATE threads SET file_path = ?, file_size = ?, file_mtime = ?, cwd = ?, status = 'pending', updated_at = datetime('now') WHERE thread_id = ?\",\n\t\t\t\t\t[t.filePath, t.fileSize, t.fileMtime, t.cwd, t.threadId],\n\t\t\t\t);\n\t\t\t\tif (existing.status === \"done\" || existing.status === \"error\") {\n\t\t\t\t\tthis.db.run(\n\t\t\t\t\t\t\"INSERT OR IGNORE INTO jobs (id, phase, thread_id, status) VALUES (?, 'stage1', ?, 'pending')\",\n\t\t\t\t\t\t[randomUUID(), t.threadId],\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tupdated++;\n\t\t\t} else {\n\t\t\t\tskipped++;\n\t\t\t}\n\t\t}\n\n\t\tthis.schedulePersist();\n\t\treturn { inserted, updated, skipped };\n\t}\n\n\t/**\n\t * Claim up to `limit` stage1 jobs for the given worker.\n\t * Uses lease-based ownership with an ownership_token UUID.\n\t */\n\tclaimStage1Jobs(\n\t\tworkerId: string,\n\t\tlimit: number,\n\t\tleaseSeconds: number,\n\t): Array<{ jobId: string; threadId: string; ownershipToken: string }> {\n\t\tconst token = randomUUID();\n\t\tconst expiresAt = new Date(Date.now() + leaseSeconds * 1000).toISOString();\n\n\t\tthis.db.run(\n\t\t\t`UPDATE jobs SET\n\t\t\t\tstatus = 'claimed',\n\t\t\t\tworker_id = ?,\n\t\t\t\townership_token = ?,\n\t\t\t\tlease_expires_at = ?,\n\t\t\t\tupdated_at = datetime('now')\n\t\t\tWHERE id IN (\n\t\t\t\tSELECT id FROM jobs\n\t\t\t\tWHERE phase = 'stage1'\n\t\t\t\t\tAND (status = 'pending' OR (status = 'claimed' AND lease_expires_at < datetime('now')))\n\t\t\t\tLIMIT ?\n\t\t\t)`,\n\t\t\t[workerId, token, expiresAt, limit],\n\t\t);\n\n\t\tconst rows = this.queryAll<{ id: string; thread_id: string }>(\n\t\t\t\"SELECT id, thread_id FROM jobs WHERE ownership_token = ? AND status = 'claimed'\",\n\t\t\t[token],\n\t\t);\n\n\t\tthis.schedulePersist();\n\n\t\treturn rows.map((r) => ({\n\t\t\tjobId: r.id,\n\t\t\tthreadId: r.thread_id,\n\t\t\townershipToken: token,\n\t\t}));\n\t}\n\n\t/**\n\t * Mark a stage1 job as complete and store the extraction output.\n\t */\n\tcompleteStage1Job(threadId: string, output: string): void {\n\t\tthis.db.run(\n\t\t\t\"UPDATE jobs SET status = 'done', updated_at = datetime('now') WHERE thread_id = ? AND phase = 'stage1' AND status = 'claimed'\",\n\t\t\t[threadId],\n\t\t);\n\t\tthis.db.run(\n\t\t\t\"INSERT OR REPLACE INTO stage1_outputs (thread_id, extraction_json, created_at) VALUES (?, ?, datetime('now'))\",\n\t\t\t[threadId, output],\n\t\t);\n\t\tthis.db.run(\n\t\t\t\"UPDATE threads SET status = 'done', updated_at = datetime('now') WHERE thread_id = ?\",\n\t\t\t[threadId],\n\t\t);\n\t\tthis.schedulePersist();\n\t}\n\n\t/**\n\t * Mark a stage1 job as errored.\n\t */\n\tfailStage1Job(threadId: string, errorMessage: string): void {\n\t\tthis.db.run(\n\t\t\t\"UPDATE jobs SET status = 'error', error_message = ?, updated_at = datetime('now') WHERE thread_id = ? AND phase = 'stage1' AND status = 'claimed'\",\n\t\t\t[errorMessage, threadId],\n\t\t);\n\t\tthis.db.run(\n\t\t\t\"UPDATE threads SET status = 'error', error_message = ?, updated_at = datetime('now') WHERE thread_id = ?\",\n\t\t\t[errorMessage, threadId],\n\t\t);\n\t\tthis.schedulePersist();\n\t}\n\n\t/**\n\t * Try to claim the global phase 2 consolidation job.\n\t * Only one worker can hold this at a time.\n\t */\n\ttryClaimGlobalPhase2Job(\n\t\tworkerId: string,\n\t\tleaseSeconds: number,\n\t): { jobId: string; ownershipToken: string } | null {\n\t\tconst token = randomUUID();\n\t\tconst expiresAt = new Date(Date.now() + leaseSeconds * 1000).toISOString();\n\n\t\tconst pendingStage1 = this.queryOne<{ cnt: number }>(\n\t\t\t\"SELECT COUNT(*) as cnt FROM jobs WHERE phase = 'stage1' AND status IN ('pending', 'claimed')\",\n\t\t);\n\n\t\tif (pendingStage1 && pendingStage1.cnt > 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst existingPhase2 = this.queryOne<{ id: string }>(\n\t\t\t\"SELECT id FROM jobs WHERE phase = 'stage2' AND status = 'claimed' AND lease_expires_at > datetime('now')\",\n\t\t);\n\n\t\tif (existingPhase2) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst outputCount = this.queryOne<{ cnt: number }>(\n\t\t\t\"SELECT COUNT(*) as cnt FROM stage1_outputs\",\n\t\t);\n\n\t\tif (!outputCount || outputCount.cnt === 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst jobId = randomUUID();\n\t\tthis.db.run(\n\t\t\t\"INSERT INTO jobs (id, phase, status, worker_id, ownership_token, lease_expires_at) VALUES (?, 'stage2', 'claimed', ?, ?, ?)\",\n\t\t\t[jobId, workerId, token, expiresAt],\n\t\t);\n\n\t\tthis.schedulePersist();\n\t\treturn { jobId, ownershipToken: token };\n\t}\n\n\t/**\n\t * Complete the phase 2 consolidation job.\n\t */\n\tcompletePhase2Job(jobId: string): void {\n\t\tthis.db.run(\n\t\t\t\"UPDATE jobs SET status = 'done', updated_at = datetime('now') WHERE id = ? AND phase = 'stage2'\",\n\t\t\t[jobId],\n\t\t);\n\t\tthis.schedulePersist();\n\t}\n\n\t/**\n\t * Get all stage1 extraction outputs.\n\t */\n\tgetStage1Outputs(): Array<{ threadId: string; extractionJson: string }> {\n\t\tconst rows = this.queryAll<{ thread_id: string; extraction_json: string }>(\n\t\t\t\"SELECT thread_id, extraction_json FROM stage1_outputs\",\n\t\t);\n\n\t\treturn rows.map((r) => ({\n\t\t\tthreadId: r.thread_id,\n\t\t\textractionJson: r.extraction_json,\n\t\t}));\n\t}\n\n\t/**\n\t * Get all stage1 outputs for a specific cwd.\n\t */\n\tgetStage1OutputsForCwd(cwd: string): Array<{ threadId: string; extractionJson: string }> {\n\t\tconst rows = this.queryAll<{ thread_id: string; extraction_json: string }>(\n\t\t\t`SELECT s.thread_id, s.extraction_json FROM stage1_outputs s\n\t\t\tINNER JOIN threads t ON t.thread_id = s.thread_id\n\t\t\tWHERE t.cwd = ?`,\n\t\t\t[cwd],\n\t\t);\n\n\t\treturn rows.map((r) => ({\n\t\t\tthreadId: r.thread_id,\n\t\t\textractionJson: r.extraction_json,\n\t\t}));\n\t}\n\n\t/**\n\t * Get thread info by ID.\n\t */\n\tgetThread(threadId: string): ThreadRow | undefined {\n\t\treturn this.queryOne<ThreadRow>(\n\t\t\t\"SELECT * FROM threads WHERE thread_id = ?\",\n\t\t\t[threadId],\n\t\t);\n\t}\n\n\t/**\n\t * Get pipeline statistics.\n\t */\n\tgetStats(): {\n\t\ttotalThreads: number;\n\t\tpendingThreads: number;\n\t\tdoneThreads: number;\n\t\terrorThreads: number;\n\t\ttotalStage1Outputs: number;\n\t\tpendingStage1Jobs: number;\n\t} {\n\t\tconst threads = this.queryOne<{ total: number; pending: number; done: number; errors: number }>(`\n\t\t\tSELECT\n\t\t\t\tCOUNT(*) as total,\n\t\t\t\tSUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending,\n\t\t\t\tSUM(CASE WHEN status = 'done' THEN 1 ELSE 0 END) as done,\n\t\t\t\tSUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) as errors\n\t\t\tFROM threads\n\t\t`)!;\n\n\t\tconst outputs = this.queryOne<{ cnt: number }>(\n\t\t\t\"SELECT COUNT(*) as cnt FROM stage1_outputs\",\n\t\t)!;\n\n\t\tconst pendingJobs = this.queryOne<{ cnt: number }>(\n\t\t\t\"SELECT COUNT(*) as cnt FROM jobs WHERE phase = 'stage1' AND status IN ('pending', 'claimed')\",\n\t\t)!;\n\n\t\treturn {\n\t\t\ttotalThreads: threads.total,\n\t\t\tpendingThreads: threads.pending,\n\t\t\tdoneThreads: threads.done,\n\t\t\terrorThreads: threads.errors,\n\t\t\ttotalStage1Outputs: outputs.cnt,\n\t\t\tpendingStage1Jobs: pendingJobs.cnt,\n\t\t};\n\t}\n\n\t/**\n\t * Clear all data (for /memory clear).\n\t */\n\tclearAll(): void {\n\t\tthis.db.run(\"DELETE FROM stage1_outputs\");\n\t\tthis.db.run(\"DELETE FROM jobs\");\n\t\tthis.db.run(\"DELETE FROM threads\");\n\t\tthis.schedulePersist();\n\t}\n\n\t/**\n\t * Clear data for a specific cwd (for /memory clear in project scope).\n\t */\n\tclearForCwd(cwd: string): void {\n\t\tthis.db.run(\n\t\t\t\"DELETE FROM stage1_outputs WHERE thread_id IN (SELECT thread_id FROM threads WHERE cwd = ?)\",\n\t\t\t[cwd],\n\t\t);\n\t\tthis.db.run(\n\t\t\t\"DELETE FROM jobs WHERE thread_id IN (SELECT thread_id FROM threads WHERE cwd = ?)\",\n\t\t\t[cwd],\n\t\t);\n\t\tthis.db.run(\"DELETE FROM threads WHERE cwd = ?\", [cwd]);\n\t\tthis.schedulePersist();\n\t}\n\n\t/**\n\t * Reset all threads to pending (for /memory rebuild).\n\t */\n\tresetAllForCwd(cwd: string): void {\n\t\tthis.db.run(\n\t\t\t\"DELETE FROM stage1_outputs WHERE thread_id IN (SELECT thread_id FROM threads WHERE cwd = ?)\",\n\t\t\t[cwd],\n\t\t);\n\t\tthis.db.run(\n\t\t\t\"DELETE FROM jobs WHERE thread_id IN (SELECT thread_id FROM threads WHERE cwd = ?)\",\n\t\t\t[cwd],\n\t\t);\n\t\tthis.db.run(\n\t\t\t\"UPDATE threads SET status = 'pending', updated_at = datetime('now') WHERE cwd = ?\",\n\t\t\t[cwd],\n\t\t);\n\n\t\tconst threads = this.queryAll<{ thread_id: string }>(\n\t\t\t\"SELECT thread_id FROM threads WHERE cwd = ?\",\n\t\t\t[cwd],\n\t\t);\n\n\t\tfor (const t of threads) {\n\t\t\tthis.db.run(\n\t\t\t\t\"INSERT INTO jobs (id, phase, thread_id, status) VALUES (?, 'stage1', ?, 'pending')\",\n\t\t\t\t[randomUUID(), t.thread_id],\n\t\t\t);\n\t\t}\n\t\tthis.schedulePersist();\n\t}\n\n\tclose(): void {\n\t\tif (this.persistTimer) {\n\t\t\tclearTimeout(this.persistTimer);\n\t\t\tthis.persistTimer = null;\n\t\t}\n\t\tthis.persist();\n\t\tthis.db.close();\n\t}\n}\n"]}
|
|
@@ -1053,6 +1053,42 @@ test("chat-controller rolls up only contiguous low-signal tool runs on message_e
|
|
|
1053
1053
|
assert.equal(host.chatContainer._prevRender, null, "summary reposition must invalidate the chat container render cache");
|
|
1054
1054
|
});
|
|
1055
1055
|
|
|
1056
|
+
test("chat-controller rolls up low-signal direct tool execution events on agent_end", async () => {
|
|
1057
|
+
(globalThis as any)[Symbol.for("@gsd/pi-coding-agent:theme")] = {
|
|
1058
|
+
fg: (_key: string, text: string) => text,
|
|
1059
|
+
bg: (_key: string, text: string) => text,
|
|
1060
|
+
bold: (text: string) => text,
|
|
1061
|
+
italic: (text: string) => text,
|
|
1062
|
+
truncate: (text: string) => text,
|
|
1063
|
+
};
|
|
1064
|
+
|
|
1065
|
+
const host = createHost();
|
|
1066
|
+
host.getMarkdownThemeWithSettings = () => ({});
|
|
1067
|
+
|
|
1068
|
+
for (const toolCallId of ["bash-1", "bash-2", "bash-3"]) {
|
|
1069
|
+
await handleAgentEvent(host, {
|
|
1070
|
+
type: "tool_execution_start",
|
|
1071
|
+
toolCallId,
|
|
1072
|
+
toolName: "bash",
|
|
1073
|
+
args: { command: "true" },
|
|
1074
|
+
} as any);
|
|
1075
|
+
await handleAgentEvent(host, {
|
|
1076
|
+
type: "tool_execution_end",
|
|
1077
|
+
toolCallId,
|
|
1078
|
+
isError: false,
|
|
1079
|
+
result: { content: [], details: {} },
|
|
1080
|
+
} as any);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
assert.equal(host.chatContainer.children.length, 3, "direct tool events render as individual rows while running");
|
|
1084
|
+
|
|
1085
|
+
await handleAgentEvent(host, { type: "agent_end" } as any);
|
|
1086
|
+
|
|
1087
|
+
assert.equal(host.chatContainer.children.length, 1, "direct low-signal tool rows should roll up on agent_end");
|
|
1088
|
+
assert.equal(host.chatContainer.children[0]?.constructor?.name, "ToolPhaseSummaryComponent");
|
|
1089
|
+
assert.match(host.chatContainer.children[0].render(120).join("\n"), /Setup \/ shell 3 actions/);
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1056
1092
|
// Regression test for issue #4144: interleaved text/tool content must render in content[] index order.
|
|
1057
1093
|
// Stream: [text "A", toolCall T1, text "B", toolCall T2, text "C"]
|
|
1058
1094
|
// Expected chatContainer order: textRun(A), toolExec(T1), textRun(B), toolExec(T2), textRun(C)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it, afterEach } from "node:test";
|
|
3
|
+
import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
|
|
7
|
+
import { atomicWriteDbSnapshotSync } from "./db-snapshot.js";
|
|
8
|
+
|
|
9
|
+
describe("atomicWriteDbSnapshotSync", () => {
|
|
10
|
+
let dir: string;
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
if (dir) {
|
|
14
|
+
rmSync(dir, { recursive: true, force: true });
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("writes the full snapshot and leaves no temp file after success", () => {
|
|
19
|
+
dir = mkdtempSync(join(tmpdir(), "gsd-db-snapshot-test-"));
|
|
20
|
+
const dbPath = join(dir, "agent.db");
|
|
21
|
+
const snapshot = new Uint8Array([0x53, 0x51, 0x4c, 0x69, 0x74, 0x65]);
|
|
22
|
+
|
|
23
|
+
atomicWriteDbSnapshotSync(dbPath, snapshot);
|
|
24
|
+
|
|
25
|
+
assert.deepEqual(readFileSync(dbPath), Buffer.from(snapshot));
|
|
26
|
+
assert.equal(existsSync(`${dbPath}.tmp`), false);
|
|
27
|
+
assert.deepEqual(
|
|
28
|
+
readdirSync(dir).filter((entry) => entry.includes("agent.db") && entry.includes(".tmp")),
|
|
29
|
+
[],
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { closeSync, fsyncSync, openSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { basename, dirname, join } from "node:path";
|
|
4
|
+
|
|
5
|
+
export type DbSnapshot = Buffer | Uint8Array;
|
|
6
|
+
|
|
7
|
+
function closeBestEffort(fd: number | null): void {
|
|
8
|
+
if (fd === null) return;
|
|
9
|
+
try {
|
|
10
|
+
closeSync(fd);
|
|
11
|
+
} catch {
|
|
12
|
+
// Preserve the original write/rename failure when cleaning up.
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function fsyncDirectoryBestEffort(dirPath: string): void {
|
|
17
|
+
let fd: number | null = null;
|
|
18
|
+
try {
|
|
19
|
+
fd = openSync(dirPath, "r");
|
|
20
|
+
fsyncSync(fd);
|
|
21
|
+
} catch {
|
|
22
|
+
// Directory fsync is unsupported on some platforms/filesystems.
|
|
23
|
+
} finally {
|
|
24
|
+
closeBestEffort(fd);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Persist a sql.js database export without exposing callers to a torn live file.
|
|
30
|
+
*
|
|
31
|
+
* The snapshot is written to a unique temp file in the same directory, flushed,
|
|
32
|
+
* then renamed over the target. A hard kill during the temp write leaves the
|
|
33
|
+
* previous target intact; after rename, readers see the complete new snapshot.
|
|
34
|
+
*
|
|
35
|
+
* The rename replaces the target inode: existing file mode/ownership is not
|
|
36
|
+
* preserved, and a symlink at dbPath is replaced rather than written through.
|
|
37
|
+
* Agent DB snapshots are owned runtime files, so this trade-off favors a
|
|
38
|
+
* private 0600 replacement over retaining prior target metadata.
|
|
39
|
+
*/
|
|
40
|
+
export function atomicWriteDbSnapshotSync(dbPath: string, snapshot: DbSnapshot): void {
|
|
41
|
+
const dirPath = dirname(dbPath);
|
|
42
|
+
const tmpPath = join(dirPath, `.${basename(dbPath)}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`);
|
|
43
|
+
let fd: number | null = null;
|
|
44
|
+
let renamed = false;
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
fd = openSync(tmpPath, "wx", 0o600);
|
|
48
|
+
writeFileSync(fd, Buffer.from(snapshot));
|
|
49
|
+
fsyncSync(fd);
|
|
50
|
+
closeSync(fd);
|
|
51
|
+
fd = null;
|
|
52
|
+
|
|
53
|
+
renameSync(tmpPath, dbPath);
|
|
54
|
+
renamed = true;
|
|
55
|
+
fsyncDirectoryBestEffort(dirPath);
|
|
56
|
+
} finally {
|
|
57
|
+
closeBestEffort(fd);
|
|
58
|
+
if (!renamed) {
|
|
59
|
+
try {
|
|
60
|
+
unlinkSync(tmpPath);
|
|
61
|
+
} catch {
|
|
62
|
+
// The temp file may not have been created yet.
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -894,6 +894,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
894
894
|
component.setExpanded(host.toolOutputExpanded);
|
|
895
895
|
host.chatContainer.addChild(component);
|
|
896
896
|
host.pendingTools.set(event.toolCallId, component);
|
|
897
|
+
renderedSegments.push({ kind: "tool", contentIndex: Number.MAX_SAFE_INTEGER, component });
|
|
897
898
|
host.ui.requestRender();
|
|
898
899
|
}
|
|
899
900
|
break;
|
|
@@ -927,6 +928,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
927
928
|
host.streamingComponent.setShowMetadata(true);
|
|
928
929
|
host.streamingComponent.updateContent(host.streamingMessage);
|
|
929
930
|
}
|
|
931
|
+
replaceCompactToolRowsWithPhaseSummary(host);
|
|
930
932
|
host.streamingComponent = undefined;
|
|
931
933
|
host.streamingMessage = undefined;
|
|
932
934
|
renderedSegments = [];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
|
|
6
|
+
test("MemoryStorage persists sql.js snapshots through the atomic DB snapshot writer", () => {
|
|
7
|
+
const src = readFileSync(
|
|
8
|
+
join(process.cwd(), "packages", "pi-coding-agent", "src", "resources", "extensions", "memory", "storage.ts"),
|
|
9
|
+
"utf-8",
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
assert.match(src, /atomicWriteDbSnapshotSync\(/, "storage must use atomic DB snapshot writes");
|
|
13
|
+
assert.doesNotMatch(src, /writeFileSync\(this\.dbPath/, "direct live DB overwrite is forbidden");
|
|
14
|
+
});
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
import initSqlJs, { type Database as SqlJsDatabase } from "sql.js";
|
|
11
11
|
import { randomUUID } from "crypto";
|
|
12
|
-
import { existsSync, mkdirSync, readFileSync
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync } from "fs";
|
|
13
13
|
import { dirname } from "path";
|
|
14
|
+
import { atomicWriteDbSnapshotSync } from "../../../core/db-snapshot.js";
|
|
14
15
|
|
|
15
16
|
export interface ThreadRow {
|
|
16
17
|
thread_id: string;
|
|
@@ -74,7 +75,7 @@ export class MemoryStorage {
|
|
|
74
75
|
|
|
75
76
|
private persist(): void {
|
|
76
77
|
const data = this.db.export();
|
|
77
|
-
|
|
78
|
+
atomicWriteDbSnapshotSync(this.dbPath, data);
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
private schedulePersist(): void {
|