wyrm-mcp 3.7.2 → 5.0.0

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 (77) hide show
  1. package/dist/agent-loop.d.ts +86 -0
  2. package/dist/agent-loop.d.ts.map +1 -0
  3. package/dist/agent-loop.js +321 -0
  4. package/dist/agent-loop.js.map +1 -0
  5. package/dist/audit.d.ts +82 -0
  6. package/dist/audit.d.ts.map +1 -0
  7. package/dist/audit.js +151 -0
  8. package/dist/audit.js.map +1 -0
  9. package/dist/causality.d.ts +76 -0
  10. package/dist/causality.d.ts.map +1 -0
  11. package/dist/causality.js +115 -0
  12. package/dist/causality.js.map +1 -0
  13. package/dist/database.d.ts.map +1 -1
  14. package/dist/database.js +5 -0
  15. package/dist/database.js.map +1 -1
  16. package/dist/failure-patterns.d.ts +60 -0
  17. package/dist/failure-patterns.d.ts.map +1 -0
  18. package/dist/failure-patterns.js +117 -0
  19. package/dist/failure-patterns.js.map +1 -0
  20. package/dist/federation.d.ts +82 -0
  21. package/dist/federation.d.ts.map +1 -0
  22. package/dist/federation.js +220 -0
  23. package/dist/federation.js.map +1 -0
  24. package/dist/goals.d.ts +95 -0
  25. package/dist/goals.d.ts.map +1 -0
  26. package/dist/goals.js +146 -0
  27. package/dist/goals.js.map +1 -0
  28. package/dist/hours.d.ts +82 -0
  29. package/dist/hours.d.ts.map +1 -0
  30. package/dist/hours.js +162 -0
  31. package/dist/hours.js.map +1 -0
  32. package/dist/http-fast.d.ts.map +1 -1
  33. package/dist/http-fast.js +183 -1
  34. package/dist/http-fast.js.map +1 -1
  35. package/dist/http-server.js +1 -1
  36. package/dist/http-server.js.map +1 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1475 -150
  39. package/dist/index.js.map +1 -1
  40. package/dist/indexer.d.ts +2 -0
  41. package/dist/indexer.d.ts.map +1 -1
  42. package/dist/indexer.js +4 -0
  43. package/dist/indexer.js.map +1 -1
  44. package/dist/mcp-client.d.ts +76 -0
  45. package/dist/mcp-client.d.ts.map +1 -0
  46. package/dist/mcp-client.js +259 -0
  47. package/dist/mcp-client.js.map +1 -0
  48. package/dist/migrations.d.ts.map +1 -1
  49. package/dist/migrations.js +366 -1
  50. package/dist/migrations.js.map +1 -1
  51. package/dist/presence.d.ts +71 -0
  52. package/dist/presence.d.ts.map +1 -0
  53. package/dist/presence.js +133 -0
  54. package/dist/presence.js.map +1 -0
  55. package/dist/rehydration.d.ts +39 -0
  56. package/dist/rehydration.d.ts.map +1 -0
  57. package/dist/rehydration.js +178 -0
  58. package/dist/rehydration.js.map +1 -0
  59. package/dist/setup.js +0 -0
  60. package/dist/sub-agent.d.ts +58 -0
  61. package/dist/sub-agent.d.ts.map +1 -0
  62. package/dist/sub-agent.js +292 -0
  63. package/dist/sub-agent.js.map +1 -0
  64. package/dist/symbols.d.ts +65 -0
  65. package/dist/symbols.d.ts.map +1 -0
  66. package/dist/symbols.js +291 -0
  67. package/dist/symbols.js.map +1 -0
  68. package/dist/tool-analytics.d.ts +85 -0
  69. package/dist/tool-analytics.d.ts.map +1 -0
  70. package/dist/tool-analytics.js +161 -0
  71. package/dist/tool-analytics.js.map +1 -0
  72. package/dist/wyrm-cli.js +0 -0
  73. package/dist/wyrm-loop.d.ts +27 -0
  74. package/dist/wyrm-loop.d.ts.map +1 -0
  75. package/dist/wyrm-loop.js +162 -0
  76. package/dist/wyrm-loop.js.map +1 -0
  77. package/package.json +8 -7
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Agent loop — OODA + ReAct (Observe → Orient → Decide → Act).
3
+ *
4
+ * This is the thing that turns Wyrm from "memory tool" into "agent".
5
+ * Given a goal (or ad-hoc query), we run a multi-turn loop where:
6
+ *
7
+ * 1. Observe — assemble relevant context from Wyrm's own data
8
+ * (failures, truths, sessions, symbols, prior iterations)
9
+ * 2. Orient — LLM synthesises: "given this context, what's the state?"
10
+ * 3. Decide — LLM proposes ONE action: either a Wyrm tool call,
11
+ * an external MCP server call, or 'done' / 'block' / 'escalate'
12
+ * 4. Act — execute the proposed action, capture result
13
+ * 5. Loop — feed result back into the next Observe step
14
+ *
15
+ * Each iteration logs to `goal_iterations` if attached to a goal.
16
+ * Hard caps: `max_iterations` per goal, ~60s wall clock per iteration,
17
+ * actions matched against a whitelist (no `wyrm_audit_export` or other
18
+ * data-exfiltration tools from inside the loop).
19
+ *
20
+ * LLM protocol — JSON envelope works with both:
21
+ * - Ollama JSON mode (most models 7B+)
22
+ * - OpenAI native function calling (gpt-4o, gpt-4o-mini, ...)
23
+ *
24
+ * The response shape we ask for:
25
+ * {"thought":"...","action":"<tool_name>","args":{...}} or
26
+ * {"thought":"...","action":"done","summary":"..."} or
27
+ * {"thought":"...","action":"block","reason":"..."}
28
+ *
29
+ * @copyright 2026 Ghost Protocol (Pvt) Ltd. All Rights Reserved.
30
+ * @license Proprietary
31
+ */
32
+ import type Database from 'better-sqlite3';
33
+ import { OutboundMcpClient } from './mcp-client.js';
34
+ export interface AgentAction {
35
+ thought?: string;
36
+ action: string;
37
+ args?: Record<string, unknown>;
38
+ summary?: string;
39
+ reason?: string;
40
+ }
41
+ export interface IterationResult {
42
+ iteration_num: number;
43
+ observe_summary: string;
44
+ orient_summary: string;
45
+ decided: AgentAction;
46
+ action_result: string;
47
+ outcome: 'progressed' | 'blocked' | 'done' | 'error';
48
+ latency_ms: number;
49
+ tokens_in?: number;
50
+ tokens_out?: number;
51
+ model: string;
52
+ degraded: boolean;
53
+ }
54
+ export interface ToolDispatcher {
55
+ (toolName: string, args: Record<string, unknown>): Promise<{
56
+ ok: boolean;
57
+ result?: unknown;
58
+ error?: string;
59
+ }>;
60
+ }
61
+ export declare class AgentLoop {
62
+ private db;
63
+ private externalClient;
64
+ private internalToolDispatch;
65
+ private subAgent;
66
+ private goals;
67
+ private failures;
68
+ constructor(db: Database.Database, externalClient: OutboundMcpClient, internalToolDispatch: ToolDispatcher);
69
+ /** Run ONE OODA iteration on a goal. Returns the iteration result. */
70
+ iterate(goal_id: number, opts?: {
71
+ ollama_url?: string;
72
+ openai_api_key?: string;
73
+ model_override?: string;
74
+ }): Promise<IterationResult | null>;
75
+ /** Iterate until done / blocked / cap hit. Returns the array of iterations. */
76
+ pursue(goal_id: number, opts?: {
77
+ max_steps?: number;
78
+ ollama_url?: string;
79
+ openai_api_key?: string;
80
+ model_override?: string;
81
+ }): Promise<IterationResult[]>;
82
+ private observe;
83
+ private orientAndDecide;
84
+ private act;
85
+ }
86
+ //# sourceMappingURL=agent-loop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-loop.d.ts","sourceRoot":"","sources":["../src/agent-loop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAI3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAwBpD,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,WAAW,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,YAAY,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC/G;AAED,qBAAa,SAAS;IAMlB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,oBAAoB;IAP9B,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,QAAQ,CAAkB;gBAGxB,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,cAAc,EAAE,iBAAiB,EACjC,oBAAoB,EAAE,cAAc;IAO9C,sEAAsE;IAChE,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IA+EjJ,+EAA+E;IACzE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAgB/J,OAAO,CAAC,OAAO;YAwED,eAAe;YA0Ef,GAAG;CA4BlB"}
@@ -0,0 +1,321 @@
1
+ /**
2
+ * Agent loop — OODA + ReAct (Observe → Orient → Decide → Act).
3
+ *
4
+ * This is the thing that turns Wyrm from "memory tool" into "agent".
5
+ * Given a goal (or ad-hoc query), we run a multi-turn loop where:
6
+ *
7
+ * 1. Observe — assemble relevant context from Wyrm's own data
8
+ * (failures, truths, sessions, symbols, prior iterations)
9
+ * 2. Orient — LLM synthesises: "given this context, what's the state?"
10
+ * 3. Decide — LLM proposes ONE action: either a Wyrm tool call,
11
+ * an external MCP server call, or 'done' / 'block' / 'escalate'
12
+ * 4. Act — execute the proposed action, capture result
13
+ * 5. Loop — feed result back into the next Observe step
14
+ *
15
+ * Each iteration logs to `goal_iterations` if attached to a goal.
16
+ * Hard caps: `max_iterations` per goal, ~60s wall clock per iteration,
17
+ * actions matched against a whitelist (no `wyrm_audit_export` or other
18
+ * data-exfiltration tools from inside the loop).
19
+ *
20
+ * LLM protocol — JSON envelope works with both:
21
+ * - Ollama JSON mode (most models 7B+)
22
+ * - OpenAI native function calling (gpt-4o, gpt-4o-mini, ...)
23
+ *
24
+ * The response shape we ask for:
25
+ * {"thought":"...","action":"<tool_name>","args":{...}} or
26
+ * {"thought":"...","action":"done","summary":"..."} or
27
+ * {"thought":"...","action":"block","reason":"..."}
28
+ *
29
+ * @copyright 2026 Ghost Protocol (Pvt) Ltd. All Rights Reserved.
30
+ * @license Proprietary
31
+ */
32
+ import { Goals } from './goals.js';
33
+ import { SubAgent } from './sub-agent.js';
34
+ import { FailurePatterns } from './failure-patterns.js';
35
+ import { sanitizeFtsQuery } from './security.js';
36
+ const DEFAULT_MAX_ITERATIONS = 20;
37
+ const ITER_HARD_TIMEOUT_MS = 90_000;
38
+ // Tools the agent is allowed to call from inside the loop. Whitelist —
39
+ // excludes audit export, sync push, prune, encrypt setup, etc.
40
+ const SAFE_INTERNAL_TOOLS = new Set([
41
+ // Read-mostly
42
+ 'wyrm_search', 'wyrm_recall', 'wyrm_project_context', 'wyrm_global_context',
43
+ 'wyrm_truth_get', 'wyrm_all_quests', 'wyrm_failure_check', 'wyrm_failure_list',
44
+ 'wyrm_symbol_search', 'wyrm_symbol_callers', 'wyrm_symbol_stats',
45
+ 'wyrm_session_rehydrate', 'wyrm_decision_upstream', 'wyrm_decision_downstream',
46
+ 'wyrm_presence_list', 'wyrm_sync_conflicts',
47
+ // Bounded writes (operator can audit each via goal_iterations)
48
+ 'wyrm_quest_add', 'wyrm_failure_record', 'wyrm_decided_because',
49
+ 'wyrm_capture', 'wyrm_remember', 'wyrm_distill',
50
+ // Status updates
51
+ 'wyrm_quest_complete',
52
+ // External call gateway
53
+ 'wyrm_call_external',
54
+ ]);
55
+ export class AgentLoop {
56
+ db;
57
+ externalClient;
58
+ internalToolDispatch;
59
+ subAgent;
60
+ goals;
61
+ failures;
62
+ constructor(db, externalClient, internalToolDispatch) {
63
+ this.db = db;
64
+ this.externalClient = externalClient;
65
+ this.internalToolDispatch = internalToolDispatch;
66
+ this.subAgent = new SubAgent(db);
67
+ this.goals = new Goals(db);
68
+ this.failures = new FailurePatterns(db);
69
+ }
70
+ /** Run ONE OODA iteration on a goal. Returns the iteration result. */
71
+ async iterate(goal_id, opts) {
72
+ const goal = this.goals.get(goal_id);
73
+ if (!goal)
74
+ return null;
75
+ if (goal.status !== 'active')
76
+ return null;
77
+ if (goal.iterations_count >= goal.max_iterations) {
78
+ // Hit the cap — record a final 'blocked' iteration and abandon.
79
+ this.goals.recordIteration(goal_id, {
80
+ outcome: 'blocked',
81
+ notes: `Hit max_iterations=${goal.max_iterations}`,
82
+ latency_ms: 0,
83
+ });
84
+ this.goals.abandon(goal_id, `Exceeded max_iterations=${goal.max_iterations}`);
85
+ return null;
86
+ }
87
+ const start = Date.now();
88
+ // ---- 1. OBSERVE ----
89
+ const observation = this.observe(goal);
90
+ // ---- 2. ORIENT + DECIDE (one LLM call) ----
91
+ const { decided, orient_summary, model, tokens_in, tokens_out, degraded } = await this.orientAndDecide(goal, observation, opts);
92
+ // ---- 3. ACT ----
93
+ const actionResult = await this.act(decided);
94
+ const latency = Date.now() - start;
95
+ const outcome = decided.action === 'done' ? 'done'
96
+ : decided.action === 'block' || decided.action === 'escalate' ? 'blocked'
97
+ : actionResult.startsWith('ERROR:') ? 'error'
98
+ : 'progressed';
99
+ // ---- 4. RECORD ----
100
+ this.goals.recordIteration(goal_id, {
101
+ observe_summary: observation.summary,
102
+ orient_summary,
103
+ decided_action: JSON.stringify(decided),
104
+ action_result: actionResult.slice(0, 4000),
105
+ outcome,
106
+ latency_ms: latency,
107
+ tokens_in,
108
+ tokens_out,
109
+ model,
110
+ });
111
+ // Append an agent_actions row too — the higher-level audit
112
+ try {
113
+ this.db.prepare(`
114
+ INSERT INTO agent_actions
115
+ (actor, goal_id, action_kind, summary, result_status)
116
+ VALUES ('wyrm-loop', ?, ?, ?, ?)
117
+ `).run(goal_id, decided.action === 'done' || decided.action === 'block' ? decided.action : 'act', `${decided.action}: ${(decided.thought ?? decided.summary ?? '').slice(0, 200)}`, outcome === 'done' ? 'success'
118
+ : outcome === 'progressed' ? 'partial'
119
+ : outcome === 'blocked' ? 'noop'
120
+ : 'failure');
121
+ }
122
+ catch { /* best-effort */ }
123
+ return {
124
+ iteration_num: goal.iterations_count + 1,
125
+ observe_summary: observation.summary,
126
+ orient_summary,
127
+ decided,
128
+ action_result: actionResult,
129
+ outcome,
130
+ latency_ms: latency,
131
+ tokens_in,
132
+ tokens_out,
133
+ model,
134
+ degraded,
135
+ };
136
+ }
137
+ /** Iterate until done / blocked / cap hit. Returns the array of iterations. */
138
+ async pursue(goal_id, opts) {
139
+ const cap = Math.min(50, Math.max(1, opts?.max_steps ?? 10));
140
+ const out = [];
141
+ for (let i = 0; i < cap; i++) {
142
+ const r = await this.iterate(goal_id, opts);
143
+ if (!r)
144
+ break;
145
+ out.push(r);
146
+ if (r.outcome === 'done' || r.outcome === 'blocked' || r.outcome === 'error')
147
+ break;
148
+ }
149
+ return out;
150
+ }
151
+ // ============================================================
152
+ // OODA stages
153
+ // ============================================================
154
+ observe(goal) {
155
+ const parts = [];
156
+ const tags = [];
157
+ // Last 3 iterations — what's been tried already
158
+ const prior = this.goals.iterationsFor(goal.id, 3);
159
+ if (prior.length > 0) {
160
+ parts.push('## Recent iterations');
161
+ for (const it of prior.reverse()) {
162
+ parts.push(`- #${it.iteration_num} (${it.outcome}): ${it.notes ?? it.action_result?.slice(0, 200) ?? '(no notes)'}`);
163
+ }
164
+ tags.push(`iter:${prior.length}`);
165
+ }
166
+ // Project context (truths + open quests)
167
+ if (goal.project_id != null) {
168
+ try {
169
+ const truths = this.db.prepare(`
170
+ SELECT category, key, value, rationale FROM ground_truths
171
+ WHERE project_id = ? AND is_current = 1
172
+ ORDER BY confidence DESC LIMIT 10
173
+ `).all(goal.project_id);
174
+ if (truths.length > 0) {
175
+ parts.push('## Ground truths');
176
+ for (const t of truths) {
177
+ parts.push(`- ${t.category}.${t.key} = ${t.value}${t.rationale ? ` (${t.rationale})` : ''}`);
178
+ }
179
+ tags.push(`truths:${truths.length}`);
180
+ }
181
+ const quests = this.db.prepare(`
182
+ SELECT id, title, priority FROM quests
183
+ WHERE project_id = ? AND status IN ('pending','in_progress')
184
+ ORDER BY
185
+ CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1
186
+ WHEN 'medium' THEN 2 ELSE 3 END
187
+ LIMIT 8
188
+ `).all(goal.project_id);
189
+ if (quests.length > 0) {
190
+ parts.push('## Open quests');
191
+ for (const q of quests)
192
+ parts.push(`- #${q.id} [${q.priority}] ${q.title}`);
193
+ tags.push(`quests:${quests.length}`);
194
+ }
195
+ }
196
+ catch { /* tables may not exist on very old DBs */ }
197
+ }
198
+ // Failures around the goal topic
199
+ try {
200
+ const failsQuery = sanitizeFtsQuery(goal.title.slice(0, 100));
201
+ if (failsQuery) {
202
+ const fails = this.db.prepare(`
203
+ SELECT fp.id, fp.scope, fp.target, fp.description, fp.why_failed
204
+ FROM failure_patterns fp
205
+ JOIN failure_patterns_fts fts ON fts.rowid = fp.id
206
+ WHERE failure_patterns_fts MATCH ? AND fp.resolved = 0
207
+ ORDER BY fp.occurrences DESC LIMIT 5
208
+ `).all(failsQuery);
209
+ if (fails.length > 0) {
210
+ parts.push('## Known failures — DO NOT repeat');
211
+ for (const f of fails) {
212
+ parts.push(`- #${f.id} ${f.scope}:${f.target} — ${f.description}${f.why_failed ? ` (why: ${f.why_failed})` : ''}`);
213
+ }
214
+ tags.push(`fails:${fails.length}`);
215
+ }
216
+ }
217
+ }
218
+ catch { /* failure_patterns may not exist */ }
219
+ const detail = parts.join('\n');
220
+ const summary = tags.join(' ') || '(no signal)';
221
+ return { summary, detail };
222
+ }
223
+ async orientAndDecide(goal, observation, opts) {
224
+ const toolNames = Array.from(SAFE_INTERNAL_TOOLS).sort();
225
+ const prompt = [
226
+ `You are Wyrm, an autonomous agent pursuing this goal across multiple sessions.`,
227
+ ``,
228
+ `GOAL: ${goal.title}`,
229
+ goal.description ? `DESCRIPTION: ${goal.description}` : '',
230
+ goal.success_criteria ? `SUCCESS CRITERIA: ${goal.success_criteria}` : '',
231
+ goal.deadline ? `DEADLINE: ${goal.deadline}` : '',
232
+ ``,
233
+ `=== OBSERVED CONTEXT ===`,
234
+ observation.detail || '(no context — fresh start)',
235
+ `=== END CONTEXT ===`,
236
+ ``,
237
+ `Decide ONE next action. Respond with a single JSON object only. Schema:`,
238
+ ` {"thought": "<reasoning>", "action": "<one of: done | block | escalate | ${toolNames.join(' | ')}>", "args": {...}, "summary": "<if done>", "reason": "<if block>"}`,
239
+ ``,
240
+ `Rules:`,
241
+ `- If success_criteria is met based on context, respond with action="done" and a summary.`,
242
+ `- If you are stuck (need human input, missing capability), respond with action="block" and a reason.`,
243
+ `- If you've tried the same approach 3+ times unsuccessfully, switch tactics or block.`,
244
+ `- Otherwise pick exactly ONE tool from the list. Provide "args" matching that tool's schema.`,
245
+ `- Do NOT repeat actions listed in "Known failures".`,
246
+ ``,
247
+ `Respond with ONLY the JSON object — no markdown, no preamble.`,
248
+ ].filter(Boolean).join('\n');
249
+ // We use SubAgent.ask but bypass its context assembly — we supply the
250
+ // full prompt and just want the LLM response.
251
+ const r = await this.subAgent.ask({
252
+ query: prompt,
253
+ project_id: goal.project_id ?? null,
254
+ max_context_chars: 200, // minimal, we supply our own
255
+ ollama_url: opts?.ollama_url,
256
+ openai_api_key: opts?.openai_api_key,
257
+ model_override: opts?.model_override,
258
+ });
259
+ // Parse the JSON response (LLMs sometimes wrap with markdown — strip it)
260
+ const cleaned = r.answer
261
+ .replace(/^[\s\S]*?```(?:json)?/, '')
262
+ .replace(/```[\s\S]*$/, '')
263
+ .trim();
264
+ let decided;
265
+ try {
266
+ decided = JSON.parse(cleaned.length > 0 ? cleaned : r.answer);
267
+ }
268
+ catch {
269
+ // LLM gave non-JSON. Treat as 'block'.
270
+ decided = {
271
+ action: 'block',
272
+ reason: r.degraded
273
+ ? 'No LLM available'
274
+ : 'LLM returned non-JSON response (could not parse decision)',
275
+ thought: r.answer.slice(0, 500),
276
+ };
277
+ }
278
+ if (!decided.action)
279
+ decided.action = 'block';
280
+ return {
281
+ decided,
282
+ orient_summary: decided.thought ?? '(no thought)',
283
+ model: r.model,
284
+ tokens_in: r.tokens_in,
285
+ tokens_out: r.tokens_out,
286
+ degraded: r.degraded,
287
+ };
288
+ }
289
+ async act(decided) {
290
+ if (decided.action === 'done')
291
+ return `DONE: ${decided.summary ?? '(no summary)'}`;
292
+ if (decided.action === 'block')
293
+ return `BLOCKED: ${decided.reason ?? '(no reason)'}`;
294
+ if (decided.action === 'escalate')
295
+ return `ESCALATED: ${decided.reason ?? decided.summary ?? '(no reason)'}`;
296
+ // Whitelist check — refuse anything not in SAFE_INTERNAL_TOOLS or a recognized external pattern
297
+ if (decided.action === 'wyrm_call_external') {
298
+ const args = decided.args;
299
+ if (!args?.server || !args?.tool)
300
+ return 'ERROR: wyrm_call_external requires server + tool';
301
+ const r = await this.externalClient.call(args.server, args.tool, args.args ?? {});
302
+ return r.ok
303
+ ? `OK external ${args.server}.${args.tool}: ${JSON.stringify(r.result).slice(0, 500)}`
304
+ : `ERROR external ${args.server}.${args.tool}: ${r.error ?? 'unknown'}`;
305
+ }
306
+ if (!SAFE_INTERNAL_TOOLS.has(decided.action)) {
307
+ return `ERROR: tool '${decided.action}' is not in the agent-loop whitelist`;
308
+ }
309
+ // Internal tool dispatch via the harness function
310
+ try {
311
+ const r = await this.internalToolDispatch(decided.action, decided.args ?? {});
312
+ if (r.ok)
313
+ return `OK ${decided.action}: ${JSON.stringify(r.result).slice(0, 500)}`;
314
+ return `ERROR ${decided.action}: ${r.error ?? 'unknown'}`;
315
+ }
316
+ catch (err) {
317
+ return `ERROR ${decided.action}: ${err.message}`;
318
+ }
319
+ }
320
+ }
321
+ //# sourceMappingURL=agent-loop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-loop.js","sourceRoot":"","sources":["../src/agent-loop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,OAAO,EAAE,KAAK,EAAiC,MAAM,YAAY,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAqB,MAAM,uBAAuB,CAAC;AAE3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAClC,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAEpC,uEAAuE;AACvE,+DAA+D;AAC/D,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAS;IAC1C,cAAc;IACd,aAAa,EAAE,aAAa,EAAE,sBAAsB,EAAE,qBAAqB;IAC3E,gBAAgB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,mBAAmB;IAC9E,oBAAoB,EAAE,qBAAqB,EAAE,mBAAmB;IAChE,wBAAwB,EAAE,wBAAwB,EAAE,0BAA0B;IAC9E,oBAAoB,EAAE,qBAAqB;IAC3C,+DAA+D;IAC/D,gBAAgB,EAAE,qBAAqB,EAAE,sBAAsB;IAC/D,cAAc,EAAE,eAAe,EAAE,cAAc;IAC/C,iBAAiB;IACjB,qBAAqB;IACrB,wBAAwB;IACxB,oBAAoB;CACrB,CAAC,CAAC;AA4BH,MAAM,OAAO,SAAS;IAMV;IACA;IACA;IAPF,QAAQ,CAAW;IACnB,KAAK,CAAQ;IACb,QAAQ,CAAkB;IAElC,YACU,EAAqB,EACrB,cAAiC,EACjC,oBAAoC;QAFpC,OAAE,GAAF,EAAE,CAAmB;QACrB,mBAAc,GAAd,cAAc,CAAmB;QACjC,yBAAoB,GAApB,oBAAoB,CAAgB;QAE5C,IAAI,CAAC,QAAQ,GAAG,IAAI,QAAQ,CAAC,EAAE,CAAC,CAAC;QACjC,IAAI,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,EAAE,CAAC,CAAC;QAC3B,IAAI,CAAC,QAAQ,GAAG,IAAI,eAAe,CAAC,EAAE,CAAC,CAAC;IAC1C,CAAC;IAED,sEAAsE;IACtE,KAAK,CAAC,OAAO,CAAC,OAAe,EAAE,IAAgF;QAC7G,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QACvB,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC1C,IAAI,IAAI,CAAC,gBAAgB,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACjD,gEAAgE;YAChE,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,OAAO,EAAE;gBAClC,OAAO,EAAE,SAAS;gBAClB,KAAK,EAAE,sBAAsB,IAAI,CAAC,cAAc,EAAE;gBAClD,UAAU,EAAE,CAAC;aACd,CAAC,CAAC;YACH,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,2BAA2B,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC;YAC9E,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEzB,uBAAuB;QACvB,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAEvC,8CAA8C;QAC9C,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,GACvE,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;QAEtD,mBAAmB;QACnB,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAE7C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;QACnC,MAAM,OAAO,GACX,OAAO,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM;YAClC,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,SAAS;gBACzE,CAAC,CAAC,YAAY,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO;oBAC7C,CAAC,CAAC,YAAY,CAAC;QAEjB,sBAAsB;QACtB,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,OAAO,EAAE;YAClC,eAAe,EAAE,WAAW,CAAC,OAAO;YACpC,cAAc;YACd,cAAc,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;YACvC,aAAa,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC;YAC1C,OAAO;YACP,UAAU,EAAE,OAAO;YACnB,SAAS;YACT,UAAU;YACV,KAAK;SACN,CAAC,CAAC;QAEH,2DAA2D;QAC3D,IAAI,CAAC;YACH,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;OAIf,CAAC,CAAC,GAAG,CACJ,OAAO,EACP,OAAO,CAAC,MAAM,KAAK,MAAM,IAAI,OAAO,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,EAChF,GAAG,OAAO,CAAC,MAAM,KAAK,CAAC,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAChF,OAAO,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS;gBAC5B,CAAC,CAAC,OAAO,KAAK,YAAY,CAAC,CAAC,CAAC,SAAS;oBACtC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM;wBAChC,CAAC,CAAC,SAAS,CACd,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;QAE7B,OAAO;YACL,aAAa,EAAE,IAAI,CAAC,gBAAgB,GAAG,CAAC;YACxC,eAAe,EAAE,WAAW,CAAC,OAAO;YACpC,cAAc;YACd,OAAO;YACP,aAAa,EAAE,YAAY;YAC3B,OAAO;YACP,UAAU,EAAE,OAAO;YACnB,SAAS;YACT,UAAU;YACV,KAAK;YACL,QAAQ;SACT,CAAC;IACJ,CAAC;IAED,+EAA+E;IAC/E,KAAK,CAAC,MAAM,CAAC,OAAe,EAAE,IAAoG;QAChI,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC,CAAC;QAC7D,MAAM,GAAG,GAAsB,EAAE,CAAC;QAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7B,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YAC5C,IAAI,CAAC,CAAC;gBAAE,MAAM;YACd,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACZ,IAAI,CAAC,CAAC,OAAO,KAAK,MAAM,IAAI,CAAC,CAAC,OAAO,KAAK,SAAS,IAAI,CAAC,CAAC,OAAO,KAAK,OAAO;gBAAE,MAAM;QACtF,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,+DAA+D;IAC/D,cAAc;IACd,+DAA+D;IAEvD,OAAO,CAAC,IAAU;QACxB,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAa,EAAE,CAAC;QAE1B,gDAAgD;QAChD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QACnD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,KAAK,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;YACnC,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;gBACjC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,aAAa,KAAK,EAAE,CAAC,OAAO,MAAM,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,YAAY,EAAE,CAAC,CAAC;YACvH,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,QAAQ,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QACpC,CAAC;QAED,yCAAyC;QACzC,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC;YAC5B,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;SAI9B,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAsF,CAAC;gBAC7G,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACtB,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;oBAC/B,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;wBACvB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;oBAC/F,CAAC;oBACD,IAAI,CAAC,IAAI,CAAC,UAAU,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;gBACvC,CAAC;gBAED,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;;;;SAO9B,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAA2D,CAAC;gBAClF,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACtB,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;oBAC7B,KAAK,MAAM,CAAC,IAAI,MAAM;wBAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;oBAC5E,IAAI,CAAC,IAAI,CAAC,UAAU,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;gBACvC,CAAC;YACH,CAAC;YAAC,MAAM,CAAC,CAAC,0CAA0C,CAAC,CAAC;QACxD,CAAC;QAED,iCAAiC;QACjC,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;YAC9D,IAAI,UAAU,EAAE,CAAC;gBACf,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;;;SAM7B,CAAC,CAAC,GAAG,CAAC,UAAU,CAAyG,CAAC;gBAC3H,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACrB,KAAK,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;oBAChD,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;wBACtB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;oBACrH,CAAC;oBACD,IAAI,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;gBACrC,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC,CAAC,oCAAoC,CAAC,CAAC;QAEhD,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,aAAa,CAAC;QAChD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAC7B,CAAC;IAEO,KAAK,CAAC,eAAe,CAC3B,IAAU,EACV,WAAgD,EAChD,IAAgF;QAEhF,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,IAAI,EAAE,CAAC;QACzD,MAAM,MAAM,GAAG;YACb,gFAAgF;YAChF,EAAE;YACF,SAAS,IAAI,CAAC,KAAK,EAAE;YACrB,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,gBAAgB,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE;YAC1D,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,qBAAqB,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE;YACzE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE;YACjD,EAAE;YACF,0BAA0B;YAC1B,WAAW,CAAC,MAAM,IAAI,4BAA4B;YAClD,qBAAqB;YACrB,EAAE;YACF,yEAAyE;YACzE,8EAA8E,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,oEAAoE;YACvK,EAAE;YACF,QAAQ;YACR,0FAA0F;YAC1F,sGAAsG;YACtG,uFAAuF;YACvF,8FAA8F;YAC9F,qDAAqD;YACrD,EAAE;YACF,+DAA+D;SAChE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAE7B,sEAAsE;QACtE,8CAA8C;QAC9C,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;YAChC,KAAK,EAAE,MAAM;YACb,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI;YACnC,iBAAiB,EAAE,GAAG,EAAmB,6BAA6B;YACtE,UAAU,EAAE,IAAI,EAAE,UAAU;YAC5B,cAAc,EAAE,IAAI,EAAE,cAAc;YACpC,cAAc,EAAE,IAAI,EAAE,cAAc;SACrC,CAAC,CAAC;QAEH,yEAAyE;QACzE,MAAM,OAAO,GAAG,CAAC,CAAC,MAAM;aACrB,OAAO,CAAC,uBAAuB,EAAE,EAAE,CAAC;aACpC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC;aAC1B,IAAI,EAAE,CAAC;QAEV,IAAI,OAAoB,CAAC;QACzB,IAAI,CAAC;YACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAgB,CAAC;QAC/E,CAAC;QAAC,MAAM,CAAC;YACP,uCAAuC;YACvC,OAAO,GAAG;gBACR,MAAM,EAAE,OAAO;gBACf,MAAM,EAAE,CAAC,CAAC,QAAQ;oBAChB,CAAC,CAAC,kBAAkB;oBACpB,CAAC,CAAC,2DAA2D;gBAC/D,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;aAChC,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,MAAM;YAAE,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC;QAE9C,OAAO;YACL,OAAO;YACP,cAAc,EAAE,OAAO,CAAC,OAAO,IAAI,cAAc;YACjD,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,QAAQ,EAAE,CAAC,CAAC,QAAQ;SACrB,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,GAAG,CAAC,OAAoB;QACpC,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;YAAE,OAAO,SAAS,OAAO,CAAC,OAAO,IAAI,cAAc,EAAE,CAAC;QACnF,IAAI,OAAO,CAAC,MAAM,KAAK,OAAO;YAAE,OAAO,YAAY,OAAO,CAAC,MAAM,IAAI,aAAa,EAAE,CAAC;QACrF,IAAI,OAAO,CAAC,MAAM,KAAK,UAAU;YAAE,OAAO,cAAc,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,OAAO,IAAI,aAAa,EAAE,CAAC;QAE7G,gGAAgG;QAChG,IAAI,OAAO,CAAC,MAAM,KAAK,oBAAoB,EAAE,CAAC;YAC5C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAsF,CAAC;YAC5G,IAAI,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC,IAAI,EAAE,IAAI;gBAAE,OAAO,kDAAkD,CAAC;YAC5F,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;YAClF,OAAO,CAAC,CAAC,EAAE;gBACT,CAAC,CAAC,eAAe,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE;gBACtF,CAAC,CAAC,kBAAkB,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,KAAK,IAAI,SAAS,EAAE,CAAC;QAC5E,CAAC;QAED,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7C,OAAO,gBAAgB,OAAO,CAAC,MAAM,sCAAsC,CAAC;QAC9E,CAAC;QAED,kDAAkD;QAClD,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;YAC9E,IAAI,CAAC,CAAC,EAAE;gBAAE,OAAO,MAAM,OAAO,CAAC,MAAM,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;YACnF,OAAO,SAAS,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,KAAK,IAAI,SAAS,EAAE,CAAC;QAC5D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,SAAS,OAAO,CAAC,MAAM,KAAM,GAAa,CAAC,OAAO,EAAE,CAAC;QAC9D,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Compliance audit trail (Tier 3.9).
3
+ *
4
+ * Hash-chained event log: each row records sha256(prev_hash || payload ||
5
+ * timestamp). Tampering with any historical entry breaks the chain.
6
+ * Optional Ed25519 signing layered on top provides a SOC2 / HIPAA-grade
7
+ * audit surface — every AI decision, prompt, tool call, code edit, with
8
+ * the prompt that caused it.
9
+ *
10
+ * `wyrm_audit_export(range)` produces a tamper-evident JSON bundle that
11
+ * an external auditor can verify offline:
12
+ * 1. Recompute the hash chain from genesis through the range.
13
+ * 2. Verify Ed25519 signatures against the operator's public key.
14
+ * 3. Confirm no gaps in IDs.
15
+ *
16
+ * @copyright 2026 Ghost Protocol (Pvt) Ltd. All Rights Reserved.
17
+ * @license Proprietary
18
+ */
19
+ import type Database from 'better-sqlite3';
20
+ export interface AuditEntry {
21
+ id: number;
22
+ event_kind: string;
23
+ actor: string | null;
24
+ project_id: number | null;
25
+ payload_json: string;
26
+ prev_hash: string;
27
+ row_hash: string;
28
+ signature: string | null;
29
+ logged_at: string;
30
+ }
31
+ export interface LogInput {
32
+ event_kind: string;
33
+ actor?: string | null;
34
+ project_id?: number | null;
35
+ payload: Record<string, unknown>;
36
+ signature?: string | null;
37
+ }
38
+ export interface AuditBundle {
39
+ version: 1;
40
+ range: {
41
+ start: string;
42
+ end: string;
43
+ };
44
+ exported_at: string;
45
+ entry_count: number;
46
+ first_id: number | null;
47
+ last_id: number | null;
48
+ genesis_prev_hash: string;
49
+ final_row_hash: string;
50
+ entries: AuditEntry[];
51
+ }
52
+ export interface VerificationResult {
53
+ ok: boolean;
54
+ total: number;
55
+ verified: number;
56
+ first_invalid_id?: number;
57
+ reason?: string;
58
+ }
59
+ export declare class AuditLog {
60
+ private db;
61
+ constructor(db: Database.Database);
62
+ /** Get the hash of the most recent entry (or 'genesis' if empty). */
63
+ private latestHash;
64
+ /** Append a hash-chained event. Returns the inserted row. */
65
+ log(input: LogInput): AuditEntry;
66
+ get(id: number): AuditEntry | null;
67
+ /** Verify the entire chain from the first row through the latest.
68
+ * Useful for periodic self-audits. */
69
+ verify(): VerificationResult;
70
+ /** Export a date-ranged tamper-evident bundle. Range is inclusive on
71
+ * both ends. Verification still walks from genesis to ensure no gaps. */
72
+ export(opts: {
73
+ range_start?: string;
74
+ range_end?: string;
75
+ }): AuditBundle;
76
+ /** Statically verify a previously-exported bundle (no DB required).
77
+ * Useful for the external auditor who only has the JSON file. */
78
+ static verifyBundle(bundle: AuditBundle): VerificationResult;
79
+ /** Slim query for recent entries — UI / dashboards. */
80
+ recent(limit?: number, kind?: string): AuditEntry[];
81
+ }
82
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAG3C,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,QAAQ;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,CAAC,CAAC;IACX,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAID,qBAAa,QAAQ;IACP,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,QAAQ,CAAC,QAAQ;IAEzC,qEAAqE;IACrE,OAAO,CAAC,UAAU;IAOlB,6DAA6D;IAC7D,GAAG,CAAC,KAAK,EAAE,QAAQ,GAAG,UAAU;IAwBhC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAMlC;0CACsC;IACtC,MAAM,IAAI,kBAAkB;IA8B5B;6EACyE;IACzE,MAAM,CAAC,IAAI,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,WAAW;IA+BvE;qEACiE;IACjE,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,kBAAkB;IAoC5D,uDAAuD;IACvD,MAAM,CAAC,KAAK,SAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,UAAU,EAAE;CAUjD"}
package/dist/audit.js ADDED
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Compliance audit trail (Tier 3.9).
3
+ *
4
+ * Hash-chained event log: each row records sha256(prev_hash || payload ||
5
+ * timestamp). Tampering with any historical entry breaks the chain.
6
+ * Optional Ed25519 signing layered on top provides a SOC2 / HIPAA-grade
7
+ * audit surface — every AI decision, prompt, tool call, code edit, with
8
+ * the prompt that caused it.
9
+ *
10
+ * `wyrm_audit_export(range)` produces a tamper-evident JSON bundle that
11
+ * an external auditor can verify offline:
12
+ * 1. Recompute the hash chain from genesis through the range.
13
+ * 2. Verify Ed25519 signatures against the operator's public key.
14
+ * 3. Confirm no gaps in IDs.
15
+ *
16
+ * @copyright 2026 Ghost Protocol (Pvt) Ltd. All Rights Reserved.
17
+ * @license Proprietary
18
+ */
19
+ import { createHash } from 'crypto';
20
+ const GENESIS_HASH = 'genesis';
21
+ export class AuditLog {
22
+ db;
23
+ constructor(db) {
24
+ this.db = db;
25
+ }
26
+ /** Get the hash of the most recent entry (or 'genesis' if empty). */
27
+ latestHash() {
28
+ const row = this.db.prepare('SELECT row_hash FROM audit_log ORDER BY id DESC LIMIT 1').get();
29
+ return row?.row_hash ?? GENESIS_HASH;
30
+ }
31
+ /** Append a hash-chained event. Returns the inserted row. */
32
+ log(input) {
33
+ const payloadJson = JSON.stringify(input.payload);
34
+ const loggedAt = new Date().toISOString();
35
+ const prevHash = this.latestHash();
36
+ const hashInput = `${prevHash}|${input.event_kind}|${payloadJson}|${loggedAt}`;
37
+ const rowHash = createHash('sha256').update(hashInput).digest('hex');
38
+ const info = this.db.prepare(`
39
+ INSERT INTO audit_log
40
+ (event_kind, actor, project_id, payload_json, prev_hash, row_hash, signature, logged_at)
41
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
42
+ `).run(input.event_kind, input.actor ?? null, input.project_id ?? null, payloadJson, prevHash, rowHash, input.signature ?? null, loggedAt);
43
+ return this.get(info.lastInsertRowid);
44
+ }
45
+ get(id) {
46
+ return this.db.prepare('SELECT * FROM audit_log WHERE id = ?').get(id) ?? null;
47
+ }
48
+ /** Verify the entire chain from the first row through the latest.
49
+ * Useful for periodic self-audits. */
50
+ verify() {
51
+ const rows = this.db.prepare('SELECT * FROM audit_log ORDER BY id ASC').all();
52
+ if (rows.length === 0)
53
+ return { ok: true, total: 0, verified: 0 };
54
+ let expectedPrev = GENESIS_HASH;
55
+ for (const row of rows) {
56
+ if (row.prev_hash !== expectedPrev) {
57
+ return {
58
+ ok: false, total: rows.length, verified: row.id - 1,
59
+ first_invalid_id: row.id,
60
+ reason: `prev_hash mismatch: expected ${expectedPrev}, got ${row.prev_hash}`,
61
+ };
62
+ }
63
+ const recomputed = createHash('sha256')
64
+ .update(`${row.prev_hash}|${row.event_kind}|${row.payload_json}|${row.logged_at}`)
65
+ .digest('hex');
66
+ if (recomputed !== row.row_hash) {
67
+ return {
68
+ ok: false, total: rows.length, verified: row.id - 1,
69
+ first_invalid_id: row.id,
70
+ reason: `row_hash mismatch at id ${row.id}: recomputed ${recomputed}, stored ${row.row_hash}`,
71
+ };
72
+ }
73
+ expectedPrev = row.row_hash;
74
+ }
75
+ return { ok: true, total: rows.length, verified: rows.length };
76
+ }
77
+ /** Export a date-ranged tamper-evident bundle. Range is inclusive on
78
+ * both ends. Verification still walks from genesis to ensure no gaps. */
79
+ export(opts) {
80
+ const params = [];
81
+ let where = '';
82
+ if (opts.range_start) {
83
+ where += ' AND logged_at >= ?';
84
+ params.push(opts.range_start);
85
+ }
86
+ if (opts.range_end) {
87
+ where += ' AND logged_at <= ?';
88
+ params.push(opts.range_end);
89
+ }
90
+ const entries = this.db.prepare(`SELECT * FROM audit_log WHERE 1=1 ${where} ORDER BY id ASC`).all(...params);
91
+ return {
92
+ version: 1,
93
+ range: {
94
+ start: opts.range_start ?? entries[0]?.logged_at ?? new Date(0).toISOString(),
95
+ end: opts.range_end ?? entries[entries.length - 1]?.logged_at ?? new Date().toISOString(),
96
+ },
97
+ exported_at: new Date().toISOString(),
98
+ entry_count: entries.length,
99
+ first_id: entries[0]?.id ?? null,
100
+ last_id: entries[entries.length - 1]?.id ?? null,
101
+ genesis_prev_hash: entries[0]?.prev_hash ?? GENESIS_HASH,
102
+ final_row_hash: entries[entries.length - 1]?.row_hash ?? GENESIS_HASH,
103
+ entries,
104
+ };
105
+ }
106
+ /** Statically verify a previously-exported bundle (no DB required).
107
+ * Useful for the external auditor who only has the JSON file. */
108
+ static verifyBundle(bundle) {
109
+ if (bundle.version !== 1) {
110
+ return { ok: false, total: bundle.entry_count, verified: 0, reason: `Unknown bundle version ${bundle.version}` };
111
+ }
112
+ let expectedPrev = bundle.genesis_prev_hash;
113
+ let verified = 0;
114
+ for (const row of bundle.entries) {
115
+ if (row.prev_hash !== expectedPrev) {
116
+ return {
117
+ ok: false, total: bundle.entries.length, verified,
118
+ first_invalid_id: row.id,
119
+ reason: `prev_hash mismatch: expected ${expectedPrev}, got ${row.prev_hash}`,
120
+ };
121
+ }
122
+ const recomputed = createHash('sha256')
123
+ .update(`${row.prev_hash}|${row.event_kind}|${row.payload_json}|${row.logged_at}`)
124
+ .digest('hex');
125
+ if (recomputed !== row.row_hash) {
126
+ return {
127
+ ok: false, total: bundle.entries.length, verified,
128
+ first_invalid_id: row.id,
129
+ reason: `row_hash mismatch at id ${row.id}`,
130
+ };
131
+ }
132
+ expectedPrev = row.row_hash;
133
+ verified++;
134
+ }
135
+ if (bundle.entries.length > 0 && bundle.final_row_hash !== expectedPrev) {
136
+ return {
137
+ ok: false, total: bundle.entries.length, verified,
138
+ reason: `final_row_hash mismatch`,
139
+ };
140
+ }
141
+ return { ok: true, total: bundle.entries.length, verified };
142
+ }
143
+ /** Slim query for recent entries — UI / dashboards. */
144
+ recent(limit = 100, kind) {
145
+ if (kind) {
146
+ return this.db.prepare('SELECT * FROM audit_log WHERE event_kind = ? ORDER BY id DESC LIMIT ?').all(kind, limit);
147
+ }
148
+ return this.db.prepare('SELECT * FROM audit_log ORDER BY id DESC LIMIT ?').all(limit);
149
+ }
150
+ }
151
+ //# sourceMappingURL=audit.js.map