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
@@ -1,21 +1,21 @@
1
- import{readFileSync as C,statSync as D}from"node:fs";import{isAbsolute as k,relative as W,resolve as g,sep as j}from"node:path";import{sanitizeFtsQuery as H}from"./security.js";import{WyrmCrypto as F}from"./crypto.js";import{emitEvent as m,isLiveMemoryEnabled as $}from"./events.js";import{getActor as h,hasExplicitAttribution as N}from"./handlers/boundary.js";import{readActor as B,resolveAttribution as q}from"./attribution.js";import{normalizeContentSignature as y,containsFailedSignature as v}from"./content-signature.js";const x=new Set(["a","an","the","this","that","these","those","and","or","but","if","then","than","so","to","of","in","on","at","by","for","from","with","without","no","not","as","is","are","was","were","be","been","being","will","would","can","could","should","it","its","i","we","you","my","our","your","again","just","now","please","let","lets","do","does","did","into","over","about"]),M={run:0,project:1,global:2},Y=8,ee="source changed since this failure was recorded \u2014 re-verify",T=200,L=5;function te(c){const e=n=>Object.entries(n).filter(([,r])=>r>0).map(([r,i])=>`${r} ${i}`).join(" \xB7 ")||"none",t=n=>n.map(r=>`${"who"in r?r.who:r.run_id} \xD7${r.count}`).join(" \xB7 ")||"none",o=c.blocked.this_run.run_id===null?"- This run: (no run identity \u2014 export WYRM_RUN_ID or pass run_id)":`- This run (${c.blocked.this_run.run_id}): ${c.blocked.this_run.blocked} repeat(s) blocked`;return`\u{F115D} **Failure Analytics** (${c.project_id===null?"all projects":`project #${c.project_id}`})
1
+ import{readFileSync as j,statSync as D}from"node:fs";import{isAbsolute as W,relative as H,resolve as g,sep as y}from"node:path";import{sanitizeFtsQuery as F}from"./security.js";import{WyrmCrypto as q}from"./crypto.js";import{emitEvent as O,isLiveMemoryEnabled as $}from"./events.js";import{getActor as R,hasExplicitAttribution as m}from"./handlers/boundary.js";import{readActor as v,resolveAttribution as B}from"./attribution.js";import{recordGovernanceEvent as L}from"./governance.js";import{normalizeContentSignature as w,containsFailedSignature as x}from"./content-signature.js";const Y=new Set(["a","an","the","this","that","these","those","and","or","but","if","then","than","so","to","of","in","on","at","by","for","from","with","without","no","not","as","is","are","was","were","be","been","being","will","would","can","could","should","it","its","i","we","you","my","our","your","again","just","now","please","let","lets","do","does","did","into","over","about"]),M={run:0,project:1,global:2},z=8,oe="source changed since this failure was recorded \u2014 re-verify",P="no hits in the quiet period, so it warns instead of blocking; recording it again re-arms the block",G=60,V=44;function ae(d=process.env){const e=(d.WYRM_FAILURE_QUIET_DAYS??"").trim();if(e==="")return G;if(/^(off|false|no)$/i.test(e))return 0;const t=Number(e);return Number.isFinite(t)&&t>0?t:0}const T=200,A=5;function se(d){const e=r=>Object.entries(r).filter(([,n])=>n>0).map(([n,o])=>`${n} ${o}`).join(" \xB7 ")||"none",t=r=>r.map(n=>`${"who"in n?n.who:n.run_id} \xD7${n.count}`).join(" \xB7 ")||"none",i=d.blocked.this_run.run_id===null?"- This run: (no run identity; export WYRM_RUN_ID or pass run_id)":`- This run (${d.blocked.this_run.run_id}): ${d.blocked.this_run.blocked} repeat(s) blocked`;return`\u{F115D} **Failure Analytics** (${d.project_id===null?"all projects":`project #${d.project_id}`})
2
2
 
3
- **Recorded:** ${c.recorded.total} failure(s), ${c.recorded.unresolved} unresolved
4
- - By quarantine: ${e(c.recorded.by_quarantine_scope)}
5
- - By scope: ${e(c.recorded.by_scope)}
6
- - By agent: ${t(c.recorded.by_agent)}
7
- - By run: ${t(c.recorded.by_run)}
3
+ **Recorded:** ${d.recorded.total} failure(s), ${d.recorded.unresolved} unresolved
4
+ - By quarantine: ${e(d.recorded.by_quarantine_scope)}
5
+ - By scope: ${e(d.recorded.by_scope)}
6
+ - By agent: ${t(d.recorded.by_agent)}
7
+ - By run: ${t(d.recorded.by_run)}
8
8
 
9
- **Confirmations:** ${c.confirmations.total} from ${c.confirmations.distinct_agents} distinct agent(s)
9
+ **Confirmations:** ${d.confirmations.total} from ${d.confirmations.distinct_agents} distinct agent(s)
10
10
 
11
- **Repeats blocked:** ${c.blocked.total} total
12
- ${o}
13
- - By agent: ${t(c.blocked.by_agent)}
14
- - By run: ${t(c.blocked.by_run)}
15
- - Top blockers: ${c.blocked.top_blocked.map(n=>`#${n.failure_id} ${n.scope}:${n.target} \xD7${n.blocks}`).join(" \xB7 ")||"none"}`}function z(c,e){if(c==="exact")return 1;if(c==="target")return .95;if(c==="canonical")return .9;if(c==="related")return .4;const t=Math.max(0,-(e??0));return Number((.05+.85*(t/(t+2))).toFixed(4))}function b(c){return c.toLowerCase().replace(/\s+/g," ").trim()}function w(c){return b(c).slice(0,200)}function I(c){const e=b(c).split(" ").filter(Boolean);let t=0;const o=[];for(;t<e.length&&!e[t].startsWith("-");)o.push(e[t]),t++;const n=[],r=[];for(;t<e.length;t++){const i=e[t];i.startsWith("-")?!i.includes("=")&&t+1<e.length&&!e[t+1].startsWith("-")?(r.push(`${i}=${e[t+1]}`),t++):r.push(i):n.push(i)}return r.sort(),[...o,...n,...r].join(" ")}function U(c){const e=b(c).split(" ").filter(Boolean),t=[];for(const o of e){if(o.startsWith("-"))break;t.push(o)}return t.length>=2?t.join(" "):""}class O{db;constructor(e){this.db=e}static signature(e,t,o){return`${e}:${w(t)}:${w(o).slice(0,80)}`}record(e){const t=O.signature(e.scope,e.target,e.description),o=e.project_id??null,n=e.content&&y(e.content)||null,r=e.scope==="command"&&I(e.target)||null,i=e.scope==="command"&&U(e.target)||null,a=h(),_=this.db.transaction(()=>{const d=this.db.prepare(`
11
+ **Repeats blocked:** ${d.blocked.total} total
12
+ ${i}
13
+ - By agent: ${t(d.blocked.by_agent)}
14
+ - By run: ${t(d.blocked.by_run)}
15
+ - Top blockers: ${d.blocked.top_blocked.map(r=>`#${r.failure_id} ${r.scope}:${r.target} \xD7${r.blocks}`).join(" \xB7 ")||"none"}`}function Q(d,e){if(d==="exact")return 1;if(d==="target")return .95;if(d==="canonical")return .9;if(d==="related")return .4;const t=Math.max(0,-(e??0));return Number((.05+.85*(t/(t+2))).toFixed(4))}function b(d){return d.toLowerCase().replace(/\s+/g," ").trim()}function U(d){return b(d).slice(0,200)}function I(d){const e=b(d).split(" ").filter(Boolean);let t=0;const i=[];for(;t<e.length&&!e[t].startsWith("-");)i.push(e[t]),t++;const r=[],n=[];for(;t<e.length;t++){const o=e[t];o.startsWith("-")?!o.includes("=")&&t+1<e.length&&!e[t+1].startsWith("-")?(n.push(`${o}=${e[t+1]}`),t++):n.push(o):r.push(o)}return n.sort(),[...i,...r,...n].join(" ")}function k(d){const e=b(d).split(" ").filter(Boolean),t=[];for(const i of e){if(i.startsWith("-"))break;t.push(i)}return t.length>=2?t.join(" "):""}class N{db;constructor(e){this.db=e}static signature(e,t,i){return`${e}:${U(t)}:${U(i).slice(0,80)}`}record(e){const t=N.signature(e.scope,e.target,e.description),i=e.project_id??null,r=e.content&&w(e.content)||null,n=e.scope==="command"&&I(e.target)||null,o=e.scope==="command"&&k(e.target)||null,s=R(),p=this.db.transaction(()=>{const u=this.db.prepare(`
16
16
  SELECT * FROM failure_patterns
17
17
  WHERE signature = ? AND (project_id IS ? OR project_id = ?)
18
- `).get(t,o,o);if(d)return this.db.prepare(`
18
+ `).get(t,i,i);if(u)return this.db.prepare(`
19
19
  UPDATE failure_patterns
20
20
  SET occurrences = occurrences + 1,
21
21
  last_seen = datetime('now'),
@@ -24,29 +24,32 @@ ${o}
24
24
  content_sig = COALESCE(?, content_sig),
25
25
  resolved = 0
26
26
  WHERE id = ?
27
- `).run(e.why_failed??null,e.severity??d.severity,n,d.id),this.noteConfirmation(d.id,a),this.get(d.id);const s=e.quarantine_scope??(a.run_id!==null?"run":o===null?"global":"project"),l=this.db.prepare(`
27
+ `).run(e.why_failed??null,e.severity??u.severity,r,u.id),u.downgraded_at&&(this.db.prepare("UPDATE failure_patterns SET downgraded_at = NULL WHERE id = ?").run(u.id),this.db.prepare(`
28
+ INSERT INTO failure_pattern_history (failure_id, event, detail) VALUES (?, 'rearmed', ?)
29
+ `).run(u.id,`recorded again after a quiet-period downgrade (downgraded ${u.downgraded_at}): warn -> block`)),this.noteConfirmation(u.id,s),this.get(u.id);const _=e.quarantine_scope??(s.run_id!==null?"run":i===null?"global":"project"),a=this.db.prepare(`
28
30
  INSERT INTO failure_patterns
29
31
  (project_id, signature, scope, target, description, why_failed, severity, agent_id, run_id, quarantine_scope, content_sig, canonical_target, command_head)
30
32
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
31
- `).run(o,t,e.scope,e.target,e.description,e.why_failed??null,e.severity??"medium",a.agent_id,a.run_id,s,n,r,i),p=this.get(l.lastInsertRowid);return this.noteConfirmation(p.id,a),p}).immediate();return m(this.db,{projectId:_.project_id??0,kind:"failure",refTable:"failure_patterns",refId:_.id}),_}contentMatch(e,t,o){const n=this.db.prepare(`
33
+ `).run(i,t,e.scope,e.target,e.description,e.why_failed??null,e.severity??"medium",s.agent_id,s.run_id,_,r,n,o),l=this.get(a.lastInsertRowid);return this.noteConfirmation(l.id,s),l}).immediate();return O(this.db,{projectId:p.project_id??0,kind:"failure",refTable:"failure_patterns",refId:p.id}),p}contentMatch(e,t,i){const r=this.db.prepare(`
32
34
  SELECT * FROM failure_patterns
33
35
  WHERE content_sig IS NOT NULL AND scope = ? AND resolved = 0
34
36
  AND (project_id IS ? OR project_id = ?)
35
37
  ORDER BY occurrences DESC, last_seen DESC
36
- `).all(t,o,o);for(const r of n)if(r.content_sig&&v(e,r.content_sig))return r;return null}get(e){return this.db.prepare("SELECT * FROM failure_patterns WHERE id = ?").get(e)??null}static anchorSha(e){return F.hash(y(e))}recordAnchors(e,t,o,n=!0){try{const r=g(t),i=new Set(this.db.prepare("SELECT DISTINCT rel_path FROM failure_anchors WHERE failure_id = ?").all(e).map(l=>l.rel_path));let a=Y-i.size;const u=this.db.prepare("DELETE FROM failure_anchors WHERE failure_id = ? AND rel_path = ?"),_=this.db.prepare(`
38
+ `).all(t,i,i);for(const n of r)if(n.content_sig&&x(e,n.content_sig))return n;return null}get(e){return this.db.prepare("SELECT * FROM failure_patterns WHERE id = ?").get(e)??null}static anchorSha(e){return q.hash(w(e))}recordAnchors(e,t,i,r=!0){try{const n=g(t),o=new Set(this.db.prepare("SELECT DISTINCT rel_path FROM failure_anchors WHERE failure_id = ?").all(e).map(a=>a.rel_path));let s=z-o.size;const c=this.db.prepare("DELETE FROM failure_anchors WHERE failure_id = ? AND rel_path = ?"),p=this.db.prepare(`
37
39
  INSERT INTO failure_anchors (failure_id, rel_path, sha, size, mtime_ns)
38
40
  VALUES (?, ?, ?, ?, ?)
39
- `),d=new Set;let s=0;for(const l of o){if(typeof l!="string"||l.length===0)continue;const p=k(l)?g(l):g(r,l);if(!p.startsWith(r+j)||d.has(p))continue;d.add(p);const R=W(r,p),S=i.has(R);if(!(!S&&(!n||a<=0)))try{const f=D(p,{bigint:!0});if(!f.isFile())continue;const E=O.anchorSha(C(p,"utf-8"));u.run(e,R),_.run(e,R,E,f.size,f.mtimeNs),s++,S||a--}catch{}}return s}catch{return 0}}getAnchors(e){try{const t=this.db.prepare("SELECT rel_path, sha, size, mtime_ns, recorded_at FROM failure_anchors WHERE failure_id = ?");return t.safeIntegers(!0),t.all(e)}catch{return[]}}anchorFreshness(e,t){try{if(!t)return"unverifiable";const o=this.getAnchors(e);if(o.length===0)return"unanchored";const n=g(t);let r=0;for(const i of o)try{const a=g(n,i.rel_path);if(!a.startsWith(n+j))continue;const u=D(a,{bigint:!0});if(!u.isFile())continue;if(u.size===i.size&&u.mtimeNs===i.mtime_ns){r++;continue}if(O.anchorSha(C(a,"utf-8"))===i.sha){r++;continue}return"stale"}catch{}return r>0?"fresh":"unverifiable"}catch{return"unverifiable"}}staleAnchorIds(e,t){const o=new Set,n=new Map;for(const r of e)try{if(r.match==="exact"||r.scope!=="file"&&r.scope!=="symbol"&&r.scope!=="edit")continue;let i=n.get(r.id);i===void 0&&(i=t?this.anchorFreshness(r.id,t):this.getAnchors(r.id).length===0?"unanchored":"unverifiable",n.set(r.id,i)),(i==="unanchored"||i==="stale")&&o.add(r.id)}catch{}return o}noteConfirmation(e,t){if(t.agent_id===null)return;this.db.prepare(`
41
+ `),u=new Set;let _=0;for(const a of i){if(typeof a!="string"||a.length===0)continue;const l=W(a)?g(a):g(n,a);if(!l.startsWith(n+y)||u.has(l))continue;u.add(l);const h=H(n,l),S=o.has(h);if(!(!S&&(!r||s<=0)))try{const f=D(l,{bigint:!0});if(!f.isFile())continue;const E=N.anchorSha(j(l,"utf-8"));c.run(e,h),p.run(e,h,E,f.size,f.mtimeNs),_++,S||s--}catch{}}return _}catch{return 0}}getAnchors(e){try{const t=this.db.prepare("SELECT rel_path, sha, size, mtime_ns, recorded_at FROM failure_anchors WHERE failure_id = ?");return t.safeIntegers(!0),t.all(e)}catch{return[]}}anchorFreshness(e,t){try{if(!t)return"unverifiable";const i=this.getAnchors(e);if(i.length===0)return"unanchored";const r=g(t);let n=0;for(const o of i)try{const s=g(r,o.rel_path);if(!s.startsWith(r+y))continue;const c=D(s,{bigint:!0});if(!c.isFile())continue;if(c.size===o.size&&c.mtimeNs===o.mtime_ns){n++;continue}if(N.anchorSha(j(s,"utf-8"))===o.sha){n++;continue}return"stale"}catch{}return n>0?"fresh":"unverifiable"}catch{return"unverifiable"}}staleAnchorIds(e,t){const i=new Set,r=new Map;for(const n of e)try{if(n.match==="exact"||n.scope!=="file"&&n.scope!=="symbol"&&n.scope!=="edit")continue;let o=r.get(n.id);o===void 0&&(o=t?this.anchorFreshness(n.id,t):this.getAnchors(n.id).length===0?"unanchored":"unverifiable",r.set(n.id,o)),(o==="unanchored"||o==="stale")&&i.add(n.id)}catch{}return i}noteConfirmation(e,t){if(t.agent_id===null)return;this.db.prepare(`
40
42
  INSERT OR IGNORE INTO failure_confirmations (failure_id, agent_id, run_id)
41
43
  VALUES (?, ?, ?)
42
- `).run(e,t.agent_id,t.run_id);const o=this.get(e);if(!o||o.quarantine_scope!=="run")return;const{n}=this.db.prepare("SELECT COUNT(*) AS n FROM failure_confirmations WHERE failure_id = ?").get(e);n>=2&&this.applyPromotion(e,"project")}applyPromotion(e,t){const o=this.get(e);if(!o)return!1;const n=t==="project"&&o.project_id===null?"global":t;return M[n]<=M[o.quarantine_scope]?!1:(this.db.prepare("UPDATE failure_patterns SET quarantine_scope = ? WHERE id = ?").run(n,e),m(this.db,{projectId:o.project_id??0,kind:"failure",refTable:"failure_patterns",refId:e}),!0)}promote(e,t){if(!N(h()))return{row:this.get(e),authorized:!1,changed:!1};const o=this.applyPromotion(e,t);return{row:this.get(e),authorized:!0,changed:o}}sweepRunQuarantine(e){const t=Math.max(1,Math.floor(e?.runInactiveHours??24)),o=`-${t} hours`,n=Date.now()-t*36e5,r=$(),i=this.db.transaction(()=>{const a=this.db.prepare(`
44
+ `).run(e,t.agent_id,t.run_id);const i=this.get(e);if(!i||i.quarantine_scope!=="run")return;const{n:r}=this.db.prepare("SELECT COUNT(*) AS n FROM failure_confirmations WHERE failure_id = ?").get(e);r>=2&&this.applyPromotion(e,"project")}applyPromotion(e,t){const i=this.get(e);if(!i)return!1;const r=t==="project"&&i.project_id===null?"global":t;return M[r]<=M[i.quarantine_scope]?!1:(this.db.prepare("UPDATE failure_patterns SET quarantine_scope = ? WHERE id = ?").run(r,e),O(this.db,{projectId:i.project_id??0,kind:"failure",refTable:"failure_patterns",refId:e}),!0)}promote(e,t){if(!m(R()))return{row:this.get(e),authorized:!1,changed:!1};const i=this.applyPromotion(e,t);return{row:this.get(e),authorized:!0,changed:i}}sweepRunQuarantine(e){const t=Math.max(1,Math.floor(e?.runInactiveHours??24)),i=`-${t} hours`,r=Date.now()-t*36e5,n=$(),o=this.db.transaction(()=>{const s=this.db.prepare(`
43
45
  UPDATE failure_patterns
44
46
  SET resolved = 1,
45
47
  resolution_note = COALESCE(resolution_note || ' \xB7 ', '')
46
48
  || 'auto-expired by run-quarantine sweep: run ' || run_id || ' was abandoned'
47
49
  WHERE resolved = 0 AND quarantine_scope = 'run' AND run_id IS NOT NULL
48
50
  AND run_id IN (SELECT run_id FROM runs WHERE status = 'abandoned')
49
- `).run().changes,u=this.db.prepare(`
51
+ RETURNING id
52
+ `).all().map(a=>a.id),c=s.length;c>0&&L(this.db,"failure.resolve",{bulk:!0,reason:"run_abandoned",count:c,failure_ids:s},{system:"run-quarantine-sweep"});const p=this.db.prepare(`
50
53
  SELECT fp.id FROM failure_patterns fp
51
54
  WHERE fp.resolved = 0 AND fp.quarantine_scope = 'run' AND fp.run_id IS NOT NULL
52
55
  AND (
@@ -72,18 +75,38 @@ ${o}
72
75
  )
73
76
  )
74
77
  )
