wyrm-mcp 9.2.2 → 9.2.3

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 (118) hide show
  1. package/dist/activation.js +1 -1
  2. package/dist/agent-loop.js +2 -2
  3. package/dist/audit.js +2 -2
  4. package/dist/autoconfig.js +6 -6
  5. package/dist/backup-cli.js +6 -0
  6. package/dist/backup.js +1 -0
  7. package/dist/bridge.js +1 -1
  8. package/dist/buddy-runner.js +1 -1
  9. package/dist/buddy.js +4 -4
  10. package/dist/capabilities.js +1 -1
  11. package/dist/causality.js +3 -3
  12. package/dist/cloud/cli.js +3 -3
  13. package/dist/cloud/sync-daemon-entrypoint.js +1 -1
  14. package/dist/cloud/sync-engine.js +8 -7
  15. package/dist/cloud-backup.js +4 -4
  16. package/dist/cloud-sync-entrypoint.js +1 -1
  17. package/dist/cloud-sync.js +1 -1
  18. package/dist/connectors/ingest.js +2 -2
  19. package/dist/constellation.js +1 -1
  20. package/dist/context-build-budgeted.js +4 -4
  21. package/dist/context-ranking.js +1 -1
  22. package/dist/daemon-writer.js +1 -1
  23. package/dist/database.js +41 -23
  24. package/dist/design.js +1 -1
  25. package/dist/erasure-cli.js +3 -0
  26. package/dist/erasure-log.js +8 -0
  27. package/dist/erasure.js +1 -0
  28. package/dist/event-replication.js +1 -1
  29. package/dist/events.js +11 -11
  30. package/dist/failure-patterns.js +67 -44
  31. package/dist/federation.js +11 -10
  32. package/dist/governance.js +1 -0
  33. package/dist/handlers/agent.js +4 -4
  34. package/dist/handlers/alias-adapters.js +1 -1
  35. package/dist/handlers/audit.js +1 -1
  36. package/dist/handlers/boundary.js +1 -1
  37. package/dist/handlers/capture.js +54 -52
  38. package/dist/handlers/causality.js +2 -2
  39. package/dist/handlers/cloud.js +9 -9
  40. package/dist/handlers/companion.js +4 -4
  41. package/dist/handlers/entity.js +6 -6
  42. package/dist/handlers/events.js +4 -4
  43. package/dist/handlers/failure.js +15 -12
  44. package/dist/handlers/goals.js +2 -2
  45. package/dist/handlers/intelligence.js +81 -74
  46. package/dist/handlers/mcpclient.js +3 -3
  47. package/dist/handlers/presence.js +1 -1
  48. package/dist/handlers/prompts.js +2 -2
  49. package/dist/handlers/quest.js +11 -4
  50. package/dist/handlers/recall.js +10 -10
  51. package/dist/handlers/resources.js +1 -1
  52. package/dist/handlers/review.js +3 -3
  53. package/dist/handlers/run.js +3 -3
  54. package/dist/handlers/search.js +1 -1
  55. package/dist/handlers/session.js +27 -25
  56. package/dist/handlers/share.js +3 -3
  57. package/dist/handlers/skill.js +2 -2
  58. package/dist/handlers/symbols.js +5 -5
  59. package/dist/handlers/syncops.js +2 -2
  60. package/dist/harvest.js +5 -5
  61. package/dist/hours.js +1 -1
  62. package/dist/http-auth.js +2 -2
  63. package/dist/http-fast.js +17 -17
  64. package/dist/http-projects.js +4 -0
  65. package/dist/http-rate-limit.js +1 -0
  66. package/dist/index-health.js +52 -0
  67. package/dist/index.js +2 -2
  68. package/dist/indexer.js +4 -4
  69. package/dist/intelligence.js +36 -29
  70. package/dist/lane-provenance.js +39 -0
  71. package/dist/license.js +1 -1
  72. package/dist/lineage.js +15 -0
  73. package/dist/live-embedding-state.js +2 -2
  74. package/dist/maintenance.js +2 -2
  75. package/dist/mcp-integrity.js +2 -2
  76. package/dist/memory-artifacts.js +39 -37
  77. package/dist/metabolize.js +9 -7
  78. package/dist/metrics.js +1 -1
  79. package/dist/migrate-prompt.js +1 -1
  80. package/dist/migrations.js +236 -45
  81. package/dist/priority-embed.js +1 -1
  82. package/dist/providers/embedding-provider.js +1 -1
  83. package/dist/readonly-gate.js +1 -1
  84. package/dist/redact-backfill.js +1 -0
  85. package/dist/rehydration.js +30 -20
  86. package/dist/reindex.js +1 -1
  87. package/dist/render-target.js +6 -6
  88. package/dist/repl-guard.js +1 -1
  89. package/dist/replication-daemon-entrypoint.js +1 -1
  90. package/dist/replication-daemon.js +1 -1
  91. package/dist/retention.js +1 -0
  92. package/dist/reverse-bridge.js +6 -6
  93. package/dist/review-sample.js +31 -0
  94. package/dist/security.js +1 -1
  95. package/dist/self-maintenance.js +4 -4
  96. package/dist/served-records.js +8 -2
  97. package/dist/setup-wizard.js +1 -1
  98. package/dist/setup.js +1 -1
  99. package/dist/skill-author.js +2 -2
  100. package/dist/store-open.js +1 -0
  101. package/dist/store-readonly.js +1 -0
  102. package/dist/sub-agent.js +2 -2
  103. package/dist/tool-manifest-v2.json +1 -1
  104. package/dist/tool-manifest.json +1 -1
  105. package/dist/truth-freshness.js +8 -0
  106. package/dist/ui-dashboard.js +4 -4
  107. package/dist/vault.js +1 -1
  108. package/dist/vector-init.js +1 -1
  109. package/dist/vectors.js +4 -3
  110. package/dist/visibility.js +12 -12
  111. package/dist/write-guard.js +1 -0
  112. package/dist/wyrm-cli.js +108 -92
  113. package/dist/wyrm-guard.js +13 -12
  114. package/dist/wyrm-loop.js +3 -3
  115. package/dist/wyrm-manifest.json +1 -1
  116. package/dist/wyrm-memo.js +5 -5
  117. package/package.json +1 -1
  118. package/scripts/hooks/wyrm-session-capture.mjs +3 -0
