wyrm-mcp 7.2.1 → 7.2.2

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 (150) hide show
  1. package/LICENSE +26 -667
  2. package/NOTICE +14 -33
  3. package/dist/activation.js +1 -60
  4. package/dist/agent-daemon.js +4 -281
  5. package/dist/agent-loop.js +7 -332
  6. package/dist/analytics.js +13 -236
  7. package/dist/attribution.js +1 -49
  8. package/dist/audit.js +2 -457
  9. package/dist/auto-capture.js +3 -138
  10. package/dist/auto-orchestrator.js +1 -325
  11. package/dist/autoconfig.js +39 -840
  12. package/dist/buddy-runner.js +1 -109
  13. package/dist/buddy.js +14 -564
  14. package/dist/build-flags.js +1 -17
  15. package/dist/capabilities.js +3 -183
  16. package/dist/capture.js +1 -56
  17. package/dist/causality.js +6 -107
  18. package/dist/cli.js +20 -281
  19. package/dist/cloud/cli.js +5 -541
  20. package/dist/cloud/client.js +1 -221
  21. package/dist/cloud/crypto.js +1 -85
  22. package/dist/cloud/machine-id.js +2 -113
  23. package/dist/cloud/recovery.js +1 -60
  24. package/dist/cloud/sync-engine.js +7 -543
  25. package/dist/cloud-backup.js +5 -579
  26. package/dist/cloud-profile.js +1 -138
  27. package/dist/cloud-sync-entrypoint.js +1 -47
  28. package/dist/cloud-sync.js +2 -309
  29. package/dist/constellation.js +12 -168
  30. package/dist/context-build-budgeted.js +4 -144
  31. package/dist/context-ranking.js +1 -69
  32. package/dist/crypto.js +1 -179
  33. package/dist/daemon-write-endpoint.js +1 -290
  34. package/dist/daemon-writer.js +2 -406
  35. package/dist/database.js +43 -1110
  36. package/dist/deprecations.js +2 -162
  37. package/dist/design.js +13 -141
  38. package/dist/event-replication.js +1 -112
  39. package/dist/events-sse.js +7 -43
  40. package/dist/events.js +6 -238
  41. package/dist/failure-patterns.js +42 -659
  42. package/dist/federation.js +12 -236
  43. package/dist/goals.js +13 -101
  44. package/dist/golden.js +3 -355
  45. package/dist/handlers/agent.js +4 -165
  46. package/dist/handlers/alias-adapters.js +1 -129
  47. package/dist/handlers/aliases.js +1 -171
  48. package/dist/handlers/audit.js +1 -87
  49. package/dist/handlers/boundary.js +1 -221
  50. package/dist/handlers/capture.js +73 -1109
  51. package/dist/handlers/causality.js +7 -114
  52. package/dist/handlers/cloud.js +85 -382
  53. package/dist/handlers/companion.js +28 -459
  54. package/dist/handlers/datalake.js +7 -187
  55. package/dist/handlers/dispatch-context.js +0 -22
  56. package/dist/handlers/entity.js +25 -256
  57. package/dist/handlers/events.js +16 -335
  58. package/dist/handlers/failure.js +13 -340
  59. package/dist/handlers/goals.js +4 -296
  60. package/dist/handlers/intelligence.js +126 -674
  61. package/dist/handlers/invoicing.js +1 -70
  62. package/dist/handlers/mcpclient.js +6 -137
  63. package/dist/handlers/orchestration.js +40 -125
  64. package/dist/handlers/output-schemas.js +1 -24
  65. package/dist/handlers/presence.js +3 -99
  66. package/dist/handlers/project.js +28 -182
  67. package/dist/handlers/prompts.js +6 -157
  68. package/dist/handlers/quest.js +4 -224
  69. package/dist/handlers/recall.js +11 -218
  70. package/dist/handlers/registry.js +1 -167
  71. package/dist/handlers/resources.js +1 -288
  72. package/dist/handlers/review.js +11 -74
  73. package/dist/handlers/run.js +17 -487
  74. package/dist/handlers/search.js +15 -326
  75. package/dist/handlers/session.js +28 -615
  76. package/dist/handlers/share.js +8 -184
  77. package/dist/handlers/shims.js +1 -464
  78. package/dist/handlers/skill.js +67 -449
  79. package/dist/handlers/survivors.js +1 -120
  80. package/dist/handlers/symbols.js +8 -109
  81. package/dist/handlers/syncops.js +4 -302
  82. package/dist/handlers/types.js +1 -27
  83. package/dist/harvest.js +5 -191
  84. package/dist/hours.js +7 -156
  85. package/dist/http-auth.js +3 -321
  86. package/dist/http-fast.js +21 -1137
  87. package/dist/icons.js +1 -47
  88. package/dist/index.js +2 -924
  89. package/dist/indexer.js +4 -145
  90. package/dist/intelligence.js +31 -261
  91. package/dist/internal-dispatch.js +3 -212
  92. package/dist/keyset.js +1 -110
  93. package/dist/knowledge-graph.js +12 -176
  94. package/dist/license.js +2 -441
  95. package/dist/logger.js +2 -199
  96. package/dist/maintenance.js +2 -148
  97. package/dist/mcp-client.js +6 -262
  98. package/dist/memory-artifacts.js +30 -449
  99. package/dist/migrate-prompt.js +2 -124
  100. package/dist/migrations.js +40 -655
  101. package/dist/performance.js +1 -228
  102. package/dist/presence.js +11 -140
  103. package/dist/priority-embed.js +5 -164
  104. package/dist/providers/embedding-provider.js +1 -196
  105. package/dist/readonly-gate.js +1 -29
  106. package/dist/rehydration.js +9 -157
  107. package/dist/reindex.js +1 -88
  108. package/dist/render-target.js +21 -514
  109. package/dist/render.js +4 -280
  110. package/dist/repl-guard.js +1 -173
  111. package/dist/replication-daemon-entrypoint.js +1 -31
  112. package/dist/replication-daemon.js +2 -262
  113. package/dist/resilience.js +1 -591
  114. package/dist/reverse-bridge.js +5 -360
  115. package/dist/security.js +1 -244
  116. package/dist/session-seen.js +3 -51
  117. package/dist/setup.js +1 -260
  118. package/dist/skill-author.js +5 -168
  119. package/dist/spec-kit.js +1 -191
  120. package/dist/sqlite-busy.js +1 -154
  121. package/dist/statusline.js +11 -315
  122. package/dist/sub-agent.js +13 -262
  123. package/dist/summarizer.js +13 -139
  124. package/dist/symbols.js +7 -283
  125. package/dist/sync.js +5 -359
  126. package/dist/tasks-dispatch.js +1 -84
  127. package/dist/tasks.js +1 -282
  128. package/dist/token-budget.js +1 -143
  129. package/dist/tool-analytics.js +7 -129
  130. package/dist/tool-annotations.js +1 -365
  131. package/dist/tool-manifest-v2.json +1 -1
  132. package/dist/tool-manifest.json +1 -1
  133. package/dist/tool-profiles.js +1 -75
  134. package/dist/trace-harvest.js +6 -244
  135. package/dist/types.js +1 -30
  136. package/dist/ui-dashboard.js +41 -50
  137. package/dist/ulid.js +1 -81
  138. package/dist/validate.js +1 -129
  139. package/dist/vault.js +1 -534
  140. package/dist/vectors.js +3 -184
  141. package/dist/version-check.js +4 -136
  142. package/dist/visibility.js +19 -155
  143. package/dist/wyrm-cli.js +98 -2464
  144. package/dist/wyrm-guard.js +14 -424
  145. package/dist/wyrm-loop.js +3 -150
  146. package/dist/wyrm-manifest.json +1 -1
  147. package/dist/wyrm-statusline-daemon.js +1 -11
  148. package/dist/wyrm-statusline.js +4 -56
  149. package/dist/wyrm-ui.js +9 -77
  150. package/package.json +4 -2