75
- `).all({cut:o,cutMs:n,ttl:r?1:0}).map(s=>s.id),_=this.db.prepare(`
78
+ `).all({cut:i,cutMs:r,ttl:n?1:0}).map(a=>a.id),u=this.db.prepare(`
76
79
  UPDATE failure_patterns
77
80
  SET quarantine_scope = CASE WHEN project_id IS NULL THEN 'global' ELSE 'project' END
78
81
  WHERE id = ?
79
- `);for(const s of u)_.run(s);const d=r?this.db.prepare(`
82
+ `);for(const a of p)u.run(a);const _=n?this.db.prepare(`
80
83
  UPDATE runs SET status = 'abandoned', updated_at = datetime('now')
81
84
  WHERE status = 'running' AND updated_at < datetime('now', @cut)
82
85
  AND NOT EXISTS (
83
86
  SELECT 1 FROM events e
84
87
  WHERE e.run_id = runs.run_id AND e.created_at >= @cutMs
85
88
  )
86
- `).run({cut:o,cutMs:n}).changes:0;return{promotedIds:u,expired:a,abandoned:d}})();for(const a of i.promotedIds){const u=this.get(a);m(this.db,{projectId:u?.project_id??0,kind:"failure",refTable:"failure_patterns",refId:a,agentId:null,runId:null})}return{promoted:i.promotedIds.length,expired:i.expired,runs_abandoned:i.abandoned,...r?{}:{degraded:"live memory off (WYRM_LIVE_MEMORY) \u2014 run-tagged events do not exist, so the run-inactivity TTL paths were skipped; only explicit run verdicts acted (completed/failed promote, abandoned expires)"}}}check(e,t,o,n,r){return this.query(e,t,o,n??null,r).map(({_match:i,_rank:a,...u})=>u)}checkVerdict(e,t,o,n,r){const i=new Set,a=[],u=(s,l,p=null)=>{i.has(s.id)||(i.add(s.id),a.push(this.toVerdictMatch(s,l,p)))};for(const s of this.query(e,t,o,n??null,r))u(s,s._match,s._rank);const _=r?.runScope??"any",d=s=>_==="any"||s.quarantine_scope===_;for(const s of this.targetIdentityMatches(e,t,n??null,r))d(s)&&u(s,"target");for(const s of this.canonicalMatches(e,t,n??null,r))d(s)&&u(s,"canonical");return{blocked:a.length>0,matches:a,recorded_by_agent:a[0]?.recorded_by_agent??null,run_id:a[0]?.run_id??null,confidence:a.reduce((s,l)=>Math.max(s,l.confidence),0)}}toVerdictMatch(e,t,o=null){const n=q(e);return{id:e.id,pattern:e.signature,scope:e.scope,quarantine_scope:e.quarantine_scope,target:e.target,description:e.description,why_failed:e.why_failed,severity:e.severity,occurrences:e.occurrences,last_seen:e.last_seen,recorded_by_agent:n.actor,run_id:n.run_id,match:t,confidence:z(t,o)}}targetIdentityMatches(e,t,o,n){const r=b(t),i=r.slice(0,200);if(i.length===0)return[];const a=n?.callerRunId!==void 0?n.callerRunId:h().run_id,u=`${e}:${i}:`,_=`${e}:${i};`;return this.db.prepare(`
89
+ `).run({cut:i,cutMs:r}).changes:0;return{promotedIds:p,expired:c,abandoned:_}})();for(const s of o.promotedIds){const c=this.get(s);O(this.db,{projectId:c?.project_id??0,kind:"failure",refTable:"failure_patterns",refId:s,agentId:null,runId:null})}return{promoted:o.promotedIds.length,expired:o.expired,runs_abandoned:o.abandoned,...n?{}:{degraded:"live memory off (WYRM_LIVE_MEMORY): run-tagged events do not exist, so the run-inactivity TTL paths were skipped; only explicit run verdicts acted (completed/failed promote, abandoned expires)"}}}check(e,t,i,r,n){return this.query(e,t,i,r??null,n).map(({_match:o,_rank:s,...c})=>c)}checkVerdict(e,t,i,r,n){const o=new Set,s=[],c=(a,l,h=null)=>{o.has(a.id)||(o.add(a.id),s.push(this.toVerdictMatch(a,l,h)))};for(const a of this.query(e,t,i,r??null,n))c(a,a._match,a._rank);const p=n?.runScope??"any",u=a=>p==="any"||a.quarantine_scope===p;for(const a of this.targetIdentityMatches(e,t,r??null,n))u(a)&&c(a,"target");for(const a of this.canonicalMatches(e,t,r??null,n))u(a)&&c(a,"canonical");const _=s.find(a=>!a.quiet_downgrade);return{blocked:_!==void 0,matches:s,recorded_by_agent:(_??s[0])?.recorded_by_agent??null,run_id:(_??s[0])?.run_id??null,confidence:s.reduce((a,l)=>Math.max(a,l.confidence),0)}}toVerdictMatch(e,t,i=null){const r=B(e);return{id:e.id,pattern:e.signature,scope:e.scope,quarantine_scope:e.quarantine_scope,target:e.target,description:e.description,why_failed:e.why_failed,severity:e.severity,occurrences:e.occurrences,last_seen:e.last_seen,recorded_by_agent:r.actor,run_id:r.run_id,match:t,confidence:Q(t,i),...e.downgraded_at?{quiet_downgrade:P}:{}}}sweepQuiet(e,t={}){if(!(e>0))return{downgraded:0,ids:[]};const i=this.db.transaction(()=>{const r=this.quietCandidates(e,t),n=this.db.prepare(`
90
+ UPDATE failure_patterns SET downgraded_at = datetime('now')
91
+ WHERE id = ? AND resolved = 0 AND downgraded_at IS NULL
92
+ `),o=this.db.prepare(`
93
+ INSERT INTO failure_pattern_history (failure_id, event, detail) VALUES (?, 'downgraded', ?)
94
+ `),s=[];for(const c of r)n.run(c.id).changes!==0&&(o.run(c.id,c.quiet_since===c.last_hit?`no hits for ${e}d (last hit ${c.last_hit}): block -> warn`:`no hits for ${e}d since the quiet clock started ${c.quiet_since} (last hit ${c.last_hit}): block -> warn`),s.push(c.id));return s})();for(const r of i){const n=this.get(r);O(this.db,{projectId:n?.project_id??0,kind:"failure",refTable:"failure_patterns",refId:r,agentId:null,runId:null})}return{downgraded:i.length,ids:i}}quietClockStart(){try{return this.db.prepare("SELECT applied_at FROM schema_versions WHERE version = ?").get(V)?.applied_at??null}catch{return null}}quietCandidates(e,t={}){if(!(e>0))return[];const i=t.ignoreUpgradeGrace?null:this.quietClockStart();return!t.ignoreUpgradeGrace&&i===null?[]:this.db.prepare(`
95
+ SELECT id, scope, target, datetime(last_hit_jd) AS last_hit, datetime(quiet_from_jd) AS quiet_since FROM (
96
+ SELECT id, scope, target, last_hit_jd,
97
+ CASE WHEN @since IS NULL THEN last_hit_jd ELSE MAX(last_hit_jd, julianday(@since)) END AS quiet_from_jd
98
+ FROM (
99
+ SELECT fp.id AS id, fp.scope AS scope, fp.target AS target,
100
+ MAX(julianday(COALESCE(fp.last_seen, fp.first_seen)),
101
+ COALESCE((SELECT MAX(julianday(b.blocked_at)) FROM failure_blocks b WHERE b.failure_id = fp.id),
102
+ julianday(COALESCE(fp.last_seen, fp.first_seen)))) AS last_hit_jd
103
+ FROM failure_patterns fp
104
+ WHERE fp.resolved = 0 AND fp.downgraded_at IS NULL
105
+ )
106
+ )
107
+ WHERE last_hit_jd IS NOT NULL AND quiet_from_jd IS NOT NULL AND julianday('now') - quiet_from_jd > @days
108
+ ORDER BY id
109
+ `).all({days:e,since:i})}history(e){try{return this.db.prepare("SELECT * FROM failure_pattern_history WHERE failure_id = ? ORDER BY id").all(e)}catch{return[]}}targetIdentityMatches(e,t,i,r){const n=b(t),o=n.slice(0,200);if(o.length===0)return[];const s=r?.callerRunId!==void 0?r.callerRunId:R().run_id,c=`${e}:${o}:`,p=`${e}:${o};`;return this.db.prepare(`
87
110
  SELECT * FROM failure_patterns