package/dist/database.js CHANGED
@@ -1,14 +1,14 @@
1
- import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSync as O,readdirSync as I,statSync as A,writeFileSync as w,chmodSync as M}from"fs";import{homedir as y}from"os";import{join as d,basename as j,resolve as v,normalize as H}from"path";import{spawnSync as T}from"child_process";import{getResilienceManager as F}from"./resilience.js";import{getStoreLogger as W}from"./logger.js";import{runMigrations as U,schemaSkew as x,getSchemaVersion as P}from"./migrations.js";import{validateProjectPath as N,buildFtsMatchQuery as h,redactCredentials as C}from"./security.js";import{emitEvent as m,eventsSince as B,subscribeEvents as q,isLiveMemoryEnabled as $,ingestRemoteEvent as Y,eventsForPush as G,pruneEvents as V,getMeta as J,setMeta as z}from"./events.js";import{getActor as k}from"./handlers/boundary.js";import{readSkillContent as D,slugify as Q,skillContentSha as K}from"./skill-author.js";import{resolveProjectForPath as Z}from"./project-resolve.js";function me(){return process.env.WYRM_DB_PATH??process.env.WYRM_DB??d(y(),".wyrm","wyrm.db")}function X(f,t){const e=(s,r)=>{try{u(s)&&M(s,r)}catch{}};t&&e(t,448),e(f,384),e(`${f}-wal`,384),e(`${f}-shm`,384)}const ee={truth:{select:`SELECT t.cross_project_visibility AS vis, t.project_id AS pid, p.name AS pname, p.sync_policy AS policy
1
+ import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSync as O,readdirSync as A,statSync as w,writeFileSync as j,chmodSync as M}from"fs";import{homedir as y}from"os";import{join as d,basename as v,resolve as H,normalize as F}from"path";import{spawnSync as T}from"child_process";import{getResilienceManager as W}from"./resilience.js";import{getStoreLogger as U}from"./logger.js";import{runMigrations as x,schemaSkew as P,getSchemaVersion as q}from"./migrations.js";import{recordGovernanceEvent as b}from"./governance.js";import{enforceReadOnlyOpen as B,newerStoreMessage as $,newerStoreWritesAllowed as Y,NEWER_STORE_OVERRIDE_ENV as G}from"./store-readonly.js";import{describeStoreError as De}from"./store-readonly.js";import{validateProjectPath as C,buildFtsMatchQuery as m,redactCredentials as k}from"./security.js";import{emitEvent as E,eventsSince as V,subscribeEvents as J,isLiveMemoryEnabled as Q,ingestRemoteEvent as z,eventsForPush as K,pruneEvents as Z,getMeta as X,setMeta as ee}from"./events.js";import{getActor as D}from"./handlers/boundary.js";import{eraseLakeRows as te}from"./erasure.js";import{readSkillContent as I,slugify as se,skillContentSha as re}from"./skill-author.js";import{resolveProjectForPath as ie}from"./project-resolve.js";function Oe(){return process.env.WYRM_DB_PATH??process.env.WYRM_DB??d(y(),".wyrm","wyrm.db")}function ne(f,t){const e=(s,r)=>{try{u(s)&&M(s,r)}catch{}};t&&e(t,448),e(f,384),e(`${f}-wal`,384),e(`${f}-shm`,384)}const oe={truth:{select:`SELECT t.cross_project_visibility AS vis, t.project_id AS pid, p.name AS pname, p.sync_policy AS policy
2
2
  FROM ground_truths t LEFT JOIN projects p ON p.id = t.project_id WHERE t.id = ?`,update:"UPDATE ground_truths SET cross_project_visibility = ?, updated_at = datetime('now') WHERE id = ?"},memory:{select:`SELECT t.cross_project_visibility AS vis, t.project_id AS pid, p.name AS pname, p.sync_policy AS policy
3
3
  FROM memory_artifacts t LEFT JOIN projects p ON p.id = t.project_id WHERE t.id = ?`,update:"UPDATE memory_artifacts SET cross_project_visibility = ?, updated_at = datetime('now') WHERE id = ?"},quest:{select:`SELECT t.cross_project_visibility AS vis, t.project_id AS pid, p.name AS pname, p.sync_policy AS policy
4
- FROM quests t LEFT JOIN projects p ON p.id = t.project_id WHERE t.id = ?`,update:"UPDATE quests SET cross_project_visibility = ?, updated_at = datetime('now') WHERE id = ?"}},te=new Set(["within","org","public"]);class _e{db;BATCH_SIZE=1e3;resilience;logger;dbPath;constructor(t){const e=d(y(),".wyrm");u(e)||O(e,{recursive:!0,mode:448}),this.dbPath=t||d(e,"wyrm.db"),this.logger=W(this.dbPath),this.resilience=F(),this.db=this.initializeDatabase(this.dbPath),X(this.dbPath,e),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("cache_size = -64000"),this.db.pragma("temp_store = MEMORY"),this.db.pragma("busy_timeout = 5000"),this.db.pragma("mmap_size = 268435456"),this.db.pragma("page_size = 4096"),this.db.pragma("foreign_keys = ON"),this.db.pragma("secure_delete = ON");const s=U(this.db),r=x(this.db);r.storeIsNewer&&this.logger.warn(`This store is at schema v${r.store}, newer than this Wyrm knows (v${r.binary}). An older binary is writing to a newer store under older rules. Upgrade Wyrm on this machine.`),s.length>0&&this.logger.info(`Applied ${s.length} migration(s), now at v${P(this.db)}`),this.recoverIncompleteOperations()}getDatabase(){return this.db}getDatabasePath(){return this.dbPath}initializeDatabase(t){const e=this.resilience.withRetrySync(()=>L(t),"database_init",{maxAttempts:3,baseDelayMs:500});if(!e.success)throw this.logger.error("Failed to initialize database",{path:t,error:e.error?.message}),e.error||new Error("Database initialization failed");return e.data}recoverIncompleteOperations(){const t=this.resilience.getIncompleteOperations();for(const e of t)this.logger.warn("Found incomplete operation from previous session",{operation:e.operation,stage:e.stage,id:e.id}),e.operation==="batch_insert"&&this.logger.info("Batch insert was incomplete - data may need re-import"),this.resilience.completeCheckpoint(e.id)}addWatchDir(t,e=!0){return N(t),this.db.prepare(`
4
+ FROM quests t LEFT JOIN projects p ON p.id = t.project_id WHERE t.id = ?`,update:"UPDATE quests SET cross_project_visibility = ?, updated_at = datetime('now') WHERE id = ?"}},ae=new Set(["within","org","public"]);class ye{db;BATCH_SIZE=1e3;resilience;logger;dbPath;constructor(t){const e=d(y(),".wyrm");u(e)||O(e,{recursive:!0,mode:448}),this.dbPath=t||d(e,"wyrm.db"),this.logger=U(this.dbPath),this.resilience=W(),this.db=this.initializeDatabase(this.dbPath),ne(this.dbPath,e),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("cache_size = -64000"),this.db.pragma("temp_store = MEMORY"),this.db.pragma("busy_timeout = 5000"),this.db.pragma("mmap_size = 268435456"),this.db.pragma("page_size = 4096"),this.db.pragma("foreign_keys = ON"),this.db.pragma("secure_delete = ON");const s=x(this.db),r=P(this.db);r.storeIsNewer&&(Y()?this.logger.warn(`This store is at schema v${r.store}, newer than this Wyrm knows (v${r.binary}). ${G}=1 is set, so this older binary is writing to it under older rules. Upgrade wyrm-mcp on this machine.`):(B(this.db,r),this.logger.warn($(r.store,r.binary)))),s.length>0&&this.logger.info(`Applied ${s.length} migration(s), now at v${q(this.db)}`),this.recoverIncompleteOperations()}getDatabase(){return this.db}getDatabasePath(){return this.dbPath}initializeDatabase(t){const e=this.resilience.withRetrySync(()=>L(t),"database_init",{maxAttempts:3,baseDelayMs:500});if(!e.success)throw this.logger.error("Failed to initialize database",{path:t,error:e.error?.message}),e.error||new Error("Database initialization failed");return e.data}recoverIncompleteOperations(){const t=this.resilience.getIncompleteOperations();for(const e of t)this.logger.warn("Found incomplete operation from previous session",{operation:e.operation,stage:e.stage,id:e.id}),e.operation==="batch_insert"&&this.logger.info("Batch insert was incomplete - data may need re-import"),this.resilience.completeCheckpoint(e.id)}addWatchDir(t,e=!0){return C(t),this.db.prepare(`
5
5
  INSERT INTO watch_dirs (path, recursive)
6
6
  VALUES (?, ?)
7
7
  ON CONFLICT(path) DO UPDATE SET recursive = excluded.recursive
8
8
  RETURNING *
9
- `).get(t,e?1:0)}getWatchDirs(){return this.db.prepare("SELECT * FROM watch_dirs").all()}removeWatchDir(t){this.db.prepare("DELETE FROM watch_dirs WHERE path = ?").run(t)}scanForProjects(t,e=!0){N(t);const s=[],r=(i,n=0)=>{if(!(n>3&&e))try{const a=I(i,{withFileTypes:!0});for(const o of a){if(!o.isDirectory()||o.name.startsWith(".")&&o.name!==".git")continue;const c=d(i,o.name),l=d(c,".git");if(u(l)){const p=this.registerProjectFromPath(c);p&&s.push(p)}else e&&n<3&&r(c,n+1)}}catch{}};return r(t),this.db.prepare(`
9
+ `).get(t,e?1:0)}getWatchDirs(){return this.db.prepare("SELECT * FROM watch_dirs").all()}removeWatchDir(t){this.db.prepare("DELETE FROM watch_dirs WHERE path = ?").run(t)}scanForProjects(t,e=!0){C(t);const s=[],r=(i,n=0)=>{if(!(n>3&&e))try{const a=A(i,{withFileTypes:!0});for(const o of a){if(!o.isDirectory()||o.name.startsWith(".")&&o.name!==".git")continue;const c=d(i,o.name),l=d(c,".git");if(u(l)){const p=this.registerProjectFromPath(c);p&&s.push(p)}else e&&n<3&&r(c,n+1)}}catch{}};return r(t),this.db.prepare(`
10
10
  UPDATE watch_dirs SET last_scan = datetime('now') WHERE path = ?
11
- `).run(t),s}scanAllWatchDirs(){const t=this.getWatchDirs(),e=[];for(const s of t){const r=this.scanForProjects(s.path,!!s.recursive);e.push(...r)}return e}registerProjectFromPath(t){try{const e=H(v(t));if(!u(e)||!A(e).isDirectory())return null;const s=j(e);let r,i,n,a;try{const o=T("git",["config","--get","remote.origin.url"],{cwd:e,encoding:"utf-8",timeout:5e3,shell:!1});o.status===0&&(r=o.stdout.trim());const c=T("git",["rev-parse","--abbrev-ref","HEAD"],{cwd:e,encoding:"utf-8",timeout:5e3,shell:!1});c.status===0&&(i=c.stdout.trim());const l=T("git",["log","-1","--format=%h %s"],{cwd:e,encoding:"utf-8",timeout:5e3,shell:!1});l.status===0&&(n=l.stdout.trim())}catch{}return u(d(e,"package.json"))?(a="Node.js",u(d(e,"next.config.js"))||u(d(e,"next.config.ts"))||u(d(e,"next.config.mjs"))?a="Next.js":u(d(e,"vite.config.ts"))&&(a="Vite")):u(d(e,"requirements.txt"))||u(d(e,"pyproject.toml"))?a="Python":u(d(e,"composer.json"))?a="PHP":u(d(e,"Cargo.toml"))?a="Rust":u(d(e,"go.mod"))&&(a="Go"),this.registerProject(s,e,r,a,n,i)}catch{return null}}registerProject(t,e,s,r,i,n){return this.db.prepare(`
11
+ `).run(t),s}scanAllWatchDirs(){const t=this.getWatchDirs(),e=[];for(const s of t){const r=this.scanForProjects(s.path,!!s.recursive);e.push(...r)}return e}registerProjectFromPath(t){try{const e=F(H(t));if(!u(e)||!w(e).isDirectory())return null;const s=v(e);let r,i,n,a;try{const o=T("git",["config","--get","remote.origin.url"],{cwd:e,encoding:"utf-8",timeout:5e3,shell:!1});o.status===0&&(r=o.stdout.trim());const c=T("git",["rev-parse","--abbrev-ref","HEAD"],{cwd:e,encoding:"utf-8",timeout:5e3,shell:!1});c.status===0&&(i=c.stdout.trim());const l=T("git",["log","-1","--format=%h %s"],{cwd:e,encoding:"utf-8",timeout:5e3,shell:!1});l.status===0&&(n=l.stdout.trim())}catch{}return u(d(e,"package.json"))?(a="Node.js",u(d(e,"next.config.js"))||u(d(e,"next.config.ts"))||u(d(e,"next.config.mjs"))?a="Next.js":u(d(e,"vite.config.ts"))&&(a="Vite")):u(d(e,"requirements.txt"))||u(d(e,"pyproject.toml"))?a="Python":u(d(e,"composer.json"))?a="PHP":u(d(e,"Cargo.toml"))?a="Rust":u(d(e,"go.mod"))&&(a="Go"),this.registerProject(s,e,r,a,n,i)}catch{return null}}registerProject(t,e,s,r,i,n){return this.db.prepare(`
12
12
  INSERT INTO projects (name, path, repo, stack, last_commit, branch)
13
13
  VALUES (?, ?, ?, ?, ?, ?)
14
14
  ON CONFLICT(path) DO UPDATE SET
@@ -19,22 +19,22 @@ import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSy
19
19
  branch = COALESCE(excluded.branch, projects.branch),
20
20
  updated_at = datetime('now')
21
21
  RETURNING *
22
- `).get(t,e,s||null,r||null,i||null,n||null)}getProject(t){return this.db.prepare("SELECT * FROM projects WHERE path = ?").get(t)}getProjectForPath(t){const{id:e,root:s}=Z(this.db,t);return e!==null&&s!==null?{id:e,path:s}:void 0}getProjectById(t){return this.db.prepare("SELECT * FROM projects WHERE id = ?").get(t)}getProjectByName(t){return this.db.prepare("SELECT * FROM projects WHERE name = ?").get(t)}getAllProjects(t=100,e=0){return this.db.prepare(`
22
+ `).get(t,e,s||null,r||null,i||null,n||null)}getProject(t){return this.db.prepare("SELECT * FROM projects WHERE path = ?").get(t)}getProjectForPath(t){const{id:e,root:s}=ie(this.db,t);return e!==null&&s!==null?{id:e,path:s}:void 0}getProjectById(t){return this.db.prepare("SELECT * FROM projects WHERE id = ?").get(t)}getProjectByName(t){return this.db.prepare("SELECT * FROM projects WHERE name = ?").get(t)}getAllProjects(t=100,e=0){return this.db.prepare(`
23
23
  SELECT * FROM projects ORDER BY updated_at DESC LIMIT ? OFFSET ?
24
24
  `).all(t,e)}searchProjects(t){const e=`%${t}%`;return this.db.prepare(`
25
25
  SELECT * FROM projects
26
26
  WHERE name LIKE ? OR stack LIKE ? OR repo LIKE ?
27
27
  ORDER BY updated_at DESC
28
28
  LIMIT 50
29
- `).all(e,e,e)}createSession(t,e){const s=this.estimateTokens((e.objectives||"")+(e.completed||"")+(e.issues||"")+(e.notes||"")),r=k(),i=this.resilience.withRetrySync(()=>this.db.prepare(`
29
+ `).all(e,e,e)}createSession(t,e){const s=this.estimateTokens((e.objectives||"")+(e.completed||"")+(e.issues||"")+(e.notes||"")),r=D(),i=this.resilience.withRetrySync(()=>this.db.prepare(`
30
30
  INSERT INTO sessions (project_id, date, objectives, completed, issues, commits, files_changed, notes, tokens_estimate, agent_id, run_id)
31
31
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
32
32
  RETURNING *
33
- `).get(t,e.date||new Date().toISOString().split("T")[0],e.objectives||"",e.completed||"",e.issues||"",e.commits||"",e.files_changed||"",e.notes||"",s,r.agent_id,r.run_id),"createSession");if(!i.success)throw i.error||new Error("Failed to create session");return m(this.db,{projectId:t,kind:"session_update",refTable:"sessions",refId:i.data.id}),i.data}updateSession(t,e){const s=[],r=[],i=new Set(["objectives","completed","issues","commits","files_changed","notes","summary","is_archived"]);for(const[a,o]of Object.entries(e))i.has(a)&&o!==void 0&&(s.push(`${a} = ?`),r.push(o));if(s.length===0)return this.getSession(t);if(e.objectives||e.completed||e.issues||e.notes){const a=this.getSession(t);if(a){const o=this.estimateTokens((e.objectives||a.objectives)+(e.completed||a.completed)+(e.issues||a.issues)+(e.notes||a.notes));s.push("tokens_estimate = ?"),r.push(o)}}r.push(t);const n=this.resilience.withRetrySync(()=>this.db.prepare(`
33
+ `).get(t,e.date||new Date().toISOString().split("T")[0],e.objectives||"",e.completed||"",e.issues||"",e.commits||"",e.files_changed||"",e.notes||"",s,r.agent_id,r.run_id),"createSession");if(!i.success)throw i.error||new Error("Failed to create session");return E(this.db,{projectId:t,kind:"session_update",refTable:"sessions",refId:i.data.id}),i.data}updateSession(t,e){const s=[],r=[],i=new Set(["objectives","completed","issues","commits","files_changed","notes","summary","is_archived"]);for(const[a,o]of Object.entries(e))i.has(a)&&o!==void 0&&(s.push(`${a} = ?`),r.push(o));if(s.length===0)return this.getSession(t);if(e.objectives||e.completed||e.issues||e.notes){const a=this.getSession(t);if(a){const o=this.estimateTokens((e.objectives||a.objectives)+(e.completed||a.completed)+(e.issues||a.issues)+(e.notes||a.notes));s.push("tokens_estimate = ?"),r.push(o)}}r.push(t);const n=this.resilience.withRetrySync(()=>this.db.prepare(`
34
34
  UPDATE sessions SET ${s.join(", ")} WHERE id = ? RETURNING *
35
- `).get(...r),"updateSession");if(!n.success)throw n.error||new Error("Failed to update session");return m(this.db,{projectId:n.data.project_id,kind:"session_update",refTable:"sessions",refId:t}),n.data}getSession(t){return this.db.prepare("SELECT * FROM sessions WHERE id = ?").get(t)}updateContinuity(t,e,s){const r=e.trim();if(!r)return;const i=12e3,n=this.db.prepare("SELECT thread, turn_count FROM project_continuity WHERE project_id = ?").get(t),a=new Date().toISOString(),o=`[${a}${s?" \xB7 "+s:""}] ${r}`;let c=n?.thread?n.thread+`
35
+ `).get(...r),"updateSession");if(!n.success)throw n.error||new Error("Failed to update session");return E(this.db,{projectId:n.data.project_id,kind:"session_update",refTable:"sessions",refId:t}),n.data}getSession(t){return this.db.prepare("SELECT * FROM sessions WHERE id = ?").get(t)}updateContinuity(t,e,s){const r=e.trim();if(!r)return;const i=12e3,n=this.db.prepare("SELECT thread, turn_count FROM project_continuity WHERE project_id = ?").get(t),a=new Date().toISOString(),o=`[${a}${s?" \xB7 "+s:""}] ${r}`;let c=n?.thread?n.thread+`
36
36
 
37
- `+o:o;c.length>i&&(c=`[\u2026earlier continuity trimmed \u2014 gist retained below\u2026]
37
+ `+o:o;c.length>i&&(c=`[\u2026earlier continuity trimmed; gist retained below\u2026]
38
38
 
39
39
  `+c.slice(c.length-i));const l=(n?.turn_count??0)+1;this.resilience.withRetrySync(()=>(this.db.prepare(`
40
40
  INSERT INTO project_continuity (project_id, thread, updated_at, updated_by, turn_count)
@@ -66,7 +66,7 @@ import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSy
66
66
  LIMIT ?
67
67
  `).all(t,e)}getTodaySession(t){const e=new Date().toISOString().split("T")[0];return this.db.prepare(`
68
68
  SELECT * FROM sessions WHERE project_id = ? AND date = ?
69
- `).get(t,e)}searchSessions(t,e){const s=h(t);if(!s)return[];try{return e?this.db.prepare(`
69
+ `).get(t,e)}searchSessions(t,e){const s=m(t);if(!s)return[];try{return e?this.db.prepare(`
70
70
  SELECT s.* FROM sessions s
71
71
  JOIN sessions_fts fts ON s.id = fts.rowid
72
72
  WHERE sessions_fts MATCH ? AND s.project_id = ?
@@ -92,13 +92,31 @@ import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSy
92
92
  `).run(t,t,e).changes}getSessionTokenUsage(t){return this.db.prepare(`
93
93
  SELECT COALESCE(SUM(tokens_estimate), 0) as total
94
94
  FROM sessions WHERE project_id = ? AND is_archived = 0
95
- `).get(t).total}addQuest(t,e,s,r="medium",i){const n=k(),a=this.db.prepare(`
95
+ `).get(t).total}addQuest(t,e,s,r="medium",i){const n=D(),a=this.db.prepare(`
96
96
  INSERT INTO quests (project_id, title, description, priority, tags, agent_id, run_id, updated_at)
97
97
  VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))