@@ -1,217 +1,8 @@
1
- /**
2
- * Internal tool dispatcher for the AgentLoop (v7 F4 T036 — extracted from the
3
- * index.ts monolith). A curated subset of read-mostly + bounded-write tools the
4
- * autonomous OODA loop is allowed to call; the whitelist itself is enforced in
5
- * agent-loop.ts (SAFE_INTERNAL_TOOLS). Lifted VERBATIM out of index.ts behind a
6
- * factory that captures the same subsystem singletons it always closed over —
7
- * the single-quoted `case` labels here are what tests/tool-surface-integrity.ts
8
- * scans to keep the whitelist and the dispatcher in lockstep.
9
- *
10
- * @copyright 2026 Ghost Protocol (Pvt) Ltd.
11
- * @license AGPL-3.0-or-later
12
- */
13
- import { sanitizeFtsQuery } from "./security.js";
14
- import { classifyCapture } from "./capture.js";
15
- export function makeInternalDispatch(deps) {
16
- const { db, failures, symbols, rehydration, causality, presence, federation, memory, groundTruths } = deps;
17
- const internalDispatch = async (name, args) => {
18
- try {
19
- switch (name) {
20
- case 'wyrm_search': {
21
- const q = String(args.query ?? args.q ?? '').slice(0, 200);
22
- if (!q)
23
- return { ok: false, error: 'query required' };
24
- const sanitized = sanitizeFtsQuery(q);
25
- const rows = db.getDatabase().prepare(`
1
+ import{sanitizeFtsQuery as k}from"./security.js";import{classifyCapture as _}from"./capture.js";function q(p){const{db:r,failures:i,symbols:a,rehydration:m,causality:u,presence:f,federation:j,memory:c,groundTruths:y}=p;return async(d,s)=>{try{switch(d){case"wyrm_search":{const e=String(s.query??s.q??"").slice(0,200);if(!e)return{ok:!1,error:"query required"};const t=k(e);return{ok:!0,result:r.getDatabase().prepare(`
26
2
  SELECT s.id, s.date, s.summary FROM sessions s
27
3
  JOIN sessions_fts fts ON fts.rowid = s.id
28
4
  WHERE sessions_fts MATCH ? LIMIT 10
29
- `).all(sanitized);
30
- return { ok: true, result: rows };
31
- }
32
- case 'wyrm_all_quests': {
33
- return { ok: true, result: db.getAllPendingQuests().slice(0, 30) };
34
- }
35
- case 'wyrm_truth_get': {
36
- const projPath = args.projectPath;
37
- const proj = projPath ? db.getProject(projPath) : null;
38
- const rows = proj
39
- ? db.getDatabase().prepare(`
5
+ `).all(t)}}case"wyrm_all_quests":return{ok:!0,result:r.getAllPendingQuests().slice(0,30)};case"wyrm_truth_get":{const e=s.projectPath,t=e?r.getProject(e):null;return{ok:!0,result:t?r.getDatabase().prepare(`
40
6
  SELECT category, key, value, rationale FROM ground_truths
41
7
  WHERE project_id = ? AND is_current = 1 ORDER BY confidence DESC LIMIT 30
42
- `).all(proj.id)
43
- : [];
44
- return { ok: true, result: rows };
45
- }
46
- case 'wyrm_failure_check': {
47
- const a = args;
48
- const proj = a.projectPath ? db.getProject(a.projectPath) : null;
49
- return { ok: true, result: failures.check(a.scope, a.target, a.description, proj?.id ?? null) };
50
- }
51
- case 'wyrm_failure_list': {
52
- const a = args;
53
- const proj = a.projectPath ? db.getProject(a.projectPath) : null;
54
- return { ok: true, result: failures.list(proj?.id ?? null, Math.min(50, a.limit ?? 20)) };
55
- }
56
- case 'wyrm_failure_record': {
57
- const a = args;
58
- const proj = a.projectPath ? db.getProject(a.projectPath) : null;
59
- return { ok: true, result: failures.record({ project_id: proj?.id ?? null, scope: a.scope, target: a.target, description: a.description, why_failed: a.why_failed, severity: a.severity }) };
60
- }
61
- case 'wyrm_symbol_search': {
62
- const a = args;
63
- if (!a.symbol)
64
- return { ok: false, error: 'symbol required' };
65
- const proj = a.projectPath ? db.getProject(a.projectPath) : null;
66
- return { ok: true, result: symbols.search(a.symbol, { projectId: proj?.id, kind: a.kind, language: a.language, exact: a.exact, limit: Math.min(50, a.limit ?? 20) }) };
67
- }
68
- case 'wyrm_symbol_callers': {
69
- const a = args;
70
- const proj = a.projectPath ? db.getProject(a.projectPath) : null;
71
- return { ok: true, result: symbols.callers(a.symbol, proj?.id).slice(0, 30) };
72
- }
73
- case 'wyrm_symbol_stats': {
74
- const a = args;
75
- const proj = a.projectPath ? db.getProject(a.projectPath) : null;
76
- return { ok: true, result: symbols.stats(proj?.id) };
77
- }
78
- case 'wyrm_session_rehydrate': {
79
- const a = args;
80
- const brief = rehydration.rehydrate(a.session_id);
81
- return { ok: brief != null, result: brief, error: brief ? undefined : 'session not found' };
82
- }
83
- case 'wyrm_decision_upstream': {
84
- const a = args;
85
- return { ok: true, result: causality.upstreamOf(a.kind, a.id) };
86
- }
87
- case 'wyrm_decision_downstream': {
88
- const a = args;
89
- return { ok: true, result: causality.downstreamOf(a.kind, a.id) };
90
- }
91
- case 'wyrm_decided_because': {
92
- const a = args;
93
- const proj = db.getProject(a.projectPath);
94
- if (!proj)
95
- return { ok: false, error: 'project not found' };
96
- return { ok: true, result: causality.link({ project_id: proj.id, ...a }) };
97
- }
98
- case 'wyrm_presence_list': {
99
- const a = args;
100
- const proj = a.projectPath ? db.getProject(a.projectPath) : null;
101
- return { ok: true, result: presence.liveAgents(proj?.id ?? null) };
102
- }
103
- case 'wyrm_sync_conflicts': {
104
- return { ok: true, result: federation.unresolvedConflicts(20) };
105
- }
106
- case 'wyrm_quest_add': {
107
- const a = args;
108
- const proj = db.getProject(a.projectPath);
109
- if (!proj)
110
- return { ok: false, error: 'project not found' };
111
- const q = db.addQuest(proj.id, a.title, a.description, a.priority || 'medium', a.tags);
112
- return { ok: true, result: { id: q.id, title: q.title } };
113
- }
114
- case 'wyrm_quest_complete': {
115
- const a = args;
116
- const q = db.updateQuest(a.questId, 'completed');
117
- return { ok: true, result: { id: q.id, status: 'completed' } };
118
- }
119
- case 'wyrm_project_context': {
120
- const a = args;
121
- const proj = db.getProject(a.projectPath);
122
- if (!proj)
123
- return { ok: false, error: 'project not found' };
124
- // Summary: open quests + latest session
125
- const ctx = {
126
- project: proj.name,
127
- quests: db.getAllPendingQuests().filter(q => q.project_id === proj.id).slice(0, 10),
128
- last_session: db.getDatabase().prepare('SELECT id, date, summary FROM sessions WHERE project_id = ? ORDER BY date DESC LIMIT 1').get(proj.id),
129
- };
130
- return { ok: true, result: ctx };
131
- }
132
- // ── Memory ops the OODA loop is whitelisted for (SAFE_INTERNAL_TOOLS).
133
- // These were promised by the whitelist but had no dispatch case until 6.8.1,
134
- // so the loop silently failed on them. Kept in lockstep by
135
- // tests/tool-surface-integrity.test.ts.
136
- case 'wyrm_global_context': {
137
- const a = args;
138
- const projects = db.getAllProjects(a.maxProjects ?? 20).map((p) => ({
139
- name: p.name, stack: p.stack ?? null, stats: db.getProjectStats(p.id),
140
- }));
141
- const result = { globalContext: db.getAllGlobalContext(), projects };
142
- if (a.includeQuests)
143
- result.quests = db.getAllPendingQuests().slice(0, 20);
144
- return { ok: true, result };
145
- }
146
- case 'wyrm_recall': {
147
- const a = args;
148
- const proj = a.projectPath ? db.getProject(a.projectPath) : null;
149
- if (!proj)
150
- return { ok: false, error: 'project not found' };
151
- if (!a.query)
152
- return { ok: false, error: 'query required' };
153
- const results = memory.recall(proj.id, a.query, { kind: a.kind, limit: Math.min(20, a.limit ?? 10), minConfidence: a.minConfidence });
154
- return { ok: true, result: results.map((r) => ({ id: r.artifact.id, kind: r.artifact.kind, problem: r.artifact.problem, validated_fix: r.artifact.validated_fix, relevance: r.relevance_score })) };
155
- }
156
- case 'wyrm_remember': {
157
- const a = args;
158
- const proj = a.projectPath ? db.getProject(a.projectPath) : null;
159
- if (!proj)
160
- return { ok: false, error: 'project not found' };
161
- if (!a.problem)
162
- return { ok: false, error: 'problem required' };
163
- const art = memory.add(proj.id, { kind: a.kind, problem: a.problem, constraints: a.constraints, validatedFix: a.validatedFix, whyItWorked: a.whyItWorked, outcome: a.outcome, tags: a.tags, confidence: a.confidence, sourceSessionId: a.sourceSessionId });
164
- return { ok: true, result: { id: art.id, kind: art.kind } };
165
- }
166
- case 'wyrm_distill': {
167
- const a = args;
168
- const proj = a.projectPath ? db.getProject(a.projectPath) : null;
169
- if (!proj)
170
- return { ok: false, error: 'project not found' };
171
- if (!a.candidates?.length)
172
- return { ok: false, error: 'candidates required' };
173
- const ids = a.candidates.map((c) => memory.add(proj.id, { kind: c.kind, problem: c.title, validatedFix: c.content, tags: c.tags ?? [], confidence: c.confidence ?? 0.7, needsReview: 1 }).id);
174
- return { ok: true, result: { queued: ids.length, ids } };
175
- }
176
- case 'wyrm_capture': {
177
- const a = args;
178
- if (!a.content)
179
- return { ok: false, error: 'content required' };
180
- let cls = classifyCapture(a.content);
181
- if (a.mode && a.mode !== 'auto') {
182
- const sub = { quest: 'quest', truth: 'decision', memory: 'pattern' };
183
- cls = { type: a.mode, subtype: sub[a.mode] ?? a.mode, confidence: 100, reasoning: `mode:${a.mode}` };
184
- }
185
- const pid = a.project_id ?? null;
186
- if (cls.type === 'quest') {
187
- if (pid === null)
188
- return { ok: false, error: 'project_id required for quest' };
189
- const q = db.addQuest(pid, a.content.slice(0, 200), '', 'medium', a.tags?.join(','));
190
- return { ok: true, result: { type: 'quest', id: q.id } };
191
- }
192
- if (cls.type === 'truth') {
193
- if (pid === null)
194
- return { ok: false, error: 'project_id required for truth' };
195
- if (a.mode === 'truth' || cls.confidence >= 100) {
196
- const t = groundTruths.set(pid, { category: 'decision', key: a.content.slice(0, 60), value: a.content });
197
- return { ok: true, result: { type: 'truth', id: t.id } };
198
- }
199
- const art = memory.add(pid, { kind: 'pattern', problem: a.content, tags: a.tags ?? [], confidence: cls.confidence / 100, needsReview: 1 });
200
- return { ok: true, result: { type: 'memory_review', id: art.id } };
201
- }
202
- if (pid === null)
203
- return { ok: false, error: 'project_id required for memory' };
204
- const art = memory.add(pid, { kind: 'pattern', problem: a.content, tags: a.tags ?? [], confidence: (cls.confidence ?? 70) / 100, needsReview: 1 });
205
- return { ok: true, result: { type: 'memory', id: art.id } };
206
- }
207
- default:
208
- return { ok: false, error: `Internal tool '${name}' not implemented in dispatcher` };
209
- }
210
- }
211
- catch (err) {
212
- return { ok: false, error: err.message };
213
- }
214
- };
215
- return internalDispatch;
216
- }
217
- //# sourceMappingURL=internal-dispatch.js.map
8
+ `).all(t.id):[]}}case"wyrm_failure_check":{const e=s,t=e.projectPath?r.getProject(e.projectPath):null;return{ok:!0,result:i.check(e.scope,e.target,e.description,t?.id??null)}}case"wyrm_failure_list":{const e=s,t=e.projectPath?r.getProject(e.projectPath):null;return{ok:!0,result:i.list(t?.id??null,Math.min(50,e.limit??20))}}case"wyrm_failure_record":{const e=s,t=e.projectPath?r.getProject(e.projectPath):null;return{ok:!0,result:i.record({project_id:t?.id??null,scope:e.scope,target:e.target,description:e.description,why_failed:e.why_failed,severity:e.severity})}}case"wyrm_symbol_search":{const e=s;if(!e.symbol)return{ok:!1,error:"symbol required"};const t=e.projectPath?r.getProject(e.projectPath):null;return{ok:!0,result:a.search(e.symbol,{projectId:t?.id,kind:e.kind,language:e.language,exact:e.exact,limit:Math.min(50,e.limit??20)})}}case"wyrm_symbol_callers":{const e=s,t=e.projectPath?r.getProject(e.projectPath):null;return{ok:!0,result:a.callers(e.symbol,t?.id).slice(0,30)}}case"wyrm_symbol_stats":{const e=s,t=e.projectPath?r.getProject(e.projectPath):null;return{ok:!0,result:a.stats(t?.id)}}case"wyrm_session_rehydrate":{const e=s,t=m.rehydrate(e.session_id);return{ok:t!=null,result:t,error:t?void 0:"session not found"}}case"wyrm_decision_upstream":{const e=s;return{ok:!0,result:u.upstreamOf(e.kind,e.id)}}case"wyrm_decision_downstream":{const e=s;return{ok:!0,result:u.downstreamOf(e.kind,e.id)}}case"wyrm_decided_because":{const e=s,t=r.getProject(e.projectPath);return t?{ok:!0,result:u.link({project_id:t.id,...e})}:{ok:!1,error:"project not found"}}case"wyrm_presence_list":{const e=s,t=e.projectPath?r.getProject(e.projectPath):null;return{ok:!0,result:f.liveAgents(t?.id??null)}}case"wyrm_sync_conflicts":return{ok:!0,result:j.unresolvedConflicts(20)};case"wyrm_quest_add":{const e=s,t=r.getProject(e.projectPath);if(!t)return{ok:!1,error:"project not found"};const o=r.addQuest(t.id,e.title,e.description,e.priority||"medium",e.tags);return{ok:!0,result:{id:o.id,title:o.title}}}case"wyrm_quest_complete":{const e=s;return{ok:!0,result:{id:r.updateQuest(e.questId,"completed").id,status:"completed"}}}case"wyrm_project_context":{const e=s,t=r.getProject(e.projectPath);return t?{ok:!0,result:{project:t.name,quests:r.getAllPendingQuests().filter(n=>n.project_id===t.id).slice(0,10),last_session:r.getDatabase().prepare("SELECT id, date, summary FROM sessions WHERE project_id = ? ORDER BY date DESC LIMIT 1").get(t.id)}}:{ok:!1,error:"project not found"}}case"wyrm_global_context":{const e=s,t=r.getAllProjects(e.maxProjects??20).map(n=>({name:n.name,stack:n.stack??null,stats:r.getProjectStats(n.id)})),o={globalContext:r.getAllGlobalContext(),projects:t};return e.includeQuests&&(o.quests=r.getAllPendingQuests().slice(0,20)),{ok:!0,result:o}}case"wyrm_recall":{const e=s,t=e.projectPath?r.getProject(e.projectPath):null;return t?e.query?{ok:!0,result:c.recall(t.id,e.query,{kind:e.kind,limit:Math.min(20,e.limit??10),minConfidence:e.minConfidence}).map(n=>({id:n.artifact.id,kind:n.artifact.kind,problem:n.artifact.problem,validated_fix:n.artifact.validated_fix,relevance:n.relevance_score}))}:{ok:!1,error:"query required"}:{ok:!1,error:"project not found"}}case"wyrm_remember":{const e=s,t=e.projectPath?r.getProject(e.projectPath):null;if(!t)return{ok:!1,error:"project not found"};if(!e.problem)return{ok:!1,error:"problem required"};const o=c.add(t.id,{kind:e.kind,problem:e.problem,constraints:e.constraints,validatedFix:e.validatedFix,whyItWorked:e.whyItWorked,outcome:e.outcome,tags:e.tags,confidence:e.confidence,sourceSessionId:e.sourceSessionId});return{ok:!0,result:{id:o.id,kind:o.kind}}}case"wyrm_distill":{const e=s,t=e.projectPath?r.getProject(e.projectPath):null;if(!t)return{ok:!1,error:"project not found"};if(!e.candidates?.length)return{ok:!1,error:"candidates required"};const o=e.candidates.map(n=>c.add(t.id,{kind:n.kind,problem:n.title,validatedFix:n.content,tags:n.tags??[],confidence:n.confidence??.7,needsReview:1}).id);return{ok:!0,result:{queued:o.length,ids:o}}}case"wyrm_capture":{const e=s;if(!e.content)return{ok:!1,error:"content required"};let t=_(e.content);if(e.mode&&e.mode!=="auto"){const l={quest:"quest",truth:"decision",memory:"pattern"};t={type:e.mode,subtype:l[e.mode]??e.mode,confidence:100,reasoning:`mode:${e.mode}`}}const o=e.project_id??null;return t.type==="quest"?o===null?{ok:!1,error:"project_id required for quest"}:{ok:!0,result:{type:"quest",id:r.addQuest(o,e.content.slice(0,200),"","medium",e.tags?.join(",")).id}}:t.type==="truth"?o===null?{ok:!1,error:"project_id required for truth"}:e.mode==="truth"||t.confidence>=100?{ok:!0,result:{type:"truth",id:y.set(o,{category:"decision",key:e.content.slice(0,60),value:e.content}).id}}:{ok:!0,result:{type:"memory_review",id:c.add(o,{kind:"pattern",problem:e.content,tags:e.tags??[],confidence:t.confidence/100,needsReview:1}).id}}:o===null?{ok:!1,error:"project_id required for memory"}:{ok:!0,result:{type:"memory",id:c.add(o,{kind:"pattern",problem:e.content,tags:e.tags??[],confidence:(t.confidence??70)/100,needsReview:1}).id}}}default:return{ok:!1,error:`Internal tool '${d}' not implemented in dispatcher`}}}catch(e){return{ok:!1,error:e.message}}}}export{q as makeInternalDispatch};
package/dist/keyset.js CHANGED
@@ -1,110 +1 @@
1
- /**
2
- * Keyset (cursor) pagination — the v7 F4 T035 context-economy primitive.
3
- *
4
- * THE MOVE: a big listing returns a PAGE plus an opaque `nextCursor`, not the
5
- * whole table. The cursor is a COMPOSITE `(sortKey, id)` — never a bare
6
- * sortKey. This is deliberate: the wyrm-cloud sync keyset bug (fixed at
7
- * cloud/client.ts:299 with a `"<updated_at>:<id>"` composite) is the
8
- * cautionary tale — a bare `sortKey` cursor SILENTLY DROPS every row that
9
- * shares the boundary row's sort value (e.g. three artifacts with identical
10
- * `confidence` straddling a page edge). The id tiebreak makes the keyset
11
- * total-ordered, so `(sortKey, id) > (lastSortKey, lastId)` resumes EXACTLY
12
- * after the last emitted row, dropping nothing and repeating nothing.
13
- *
14
- * The cursor is OPAQUE on the wire: a base64url-encoded `"<sortKey>:<id>"`
15
- * string. Callers MUST treat it as a token (round-trip it verbatim); they
16
- * never parse it. A malformed/garbage cursor decodes to `null` and the caller
17
- * degrades to "start from the first page" (never throws on the wire — a
18
- * resource-less or buggy client can't be wedged by a bad token).
19
- *
20
- * Article III: pure, deterministic, no network, no clock, no LLM. Same rows in
21
- * ⇒ same page + same cursor out. Article VII: hard default + max caps so an
22
- * unbounded `limit` can never be requested; the encode/decode is total (no
23
- * throw on adversarial input).
24
- *
25
- * @copyright 2026 Ghost Protocol (Pvt) Ltd.
26
- * @license AGPL-3.0-or-later — dual-licensed; commercial terms: ghosts.lk@proton.me. See LICENSE.
27
- */
28
- /** Default page size when a caller passes no `limit`. */
29
- export const DEFAULT_PAGE_SIZE = 50;
30
- /** Absolute ceiling on a single page — a caller can never exceed this. */
31
- export const MAX_PAGE_SIZE = 200;
32
- /**
33
- * Clamp a requested limit into `[1, MAX_PAGE_SIZE]`, defaulting to
34
- * `DEFAULT_PAGE_SIZE` when absent/NaN/≤0. The hard cap holds regardless of
35
- * what the caller asks for (Article VII: bounded by construction).
36
- */
37
- export function clampPageSize(requested, opts = {}) {
38
- const def = opts.defaultSize ?? DEFAULT_PAGE_SIZE;
39
- const max = opts.maxSize ?? MAX_PAGE_SIZE;
40
- if (requested == null || !Number.isFinite(requested) || requested <= 0)
41
- return Math.min(def, max);
42
- return Math.min(Math.floor(requested), max);
43
- }
44
- /**
45
- * Encode a composite `(sortKey, id)` anchor into an opaque base64url token.
46
- * The sortKey is length-prefixed (`<len>.<sortKey>:<id>`) so a sortKey that
47
- * itself contains a `:` (e.g. an ISO timestamp `2026-06-13T10:00:00`) round-
48
- * trips losslessly — decode reads exactly `<len>` chars for the sortKey.
49
- */
50
- export function encodeCursor(cursor) {
51
- const sk = String(cursor.sortKey);
52
- const raw = `${sk.length}.${sk}:${cursor.id}`;
53
- return Buffer.from(raw, 'utf8').toString('base64url');
54
- }
55
- /**
56
- * Decode an opaque cursor token back into `(sortKey, id)`. Returns `null` for
57
- * any malformed/garbage/empty token — the caller degrades to the first page
58
- * rather than throwing on the wire (Article VII: total on adversarial input).
59
- */
60
- export function decodeCursor(token) {
61
- if (typeof token !== 'string' || token.length === 0)
62
- return null;
63
- let raw;
64
- try {
65
- raw = Buffer.from(token, 'base64url').toString('utf8');
66
- }
67
- catch {
68
- return null;
69
- }
70
- // Grammar: <len>.<sortKey(len chars)>:<id>
71
- const dot = raw.indexOf('.');
72
- if (dot <= 0)
73
- return null;
74
- const lenStr = raw.slice(0, dot);
75
- if (!/^\d{1,9}$/.test(lenStr))
76
- return null;
77
- const len = Number(lenStr);
78
- const after = raw.slice(dot + 1);
79
- if (after.length < len + 1)
80
- return null;
81
- const sortKey = after.slice(0, len);
82
- const sep = after.charAt(len);
83
- if (sep !== ':')
84
- return null;
85
- const idStr = after.slice(len + 1);
86
- if (!/^\d{1,15}$/.test(idStr))
87
- return null;
88
- const id = Number(idStr);
89
- if (!Number.isSafeInteger(id) || id < 0)
90
- return null;
91
- return { sortKey, id };
92
- }
93
- /**
94
- * Build a page from an OVER-FETCHED row array. Call the underlying query with
95
- * `limit = pageSize + 1`; pass the result here. If `pageSize + 1` rows came
96
- * back there is a further page: slice to `pageSize` and mint a `nextCursor`
97
- * from the LAST emitted row via `anchorOf`. The over-fetch-by-one is how we
98
- * know "is there more" without a second COUNT query.
99
- *
100
- * `anchorOf` must return the SAME `(sortKey, id)` the query's WHERE/ORDER use,
101
- * or pages will skip/repeat. Keep them in lockstep at the call site.
102
- */
103
- export function buildPage(rows, pageSize, anchorOf) {
104
- if (rows.length <= pageSize)
105
- return { items: rows };
106
- const items = rows.slice(0, pageSize);
107
- const last = items[items.length - 1];
108
- return { items, nextCursor: encodeCursor(anchorOf(last)) };
109
- }
110
- //# sourceMappingURL=keyset.js.map
1
+ const a=50,m=200;function S(t,n={}){const r=n.defaultSize??50,e=n.maxSize??200;return t==null||!Number.isFinite(t)||t<=0?Math.min(r,e):Math.min(Math.floor(t),e)}function c(t){const n=String(t.sortKey),r=`${n.length}.${n}:${t.id}`;return Buffer.from(r,"utf8").toString("base64url")}function g(t){if(typeof t!="string"||t.length===0)return null;let n;try{n=Buffer.from(t,"base64url").toString("utf8")}catch{return null}const r=n.indexOf(".");if(r<=0)return null;const e=n.slice(0,r);if(!/^\d{1,9}$/.test(e))return null;const l=Number(e),u=n.slice(r+1);if(u.length<l+1)return null;const s=u.slice(0,l);if(u.charAt(l)!==":")return null;const o=u.slice(l+1);if(!/^\d{1,15}$/.test(o))return null;const i=Number(o);return!Number.isSafeInteger(i)||i<0?null:{sortKey:s,id:i}}function d(t,n,r){if(t.length<=n)return{items:t};const e=t.slice(0,n),l=e[e.length-1];return{items:e,nextCursor:c(r(l))}}export{a as DEFAULT_PAGE_SIZE,m as MAX_PAGE_SIZE,d as buildPage,S as clampPageSize,g as decodeCursor,c as encodeCursor};
@@ -1,139 +1,31 @@
1
- /**
2
- * Wyrm Knowledge Graph — Entity & Relationship Management
3
- *
4
- * @copyright 2026 Ghost Protocol (Pvt) Ltd.
5
- * @license AGPL-3.0-or-later — dual-licensed; commercial terms: ghosts.lk@proton.me. See LICENSE.
6
- *
7
- * Manual-first knowledge graph with provenance tracking:
8
- * - Named entities with types and metadata
9
- * - Typed, directed relationships between entities
10
- * - Aliases for deduplication
11
- * - Graph traversal via recursive CTEs (neighborhood, paths)
12
- * - FTS5 search on entity names
13
- */
14
- const MAX_DEPTH = 5;
15
- const MAX_RESULTS = 200;
16
- /**
17
- * Knowledge graph operations backed by SQLite.
18
- */
19
- export class KnowledgeGraph {
20
- db;
21
- constructor(db) {
22
- this.db = db;
23
- }
24
- // ==================== ENTITIES ====================
25
- addEntity(projectId, name, type, metadata, createdBy) {
26
- const result = this.db.prepare(`
1
+ const p=5,_=200;class l{db;constructor(t){this.db=t}addEntity(t,e,i,n,r){const s=this.db.prepare(`
27
2
  INSERT INTO entities (project_id, name, type, metadata, created_by)
28
3
  VALUES (?, ?, ?, ?, ?)
29
- `).run(projectId, name.trim(), type.trim(), metadata ?? null, createdBy ?? 'local');
30
- return this.db.prepare('SELECT * FROM entities WHERE id = ?').get(result.lastInsertRowid);
31
- }
32
- getEntity(id) {
33
- return this.db.prepare('SELECT * FROM entities WHERE id = ?').get(id) ?? null;
34
- }
35
- findEntity(projectId, name, type) {
36
- if (type) {
37
- return this.db.prepare('SELECT * FROM entities WHERE project_id = ? AND name = ? AND type = ?').get(projectId, name, type) ?? null;
38
- }
39
- return this.db.prepare('SELECT * FROM entities WHERE project_id = ? AND name = ?').get(projectId, name) ?? null;
40
- }
41
- updateEntity(id, updates) {
42
- const entity = this.getEntity(id);
43
- if (!entity)
44
- return null;
45
- this.db.prepare(`
4
+ `).run(t,e.trim(),i.trim(),n??null,r??"local");return this.db.prepare("SELECT * FROM entities WHERE id = ?").get(s.lastInsertRowid)}getEntity(t){return this.db.prepare("SELECT * FROM entities WHERE id = ?").get(t)??null}findEntity(t,e,i){return i?this.db.prepare("SELECT * FROM entities WHERE project_id = ? AND name = ? AND type = ?").get(t,e,i)??null:this.db.prepare("SELECT * FROM entities WHERE project_id = ? AND name = ?").get(t,e)??null}updateEntity(t,e){return this.getEntity(t)?(this.db.prepare(`
46
5
  UPDATE entities SET
47
6
  name = COALESCE(?, name),
48
7
  type = COALESCE(?, type),
49
8
  metadata = COALESCE(?, metadata),
50
9
  updated_at = datetime('now')
51
10
  WHERE id = ?
52
- `).run(updates.name ?? null, updates.type ?? null, updates.metadata ?? null, id);
53
- return this.getEntity(id);
54
- }
55
- deleteEntity(id) {
56
- const result = this.db.prepare('DELETE FROM entities WHERE id = ?').run(id);
57
- return result.changes > 0;
58
- }
59
- listEntities(projectId, options) {
60
- let sql = 'SELECT * FROM entities WHERE project_id = ?';
61
- const params = [projectId];
62
- if (options?.type) {
63
- sql += ' AND type = ?';
64
- params.push(options.type);
65
- }
66
- sql += ' ORDER BY name ASC';
67
- sql += ` LIMIT ? OFFSET ?`;
68
- params.push(options?.limit ?? 50, options?.offset ?? 0);
69
- return this.db.prepare(sql).all(...params);
70
- }
71
- searchEntities(projectId, query, limit = 20) {
72
- // Try FTS first, fall back to LIKE
73
- try {
74
- const ftsResults = this.db.prepare(`
11
+ `).run(e.name??null,e.type??null,e.metadata??null,t),this.getEntity(t)):null}deleteEntity(t){return this.db.prepare("DELETE FROM entities WHERE id = ?").run(t).changes>0}listEntities(t,e){let i="SELECT * FROM entities WHERE project_id = ?";const n=[t];return e?.type&&(i+=" AND type = ?",n.push(e.type)),i+=" ORDER BY name ASC",i+=" LIMIT ? OFFSET ?",n.push(e?.limit??50,e?.offset??0),this.db.prepare(i).all(...n)}searchEntities(t,e,i=20){try{const n=this.db.prepare(`
75
12
  SELECT e.* FROM entities e
76
13
  JOIN entities_fts f ON f.rowid = e.id
77
14
  WHERE f.entities_fts MATCH ? AND e.project_id = ?
78
15
  LIMIT ?
79
- `).all(query, projectId, limit);
80
- if (ftsResults.length > 0)
81
- return ftsResults;
82
- }
83
- catch {
84
- // FTS query syntax error — fall back to LIKE
85
- }
86
- return this.db.prepare(`
16
+ `).all(e,t,i);if(n.length>0)return n}catch{}return this.db.prepare(`
87
17
  SELECT * FROM entities WHERE project_id = ? AND (name LIKE ? OR type LIKE ?)
88
18
  ORDER BY name ASC LIMIT ?
89
- `).all(projectId, `%${query}%`, `%${query}%`, limit);
90
- }
91
- // ==================== ALIASES ====================
92
- addAlias(entityId, alias) {
93
- this.db.prepare('INSERT OR IGNORE INTO entity_aliases (entity_id, alias) VALUES (?, ?)').run(entityId, alias.trim());
94
- }
95
- getAliases(entityId) {
96
- const rows = this.db.prepare('SELECT alias FROM entity_aliases WHERE entity_id = ?').all(entityId);
97
- return rows.map(r => r.alias);
98
- }
99
- findByAlias(projectId, alias) {
100
- return this.db.prepare(`
19
+ `).all(t,`%${e}%`,`%${e}%`,i)}addAlias(t,e){this.db.prepare("INSERT OR IGNORE INTO entity_aliases (entity_id, alias) VALUES (?, ?)").run(t,e.trim())}getAliases(t){return this.db.prepare("SELECT alias FROM entity_aliases WHERE entity_id = ?").all(t).map(i=>i.alias)}findByAlias(t,e){return this.db.prepare(`
101
20
  SELECT e.* FROM entities e
102
21
  JOIN entity_aliases a ON a.entity_id = e.id
103
22
  WHERE e.project_id = ? AND a.alias = ?
104
- `).get(projectId, alias) ?? null;
105
- }
106
- // ==================== RELATIONSHIPS ====================
107
- addRelationship(projectId, sourceId, targetId, type, options) {
108
- const result = this.db.prepare(`
23
+ `).get(t,e)??null}addRelationship(t,e,i,n,r){const s=this.db.prepare(`
109
24
  INSERT INTO relationships (
110
25
  project_id, source_entity_id, target_entity_id, relationship_type,
111
26
  weight, confidence, source_memory, extraction_method, metadata, created_by
112
27
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
113
- `).run(projectId, sourceId, targetId, type.trim(), options?.weight ?? 1.0, options?.confidence ?? 1.0, options?.sourceMemory ?? null, options?.extractionMethod ?? 'manual', options?.metadata ?? null, options?.createdBy ?? 'local');
114
- return this.db.prepare('SELECT * FROM relationships WHERE id = ?').get(result.lastInsertRowid);
115
- }
116
- getRelationships(entityId, direction = 'both') {
117
- switch (direction) {
118
- case 'outgoing':
119
- return this.db.prepare('SELECT * FROM relationships WHERE source_entity_id = ?').all(entityId);
120
- case 'incoming':
121
- return this.db.prepare('SELECT * FROM relationships WHERE target_entity_id = ?').all(entityId);
122
- case 'both':
123
- return this.db.prepare('SELECT * FROM relationships WHERE source_entity_id = ? OR target_entity_id = ?').all(entityId, entityId);
124
- }
125
- }
126
- deleteRelationship(id) {
127
- return this.db.prepare('DELETE FROM relationships WHERE id = ?').run(id).changes > 0;
128
- }
129
- // ==================== GRAPH QUERIES ====================
130
- /**
131
- * Get neighborhood — all nodes within N hops of a given entity.
132
- * Uses recursive CTE with cycle detection.
133
- */
134
- getNeighborhood(entityId, maxDepth = 2) {
135
- const depth = Math.min(maxDepth, MAX_DEPTH);
136
- const nodes = this.db.prepare(`
28
+ `).run(t,e,i,n.trim(),r?.weight??1,r?.confidence??1,r?.sourceMemory??null,r?.extractionMethod??"manual",r?.metadata??null,r?.createdBy??"local");return this.db.prepare("SELECT * FROM relationships WHERE id = ?").get(s.lastInsertRowid)}getRelationships(t,e="both"){switch(e){case"outgoing":return this.db.prepare("SELECT * FROM relationships WHERE source_entity_id = ?").all(t);case"incoming":return this.db.prepare("SELECT * FROM relationships WHERE target_entity_id = ?").all(t);case"both":return this.db.prepare("SELECT * FROM relationships WHERE source_entity_id = ? OR target_entity_id = ?").all(t,t)}}deleteRelationship(t){return this.db.prepare("DELETE FROM relationships WHERE id = ?").run(t).changes>0}getNeighborhood(t,e=2){const i=Math.min(e,5),n=this.db.prepare(`
137
29
  WITH RECURSIVE neighbors(id, depth, visited) AS (
138
30
  SELECT ?, 0, ',' || ? || ','
139
31
  UNION ALL
@@ -152,31 +44,16 @@ export class KnowledgeGraph {
152
44
  GROUP BY e.id
153
45
  ORDER BY depth ASC
154
46
  LIMIT ?
155
- `).all(entityId, entityId, depth, MAX_RESULTS);
156
- // Get edges between all discovered nodes
157
- const nodeIds = nodes.map(n => n.id);
158
- if (nodeIds.length === 0)
159
- return { nodes: [], edges: [] };
160
- const placeholders = nodeIds.map(() => '?').join(',');
161
- const edges = this.db.prepare(`
47
+ `).all(t,t,i,200),r=n.map(a=>a.id);if(r.length===0)return{nodes:[],edges:[]};const s=r.map(()=>"?").join(","),E=this.db.prepare(`
162
48
  SELECT r.source_entity_id as source_id, s.name as source_name,
163
49
  r.target_entity_id as target_id, t.name as target_name,
164
50
  r.relationship_type, r.weight
165
51
  FROM relationships r
166
52
  JOIN entities s ON s.id = r.source_entity_id
167
53
  JOIN entities t ON t.id = r.target_entity_id
168
- WHERE r.source_entity_id IN (${placeholders})
169
- AND r.target_entity_id IN (${placeholders})
170
- `).all(...nodeIds, ...nodeIds);
171
- return { nodes, edges };
172
- }
173
- /**
174
- * Find path between two entities using BFS with cycle detection.
175
- * Returns the shortest path as a list of entity IDs, or null if no path exists.
176
- */
177
- findPath(sourceId, targetId, maxDepth = 5) {
178
- const depth = Math.min(maxDepth, MAX_DEPTH);
179
- const rows = this.db.prepare(`
54
+ WHERE r.source_entity_id IN (${s})
55
+ AND r.target_entity_id IN (${s})
56
+ `).all(...r,...r);return{nodes:n,edges:E}}findPath(t,e,i=5){const n=Math.min(i,5),r=this.db.prepare(`
180
57
  WITH RECURSIVE path_finder(id, path, depth) AS (
181
58
  SELECT ?, CAST(? AS TEXT), 0
182
59
  UNION ALL
@@ -191,45 +68,4 @@ export class KnowledgeGraph {
191
68
  AND INSTR(',' || pf.path || ',', ',' || CASE WHEN r.source_entity_id = pf.id THEN r.target_entity_id ELSE r.source_entity_id END || ',') = 0
192
69
  )
193
70
  SELECT path FROM path_finder WHERE id = ? LIMIT 1
194
- `).all(sourceId, sourceId, depth, targetId);
195
- if (rows.length === 0)
196
- return null;
197
- return rows[0].path.split(',').map(Number);
198
- }
199
- /**
200
- * Merge two entities: moves all relationships and aliases from source to target,
201
- * adds source name as alias on target, then deletes source.
202
- */
203
- mergeEntities(sourceId, targetId) {
204
- const source = this.getEntity(sourceId);
205
- const target = this.getEntity(targetId);
206
- if (!source || !target)
207
- return null;
208
- const merge = this.db.transaction(() => {
209
- // Add source name as alias on target
210
- this.addAlias(targetId, source.name);
211
- // Move source's aliases to target
212
- this.db.prepare('UPDATE entity_aliases SET entity_id = ? WHERE entity_id = ?').run(targetId, sourceId);
213
- // Redirect relationships from source to target
214
- this.db.prepare('UPDATE relationships SET source_entity_id = ? WHERE source_entity_id = ?').run(targetId, sourceId);
215
- this.db.prepare('UPDATE relationships SET target_entity_id = ? WHERE target_entity_id = ?').run(targetId, sourceId);
216
- // Remove self-loops created by merge
217
- this.db.prepare('DELETE FROM relationships WHERE source_entity_id = target_entity_id').run();
218
- // Delete source entity
219
- this.deleteEntity(sourceId);
220
- });
221
- merge();
222
- return this.getEntity(targetId);
223
- }
224
- // ==================== STATS ====================
225
- getStats(projectId) {
226
- const entities = this.db.prepare('SELECT COUNT(*) as c FROM entities WHERE project_id = ?').get(projectId).c;
227
- const relationships = this.db.prepare('SELECT COUNT(*) as c FROM relationships WHERE project_id = ?').get(projectId).c;
228
- const typeRows = this.db.prepare('SELECT type, COUNT(*) as cnt FROM entities WHERE project_id = ? GROUP BY type').all(projectId);
229
- const entityTypes = {};
230
- for (const r of typeRows)
231
- entityTypes[r.type] = r.cnt;
232
- return { entities, relationships, entityTypes };
233
- }
234
- }
235
- //# sourceMappingURL=knowledge-graph.js.map
71
+ `).all(t,t,n,e);return r.length===0?null:r[0].path.split(",").map(Number)}mergeEntities(t,e){const i=this.getEntity(t),n=this.getEntity(e);return!i||!n?null:(this.db.transaction(()=>{this.addAlias(e,i.name),this.db.prepare("UPDATE entity_aliases SET entity_id = ? WHERE entity_id = ?").run(e,t),this.db.prepare("UPDATE relationships SET source_entity_id = ? WHERE source_entity_id = ?").run(e,t),this.db.prepare("UPDATE relationships SET target_entity_id = ? WHERE target_entity_id = ?").run(e,t),this.db.prepare("DELETE FROM relationships WHERE source_entity_id = target_entity_id").run(),this.deleteEntity(t)})(),this.getEntity(e))}getStats(t){const e=this.db.prepare("SELECT COUNT(*) as c FROM entities WHERE project_id = ?").get(t).c,i=this.db.prepare("SELECT COUNT(*) as c FROM relationships WHERE project_id = ?").get(t).c,n=this.db.prepare("SELECT type, COUNT(*) as cnt FROM entities WHERE project_id = ? GROUP BY type").all(t),r={};for(const s of n)r[s.type]=s.cnt;return{entities:e,relationships:i,entityTypes:r}}}export{l as KnowledgeGraph};