88
111
  WHERE resolved = 0
89
112
  AND signature >= @lo AND signature < @hi
@@ -91,65 +114,65 @@ ${o}
91
114
  AND (quarantine_scope <> 'run' OR run_id = @run)
92
115
  ORDER BY occurrences DESC, last_seen DESC
93
116
  LIMIT @cap
94
- `).all({lo:u,hi:_,project:o??null,run:a,cap:T}).filter(s=>b(s.target)===r).slice(0,L)}canonicalMatches(e,t,o,n){if(e!=="command")return[];const r=I(t);if(r.length===0)return[];const i=n?.callerRunId!==void 0?n.callerRunId:h().run_id;return this.db.prepare(`
117
+ `).all({lo:c,hi:p,project:i??null,run:s,cap:T}).filter(_=>b(_.target)===n).slice(0,A)}canonicalMatches(e,t,i,r){if(e!=="command")return[];const n=I(t);if(n.length===0)return[];const o=r?.callerRunId!==void 0?r.callerRunId:R().run_id;return this.db.prepare(`
95
118
  SELECT * FROM failure_patterns
96
119
  WHERE resolved = 0 AND scope = 'command' AND canonical_target = @canon
97
120
  AND (project_id IS @project OR project_id = @project OR project_id IS NULL)
98
121
  AND (quarantine_scope <> 'run' OR run_id = @run)
99
122
  ORDER BY occurrences DESC, last_seen DESC
100
123
  LIMIT @cap
101
- `).all({canon:r,project:o??null,run:i,cap:T}).filter(u=>I(u.target)===r).slice(0,L)}commandHeadFamily(e,t,o,n){if(e!=="command")return[];const r=U(t);if(r.length===0)return[];const i=n?.callerRunId!==void 0?n.callerRunId:h().run_id;return this.db.prepare(`
124
+ `).all({canon:n,project:i??null,run:o,cap:T}).filter(c=>I(c.target)===n).slice(0,A)}commandHeadFamily(e,t,i,r){if(e!=="command")return[];const n=k(t);if(n.length===0)return[];const o=r?.callerRunId!==void 0?r.callerRunId:R().run_id;return this.db.prepare(`
102
125
  SELECT * FROM failure_patterns
103
126
  WHERE resolved = 0 AND scope = 'command' AND command_head = @head
104
127
  AND (project_id IS @project OR project_id = @project OR project_id IS NULL)
105
128
  AND (quarantine_scope <> 'run' OR run_id = @run)
106
129
  ORDER BY occurrences DESC, last_seen DESC
107
130
  LIMIT @cap
108
- `).all({head:r,project:o??null,run:i,cap:T}).slice(0,L)}recordBlock(e,t){if(!(!e.blocked||e.matches.length===0))try{const o=h(),n=e.matches.find(r=>!r.stale_anchor)??e.matches[0];this.db.prepare(`
131
+ `).all({head:n,project:i??null,run:o,cap:T}).slice(0,A)}recordBlock(e,t){if(!(!e.blocked||e.matches.length===0))try{const i=R(),r=e.matches.find(n=>!n.stale_anchor)??e.matches[0];this.db.prepare(`
109
132
  INSERT INTO failure_blocks (failure_id, project_id, agent_id, run_id)
110
133
  VALUES (?, ?, ?, ?)
111
- `).run(n.id,t??null,o.agent_id,o.run_id)}catch{}}pruneBlocks(e){const t=Math.max(1,Math.floor(e));try{return this.db.prepare("DELETE FROM failure_blocks WHERE blocked_at < datetime('now', @cut)").run({cut:`-${t} days`}).changes}catch{return 0}}failureStats(e){const t=e?.projectId??null,o=e?.callerRunId!==void 0?e.callerRunId:h().run_id,n={project:t},r=this.db.prepare(`
134
+ `).run(r.id,t??null,i.agent_id,i.run_id)}catch{}}pruneBlocks(e){const t=Math.max(1,Math.floor(e));try{return this.db.prepare("DELETE FROM failure_blocks WHERE blocked_at < datetime('now', @cut)").run({cut:`-${t} days`}).changes}catch{return 0}}failureStats(e){const t=e?.projectId??null,i=e?.callerRunId!==void 0?e.callerRunId:R().run_id,r={project:t},n=this.db.prepare(`
112
135
  SELECT COUNT(*) AS total, COALESCE(SUM(resolved = 0), 0) AS unresolved
113
136
  FROM failure_patterns WHERE (@project IS NULL OR project_id = @project)
114
- `).get(n),i={run:0,project:0,global:0};for(const E of this.db.prepare(`
137
+ `).get(r),o={run:0,project:0,global:0};for(const E of this.db.prepare(`
115
138
  SELECT quarantine_scope AS k, COUNT(*) AS n FROM failure_patterns
116
139
  WHERE (@project IS NULL OR project_id = @project) GROUP BY quarantine_scope