98
98
  RETURNING *
99
- `).get(t,C(e),C(s||""),r,i||null,n.agent_id,n.run_id);return m(this.db,{projectId:t,kind:"quest",refTable:"quests",refId:a.id}),a}updateQuest(t,e){const s=e==="completed"?new Date().toISOString():null,r=this.db.prepare(`
99
+ `).get(t,k(e),k(s||""),r,i||null,n.agent_id,n.run_id);return E(this.db,{projectId:t,kind:"quest",refTable:"quests",refId:a.id}),a}updateQuest(t,e){const s=e==="completed"?new Date().toISOString():null,r=this.db.prepare(`
100
100
  UPDATE quests SET status = ?, completed_at = ?, updated_at = datetime('now') WHERE id = ? RETURNING *
101
- `).get(e,s,t);return r&&m(this.db,{projectId:r.project_id,kind:"quest",refTable:"quests",refId:t}),r}getPendingQuests(t){return this.db.prepare(`
101
+ `).get(e,s,t);return r&&E(this.db,{projectId:r.project_id,kind:"quest",refTable:"quests",refId:t}),r}parkQuest(t){const e=this.db.prepare(`
102
+ UPDATE quests SET status = 'parked', updated_at = datetime('now')
103
+ WHERE id = ? AND status IN ('pending', 'in_progress') RETURNING *
104
+ `).get(t);return e?(E(this.db,{projectId:e.project_id,kind:"quest",refTable:"quests",refId:t}),e):null}unparkQuest(t){const e=this.db.prepare(`
105
+ UPDATE quests SET status = 'pending', updated_at = datetime('now')
106
+ WHERE id = ? AND status = 'parked' RETURNING *
107
+ `).get(t);return e?(E(this.db,{projectId:e.project_id,kind:"quest",refTable:"quests",refId:t}),e):null}getAllParkedQuests(){return this.db.prepare(`
108
+ SELECT q.*, p.name as project_name FROM quests q
109
+ JOIN projects p ON q.project_id = p.id
110
+ WHERE q.status = 'parked'
111
+ ORDER BY
112
+ CASE q.priority
113
+ WHEN 'critical' THEN 1
114
+ WHEN 'high' THEN 2
115
+ WHEN 'medium' THEN 3
116
+ WHEN 'low' THEN 4
117
+ END,
118
+ q.id ASC
119
+ `).all()}getPendingQuests(t){return this.db.prepare(`
102
120
  SELECT * FROM quests
103
121
  WHERE project_id = ? AND status IN ('pending', 'in_progress')
104
122
  ORDER BY
@@ -121,7 +139,7 @@ import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSy
121
139
  WHEN 'low' THEN 4
122
140
  END,
123
141
  q.created_at ASC
124
- `).all()}searchQuests(t){const e=h(t);if(!e)return[];try{return this.db.prepare(`
142
+ `).all()}searchQuests(t){const e=m(t);if(!e)return[];try{return this.db.prepare(`
125
143
  SELECT q.* FROM quests q
126
144
  JOIN quests_fts fts ON q.id = fts.rowid
127
145
  WHERE quests_fts MATCH ?
@@ -142,7 +160,7 @@ import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSy
142
160
  SELECT value FROM context WHERE project_id = ? AND key = ?
143
161
  `).get(t,e)?.value}getAllContext(t){const e=this.db.prepare(`
144
162
  SELECT key, value FROM context WHERE project_id = ?
145
- `).all(t),s={};for(const r of e)s[r.key]=r.value;return s}liveMemoryEnabled(){return $()}eventsSince(t,e=0,s=100){return B(this.db,t,e,s)}subscribeEvents(t,e=20){return q(this.db,t,e)}publishEvent(t){m(this.db,t)}pruneEvents(t={}){return V(this.db,t)}ingestRemoteEvent(t,e){return Y(this.db,t,e)}eventsForPush(t,e=0,s=200){return G(this.db,t,e,s)}getMeta(t){return J(this.db,t)}setMeta(t,e){z(this.db,t,e)}setGlobalContext(t,e){this.db.prepare(`
163
+ `).all(t),s={};for(const r of e)s[r.key]=r.value;return s}liveMemoryEnabled(){return Q()}eventsSince(t,e=0,s=100){return V(this.db,t,e,s)}subscribeEvents(t,e=20){return J(this.db,t,e)}publishEvent(t){E(this.db,t)}pruneEvents(t={}){return Z(this.db,t)}ingestRemoteEvent(t,e){return z(this.db,t,e)}eventsForPush(t,e=0,s=200){return K(this.db,t,e,s)}getMeta(t){return X(this.db,t)}setMeta(t,e){ee(this.db,t,e)}setGlobalContext(t,e){this.db.prepare(`
146
164
  INSERT INTO global_context (key, value)
147
165
  VALUES (?, ?)
148
166
  ON CONFLICT(key) DO UPDATE SET
@@ -150,7 +168,7 @@ import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSy
150
168
  updated_at = datetime('now')
151
169
  `).run(t,e)}getGlobalContext(t){return this.db.prepare(`
152
170
  SELECT value FROM global_context WHERE key = ?
153
- `).get(t)?.value}getAllGlobalContext(){const t=this.db.prepare("SELECT key, value FROM global_context").all(),e={};for(const s of t)e[s.key]=s.value;return e}registerSkill(t,e,s,r,i,n,a,o){const c=o?.tier??"atomic",l=JSON.stringify(Array.isArray(o?.governs)?o.governs:[]),p=JSON.stringify(Array.isArray(o?.composes)?o.composes:[]),_=D({skillPath:s,name:t}),g=_?.content??null,S=_?.sha256??null,R=_?new Date().toISOString():null,E=this.resilience.withRetrySync(()=>this.db.prepare(`
171
+ `).get(t)?.value}getAllGlobalContext(){const t=this.db.prepare("SELECT key, value FROM global_context").all(),e={};for(const s of t)e[s.key]=s.value;return e}registerSkill(t,e,s,r,i,n,a,o){const c=o?.tier??"atomic",l=JSON.stringify(Array.isArray(o?.governs)?o.governs:[]),p=JSON.stringify(Array.isArray(o?.composes)?o.composes:[]),_=I({skillPath:s,name:t}),g=_?.content??null,S=_?.sha256??null,R=_?new Date().toISOString():null,h=this.resilience.withRetrySync(()=>this.db.prepare(`
154
172
  INSERT INTO skills (name, description, skill_path, category, author, version, tags, tier, governs, composes, content, content_sha256, content_updated_at, is_active, usage_count)
155
173
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 0)
156
174
  ON CONFLICT(name) DO UPDATE SET
@@ -172,13 +190,13 @@ import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSy
172
190
  updated_at = datetime('now'),
173
191
  is_active = 1
174
192
  RETURNING *
