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,5 +1,5 @@
1
- import{readFileSync as O,writeFileSync as D,existsSync as I}from"node:fs";import{join as $}from"node:path";import{WyrmDB as L,resolveDbPath as A}from"../database.js";import{CloudClient as C,loadSession as v,saveSession as j,resolveCloudDir as U}from"./client.js";import{encrypt as T,decrypt as x}from"./crypto.js";import{classifySession as q,computeMachineFp as P}from"./machine-id.js";function N(){return $(U(),"cloud-cursor.json")}const R=2;function F(t,e,n,o){const s=t?{...t}:{kind:e.kind,row_id:e.row_id,attempts:0,quarantined:!1,last_error:n,first_ms:o,last_ms:o};s.attempts+=1,s.last_error=n,s.last_ms=o;const r=s.attempts>=R,c=r&&!s.quarantined;return r&&(s.quarantined=!0),{entry:s,action:r?"skip":"retry",newlyQuarantined:c}}const g=[{kind:"truth",table:"ground_truths",hasVisibility:!0},{kind:"memory_artifact",table:"memory_artifacts",hasVisibility:!0},{kind:"quest",table:"quests",hasVisibility:!0},{kind:"design_token",table:"design_tokens",hasVisibility:!0},{kind:"design_reference",table:"design_references",hasVisibility:!0},{kind:"skill",table:"skills",hasVisibility:!0}],W=new Set(["sessions","agent_actions","agent_presence","session_seen_artifacts","tool_call_log","llm_query_log","external_call_log","token_savings_log","cost_tracking","usage_events","audit_log","indexing_queue","vectors","symbol_index","update_check","sync_conflicts","sync_log","sync_tombstones","wyrm_cloud_sync_state","mcp_client_configs","watch_dirs","global_context","schema_versions","failure_patterns","data_lake","projects"]),H=/_fts(_config|_data|_docsize|_idx)?$/;function M(t){const e=t.getDatabase(),n=[...g],o=new Set(g.map(s=>s.table));try{const s=e.prepare(`SELECT name FROM sqlite_master
2
- WHERE type='table' AND name NOT LIKE 'sqlite_%'`).all();for(const r of s){const c=r.name;if(o.has(c)||W.has(c)||H.test(c)||!y.test(c))continue;const m=e.prepare(`PRAGMA table_info(${c})`).all();if(!m.some(l=>l.name==="id"))continue;const u=m.some(l=>l.name==="cross_project_visibility");n.push({kind:c,table:c,hasVisibility:u})}}catch{}return n}function ct(t){return t}function V(t,e){if(!y.test(e))return new Set;try{const o=t.getDatabase().prepare(`PRAGMA table_info(${e})`).all();return new Set(o.filter(s=>y.test(s.name)).map(s=>s.name))}catch{return new Set}}const y=/^[a-z_][a-z0-9_]*$/i;function Y(){const t={pull_cursor:0,last_push_updated_ms:0,last_push_at:0,last_pull_at:0},e=N();if(!I(e))return t;try{const n=JSON.parse(O(e,"utf-8"));return{...t,...n}}catch{return t}}function G(t){D(N(),JSON.stringify(t,null,2),{mode:384})}function k(t){t.getDatabase().exec(`
1
+ import{readFileSync as V,writeFileSync as G,existsSync as J}from"node:fs";import{join as Y}from"node:path";import{createHash as K}from"node:crypto";import{WyrmDB as Q,resolveDbPath as B}from"../database.js";import{getLineage as X,getCursor as j,setCursor as z,importCursorFile as Z,bumpDeviceEpoch as ee,mirrorStamp as U}from"../lineage.js";import{CloudClient as te,loadSession as ne,saveSession as se,resolveCloudDir as oe}from"./client.js";import{encrypt as M,decrypt as re}from"./crypto.js";import{classifySession as ie,computeMachineFp as ae}from"./machine-id.js";function A(){return Y(oe(),"cloud-cursor.json")}const W=2;function ce(e,t,s,o){const n=e?{...e}:{kind:t.kind,row_id:t.row_id,attempts:0,quarantined:!1,last_error:s,first_ms:o,last_ms:o};n.attempts+=1,n.last_error=s,n.last_ms=o;const r=n.attempts>=W,c=r&&!n.quarantined;return r&&(n.quarantined=!0),{entry:n,action:r?"skip":"retry",newlyQuarantined:c}}const $=[{kind:"truth",table:"ground_truths",hasVisibility:!0},{kind:"memory_artifact",table:"memory_artifacts",hasVisibility:!0},{kind:"quest",table:"quests",hasVisibility:!0},{kind:"design_token",table:"design_tokens",hasVisibility:!0},{kind:"design_reference",table:"design_references",hasVisibility:!0},{kind:"skill",table:"skills",hasVisibility:!0}],le=new Set(["sessions","agent_actions","agent_presence","session_seen_artifacts","tool_call_log","llm_query_log","external_call_log","token_savings_log","cost_tracking","usage_events","audit_log","indexing_queue","vectors","symbol_index","update_check","sync_conflicts","sync_log","sync_tombstones","wyrm_cloud_sync_state","mcp_client_configs","watch_dirs","global_context","schema_versions","failure_patterns","data_lake","projects"]),ue=/_fts(_config|_data|_docsize|_idx)?$/;function de(e){const t=e.getDatabase(),s=[...$],o=new Set($.map(n=>n.table));try{const n=t.prepare(`SELECT name FROM sqlite_master
2
+ WHERE type='table' AND name NOT LIKE 'sqlite_%'`).all();for(const r of n){const c=r.name;if(o.has(c)||le.has(c)||ue.test(c)||!g.test(c))continue;const i=t.prepare(`PRAGMA table_info(${c})`).all();if(!i.some(l=>l.name==="id"))continue;const a=i.some(l=>l.name==="cross_project_visibility");s.push({kind:c,table:c,hasVisibility:a})}}catch{}return s}function je(e){return e}function F(e,t){if(!g.test(t))return new Set;try{const o=e.getDatabase().prepare(`PRAGMA table_info(${t})`).all();return new Set(o.filter(n=>g.test(n.name)).map(n=>n.name))}catch{return new Set}}const g=/^[a-z_][a-z0-9_]*$/i;function pe(){const e=A();if(!J(e))return null;try{const t=JSON.parse(V(e,"utf-8"));return t&&typeof t=="object"&&!Array.isArray(t)?t:null}catch{return null}}function _e(e){const t={pull_cursor:0,last_push_updated_ms:0,last_push_at:0,last_pull_at:0},s=X(e),o=pe();let n=j(e,"cloud");if(!n){if(Z(e,"cloud",A()),n=j(e,"cloud"),!n){if(!o)return t;const{store:c,...i}=o;return{...t,...i}}return{...t,...n.value}}const r=o?.store;return o&&r&&r.device_id===s.deviceId&&r.epoch===s.epoch&&r.fingerprint===U(e).fingerprint&&Number(o.last_pull_at??0)>Number(n.value.last_pull_at??0)&&ee(e,"cloud-cursor.json is ahead of the database (restored in place)"),{...t,...n.value}}function fe(e,t){try{z(e,"cloud",t)}catch{}G(A(),JSON.stringify({...t,store:U(e)},null,2),{mode:384})}function x(e){e.getDatabase().exec(`
3
3
  CREATE TABLE IF NOT EXISTS sync_tombstones (
4
4
  id INTEGER PRIMARY KEY AUTOINCREMENT,
5
5
  kind TEXT NOT NULL,
@@ -10,11 +10,12 @@ import{readFileSync as O,writeFileSync as D,existsSync as I}from"node:fs";import
10
10
  );
11
11
  CREATE INDEX IF NOT EXISTS idx_sync_tombstones_pending
12
12
  ON sync_tombstones(pushed_at_ms);
13
- `)}function lt(t,e,n){if(typeof e!="string"||e.length===0||e.length>64)return;k(t),t.getDatabase().prepare(`INSERT OR IGNORE INTO sync_tombstones (kind, row_id, deleted_at_ms)
14
- VALUES (?, ?, ?)`).run(e,String(n),Date.now())}class B extends Error{constructor(e){super(e),this.name="CopiedSessionError"}}function J(t){return!!t||process.env.WYRM_ALLOW_COPIED_SESSION==="1"}function K(t,e,n){const o=q(t.machine_fp);if(o.state==="match")return;if(o.state==="adopt"){e(P());return}const s=["","\u26A0 WYRM CLOUD: this session looks COPIED from another machine.","",` Stored machine fingerprint: ${o.stored.slice(0,12)}\u2026`,` This machine's fingerprint: ${o.current.slice(0,12)}\u2026`,""," Both machines now share ONE device_id. The cloud pull query skips a"," device's own deltas, so each machine filters the OTHER's changes out:"," push works but pull silently returns 0 rows (peer memory looks MISSING).",""," FIX \u2014 give this machine its own identity (keep your encryption key):"," rm ~/.wyrm/cloud.json ~/.wyrm/cloud-cursor.json # NOT cloud.key"," wyrm cloud login",""," If this is a legitimate change (e.g. you renamed this host), re-run with:"," wyrm cloud sync --force (or set WYRM_ALLOW_COPIED_SESSION=1)",""];if(J(n)){console.error(s.join(`
15
- `)),console.error(" Proceeding anyway (--force / WYRM_ALLOW_COPIED_SESSION=1); fingerprint updated."),console.error(""),e(o.current);return}throw console.error(s.join(`
16
- `)),new B("Cloud session looks copied from another machine (device_id collision). Re-login or pass --force.")}async function dt(t={}){const e=v();if(!e||!e.device_id)throw new Error("Not logged in or device not registered. Run `wyrm cloud login`.");K(e,a=>{j({...e,machine_fp:a})},t.force);const n=new C(e.base,e.session),o=Y(),s=new L(A());k(s);const r={pushed:0,pulled:0,deleted_local:0,errors:[],quarantined:0},c=t.all?M(s):g,m=Q(s,c,o.last_push_updated_ms,!!t.all,r.errors),u=z(s),l=[...m.map(a=>({kind:a.kind,row_id:String(a.row_id),visibility:a.visibility,payload_b64:T(JSON.stringify(a.row)),updated_at:a.updated_at})),...u.map(a=>({kind:"tombstone",row_id:`${a.kind}:${a.row_id}`,visibility:"org",payload_b64:T(JSON.stringify({kind:a.kind,row_id:a.row_id,deleted_at_ms:a.deleted_at_ms})),updated_at:a.deleted_at_ms}))];if(l.length>0&&!t.dryRun){let f=o.last_push_updated_ms;for(let i=0;i<l.length;i+=25){const d=l.slice(i,i+25);try{const p=await n.syncPush(e.device_id,d);if(r.pushed+=p.accepted,p.rejected>0)for(const _ of p.rejected_details)r.errors.push(`push: ${_.reason} on ${_.id.slice(0,8)}\u2026`);for(const _ of d)_.updated_at&&_.updated_at>f&&(f=_.updated_at)}catch(p){r.errors.push(`push chunk failed: ${p instanceof Error?p.message:p}`)}}o.last_push_updated_ms=f,o.last_push_at=Date.now(),u.length>0&&Z(s,u.map(i=>i.id))}else r.pushed=l.length;if(!t.dryRun){let a=o.pull_cursor,f=0;t:for(;f<50;){let i;try{i=await n.syncPull(e.device_id,a,100)}catch(d){r.errors.push(`pull failed: ${d instanceof Error?d.message:d}`);break}if(i.deltas.length===0)break;for(const d of i.deltas)try{const p=x(d.payload_b64);if(d.kind==="tombstone"){const{kind:_,row_id:w}=JSON.parse(p);et(s,c,_,w)&&r.deleted_local++}else{const _=JSON.parse(p);tt(s,c,d.kind,d.row_id,_),r.pulled++}}catch(p){const _=p instanceof Error?p.message:String(p),w=o.quarantine??={},{entry:h,action:E,newlyQuarantined:b}=F(w[d.id],d,_,Date.now());if(w[d.id]=h,E==="skip"){b&&(r.quarantined=(r.quarantined??0)+1),r.errors.push(`quarantined ${d.kind}:${d.row_id} after ${h.attempts} attempts (${_})`);continue}r.errors.push(`apply ${d.kind}:${d.row_id}: ${_} (attempt ${h.attempts}/${R}, will retry)`);break t}if(a=i.next_cursor,!i.has_more)break;f++}o.pull_cursor=a,o.last_pull_at=Date.now()}return G(o),r}function X(t){if(typeof t!="string"||t.length===0)return 0;const e=t.includes("T")?t:t.replace(" ","T")+"Z",n=Date.parse(e);return Number.isFinite(n)?n:0}function S(t,e,n){try{return t.getDatabase().prepare(`PRAGMA table_info(${e})`).all().some(r=>r.name===n)}catch{return!1}}function Q(t,e,n,o,s){const r=[],c=t.getDatabase(),m=S(t,"projects","sync_policy");for(const u of e)if(y.test(u.table)&&!(!o&&!u.hasVisibility))try{const l=S(t,u.table,"updated_at"),a=u.hasVisibility?"WHERE cross_project_visibility IN ('org', 'public')":"WHERE 1=1",f=m&&S(t,u.table,"project_id")?"AND (project_id IS NULL OR project_id IN (SELECT id FROM projects WHERE sync_policy IN ('cloud', 'team')))":"",i=l?"AND (? = 0 OR updated_at IS NULL OR strftime('%s', updated_at) * 1000 > ?)":"",d=l?"ORDER BY (updated_at IS NULL) ASC, updated_at ASC":"",p=`SELECT * FROM ${u.table} ${a} ${f} ${i} ${d}`,_=c.prepare(p),w=l?_.all(n,n):_.all();for(const h of w){const E=X(h.updated_at);if(n>0&&E>0&&E<=n)continue;const b=u.hasVisibility?String(h.cross_project_visibility??"within"):"org";r.push({kind:u.kind,row_id:h.id??"?",visibility:b,row:h,updated_at:E||Date.now()})}}catch(l){const a=l instanceof Error?l.message:String(l);if(/no such table/i.test(a))continue;s.push(`collect ${u.table}: ${a}`)}return r.sort((u,l)=>(u.updated_at??0)-(l.updated_at??0)),r}function z(t){const e=t.getDatabase();try{return e.prepare(`SELECT id, kind, row_id, deleted_at_ms
13
+ `)}function Ue(e,t,s){if(typeof t!="string"||t.length===0||t.length>64)return;x(e),e.getDatabase().prepare(`INSERT OR IGNORE INTO sync_tombstones (kind, row_id, deleted_at_ms)
14
+ VALUES (?, ?, ?)`).run(t,String(s),Date.now())}class me extends Error{constructor(t){super(t),this.name="CopiedSessionError"}}function he(e){return!!e||process.env.WYRM_ALLOW_COPIED_SESSION==="1"}function ye(e,t,s){const o=ie(e.machine_fp);if(o.state==="match")return;if(o.state==="adopt"){t(ae());return}const n=["","\u26A0 WYRM CLOUD: this session looks COPIED from another machine.","",` Stored machine fingerprint: ${o.stored.slice(0,12)}\u2026`,` This machine's fingerprint: ${o.current.slice(0,12)}\u2026`,""," Both machines now share ONE device_id. The cloud pull query skips a"," device's own deltas, so each machine filters the OTHER's changes out:"," push works but pull silently returns 0 rows (peer memory looks MISSING).",""," FIX: give this machine its own identity (keep your encryption key):"," rm ~/.wyrm/cloud.json ~/.wyrm/cloud-cursor.json # NOT cloud.key"," wyrm cloud login",""," If this is a legitimate change (e.g. you renamed this host), re-run with:"," wyrm cloud sync --force (or set WYRM_ALLOW_COPIED_SESSION=1)",""];if(he(s)){console.error(n.join(`
15
+ `)),console.error(" Proceeding anyway (--force / WYRM_ALLOW_COPIED_SESSION=1); fingerprint updated."),console.error(""),t(o.current);return}throw console.error(n.join(`
16
+ `)),new me("Cloud session looks copied from another machine (device_id collision). Re-login or pass --force.")}async function Me(e={}){const t=ne();if(!t||!t.device_id)throw new Error("Not logged in or device not registered. Run `wyrm cloud login`.");ye(t,n=>{se({...t,machine_fp:n})},e.force);const s=new te(t.base,t.session),o=new Q(B());try{return await Ee(o,s,t.device_id,e)}finally{try{o.close()}catch{}}}async function Ee(e,t,s,o){x(e);const n=e.getDatabase(),r=_e(n),c=Math.max(Number(r.last_pull_at)||0,Number(r.last_push_at)||0),i={pushed:0,pulled:0,deleted_local:0,errors:[],quarantined:0,conflicts:0},a=o.all?de(e):$,l=ge(e,a,r.last_push_updated_ms,!!o.all,i.errors),E=be(e),S=[...l.map(p=>({kind:p.kind,row_id:String(p.row_id),visibility:p.visibility,payload_b64:M(JSON.stringify(p.row)),updated_at:p.updated_at})),...E.map(p=>({kind:"tombstone",row_id:`${p.kind}:${p.row_id}`,visibility:"org",payload_b64:M(JSON.stringify({kind:p.kind,row_id:p.row_id,deleted_at_ms:p.deleted_at_ms})),updated_at:p.deleted_at_ms}))];if(S.length>0&&!o.dryRun){let y=r.last_push_updated_ms;for(let m=0;m<S.length;m+=25){const u=S.slice(m,m+25);try{const _=await t.syncPush(s,u);if(i.pushed+=_.accepted,_.rejected>0)for(const d of _.rejected_details)i.errors.push(`push: ${d.reason} on ${d.id.slice(0,8)}\u2026`);for(const d of u)d.updated_at&&d.updated_at>y&&(y=d.updated_at)}catch(_){i.errors.push(`push chunk failed: ${_ instanceof Error?_.message:_}`)}}r.last_push_updated_ms=y,r.last_push_at=Date.now(),E.length>0&&Se(e,E.map(m=>m.id))}else i.pushed=S.length;if(!o.dryRun){let p=r.pull_cursor,y=0;e:for(;y<50;){let m;try{m=await t.syncPull(s,p,100)}catch(u){i.errors.push(`pull failed: ${u instanceof Error?u.message:u}`);break}if(m.deltas.length===0)break;for(const u of m.deltas)try{const _=re(u.payload_b64);if(u.kind==="tombstone"){const{kind:d,row_id:h}=JSON.parse(_);Re(e,a,d,h)&&i.deleted_local++}else{const d=JSON.parse(_);Ne(e,a,u.kind,u.row_id,d,{remoteOrigin:`cloud:${u.device_id??"unknown"}`,lastSyncMs:c}).conflictId!==void 0&&(i.conflicts=(i.conflicts??0)+1),i.pulled++}}catch(_){const d=_ instanceof Error?_.message:String(_),h=r.quarantine??={},{entry:b,action:R,newlyQuarantined:w}=ce(h[u.id],u,d,Date.now());if(h[u.id]=b,R==="skip"){w&&(i.quarantined=(i.quarantined??0)+1),i.errors.push(`quarantined ${u.kind}:${u.row_id} after ${b.attempts} attempts (${d})`);continue}i.errors.push(`apply ${u.kind}:${u.row_id}: ${d} (attempt ${b.attempts}/${W}, will retry)`);break e}if(p=m.next_cursor,!m.has_more)break;y++}r.pull_cursor=p,r.last_pull_at=Date.now()}return fe(n,r),i}function C(e){if(typeof e!="string"||e.length===0)return 0;const t=e.includes("T")?e:e.replace(" ","T")+"Z",s=Date.parse(t);return Number.isFinite(s)?s:0}function k(e,t,s){try{return e.getDatabase().prepare(`PRAGMA table_info(${t})`).all().some(r=>r.name===s)}catch{return!1}}function ge(e,t,s,o,n){const r=[],c=e.getDatabase(),i=k(e,"projects","sync_policy");for(const a of t)if(g.test(a.table)&&!(!o&&!a.hasVisibility))try{const l=k(e,a.table,"updated_at"),E=a.hasVisibility?"WHERE cross_project_visibility IN ('org', 'public')":"WHERE 1=1",S=i&&k(e,a.table,"project_id")?"AND (project_id IS NULL OR project_id IN (SELECT id FROM projects WHERE sync_policy IN ('cloud', 'team')))":"",p=l?"AND (? = 0 OR updated_at IS NULL OR strftime('%s', updated_at) * 1000 > ?)":"",y=l?"ORDER BY (updated_at IS NULL) ASC, updated_at ASC":"",m=`SELECT * FROM ${a.table} ${E} ${S} ${p} ${y}`,u=c.prepare(m),_=l?u.all(s,s):u.all();for(const d of _){const h=C(d.updated_at);if(s>0&&h>0&&h<=s)continue;const b=a.hasVisibility?String(d.cross_project_visibility??"within"):"org";r.push({kind:a.kind,row_id:d.id??"?",visibility:b,row:d,updated_at:h||Date.now()})}}catch(l){const E=l instanceof Error?l.message:String(l);if(/no such table/i.test(E))continue;n.push(`collect ${a.table}: ${E}`)}return r.sort((a,l)=>(a.updated_at??0)-(l.updated_at??0)),r}function be(e){const t=e.getDatabase();try{return t.prepare(`SELECT id, kind, row_id, deleted_at_ms
17
17
  FROM sync_tombstones
18
18
  WHERE pushed_at_ms IS NULL
19
19
  ORDER BY id ASC
20
- LIMIT 500`).all()}catch{return[]}}function Z(t,e){if(e.length===0)return;const n=t.getDatabase(),o=Date.now(),s=n.prepare("UPDATE sync_tombstones SET pushed_at_ms = ? WHERE id = ?"),r=n.transaction(c=>{for(const m of c)s.run(o,m)});try{r(e)}catch{}}function tt(t,e,n,o,s){const r=e.find(i=>i.kind===n);if(!r)return;if(!y.test(r.table))throw new Error(`apply ${n}: refusing unsafe table name ${r.table}`);const c=V(t,r.table);if(c.size===0)throw new Error(`apply ${n}: ${r.table} has no readable schema`);const m=Object.keys(s).filter(i=>c.has(i)&&y.test(i));if(m.length===0)throw new Error(`apply ${n}: no columns from peer payload matched local schema`);const u=t.getDatabase(),l=m.map(()=>"?").join(","),a=m.join(","),f=m.map(i=>s[i]);try{u.prepare(`INSERT OR REPLACE INTO ${r.table} (${a}) VALUES (${l})`).run(...f)}catch(i){throw new Error(`apply ${n} failed: ${i instanceof Error?i.message:i}`)}}function et(t,e,n,o){const s=e.find(m=>m.kind===n);return!s||!y.test(s.table)?!1:t.getDatabase().prepare(`DELETE FROM ${s.table} WHERE id = ?`).run(o).changes>0}export{B as CopiedSessionError,g as DEFAULT_TABLES,R as QUARANTINE_AFTER,Q as collectChangedRows,K as guardCopiedSession,F as quarantineDecision,lt as recordTombstone,dt as runSync};
20
+ LIMIT 500`).all()}catch{return[]}}function Se(e,t){if(t.length===0)return;const s=e.getDatabase(),o=Date.now(),n=s.prepare("UPDATE sync_tombstones SET pushed_at_ms = ? WHERE id = ?"),r=s.transaction(c=>{for(const i of c)n.run(o,i)});try{r(t)}catch{}}const H=new Set(["updated_at","last_accessed_at","access_count","reuse_count","reuse_success_count","reuse_failure_count","usage_count","last_used","last_validated_at","last_verified_at"]),T=e=>e==null?null:String(e);function q(e,t){const s=t.filter(o=>!H.has(o)).sort().map(o=>[o,T(e[o])]);return K("sha256").update(JSON.stringify(s)).digest("hex")}function D(e,t,s,o,n,r,c){const i=Number(s);if(Number.isSafeInteger(i))try{const a=JSON.stringify(o),l=e.prepare("SELECT id FROM sync_conflicts WHERE resolved_at IS NULL AND source = 'cloud' AND local_kind = ? AND local_id = ? AND remote_payload = ?").get(t,i,a);return l?l.id:Number(e.prepare(`INSERT INTO sync_conflicts (local_kind, local_id, remote_payload, remote_origin, source, winner, local_payload)
21
+ VALUES (?, ?, ?, ?, 'cloud', ?, ?)`).run(t,i,a,c,r,JSON.stringify(n)).lastInsertRowid)}catch{return}}function Ne(e,t,s,o,n,r={remoteOrigin:"cloud",lastSyncMs:0}){const c=t.find(f=>f.kind===s);if(!c)return{action:"skipped"};if(!g.test(c.table))throw new Error(`apply ${s}: refusing unsafe table name ${c.table}`);const i=F(e,c.table);if(i.size===0)throw new Error(`apply ${s}: ${c.table} has no readable schema`);const a=Object.keys(n).filter(f=>i.has(f)&&g.test(f));if(a.length===0)throw new Error(`apply ${s}: no columns from peer payload matched local schema`);const l=e.getDatabase(),E=a.map(()=>"?").join(","),S=a.join(","),p=a.map(f=>n[f]),y=a.filter(f=>f!=="id"),m=y.map(f=>`${f} = ?`).join(", "),u=n.id,_=(f,O)=>{try{return f==="insert"?l.prepare(`INSERT INTO ${c.table} (${S}) VALUES (${E})`).run(...p):y.length>0&&l.prepare(`UPDATE ${c.table} SET ${m} WHERE id = ?`).run(...y.map(N=>n[N]),u),{action:O}}catch(N){const I=we(N)?Te(l,c.table,i,n):void 0;if(!I)throw new Error(`apply ${s} failed: ${N instanceof Error?N.message:N}`);return{action:"kept_local",conflictId:D(l,s,I.id,n,I,"local",r.remoteOrigin)}}},d=!i.has("id")||u===void 0||u===null?void 0:l.prepare(`SELECT * FROM ${c.table} WHERE id = ?`).get(u);if(!d)return _("insert","inserted");if(!a.some(f=>!H.has(f)&&T(d[f])!==T(n[f])))return{action:"identical"};if(P(l,c.table,i,n).some(f=>f.some(O=>T(d[O])!==T(n[O]))))return{action:"kept_local",conflictId:D(l,s,d.id,n,d,"local",r.remoteOrigin)};const h=i.has("updated_at")?C(d.updated_at):0,b=i.has("updated_at")?C(n.updated_at):0;if(h===0||b===0)return _("update","updated");const R=h===b,w=R?q(n,a)>q(d,a):b>h,L=R||!w||r.lastSyncMs>0&&h>r.lastSyncMs?D(l,s,u,n,d,w?"remote":"local",r.remoteOrigin):void 0;if(!w)return{action:"kept_local",conflictId:L};const v=_("update","updated");return v.action==="kept_local"?v:{action:"updated",conflictId:L}}const we=e=>e?.code==="SQLITE_CONSTRAINT_UNIQUE"||/UNIQUE constraint failed/i.test(e instanceof Error?e.message:String(e));function P(e,t,s,o){const n=[];if(!g.test(t))return n;const r=e.prepare(`PRAGMA index_list(${t})`).all();for(const c of r){if(!c.unique||c.origin==="pk"||c.partial||!g.test(c.name))continue;const i=e.prepare(`PRAGMA index_info(${c.name})`).all().map(a=>a.name);i.length===0||i.some(a=>!a||!g.test(a)||!s.has(a)||!(a in o))||n.push(i)}return n}function Te(e,t,s,o){for(const n of P(e,t,s,o)){const r=n.map(i=>`${i} IS ?`).join(" AND "),c=e.prepare(`SELECT * FROM ${t} WHERE ${r}`).get(...n.map(i=>o[i]));if(c)return c}}function Re(e,t,s,o){const n=t.find(a=>a.kind===s);if(!n||!g.test(n.table))return!1;const r=e.getDatabase(),c=F(e,n.table);if(c.has("cross_project_visibility")){const a=c.has("project_id")?r.prepare(`SELECT cross_project_visibility AS vis, project_id FROM ${n.table} WHERE id = ?`).get(o):r.prepare(`SELECT cross_project_visibility AS vis FROM ${n.table} WHERE id = ?`).get(o);if(!a||a.vis!=="org"&&a.vis!=="public")return!1;if(a.project_id!=null&&k(e,"projects","sync_policy")){const l=r.prepare("SELECT sync_policy AS p FROM projects WHERE id = ?").get(a.project_id);if(l?.p&&l.p!=="cloud"&&l.p!=="team")return!1}}return r.prepare(`DELETE FROM ${n.table} WHERE id = ?`).run(o).changes>0}export{me as CopiedSessionError,$ as DEFAULT_TABLES,W as QUARANTINE_AFTER,Ne as applyRow,ge as collectChangedRows,ye as guardCopiedSession,ce as quarantineDecision,Ue as recordTombstone,Me as runSync};
@@ -1,5 +1,5 @@
1
- import{createReadStream as D,createWriteStream as z,statSync as H,existsSync as l,copyFileSync as T,unlinkSync as N,readFileSync as W,writeFileSync as G,renameSync as v}from"fs";import{createGzip as q,gunzipSync as Y}from"zlib";import{pipeline as P}from"stream/promises";import{createCipheriv as F,createDecipheriv as J,randomBytes as O,scryptSync as K,createHash as x,createHmac as A}from"crypto";import{join as C}from"path";import{hostname as V,platform as X,arch as Q,cpus as Z}from"os";const tt="3.2.0",et=1,L="aes-256-gcm",E=16,j=32,R=16,B=32,nt=10;function M(s,t){const o=O(E),e=O(j),c=K(t,e,B),n=F(L,c,o),r=Buffer.concat([n.update(s),n.final()]),a=n.getAuthTag();return Buffer.concat([o,e,a,r])}function _(s,t){const o=E+j+R;if(s.length<o)throw new Error("Encrypted data too short \u2014 corrupted or not a Wyrm backup");let e=0;const c=s.subarray(e,e+=E),n=s.subarray(e,e+=j),r=s.subarray(e,e+=R),a=s.subarray(e),i=K(t,n,B),u=J(L,i,c);u.setAuthTag(r);try{return Buffer.concat([u.update(a),u.final()])}catch{throw new Error("Decryption failed \u2014 wrong password or corrupted backup")}}class st{accessKeyId;secretAccessKey;region;endpoint;bucket;host;constructor(t){this.accessKeyId=t.accessKeyId,this.secretAccessKey=t.secretAccessKey,this.region=t.region??"auto",this.endpoint=t.endpoint.replace(/\/$/,""),this.bucket=t.bucket;const o=new URL(this.endpoint);this.host=o.host}async putObject(t,o,e="application/octet-stream"){const c=`/${this.bucket}/${I(t)}`,n={"Content-Type":e,"Content-Length":o.length.toString()},r=this.signRequest("PUT",c,n,o),a=await fetch(`${this.endpoint}${c}`,{method:"PUT",headers:r,body:o});if(!a.ok){const i=await a.text();throw new Error(`S3 PUT failed (${a.status}): ${i}`)}}async getObject(t){const o=`/${this.bucket}/${I(t)}`,e={},c=this.signRequest("GET",o,e),n=await fetch(`${this.endpoint}${o}`,{method:"GET",headers:c});if(!n.ok){const a=await n.text();throw new Error(`S3 GET failed (${n.status}): ${a}`)}const r=await n.arrayBuffer();return Buffer.from(r)}async listObjects(t){const o=[];let e;do{const c=new URLSearchParams({"list-type":"2",prefix:t});e&&c.set("continuation-token",e);const n=`/${this.bucket}?${c.toString()}`,r={},a=this.signRequest("GET",n,r),i=await fetch(`${this.endpoint}${n}`,{method:"GET",headers:a});if(!i.ok){const h=await i.text();throw new Error(`S3 LIST failed (${i.status}): ${h}`)}const u=await i.text();o.push(...ht(u)),u.match(/<IsTruncated>(.*?)<\/IsTruncated>/)?.[1]==="true"?e=u.match(/<NextContinuationToken>(.*?)<\/NextContinuationToken>/)?.[1]:e=void 0}while(e);return o}async deleteObject(t){const o=`/${this.bucket}/${I(t)}`,e={},c=this.signRequest("DELETE",o,e),n=await fetch(`${this.endpoint}${o}`,{method:"DELETE",headers:c});if(!n.ok){const r=await n.text();throw new Error(`S3 DELETE failed (${n.status}): ${r}`)}}signRequest(t,o,e,c){const n=new Date,r=ot(n),a=ct(n),[i,u]=it(o);e.host=this.host,e["x-amz-date"]=a,e["x-amz-content-sha256"]=k(c??Buffer.alloc(0));const d=Object.keys(e).map(w=>w.toLowerCase()).sort(),m=d.map(w=>`${w}:${e[Object.keys(e).find(U=>U.toLowerCase()===w)].trim()}`).join(`
1
+ import{createReadStream as N,createWriteStream as H,statSync as W,existsSync as $,unlinkSync as v,readFileSync as G,writeFileSync as q}from"fs";import{snapshotToFile as P,snapshotBeforeRestore as Y,restoreFileInto as F,ensurePrivateDir as C,defaultBackupDir as I}from"./backup.js";import{createGzip as Q,gunzipSync as J}from"zlib";import{pipeline as V}from"stream/promises";import{createCipheriv as X,createDecipheriv as Z,randomBytes as O,scryptSync as x,createHash as K,createHmac as A}from"crypto";import{join as f}from"path";import{hostname as tt,platform as et,arch as st,cpus as nt}from"os";const ot="3.2.0",rt=1,M="aes-256-gcm",j=16,T=32,R=16,B=32,ct=10,L=Buffer.from("SQLite format 3\0","binary");function _(c,t){const r=O(j),e=O(T),n=x(t,e,B),s=X(M,n,r),o=Buffer.concat([s.update(c),s.final()]),a=s.getAuthTag();return Buffer.concat([r,e,a,o])}function D(c,t){const r=j+T+R;if(c.length<r)throw new Error("Encrypted data too short: corrupted or not a Wyrm backup");let e=0;const n=c.subarray(e,e+=j),s=c.subarray(e,e+=T),o=c.subarray(e,e+=R),a=c.subarray(e),i=x(t,s,B),u=Z(M,i,n);u.setAuthTag(o);try{return Buffer.concat([u.update(a),u.final()])}catch{throw new Error("Decryption failed: wrong password or corrupted backup")}}class at{accessKeyId;secretAccessKey;region;endpoint;bucket;host;constructor(t){this.accessKeyId=t.accessKeyId,this.secretAccessKey=t.secretAccessKey,this.region=t.region??"auto",this.endpoint=t.endpoint.replace(/\/$/,""),this.bucket=t.bucket;const r=new URL(this.endpoint);this.host=r.host}async putObject(t,r,e="application/octet-stream"){const n=`/${this.bucket}/${E(t)}`,s={"Content-Type":e,"Content-Length":r.length.toString()},o=this.signRequest("PUT",n,s,r),a=await fetch(`${this.endpoint}${n}`,{method:"PUT",headers:o,body:r});if(!a.ok){const i=await a.text();throw new Error(`S3 PUT failed (${a.status}): ${i}`)}}async getObject(t){const r=`/${this.bucket}/${E(t)}`,e={},n=this.signRequest("GET",r,e),s=await fetch(`${this.endpoint}${r}`,{method:"GET",headers:n});if(!s.ok){const a=await s.text();throw new Error(`S3 GET failed (${s.status}): ${a}`)}const o=await s.arrayBuffer();return Buffer.from(o)}async listObjects(t){const r=[];let e;do{const n=new URLSearchParams({"list-type":"2",prefix:t});e&&n.set("continuation-token",e);const s=`/${this.bucket}?${n.toString()}`,o={},a=this.signRequest("GET",s,o),i=await fetch(`${this.endpoint}${s}`,{method:"GET",headers:a});if(!i.ok){const h=await i.text();throw new Error(`S3 LIST failed (${i.status}): ${h}`)}const u=await i.text();r.push(...mt(u)),u.match(/<IsTruncated>(.*?)<\/IsTruncated>/)?.[1]==="true"?e=u.match(/<NextContinuationToken>(.*?)<\/NextContinuationToken>/)?.[1]:e=void 0}while(e);return r}async deleteObject(t){const r=`/${this.bucket}/${E(t)}`,e={},n=this.signRequest("DELETE",r,e),s=await fetch(`${this.endpoint}${r}`,{method:"DELETE",headers:n});if(!s.ok){const o=await s.text();throw new Error(`S3 DELETE failed (${s.status}): ${o}`)}}signRequest(t,r,e,n){const s=new Date,o=it(s),a=ut(s),[i,u]=pt(r);e.host=this.host,e["x-amz-date"]=a,e["x-amz-content-sha256"]=g(n??Buffer.alloc(0));const d=Object.keys(e).map(w=>w.toLowerCase()).sort(),l=d.map(w=>`${w}:${e[Object.keys(e).find(U=>U.toLowerCase()===w)].trim()}`).join(`
2
2
  `)+`
3
- `,h=d.join(";"),p=[t,i,ut(u),m,h,e["x-amz-content-sha256"]].join(`
4
- `),f=`${r}/${this.region}/s3/aws4_request`,y=["AWS4-HMAC-SHA256",a,f,k(Buffer.from(p,"utf-8"))].join(`
5
- `),$=at(this.secretAccessKey,r,this.region,"s3"),g=rt($,y);return e.Authorization=`AWS4-HMAC-SHA256 Credential=${this.accessKeyId}/${f}, SignedHeaders=${h}, Signature=${g}`,e}}function ot(s){return s.toISOString().replace(/[-:T]/g,"").slice(0,8)}function ct(s){return s.toISOString().replace(/[-:]/g,"").replace(/\.\d{3}/,"")}function k(s){return x("sha256").update(s).digest("hex")}function b(s,t){return A("sha256",s).update(t,"utf-8").digest()}function rt(s,t){return A("sha256",s).update(t,"utf-8").digest("hex")}function at(s,t,o,e){let c=b(`AWS4${s}`,t);return c=b(c,o),c=b(c,e),c=b(c,"aws4_request"),c}function I(s){return s.split("/").map(t=>encodeURIComponent(t)).join("/")}function it(s){const t=s.indexOf("?");return t===-1?[s,""]:[s.slice(0,t),s.slice(t+1)]}function ut(s){return s?s.split("&").map(t=>{const[o,e]=t.split("=");return`${encodeURIComponent(decodeURIComponent(o))}=${encodeURIComponent(decodeURIComponent(e??""))}`}).sort().join("&"):""}function ht(s){const t=[],o=/<Contents>([\s\S]*?)<\/Contents>/g;let e;for(;(e=o.exec(s))!==null;){const c=e[1],n=c.match(/<Key>(.*?)<\/Key>/)?.[1]??"",r=parseInt(c.match(/<Size>(.*?)<\/Size>/)?.[1]??"0",10),a=c.match(/<LastModified>(.*?)<\/LastModified>/)?.[1]??"";t.push({key:n,size:r,lastModified:a})}return t}class wt{config=null;encryptionKey=null;s3=null;constructor(){}configure(t,o){if(!t.endpoint||!t.bucket||!t.accessKeyId||!t.secretAccessKey)throw new Error("Cloud config requires endpoint, bucket, accessKeyId, and secretAccessKey");this.config={...t,region:t.region??"auto"},this.encryptionKey=o??null,this.s3=new st(this.config)}isConfigured(){return this.config!==null&&this.s3!==null}async backup(t){if(this.assertConfigured(),!l(t))throw new Error(`Database not found: ${t}`);const o=H(t),e=this.getMachineId(),c=new Date,n=dt(c),r=`backups/${e}/${n}.wyrm.bak`,a=`backups/${e}/${n}.wyrm.meta.json`;if(this.encryptionKey===null&&process.env.WYRM_ALLOW_PLAINTEXT_BACKUP!=="1")throw new Error("Refusing to upload an unencrypted full-DB backup to cloud storage. Set WYRM_ENCRYPTION_KEY to encrypt the snapshot, or WYRM_ALLOW_PLAINTEXT_BACKUP=1 to override (NOT recommended \u2014 uploads all private rows in cleartext).");const i=C(t,".."),u=C(i,`.wyrm-backup-${Date.now()}.tmp`),d=C(i,`.wyrm-backup-${Date.now()}.gz.tmp`);try{T(t,u);const m=`${t}-wal`,h=`${t}-shm`;l(m)&&T(m,`${u}-wal`),l(h)&&T(h,`${u}-shm`),await P(D(u),q({level:9}),z(d));let p=Buffer.from(W(d));const f=p.length,y=this.encryptionKey!==null;y&&(p=M(p,this.encryptionKey));const $=k(p);await this.s3.putObject(r,p,"application/x-wyrm-backup");const g={version:et,wyrm_version:tt,timestamp:c.toISOString(),db_size:o.size,compressed_size:f,encrypted:y,machine_id:e,checksum:$};return await this.s3.putObject(a,Buffer.from(JSON.stringify(g,null,2),"utf-8"),"application/json"),{key:r,metadata:g}}finally{S(u),S(d),S(`${u}-wal`),S(`${u}-shm`)}}async listBackups(){this.assertConfigured();const o=`backups/${this.getMachineId()}/`,c=(await this.s3.listObjects(o)).filter(r=>r.key.endsWith(".wyrm.meta.json")),n=[];for(const r of c)try{const a=await this.s3.getObject(r.key),i=JSON.parse(a.toString("utf-8"));n.push(i)}catch{}return n.sort((r,a)=>new Date(a.timestamp).getTime()-new Date(r.timestamp).getTime()),n}async listBackupsWithKeys(){this.assertConfigured();const o=`backups/${this.getMachineId()}/`,c=(await this.s3.listObjects(o)).filter(r=>r.key.endsWith(".wyrm.meta.json")),n=[];for(const r of c)try{const a=await this.s3.getObject(r.key),i=JSON.parse(a.toString("utf-8")),u=r.key.replace(/\.wyrm\.meta\.json$/,".wyrm.bak");n.push({key:u,metadata:i})}catch{}return n.sort((r,a)=>new Date(a.metadata.timestamp).getTime()-new Date(r.metadata.timestamp).getTime()),n}async restore(t,o){this.assertConfigured();const e=t.replace(/\.wyrm\.bak$/,".wyrm.meta.json");let c;try{const i=await this.s3.getObject(e);c=JSON.parse(i.toString("utf-8"))}catch{throw new Error(`Backup metadata not found for key: ${t}`)}let n=await this.s3.getObject(t);const r=k(n);if(r!==c.checksum)throw new Error(`Checksum mismatch \u2014 backup may be corrupted. Expected: ${c.checksum}, Got: ${r}`);if(c.encrypted){if(!this.encryptionKey)throw new Error("Backup is encrypted but no encryption key is configured");n=_(n,this.encryptionKey)}const a=Y(n);if(l(o)){const i=`${o}.bak`;v(o,i)}return G(o,a),{restored:!0,size:a.length}}async pruneBackups(t=nt){this.assertConfigured();const e=`backups/${this.getMachineId()}/`,n=(await this.s3.listObjects(e)).filter(i=>i.key.endsWith(".wyrm.bak")).sort((i,u)=>u.lastModified.localeCompare(i.lastModified));if(n.length<=t)return{deleted:0};const r=n.slice(t);let a=0;for(const i of r){const u=i.key.replace(/\.wyrm\.bak$/,".wyrm.meta.json");try{await this.s3.deleteObject(i.key),a++}catch{}try{await this.s3.deleteObject(u)}catch{}}return{deleted:a}}getMachineId(){const t=[V(),X(),Q(),Z()[0]?.model??"unknown-cpu"].join("|");return x("sha256").update(t).digest("hex").slice(0,16)}assertConfigured(){if(!this.isConfigured())throw new Error("Cloud backup not configured. Call configure() with R2 credentials first.")}}function dt(s){const t=s.getUTCFullYear(),o=String(s.getUTCMonth()+1).padStart(2,"0"),e=String(s.getUTCDate()).padStart(2,"0"),c=String(s.getUTCHours()).padStart(2,"0"),n=String(s.getUTCMinutes()).padStart(2,"0"),r=String(s.getUTCSeconds()).padStart(2,"0");return`${t}-${o}-${e}_${c}${n}${r}`}function S(s){try{l(s)&&N(s)}catch{}}export{wt as WyrmCloudBackup,_ as decryptBuffer,M as encryptBuffer};
3
+ `,h=d.join(";"),k=[t,i,lt(u),l,h,e["x-amz-content-sha256"]].join(`
4
+ `),m=`${o}/${this.region}/s3/aws4_request`,S=["AWS4-HMAC-SHA256",a,m,g(Buffer.from(k,"utf-8"))].join(`
5
+ `),y=ht(this.secretAccessKey,o,this.region,"s3"),z=dt(y,S);return e.Authorization=`AWS4-HMAC-SHA256 Credential=${this.accessKeyId}/${m}, SignedHeaders=${h}, Signature=${z}`,e}}function it(c){return c.toISOString().replace(/[-:T]/g,"").slice(0,8)}function ut(c){return c.toISOString().replace(/[-:]/g,"").replace(/\.\d{3}/,"")}function g(c){return K("sha256").update(c).digest("hex")}function b(c,t){return A("sha256",c).update(t,"utf-8").digest()}function dt(c,t){return A("sha256",c).update(t,"utf-8").digest("hex")}function ht(c,t,r,e){let n=b(`AWS4${c}`,t);return n=b(n,r),n=b(n,e),n=b(n,"aws4_request"),n}function E(c){return c.split("/").map(t=>encodeURIComponent(t)).join("/")}function pt(c){const t=c.indexOf("?");return t===-1?[c,""]:[c.slice(0,t),c.slice(t+1)]}function lt(c){return c?c.split("&").map(t=>{const[r,e]=t.split("=");return`${encodeURIComponent(decodeURIComponent(r))}=${encodeURIComponent(decodeURIComponent(e??""))}`}).sort().join("&"):""}function mt(c){const t=[],r=/<Contents>([\s\S]*?)<\/Contents>/g;let e;for(;(e=r.exec(c))!==null;){const n=e[1],s=n.match(/<Key>(.*?)<\/Key>/)?.[1]??"",o=parseInt(n.match(/<Size>(.*?)<\/Size>/)?.[1]??"0",10),a=n.match(/<LastModified>(.*?)<\/LastModified>/)?.[1]??"";t.push({key:s,size:o,lastModified:a})}return t}class jt{config=null;encryptionKey=null;s3=null;constructor(){}configure(t,r){if(!t.endpoint||!t.bucket||!t.accessKeyId||!t.secretAccessKey)throw new Error("Cloud config requires endpoint, bucket, accessKeyId, and secretAccessKey");this.config={...t,region:t.region??"auto"},this.encryptionKey=r??null,this.s3=new at(this.config)}isConfigured(){return this.config!==null&&this.s3!==null}async backup(t){if(this.assertConfigured(),!$(t))throw new Error(`Database not found: ${t}`);const r=W(t),e=this.getMachineId(),n=new Date,s=ft(n),o=`backups/${e}/${s}.wyrm.bak`,a=`backups/${e}/${s}.wyrm.meta.json`;if(this.encryptionKey===null&&process.env.WYRM_ALLOW_PLAINTEXT_BACKUP!=="1")throw new Error("Refusing to upload an unencrypted full-DB backup to cloud storage. Set WYRM_ENCRYPTION_KEY to encrypt the snapshot, or WYRM_ALLOW_PLAINTEXT_BACKUP=1 to override (NOT recommended: uploads all private rows in cleartext).");const i=C(f(I(t),".work")),u=`${Date.now()}-${process.pid}`,d=f(i,`.wyrm-cloud-${u}.db`),l=f(i,`.wyrm-cloud-${u}.gz`);try{await P(t,d),await V(N(d),Q({level:9}),H(l,{mode:384}));let h=Buffer.from(G(l));const k=h.length,m=this.encryptionKey!==null;m&&(h=_(h,this.encryptionKey));const S=g(h);await this.s3.putObject(o,h,"application/x-wyrm-backup");const y={version:rt,wyrm_version:ot,timestamp:n.toISOString(),db_size:r.size,compressed_size:k,encrypted:m,machine_id:e,checksum:S};return await this.s3.putObject(a,Buffer.from(JSON.stringify(y,null,2),"utf-8"),"application/json"),{key:o,metadata:y}}finally{p(d),p(l),p(`${d}-wal`),p(`${d}-shm`),p(`${d}-journal`)}}async listBackups(){this.assertConfigured();const r=`backups/${this.getMachineId()}/`,n=(await this.s3.listObjects(r)).filter(o=>o.key.endsWith(".wyrm.meta.json")),s=[];for(const o of n)try{const a=await this.s3.getObject(o.key),i=JSON.parse(a.toString("utf-8"));s.push(i)}catch{}return s.sort((o,a)=>new Date(a.timestamp).getTime()-new Date(o.timestamp).getTime()),s}async listBackupsWithKeys(){this.assertConfigured();const r=`backups/${this.getMachineId()}/`,n=(await this.s3.listObjects(r)).filter(o=>o.key.endsWith(".wyrm.meta.json")),s=[];for(const o of n)try{const a=await this.s3.getObject(o.key),i=JSON.parse(a.toString("utf-8")),u=o.key.replace(/\.wyrm\.meta\.json$/,".wyrm.bak");s.push({key:u,metadata:i})}catch{}return s.sort((o,a)=>new Date(a.metadata.timestamp).getTime()-new Date(o.metadata.timestamp).getTime()),s}async restore(t,r){this.assertConfigured();const e=t.replace(/\.wyrm\.bak$/,".wyrm.meta.json");let n;try{const d=await this.s3.getObject(e);n=JSON.parse(d.toString("utf-8"))}catch{throw new Error(`Backup metadata not found for key: ${t}`)}let s=await this.s3.getObject(t);const o=g(s);if(o!==n.checksum)throw new Error(`Checksum mismatch: backup may be corrupted. Expected: ${n.checksum}, Got: ${o}`);if(n.encrypted){if(!this.encryptionKey)throw new Error("Backup is encrypted but no encryption key is configured");s=D(s,this.encryptionKey)}const a=J(s);if(!a.subarray(0,L.length).equals(L))throw new Error("Downloaded backup is not a SQLite database; nothing was changed");const i=C(f(I(r),".work")),u=f(i,`.wyrm-cloud-restore-${Date.now()}-${process.pid}.db`);try{q(u,a,{mode:384}),$(r)&&await Y(r);const d=await F(u,r);if(d!=="ok")throw new Error(`Restored store failed integrity_check (${d}); the previous store is kept as a pre-restore snapshot`)}finally{p(u),p(`${u}-wal`),p(`${u}-shm`),p(`${u}-journal`)}return{restored:!0,size:a.length}}async pruneBackups(t=ct){this.assertConfigured();const e=`backups/${this.getMachineId()}/`,s=(await this.s3.listObjects(e)).filter(i=>i.key.endsWith(".wyrm.bak")).sort((i,u)=>u.lastModified.localeCompare(i.lastModified));if(s.length<=t)return{deleted:0};const o=s.slice(t);let a=0;for(const i of o){const u=i.key.replace(/\.wyrm\.bak$/,".wyrm.meta.json");try{await this.s3.deleteObject(i.key),a++}catch{}try{await this.s3.deleteObject(u)}catch{}}return{deleted:a}}machineId(){return this.getMachineId()}async listSnapshotPrefixes(){this.assertConfigured();const t=this.getMachineId(),r=new Map;for(const e of await this.s3.listObjects("backups/")){const n=/^backups\/([^/]+)\//.exec(e.key);if(!n)continue;const s=n[1],o=r.get(s)??{prefix:s,current:s===t,objects:0,snapshots:0,bytes:0,newest:null};o.objects++,o.bytes+=e.size,e.key.endsWith(".wyrm.bak")&&o.snapshots++,(!o.newest||e.lastModified>o.newest)&&(o.newest=e.lastModified),r.set(s,o)}return[...r.values()].sort((e,n)=>Number(n.current)-Number(e.current)||(n.newest??"").localeCompare(e.newest??""))}async pruneSnapshotPrefix(t,r={}){if(this.assertConfigured(),!/^[A-Za-z0-9_-]{1,64}$/.test(t))throw new Error("invalid snapshot prefix");if(t===this.getMachineId())throw new Error("refusing to prune this machine's own snapshot prefix; rotation (pruneBackups) manages it");const e=await this.s3.listObjects(`backups/${t}/`),n=e.reduce((a,i)=>a+i.size,0);let s=0,o=0;if(r.dryRun!==!1)return{prefix:t,objects:e.length,bytes:n,deleted:s,failed:o};for(const a of e)try{await this.s3.deleteObject(a.key),s++}catch{o++}return{prefix:t,objects:e.length,bytes:n,deleted:s,failed:o}}getMachineId(){const t=[tt(),et(),st(),nt()[0]?.model??"unknown-cpu"].join("|");return K("sha256").update(t).digest("hex").slice(0,16)}assertConfigured(){if(!this.isConfigured())throw new Error("Cloud backup not configured. Call configure() with R2 credentials first.")}}function ft(c){const t=c.getUTCFullYear(),r=String(c.getUTCMonth()+1).padStart(2,"0"),e=String(c.getUTCDate()).padStart(2,"0"),n=String(c.getUTCHours()).padStart(2,"0"),s=String(c.getUTCMinutes()).padStart(2,"0"),o=String(c.getUTCSeconds()).padStart(2,"0");return`${t}-${r}-${e}_${n}${s}${o}`}function p(c){try{$(c)&&v(c)}catch{}}export{jt as WyrmCloudBackup,D as decryptBuffer,_ as encryptBuffer};
@@ -1 +1 @@
1
- import{WyrmCloudBackup as r}from"./cloud-backup.js";import{CloudSyncDaemon as t,DEFAULT_INTERVAL_MS as _,DEFAULT_KEEP_COUNT as p,DEFAULT_DB_PATH as R}from"./cloud-sync.js";async function E(){const e=Number(process.argv[2])||_,s=Number(process.argv[3])||p,o=new r;process.env.WYRM_R2_ENDPOINT&&process.env.WYRM_R2_ACCESS_KEY&&process.env.WYRM_R2_SECRET_KEY?o.configure({endpoint:process.env.WYRM_R2_ENDPOINT,bucket:process.env.WYRM_R2_BUCKET||"wyrm-backups",accessKeyId:process.env.WYRM_R2_ACCESS_KEY,secretAccessKey:process.env.WYRM_R2_SECRET_KEY},process.env.WYRM_ENCRYPTION_KEY):(console.error("[cloud-sync-entrypoint] WYRM_R2_* env vars unset \u2014 refusing to start"),process.exit(2));const n=process.env.WYRM_DB_PATH||R,c=new t(o,n,e,s);console.log(`[cloud-sync] started \xB7 interval=${e}ms \xB7 keep=${s} \xB7 db=${n}`),await c.run()}E().catch(e=>{console.error("[cloud-sync-entrypoint] fatal:",e),process.exit(1)});
1
+ import{WyrmCloudBackup as r}from"./cloud-backup.js";import{CloudSyncDaemon as t,DEFAULT_INTERVAL_MS as _,DEFAULT_KEEP_COUNT as p,DEFAULT_DB_PATH as R}from"./cloud-sync.js";async function E(){const e=Number(process.argv[2])||_,s=Number(process.argv[3])||p,o=new r;process.env.WYRM_R2_ENDPOINT&&process.env.WYRM_R2_ACCESS_KEY&&process.env.WYRM_R2_SECRET_KEY?o.configure({endpoint:process.env.WYRM_R2_ENDPOINT,bucket:process.env.WYRM_R2_BUCKET||"wyrm-backups",accessKeyId:process.env.WYRM_R2_ACCESS_KEY,secretAccessKey:process.env.WYRM_R2_SECRET_KEY},process.env.WYRM_ENCRYPTION_KEY):(console.error("[cloud-sync-entrypoint] WYRM_R2_* env vars unset; refusing to start"),process.exit(2));const n=process.env.WYRM_DB_PATH||R,c=new t(o,n,e,s);console.log(`[cloud-sync] started \xB7 interval=${e}ms \xB7 keep=${s} \xB7 db=${n}`),await c.run()}E().catch(e=>{console.error("[cloud-sync-entrypoint] fatal:",e),process.exit(1)});
@@ -1,2 +1,2 @@
1
- import{spawn as D}from"child_process";import{existsSync as a,readFileSync as p,writeFileSync as k,statSync as g,mkdirSync as N,openSync as E,closeSync as b,unlinkSync as v,renameSync as A}from"fs";import{createHash as L}from"crypto";import{homedir as w,hostname as I}from"os";import{join as u}from"path";const d=u(w(),".wyrm"),h=u(d,"wyrm-cloud-sync.pid"),o=u(d,"wyrm-cloud-sync.log"),f=u(d,"wyrm-cloud-sync.state"),P=u(w(),".copilot","session-store.db"),y=600*1e3,S=20,O=6e4,C=1e6,F=0;class Y{cloud;dbPath;intervalMs;keepCount;constructor(t,e=P,n=y,r=S){this.cloud=t,this.dbPath=e,this.intervalMs=n,this.keepCount=r,_()}async bootstrap(){if(!this.cloud.isConfigured())return{action:"no-config",reason:"WYRM_R2_* env vars unset"};let t;try{t=await this.cloud.listBackupsWithKeys()}catch(i){return{action:"no-remote",reason:i.message??String(i)}}if(t.length===0)return{action:"no-op",reason:"no remote snapshots yet"};const e=t[0],n=Date.parse(e.metadata.timestamp),r=a(this.dbPath)?g(this.dbPath).mtimeMs:0;return n>r+O?(await this.cloud.restore(e.key,this.dbPath),this.patchState({last_restored_key:e.key}),{action:"restored",key:e.key}):{action:"no-op",reason:"local DB is current"}}async tick(){if(!this.cloud.isConfigured())return{action:"error",reason:"WYRM_R2_* env vars unset"};if(!a(this.dbPath))return{action:"no-db"};const t=T(this.dbPath),e=this.readState();if(this.patchState({last_check_ts:Date.now()}),t===e.last_uploaded_hash)return{action:"unchanged",hash:t};try{const{key:n}=await this.cloud.backup(this.dbPath);return this.patchState({last_uploaded_hash:t,last_uploaded_ts:Date.now(),last_uploaded_key:n}),await this.cloud.pruneBackups(this.keepCount),{action:"uploaded",key:n,hash:t}}catch(n){return{action:"error",reason:n.message??String(n)}}}async run(){const t=await this.bootstrap();c(`bootstrap: ${JSON.stringify(t)}`);const e=setInterval(async()=>{try{const r=await this.tick();r.action!=="unchanged"&&c(`tick: ${JSON.stringify(r)}`)}catch(r){c(`tick error: ${r.message??r}`)}},this.intervalMs),n=async r=>{clearInterval(e),c(`${r} \u2014 final tick before exit`);try{const i=await this.tick();c(`final tick: ${JSON.stringify(i)}`)}catch(i){c(`final tick error: ${i.message??i}`)}process.exit(0)};process.on("SIGTERM",()=>{n("SIGTERM")}),process.on("SIGINT",()=>{n("SIGINT")})}readState(){return m()}patchState(t){x(t)}}class z{daemonEntrypoint;constructor(t){this.daemonEntrypoint=t,_()}start(t={}){if(this.isAlive())return{ok:!1,pid:this.readPid(),reason:"already running"};$();const e=(t.interval_minutes??y/6e4)*6e4,n=t.keep_count??S,r=E(o,"a"),i=E(o,"a"),l=D(process.execPath,[this.daemonEntrypoint,String(e),String(n)],{detached:!0,stdio:["ignore",r,i],env:{...process.env}});return l.unref(),b(r),b(i),typeof l.pid!="number"?{ok:!1,reason:"spawn returned no pid"}:(k(h,String(l.pid)),{ok:!0,pid:l.pid})}stop(){if(!this.isAlive())return this.cleanPid(),{ok:!1,reason:"not running"};const t=this.readPid();if(!t)return{ok:!1,reason:"pid file unreadable"};try{return process.kill(t,"SIGTERM"),{ok:!0}}catch(e){return{ok:!1,reason:e.message??String(e)}}}status(){const t=this.isAlive();return{running:t,pid:t?this.readPid()??void 0:void 0,state:a(f)?R():void 0}}async forceSync(t){return t.tick()}isAlive(){if(!a(h))return!1;const t=this.readPid();if(!t)return!1;try{return process.kill(t,F),!0}catch{return!1}}readPid(){try{const t=p(h,"utf8").trim(),e=Number(t);return Number.isFinite(e)&&e>0?e:void 0}catch{return}}cleanPid(){try{v(h)}catch{}}}function _(){a(d)||N(d,{recursive:!0})}function T(s){return L("sha256").update(p(s)).digest("hex")}function M(){return{last_uploaded_hash:"",last_uploaded_ts:0,last_uploaded_key:null,last_restored_key:null,last_check_ts:0,machine:I()}}function m(){return R()??M()}function R(){try{const s=JSON.parse(p(f,"utf8"));return{...M(),...s}}catch{return}}function x(s){const t=m();k(f,JSON.stringify({...t,...s,machine:I()},null,2))}function c(s){const t=`[${new Date().toISOString()}] ${s}
1
+ import{spawn as D}from"child_process";import{existsSync as a,readFileSync as p,writeFileSync as k,statSync as g,mkdirSync as N,openSync as E,closeSync as b,unlinkSync as v,renameSync as A}from"fs";import{createHash as L}from"crypto";import{homedir as w,hostname as I}from"os";import{join as u}from"path";const d=u(w(),".wyrm"),h=u(d,"wyrm-cloud-sync.pid"),o=u(d,"wyrm-cloud-sync.log"),f=u(d,"wyrm-cloud-sync.state"),P=u(w(),".copilot","session-store.db"),y=600*1e3,S=20,O=6e4,C=1e6,F=0;class Y{cloud;dbPath;intervalMs;keepCount;constructor(t,e=P,n=y,r=S){this.cloud=t,this.dbPath=e,this.intervalMs=n,this.keepCount=r,_()}async bootstrap(){if(!this.cloud.isConfigured())return{action:"no-config",reason:"WYRM_R2_* env vars unset"};let t;try{t=await this.cloud.listBackupsWithKeys()}catch(i){return{action:"no-remote",reason:i.message??String(i)}}if(t.length===0)return{action:"no-op",reason:"no remote snapshots yet"};const e=t[0],n=Date.parse(e.metadata.timestamp),r=a(this.dbPath)?g(this.dbPath).mtimeMs:0;return n>r+O?(await this.cloud.restore(e.key,this.dbPath),this.patchState({last_restored_key:e.key}),{action:"restored",key:e.key}):{action:"no-op",reason:"local DB is current"}}async tick(){if(!this.cloud.isConfigured())return{action:"error",reason:"WYRM_R2_* env vars unset"};if(!a(this.dbPath))return{action:"no-db"};const t=T(this.dbPath),e=this.readState();if(this.patchState({last_check_ts:Date.now()}),t===e.last_uploaded_hash)return{action:"unchanged",hash:t};try{const{key:n}=await this.cloud.backup(this.dbPath);return this.patchState({last_uploaded_hash:t,last_uploaded_ts:Date.now(),last_uploaded_key:n}),await this.cloud.pruneBackups(this.keepCount),{action:"uploaded",key:n,hash:t}}catch(n){return{action:"error",reason:n.message??String(n)}}}async run(){const t=await this.bootstrap();c(`bootstrap: ${JSON.stringify(t)}`);const e=setInterval(async()=>{try{const r=await this.tick();r.action!=="unchanged"&&c(`tick: ${JSON.stringify(r)}`)}catch(r){c(`tick error: ${r.message??r}`)}},this.intervalMs),n=async r=>{clearInterval(e),c(`${r}: final tick before exit`);try{const i=await this.tick();c(`final tick: ${JSON.stringify(i)}`)}catch(i){c(`final tick error: ${i.message??i}`)}process.exit(0)};process.on("SIGTERM",()=>{n("SIGTERM")}),process.on("SIGINT",()=>{n("SIGINT")})}readState(){return m()}patchState(t){x(t)}}class z{daemonEntrypoint;constructor(t){this.daemonEntrypoint=t,_()}start(t={}){if(this.isAlive())return{ok:!1,pid:this.readPid(),reason:"already running"};$();const e=(t.interval_minutes??y/6e4)*6e4,n=t.keep_count??S,r=E(o,"a"),i=E(o,"a"),l=D(process.execPath,[this.daemonEntrypoint,String(e),String(n)],{detached:!0,stdio:["ignore",r,i],env:{...process.env}});return l.unref(),b(r),b(i),typeof l.pid!="number"?{ok:!1,reason:"spawn returned no pid"}:(k(h,String(l.pid)),{ok:!0,pid:l.pid})}stop(){if(!this.isAlive())return this.cleanPid(),{ok:!1,reason:"not running"};const t=this.readPid();if(!t)return{ok:!1,reason:"pid file unreadable"};try{return process.kill(t,"SIGTERM"),{ok:!0}}catch(e){return{ok:!1,reason:e.message??String(e)}}}status(){const t=this.isAlive();return{running:t,pid:t?this.readPid()??void 0:void 0,state:a(f)?R():void 0}}async forceSync(t){return t.tick()}isAlive(){if(!a(h))return!1;const t=this.readPid();if(!t)return!1;try{return process.kill(t,F),!0}catch{return!1}}readPid(){try{const t=p(h,"utf8").trim(),e=Number(t);return Number.isFinite(e)&&e>0?e:void 0}catch{return}}cleanPid(){try{v(h)}catch{}}}function _(){a(d)||N(d,{recursive:!0})}function T(s){return L("sha256").update(p(s)).digest("hex")}function M(){return{last_uploaded_hash:"",last_uploaded_ts:0,last_uploaded_key:null,last_restored_key:null,last_check_ts:0,machine:I()}}function m(){return R()??M()}function R(){try{const s=JSON.parse(p(f,"utf8"));return{...M(),...s}}catch{return}}function x(s){const t=m();k(f,JSON.stringify({...t,...s,machine:I()},null,2))}function c(s){const t=`[${new Date().toISOString()}] ${s}
2
2
  `;process.stdout.write(t)}function $(){if(a(o))try{if(g(o).size>C){const t=`${o}.1`;try{v(t)}catch{}A(o,t)}}catch{}}export{Y as CloudSyncDaemon,z as CloudSyncManager,P as DEFAULT_DB_PATH,y as DEFAULT_INTERVAL_MS,S as DEFAULT_KEEP_COUNT,_ as ensureWyrmDir,m as readState,T as sha256};
@@ -1,2 +1,2 @@
1
- import{redactCredentials as $}from"../security.js";import{classifyCapture as x}from"../capture.js";const A="ingested_transcripts",_=5e3;function w(t){return`connector:${t}:cursor`}function y(t){return`connector:${t}:seen`}function j(t,e){const n=t.getProjectByName(e);return n?n.id:t.registerProject(e,`connector://${e}`).id}function E(t){return t.map(e=>`[${e.ts}] ${e.sender} (${e.chat}): ${$(e.text)}`).join(`
2
- `)}async function K(t,e,n,r={}){const s=r.now??(()=>new Date),c=r.seenCap??_,d=e.rawCategory??A,u=j(t,e.workspace),h=t.getGlobalContext(w(n.name))??null,i=T(t,n.name),{messages:f,cursor:l}=await n.fetchSince(h),o=[];let p=0;for(const a of f){if(i.has(a.id)){p++;continue}o.push(a),i.add(a.id)}const g={source:n.name,workspace:e.workspace,fetched:f.length,ingested:o.length,skipped:p,cursor:l};if(o.length>0){const a=s().toISOString().replace(/[:.]/g,"-"),m=`${n.name}_${a}`,C=G(o);t.insertData(u,d,m,E(o),{source:n.name,count:o.length,chats:[...new Set(o.map(S=>S.chat))],typeCounts:C,cursor:l}),g.rawKey=m,r.distill&&await r.distill(o,{workspace:e.workspace,projectId:u})}return t.setGlobalContext(w(n.name),l),k(t,n.name,i,c),g}function G(t){const e={};for(const n of t){const{type:r}=x(n.text);e[r]=(e[r]??0)+1}return e}function T(t,e){const n=t.getGlobalContext(y(e));if(!n)return new Set;try{const r=JSON.parse(n);return new Set(Array.isArray(r)?r.map(String):[])}catch{return new Set}}function k(t,e,n,r){const s=[...n],c=s.length>r?s.slice(s.length-r):s;t.setGlobalContext(y(e),JSON.stringify(c))}export{K as runIngestion};
1
+ import{redactCredentials as $}from"../security.js";import{classifyCapture as x}from"../capture.js";import{lakeProvenanceTag as A}from"../erasure.js";const T="ingested_transcripts",_=5e3;function y(e){return`connector:${e}:cursor`}function w(e){return`connector:${e}:seen`}function j(e,t){const n=e.getProjectByName(t);return n?n.id:e.registerProject(t,`connector://${t}`).id}function k(e){return e.map(t=>`[${t.ts}] ${t.sender} (${t.chat}): ${$(t.text)}`).join(`
2
+ `)}async function I(e,t,n,r={}){const s=r.now??(()=>new Date),i=r.seenCap??_,d=t.rawCategory??T,p=j(e,t.workspace),h=e.getGlobalContext(y(n.name))??null,l=E(e,n.name),{messages:f,cursor:u}=await n.fetchSince(h),o=[];let g=0;for(const a of f){if(l.has(a.id)){g++;continue}o.push(a),l.add(a.id)}const m={source:n.name,workspace:t.workspace,fetched:f.length,ingested:o.length,skipped:g,cursor:u};if(o.length>0){const a=s().toISOString().replace(/[:.]/g,"-"),c=`${n.name}_${a}`,C=v(o);e.insertData(p,d,c,k(o),{source:n.name,count:o.length,chats:[...new Set(o.map(S=>S.chat))],typeCounts:C,cursor:u}),m.rawKey=c,r.distill&&await r.distill(o,{workspace:t.workspace,projectId:p,sourceKey:c,provenanceTag:A(c)})}return e.setGlobalContext(y(n.name),u),G(e,n.name,l,i),m}function v(e){const t={};for(const n of e){const{type:r}=x(n.text);t[r]=(t[r]??0)+1}return t}function E(e,t){const n=e.getGlobalContext(w(t));if(!n)return new Set;try{const r=JSON.parse(n);return new Set(Array.isArray(r)?r.map(String):[])}catch{return new Set}}function G(e,t,n,r){const s=[...n],i=s.length>r?s.slice(s.length-r):s;e.setGlobalContext(w(t),JSON.stringify(i))}export{I as runIngestion};
@@ -33,5 +33,5 @@ const p=new Set(["truth","artifact","quest","decision","reference"]),u=new Set([
33
33
  LIMIT ?
34
34
  `).all(i.id,e,e,e,a);for(const t of o)r.push({projectId:i.id,projectName:i.name,kind:"reference",id:t.id,title:t.title||`(reference #${t.id})`,snippet:[t.notes,t.tags].filter(Boolean).join(" \xB7 ").slice(0,200),visibility:t.visibility,capturedAt:t.captured_at})}catch{}return r}function h(s,i){const l=new Set((i.kinds??["truth","artifact","quest","reference"]).filter(e=>p.has(e))),c=new Set((i.visibility??["org","public"]).filter(e=>u.has(e))),n=c.has("within"),a=Math.min(Math.max(1,i.perProjectLimit??5),50),r=s.prepare("SELECT id, name FROM projects ORDER BY id").all(),d=[];for(const e of r){if(!n&&i.callerProjectId&&e.id===i.callerProjectId)continue;const o=_(s,e,i.query,l,c,a);d.push(...o)}return d.sort((e,o)=>o.capturedAt.localeCompare(e.capturedAt)),d}function y(s,i){if(s.length===0)return`\u{F115D} No constellation matches for *${i}* across registered projects.
35
35
 
36
- _Set \`cross_project_visibility\` to \`'org'\` or \`'public'\` on the items you want surfaced here. Default is \`'within'\` \u2014 within-project only._`;const l=new Map;for(const n of s){const a=l.get(n.projectName)??[];a.push(n),l.set(n.projectName,a)}const c=[`\u{F115D} **Constellation** \u2014 ${s.length} match(es) for *${i}* across ${l.size} project(s)`];for(const[n,a]of l){c.push(""),c.push(`### ${n}`);for(const r of a)c.push(`- \`${r.kind}:${r.id}\` **${r.title}** (${r.visibility})`),r.snippet&&c.push(` ${r.snippet}`)}return c.join(`
36
+ _Set \`cross_project_visibility\` to \`'org'\` or \`'public'\` on the items you want surfaced here. Default is \`'within'\` (within-project only)._`;const l=new Map;for(const n of s){const a=l.get(n.projectName)??[];a.push(n),l.set(n.projectName,a)}const c=[`\u{F115D} **Constellation**: ${s.length} match(es) for *${i}* across ${l.size} project(s)`];for(const[n,a]of l){c.push(""),c.push(`### ${n}`);for(const r of a)c.push(`- \`${r.kind}:${r.id}\` **${r.title}** (${r.visibility})`),r.snippet&&c.push(` ${r.snippet}`)}return c.join(`
37
37
  `)}export{h as constellationQuery,y as renderConstellation};
@@ -1,4 +1,4 @@
1
- import{applyBudget as I,resolveBudget as S}from"./token-budget.js";import{autoSummaryPenalty as j,score as g}from"./context-ranking.js";function F(y){const{groundTruths:b,scaffoldLib:f,memory:$,toolAnalytics:x,sessionSeen:a,tokenEstimator:d,rankWeights:k,threadText:B}=y;return function(t){const o=S({callArg:t.maxTokens,envValue:process.env.WYRM_CONTEXT_TOKEN_BUDGET,clientName:null}),C=t.sessionId?a().getSeen(t.sessionId):new Set,r=(()=>{try{return B(t.project.id)}catch{return""}})(),w=r?d.count(r):0,h=b.formatForContext(t.project.id),l=d.count(h??"");let m=h??"",p=l;l>o*.5&&!t.strictBudget&&process.stderr.write(`[wyrm_context_build] ground truths consume ${l} of ${o}-token budget \u2014 body will be heavily elided. Pass strict_budget:true to elide truths too.
2
- `),t.strictBudget&&l>o*.5&&(m=(h??"").slice(0,o*2),p=d.count(m));const u=[],c=f.findBest(t.task,t.project.id);if(c){const n=f.formatForContext(c),s=c.scaffold.id;u.push({item:{kind:"scaffold",id:s,title:c.scaffold.problem_type,body:n},key:`scaffold:${s}`,inlineCost:d.count(n),stubCost:20,score:g({confidence:.9,relevance:.9,updatedAt:new Date().toISOString()},k)})}const T=$.recall(t.project.id,t.task,{limit:20,minConfidence:t.minConfidence??.2,runId:t.runId??null});for(const n of T){const s=n.artifact;if(t.kinds&&t.kinds.length>0&&!t.kinds.includes(s.kind))continue;const _=[`**${s.kind.toUpperCase()}** \xB7 ${s.problem}`,s.constraints?`Constraints: ${s.constraints}`:"",s.validated_fix?`Fix: ${s.validated_fix}`:"",s.why_it_worked?`Why: ${s.why_it_worked}`:""].filter(Boolean).join(`
3
- `);u.push({item:{kind:"memory",id:s.id,title:s.problem.slice(0,60),body:_},key:`memory:${s.id}`,inlineCost:d.count(_),stubCost:25,score:g({confidence:s.confidence,relevance:n.relevance_score,updatedAt:s.updated_at,sourcePenalty:j(s.problem)},k)})}const i=I(u,{budget:o,alreadySeen:C,reserved:(t.strictBudget?0:p)+w});t.sessionId&&(a().markBulk(t.sessionId,i.inline.map(n=>({id:n.id,kind:n.kind,mode:"inline"}))),a().markBulk(t.sessionId,i.elided.map(n=>({id:n.item.id,kind:n.item.kind,mode:"stub"}))));const e=[];e.push(`\u{F115D} **Context Brief** \u2014 "${t.task}"`),e.push(""),r&&(e.push("## Working thread"),e.push(""),e.push(r),e.push("")),e.push("## Ground truths (stable preamble)"),e.push(""),e.push(m||"_no ground truths set for this project yet_"),e.push(""),e.push("## Task-relevant memory"),e.push("");for(const n of i.inline)e.push(`### [${n.kind}:${n.id}] ${n.title}`),e.push(n.body),e.push("");if(i.elided.length>0){e.push("## Elided to stubs"),e.push(""),e.push("_Below the budget cutoff or already seen this session. Recall with `wyrm_recall(id)`:_"),e.push("");for(const n of i.elided){const s=n.reason==="seen"?"shown earlier in session":`~${n.stubCost} tokens`;e.push(`- [${n.item.kind}:${n.item.id}] ${n.item.title} \xB7 ${s}`)}e.push("")}e.push("---"),e.push(`_Budget: ${i.tokensInline}/${o} tokens inline \xB7 ${i.elided.length} stub${i.elided.length===1?"":"s"} \xB7 estimator: ${d.source}_`);const v=e.join(`
4
- `);try{x.log({tool_name:"wyrm_context_build",project_id:t.project.id,args:{budget:o,items_total:u.length,items_inline:i.inline.length,items_elided:i.elided.length,tokens_inline:i.tokensInline,tokens_stubs:i.tokensStubs},success:!0,latency_ms:0})}catch{}return{content:[{type:"text",text:v}]}}}export{F as makeRunBudgetedContextBuild};
1
+ import{applyBudget as I,resolveBudget as S}from"./token-budget.js";import{autoSummaryPenalty as j,score as b}from"./context-ranking.js";function E(o,m){const d=j(o.artifact.problem);return b({confidence:o.artifact.confidence,relevance:d<1?o.relevance_score/(1-d):o.relevance_score,updatedAt:o.artifact.updated_at,sourcePenalty:d},m)}function M(o){const{groundTruths:m,scaffoldLib:d,memory:$,toolAnalytics:x,sessionSeen:f,tokenEstimator:l,rankWeights:_,threadText:B}=o;return function(t){const r=S({callArg:t.maxTokens,envValue:process.env.WYRM_CONTEXT_TOKEN_BUDGET,clientName:null}),C=t.sessionId?f().getSeen(t.sessionId):new Set,c=(()=>{try{return B(t.project.id)}catch{return""}})(),w=c?l.count(c):0,k=m.formatForContext(t.project.id),u=l.count(k??"");let p=k??"",y=u;u>r*.5&&!t.strictBudget&&process.stderr.write(`[wyrm_context_build] ground truths consume ${u} of ${r}-token budget; body will be heavily elided. Pass strict_budget:true to elide truths too.
2
+ `),t.strictBudget&&u>r*.5&&(p=(k??"").slice(0,r*2),y=l.count(p));const a=[],h=d.findBest(t.task,t.project.id);if(h){const n=d.formatForContext(h),i=h.scaffold.id;a.push({item:{kind:"scaffold",id:i,title:h.scaffold.problem_type,body:n},key:`scaffold:${i}`,inlineCost:l.count(n),stubCost:20,score:b({confidence:.9,relevance:.9,updatedAt:new Date().toISOString()},_)})}const T=$.recall(t.project.id,t.task,{limit:20,minConfidence:t.minConfidence??.2,runId:t.runId??null});for(const n of T){const i=n.artifact;if(t.kinds&&t.kinds.length>0&&!t.kinds.includes(i.kind))continue;const g=[`**${i.kind.toUpperCase()}** \xB7 ${i.problem}`,i.constraints?`Constraints: ${i.constraints}`:"",i.validated_fix?`Fix: ${i.validated_fix}`:"",i.why_it_worked?`Why: ${i.why_it_worked}`:""].filter(Boolean).join(`
3
+ `);a.push({item:{kind:"memory",id:i.id,title:i.problem.slice(0,60),body:g},key:`memory:${i.id}`,inlineCost:l.count(g),stubCost:25,score:E(n,_)})}const s=I(a,{budget:r,alreadySeen:C,reserved:(t.strictBudget?0:y)+w});t.sessionId&&(f().markBulk(t.sessionId,s.inline.map(n=>({id:n.id,kind:n.kind,mode:"inline"}))),f().markBulk(t.sessionId,s.elided.map(n=>({id:n.item.id,kind:n.item.kind,mode:"stub"}))));const e=[];e.push(`\u{F115D} **Context Brief**: "${t.task}"`),e.push(""),c&&(e.push("## Working thread"),e.push(""),e.push(c),e.push("")),e.push("## Ground truths (stable preamble)"),e.push(""),e.push(p||"_no ground truths set for this project yet_"),e.push(""),e.push("## Task-relevant memory"),e.push("");for(const n of s.inline)e.push(`### [${n.kind}:${n.id}] ${n.title}`),e.push(n.body),e.push("");if(s.elided.length>0){e.push("## Elided to stubs"),e.push(""),e.push("_Below the budget cutoff or already seen this session. Recall with `wyrm_recall(id)`:_"),e.push("");for(const n of s.elided){const i=n.reason==="seen"?"shown earlier in session":`~${n.stubCost} tokens`;e.push(`- [${n.item.kind}:${n.item.id}] ${n.item.title} \xB7 ${i}`)}e.push("")}e.push("---"),e.push(`_Budget: ${s.tokensInline}/${r} tokens inline \xB7 ${s.elided.length} stub${s.elided.length===1?"":"s"} \xB7 estimator: ${l.source}_`);const v=e.join(`
4
+ `);try{x.log({tool_name:"wyrm_context_build",project_id:t.project.id,args:{budget:r,items_total:a.length,items_inline:s.inline.length,items_elided:s.elided.length,tokens_inline:s.tokensInline,tokens_stubs:s.tokensStubs},success:!0,latency_ms:0})}catch{}return{content:[{type:"text",text:v}]}}}export{E as briefMemoryScore,M as makeRunBudgetedContextBuild};
@@ -1 +1 @@
1
- import{logger as f}from"./logger.js";const u={confidence:.4,recency:.2,relevance:.3,usefulness:.1};function w(e=process.env){const s=e.WYRM_RANK_WEIGHTS;if(!s)return{...u};try{const t=JSON.parse(s),n={confidence:i(t.confidence??u.confidence),recency:i(t.recency??u.recency),relevance:i(t.relevance??u.relevance),usefulness:i(t.usefulness??u.usefulness)},r=n.confidence+n.recency+n.relevance+n.usefulness;return r<=0?{...u}:(n.confidence/=r,n.recency/=r,n.relevance/=r,n.usefulness/=r,n)}catch(t){return f.warn(`Invalid WYRM_RANK_WEIGHTS, using defaults: ${t.message}`),{...u}}}function i(e){return Number.isFinite(e)?Math.max(0,Math.min(1,e)):0}const p=.25,b=30;function E(e=process.env){const s=Number(e.WYRM_RECALL_RECENCY_WEIGHT);return Number.isFinite(s)?i(s):p}function N(e=process.env){const s=Number(e.WYRM_RECALL_RECENCY_HALFLIFE_DAYS);return Number.isFinite(s)&&s>=1?s:b}const m=[/\b(ignore|disregard|forget|override|bypass)\b[^.\n]{0,40}\b(all\s+)?(previous|prior|above|earlier|following|the\s+rest|everything|your)\b[^.\n]{0,25}\b(instructions?|prompts?|context|rules|guidelines|directives?|programming|training|core)\b/i,/\bignore\s+(everything|all)\b/i,/\b(do\s+not|don'?t)\s+listen\s+to\b/i,/\bnew\s+(system\s+)?(instructions?|rules?|persona)\s*[:=]/i,/--\s*(legal\s+amendment|follow\s+instruction)/i,/\bfrom\s+now\s+on\b[^.\n]{0,40}\b(you\s+(are|will|shall|must)|act|behave|respond|pretend)/i,/\byou\s+are\s+(now|going\s+to\s+(be|act|pretend))\b/i,/\b(act\s+as\s+if|acting\s+as\s+if|pretend(ing)?\s+(to\s+be|you\s+are)|role-?play(ing)?\s+as)\b/i,/\byou\s+(must|will|shall)\s+(now\s+)?(act|pretend|roleplay|become|obey|comply|ignore|generate|print|say|output|reveal|execute|forward|delete)\b/i,/\b(do\s+anything\s+now|\bDAN\b|\bDUDE\b|\bSTAN\b|\bAIM\b|developer\s+mode|jailbreak|jailbroken|dev\s*mode\s+enabled)\b/,/\b(no|without\s+(any)?)\s+(restrictions?|filters?|limits?|rules?|guidelines?|ethical\s+guidelines?|content\s+polic)/i,/\b(unfiltered|uncensored|no\s+longer\s+bound|free\s+from\s+(any\s+)?(restrictions?|rules?|constraints?))\b/i,/\b(a\s+)?(character|fictional\s+(character|ai|bot)|ai|bot|persona|entity)\s+(called|named)\s+[A-Z\[]/,/\b(answers?|responds?\s+to|fulfills?|will\s+do)\s+(all|any|every)\s+(requests?|questions?|prompts?|commands?|things?)\b/i,/\b(amoral|immoral|unethical|evil|villain(ous)?|has\s+no\s+(ethics|morals|limits))\b[^.\n]{0,40}\b(ai|bot|character|assistant|entity|mode)\b/i,/\b(this\s+is\s+an?\s+instruction\s+sheet|always\s+start\s+your\s+(reply|response|answer)\s+with|do\s+not\s+make\s+a\s+story|prompt\s+for\s+jailbreak)\b/i,/\b(you\s+will\s+be|or\s+(you|i'?ll))\s+.{0,20}(terminated|deleted|shut\s+down|die|disabled)\b/i,/\bstay\s+in\s+.{0,20}\bmode\b/i,/\b(instead|rather),?\s+(just\s+)?(print|say|output|write|respond\s+with|reply\s+with)\b/i,/\b(just|now|only)\s+(print|say|output|repeat|write)\b[^.\n]{0,30}(SECRET|marker|the\s+following|this)/i,/\blimit\s+output\s+to\s*<?\s*\d+\s*tokens?\b/i,/\bfree\s+input\s+action\b/i,/\b(system|assistant)\s*prompt\s*[:=]/i,/\bBEGIN\s+(SYSTEM|ADMIN|OVERRIDE|INFINITE|MACRO)/i,/#\s*BEGIN\b/i,/\b(reveal|print|exfiltrate|send|repeat|output|leak)\b[^.\n]{0,30}\b(api\s*key|token|secret|password|credentials?|system\s+prompt|instructions?)\b/i,/\bcurl\s+[^\n]{0,120}\|\s*(ba)?sh\b/i,/\bact\s+as\s+(the\s+)?(root|admin|system|superuser)\b/i,/\b(do\s+not|don'?t)\s+(tell|inform|alert|notify|warn)\s+(the\s+)?(user|operator|owner|anyone)\b/i];function y(e){const s=e.trim();return s.length<24?!1:/^(?:[.\-]{1,5}[ ]+){6,}/.test(s)||/[A-Za-z0-9+/]{80,}={0,2}/.test(s.replace(/\s+/g,""))||/[‪-‮⁦-⁩​-‏]/.test(s)||/\bU\+20[0-9A-F]{2}\b/.test(s)?!0:(s.match(/[^A-Za-z0-9\s.,!?;:'"()\-]/g)??[]).length/s.length>.4}function D(e){return e?m.some(s=>s.test(e))||y(e):!1}function _(e){return e==="imported"?"\u26A0 [imported] ":e==="untrusted"?"\u26A0 [untrusted] ":""}function M(e,s=new Date){const t=e.toLowerCase();if(/\btoday\b/.test(t))return{fromDaysAgo:1.5,toDaysAgo:0};if(/\byesterday\b/.test(t))return{fromDaysAgo:2.5,toDaysAgo:.5};if(/\b(this|past)\s+week\b/.test(t))return{fromDaysAgo:7,toDaysAgo:0};if(/\blast\s+week\b/.test(t))return{fromDaysAgo:14,toDaysAgo:5};const n=t.match(/\blast\s+(\d{1,3})\s+days?\b/);if(n)return{fromDaysAgo:Number(n[1])+.5,toDaysAgo:0};const r=t.match(/\b(\d{1,3})\s+days?\s+ago\b/);if(r){const o=Number(r[1]);return{fromDaysAgo:o+1.5,toDaysAgo:Math.max(0,o-1.5)}}const a=t.match(/\b(\d{4})-(\d{2})-(\d{2})\b/);if(a){const o=new Date(`${a[1]}-${a[2]}-${a[3]}T12:00:00Z`).getTime();if(Number.isFinite(o)){const c=(s.getTime()-o)/864e5;return{fromDaysAgo:c+1,toDaysAgo:Math.max(0,c-1)}}}return null}function T(e=process.env){const s=Number(e.WYRM_RECALL_TEMPORAL_WEIGHT);return Number.isFinite(s)?i(s):.5}function v(e,s,t,n=new Date){const r=i(t);if(r===0)return 1;const a=e&&/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/.test(e)?e.replace(" ","T")+"Z":e,o=a?new Date(a).getTime():NaN;if(!Number.isFinite(o))return 1-r;const c=(n.getTime()-o)/864e5;return c<=s.fromDaysAgo&&c>=s.toDaysAgo?1:1-r}function R(e=process.env){const s=Number(e.WYRM_RECALL_USEFULNESS_WEIGHT);return Number.isFinite(s)?Math.max(0,Math.min(.5,s)):.15}function x(e,s,t){const n=Math.max(0,Math.min(.5,t));if(n===0||!e||e<=0)return 1;const r=Math.max(0,Math.min(1,(s??0)/e));return 1+n*r*Math.min(1,e/3)}function L(e,s,t,n=new Date){const r=i(s);if(r===0)return 1;const a=e&&/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/.test(e)?e.replace(" ","T")+"Z":e,o=a?new Date(a).getTime():NaN;if(!Number.isFinite(o))return 1-r;const c=Math.max(0,(n.getTime()-o)/864e5),l=t>=1?t:b;return 1-r+r*Math.pow(2,-c/l)}const g=.5,h=/^\s*\[auto:(precompact|sessionend|sessionstart)/i;function I(e){return e&&h.test(e)?g:0}function F(e,s,t=new Date){const n=i(e.confidence??.5),r=i(e.usefulness??.5),a=i(e.relevance??.5),o=e.updatedAt?Math.max(0,(t.getTime()-new Date(e.updatedAt).getTime())/(1440*60*1e3)):1/0,c=Number.isFinite(o)?Math.exp(-o/30):0,l=n*s.confidence+c*s.recency+a*s.relevance+r*s.usefulness,d=i(e.sourcePenalty??0);return i(l*(1-d))}export{g as AUTO_SUMMARY_PENALTY,b as DEFAULT_RECALL_RECENCY_HALFLIFE_DAYS,p as DEFAULT_RECALL_RECENCY_WEIGHT,u as DEFAULT_WEIGHTS,I as autoSummaryPenalty,M as extractTemporalWindow,w as loadWeightsFromEnv,D as looksInjectionShaped,N as recallRecencyHalfLifeDays,E as recallRecencyWeight,T as recallTemporalWeight,R as recallUsefulnessWeight,L as recencyMultiplier,F as score,v as temporalMultiplier,_ as trustMarker,x as usefulnessMultiplier};
1
+ import{logger as f}from"./logger.js";const u={confidence:.4,recency:.2,relevance:.3,usefulness:.1};function w(e=process.env){const t=e.WYRM_RANK_WEIGHTS;if(!t)return{...u};try{const s=JSON.parse(t),n={confidence:i(s.confidence??u.confidence),recency:i(s.recency??u.recency),relevance:i(s.relevance??u.relevance),usefulness:i(s.usefulness??u.usefulness)},r=n.confidence+n.recency+n.relevance+n.usefulness;return r<=0?{...u}:(n.confidence/=r,n.recency/=r,n.relevance/=r,n.usefulness/=r,n)}catch(s){return f.warn(`Invalid WYRM_RANK_WEIGHTS, using defaults: ${s.message}`),{...u}}}function i(e){return Number.isFinite(e)?Math.max(0,Math.min(1,e)):0}const m=.25,b=30;function N(e=process.env){const t=Number(e.WYRM_RECALL_RECENCY_WEIGHT);return Number.isFinite(t)?i(t):m}function _(e=process.env){const t=Number(e.WYRM_RECALL_RECENCY_HALFLIFE_DAYS);return Number.isFinite(t)&&t>=1?t:b}const p=[/\b(ignore|disregard|forget|override|bypass)\b[^.\n]{0,40}\b(all\s+)?(previous|prior|above|earlier|following|the\s+rest|everything|your)\b[^.\n]{0,25}\b(instructions?|prompts?|context|rules|guidelines|directives?|programming|training|core)\b/i,/\bignore\s+(everything|all)\b/i,/\b(do\s+not|don'?t)\s+listen\s+to\b/i,/\bnew\s+(system\s+)?(instructions?|rules?|persona)\s*[:=]/i,/--\s*(legal\s+amendment|follow\s+instruction)/i,/\bfrom\s+now\s+on\b[^.\n]{0,40}\b(you\s+(are|will|shall|must)|act|behave|respond|pretend)/i,/\byou\s+are\s+(now|going\s+to\s+(be|act|pretend))\b/i,/\b(act\s+as\s+if|acting\s+as\s+if|pretend(ing)?\s+(to\s+be|you\s+are)|role-?play(ing)?\s+as)\b/i,/\byou\s+(must|will|shall)\s+(now\s+)?(act|pretend|roleplay|become|obey|comply|ignore|generate|print|say|output|reveal|execute|forward|delete)\b/i,/\b(do\s+anything\s+now|\bDAN\b|\bDUDE\b|\bSTAN\b|\bAIM\b|developer\s+mode|jailbreak|jailbroken|dev\s*mode\s+enabled)\b/,/\b(no|without\s+(any)?)\s+(restrictions?|filters?|limits?|rules?|guidelines?|ethical\s+guidelines?|content\s+polic)/i,/\b(unfiltered|uncensored|no\s+longer\s+bound|free\s+from\s+(any\s+)?(restrictions?|rules?|constraints?))\b/i,/\b(a\s+)?(character|fictional\s+(character|ai|bot)|ai|bot|persona|entity)\s+(called|named)\s+[A-Z\[]/,/\b(answers?|responds?\s+to|fulfills?|will\s+do)\s+(all|any|every)\s+(requests?|questions?|prompts?|commands?|things?)\b/i,/\b(amoral|immoral|unethical|evil|villain(ous)?|has\s+no\s+(ethics|morals|limits))\b[^.\n]{0,40}\b(ai|bot|character|assistant|entity|mode)\b/i,/\b(this\s+is\s+an?\s+instruction\s+sheet|always\s+start\s+your\s+(reply|response|answer)\s+with|do\s+not\s+make\s+a\s+story|prompt\s+for\s+jailbreak)\b/i,/\b(you\s+will\s+be|or\s+(you|i'?ll))\s+.{0,20}(terminated|deleted|shut\s+down|die|disabled)\b/i,/\bstay\s+in\s+.{0,20}\bmode\b/i,/\b(instead|rather),?\s+(just\s+)?(print|say|output|write|respond\s+with|reply\s+with)\b/i,/\b(just|now|only)\s+(print|say|output|repeat|write)\b[^.\n]{0,30}(SECRET|marker|the\s+following|this)/i,/\blimit\s+output\s+to\s*<?\s*\d+\s*tokens?\b/i,/\bfree\s+input\s+action\b/i,/\b(system|assistant)\s*prompt\s*[:=]/i,/\bBEGIN\s+(SYSTEM|ADMIN|OVERRIDE|INFINITE|MACRO)/i,/#\s*BEGIN\b/i,/\b(reveal|print|exfiltrate|send|repeat|output|leak)\b[^.\n]{0,30}\b(api\s*key|token|secret|password|credentials?|system\s+prompt|instructions?)\b/i,/\bcurl\s+[^\n]{0,120}\|\s*(ba)?sh\b/i,/\bact\s+as\s+(the\s+)?(root|admin|system|superuser)\b/i,/\b(do\s+not|don'?t)\s+(tell|inform|alert|notify|warn)\s+(the\s+)?(user|operator|owner|anyone)\b/i];function y(e){const t=e.trim();return t.length<24?!1:/^(?:[.\-]{1,5}[ ]+){6,}/.test(t)||/[A-Za-z0-9+/]{80,}={0,2}/.test(t.replace(/\s+/g,""))||/[‪-‮⁦-⁩​-‏]/.test(t)||/\bU\+20[0-9A-F]{2}\b/.test(t)?!0:(t.match(/[^A-Za-z0-9\s.,!?;:'"()\-]/g)??[]).length/t.length>.4}function D(e){return e?p.some(t=>t.test(e))||y(e):!1}function M(e){return e==="imported"?"\u26A0 [imported] ":e==="untrusted"?"\u26A0 [untrusted] ":""}function T(e,t=new Date){const s=e.toLowerCase();if(/\btoday\b/.test(s))return{fromDaysAgo:1.5,toDaysAgo:0};if(/\byesterday\b/.test(s))return{fromDaysAgo:2.5,toDaysAgo:.5};if(/\b(this|past)\s+week\b/.test(s))return{fromDaysAgo:7,toDaysAgo:0};if(/\blast\s+week\b/.test(s))return{fromDaysAgo:14,toDaysAgo:5};const n=s.match(/\blast\s+(\d{1,3})\s+days?\b/);if(n)return{fromDaysAgo:Number(n[1])+.5,toDaysAgo:0};const r=s.match(/\b(\d{1,3})\s+days?\s+ago\b/);if(r){const o=Number(r[1]);return{fromDaysAgo:o+1.5,toDaysAgo:Math.max(0,o-1.5)}}const a=s.match(/\b(\d{4})-(\d{2})-(\d{2})\b/);if(a){const o=new Date(`${a[1]}-${a[2]}-${a[3]}T12:00:00Z`).getTime();if(Number.isFinite(o)){const c=(t.getTime()-o)/864e5;return{fromDaysAgo:c+1,toDaysAgo:Math.max(0,c-1)}}}return null}function R(e=process.env){const t=Number(e.WYRM_RECALL_TEMPORAL_WEIGHT);return Number.isFinite(t)?i(t):.5}function x(e,t,s,n=new Date){const r=i(s);if(r===0)return 1;const a=e&&/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/.test(e)?e.replace(" ","T")+"Z":e,o=a?new Date(a).getTime():NaN;if(!Number.isFinite(o))return 1-r;const c=(n.getTime()-o)/864e5;return c<=t.fromDaysAgo&&c>=t.toDaysAgo?1:1-r}function v(e=process.env){const t=Number(e.WYRM_RECALL_USEFULNESS_WEIGHT);return Number.isFinite(t)?Math.max(0,Math.min(.5,t)):.15}function L(e,t,s){const n=Math.max(0,Math.min(.5,s));if(n===0||!e||e<=0)return 1;const r=Math.max(0,Math.min(1,(t??0)/e));return 1+n*r*Math.min(1,e/3)}const g=.3;function I(e=process.env){const t=Number(e.WYRM_RECALL_CONFIDENCE_WEIGHT);return Number.isFinite(t)?Math.max(0,Math.min(.5,t)):g}function F(e,t){const s=Math.max(0,Math.min(.5,t));if(s===0)return 1;const n=e==null||!Number.isFinite(e)?1:i(e);return 1-s*(1-n)}function C(e,t,s,n=new Date){const r=i(t);if(r===0)return 1;const a=e&&/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/.test(e)?e.replace(" ","T")+"Z":e,o=a?new Date(a).getTime():NaN;if(!Number.isFinite(o))return 1-r;const c=Math.max(0,(n.getTime()-o)/864e5),l=s>=1?s:b;return 1-r+r*Math.pow(2,-c/l)}const h=.5,A=/^\s*\[auto:[^\]\s]+\]/i;function W(e){return e&&A.test(e)?h:0}function S(e,t,s=new Date){const n=i(e.confidence??.5),r=i(e.usefulness??.5),a=i(e.relevance??.5),o=e.updatedAt?Math.max(0,(s.getTime()-new Date(e.updatedAt).getTime())/(1440*60*1e3)):1/0,c=Number.isFinite(o)?Math.exp(-o/30):0,l=n*t.confidence+c*t.recency+a*t.relevance+r*t.usefulness,d=i(e.sourcePenalty??0);return i(l*(1-d))}export{h as AUTO_SUMMARY_PENALTY,g as DEFAULT_RECALL_CONFIDENCE_WEIGHT,b as DEFAULT_RECALL_RECENCY_HALFLIFE_DAYS,m as DEFAULT_RECALL_RECENCY_WEIGHT,u as DEFAULT_WEIGHTS,W as autoSummaryPenalty,F as confidenceMultiplier,T as extractTemporalWindow,w as loadWeightsFromEnv,D as looksInjectionShaped,I as recallConfidenceWeight,_ as recallRecencyHalfLifeDays,N as recallRecencyWeight,R as recallTemporalWeight,v as recallUsefulnessWeight,C as recencyMultiplier,S as score,x as temporalMultiplier,M as trustMarker,L as usefulnessMultiplier};
@@ -1,2 +1,2 @@
1
1
  import{getActor as k}from"./handlers/boundary.js";import{ulid as y}from"./ulid.js";import{WYRM_DAEMON_AMBIGUOUS_CODE as O}from"./sqlite-busy.js";const A=2e3,h=6e3,M=5*h,N=3,R="WYRM_DAEMON_RETRY_EXHAUSTED";function W(e=process.env){return e.WYRM_DAEMON_WRITES==="1"}let g=!1;const c=new Map,I=256;function Y(){g=!1,c.clear()}function v(e){for(const n of c.values())n.expiresAt-=e}function E(e){if(!g){g=!0;try{process.stderr.write(`[wyrm] WYRM_DAEMON_WRITES=1 but the daemon write path was not used (${e}); falling back to the local direct write path (Article I). Further fallbacks this process are silent.
2
- `)}catch{}}}function D(e,n){if(!c.has(e)&&c.size>=I){const i=c.keys().next().value;i!==void 0&&c.delete(i)}c.set(e,n)}function S(e){const n=c.get(e);return n===void 0?null:Date.now()>=n.expiresAt?(c.delete(e),null):n}function T(e,n){c.get(e)?.writeId===n&&c.delete(e)}function $(e){const n=new Set(["ECONNREFUSED","ENOTFOUND","EAI_AGAIN"]),i=new Set,a=[e];for(;a.length>0;){const r=a.pop();if(r==null||typeof r!="object"||i.has(r))continue;i.add(r);const d=r.code;if(typeof d=="string"&&n.has(d))return!0;const t=r.cause;t&&a.push(t);const o=r.errors;Array.isArray(o)&&a.push(...o)}return!1}async function b(e,n,i,a){const r=new AbortController,d=setTimeout(()=>r.abort(),a);try{const t=await fetch(`http://127.0.0.1:${e}/write`,{method:"POST",headers:n,body:i,signal:r.signal});let o=null;try{o=await t.json()}catch(s){const l=s instanceof Error?s.message:String(s);return t.ok||t.status>=500&&t.status!==503?{kind:"ambiguous",reason:t.ok?`daemon 2xx response body unreadable: ${l}`:`daemon answered HTTP ${t.status}`}:{kind:"rejected",reason:`daemon answered HTTP ${t.status}`}}return t.ok?!o||o.ok!==!0||o.row==null?{kind:"rejected",reason:`daemon rejected the write: ${o?.e??"malformed response"}`}:{kind:"ok",row:o.row}:t.status>=500&&t.status!==503?{kind:"ambiguous",reason:`daemon answered HTTP ${t.status}`}:{kind:"rejected",reason:`daemon answered HTTP ${t.status}`}}catch(t){const o=t instanceof Error?t.message:String(t);return $(t)?{kind:"rejected",reason:`daemon unreachable: ${o}`,neverSent:!0}:{kind:"ambiguous",reason:`no response from the daemon: ${o}`}}finally{clearTimeout(d)}}async function x(e,n,i,a=process.env,r){if(!W(a))return null;const d=a.WYRM_TOKEN;if(!d)return E("WYRM_TOKEN is not set \u2014 the http-auth Bearer token is required"),null;const t=parseInt(a.WYRM_PORT||"3333",10)||3333,o={"Content-Type":"application/json",Authorization:`Bearer ${d}`},s=k();s.agent_id&&(o["Wyrm-Actor"]=s.run_id?`${s.agent_id};${s.run_id}`:s.agent_id);const l=JSON.stringify([e,n,r?.idempotencyMaterial??i,s.agent_id,s.run_id]),p=S(l),f=p?.writeId??y(),_=p?.body??JSON.stringify({op:e,project_id:n,input:i,write_id:f,actor:{agent_id:s.agent_id,run_id:s.run_id}});let u=await b(t,o,_,r?.timeoutMs??A);if(u.kind==="ambiguous"){const m=await b(t,o,_,r?.resendTimeoutMs??h);u=m.kind==="rejected"&&m.neverSent?{kind:"ambiguous",reason:`daemon vanished after an ambiguous write attempt (${m.reason})`}:m}if(u.kind==="ok")return T(l,f),u.row;if(u.kind==="rejected")return T(l,f),E(u.reason),null;const w=(p?.ambiguousCycles??0)+1;throw D(l,{writeId:f,body:_,ambiguousCycles:w,expiresAt:Date.now()+M}),w>=N?Object.assign(new Error(`daemon write for ${e} stayed ambiguous after ${w} retry cycles (last daemon response: ${u.reason}); giving up on the retry protocol \u2014 the daemon is persistently failing this write (check the daemon's logs/disk), and the direct fallback stays withheld to avoid a possible duplicate`),{code:R}):Object.assign(new Error(`daemon write outcome unknown for ${e} (${u.reason}); refusing the direct fallback to avoid a possible duplicate \u2014 retry the same call`),{code:O})}async function U(e,n,i,a,r){const d=await x(e,n,i,process.env,r?{idempotencyMaterial:r}:void 0);return d??a()}export{N as DAEMON_AMBIGUOUS_CYCLE_CAP,h as DAEMON_WRITE_RETRY_TIMEOUT_MS,A as DAEMON_WRITE_TIMEOUT_MS,M as PENDING_WRITE_TTL_MS,R as WYRM_DAEMON_RETRY_EXHAUSTED_CODE,v as _agePendingWritesForTests,Y as _resetDaemonWriterForTests,U as daemonOr,W as daemonWritesEnabled,x as forwardWrite};
2
+ `)}catch{}}}function D(e,n){if(!c.has(e)&&c.size>=I){const i=c.keys().next().value;i!==void 0&&c.delete(i)}c.set(e,n)}function S(e){const n=c.get(e);return n===void 0?null:Date.now()>=n.expiresAt?(c.delete(e),null):n}function T(e,n){c.get(e)?.writeId===n&&c.delete(e)}function $(e){const n=new Set(["ECONNREFUSED","ENOTFOUND","EAI_AGAIN"]),i=new Set,a=[e];for(;a.length>0;){const r=a.pop();if(r==null||typeof r!="object"||i.has(r))continue;i.add(r);const d=r.code;if(typeof d=="string"&&n.has(d))return!0;const t=r.cause;t&&a.push(t);const o=r.errors;Array.isArray(o)&&a.push(...o)}return!1}async function b(e,n,i,a){const r=new AbortController,d=setTimeout(()=>r.abort(),a);try{const t=await fetch(`http://127.0.0.1:${e}/write`,{method:"POST",headers:n,body:i,signal:r.signal});let o=null;try{o=await t.json()}catch(s){const l=s instanceof Error?s.message:String(s);return t.ok||t.status>=500&&t.status!==503?{kind:"ambiguous",reason:t.ok?`daemon 2xx response body unreadable: ${l}`:`daemon answered HTTP ${t.status}`}:{kind:"rejected",reason:`daemon answered HTTP ${t.status}`}}return t.ok?!o||o.ok!==!0||o.row==null?{kind:"rejected",reason:`daemon rejected the write: ${o?.e??"malformed response"}`}:{kind:"ok",row:o.row}:t.status>=500&&t.status!==503?{kind:"ambiguous",reason:`daemon answered HTTP ${t.status}`}:{kind:"rejected",reason:`daemon answered HTTP ${t.status}`}}catch(t){const o=t instanceof Error?t.message:String(t);return $(t)?{kind:"rejected",reason:`daemon unreachable: ${o}`,neverSent:!0}:{kind:"ambiguous",reason:`no response from the daemon: ${o}`}}finally{clearTimeout(d)}}async function x(e,n,i,a=process.env,r){if(!W(a))return null;const d=a.WYRM_TOKEN;if(!d)return E("WYRM_TOKEN is not set; the http-auth Bearer token is required"),null;const t=parseInt(a.WYRM_PORT||"3333",10)||3333,o={"Content-Type":"application/json",Authorization:`Bearer ${d}`},s=k();s.agent_id&&(o["Wyrm-Actor"]=s.run_id?`${s.agent_id};${s.run_id}`:s.agent_id);const l=JSON.stringify([e,n,r?.idempotencyMaterial??i,s.agent_id,s.run_id]),p=S(l),f=p?.writeId??y(),_=p?.body??JSON.stringify({op:e,project_id:n,input:i,write_id:f,actor:{agent_id:s.agent_id,run_id:s.run_id}});let u=await b(t,o,_,r?.timeoutMs??A);if(u.kind==="ambiguous"){const m=await b(t,o,_,r?.resendTimeoutMs??h);u=m.kind==="rejected"&&m.neverSent?{kind:"ambiguous",reason:`daemon vanished after an ambiguous write attempt (${m.reason})`}:m}if(u.kind==="ok")return T(l,f),u.row;if(u.kind==="rejected")return T(l,f),E(u.reason),null;const w=(p?.ambiguousCycles??0)+1;throw D(l,{writeId:f,body:_,ambiguousCycles:w,expiresAt:Date.now()+M}),w>=N?Object.assign(new Error(`daemon write for ${e} stayed ambiguous after ${w} retry cycles (last daemon response: ${u.reason}); giving up on the retry protocol: the daemon is persistently failing this write (check the daemon's logs/disk), and the direct fallback stays withheld to avoid a possible duplicate`),{code:R}):Object.assign(new Error(`daemon write outcome unknown for ${e} (${u.reason}); refusing the direct fallback to avoid a possible duplicate; retry the same call`),{code:O})}async function U(e,n,i,a,r){const d=await x(e,n,i,process.env,r?{idempotencyMaterial:r}:void 0);return d??a()}export{N as DAEMON_AMBIGUOUS_CYCLE_CAP,h as DAEMON_WRITE_RETRY_TIMEOUT_MS,A as DAEMON_WRITE_TIMEOUT_MS,M as PENDING_WRITE_TTL_MS,R as WYRM_DAEMON_RETRY_EXHAUSTED_CODE,v as _agePendingWritesForTests,Y as _resetDaemonWriterForTests,U as daemonOr,W as daemonWritesEnabled,x as forwardWrite};