117
- `).all(n))i[E.k]=E.n;const a={file:0,symbol:0,command:0,prompt:0,edit:0};for(const E of this.db.prepare(`
140
+ `).all(r))o[E.k]=E.n;const s={file:0,symbol:0,command:0,prompt:0,edit:0};for(const E of this.db.prepare(`
118
141
  SELECT scope AS k, COUNT(*) AS n FROM failure_patterns
119
142
  WHERE (@project IS NULL OR project_id = @project) GROUP BY scope
120
- `).all(n))a[E.k]=E.n;const u=E=>E.map(A=>({who:B(A.agent_id),count:A.count})),_=u(this.db.prepare(`
143
+ `).all(r))s[E.k]=E.n;const c=E=>E.map(C=>({who:v(C.agent_id),count:C.count})),p=c(this.db.prepare(`
121
144
  SELECT agent_id, COUNT(*) AS count FROM failure_patterns
122
145
  WHERE (@project IS NULL OR project_id = @project)
123
146
  GROUP BY agent_id ORDER BY count DESC, agent_id ASC LIMIT 10
124
- `).all(n)),d=this.db.prepare(`
147
+ `).all(r)),u=this.db.prepare(`
125
148
  SELECT run_id, COUNT(*) AS count FROM failure_patterns
126
149
  WHERE run_id IS NOT NULL AND (@project IS NULL OR project_id = @project)
127
150
  GROUP BY run_id ORDER BY count DESC, run_id ASC LIMIT 10
128
- `).all(n),s=u(this.db.prepare(`
151
+ `).all(r),_=c(this.db.prepare(`
129
152
  SELECT agent_id, COUNT(*) AS count FROM failure_blocks
130
153
  WHERE (@project IS NULL OR project_id = @project)
131
154
  GROUP BY agent_id ORDER BY count DESC, agent_id ASC LIMIT 10
132
- `).all(n)),l=this.db.prepare(`
155
+ `).all(r)),a=this.db.prepare(`
133
156
  SELECT run_id, COUNT(*) AS count FROM failure_blocks
134
157
  WHERE run_id IS NOT NULL AND (@project IS NULL OR project_id = @project)
135
158
  GROUP BY run_id ORDER BY count DESC, run_id ASC LIMIT 10
136
- `).all(n),p=this.db.prepare(`
159
+ `).all(r),l=this.db.prepare(`
137
160
  SELECT COUNT(*) AS total, COUNT(DISTINCT fc.agent_id) AS distinct_agents
138
161
  FROM failure_confirmations fc
139
162
  JOIN failure_patterns fp ON fp.id = fc.failure_id
140
163
  WHERE (@project IS NULL OR fp.project_id = @project)
141
- `).get(n),R=this.db.prepare(`
164
+ `).get(r),h=this.db.prepare(`
142
165
  SELECT COUNT(*) AS n FROM failure_blocks WHERE (@project IS NULL OR project_id = @project)
143
- `).get(n).n,S=this.db.prepare(`
166
+ `).get(r).n,S=this.db.prepare(`
144
167
  SELECT COUNT(*) AS n FROM failure_blocks
145
168
  WHERE run_id = @run AND (@project IS NULL OR project_id = @project)
146
- `).get({...n,run:o}).n,f=this.db.prepare(`
169
+ `).get({...r,run:i}).n,f=this.db.prepare(`
147
170
  SELECT fb.failure_id, fp.scope, fp.target, COUNT(*) AS blocks
148
171
  FROM failure_blocks fb
149
172
  JOIN failure_patterns fp ON fp.id = fb.failure_id
150
173
  WHERE (@project IS NULL OR fb.project_id = @project)
151
174
  GROUP BY fb.failure_id ORDER BY blocks DESC, fb.failure_id ASC LIMIT 5
152
- `).all(n);return{view:"failures",project_id:t,recorded:{total:r.total,unresolved:r.unresolved,by_quarantine_scope:i,by_scope:a,by_agent:_,by_run:d},confirmations:{total:p.total,distinct_agents:p.distinct_agents},blocked:{total:R,by_agent:s,by_run:l,this_run:{run_id:o??null,blocked:S},top_blocked:f}}}query(e,t,o,n,r){const i=r?.callerRunId!==void 0?r.callerRunId:h().run_id,a=r?.runScope??"any",u={project:n,run:i,tier:a==="any"?null:a},_=O.signature(e,t,o),d=this.db.prepare(`
175
+ `).all(r);return{view:"failures",project_id:t,recorded:{total:n.total,unresolved:n.unresolved,by_quarantine_scope:o,by_scope:s,by_agent:p,by_run:u},confirmations:{total:l.total,distinct_agents:l.distinct_agents},blocked:{total:h,by_agent:_,by_run:a,this_run:{run_id:i??null,blocked:S},top_blocked:f}}}query(e,t,i,r,n){const o=n?.callerRunId!==void 0?n.callerRunId:R().run_id,s=n?.runScope??"any",c={project:r,run:o,tier:s==="any"?null:s},p=N.signature(e,t,i),u=this.db.prepare(`
153
176
  SELECT *, NULL AS _rank FROM failure_patterns
154
177
  WHERE resolved = 0 AND signature = @sig
155
178
  AND (project_id IS @project OR project_id = @project OR project_id IS NULL)
@@ -157,7 +180,7 @@ ${o}
157
180
  AND (@tier IS NULL OR quarantine_scope = @tier)
158
181
  ORDER BY occurrences DESC, last_seen DESC
159
182
  LIMIT 5
160
- `).all({...u,sig:_});if(d.length>0)return d.map(f=>({...f,_match:"exact",_rank:null}));const s=H(`${t} ${o}`.slice(0,100));if(!s)return[];const l=s.split(" ").filter(f=>/[\p{L}\p{N}]/u.test(f)),p=l.filter(f=>!x.has(f.toLowerCase())),R=p.length?p:l;if(R.length===0)return[];const S=R.map(f=>`"${f.replace(/"/g,"")}"`).join(" ");try{return this.db.prepare(`
183
+ `).all({...c,sig:p});if(u.length>0)return u.map(f=>({...f,_match:"exact",_rank:null}));const _=F(`${t} ${i}`.slice(0,100));if(!_)return[];const a=_.split(" ").filter(f=>/[\p{L}\p{N}]/u.test(f)),l=a.filter(f=>!Y.has(f.toLowerCase())),h=l.length?l:a;if(h.length===0)return[];const S=h.map(f=>`"${f.replace(/"/g,"")}"`).join(" ");try{return this.db.prepare(`
161
184
  SELECT fp.*, bm25(failure_patterns_fts) AS _rank FROM failure_patterns fp
162
185
  JOIN failure_patterns_fts fts ON fts.rowid = fp.id
163
186
  WHERE failure_patterns_fts MATCH @q
@@ -168,11 +191,11 @@ ${o}
168
191
  AND (@tier IS NULL OR fp.quarantine_scope = @tier)
169
192
  ORDER BY fp.occurrences DESC, fp.last_seen DESC
170
193
  LIMIT 5
171
- `).all({...u,q:S,scope:e}).map(E=>({...E,_match:"fuzzy"}))}catch{return[]}}list(e,t=20){return e==null?this.db.prepare(`SELECT * FROM failure_patterns WHERE resolved = 0
194
+ `).all({...c,q:S,scope:e}).map(E=>({...E,_match:"fuzzy"}))}catch{return[]}}list(e,t=20){return e==null?this.db.prepare(`SELECT * FROM failure_patterns WHERE resolved = 0
172
195
  ORDER BY last_seen DESC LIMIT ?`).all(t):this.db.prepare(`SELECT * FROM failure_patterns
173
196
  WHERE resolved = 0 AND project_id = ?