175
- `).get(t,e,s,r||null,i||null,n||null,a||null,c,l,p,g,S,R),"registerSkill");if(!E.success)throw E.error||new Error("Failed to register skill");return E.data}getSkill(t){const e=this.db.prepare("SELECT * FROM skills WHERE name = ?").get(t);return e&&this.db.prepare("UPDATE skills SET last_used = datetime('now'), usage_count = usage_count + 1 WHERE id = ?").run(e.id),e}backfillSkillContent(t={}){const e=t.read??D,s=this.db.prepare("SELECT id, name, skill_path, content_sha256 FROM skills").all();let r=0,i=0,n=0;const a=this.db.prepare("UPDATE skills SET content = ?, content_sha256 = ?, content_updated_at = ?, updated_at = datetime('now') WHERE id = ?");return this.db.transaction(()=>{for(const c of s){const l=e({skillPath:c.skill_path,name:c.name,skillsDir:t.skillsDir});if(!l){n++;continue}if(c.content_sha256&&c.content_sha256===l.sha256){i++;continue}a.run(l.content,l.sha256,new Date().toISOString(),c.id),r++}})(),{total:s.length,filled:r,unchanged:i,missing:n}}exportSkillContent(t,e={}){const s=e.includeInactive?"":" WHERE is_active = 1",r=this.db.prepare(`SELECT name, content FROM skills${s} ORDER BY name`).all();let i=0,n=0,a=0;const o=new Set;for(const c of r){if(c.content==null){n++;continue}let l=Q(c.name);if(!l){n++;continue}o.has(l)&&(l=`${l}-${K(c.name).slice(0,8)}`,a++),o.add(l);const p=d(t,l);O(p,{recursive:!0}),w(d(p,"SKILL.md"),c.content,"utf-8"),i++}return{total:r.length,written:i,skipped_no_content:n,collisions:a}}setSkillVisibility(t,e){return this.db.prepare("UPDATE skills SET cross_project_visibility = ?, updated_at = datetime('now') WHERE name = ? RETURNING *").get(e,t)}setAllSkillsVisibility(t,e={}){const s=[],r=[t];e.includeInactive||s.push("is_active = 1"),e.tier&&(s.push("tier = ?"),r.push(e.tier));const i=s.length?" WHERE "+s.join(" AND "):"";return this.db.prepare(`UPDATE skills SET cross_project_visibility = ?, updated_at = datetime('now')${i}`).run(...r).changes}setRowVisibility(t,e,s){const r=ee[t];if(!r)throw new Error(`Unknown visibility kind: ${String(t)} (use truth|memory|quest)`);if(!te.has(s))throw new Error(`Unknown visibility: ${String(s)} (use within|org|public)`);const i=this.db.prepare(r.select).get(e),n={kind:t,id:e,visibility:s};if(!i)return{...n,outcome:"not_found",projectId:null,projectName:null,syncPolicy:null,egressEligible:!1};const a={projectId:i.pid,projectName:i.pname,syncPolicy:i.policy,previous:i.vis},o=c=>c!=="within"&&(i.pid==null||i.policy==="cloud"||i.policy==="team");return s!=="within"&&i.policy==="private"?{...n,...a,outcome:"refused_private_grove",egressEligible:!1}:i.vis===s?{...n,...a,outcome:"unchanged",egressEligible:o(s)}:(this.db.prepare(r.update).run(s,e),{...n,...a,outcome:"updated",egressEligible:o(s)})}listSkills(t,e,s,r){let i="SELECT * FROM skills WHERE 1=1";const n=[];if(t!==void 0&&(i+=" AND is_active = ?",n.push(t?1:0)),e&&(i+=" AND category = ?",n.push(e)),r&&(i+=" AND tier = ?",n.push(r)),s){const a=h(s);a&&(i+=" AND id IN (SELECT rowid FROM skills_fts WHERE skills_fts MATCH ?)",n.push(a))}return i+=" ORDER BY updated_at DESC",this.db.prepare(i).all(...n)}searchSkills(t,e=20,s){const r=h(t);if(!r)return[];const i=s?" AND s.tier = ?":"",n=this.db.prepare(`
193
+ `).get(t,e,s,r||null,i||null,n||null,a||null,c,l,p,g,S,R),"registerSkill");if(!h.success)throw h.error||new Error("Failed to register skill");return h.data}getSkill(t){const e=this.db.prepare("SELECT * FROM skills WHERE name = ?").get(t);return e&&this.db.prepare("UPDATE skills SET last_used = datetime('now'), usage_count = usage_count + 1 WHERE id = ?").run(e.id),e}backfillSkillContent(t={}){const e=t.read??I,s=this.db.prepare("SELECT id, name, skill_path, content_sha256 FROM skills").all();let r=0,i=0,n=0;const a=this.db.prepare("UPDATE skills SET content = ?, content_sha256 = ?, content_updated_at = ?, updated_at = datetime('now') WHERE id = ?");return this.db.transaction(()=>{for(const c of s){const l=e({skillPath:c.skill_path,name:c.name,skillsDir:t.skillsDir});if(!l){n++;continue}if(c.content_sha256&&c.content_sha256===l.sha256){i++;continue}a.run(l.content,l.sha256,new Date().toISOString(),c.id),r++}})(),{total:s.length,filled:r,unchanged:i,missing:n}}exportSkillContent(t,e={}){const s=e.includeInactive?"":" WHERE is_active = 1",r=this.db.prepare(`SELECT name, content FROM skills${s} ORDER BY name`).all();let i=0,n=0,a=0;const o=new Set;for(const c of r){if(c.content==null){n++;continue}let l=se(c.name);if(!l){n++;continue}o.has(l)&&(l=`${l}-${re(c.name).slice(0,8)}`,a++),o.add(l);const p=d(t,l);O(p,{recursive:!0}),j(d(p,"SKILL.md"),c.content,"utf-8"),i++}return{total:r.length,written:i,skipped_no_content:n,collisions:a}}setSkillVisibility(t,e){return this.db.transaction(()=>{const s=this.db.prepare("UPDATE skills SET cross_project_visibility = ?, updated_at = datetime('now') WHERE name = ? RETURNING *").get(e,t);return s&&b(this.db,"visibility.set",{kind:"skill",row_id:s.id,to:e}),s})()}setAllSkillsVisibility(t,e={}){const s=[],r=[t];e.includeInactive||s.push("is_active = 1"),e.tier&&(s.push("tier = ?"),r.push(e.tier));const i=s.length?" WHERE "+s.join(" AND "):"";return this.db.transaction(()=>{const n=this.db.prepare(`UPDATE skills SET cross_project_visibility = ?, updated_at = datetime('now')${i}`).run(...r);return n.changes>0&&b(this.db,"visibility.set",{kind:"skill",count:n.changes,to:t}),n.changes})()}setRowVisibility(t,e,s){const r=oe[t];if(!r)throw new Error(`Unknown visibility kind: ${String(t)} (use truth|memory|quest)`);if(!ae.has(s))throw new Error(`Unknown visibility: ${String(s)} (use within|org|public)`);const i=this.db.prepare(r.select).get(e),n={kind:t,id:e,visibility:s};if(!i)return{...n,outcome:"not_found",projectId:null,projectName:null,syncPolicy:null,egressEligible:!1};const a={projectId:i.pid,projectName:i.pname,syncPolicy:i.policy,previous:i.vis},o=c=>c!=="within"&&(i.pid==null||i.policy==="cloud"||i.policy==="team");return s!=="within"&&i.policy==="private"?{...n,...a,outcome:"refused_private_grove",egressEligible:!1}:i.vis===s?{...n,...a,outcome:"unchanged",egressEligible:o(s)}:(this.db.transaction(()=>{this.db.prepare(r.update).run(s,e),b(this.db,"visibility.set",{kind:t,row_id:e,from:i.vis,to:s},{projectId:i.pid})})(),{...n,...a,outcome:"updated",egressEligible:o(s)})}listSkills(t,e,s,r){let i="SELECT * FROM skills WHERE 1=1";const n=[];if(t!==void 0&&(i+=" AND is_active = ?",n.push(t?1:0)),e&&(i+=" AND category = ?",n.push(e)),r&&(i+=" AND tier = ?",n.push(r)),s){const a=m(s);a&&(i+=" AND id IN (SELECT rowid FROM skills_fts WHERE skills_fts MATCH ?)",n.push(a))}return i+=" ORDER BY updated_at DESC",this.db.prepare(i).all(...n)}searchSkills(t,e=20,s){const r=m(t);if(!r)return[];const i=s?" AND s.tier = ?":"",n=this.db.prepare(`
176
194
  SELECT s.* FROM skills s
177
195
  JOIN skills_fts ON s.id = skills_fts.rowid
178
196
  WHERE skills_fts MATCH ?${i}
179
197
  ORDER BY rank
180
198
  LIMIT ?
181
- `);return s?n.all(r,s,e):n.all(r,e)}getSkillGraph(t){const e=this.db.prepare("SELECT * FROM skills").all(),s=new Map;for(const o of e)s.set(o.name,o);const r=o=>{if(!o)return[];try{const c=JSON.parse(o);return Array.isArray(c)?c.filter(l=>typeof l=="string"):[]}catch{return[]}},i=(o,c)=>{const l=s.get(o);if(!l)return null;const p=r(l.governs),_=r(l.composes),g={name:l.name,tier:l.tier??"atomic",description:l.description,governs:p,composes:_,children:[]},S=[];if(!c.has(o)){const R=new Set(c).add(o);for(const E of p){const b=i(E,R);b?g.children.push(b):S.push(E)}}return S.length&&(g.missing=S),g};if(t){const o=i(t,new Set);return o?[o]:[]}const n=e.filter(o=>(o.tier??"atomic")==="god").map(o=>o.name).sort(),a=[];for(const o of n){const c=i(o,new Set);c&&a.push(c)}return a}updateSkill(t,e){const s=[],r=[];return e.description!==void 0&&(s.push("description = ?"),r.push(e.description)),e.skill_path!==void 0&&(s.push("skill_path = ?"),r.push(e.skill_path)),e.category!==void 0&&(s.push("category = ?"),r.push(e.category)),e.is_active!==void 0&&(s.push("is_active = ?"),r.push(e.is_active?1:0)),e.tags!==void 0&&(s.push("tags = ?"),r.push(e.tags)),e.version!==void 0&&(s.push("version = ?"),r.push(e.version)),s.length===0?this.getSkill(t):(s.push("updated_at = datetime('now')"),r.push(t),this.db.prepare(`
199
+ `);return s?n.all(r,s,e):n.all(r,e)}getSkillGraph(t){const e=this.db.prepare("SELECT * FROM skills").all(),s=new Map;for(const o of e)s.set(o.name,o);const r=o=>{if(!o)return[];try{const c=JSON.parse(o);return Array.isArray(c)?c.filter(l=>typeof l=="string"):[]}catch{return[]}},i=(o,c)=>{const l=s.get(o);if(!l)return null;const p=r(l.governs),_=r(l.composes),g={name:l.name,tier:l.tier??"atomic",description:l.description,governs:p,composes:_,children:[]},S=[];if(!c.has(o)){const R=new Set(c).add(o);for(const h of p){const N=i(h,R);N?g.children.push(N):S.push(h)}}return S.length&&(g.missing=S),g};if(t){const o=i(t,new Set);return o?[o]:[]}const n=e.filter(o=>(o.tier??"atomic")==="god").map(o=>o.name).sort(),a=[];for(const o of n){const c=i(o,new Set);c&&a.push(c)}return a}updateSkill(t,e){const s=[],r=[];return e.description!==void 0&&(s.push("description = ?"),r.push(e.description)),e.skill_path!==void 0&&(s.push("skill_path = ?"),r.push(e.skill_path)),e.category!==void 0&&(s.push("category = ?"),r.push(e.category)),e.is_active!==void 0&&(s.push("is_active = ?"),r.push(e.is_active?1:0)),e.tags!==void 0&&(s.push("tags = ?"),r.push(e.tags)),e.version!==void 0&&(s.push("version = ?"),r.push(e.version)),s.length===0?this.getSkill(t):(s.push("updated_at = datetime('now')"),r.push(t),this.db.prepare(`
182
200
  UPDATE skills SET ${s.join(", ")} WHERE name = ? RETURNING *