174
- ORDER BY last_seen DESC LIMIT ?`).all(e,t)}resolve(e,t){if(!N(h()))return{row:this.get(e),authorized:!1,changed:!1};const o=this.db.prepare(`
175
- UPDATE failure_patterns
176
- SET resolved = 1, resolution_note = ?
177
- WHERE id = ?
178
- `).run(t??null,e);return{row:this.get(e),authorized:!0,changed:o.changes>0}}delete(e){if(!N(h()))return!1;try{this.db.prepare("DELETE FROM failure_anchors WHERE failure_id = ?").run(e)}catch{}return this.db.prepare("DELETE FROM failure_patterns WHERE id = ?").run(e).changes>0}}export{Y as ANCHOR_CAP,O as FailurePatterns,ee as STALE_ANCHOR_FLAG,I as canonicalizeCommand,U as commandHead,z as matchConfidence,b as normalizeIdentity,w as normalizeSignal,te as renderFailureStats};
197
+ ORDER BY last_seen DESC LIMIT ?`).all(e,t)}resolve(e,t){if(!m(R()))return{row:this.get(e),authorized:!1,changed:!1};const i=this.db.transaction(()=>{const r=this.db.prepare(`
198
+ UPDATE failure_patterns
199
+ SET resolved = 1, resolution_note = ?
200
+ WHERE id = ?
201
+ `).run(t??null,e);return r.changes>0&&L(this.db,"failure.resolve",{failure_id:e}),r})();return{row:this.get(e),authorized:!0,changed:i.changes>0}}delete(e){return m(R())?this.db.transaction(()=>{try{this.db.prepare("DELETE FROM failure_anchors WHERE failure_id = ?").run(e)}catch{}const t=this.db.prepare("DELETE FROM failure_patterns WHERE id = ?").run(e);return t.changes>0&&L(this.db,"failure.delete",{failure_id:e}),t.changes>0})():!1}}export{z as ANCHOR_CAP,G as DEFAULT_FAILURE_QUIET_DAYS,N as FailurePatterns,V as QUIET_CLOCK_MIGRATION,P as QUIET_DOWNGRADE_FLAG,oe as STALE_ANCHOR_FLAG,I as canonicalizeCommand,k as commandHead,ae as failureQuietDays,Q as matchConfidence,b as normalizeIdentity,U as normalizeSignal,se as renderFailureStats};
@@ -1,33 +1,34 @@
1
- const f={sessions:["date","objectives","completed","issues","commits","files_changed","notes","summary","tokens_estimate","is_archived","created_at"],quests:["title","description","priority","status","tags","created_at","completed_at"],ground_truths:["category","key","value","rationale","source","is_current","confidence","last_verified_at","created_at","ttl_days"],memory_artifacts:["kind","problem","constraints","validated_fix","why_it_worked","outcome","tags","confidence","last_validated_at","created_at"],decision_edges:["from_kind","from_id","to_kind","to_id","relation","rationale","confidence","created_at","invalidated_at"]},l={session:{table:"sessions",ts:"created_at"},quest:{table:"quests",ts:"created_at"},truth:{table:"ground_truths",ts:"updated_at"},artifact:{table:"memory_artifacts",ts:"last_accessed_at"},edge:{table:"decision_edges",ts:"created_at"}};class h{db;constructor(t){this.db=t}share(t,r){const e=l[t];return!e||this.isPrivateGroveRow(e.table,r)?!1:this.db.prepare(`UPDATE ${e.table} SET is_shared = 1 WHERE id = ?`).run(r).changes>0}safeRemoteColumns(t,r){if(!new Set(Object.values(l).map(n=>n.table)).has(t))return[];let s;try{const n=this.db.prepare(`PRAGMA table_info(${t})`).all();s=new Set(n.map(a=>a.name))}catch{return[]}return(f[t]??[]).filter(n=>/^[A-Za-z_][A-Za-z0-9_]*$/.test(n)&&s.has(n)&&n in r)}isPrivateGroveRow(t,r){const e=this.groveOf(t,r);return!e||e.policy==null?!1:e.policy==="private"}groveOf(t,r){try{return this.db.prepare(`SELECT t.project_id AS project_id, (SELECT sync_policy FROM projects WHERE id = t.project_id) AS policy
2
- FROM ${t} t WHERE t.id = ?`).get(r)}catch{return}}shareOutcome(t,r){const e=l[t];if(!e)return{ok:!1,reason:"not_found"};const s=this.groveOf(e.table,r);return s&&s.policy==="private"?{ok:!1,reason:"private_grove",projectId:s.project_id}:this.share(t,r)?{ok:!0}:{ok:!1,reason:"not_found"}}projectsHasSyncPolicy(){try{return this.db.prepare("PRAGMA table_info(projects)").all().some(t=>t.name==="sync_policy")}catch{return!1}}unshare(t,r){const e=l[t];return e?this.db.prepare(`UPDATE ${e.table} SET is_shared = 0 WHERE id = ?`).run(r).changes>0:!1}collectForPush(t,r=500){const e=t??"1970-01-01",s=[],c=this.projectsHasSyncPolicy()?"AND (project_id IS NULL OR project_id IN (SELECT id FROM projects WHERE sync_policy = 'team'))":"";for(const[n,a]of Object.entries(l)){if(s.length>=r)break;const d=n,o=this.db.prepare(`SELECT * FROM ${a.table}
1
+ import{recordGovernanceEvent as _}from"./governance.js";const h={sessions:["date","objectives","completed","issues","commits","files_changed","notes","summary","tokens_estimate","is_archived","created_at"],quests:["title","description","priority","status","tags","created_at","completed_at"],ground_truths:["category","key","value","rationale","source","is_current","confidence","last_verified_at","created_at","ttl_days"],memory_artifacts:["kind","problem","constraints","validated_fix","why_it_worked","outcome","tags","confidence","last_validated_at","created_at"],decision_edges:["from_kind","from_id","to_kind","to_id","relation","rationale","confidence","created_at","invalidated_at"]},d={session:{table:"sessions",ts:"created_at"},quest:{table:"quests",ts:"created_at"},truth:{table:"ground_truths",ts:"updated_at"},artifact:{table:"memory_artifacts",ts:"last_accessed_at"},edge:{table:"decision_edges",ts:"created_at"}},E={truth:"ground_truths",memory_artifact:"memory_artifacts",quest:"quests",design_token:"design_tokens",design_reference:"design_references",skill:"skills"};class b{db;constructor(e){this.db=e}share(e,s){const r=d[e];return!r||this.isPrivateGroveRow(r.table,s)?!1:this.db.transaction(()=>{const t=this.db.prepare(`UPDATE ${r.table} SET is_shared = 1 WHERE id = ?`).run(s);return t.changes>0&&_(this.db,"visibility.share",{kind:e,row_id:s,shared:!0}),t.changes>0})()}safeRemoteColumns(e,s){if(!new Set(Object.values(d).map(n=>n.table)).has(e))return[];let t;try{const n=this.db.prepare(`PRAGMA table_info(${e})`).all();t=new Set(n.map(a=>a.name))}catch{return[]}return(h[e]??[]).filter(n=>/^[A-Za-z_][A-Za-z0-9_]*$/.test(n)&&t.has(n)&&n in s)}isPrivateGroveRow(e,s){const r=this.groveOf(e,s);return!r||r.policy==null?!1:r.policy==="private"}groveOf(e,s){try{return this.db.prepare(`SELECT t.project_id AS project_id, (SELECT sync_policy FROM projects WHERE id = t.project_id) AS policy
2
+ FROM ${e} t WHERE t.id = ?`).get(s)}catch{return}}shareOutcome(e,s){const r=d[e];if(!r)return{ok:!1,reason:"not_found"};const t=this.groveOf(r.table,s);return t&&t.policy==="private"?{ok:!1,reason:"private_grove",projectId:t.project_id}:this.share(e,s)?{ok:!0}:{ok:!1,reason:"not_found"}}projectsHasSyncPolicy(){try{return this.db.prepare("PRAGMA table_info(projects)").all().some(e=>e.name==="sync_policy")}catch{return!1}}unshare(e,s){const r=d[e];return r?this.db.transaction(()=>{const t=this.db.prepare(`UPDATE ${r.table} SET is_shared = 0 WHERE id = ?`).run(s);return t.changes>0&&_(this.db,"visibility.share",{kind:e,row_id:s,shared:!1}),t.changes>0})():!1}collectForPush(e,s=500){const r=e??"1970-01-01",t=[],c=this.projectsHasSyncPolicy()?"AND (project_id IS NULL OR project_id IN (SELECT id FROM projects WHERE sync_policy = 'team'))":"";for(const[n,a]of Object.entries(d)){if(t.length>=s)break;const l=n,o=this.db.prepare(`SELECT * FROM ${a.table}
3
3
  WHERE is_shared = 1 ${c} AND ${a.ts} > ?
4
4
  ORDER BY ${a.ts} ASC
5
- LIMIT ?`).all(e,Math.max(1,r-s.length));for(const i of o)s.push({kind:d,id:i.id,project_id:i.project_id??null,payload:i,updated_at:i[a.ts]||new Date().toISOString()})}return s}applyRemote(t,r){const e=l[t.kind];if(!e)return{kind:"skipped"};const s=this.db.prepare(`SELECT ${e.ts} as ts FROM ${e.table} WHERE id = ?`).get(t.id);if(!s){const o=this.safeRemoteColumns(e.table,t.payload);if(o.length===0)return{kind:"skipped"};if(t.project_id==null)return{kind:"skipped"};const i=["id","project_id",...o],p=i.map(()=>"?").join(","),u=[t.id,t.project_id,...o.map(_=>t.payload[_])];try{return this.db.prepare(`INSERT INTO ${e.table} (${i.join(",")}) VALUES (${p})`).run(...u),{kind:"inserted"}}catch{return{kind:"skipped"}}}if(s.ts>t.updated_at){const i=this.db.prepare(`
5
+ LIMIT ?`).all(r,Math.max(1,s-t.length));for(const i of o)t.push({kind:l,id:i.id,project_id:i.project_id??null,payload:i,updated_at:i[a.ts]||new Date().toISOString()})}return t}applyRemote(e,s){const r=d[e.kind];if(!r)return{kind:"skipped"};const t=this.db.prepare(`SELECT ${r.ts} as ts FROM ${r.table} WHERE id = ?`).get(e.id);if(!t){const o=this.safeRemoteColumns(r.table,e.payload);if(o.length===0)return{kind:"skipped"};if(e.project_id==null)return{kind:"skipped"};const i=["id","project_id",...o],u=i.map(()=>"?").join(","),p=[e.id,e.project_id,...o.map(f=>e.payload[f])];try{return this.db.prepare(`INSERT INTO ${r.table} (${i.join(",")}) VALUES (${u})`).run(...p),{kind:"inserted"}}catch{return{kind:"skipped"}}}if(t.ts>e.updated_at){const i=this.db.prepare(`
6
6
  INSERT INTO sync_conflicts
7
7
  (local_kind, local_id, remote_payload, remote_origin)
8
8
  VALUES (?, ?, ?, ?)
9
- `).run(t.kind,t.id,JSON.stringify(t.payload),r).lastInsertRowid;if(t.project_id!=null)try{this.db.prepare(`
9
+ `).run(e.kind,e.id,JSON.stringify(e.payload),s).lastInsertRowid;if(e.project_id!=null)try{const u=this.db.prepare(`
10
10
  INSERT INTO quests (project_id, title, description, priority, status, tags)
11
11
  VALUES (?, ?, ?, 'high', 'pending', 'sync-conflict')
12
- `).run(t.project_id,`[sync] Resolve conflict on ${t.kind} #${t.id}`,`A remote ${r} pushed a ${t.kind} that conflicts with a newer local edit. See sync_conflicts row #${i} for the remote payload. Resolve with wyrm_sync_resolve (kept_local | kept_remote | merged).`)}catch{}return{kind:"conflict",conflict_id:i}}const n=this.safeRemoteColumns(e.table,t.payload);if(n.length===0)return{kind:"skipped"};const a=n.map(o=>`${o} = ?`).join(", "),d=n.map(o=>t.payload[o]);try{return this.db.prepare(`UPDATE ${e.table} SET ${a} WHERE id = ?`).run(...d,t.id),{kind:"overwritten"}}catch{return{kind:"skipped"}}}recordPush(t,r,e,s){const c=new Date().toISOString();return this.db.prepare(`
12
+ `).run(e.project_id,`[sync] Resolve conflict on ${e.kind} #${e.id}`,`A remote ${s} pushed a ${e.kind} that conflicts with a newer local edit. See sync_conflicts row #${i} for the remote payload. Resolve with wyrm_sync_resolve (kept_local | kept_remote | merged).`);this.db.prepare("UPDATE sync_conflicts SET quest_id = ? WHERE id = ?").run(u.lastInsertRowid,i)}catch{}return{kind:"conflict",conflict_id:i}}const n=this.safeRemoteColumns(r.table,e.payload);if(n.length===0)return{kind:"skipped"};const a=n.map(o=>`${o} = ?`).join(", "),l=n.map(o=>e.payload[o]);try{return this.db.prepare(`UPDATE ${r.table} SET ${a} WHERE id = ?`).run(...l,e.id),{kind:"overwritten"}}catch{return{kind:"skipped"}}}recordPush(e,s,r,t){const c=new Date().toISOString();return this.db.prepare(`
13
13
  INSERT INTO sync_log
14
14
  (direction, remote_origin, kind, count, since_cursor, new_cursor, duration_ms)
15
15
  VALUES ('push', ?, 'mixed', ?, ?, ?, ?)
16
- `).run(r,t.length,s??null,c,e),{pushed:t.length,conflicts:0,new_cursor:c,conflict_ids:[]}}applyPull(t,r,e,s){const c={};let n=0;for(const d of t){const o=this.applyRemote(d,r);(o.kind==="inserted"||o.kind==="overwritten")&&(n++,c[d.kind]=(c[d.kind]??0)+1)}const a=new Date().toISOString();return this.db.prepare(`
16
+ `).run(s,e.length,t??null,c,r),{pushed:e.length,conflicts:0,new_cursor:c,conflict_ids:[]}}applyPull(e,s,r,t){const c={};let n=0;for(const l of e){const o=this.applyRemote(l,s);(o.kind==="inserted"||o.kind==="overwritten")&&(n++,c[l.kind]=(c[l.kind]??0)+1)}const a=new Date().toISOString();return this.db.prepare(`
17
17
  INSERT INTO sync_log
18
18
  (direction, remote_origin, kind, count, since_cursor, new_cursor, duration_ms)
19
19
  VALUES ('pull', ?, 'mixed', ?, ?, ?, ?)
20
- `).run(r,n,s??null,a,e),{pulled:n,new_cursor:a,by_kind:c}}latestCursor(t,r){return this.db.prepare(`
20
+ `).run(s,n,t??null,a,r),{pulled:n,new_cursor:a,by_kind:c}}latestCursor(e,s){return this.db.prepare(`
21
21
  SELECT new_cursor FROM sync_log
22
22
  WHERE direction = ? AND remote_origin = ?
23
23
  ORDER BY id DESC LIMIT 1
24
- `).get(t,r)?.new_cursor??null}unresolvedConflicts(t=50){return this.db.prepare(`
24
+ `).get(e,s)?.new_cursor??null}unresolvedConflicts(e=50){return this.db.prepare(`
25
25
  SELECT id, local_kind, local_id, remote_origin, detected_at, quest_id
26
26
  FROM sync_conflicts
27
27
  WHERE resolved_at IS NULL
28
28
  ORDER BY detected_at DESC LIMIT ?
29
- `).all(t)}resolveConflict(t,r,e){const s=this.db.prepare("SELECT * FROM sync_conflicts WHERE id = ? AND resolved_at IS NULL").get(t);if(!s)return!1;if(r==="kept_remote"||r==="merged"){const c=l[s.local_kind];if(c){const n=r==="merged"&&e?e:JSON.parse(s.remote_payload),a=Object.keys(n).filter(i=>i!=="id"&&/^[A-Za-z_][A-Za-z0-9_]*$/.test(i)),d=a.map(i=>`${i} = ?`).join(", "),o=a.map(i=>n[i]);try{this.db.prepare(`UPDATE ${c.table} SET ${d} WHERE id = ?`).run(...o,s.local_id)}catch{}}}return this.db.prepare(`
29
+ `).all(e)}resolveConflict(e,s,r){const t=this.db.prepare("SELECT * FROM sync_conflicts WHERE id = ? AND resolved_at IS NULL").get(e);if(!t)return!1;const c=t.source==="cloud"?E[t.local_kind]:d[t.local_kind]?.table;let n=null;if(s==="merged"?n=r??null:s==="kept_remote"&&t.winner!=="remote"?n=JSON.parse(t.remote_payload):s==="kept_local"&&t.winner==="remote"&&t.local_payload&&(n=JSON.parse(t.local_payload)),c&&n){let a=null;if(t.source==="cloud")try{a=new Set(this.db.prepare(`PRAGMA table_info(${c})`).all().map(i=>i.name))}catch{a=new Set}const l=n,o=Object.keys(l).filter(i=>i!=="id"&&/^[A-Za-z_][A-Za-z0-9_]*$/.test(i)&&(!a||a.has(i)));if(o.length>0){const i=o.map(p=>`${p} = ?`).join(", "),u=o.map(p=>l[p]);try{this.db.prepare(`UPDATE ${c} SET ${i} WHERE id = ?`).run(...u,t.local_id)}catch{}}}if(this.db.prepare(`
30
30
  UPDATE sync_conflicts
31
31
  SET resolved_at = datetime('now'), resolution = ?
32
32
  WHERE id = ?
33
- `).run(r,t),!0}}export{h as Federation};
33
+ `).run(s,e),t.quest_id!=null)try{this.db.prepare(`UPDATE quests SET status = 'completed', completed_at = COALESCE(completed_at, datetime('now')), updated_at = datetime('now')
34
+ WHERE id = ? AND status != 'completed'`).run(t.quest_id)}catch{}return!0}}export{b as Federation};
@@ -0,0 +1 @@
1
+ import{basename as m}from"node:path";import{AuditLog as p,loadAuditSignerFromEnv as g}from"./audit.js";import{getActor as y,sanitizeActorId as a}from"./handlers/boundary.js";import{onErasureRecorded as _}from"./erasure-log.js";const b="governance.";let c=null;function I(e){c=e?a(e)??null:null}function A(){if(c)return c;const e=m(process.argv[1]??"").replace(/\.(c|m)?js$/,"");return e==="index"||e==="wyrm-mcp"?"mcp":e==="http-fast"||e==="wyrm-ui"?"http":e==="wyrm"||e==="wyrm-cli"?"cli":"local"}function E(){const e=y();return e.agent_id&&e.source==="writer"?`writer:${e.agent_id}`:e.agent_id?`agent:${e.agent_id}`:e.run_id?`run:${e.run_id}`:`operator:${a(process.env.WYRM_OPERATOR)??A()}`}const f=/^[A-Za-z0-9_.:@-]{1,64}$/,$=/^[a-z][a-z0-9_]{0,39}$/,l=100;function h(e){if(e===null)return null;if(typeof e=="boolean")return e;if(typeof e=="number")return Number.isFinite(e)?e:void 0;if(typeof e=="string")return f.test(e)?e:void 0}function N(e){const t={};for(const[r,n]of Object.entries(e)){if(!$.test(r))continue;if(Array.isArray(n)){const o=n.filter(i=>typeof i=="number"&&Number.isFinite(i));if(o.length===0&&n.length>0)continue;t[r]=o.slice(0,l),o.length>l&&(t[`${r}_total`]=o.length);continue}if(n&&typeof n=="object"){const o={};for(const[i,u]of Object.entries(n))f.test(i)&&typeof u=="number"&&Number.isFinite(u)&&(o[i]=u);t[r]=o;continue}const s=h(n);s!==void 0&&(t[r]=s)}return t}const d=new WeakMap;function j(e){let t=d.get(e);return t||(t=new p(e,g()),d.set(e,t)),t}function w(e,t,r,n={}){j(e).log({event_kind:`${b}${t}`,actor:n.system?`system:${n.system}`:n.actor??E(),project_id:n.projectId??null,payload:N(r),...n.system?{agent_id:null,run_id:null}:{}})}_((e,t)=>{e.mode==="erase"&&w(t,"erasure",{erasure_id:e.id,reason:e.reason,category:e.category,total:e.total,counts:e.counts})});export{b as GOVERNANCE_EVENT_PREFIX,E as governanceActor,w as recordGovernanceEvent,I as setGovernanceSurface,N as shapePayload};
@@ -1,4 +1,4 @@
1
- import{TOOL_ANNOTATIONS as l}from"../tool-annotations.js";const y=[{name:"wyrm_agent_init",description:"Bootstrap Wyrm's autonomous agent loop in one call. Starts the wyrm-loop daemon as a detached background process so subsequent ticks happen automatically every interval_seconds. Optionally seeds a first goal in the same call. Idempotent \u2014 if already running, returns status without spawning a second instance.",inputSchema:{type:"object",properties:{interval_seconds:{type:"number",description:"Seconds between OODA ticks (default 600 = 10 min, min 10)"},max_steps:{type:"number",description:"Max iterations per goal per tick (default 3, max 20)"},project_path:{type:"string",description:"Scope the daemon to a project root (optional)"},verbose:{type:"boolean",description:"Log every tick to ~/.wyrm/wyrm-loop.log"},seed_goal:{type:"object",description:"Optional first goal to set in the same call",properties:{title:{type:"string"},description:{type:"string"},success_criteria:{type:"string"},priority:{type:"string",enum:["critical","high","medium","low"]},deadline:{type:"string"},max_iterations:{type:"number"}},required:["title"]}}},annotations:l.wyrm_agent_init,aliases:[],handler:async(s,o)=>{const{agentDaemon:a,db:p,goals:n}=o,t=s,e=Math.max(10,Math.min(t.interval_seconds??600,86400)),i=a.start({interval_seconds:e,max_steps:t.max_steps,project_path:t.project_path,verbose:t.verbose??!1});if(!i.ok)return{content:[{type:"text",text:`\u{F115D} Agent init failed: ${i.error}`}],isError:!0};let c="";if(t.seed_goal){const m=t.project_path?p.getProject(t.project_path):null,d=n.set({project_id:m?.id??null,title:t.seed_goal.title,description:t.seed_goal.description,success_criteria:t.seed_goal.success_criteria,deadline:t.seed_goal.deadline,priority:t.seed_goal.priority,max_iterations:t.seed_goal.max_iterations});c=`
2
- Seeded goal #${d.id}: ${d.title}`}const r=i.status;return{content:[{type:"text",text:[`\u{F115D} Agent ${r.pid!=null&&r.pid!==i.pid?"already running":"started"} \u2014 pid ${r.pid}`,` Interval: ${e}s \xB7 Active goals: ${r.active_goals} \xB7 Total iterations: ${r.total_iterations}`,` Log: ${r.log_file}`+c].join(`
3
- `)}]}}},{name:"wyrm_agent_status",description:"Inspect the autonomous agent: is the wyrm-loop daemon running, what was the last action, how many active goals + total iterations. Use to confirm bootstrap worked.",inputSchema:{type:"object",properties:{include_log:{type:"boolean",description:"Include last ~40 lines of the daemon log"}}},annotations:l.wyrm_agent_status,aliases:[],handler:async(s,o)=>{const{agentDaemon:a,goals:p}=o,n=s,t=a.status(),e=[];if(t.running){if(e.push(`\u{F115D} Agent RUNNING \u2014 pid ${t.pid}${t.started_at?` (since ${t.started_at})`:""}`),t.uptime_seconds!=null){const i=Math.floor(t.uptime_seconds/60),c=t.uptime_seconds%60;e.push(` Uptime: ${i}m ${c}s`)}}else e.push("\u{F115D} Agent NOT RUNNING. Call wyrm_agent_init to start it.");return e.push(` Active goals: ${t.active_goals} \xB7 Total iterations: ${t.total_iterations}`),t.last_action&&e.push(` Last action (${t.last_action.ran_at}): ${t.last_action.summary} [${t.last_action.result_status??"?"}]`),n.include_log&&(e.push(""),e.push("=== Recent log ==="),e.push(a.recentLog(40))),{content:[{type:"text",text:e.join(`
4
- `)}]}}},{name:"wyrm_agent_stop",description:"Stop the autonomous agent daemon (SIGTERM with grace, then SIGKILL). Goals remain in DB and can be resumed by calling wyrm_agent_init again.",inputSchema:{type:"object",properties:{grace_ms:{type:"number",description:"Wait time before SIGKILL (default 3000, max 30000)"}}},annotations:l.wyrm_agent_stop,aliases:[],handler:async(s,o)=>{const{agentDaemon:a}=o,p=s,n=await a.stop({grace_ms:p.grace_ms});return n.ok?{content:[{type:"text",text:n.was_running?`\u{F115D} Agent stopped (was pid ${n.pid}). Goals remain in DB \u2014 wyrm_agent_init resumes.`:"\u{F115D} Agent was not running. (No-op.)"}]}:{content:[{type:"text",text:`\u{F115D} Stop failed: ${n.error}`}],isError:!0}}},{name:"wyrm_agent_restart",description:"Stop the daemon and start it again with optional new settings. Useful when changing interval_seconds or max_steps without manually stopping first.",inputSchema:{type:"object",properties:{interval_seconds:{type:"number"},max_steps:{type:"number"},project_path:{type:"string"},verbose:{type:"boolean"}}},annotations:l.wyrm_agent_restart,aliases:[],handler:async(s,o)=>{const{agentDaemon:a,goals:p}=o,n=s,t=await a.restart({interval_seconds:n.interval_seconds,max_steps:n.max_steps,project_path:n.project_path,verbose:n.verbose});if(!t.ok)return{content:[{type:"text",text:`\u{F115D} Restart failed: ${t.error}`}],isError:!0};const e=t.status;return{content:[{type:"text",text:`\u{F115D} Agent restarted \u2014 pid ${e.pid}. Active goals: ${e.active_goals}.`}]}}}];export{y as agentToolSpecs};
1
+ import{TOOL_ANNOTATIONS as l}from"../tool-annotations.js";const y=[{name:"wyrm_agent_init",description:"Bootstrap Wyrm's autonomous agent loop in one call. Starts the wyrm-loop daemon as a detached background process so subsequent ticks happen automatically every interval_seconds. Optionally seeds a first goal in the same call. Idempotent: if already running, returns status without spawning a second instance.",inputSchema:{type:"object",properties:{interval_seconds:{type:"number",description:"Seconds between OODA ticks (default 600 = 10 min, min 10)"},max_steps:{type:"number",description:"Max iterations per goal per tick (default 3, max 20)"},project_path:{type:"string",description:"Scope the daemon to a project root (optional)"},verbose:{type:"boolean",description:"Log every tick to ~/.wyrm/wyrm-loop.log"},seed_goal:{type:"object",description:"Optional first goal to set in the same call",properties:{title:{type:"string"},description:{type:"string"},success_criteria:{type:"string"},priority:{type:"string",enum:["critical","high","medium","low"]},deadline:{type:"string"},max_iterations:{type:"number"}},required:["title"]}}},annotations:l.wyrm_agent_init,aliases:[],handler:async(s,o)=>{const{agentDaemon:a,db:p,goals:n}=o,t=s,e=Math.max(10,Math.min(t.interval_seconds??600,86400)),i=a.start({interval_seconds:e,max_steps:t.max_steps,project_path:t.project_path,verbose:t.verbose??!1});if(!i.ok)return{content:[{type:"text",text:`\u{F115D} Agent init failed: ${i.error}`}],isError:!0};let c="";if(t.seed_goal){const m=t.project_path?p.getProject(t.project_path):null,d=n.set({project_id:m?.id??null,title:t.seed_goal.title,description:t.seed_goal.description,success_criteria:t.seed_goal.success_criteria,deadline:t.seed_goal.deadline,priority:t.seed_goal.priority,max_iterations:t.seed_goal.max_iterations});c=`
2
+ Seeded goal #${d.id}: ${d.title}`}const r=i.status;return{content:[{type:"text",text:[`\u{F115D} Agent ${r.pid!=null&&r.pid!==i.pid?"already running":"started"}, pid ${r.pid}`,` Interval: ${e}s \xB7 Active goals: ${r.active_goals} \xB7 Total iterations: ${r.total_iterations}`,` Log: ${r.log_file}`+c].join(`
3
+ `)}]}}},{name:"wyrm_agent_status",description:"Inspect the autonomous agent: is the wyrm-loop daemon running, what was the last action, how many active goals + total iterations. Use to confirm bootstrap worked.",inputSchema:{type:"object",properties:{include_log:{type:"boolean",description:"Include last ~40 lines of the daemon log"}}},annotations:l.wyrm_agent_status,aliases:[],handler:async(s,o)=>{const{agentDaemon:a,goals:p}=o,n=s,t=a.status(),e=[];if(t.running){if(e.push(`\u{F115D} Agent RUNNING, pid ${t.pid}${t.started_at?` (since ${t.started_at})`:""}`),t.uptime_seconds!=null){const i=Math.floor(t.uptime_seconds/60),c=t.uptime_seconds%60;e.push(` Uptime: ${i}m ${c}s`)}}else e.push("\u{F115D} Agent NOT RUNNING. Call wyrm_agent_init to start it.");return e.push(` Active goals: ${t.active_goals} \xB7 Total iterations: ${t.total_iterations}`),t.last_action&&e.push(` Last action (${t.last_action.ran_at}): ${t.last_action.summary} [${t.last_action.result_status??"?"}]`),n.include_log&&(e.push(""),e.push("=== Recent log ==="),e.push(a.recentLog(40))),{content:[{type:"text",text:e.join(`
4
+ `)}]}}},{name:"wyrm_agent_stop",description:"Stop the autonomous agent daemon (SIGTERM with grace, then SIGKILL). Goals remain in DB and can be resumed by calling wyrm_agent_init again.",inputSchema:{type:"object",properties:{grace_ms:{type:"number",description:"Wait time before SIGKILL (default 3000, max 30000)"}}},annotations:l.wyrm_agent_stop,aliases:[],handler:async(s,o)=>{const{agentDaemon:a}=o,p=s,n=await a.stop({grace_ms:p.grace_ms});return n.ok?{content:[{type:"text",text:n.was_running?`\u{F115D} Agent stopped (was pid ${n.pid}). Goals remain in DB; wyrm_agent_init resumes.`:"\u{F115D} Agent was not running. (No-op.)"}]}:{content:[{type:"text",text:`\u{F115D} Stop failed: ${n.error}`}],isError:!0}}},{name:"wyrm_agent_restart",description:"Stop the daemon and start it again with optional new settings. Useful when changing interval_seconds or max_steps without manually stopping first.",inputSchema:{type:"object",properties:{interval_seconds:{type:"number"},max_steps:{type:"number"},project_path:{type:"string"},verbose:{type:"boolean"}}},annotations:l.wyrm_agent_restart,aliases:[],handler:async(s,o)=>{const{agentDaemon:a,goals:p}=o,n=s,t=await a.restart({interval_seconds:n.interval_seconds,max_steps:n.max_steps,project_path:n.project_path,verbose:n.verbose});if(!t.ok)return{content:[{type:"text",text:`\u{F115D} Restart failed: ${t.error}`}],isError:!0};const e=t.status;return{content:[{type:"text",text:`\u{F115D} Agent restarted, pid ${e.pid}. Active goals: ${e.active_goals}.`}]}}}];export{y as agentToolSpecs};
@@ -1 +1 @@
1
- import{SHIM_ROUTES as c}from"./shims.js";const p=["project_path","persona","persona_name","mood","federate"];function f(o){return p.some(e=>o[e]!==void 0)}function u(o){const e={...o};return e.project_path===void 0&&(e.project_path=process.cwd()),e}function m(){const o={},e=(t,r)=>{if(o[t]!==void 0)throw new Error(`alias-adapters: ${t} absorbed by two shims (${o[t].target} and ${r.target})`);o[t]=r};for(const[t,r]of Object.entries(c))for(const[a,n]of Object.entries(r.routes))if(typeof n=="string"){if(n===t)continue;e(n,{target:t,adapt:i=>({...i,[r.discriminator]:a}),note:`T022: absorbed by ${t} ${r.discriminator}=${a}; resolveShimCall strips the discriminator back onto the original ${n} dispatcher case \u2014 same code path by construction (roundtrip-locked).`})}else for(const[i,d]of Object.entries(n.routes))e(d,{target:t,adapt:s=>({...s,[r.discriminator]:a,[n.key]:i}),note:`T022: absorbed by ${t} ${r.discriminator}=${a} ${n.key}=${i}; resolveShimCall strips both back onto the original ${d} dispatcher case \u2014 same code path by construction (roundtrip-locked).`});return o}const h={...m(),wyrm_buddy:{target:"buddy",adapt:u,note:"T021 buddy fold: companion params (persona/mood/size/federation) merged into the well-known buddy tool as additive optional args; both names run runCompanionBuddy (index.ts) \u2014 one code path, not a duplicate."}};export{h as ALIAS_ROUTE_OVERRIDES,p as COMPANION_BUDDY_PARAMS,u as adaptWyrmBuddyToBuddy,f as isCompanionBuddyCall};
1
+ import{SHIM_ROUTES as c}from"./shims.js";const p=["project_path","persona","persona_name","mood","federate"];function f(o){return p.some(e=>o[e]!==void 0)}function u(o){const e={...o};return e.project_path===void 0&&(e.project_path=process.cwd()),e}function m(){const o={},e=(t,r)=>{if(o[t]!==void 0)throw new Error(`alias-adapters: ${t} absorbed by two shims (${o[t].target} and ${r.target})`);o[t]=r};for(const[t,r]of Object.entries(c))for(const[a,n]of Object.entries(r.routes))if(typeof n=="string"){if(n===t)continue;e(n,{target:t,adapt:i=>({...i,[r.discriminator]:a}),note:`T022: absorbed by ${t} ${r.discriminator}=${a}; resolveShimCall strips the discriminator back onto the original ${n} dispatcher case: same code path by construction (roundtrip-locked).`})}else for(const[i,d]of Object.entries(n.routes))e(d,{target:t,adapt:s=>({...s,[r.discriminator]:a,[n.key]:i}),note:`T022: absorbed by ${t} ${r.discriminator}=${a} ${n.key}=${i}; resolveShimCall strips both back onto the original ${d} dispatcher case: same code path by construction (roundtrip-locked).`});return o}const h={...m(),wyrm_buddy:{target:"buddy",adapt:u,note:"T021 buddy fold: companion params (persona/mood/size/federation) merged into the well-known buddy tool as additive optional args; both names run runCompanionBuddy (index.ts): one code path, not a duplicate."}};export{h as ALIAS_ROUTE_OVERRIDES,p as COMPANION_BUDDY_PARAMS,u as adaptWyrmBuddyToBuddy,f as isCompanionBuddyCall};
@@ -1 +1 @@
1
- import{TOOL_ANNOTATIONS as p}from"../tool-annotations.js";import{resolveAttribution as _}from"../attribution.js";import{parseAuditSignature as h}from"../audit.js";const b=[{name:"wyrm_audit_log",description:"Append a hash-chained audit event. Each entry references the previous entry's sha-256, so tampering breaks the chain. Optional Ed25519 signature layer. Used for SOC2/HIPAA-grade audit.",inputSchema:{type:"object",properties:{event_kind:{type:"string",description:"Type of event: 'tool_call', 'key_export', 'truth_change', 'sync', 'migration', ..."},actor:{type:"string",description:"agent_id, 'system', operator email, etc."},projectPath:{type:"string",description:"Project path (optional)"},payload:{type:"object",description:"Structured event details (will be JSON-serialized)"},signature:{type:"string",description:"Optional explicit Ed25519 signature stored as-is (legacy v1 = base64 over the row_hash). Omit to auto-sign with the configured WYRM_AUDIT_SIGNING_KEY: v2 format 'v2:<key_fp>:<base64>' binding agent_id/run_id into the signed payload."}},required:["event_kind","payload"]},annotations:p.wyrm_audit_log,aliases:[],handler:async(i,n)=>{const{auditLog:r,db:e}=n,{event_kind:t,actor:a,projectPath:o,payload:l,signature:u}=i,y=o?e.getProject(o):null,s=r.log({event_kind:t,actor:a,project_id:y?.id??null,payload:l,signature:u}),d=_(s),c=h(s.signature),g=c?c.version===2?` \xB7 signed v2 key=${c.key_fp??"?"}`:" \xB7 signed v1":"";return{content:[{type:"text",text:`\u{F115D} Audit #${s.id} logged \u2014 row_hash ${s.row_hash.slice(0,16)}\u2026 \xB7 actor=${d.actor}${d.run_id?` \xB7 run=${d.run_id}`:""}${g}`}]}}},{name:"wyrm_audit_verify",description:"Walk the entire audit_log chain and verify every prev_hash / row_hash. Returns ok=true if intact, or first_invalid_id when tampering is detected. Optional public_keys also verify Ed25519 signatures.",inputSchema:{type:"object",properties:{public_keys:{type:"array",items:{type:"string"},description:"Optional PEM-encoded Ed25519 public keys. When given, every signed row is checked: v2 rows against the key matching their fingerprint, v1 rows against all keys. Fail-closed."}}},annotations:p.wyrm_audit_verify,aliases:[],handler:async(i,n)=>{const{auditLog:r}=n,{public_keys:e}=i,t=Array.isArray(e)?e.filter(o=>typeof o=="string"):[],a=r.verify(t.length>0?{publicKeys:t}:void 0);return{content:[{type:"text",text:JSON.stringify(a,null,2)}]}}},{name:"wyrm_audit_export",description:"Export a date-ranged tamper-evident audit bundle (JSON). Includes full chain so an external auditor can verify offline without DB access. Use range_start='2026-01-01' for SOC2 audit windows.",inputSchema:{type:"object",properties:{range_start:{type:"string",description:"ISO timestamp \u2014 inclusive lower bound (omit for genesis)"},range_end:{type:"string",description:"ISO timestamp \u2014 inclusive upper bound (omit for latest)"}}},annotations:p.wyrm_audit_export,aliases:[],handler:async(i,n)=>{const{auditLog:r}=n,{range_start:e,range_end:t}=i,a=r.export({range_start:e,range_end:t});return{content:[{type:"text",text:JSON.stringify(a,null,2)}]}}}];export{b as auditToolSpecs};
1
+ import{TOOL_ANNOTATIONS as p}from"../tool-annotations.js";import{resolveAttribution as _}from"../attribution.js";import{parseAuditSignature as h}from"../audit.js";const b=[{name:"wyrm_audit_log",description:"Append a hash-chained audit event. Each entry references the previous entry's sha-256, so tampering breaks the chain. Optional Ed25519 signature layer. Used for SOC2/HIPAA-grade audit.",inputSchema:{type:"object",properties:{event_kind:{type:"string",description:"Type of event: 'tool_call', 'key_export', 'truth_change', 'sync', 'migration', ..."},actor:{type:"string",description:"agent_id, 'system', operator email, etc."},projectPath:{type:"string",description:"Project path (optional)"},payload:{type:"object",description:"Structured event details (will be JSON-serialized)"},signature:{type:"string",description:"Optional explicit Ed25519 signature stored as-is (legacy v1 = base64 over the row_hash). Omit to auto-sign with the configured WYRM_AUDIT_SIGNING_KEY: v2 format 'v2:<key_fp>:<base64>' binding agent_id/run_id into the signed payload."}},required:["event_kind","payload"]},annotations:p.wyrm_audit_log,aliases:[],handler:async(i,n)=>{const{auditLog:r,db:e}=n,{event_kind:t,actor:a,projectPath:o,payload:l,signature:u}=i,y=o?e.getProject(o):null,s=r.log({event_kind:t,actor:a,project_id:y?.id??null,payload:l,signature:u}),d=_(s),c=h(s.signature),g=c?c.version===2?` \xB7 signed v2 key=${c.key_fp??"?"}`:" \xB7 signed v1":"";return{content:[{type:"text",text:`\u{F115D} Audit #${s.id} logged: row_hash ${s.row_hash.slice(0,16)}\u2026 \xB7 actor=${d.actor}${d.run_id?` \xB7 run=${d.run_id}`:""}${g}`}]}}},{name:"wyrm_audit_verify",description:"Walk the entire audit_log chain and verify every prev_hash / row_hash. Returns ok=true if intact, or first_invalid_id when tampering is detected. Optional public_keys also verify Ed25519 signatures.",inputSchema:{type:"object",properties:{public_keys:{type:"array",items:{type:"string"},description:"Optional PEM-encoded Ed25519 public keys. When given, every signed row is checked: v2 rows against the key matching their fingerprint, v1 rows against all keys. Fail-closed."}}},annotations:p.wyrm_audit_verify,aliases:[],handler:async(i,n)=>{const{auditLog:r}=n,{public_keys:e}=i,t=Array.isArray(e)?e.filter(o=>typeof o=="string"):[],a=r.verify(t.length>0?{publicKeys:t}:void 0);return{content:[{type:"text",text:JSON.stringify(a,null,2)}]}}},{name:"wyrm_audit_export",description:"Export a date-ranged tamper-evident audit bundle (JSON). Includes full chain so an external auditor can verify offline without DB access. Use range_start='2026-01-01' for SOC2 audit windows.",inputSchema:{type:"object",properties:{range_start:{type:"string",description:"ISO timestamp, inclusive lower bound (omit for genesis)"},range_end:{type:"string",description:"ISO timestamp, inclusive upper bound (omit for latest)"}}},annotations:p.wyrm_audit_export,aliases:[],handler:async(i,n)=>{const{auditLog:r}=n,{range_start:e,range_end:t}=i,a=r.export({range_start:e,range_end:t});return{content:[{type:"text",text:JSON.stringify(a,null,2)}]}}}];export{b as auditToolSpecs};
@@ -1 +1 @@
1
- import{AsyncLocalStorage as g}from"node:async_hooks";const d=Object.freeze({agent_id:null,run_id:null,source:"legacy"}),A=64,y=160;function u(e){if(typeof e!="string")return null;const n=e.trim();return n.length===0||n.length>A||!/^[\x20-\x7E]+$/.test(n)||n.includes(";")?null:n}function f(e){const n=Array.isArray(e)?e[0]:e;if(typeof n!="string"||n.length===0||n.length>y)return null;const t=n.indexOf(";"),l=u(t===-1?n:n.slice(0,t));if(!l)return null;if(t===-1)return{agent_id:l,run_id:null};const c=n.slice(t+1);if(c.trim()==="")return{agent_id:l,run_id:null};const o=u(c);return o?{agent_id:l,run_id:o}:null}function m(e={}){let n=null,t=null;const l=e.meta?.["wyrm/actor"];if(typeof l=="string"){const r=f(l);r&&({agent_id:n,run_id:t}=r)}else if(l!==null&&typeof l=="object"){const r=l;n=u(r.agent_id),t=u(r.run_id)}if(e.header!==void 0){const r=f(e.header);r&&(n=n??r.agent_id,t=t??r.run_id)}const c=[{source:"param",agent:u(e.args?.actor),run:u(e.args?.run_id)},{source:"meta",agent:n,run:t},{source:"env",agent:u(process.env.WYRM_AGENT_ID),run:u(process.env.WYRM_RUN_ID)},{source:"client",agent:u(e.clientName),run:null}];let o=null,i=null,s=null,a=null;for(const r of c)o===null&&r.agent!==null&&(o=r.agent,s=r.source),i===null&&r.run!==null&&(i=r.run,a=r.source);return o===null&&i===null?d:{agent_id:o,run_id:i,source:s??a??"legacy"}}function x(e){return e.run_id!==null?!0:e.agent_id!==null&&e.source!=="client"&&e.source!=="legacy"}const _=new g;function E(e,n){return _.run(e,n)}const p=new g;function R(e,n){return p.run(e,n)}function I(){return p.getStore()??null}function N(){const e=_.getStore();if(e)return e;const n=u(process.env.WYRM_AGENT_ID),t=u(process.env.WYRM_RUN_ID);return n!==null||t!==null?{agent_id:n,run_id:t,source:"env"}:d}export{d as LEGACY_ENVELOPE,I as currentReadCacheKey,N as getActor,x as hasExplicitAttribution,f as parseWyrmActorHeader,m as resolveActorEnvelope,E as runWithActor,R as runWithReadCacheKey,u as sanitizeActorId};
1
+ import{AsyncLocalStorage as d}from"node:async_hooks";const f=Object.freeze({agent_id:null,run_id:null,source:"legacy"}),h=64,x=160;function i(r){if(typeof r!="string")return null;const n=r.trim();return n.length===0||n.length>h||!/^[\x20-\x7E]+$/.test(n)||n.includes(";")?null:n}function g(r){const n=Array.isArray(r)?r[0]:r;if(typeof n!="string"||n.length===0||n.length>x)return null;const e=n.indexOf(";"),u=i(e===-1?n:n.slice(0,e));if(!u)return null;if(e===-1)return{agent_id:u,run_id:null};const s=n.slice(e+1);if(s.trim()==="")return{agent_id:u,run_id:null};const l=i(s);return l?{agent_id:u,run_id:l}:null}let c=null;function y(r){return r==="cli"||r==="http"||r==="mcp"||r.startsWith("mcp:")}function E(r){c=r===null?null:i(r)}function I(){return c}function D(r={}){let n=null,e=null;const u=r.meta?.["wyrm/actor"];if(typeof u=="string"){const t=g(u);t&&({agent_id:n,run_id:e}=t)}else if(u!==null&&typeof u=="object"){const t=u;n=i(t.agent_id),e=i(t.run_id)}if(r.header!==void 0){const t=g(r.header);t&&(n=n??t.agent_id,e=e??t.run_id)}const s=[{source:"param",agent:i(r.args?.actor),run:i(r.args?.run_id)},{source:"meta",agent:n,run:e},{source:"env",agent:i(process.env.WYRM_AGENT_ID),run:i(process.env.WYRM_RUN_ID)},{source:"client",agent:i(r.clientName),run:null}];let l=null,o=null,a=null,_=null;for(const t of s)l===null&&t.agent!==null&&(l=t.agent,a=t.source),o===null&&t.run!==null&&(o=t.run,_=t.source);if(r.transport==="mcp"){const t=i(r.clientName);if(E(t?i(`mcp:${t}`)??"mcp":"mcp"),l===null)return{agent_id:"mcp",run_id:o,source:"writer"}}return r.transport==="http"&&l===null?{agent_id:"http",run_id:o,source:"writer"}:l===null&&o===null?f:{agent_id:l,run_id:o,source:a??_??"legacy"}}function N(r){return r.run_id!==null?!0:r.agent_id!==null&&r.source!=="client"&&r.source!=="writer"&&r.source!=="legacy"}const p=new d;function W(r,n){return p.run(r,n)}const m=new d;function v(r,n){return m.run(r,n)}function M(){return m.getStore()??null}function A(){const r=p.getStore();if(r)return r;const n=i(process.env.WYRM_AGENT_ID),e=i(process.env.WYRM_RUN_ID),u=i(process.env.WYRM_WRITER_ID);return n!==null?{agent_id:n,run_id:e,source:"env"}:u!==null?{agent_id:u,run_id:e,source:"writer"}:c!==null?{agent_id:c,run_id:e,source:"writer"}:e!==null?{agent_id:null,run_id:e,source:"env"}:f}function w(r){const n=A(),e=n.source==="writer"&&n.agent_id!==null&&(n.agent_id===c||y(n.agent_id));if(n.agent_id!==null&&!e)return n;const u=i(r);return u===null?n:{agent_id:u,run_id:n.run_id,source:"writer"}}function S(r,n){const e=w(r);return e===A()?n():W(e,n)}export{f as LEGACY_ENVELOPE,M as currentReadCacheKey,A as getActor,I as getDefaultWriter,N as hasExplicitAttribution,g as parseWyrmActorHeader,D as resolveActorEnvelope,S as runAsWriter,W as runWithActor,v as runWithReadCacheKey,i as sanitizeActorId,E as setDefaultWriter,w as writerEnvelope};