183
201
  `).get(...r))}deleteSkill(t){return this.db.prepare("DELETE FROM skills WHERE name = ?").run(t).changes>0}deactivateSkill(t){return this.updateSkill(t,{is_active:!1})}activateSkill(t){return this.updateSkill(t,{is_active:!0})}getSkillStats(){const t=this.db.prepare("SELECT COUNT(*) as count FROM skills").get().count,e=this.db.prepare("SELECT COUNT(*) as count FROM skills WHERE is_active = 1").get().count,s=this.db.prepare(`
184
202
  SELECT category, COUNT(*) as count FROM skills WHERE category IS NOT NULL GROUP BY category
@@ -196,7 +214,7 @@ import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSy
196
214
  WHERE project_id = ?
197
215
  AND (tags = ? OR tags LIKE ? OR tags LIKE ? OR tags LIKE ?)
198
216
  LIMIT 1
199
- `).get(t,e,`${e},%`,`%,${e}`,`%,${e},%`)}updateQuestFields(t,e){const s=[],r=[];if(e.title!==void 0&&(s.push("title = ?"),r.push(e.title)),e.description!==void 0&&(s.push("description = ?"),r.push(e.description)),e.tags!==void 0&&(s.push("tags = ?"),r.push(e.tags)),s.length===0)return this.db.prepare("SELECT * FROM quests WHERE id = ?").get(t);s.push("updated_at = datetime('now')"),r.push(t);const i=this.db.prepare(`UPDATE quests SET ${s.join(", ")} WHERE id = ? RETURNING *`).get(...r);return i&&m(this.db,{projectId:i.project_id,kind:"quest",refTable:"quests",refId:t}),i}insertData(t,e,s,r,i){const n=this.resilience.withRetrySync(()=>this.db.prepare(`
217
+ `).get(t,e,`${e},%`,`%,${e}`,`%,${e},%`)}updateQuestFields(t,e){const s=[],r=[];if(e.title!==void 0&&(s.push("title = ?"),r.push(e.title)),e.description!==void 0&&(s.push("description = ?"),r.push(e.description)),e.tags!==void 0&&(s.push("tags = ?"),r.push(e.tags)),s.length===0)return this.db.prepare("SELECT * FROM quests WHERE id = ?").get(t);s.push("updated_at = datetime('now')"),r.push(t);const i=this.db.prepare(`UPDATE quests SET ${s.join(", ")} WHERE id = ? RETURNING *`).get(...r);return i&&E(this.db,{projectId:i.project_id,kind:"quest",refTable:"quests",refId:t}),i}insertData(t,e,s,r,i){const n=this.resilience.withRetrySync(()=>this.db.prepare(`
200
218
  INSERT INTO data_lake (project_id, category, key, value, metadata)
201
219
  VALUES (?, ?, ?, ?, ?)
202
220
  RETURNING *
@@ -213,7 +231,7 @@ import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSy
213
231
  WHERE project_id = ?
214
232
  ORDER BY created_at DESC
215
233
  LIMIT ? OFFSET ?
216
- `).all(t,s,r)}searchData(t,e){const s=h(t);if(!s)return[];try{return e?this.db.prepare(`
234
+ `).all(t,s,r)}searchData(t,e){const s=m(t);if(!s)return[];try{return e?this.db.prepare(`
217
235
  SELECT d.* FROM data_lake d
218
236
  JOIN data_lake_fts fts ON d.id = fts.rowid
219
237
  WHERE data_lake_fts MATCH ? AND d.project_id = ?
@@ -225,7 +243,7 @@ import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSy
225
243
  WHERE data_lake_fts MATCH ?
226
244
  ORDER BY bm25(data_lake_fts), d.created_at DESC
227
245
  LIMIT 100
228
- `).all(s)}catch{return[]}}searchArtifacts(t,e){const s=h(t);if(!s)return[];try{return e?this.db.prepare(`
246
+ `).all(s)}catch{return[]}}searchArtifacts(t,e){const s=m(t);if(!s)return[];try{return e?this.db.prepare(`
229
247
  SELECT a.* FROM memory_artifacts a
230
248
  JOIN memory_artifacts_fts fts ON a.id = fts.rowid
231
249
  WHERE memory_artifacts_fts MATCH ? AND a.project_id = ?
@@ -245,8 +263,8 @@ import{openDatabase as L}from"./storage/index.js";import{existsSync as u,mkdirSy
245
263
  WHERE project_id = ?
246
264
  GROUP BY category
247
265
  ORDER BY count DESC
248
- `).all(t)}forgetData(t){const e=[],s=[];if(t.match){e.push("(key LIKE ? OR value LIKE ?)");const o=`%${t.match}%`;s.push(o,o)}if(t.dataset&&(e.push("category = ?"),s.push(t.dataset)),typeof t.olderThanDays=="number"&&(e.push("created_at < datetime('now', ?)"),s.push(`-${Math.max(0,Math.floor(t.olderThanDays))} days`)),t.projectId!=null&&(e.push("project_id = ?"),s.push(t.projectId)),e.length===0)return{matched:0,deleted:0,samples:[]};const r=e.join(" AND "),i=this.db.prepare(`SELECT COUNT(*) AS n FROM data_lake WHERE ${r}`).get(...s).n,n=this.db.prepare(`SELECT category, key, created_at FROM data_lake WHERE ${r} ORDER BY created_at LIMIT 5`).all(...s);if(t.dryRun!==!1)return{matched:i,deleted:0,samples:n};const a=this.db.prepare(`DELETE FROM data_lake WHERE ${r}`).run(...s).changes;return{matched:i,deleted:a,samples:n}}deleteDataCategory(t,e){return this.db.prepare(`
266
+ `).all(t)}forgetData(t){const e=[],s=[];if(t.match){e.push("(key LIKE ? OR value LIKE ?)");const p=`%${t.match}%`;s.push(p,p)}if(t.dataset&&(e.push("category = ?"),s.push(t.dataset)),typeof t.olderThanDays=="number"&&(e.push("created_at < datetime('now', ?)"),s.push(`-${Math.max(0,Math.floor(t.olderThanDays))} days`)),t.projectId!=null&&(e.push("project_id = ?"),s.push(t.projectId)),e.length===0)return{matched:0,deleted:0,samples:[],counts:{},erasureId:null};const r=e.join(" AND "),i=this.db.prepare(`SELECT id, category, key FROM data_lake WHERE ${r}`).all(...s),n=i.length,a=this.db.prepare(`SELECT category, key, created_at FROM data_lake WHERE ${r} ORDER BY created_at LIMIT 5`).all(...s),o=t.dryRun!==!1,c=te(this.db,i,{subject:t.match,dryRun:o,reason:t.reason??(t.match?"subject":t.dataset?"source":"age"),predicate:{match:!!t.match,dataset:t.dataset??null,older_than_days:typeof t.olderThanDays=="number"?t.olderThanDays:null,project:t.projectId!=null},actor:t.actor??null}),l=o?0:c.counts.data_lake??0;return{matched:n,deleted:l,samples:a,counts:c.counts,erasureId:c.erasureId}}deleteDataCategory(t,e){return this.db.prepare(`
249
267
  DELETE FROM data_lake WHERE project_id = ? AND category = ?
250
268
  `).run(t,e).changes}*streamSessions(t){const e=this.db.prepare(`
251
269
  SELECT * FROM sessions WHERE project_id = ? ORDER BY date DESC
252
- `);for(const s of e.iterate(t))yield s}*streamData(t,e){const s=e?this.db.prepare("SELECT * FROM data_lake WHERE project_id = ? AND category = ?"):this.db.prepare("SELECT * FROM data_lake WHERE project_id = ?"),r=e?[t,e]:[t];for(const i of s.iterate(...r))yield i}getStats(){const t=this.db.prepare("SELECT COUNT(*) as count FROM projects").get(),e=this.db.prepare("SELECT COUNT(*) as count FROM sessions").get(),s=this.db.prepare("SELECT COUNT(*) as count FROM quests").get(),r=this.db.prepare("SELECT COUNT(*) as count FROM memory_artifacts").get(),i=this.db.prepare("SELECT COUNT(*) as count FROM data_lake").get(),n=this.db.prepare("SELECT COALESCE(SUM(tokens_estimate), 0) as total FROM sessions WHERE is_archived = 0").get(),a=this.db.pragma("page_count",{simple:!0}),o=this.db.pragma("page_size",{simple:!0}),c=a*o/(1024*1024);return{projects:t.count,sessions:e.count,quests:s.count,artifacts:r.count,dataPoints:i.count,totalTokens:n.total,dbSize:`${c.toFixed(2)} MB`}}getProjectStats(t){const e=this.db.prepare("SELECT COUNT(*) as count FROM sessions WHERE project_id = ?").get(t),s=this.db.prepare("SELECT COUNT(*) as count FROM quests WHERE project_id = ? AND status IN ('pending', 'in_progress')").get(t),r=this.db.prepare("SELECT COUNT(*) as count FROM quests WHERE project_id = ? AND status = 'completed'").get(t),i=this.db.prepare("SELECT COUNT(*) as count FROM data_lake WHERE project_id = ?").get(t),n=this.db.prepare("SELECT COALESCE(SUM(tokens_estimate), 0) as total FROM sessions WHERE project_id = ? AND is_archived = 0").get(t);return{sessions:e.count,quests:{pending:s.count,completed:r.count},dataPoints:i.count,tokens:n.total}}estimateTokens(t){return Math.ceil(t.length/4)}vacuum(){this.db.exec("VACUUM")}checkpoint(){this.db.pragma("wal_checkpoint(TRUNCATE)")}getResilienceStatus(){const t=this.resilience.getCircuitStatus(),e=this.resilience.getIncompleteOperations();return{circuitState:t.state,failures:t.failures,incompleteOps:e.length}}resetCircuitBreaker(){this.resilience.resetCircuit()}close(){try{this.checkpoint(),this.logger.info("Database checkpoint completed")}catch(t){this.logger.error("Checkpoint failed during close",{error:t.message})}try{this.db.close(),this.logger.info("Database closed successfully")}catch(t){this.logger.error("Database close failed",{error:t.message})}}checkIntegrity(){const t=[];try{const e=this.db.pragma("integrity_check",{simple:!0});e!=="ok"&&t.push(`Integrity check failed: ${e}`)}catch(e){t.push(`Integrity check error: ${e.message}`)}try{const e=this.db.pragma("foreign_key_check");e.length>0&&t.push(`Foreign key violations: ${e.length}`)}catch(e){t.push(`FK check error: ${e.message}`)}return{ok:t.length===0,issues:t}}}export{_e as WyrmDB,me as resolveDbPath,X as tightenStorePermissions};
270
+ `);for(const s of e.iterate(t))yield s}*streamData(t,e){const s=e?this.db.prepare("SELECT * FROM data_lake WHERE project_id = ? AND category = ?"):this.db.prepare("SELECT * FROM data_lake WHERE project_id = ?"),r=e?[t,e]:[t];for(const i of s.iterate(...r))yield i}getStats(){const t=this.db.prepare("SELECT COUNT(*) as count FROM projects").get(),e=this.db.prepare("SELECT COUNT(*) as count FROM sessions").get(),s=this.db.prepare("SELECT COUNT(*) as count FROM quests").get(),r=this.db.prepare("SELECT COUNT(*) as count FROM memory_artifacts").get(),i=this.db.prepare("SELECT COUNT(*) as count FROM data_lake").get(),n=this.db.prepare("SELECT COALESCE(SUM(tokens_estimate), 0) as total FROM sessions WHERE is_archived = 0").get(),a=this.db.pragma("page_count",{simple:!0}),o=this.db.pragma("page_size",{simple:!0}),c=a*o/(1024*1024);return{projects:t.count,sessions:e.count,quests:s.count,artifacts:r.count,dataPoints:i.count,totalTokens:n.total,dbSize:`${c.toFixed(2)} MB`}}getProjectStats(t){const e=this.db.prepare("SELECT COUNT(*) as count FROM sessions WHERE project_id = ?").get(t),s=this.db.prepare("SELECT COUNT(*) as count FROM quests WHERE project_id = ? AND status IN ('pending', 'in_progress')").get(t),r=this.db.prepare("SELECT COUNT(*) as count FROM quests WHERE project_id = ? AND status = 'completed'").get(t),i=this.db.prepare("SELECT COUNT(*) as count FROM data_lake WHERE project_id = ?").get(t),n=this.db.prepare("SELECT COALESCE(SUM(tokens_estimate), 0) as total FROM sessions WHERE project_id = ? AND is_archived = 0").get(t);return{sessions:e.count,quests:{pending:s.count,completed:r.count},dataPoints:i.count,tokens:n.total}}estimateTokens(t){return Math.ceil(t.length/4)}vacuum(){this.db.exec("VACUUM")}checkpoint(){this.db.pragma("wal_checkpoint(TRUNCATE)")}getResilienceStatus(){const t=this.resilience.getCircuitStatus(),e=this.resilience.getIncompleteOperations();return{circuitState:t.state,failures:t.failures,incompleteOps:e.length}}resetCircuitBreaker(){this.resilience.resetCircuit()}close(){try{this.checkpoint(),this.logger.info("Database checkpoint completed")}catch(t){this.logger.error("Checkpoint failed during close",{error:t.message})}try{this.db.close(),this.logger.info("Database closed successfully")}catch(t){this.logger.error("Database close failed",{error:t.message})}}checkIntegrity(){const t=[];try{const e=this.db.pragma("integrity_check",{simple:!0});e!=="ok"&&t.push(`Integrity check failed: ${e}`)}catch(e){t.push(`Integrity check error: ${e.message}`)}try{const e=this.db.pragma("foreign_key_check");e.length>0&&t.push(`Foreign key violations: ${e.length}`)}catch(e){t.push(`FK check error: ${e.message}`)}return{ok:t.length===0,issues:t}}}export{ye as WyrmDB,De as describeStoreError,Oe as resolveDbPath,ne as tightenStorePermissions};
package/dist/design.js CHANGED
@@ -20,7 +20,7 @@ const d=new Set(["color","type","spacing","motion","shadow","radius","breakpoint
20
20
  `).all(e)}deleteToken(e,r,t){return(this.db.prepare(`
21
21
  DELETE FROM design_tokens
22
22
  WHERE project_id = ? AND category = ? AND key = ?
23
- `).run(e,r,t).changes??0)>0}formatForContext(e){const r=this.listTokens(e);if(r.length===0)return"";const t=new Map;for(const s of r){const o=t.get(s.category)??[];o.push(s),t.set(s.category,o)}const n=[];n.push("### \u{1F3A8} Design tokens");for(const[s,o]of t){n.push(""),n.push(`**${s}:**`);for(const a of o)n.push(`- \`${a.key}\` = \`${a.value}\`${a.notes?` \u2014 ${a.notes}`:""}`)}return n.join(`
23
+ `).run(e,r,t).changes??0)>0}formatForContext(e){const r=this.listTokens(e);if(r.length===0)return"";const t=new Map;for(const s of r){const o=t.get(s.category)??[];o.push(s),t.set(s.category,o)}const n=[];n.push("### \u{1F3A8} Design tokens");for(const[s,o]of t){n.push(""),n.push(`**${s}:**`);for(const a of o)n.push(`- \`${a.key}\` = \`${a.value}\`${a.notes?` (${a.notes})`:""}`)}return n.join(`
24
24
  `)}addReference(e){if(!c.has(e.kind))throw new Error(`invalid kind: ${e.kind} (must be one of ${[...c].join(", ")})`);if(!e.location)throw new Error("location is required");const r=this.db.prepare(`
25
25
  INSERT INTO design_references (project_id, kind, location, title, notes, tags, updated_at)
26
26
  VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
@@ -0,0 +1,3 @@
1
+ import{c,formatTable as l,printSection as d,printSuccess as u,printWarning as g}from"./cli.js";import{enforceIngestRetention as m,ingestedCategories as h,RETENTION_GRACE_DAYS as y}from"./retention.js";import{redactBackfill as f}from"./redact-backfill.js";import{scanBackupHygiene as w,listBackups as b,defaultBackupDir as k}from"./backup.js";import{fmtBytes as R}from"./backup-cli.js";function $(a,o=" "){for(const[n,t]of Object.entries(a).sort((e,s)=>s[1]-e[1]))console.log(`${o}${String(t).padStart(6)} ${n}`)}function A(a,o){const n=typeof o.days=="string"?Number(o.days):void 0,t=o.yes===!0&&o["dry-run"]!==!0,e=m(a,{days:n,dryRun:!t,skipGrace:t,actor:"cli"});if(e.status==="disabled"){console.log("Ingest retention is off (WYRM_INGEST_RETAIN_DAYS=0), so nothing ages out.");return}d(t?`Ingest retention: erased (limit ${e.days}d)`:`Ingest retention (dry run, limit ${e.days}d)`),console.log(l(["Category",t?"Erased":"Rows past the limit"],e.categories.map(r=>[r.category,String(t?r.deleted:r.matched)])));const s={};for(const r of e.categories)for(const[i,p]of Object.entries(r.counts))s[i]=(s[i]??0)+p;if(Object.keys(s).length>0&&(console.log(t?" Removed, per table:":" Would remove, per table:"),$(s," ")),e.graceEndsAt){const r=Date.parse(e.graceEndsAt)<=Date.now();console.log(` Automatic enforcement by maintenance ${r?"has been active since":"starts"} ${e.graceEndsAt.slice(0,10)}.`)}else console.log(` Automatic enforcement has not started: the first maintenance run records a preview and waits ${y} days.`);t?(u(`Erased ${e.deleted} ingested row(s) and ${e.derived} derived row(s); the erasure is recorded in erasure_log.`),console.log(" Snapshots still hold the erased rows until they rotate out: wyrm backup prune, wyrm backup legacy.")):(console.log(c.dim(`
2
+ Dry run: nothing was deleted. --yes erases these now, without waiting for the grace period.`)),console.log(c.dim(" Change the limit with WYRM_INGEST_RETAIN_DAYS=<days> (0 turns retention off).")))}function _(a,o){const n=o.apply===!0&&o["dry-run"]!==!0,t=a.getDatabase(),e=f(t,{apply:n,ingestedCategories:h(a)});d(n?"Redaction back-fill":"Redaction back-fill (dry run)"),console.log(l(["Table","Scanned","Rows with credentials","Fields"],e.tables.map(r=>[r.table,String(r.scanned),String(r.rows),String(r.fields)])));const s=e.tables.reduce((r,i)=>r+i.rows,0);if(!n){console.log(c.dim(`
3
+ Dry run: nothing changed. ${s>0?"Re-run with --apply (add --vacuum to rewrite the file).":"Nothing to redact."}`));return}u(`Redacted ${s} row(s). Matched text is never printed.`),e.vectorsDropped>0&&console.log(` Dropped ${e.vectorsDropped} vector(s) built from the old text: wyrm index rebuild re-embeds them.`),o.vacuum===!0?(a.vacuum(),a.checkpoint(),console.log(" Vacuumed: the file is rewritten, so pages that held the old text are gone.")):s>0&&console.log(c.dim(" Freed pages may still hold the old text: wyrm maintenance redact --apply --vacuum rewrites the file.")),s>0&&g("Existing snapshots still hold the unredacted rows. Take a fresh one (wyrm backup create --encrypt), then remove the old ones (wyrm backup prune, wyrm backup legacy).")}function T(a){const o=w({dbPath:a}),n=[];if(o.exposed.length>0&&n.push({ok:!1,label:"Backup permissions",detail:`${o.exposed.length} backup file(s) or folder(s) in the Wyrm home are readable by other users`,fix:"wyrm backup legacy --dry-run, then wyrm backup legacy --apply (files 0600, folders 0700)"}),o.legacy.length>0){const t=o.legacy.reduce((e,s)=>e+s.bytes,0);n.push({ok:!1,label:"Stray backups",detail:`${o.legacy.length} database copy(ies) outside rotation (${R(t)}); nothing ages them out, and they keep rows an erasure removed`,fix:"wyrm backup legacy --dry-run, then --apply --adopt to move them into rotation"})}if(n.length===0){const t=b(k(a));n.push(t.length>0?{ok:!0,label:"Backups",detail:`${t.length} rotated snapshot(s), newest ${Math.floor((Date.now()-t[0].at.getTime())/864e5)}d old, no stray or exposed copies`}:{ok:!0,label:"Backups",detail:"no snapshots yet (optional): wyrm backup create --encrypt"})}return n}export{A as cmdForgetRetention,_ as cmdMaintenanceRedact,T as doctorBackupRows,$ as printErasureCounts};
@@ -0,0 +1,8 @@
1
+ import{getActor as u}from"./handlers/boundary.js";const s=new Set;function m(t){return s.add(t),()=>{s.delete(t)}}function _(t){const e={};return t.match&&(e.match=!0),typeof t.dataset=="string"&&t.dataset&&(e.dataset=t.dataset.slice(0,128)),typeof t.older_than_days=="number"&&Number.isFinite(t.older_than_days)&&(e.older_than_days=Math.floor(t.older_than_days)),t.project&&(e.project=!0),e}function g(t){const e={};for(const[r,o]of Object.entries(t))/^[a-z_]{1,64}$/.test(r)&&Number.isFinite(o)&&o>0&&(e[r]=Math.floor(o));return e}function y(t,e){const r=g(e.counts),o=Object.values(r).reduce((n,l)=>n+l,0),d=_(e.predicate),a=u(),c=t.prepare(`
2
+ INSERT INTO erasure_log (mode, reason, category, predicate_json, counts_json, total, actor, agent_id, run_id)
3
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
4
+ RETURNING id, erased_at
5
+ `).get(e.mode,e.reason,e.category?e.category.slice(0,128):null,JSON.stringify(d),JSON.stringify(r),o,e.actor??null,a.agent_id,a.run_id),i={id:c.id,erased_at:c.erased_at,mode:e.mode,reason:e.reason,category:e.category,predicate:d,counts:r,total:o,actor:e.actor??null,agent_id:a.agent_id,run_id:a.run_id};for(const n of s)n(i,t);return i}function S(t,e=50){return t.prepare(`
6
+ SELECT id, erased_at, mode, reason, category, predicate_json, counts_json, total, actor, agent_id, run_id
7
+ FROM erasure_log ORDER BY id DESC LIMIT ?
8
+ `).all(Math.max(1,Math.min(1e3,Math.floor(e)))).map(o=>({id:Number(o.id),erased_at:String(o.erased_at),mode:o.mode,reason:o.reason,category:o.category??null,predicate:JSON.parse(String(o.predicate_json)),counts:JSON.parse(String(o.counts_json)),total:Number(o.total),actor:o.actor??null,agent_id:o.agent_id??null,run_id:o.run_id??null}))}export{S as listErasures,m as onErasureRecorded,y as recordErasure};
@@ -0,0 +1 @@
1
+ import{recordErasure as y}from"./erasure-log.js";import"./governance.js";function F(t){return`lake:${t}`}const O={tool_call_log:"DELETE FROM tool_call_log WHERE id = ?",external_call_log:"DELETE FROM external_call_log WHERE id = ?",llm_query_log:"DELETE FROM llm_query_log WHERE id = ?",usage_events:"DELETE FROM usage_events WHERE id = ?",events:"DELETE FROM events WHERE cursor = ?",served_records:"DELETE FROM served_records WHERE rowid = ?",session_seen_artifacts:"DELETE FROM session_seen_artifacts WHERE rowid = ?",decision_edges:"DELETE FROM decision_edges WHERE id = ?",indexing_queue:"DELETE FROM indexing_queue WHERE id = ?",vectors:"DELETE FROM vectors WHERE id = ?",memory_artifacts:"DELETE FROM memory_artifacts WHERE id = ?",data_lake:"DELETE FROM data_lake WHERE id = ?"},M=3,T=/[A-Za-z0-9_-]/;function v(t,c){if(!c)return!1;let s=0;for(;;){const n=t.indexOf(c,s);if(n<0)return!1;const d=n>0?t[n-1]:"",E=t[n+c.length]??"";if(!(d&&T.test(d))&&!(E&&T.test(E)))return!0;s=n+1}}function i(t,c,...s){try{return t.prepare(c).all(...s)}catch{return[]}}function h(t,c,s){const n=new Set(c.map(e=>e.id)),d=[...new Set(c.map(e=>e.key).filter(Boolean))],E=new Set(d.map(F)),m=s.subject&&s.subject.length>=M?s.subject.toLowerCase():null,l=(...e)=>e.some(o=>{if(!o)return!1;if(m&&o.toLowerCase().includes(m))return!0;for(const f of d)if(v(o,f))return!0;return!1}),R={},r=(e,o)=>{(R[e]??=[]).push(o)};if(n.size===0)return{counts:{},total:0,erasureId:null};for(const e of n)r("data_lake",e);const a=new Set;if(E.size>0)for(const e of i(t,"SELECT id, tags FROM memory_artifacts WHERE instr(tags, 'lake:') > 0"))e.tags.split(",").some(o=>E.has(o.trim()))&&a.add(e.id);for(const e of a)r("memory_artifacts",e);for(const e of i(t,"SELECT id, content_id FROM vectors WHERE content_type = 'note'"))n.has(e.content_id)&&r("vectors",e.id);if(a.size>0)for(const e of i(t,"SELECT id, content_id FROM vectors WHERE content_type = 'artifact'"))a.has(e.content_id)&&r("vectors",e.id);for(const e of i(t,"SELECT id, content_type, content_id FROM indexing_queue WHERE content_type IN ('note', 'artifact')"))(e.content_type==="note"&&n.has(e.content_id)||e.content_type==="artifact"&&a.has(e.content_id))&&r("indexing_queue",e.id);for(const e of i(t,"SELECT cursor, ref_table, ref_id FROM events WHERE ref_table IN ('memory_artifacts', 'data_lake')")){const o=Number(e.ref_id);(e.ref_table==="memory_artifacts"&&a.has(o)||e.ref_table==="data_lake"&&n.has(o))&&r("events",e.cursor)}if(a.size>0){for(const e of i(t,"SELECT rowid, artifact_id FROM served_records WHERE artifact_kind = 'mem'"))a.has(e.artifact_id)&&r("served_records",e.rowid);for(const e of i(t,"SELECT rowid, artifact_id FROM session_seen_artifacts WHERE artifact_kind = 'memory'"))a.has(e.artifact_id)&&r("session_seen_artifacts",e.rowid)}for(const e of i(t,"SELECT id, from_kind, from_id, to_kind, to_id FROM decision_edges WHERE from_kind IN ('data', 'artifact') OR to_kind IN ('data', 'artifact')")){const o=(f,u)=>f==="data"&&n.has(u)||f==="artifact"&&a.has(u);(o(e.from_kind,e.from_id)||o(e.to_kind,e.to_id))&&r("decision_edges",e.id)}if(d.length>0||m){for(const e of i(t,"SELECT id, args_summary AS a, error_message AS e FROM tool_call_log"))l(e.a,e.e)&&r("tool_call_log",e.id);for(const e of i(t,"SELECT id, args_summary AS a, error_message AS e FROM external_call_log"))l(e.a,e.e)&&r("external_call_log",e.id);for(const e of i(t,"SELECT id, query AS q, context_summary AS c, response AS r, error_message AS e FROM llm_query_log"))l(e.q,e.c,e.r,e.e)&&r("llm_query_log",e.id);for(const e of i(t,"SELECT id, error_message AS e FROM usage_events WHERE error_message IS NOT NULL"))l(e.e)&&r("usage_events",e.id)}const _={};for(const[e,o]of Object.entries(R))_[e]=new Set(o).size;const g=Object.values(_).reduce((e,o)=>e+o,0);if(s.dryRun)return{counts:_,total:g,erasureId:null};const S=[...new Set(c.map(e=>e.category))];let L=null;return t.transaction(()=>{for(const e of Object.keys(O)){const o=R[e];if(!o?.length)continue;const f=t.prepare(O[e]);for(const u of new Set(o))f.run(u)}L=y(t,{mode:"erase",reason:s.reason,category:S.length===1?S[0]:null,predicate:s.predicate,counts:_,actor:s.actor??null}).id})(),{counts:_,total:g,erasureId:L}}export{h as eraseLakeRows,F as lakeProvenanceTag,v as quotesKey};
@@ -1 +1 @@
1
- import{logger as f}from"./logger.js";import{guardedFetchJson as m,checkPeerUrl as P}from"./repl-guard.js";const u=500,g=e=>`repl:push:${e.baseUrl}:${e.localProjectId}`,y=e=>`repl:pull:${e.baseUrl}:${e.localProjectId}`,_=e=>Number.parseInt(e||"0",10)||0;function v(e){const r={"Content-Type":"application/json"};return e&&(r.Authorization=`Bearer ${e}`),r}function j(e){return{origin_device:e.origin_device,origin_seq:e.origin_seq,cloud_cursor:e.cursor,kind:e.kind,ref_table:e.ref_table,ref_id:e.ref_id,actor:e.actor,is_shared:e.is_shared,payload_json:e.payload_json,created_at:e.created_at}}async function $(e,r,a){const o=P(r.baseUrl);if(!o.ok)return{pushed:0,error:`push: ${o.reason}`};const s=_(e.getMeta(g(r))),t=e.eventsForPush(r.localProjectId,s,u);if(t.length===0)return{pushed:0};try{return await m(a,r.baseUrl,"/events",{method:"POST",headers:v(r.token),body:JSON.stringify({project:r.remoteProject,events:t.map(j)})}),e.setMeta(g(r),String(t[t.length-1].cursor)),{pushed:t.length}}catch(c){const i=c instanceof Error?c.message:String(c);return f.warn("live-memory: push failed",{peer:r.baseUrl,err:i}),{pushed:0,error:`push: ${i}`}}}async function U(e,r,a){const o=_(e.getMeta(y(r)));try{const s=`/events?project=${encodeURIComponent(r.remoteProject)}&since=${o}&limit=${u}&shared=1`,t=await m(a,r.baseUrl,s,{method:"GET",headers:v(r.token)}),c=(Array.isArray(t.events)?t.events:[]).filter(n=>!!n&&typeof n=="object"&&Number.isFinite(n.cursor)).slice(0,u);let i=0,d=0,h=0,l=o;for(const n of c){const p=e.ingestRemoteEvent(r.localProjectId,{...j(n),cloud_cursor:n.cursor});p==="inserted"?i++:p==="echo"?h++:d++,n.cursor>l&&(l=n.cursor)}return e.setMeta(y(r),String(l)),{pulled:i,duplicates:d,echoes:h}}catch(s){const t=s instanceof Error?s.message:String(s);return f.warn("live-memory: pull failed",{peer:r.baseUrl,err:t}),{pulled:0,duplicates:0,echoes:0,error:`pull: ${t}`}}}async function S(e,r,a){const o=await U(e,r,a),s=await $(e,r,a),t=[o.error,s.error].filter(c=>!!c);return{pulled:o.pulled,pushed:s.pushed,duplicates:o.duplicates,echoes:o.echoes,errors:t}}export{U as pullPeerEvents,$ as pushSharedEvents,S as replicateOnce};
1
+ import{logger as f}from"./logger.js";import{guardedFetchJson as g,checkPeerUrl as P}from"./repl-guard.js";const u=500,m=e=>`repl:push:${e.baseUrl}:${e.localProjectId}`,_=e=>`repl:pull:${e.baseUrl}:${e.localProjectId}`,y=e=>Number.parseInt(e||"0",10)||0;function v(e){const r={"Content-Type":"application/json"};return e&&(r.Authorization=`Bearer ${e}`),r}function j(e){return{origin_device:e.origin_device,origin_epoch:e.origin_epoch??0,origin_seq:e.origin_seq,cloud_cursor:e.cursor,kind:e.kind,ref_table:e.ref_table,ref_id:e.ref_id,actor:e.actor,is_shared:e.is_shared,payload_json:e.payload_json,created_at:e.created_at}}async function $(e,r,a){const o=P(r.baseUrl);if(!o.ok)return{pushed:0,error:`push: ${o.reason}`};const s=y(e.getMeta(m(r))),t=e.eventsForPush(r.localProjectId,s,u);if(t.length===0)return{pushed:0};try{return await g(a,r.baseUrl,"/events",{method:"POST",headers:v(r.token),body:JSON.stringify({project:r.remoteProject,events:t.map(j)})}),e.setMeta(m(r),String(t[t.length-1].cursor)),{pushed:t.length}}catch(c){const i=c instanceof Error?c.message:String(c);return f.warn("live-memory: push failed",{peer:r.baseUrl,err:i}),{pushed:0,error:`push: ${i}`}}}async function U(e,r,a){const o=y(e.getMeta(_(r)));try{const s=`/events?project=${encodeURIComponent(r.remoteProject)}&since=${o}&limit=${u}&shared=1`,t=await g(a,r.baseUrl,s,{method:"GET",headers:v(r.token)}),c=(Array.isArray(t.events)?t.events:[]).filter(n=>!!n&&typeof n=="object"&&Number.isFinite(n.cursor)).slice(0,u);let i=0,d=0,h=0,l=o;for(const n of c){const p=e.ingestRemoteEvent(r.localProjectId,{...j(n),cloud_cursor:n.cursor});p==="inserted"?i++:p==="echo"?h++:d++,n.cursor>l&&(l=n.cursor)}return e.setMeta(_(r),String(l)),{pulled:i,duplicates:d,echoes:h}}catch(s){const t=s instanceof Error?s.message:String(s);return f.warn("live-memory: pull failed",{peer:r.baseUrl,err:t}),{pulled:0,duplicates:0,echoes:0,error:`pull: ${t}`}}}async function S(e,r,a){const o=await U(e,r,a),s=await $(e,r,a),t=[o.error,s.error].filter(c=>!!c);return{pulled:o.pulled,pushed:s.pushed,duplicates:o.duplicates,echoes:o.echoes,errors:t}}export{U as pullPeerEvents,$ as pushSharedEvents,S as replicateOnce};
package/dist/events.js CHANGED
@@ -1,18 +1,18 @@
1
- import{randomUUID as m}from"node:crypto";import{hostname as R}from"node:os";import{logger as p}from"./logger.js";import{getActor as T}from"./handlers/boundary.js";const I=new Set(["capture","session_update","quest","truth","presence","tool_call","failure","decision"]),y=64*1024,O=80,u=256,f=864e5,_=e=>typeof e=="number"&&Number.isInteger(e)&&e>=0&&e<=Number.MAX_SAFE_INTEGER,s=(e,t)=>typeof e=="string"?e.slice(0,t):null;function M(){const e=(process.env.WYRM_LIVE_MEMORY||"").toLowerCase();return e!=="0"&&e!=="false"&&e!=="off"}const S=new WeakMap;function g(e){const t=S.get(e);if(t)return t;const r=()=>e.prepare("SELECT value FROM wyrm_meta WHERE key = 'device_id'").get()?.value;let n=r();if(!n){const o=`${R().slice(0,24)}-${m().slice(0,8)}`;e.prepare("INSERT OR IGNORE INTO wyrm_meta (key, value) VALUES ('device_id', ?)").run(o),n=r()||o}return S.set(e,n),n}function c(e,t,r,n){const o=typeof e=="number"&&Number.isFinite(e)?Math.floor(e):n;return Math.max(t,Math.min(r,o))}function C(e,t){if(M())try{const r=g(e),n=t.isShared?1:0;let o=null;if(n&&t.payload!==void 0)try{o=JSON.stringify(t.payload)}catch{o=null}const i=T(),a=t.agentId!==void 0?t.agentId:i.agent_id,E=t.runId!==void 0?t.runId:i.run_id;e.prepare(`
2
- INSERT INTO events
3
- (origin_device, origin_seq, project_id, kind, ref_table, ref_id, actor, is_shared, payload_json, created_at, agent_id, run_id)
4
- VALUES (?, (SELECT COALESCE(MAX(origin_seq), 0) + 1 FROM events WHERE origin_device = ?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5
- `).run(r,r,String(t.projectId),t.kind,t.refTable??null,t.refId!=null?String(t.refId):null,t.actor??null,n,o,Date.now(),a,E)}catch(r){p.warn("live-memory: emitEvent dropped",{err:r instanceof Error?r.message:String(r)})}}function D(e,t,r=0,n=100){const o=c(n,1,1e3,100),i=c(r,0,Number.MAX_SAFE_INTEGER,0);return e.prepare("SELECT * FROM events WHERE project_id = ? AND cursor > ? ORDER BY cursor ASC LIMIT ?").all(String(t),i,o)}function F(e,t,r=20){const n=c(r,1,200,20),o=e.prepare("SELECT * FROM events WHERE project_id = ? ORDER BY cursor DESC LIMIT ?").all(String(t),n);return o.reverse(),{cursor:e.prepare("SELECT COALESCE(MAX(cursor), 0) AS c FROM events WHERE project_id = ?").get(String(t)).c,recent:o}}function v(e,t,r){try{if(!r||typeof r.origin_device!="string"||!_(r.origin_seq))return"duplicate";const n=s(r.origin_device,O);if(!n)return"duplicate";if(n===g(e))return"echo";if(typeof r.kind!="string"||!I.has(r.kind))return"duplicate";const o=r.is_shared?1:0;let i=o&&typeof r.payload_json=="string"?r.payload_json:null;i&&i.length>y&&(i=null);const a=Date.now(),E=_(r.created_at)&&r.created_at<=a+f?r.created_at:a,d=_(r.cloud_cursor)?r.cloud_cursor:null;return e.prepare(`
1
+ import{logger as _}from"./logger.js";import{getActor as m}from"./handlers/boundary.js";import{getDeviceId as R,getLineage as f,nextOriginSeq as h}from"./lineage.js";const I=new Set(["capture","session_update","quest","truth","presence","tool_call","failure","decision"]),T=64*1024,y=80,E=256,g=864e5,l=r=>typeof r=="number"&&Number.isInteger(r)&&r>=0&&r<=Number.MAX_SAFE_INTEGER,p=(r,t)=>typeof r=="string"?r.slice(0,t):null;function M(){const r=(process.env.WYRM_LIVE_MEMORY||"").toLowerCase();return r!=="0"&&r!=="false"&&r!=="off"}function a(r,t,e,n){const o=typeof r=="number"&&Number.isFinite(r)?Math.floor(r):n;return Math.max(t,Math.min(e,o))}function D(r,t){if(M())try{const{deviceId:e,epoch:n}=f(r),o=t.isShared?1:0;let i=null;if(o&&t.payload!==void 0)try{i=JSON.stringify(t.payload)}catch{i=null}const c=m(),s=t.agentId!==void 0?t.agentId:c.agent_id,u=t.runId!==void 0?t.runId:c.run_id;r.transaction(()=>{const d=h(r,e);r.prepare(`
2
+ INSERT INTO events
3
+ (origin_device, origin_epoch, origin_seq, project_id, kind, ref_table, ref_id, actor, is_shared, payload_json, created_at, agent_id, run_id)
4
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5
+ `).run(e,n,d,String(t.projectId),t.kind,t.refTable??null,t.refId!=null?String(t.refId):null,t.actor??null,o,i,Date.now(),s,u)})()}catch(e){_.warn("live-memory: emitEvent dropped",{err:e instanceof Error?e.message:String(e)})}}function C(r,t,e=0,n=100){const o=a(n,1,1e3,100),i=a(e,0,Number.MAX_SAFE_INTEGER,0);return r.prepare("SELECT * FROM events WHERE project_id = ? AND cursor > ? ORDER BY cursor ASC LIMIT ?").all(String(t),i,o)}function F(r,t,e=20){const n=a(e,1,200,20),o=r.prepare("SELECT * FROM events WHERE project_id = ? ORDER BY cursor DESC LIMIT ?").all(String(t),n);return o.reverse(),{cursor:r.prepare("SELECT COALESCE(MAX(cursor), 0) AS c FROM events WHERE project_id = ?").get(String(t)).c,recent:o}}function j(r,t,e){try{if(!e||typeof e.origin_device!="string"||!l(e.origin_seq))return"duplicate";const n=p(e.origin_device,y);if(!n)return"duplicate";const o=l(e.origin_epoch)?e.origin_epoch:0,i=f(r);if(n===i.deviceId&&o===i.epoch)return"echo";if(typeof e.kind!="string"||!I.has(e.kind))return"duplicate";const c=e.is_shared?1:0;let s=c&&typeof e.payload_json=="string"?e.payload_json:null;s&&s.length>T&&(s=null);const u=Date.now(),d=l(e.created_at)&&e.created_at<=u+g?e.created_at:u,S=l(e.cloud_cursor)?e.cloud_cursor:null;return r.prepare(`
6
6
  INSERT OR IGNORE INTO events
7
- (origin_device, origin_seq, cloud_cursor, project_id, kind, ref_table, ref_id, actor, is_shared, payload_json, created_at, agent_id, run_id)
8
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
9
- `).run(n,r.origin_seq,d,String(t),r.kind,s(r.ref_table,u),s(r.ref_id,u),s(r.actor,u),o,i,E,s(r.agent_id,u),s(r.run_id,u)).changes>0?"inserted":"duplicate"}catch(n){return p.warn("live-memory: ingestRemoteEvent dropped",{err:n instanceof Error?n.message:String(n)}),"duplicate"}}function j(e,t,r=0,n=200){const o=c(n,1,1e3,200),i=c(r,0,Number.MAX_SAFE_INTEGER,0);try{const a=e.prepare("SELECT sync_policy AS p FROM projects WHERE CAST(id AS TEXT) = ? OR path = ? LIMIT 1").get(String(t),String(t));if(a&&a.p&&a.p!=="team")return[]}catch{}return e.prepare(`
7
+ (origin_device, origin_epoch, origin_seq, cloud_cursor, project_id, kind, ref_table, ref_id, actor, is_shared, payload_json, created_at, agent_id, run_id)
8
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
9
+ `).run(n,o,e.origin_seq,S,String(t),e.kind,p(e.ref_table,E),p(e.ref_id,E),p(e.actor,E),c,s,d,p(e.agent_id,E),p(e.run_id,E)).changes>0?"inserted":"duplicate"}catch(n){return _.warn("live-memory: ingestRemoteEvent dropped",{err:n instanceof Error?n.message:String(n)}),"duplicate"}}function x(r,t,e=0,n=200){const o=a(n,1,1e3,200),i=a(e,0,Number.MAX_SAFE_INTEGER,0);try{const c=r.prepare("SELECT sync_policy AS p FROM projects WHERE CAST(id AS TEXT) = ? OR path = ? LIMIT 1").get(String(t),String(t));if(c&&c.p&&c.p!=="team")return[]}catch{}return r.prepare(`
10
10
  SELECT * FROM events
11
11
  WHERE project_id = ? AND is_shared = 1 AND cursor > ?
12
12
  ORDER BY cursor ASC LIMIT ?
13
- `).all(String(t),i,o)}function w(e,t={}){const r=c(t.olderThanDays,1,3650,90),n=c(t.maxPerProject,1,1e6,5e3);try{if(!e.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'events'").get())return{deleted:0};let i=0;const a=Date.now()-r*f;i+=e.prepare("DELETE FROM events WHERE created_at < ?").run(a).changes;const E=e.prepare("SELECT project_id FROM events GROUP BY project_id HAVING COUNT(*) > ?").all(n),d=e.prepare(`
13
+ `).all(String(t),i,o)}function w(r,t={}){const e=a(t.olderThanDays,1,3650,90),n=a(t.maxPerProject,1,1e6,5e3);try{if(!r.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'events'").get())return{deleted:0};let i=0;const c=Date.now()-e*g;i+=r.prepare("DELETE FROM events WHERE created_at < ?").run(c).changes;const s=r.prepare("SELECT project_id FROM events GROUP BY project_id HAVING COUNT(*) > ?").all(n),u=r.prepare(`
14
14
  DELETE FROM events WHERE project_id = ? AND cursor <= (
15
15
  SELECT cursor FROM events WHERE project_id = ? ORDER BY cursor DESC LIMIT 1 OFFSET ?
16
16
  )
17
- `);for(const{project_id:l}of E)i+=d.run(l,l,n).changes;return{deleted:i}}catch(o){return p.warn("live-memory: pruneEvents failed",{db:e.name,err:o instanceof Error?o.message:String(o)}),{deleted:0}}}function W(e,t){return e.prepare("SELECT value FROM wyrm_meta WHERE key = ?").get(t)?.value}function x(e,t,r){e.prepare(`INSERT INTO wyrm_meta (key, value) VALUES (?, ?)
18
- ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(t,r)}export{C as emitEvent,j as eventsForPush,D as eventsSince,g as getDeviceId,W as getMeta,v as ingestRemoteEvent,M as isLiveMemoryEnabled,w as pruneEvents,x as setMeta,F as subscribeEvents};
17
+ `);for(const{project_id:d}of s)i+=u.run(d,d,n).changes;return{deleted:i}}catch(o){return _.warn("live-memory: pruneEvents failed",{db:r.name,err:o instanceof Error?o.message:String(o)}),{deleted:0}}}function W(r,t){return r.prepare("SELECT value FROM wyrm_meta WHERE key = ?").get(t)?.value}function v(r,t,e){r.prepare(`INSERT INTO wyrm_meta (key, value) VALUES (?, ?)
18
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(t,e)}export{D as emitEvent,x as eventsForPush,C as eventsSince,R as getDeviceId,W as getMeta,j as ingestRemoteEvent,M as isLiveMemoryEnabled,w as pruneEvents,v as setMeta,F as subscribeEvents};