wyrm-mcp 7.3.2 → 7.3.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 (184) hide show
  1. package/README.md +8 -14
  2. package/dist/activation.js +59 -1
  3. package/dist/agent-daemon.js +281 -4
  4. package/dist/agent-loop.js +332 -7
  5. package/dist/analytics.js +236 -13
  6. package/dist/attribution.js +49 -1
  7. package/dist/audit.js +457 -2
  8. package/dist/auto-capture.js +138 -3
  9. package/dist/auto-orchestrator.js +325 -1
  10. package/dist/autoconfig.d.ts +50 -0
  11. package/dist/autoconfig.d.ts.map +1 -1
  12. package/dist/autoconfig.js +1115 -39
  13. package/dist/autoconfig.js.map +1 -1
  14. package/dist/buddy-runner.js +109 -1
  15. package/dist/buddy.js +564 -14
  16. package/dist/build-flags.js +15 -1
  17. package/dist/capabilities.js +183 -3
  18. package/dist/capture.js +56 -1
  19. package/dist/causality.js +148 -8
  20. package/dist/cli.js +281 -20
  21. package/dist/cloud/cli.js +541 -5
  22. package/dist/cloud/client.js +221 -1
  23. package/dist/cloud/crypto.js +85 -1
  24. package/dist/cloud/machine-id.js +113 -2
  25. package/dist/cloud/recovery.js +60 -1
  26. package/dist/cloud/sync-engine.js +543 -7
  27. package/dist/cloud-backup.js +579 -5
  28. package/dist/cloud-profile.js +138 -1
  29. package/dist/cloud-sync-entrypoint.js +47 -1
  30. package/dist/cloud-sync.js +309 -2
  31. package/dist/connectors/bridge-source.d.ts +46 -0
  32. package/dist/connectors/bridge-source.d.ts.map +1 -0
  33. package/dist/connectors/bridge-source.js +77 -0
  34. package/dist/connectors/bridge-source.js.map +1 -0
  35. package/dist/connectors/index.d.ts +24 -0
  36. package/dist/connectors/index.d.ts.map +1 -0
  37. package/dist/connectors/index.js +69 -0
  38. package/dist/connectors/index.js.map +1 -0
  39. package/dist/connectors/ingest.d.ts +16 -0
  40. package/dist/connectors/ingest.d.ts.map +1 -0
  41. package/dist/connectors/ingest.js +116 -0
  42. package/dist/connectors/ingest.js.map +1 -0
  43. package/dist/connectors/types.d.ts +99 -0
  44. package/dist/connectors/types.d.ts.map +1 -0
  45. package/dist/connectors/types.js +17 -0
  46. package/dist/connectors/types.js.map +1 -0
  47. package/dist/constellation.js +168 -12
  48. package/dist/content-signature.js +45 -1
  49. package/dist/context-build-budgeted.js +144 -4
  50. package/dist/context-ranking.js +69 -1
  51. package/dist/crypto.js +179 -1
  52. package/dist/daemon-write-endpoint.js +290 -1
  53. package/dist/daemon-writer.js +406 -2
  54. package/dist/database.js +1278 -53
  55. package/dist/deprecations.js +162 -2
  56. package/dist/design.js +141 -13
  57. package/dist/event-replication.js +112 -1
  58. package/dist/events-sse.js +43 -7
  59. package/dist/events.js +238 -6
  60. package/dist/failure-patterns.d.ts +107 -0
  61. package/dist/failure-patterns.d.ts.map +1 -1
  62. package/dist/failure-patterns.js +924 -43
  63. package/dist/failure-patterns.js.map +1 -1
  64. package/dist/federation.js +236 -12
  65. package/dist/goals.js +101 -13
  66. package/dist/golden.js +355 -3
  67. package/dist/handlers/agent.js +165 -4
  68. package/dist/handlers/alias-adapters.js +129 -1
  69. package/dist/handlers/aliases.js +171 -1
  70. package/dist/handlers/audit.js +87 -1
  71. package/dist/handlers/boundary.js +221 -1
  72. package/dist/handlers/capture.js +1114 -73
  73. package/dist/handlers/causality.js +119 -9
  74. package/dist/handlers/cloud.js +382 -85
  75. package/dist/handlers/companion.js +459 -28
  76. package/dist/handlers/datalake.js +187 -7
  77. package/dist/handlers/dispatch-context.js +22 -0
  78. package/dist/handlers/entity.js +256 -25
  79. package/dist/handlers/events.js +335 -16
  80. package/dist/handlers/failure.d.ts.map +1 -1
  81. package/dist/handlers/failure.js +408 -13
  82. package/dist/handlers/failure.js.map +1 -1
  83. package/dist/handlers/goals.js +296 -4
  84. package/dist/handlers/intelligence.js +681 -126
  85. package/dist/handlers/invoicing.js +70 -1
  86. package/dist/handlers/mcpclient.js +137 -6
  87. package/dist/handlers/orchestration.js +125 -40
  88. package/dist/handlers/output-schemas.js +24 -1
  89. package/dist/handlers/presence.js +99 -3
  90. package/dist/handlers/project.js +182 -28
  91. package/dist/handlers/prompts.js +157 -6
  92. package/dist/handlers/quest.js +224 -4
  93. package/dist/handlers/recall.js +237 -13
  94. package/dist/handlers/registry.js +167 -1
  95. package/dist/handlers/resources.js +288 -1
  96. package/dist/handlers/review.js +74 -11
  97. package/dist/handlers/run.js +498 -16
  98. package/dist/handlers/search.js +338 -15
  99. package/dist/handlers/session.js +643 -31
  100. package/dist/handlers/share.js +184 -8
  101. package/dist/handlers/shims.js +464 -1
  102. package/dist/handlers/skill.js +449 -67
  103. package/dist/handlers/survivors.js +120 -1
  104. package/dist/handlers/symbols.js +109 -8
  105. package/dist/handlers/syncops.js +302 -4
  106. package/dist/handlers/types.js +27 -1
  107. package/dist/harvest.js +191 -5
  108. package/dist/hours.js +156 -7
  109. package/dist/http-auth.js +321 -3
  110. package/dist/http-fast.js +1302 -22
  111. package/dist/icons.js +47 -1
  112. package/dist/importers.js +268 -1
  113. package/dist/index.js +840 -2
  114. package/dist/indexer.js +145 -4
  115. package/dist/intelligence.js +261 -31
  116. package/dist/internal-dispatch.js +212 -3
  117. package/dist/keyset.js +110 -1
  118. package/dist/knowledge-graph.js +176 -12
  119. package/dist/license.js +441 -2
  120. package/dist/logger.js +199 -2
  121. package/dist/maintenance.js +148 -2
  122. package/dist/mcp-client.js +262 -6
  123. package/dist/memory-artifacts.js +596 -32
  124. package/dist/migrate-prompt.js +124 -2
  125. package/dist/migrations.d.ts.map +1 -1
  126. package/dist/migrations.js +799 -42
  127. package/dist/migrations.js.map +1 -1
  128. package/dist/performance.js +228 -1
  129. package/dist/presence.js +140 -11
  130. package/dist/priority-embed.js +164 -5
  131. package/dist/providers/embedding-provider.js +196 -1
  132. package/dist/readonly-gate.js +29 -1
  133. package/dist/receipt.js +43 -1
  134. package/dist/rehydration.js +157 -9
  135. package/dist/reindex.js +88 -1
  136. package/dist/render-target.js +544 -21
  137. package/dist/render.js +280 -4
  138. package/dist/repl-guard.js +173 -1
  139. package/dist/replication-daemon-entrypoint.js +31 -1
  140. package/dist/replication-daemon.js +262 -2
  141. package/dist/rerank.js +142 -1
  142. package/dist/resilience.js +591 -1
  143. package/dist/reverse-bridge.js +360 -5
  144. package/dist/security.js +244 -1
  145. package/dist/session-seen.js +51 -3
  146. package/dist/setup.js +260 -1
  147. package/dist/skill-author.js +168 -5
  148. package/dist/spec-kit.js +191 -1
  149. package/dist/sqlite-busy.js +154 -1
  150. package/dist/statusline.js +315 -11
  151. package/dist/sub-agent.js +262 -13
  152. package/dist/summarizer.js +139 -13
  153. package/dist/symbols.js +283 -7
  154. package/dist/sync.js +359 -5
  155. package/dist/tasks-dispatch.js +84 -1
  156. package/dist/tasks.js +282 -1
  157. package/dist/token-budget.js +143 -1
  158. package/dist/tool-analytics.js +129 -7
  159. package/dist/tool-annotations.js +365 -1
  160. package/dist/tool-manifest-v2.json +1 -1
  161. package/dist/tool-manifest.json +1 -1
  162. package/dist/tool-profiles.js +75 -1
  163. package/dist/trace-harvest.js +244 -6
  164. package/dist/types.js +30 -1
  165. package/dist/ui-dashboard.js +50 -41
  166. package/dist/ulid.js +81 -1
  167. package/dist/usage-tracker.js +66 -1
  168. package/dist/validate.js +129 -1
  169. package/dist/vault.js +534 -1
  170. package/dist/vector-init.js +67 -1
  171. package/dist/vectors.js +184 -3
  172. package/dist/version-check.js +136 -4
  173. package/dist/visibility.js +155 -19
  174. package/dist/wyrm-cli.js +2845 -101
  175. package/dist/wyrm-cli.js.map +1 -1
  176. package/dist/wyrm-guard.d.ts.map +1 -1
  177. package/dist/wyrm-guard.js +475 -14
  178. package/dist/wyrm-guard.js.map +1 -1
  179. package/dist/wyrm-loop.js +150 -3
  180. package/dist/wyrm-manifest.json +1 -1
  181. package/dist/wyrm-statusline-daemon.js +11 -1
  182. package/dist/wyrm-statusline.js +56 -4
  183. package/dist/wyrm-ui.js +77 -9
  184. package/package.json +1 -1
package/dist/golden.js CHANGED
@@ -1,3 +1,355 @@
1
- import{appendFileSync as y,mkdirSync as _,existsSync as g,readFileSync as A}from"fs";import{homedir as S}from"os";import{join as p,dirname as E}from"path";import{fileURLToPath as h}from"url";function b(e,r){try{const t=r===void 0?"":` (${r instanceof Error?r.message:String(r)})`;process.stderr.write(`[wyrm] ${e}${t}
2
- `)}catch{}}const k=["token","secret","password","passwd","authorization","passphrase","bearer","credential","apikey","api_key","license"],D={wyrm_mcp_register:new Set(["env"])},T=[{label:"jwt",re:/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]*/},{label:"github-token",re:/\bgh[posu]_[A-Za-z0-9]{16,}\b/},{label:"npm-token",re:/\bnpm_[A-Za-z0-9]{16,}\b/},{label:"api-key",re:/\b[sp]k-[A-Za-z0-9_-]{16,}\b/},{label:"aws-access-key",re:/\bAKIA[0-9A-Z]{16}\b/},{label:"slack-token",re:/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/},{label:"pem-private-key",re:/-----BEGIN [A-Z ]*PRIVATE KEY-----/},{label:"bearer",re:/\bBearer\s+[A-Za-z0-9._~+\/-]{16,}/}];function R(){const e=process.env.WYRM_GOLDEN_REDACT_EXTRA;return e?new Set(e.split(",").map(r=>r.trim().toLowerCase()).filter(Boolean)):new Set}function $(e,r){const t=e.toLowerCase();if(r.has(t))return!0;for(const o of k)if(t.includes(o))return!0;return!!(t!=="key"&&t.endsWith("key"))}function u(e,r,t,o){if(e==null)return e;const s=typeof e;if(s==="string"){if(o)return t.redactions.push(r),"[REDACTED]";for(const{label:a,re:m}of T)if(m.test(e))return t.redactions.push(r),`[REDACTED:${a}]`;return e}if(s==="number"||s==="boolean")return o?(t.redactions.push(r),"[REDACTED]"):e;if(s==="bigint"||s==="function"||s==="symbol")return{$unserializable:!0};if(Buffer.isBuffer(e)||ArrayBuffer.isView(e)||e instanceof ArrayBuffer)return{$unserializable:!0};const c=e;if(t.seen.has(c))return{$unserializable:!0};t.seen.add(c);try{if(Array.isArray(e))return e.map((i,f)=>u(i,`${r}.${f}`,t,o));if(e instanceof Date)return e.toISOString();const a={},m=D[t.tool];for(const[i,f]of Object.entries(e)){const l=`${r}.${i}`;o?a[i]=u(f,l,t,!0):$(i,t.extras)?(t.redactions.push(l),a[i]="[REDACTED]"):m?.has(i)?a[i]=u(f,l,t,!0):a[i]=u(f,l,t,!1)}return a}finally{t.seen.delete(c)}}function w(e,r,t,o){const s={tool:e,extras:R(),redactions:o,seen:new WeakSet};return u(r,t,s,!1)}const C=5,n={seq:0,writeFailures:0,disabled:!1,warned:!1},F=`${process.pid}-${Math.floor(Date.now()/1e3)}`;let d=null;function I(){if(d)return d;try{const e=E(h(import.meta.url));for(const r of[p(e,"..","package.json"),p(e,"..","..","package.json")])if(g(r)){const t=JSON.parse(A(r,"utf-8")).version;if(typeof t=="string")return d=t,t}}catch{}return d="unknown",d}function L(){const e=process.env.WYRM_GOLDEN_CAPTURE;return e==="1"||e==="true"}function O(){const e=process.env.WYRM_GOLDEN_DIR?.trim();return e&&e.length>0?e:p(S(),".wyrm","golden")}function z(e){const r=new Date,t=`${r.getUTCFullYear()}${String(r.getUTCMonth()+1).padStart(2,"0")}${String(r.getUTCDate()).padStart(2,"0")}`;return p(e,`golden-${t}-${process.pid}.jsonl`)}function U(e){try{const r=O();_(r,{recursive:!0,mode:448}),y(z(r),e,{mode:384}),n.writeFailures=0}catch(r){n.writeFailures++,n.writeFailures>=C?(n.disabled=!0,n.warned||(n.warned=!0,b(`golden: capture disabled for this process after ${n.writeFailures} consecutive write failures`,r))):b("golden: capture dropped (write failed)",r)}}function N(e){const r=e;if(r&&typeof r=="object"&&r.isError){const t=Array.isArray(r.content)?r.content[0]?.text:void 0;return typeof t=="string"&&t.startsWith("Unknown tool:")?"unknown_tool":"soft_error"}return"ok"}function W(e){try{if(n.disabled||!L())return;const r=[],t=w(e.tool,e.args??{},"request.arguments",r),o=e.meta!==void 0?w(e.tool,e.meta,"request._meta",r):void 0,s=w(e.tool,e.response,"response",r),c={v:1,ts:new Date().toISOString(),seq:++n.seq,session:F,wyrm_version:I(),tool:e.tool,dispatch:e.dispatch,request:{name:e.tool,arguments:t,...o!==void 0?{_meta:o}:{}},response:s,outcome:e.outcome??N(e.response),cached:e.cached===!0,ms:e.ms,redactions:r};U(JSON.stringify(c)+`
3
- `)}catch(r){b("golden: capture dropped",r)}}function q(){n.seq=0,n.writeFailures=0,n.disabled=!1,n.warned=!1}function M(){return{...n}}export{M as __goldenStateForTests,q as __resetGoldenForTests,W as goldenCapture,L as isGoldenCaptureEnabled,w as redactForGolden};
1
+ /**
2
+ * Golden-transcript capture Wyrm 7.0 BROOD, Phase F1 (T003b).
3
+ *
4
+ * Opt-in (`WYRM_GOLDEN_CAPTURE=1`, default OFF — Article VII) full-fidelity
5
+ * request/response JSONL capture at the single CallTool dispatch choke point
6
+ * (src/index.ts), covering BOTH dispatch families (handlerRegistry + the
7
+ * switch) plus the cache-hit early return and both catch returns. This is the
8
+ * fidelity `tool_call_log` deliberately lacks (its args_summary is truncated +
9
+ * value-stripped by design — frequency/coverage ranking only, NOT replayable).
10
+ * The corpus feeds the T021 alias-spine replay gate.
11
+ *
12
+ * Design spec: specs/wyrm-v7-brood/research/golden-capture-spec.md
13
+ *
14
+ * Contract (the `emitEvent` contract — src/events.ts):
15
+ * - NEVER throws and never blocks the tool path; entire body try/catch,
16
+ * warn to stderr only (stdout is the MCP transport).
17
+ * - Circuit breaker: 5 consecutive write failures disable capture for the
18
+ * process lifetime with a single warning (no warn-per-call storm on a
19
+ * full disk).
20
+ * - Lazy init: dir creation attempted on first capture, not at startup.
21
+ * - Article III: pure local fs append — no LLM, no network, no new deps.
22
+ * - Secrets redacted BY KEY (recursive, request AND response, on a deep
23
+ * clone — the live result object is still on its way to the client and is
24
+ * never mutated), plus a value-pattern safety net for secrets smuggled
25
+ * through benign fields. Redacted paths are recorded in `redactions[]` so
26
+ * replay can wildcard them.
27
+ */
28
+ import { appendFileSync, mkdirSync, existsSync, readFileSync } from "fs";
29
+ import { homedir } from "os";
30
+ import { join, dirname } from "path";
31
+ import { fileURLToPath } from "url";
32
+ /**
33
+ * Failure warnings go DIRECTLY to stderr (the deprecations.ts pattern) — never
34
+ * through the logger. The header contract ("warn to stderr only") must hold by
35
+ * construction on the stdio CallTool path, independent of any logger console
36
+ * configuration. Failure-isolated: never throws.
37
+ */
38
+ function warnStderr(message, err) {
39
+ try {
40
+ const detail = err === undefined ? "" : ` (${err instanceof Error ? err.message : String(err)})`;
41
+ process.stderr.write(`[wyrm] ${message}${detail}\n`);
42
+ }
43
+ catch {
44
+ /* warnings must never break a tool call */
45
+ }
46
+ }
47
+ // ---------------------------------------------------------------------------
48
+ // Redaction rules (golden-capture-spec.md §3)
49
+ // ---------------------------------------------------------------------------
50
+ /** Rule 1 — substring match on key names (case-insensitive): always redact. */
51
+ const SUBSTRING_REDACT = [
52
+ "token",
53
+ "secret",
54
+ "password",
55
+ "passwd",
56
+ "authorization",
57
+ "passphrase",
58
+ "bearer",
59
+ "credential",
60
+ "apikey",
61
+ "api_key",
62
+ // wyrm_activate's `license` arg is a signed bearer-style entitlement
63
+ // credential; no VALUE_PATTERN recognizes signed-license JSON, so the key
64
+ // rule must catch it (golden-capture-spec.md §3 amendment).
65
+ "license",
66
+ ];
67
+ /**
68
+ * Rule 3 — per-tool overrides: keys whose ENTIRE value subtree has every value
69
+ * redacted regardless of key names (arbitrary env names can carry secrets that
70
+ * no key rule catches).
71
+ */
72
+ const TOOL_REDACT_ALL_VALUES = {
73
+ wyrm_mcp_register: new Set(["env"]),
74
+ };
75
+ /**
76
+ * Rule 4 — value-pattern safety net on every string value regardless of key
77
+ * name. Catches secrets smuggled through benign fields (value, notes,
78
+ * response text).
79
+ */
80
+ const VALUE_PATTERNS = [
81
+ { label: "jwt", re: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]*/ },
82
+ { label: "github-token", re: /\bgh[posu]_[A-Za-z0-9]{16,}\b/ },
83
+ { label: "npm-token", re: /\bnpm_[A-Za-z0-9]{16,}\b/ },
84
+ { label: "api-key", re: /\b[sp]k-[A-Za-z0-9_-]{16,}\b/ },
85
+ { label: "aws-access-key", re: /\bAKIA[0-9A-Z]{16}\b/ },
86
+ { label: "slack-token", re: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/ },
87
+ { label: "pem-private-key", re: /-----BEGIN [A-Z ]*PRIVATE KEY-----/ },
88
+ // Generic bearer credentials smuggled under benign keys (plausible inside
89
+ // wyrm_call_external proxied args). The {16,} run keeps prose like
90
+ // "Bearer token authentication" unmatched.
91
+ { label: "bearer", re: /\bBearer\s+[A-Za-z0-9._~+\/-]{16,}/ },
92
+ ];
93
+ /** Operator escape hatch: extra key names (exact, case-insensitive) to redact. */
94
+ function extraRedactKeys() {
95
+ const raw = process.env.WYRM_GOLDEN_REDACT_EXTRA;
96
+ if (!raw)
97
+ return new Set();
98
+ return new Set(raw
99
+ .split(",")
100
+ .map((s) => s.trim().toLowerCase())
101
+ .filter(Boolean));
102
+ }
103
+ /**
104
+ * Should this key's value be redacted outright?
105
+ * - Rule 1: substring match against SUBSTRING_REDACT (+ exact extras).
106
+ * - Rule 2: compound-suffix `*key` (privateKey, ssh_key, access-key,
107
+ * signingKey) — EXCEPT the exact bare name `key`, which in Wyrm is a
108
+ * required benign identifier on 6+ core tools (data store, set_global,
109
+ * references, design tokens — incl. the #3 most-called tool). Redacting it
110
+ * would make those write transcripts unreplayable; the value-pattern net
111
+ * backstops the residual risk. Deliberate, documented deviation
112
+ * (golden-capture-spec.md §6.1).
113
+ */
114
+ function keyIsSecret(key, extras) {
115
+ const lower = key.toLowerCase();
116
+ if (extras.has(lower))
117
+ return true;
118
+ for (const s of SUBSTRING_REDACT) {
119
+ if (lower.includes(s))
120
+ return true;
121
+ }
122
+ if (lower !== "key" && lower.endsWith("key"))
123
+ return true;
124
+ return false;
125
+ }
126
+ /**
127
+ * Deep-clone-and-redact in one pass. Walks the ORIGINAL (never mutates it) and
128
+ * builds the redacted copy. Cycles and non-serializable leaves degrade to
129
+ * `{"$unserializable": true}` — the record still captures rather than being
130
+ * dropped whole (mirrors emitEvent's payload-stringify isolation).
131
+ */
132
+ function redactWalk(value, path, ctx, redactAll) {
133
+ // Primitives
134
+ if (value === null || value === undefined)
135
+ return value;
136
+ const t = typeof value;
137
+ if (t === "string") {
138
+ if (redactAll) {
139
+ ctx.redactions.push(path);
140
+ return "[REDACTED]";
141
+ }
142
+ for (const { label, re } of VALUE_PATTERNS) {
143
+ if (re.test(value)) {
144
+ ctx.redactions.push(path);
145
+ return `[REDACTED:${label}]`;
146
+ }
147
+ }
148
+ return value;
149
+ }
150
+ if (t === "number" || t === "boolean") {
151
+ if (redactAll) {
152
+ ctx.redactions.push(path);
153
+ return "[REDACTED]";
154
+ }
155
+ return value;
156
+ }
157
+ if (t === "bigint" || t === "function" || t === "symbol") {
158
+ return { $unserializable: true };
159
+ }
160
+ // Binary views (Buffer, TypedArray, DataView, ArrayBuffer) must NOT fall
161
+ // through to Object.entries(): a Buffer would serialize as numeric byte
162
+ // values ({"0":115,"1":101,…} — trivially reconstructible) and the
163
+ // string-only VALUE_PATTERNS net would never see the content. Degrade like
164
+ // every other non-serializable leaf (golden-capture-spec.md §3 amendment).
165
+ if (Buffer.isBuffer(value) || ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {
166
+ return { $unserializable: true };
167
+ }
168
+ // Objects / arrays
169
+ const obj = value;
170
+ if (ctx.seen.has(obj))
171
+ return { $unserializable: true };
172
+ ctx.seen.add(obj);
173
+ try {
174
+ if (Array.isArray(value)) {
175
+ return value.map((item, i) => redactWalk(item, `${path}.${i}`, ctx, redactAll));
176
+ }
177
+ if (value instanceof Date)
178
+ return value.toISOString();
179
+ const out = {};
180
+ const overrides = TOOL_REDACT_ALL_VALUES[ctx.tool];
181
+ for (const [k, v] of Object.entries(value)) {
182
+ const childPath = `${path}.${k}`;
183
+ if (redactAll) {
184
+ // Inside a redact-all subtree: every leaf value is redacted.
185
+ out[k] = redactWalk(v, childPath, ctx, true);
186
+ }
187
+ else if (keyIsSecret(k, ctx.extras)) {
188
+ ctx.redactions.push(childPath);
189
+ out[k] = "[REDACTED]";
190
+ }
191
+ else if (overrides?.has(k)) {
192
+ out[k] = redactWalk(v, childPath, ctx, true);
193
+ }
194
+ else {
195
+ out[k] = redactWalk(v, childPath, ctx, false);
196
+ }
197
+ }
198
+ return out;
199
+ }
200
+ finally {
201
+ ctx.seen.delete(obj);
202
+ }
203
+ }
204
+ /**
205
+ * Redact a value tree for a given tool, recording redacted paths into
206
+ * `redactions`. Exported for direct unit testing.
207
+ */
208
+ export function redactForGolden(tool, value, rootPath, redactions) {
209
+ const ctx = { tool, extras: extraRedactKeys(), redactions, seen: new WeakSet() };
210
+ return redactWalk(value, rootPath, ctx, false);
211
+ }
212
+ // ---------------------------------------------------------------------------
213
+ // Capture state (per process)
214
+ // ---------------------------------------------------------------------------
215
+ const MAX_CONSECUTIVE_WRITE_FAILURES = 5;
216
+ const state = {
217
+ seq: 0,
218
+ writeFailures: 0,
219
+ disabled: false,
220
+ warned: false,
221
+ };
222
+ const SESSION_ID = `${process.pid}-${Math.floor(Date.now() / 1000)}`;
223
+ let _version = null;
224
+ /** Lazy wyrm-mcp version (same package.json walk as index.ts; cached). */
225
+ function wyrmVersion() {
226
+ if (_version)
227
+ return _version;
228
+ try {
229
+ const here = dirname(fileURLToPath(import.meta.url));
230
+ for (const p of [join(here, "..", "package.json"), join(here, "..", "..", "package.json")]) {
231
+ if (existsSync(p)) {
232
+ const v = JSON.parse(readFileSync(p, "utf-8")).version;
233
+ if (typeof v === "string") {
234
+ _version = v;
235
+ return v;
236
+ }
237
+ }
238
+ }
239
+ }
240
+ catch {
241
+ /* fall through */
242
+ }
243
+ _version = "unknown";
244
+ return _version;
245
+ }
246
+ /** Flag check — lazy per call (cheap string compare), default OFF. */
247
+ export function isGoldenCaptureEnabled() {
248
+ const v = process.env.WYRM_GOLDEN_CAPTURE;
249
+ return v === "1" || v === "true";
250
+ }
251
+ function goldenDir() {
252
+ const d = process.env.WYRM_GOLDEN_DIR?.trim();
253
+ return d && d.length > 0 ? d : join(homedir(), ".wyrm", "golden");
254
+ }
255
+ function goldenFilePath(dir) {
256
+ const now = new Date();
257
+ const ymd = `${now.getUTCFullYear()}${String(now.getUTCMonth() + 1).padStart(2, "0")}${String(now.getUTCDate()).padStart(2, "0")}`;
258
+ return join(dir, `golden-${ymd}-${process.pid}.jsonl`);
259
+ }
260
+ /**
261
+ * Append one pre-serialized line. Failure feeds the circuit breaker — after
262
+ * MAX_CONSECUTIVE_WRITE_FAILURES consecutive failures, capture is disabled for
263
+ * the rest of the process lifetime (warned once).
264
+ */
265
+ function writeLine(line) {
266
+ try {
267
+ const dir = goldenDir();
268
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
269
+ appendFileSync(goldenFilePath(dir), line, { mode: 0o600 });
270
+ state.writeFailures = 0;
271
+ }
272
+ catch (err) {
273
+ state.writeFailures++;
274
+ if (state.writeFailures >= MAX_CONSECUTIVE_WRITE_FAILURES) {
275
+ state.disabled = true;
276
+ if (!state.warned) {
277
+ state.warned = true;
278
+ warnStderr(`golden: capture disabled for this process after ${state.writeFailures} consecutive write failures`, err);
279
+ }
280
+ }
281
+ else {
282
+ warnStderr("golden: capture dropped (write failed)", err);
283
+ }
284
+ }
285
+ }
286
+ /** Derive outcome from a response when the call site didn't pin one. */
287
+ function deriveOutcome(response) {
288
+ const r = response;
289
+ if (r && typeof r === "object" && r.isError) {
290
+ const text = Array.isArray(r.content) ? r.content[0]?.text : undefined;
291
+ if (typeof text === "string" && text.startsWith("Unknown tool:"))
292
+ return "unknown_tool";
293
+ return "soft_error";
294
+ }
295
+ return "ok";
296
+ }
297
+ // ---------------------------------------------------------------------------
298
+ // The capture entry point — called at all four response-producing sites
299
+ // ---------------------------------------------------------------------------
300
+ /**
301
+ * Capture one CallTool round-trip as a JSONL golden record. NEVER throws,
302
+ * never blocks; no-op unless WYRM_GOLDEN_CAPTURE=1 (Article VII: default OFF).
303
+ */
304
+ export function goldenCapture(input) {
305
+ try {
306
+ if (state.disabled)
307
+ return;
308
+ if (!isGoldenCaptureEnabled())
309
+ return;
310
+ const redactions = [];
311
+ const args = redactForGolden(input.tool, input.args ?? {}, "request.arguments", redactions);
312
+ const meta = input.meta !== undefined ? redactForGolden(input.tool, input.meta, "request._meta", redactions) : undefined;
313
+ const response = redactForGolden(input.tool, input.response, "response", redactions);
314
+ const record = {
315
+ v: 1,
316
+ ts: new Date().toISOString(),
317
+ seq: ++state.seq,
318
+ session: SESSION_ID,
319
+ wyrm_version: wyrmVersion(),
320
+ tool: input.tool,
321
+ dispatch: input.dispatch,
322
+ request: {
323
+ name: input.tool,
324
+ arguments: args,
325
+ ...(meta !== undefined ? { _meta: meta } : {}),
326
+ },
327
+ response,
328
+ outcome: input.outcome ?? deriveOutcome(input.response),
329
+ cached: input.cached === true,
330
+ ms: input.ms,
331
+ redactions,
332
+ };
333
+ writeLine(JSON.stringify(record) + "\n");
334
+ }
335
+ catch (err) {
336
+ // log-and-drop: the tool result is already on its way to the client; the
337
+ // golden record is best-effort (the emitEvent contract).
338
+ warnStderr("golden: capture dropped", err);
339
+ }
340
+ }
341
+ // ---------------------------------------------------------------------------
342
+ // Test hooks (additive; no production caller)
343
+ // ---------------------------------------------------------------------------
344
+ /** Reset breaker/sequence state between tests. */
345
+ export function __resetGoldenForTests() {
346
+ state.seq = 0;
347
+ state.writeFailures = 0;
348
+ state.disabled = false;
349
+ state.warned = false;
350
+ }
351
+ /** Read-only view of the capture state for tests. */
352
+ export function __goldenStateForTests() {
353
+ return { ...state };
354
+ }
355
+ //# sourceMappingURL=golden.js.map
@@ -1,4 +1,165 @@
1
- import{TOOL_ANNOTATIONS as l}from"../tool-annotations.js";const y=[{name:"wyrm_agent_init",description:"Bootstrap Wyrm's autonomous agent loop in one call. Starts the wyrm-loop daemon as a detached background process so subsequent ticks happen automatically every interval_seconds. Optionally seeds a first goal in the same call. Idempotent \u2014 if already running, returns status without spawning a second instance.",inputSchema:{type:"object",properties:{interval_seconds:{type:"number",description:"Seconds between OODA ticks (default 600 = 10 min, min 10)"},max_steps:{type:"number",description:"Max iterations per goal per tick (default 3, max 20)"},project_path:{type:"string",description:"Scope the daemon to a project root (optional)"},verbose:{type:"boolean",description:"Log every tick to ~/.wyrm/wyrm-loop.log"},seed_goal:{type:"object",description:"Optional first goal to set in the same call",properties:{title:{type:"string"},description:{type:"string"},success_criteria:{type:"string"},priority:{type:"string",enum:["critical","high","medium","low"]},deadline:{type:"string"},max_iterations:{type:"number"}},required:["title"]}}},annotations:l.wyrm_agent_init,aliases:[],handler:async(s,o)=>{const{agentDaemon:a,db:p,goals:n}=o,t=s,e=Math.max(10,Math.min(t.interval_seconds??600,86400)),i=a.start({interval_seconds:e,max_steps:t.max_steps,project_path:t.project_path,verbose:t.verbose??!1});if(!i.ok)return{content:[{type:"text",text:`\u{F115D} Agent init failed: ${i.error}`}],isError:!0};let c="";if(t.seed_goal){const m=t.project_path?p.getProject(t.project_path):null,d=n.set({project_id:m?.id??null,title:t.seed_goal.title,description:t.seed_goal.description,success_criteria:t.seed_goal.success_criteria,deadline:t.seed_goal.deadline,priority:t.seed_goal.priority,max_iterations:t.seed_goal.max_iterations});c=`
2
- Seeded goal #${d.id}: ${d.title}`}const r=i.status;return{content:[{type:"text",text:[`\u{F115D} Agent ${r.pid!=null&&r.pid!==i.pid?"already running":"started"} \u2014 pid ${r.pid}`,` Interval: ${e}s \xB7 Active goals: ${r.active_goals} \xB7 Total iterations: ${r.total_iterations}`,` Log: ${r.log_file}`+c].join(`
3
- `)}]}}},{name:"wyrm_agent_status",description:"Inspect the autonomous agent: is the wyrm-loop daemon running, what was the last action, how many active goals + total iterations. Use to confirm bootstrap worked.",inputSchema:{type:"object",properties:{include_log:{type:"boolean",description:"Include last ~40 lines of the daemon log"}}},annotations:l.wyrm_agent_status,aliases:[],handler:async(s,o)=>{const{agentDaemon:a,goals:p}=o,n=s,t=a.status(),e=[];if(t.running){if(e.push(`\u{F115D} Agent RUNNING \u2014 pid ${t.pid}${t.started_at?` (since ${t.started_at})`:""}`),t.uptime_seconds!=null){const i=Math.floor(t.uptime_seconds/60),c=t.uptime_seconds%60;e.push(` Uptime: ${i}m ${c}s`)}}else e.push("\u{F115D} Agent NOT RUNNING. Call wyrm_agent_init to start it.");return e.push(` Active goals: ${t.active_goals} \xB7 Total iterations: ${t.total_iterations}`),t.last_action&&e.push(` Last action (${t.last_action.ran_at}): ${t.last_action.summary} [${t.last_action.result_status??"?"}]`),n.include_log&&(e.push(""),e.push("=== Recent log ==="),e.push(a.recentLog(40))),{content:[{type:"text",text:e.join(`
4
- `)}]}}},{name:"wyrm_agent_stop",description:"Stop the autonomous agent daemon (SIGTERM with grace, then SIGKILL). Goals remain in DB and can be resumed by calling wyrm_agent_init again.",inputSchema:{type:"object",properties:{grace_ms:{type:"number",description:"Wait time before SIGKILL (default 3000, max 30000)"}}},annotations:l.wyrm_agent_stop,aliases:[],handler:async(s,o)=>{const{agentDaemon:a}=o,p=s,n=await a.stop({grace_ms:p.grace_ms});return n.ok?{content:[{type:"text",text:n.was_running?`\u{F115D} Agent stopped (was pid ${n.pid}). Goals remain in DB \u2014 wyrm_agent_init resumes.`:"\u{F115D} Agent was not running. (No-op.)"}]}:{content:[{type:"text",text:`\u{F115D} Stop failed: ${n.error}`}],isError:!0}}},{name:"wyrm_agent_restart",description:"Stop the daemon and start it again with optional new settings. Useful when changing interval_seconds or max_steps without manually stopping first.",inputSchema:{type:"object",properties:{interval_seconds:{type:"number"},max_steps:{type:"number"},project_path:{type:"string"},verbose:{type:"boolean"}}},annotations:l.wyrm_agent_restart,aliases:[],handler:async(s,o)=>{const{agentDaemon:a,goals:p}=o,n=s,t=await a.restart({interval_seconds:n.interval_seconds,max_steps:n.max_steps,project_path:n.project_path,verbose:n.verbose});if(!t.ok)return{content:[{type:"text",text:`\u{F115D} Restart failed: ${t.error}`}],isError:!0};const e=t.status;return{content:[{type:"text",text:`\u{F115D} Agent restarted \u2014 pid ${e.pid}. Active goals: ${e.active_goals}.`}]}}}];export{y as agentToolSpecs};
1
+ /**
2
+ * @copyright 2026 Ghost Protocol (Pvt) Ltd.
3
+ * @license Proprietary (c) 2026 Ghost Protocol (Pvt) Ltd. All rights reserved. See LICENSE.
4
+ */
5
+ import { TOOL_ANNOTATIONS } from "../tool-annotations.js";
6
+ export const agentToolSpecs = [
7
+ {
8
+ name: "wyrm_agent_init",
9
+ description: "Bootstrap Wyrm's autonomous agent loop in one call. Starts the wyrm-loop daemon as a detached background process so subsequent ticks happen automatically every interval_seconds. Optionally seeds a first goal in the same call. Idempotent — if already running, returns status without spawning a second instance.",
10
+ inputSchema: {
11
+ type: "object",
12
+ properties: {
13
+ interval_seconds: { type: "number", description: "Seconds between OODA ticks (default 600 = 10 min, min 10)" },
14
+ max_steps: { type: "number", description: "Max iterations per goal per tick (default 3, max 20)" },
15
+ project_path: { type: "string", description: "Scope the daemon to a project root (optional)" },
16
+ verbose: { type: "boolean", description: "Log every tick to ~/.wyrm/wyrm-loop.log" },
17
+ seed_goal: {
18
+ type: "object",
19
+ description: "Optional first goal to set in the same call",
20
+ properties: {
21
+ title: { type: "string" },
22
+ description: { type: "string" },
23
+ success_criteria: { type: "string" },
24
+ priority: { type: "string", enum: ["critical", "high", "medium", "low"] },
25
+ deadline: { type: "string" },
26
+ max_iterations: { type: "number" },
27
+ },
28
+ required: ["title"],
29
+ },
30
+ },
31
+ },
32
+ annotations: TOOL_ANNOTATIONS["wyrm_agent_init"],
33
+ aliases: [],
34
+ handler: async (args, ctx) => {
35
+ const { agentDaemon, db, goals } = ctx;
36
+ const a = args;
37
+ const interval = Math.max(10, Math.min(a.interval_seconds ?? 600, 86400));
38
+ const r = agentDaemon.start({
39
+ interval_seconds: interval,
40
+ max_steps: a.max_steps,
41
+ project_path: a.project_path,
42
+ verbose: a.verbose ?? false,
43
+ });
44
+ if (!r.ok) {
45
+ return { content: [{ type: "text", text: `󱅝 Agent init failed: ${r.error}` }], isError: true };
46
+ }
47
+ let seedNote = '';
48
+ if (a.seed_goal) {
49
+ const proj = a.project_path ? db.getProject(a.project_path) : null;
50
+ const goal = goals.set({
51
+ project_id: proj?.id ?? null,
52
+ title: a.seed_goal.title,
53
+ description: a.seed_goal.description,
54
+ success_criteria: a.seed_goal.success_criteria,
55
+ deadline: a.seed_goal.deadline,
56
+ priority: a.seed_goal.priority,
57
+ max_iterations: a.seed_goal.max_iterations,
58
+ });
59
+ seedNote = `\n Seeded goal #${goal.id}: ${goal.title}`;
60
+ }
61
+ const status = r.status;
62
+ const wasAlready = status.pid != null && status.pid !== r.pid;
63
+ const text = [
64
+ `󱅝 Agent ${wasAlready ? 'already running' : 'started'} — pid ${status.pid}`,
65
+ ` Interval: ${interval}s · Active goals: ${status.active_goals} · Total iterations: ${status.total_iterations}`,
66
+ ` Log: ${status.log_file}` + seedNote,
67
+ ].join('\n');
68
+ return { content: [{ type: "text", text }] };
69
+ },
70
+ },
71
+ {
72
+ name: "wyrm_agent_status",
73
+ description: "Inspect the autonomous agent: is the wyrm-loop daemon running, what was the last action, how many active goals + total iterations. Use to confirm bootstrap worked.",
74
+ inputSchema: {
75
+ type: "object",
76
+ properties: {
77
+ include_log: { type: "boolean", description: "Include last ~40 lines of the daemon log" },
78
+ },
79
+ },
80
+ annotations: TOOL_ANNOTATIONS["wyrm_agent_status"],
81
+ aliases: [],
82
+ handler: async (args, ctx) => {
83
+ const { agentDaemon, goals } = ctx;
84
+ const a = args;
85
+ const status = agentDaemon.status();
86
+ const lines = [];
87
+ if (status.running) {
88
+ lines.push(`󱅝 Agent RUNNING — pid ${status.pid}${status.started_at ? ` (since ${status.started_at})` : ''}`);
89
+ if (status.uptime_seconds != null) {
90
+ const m = Math.floor(status.uptime_seconds / 60);
91
+ const s = status.uptime_seconds % 60;
92
+ lines.push(` Uptime: ${m}m ${s}s`);
93
+ }
94
+ }
95
+ else {
96
+ lines.push(`󱅝 Agent NOT RUNNING. Call wyrm_agent_init to start it.`);
97
+ }
98
+ lines.push(` Active goals: ${status.active_goals} · Total iterations: ${status.total_iterations}`);
99
+ if (status.last_action) {
100
+ lines.push(` Last action (${status.last_action.ran_at}): ${status.last_action.summary} [${status.last_action.result_status ?? '?'}]`);
101
+ }
102
+ if (a.include_log) {
103
+ lines.push('');
104
+ lines.push('=== Recent log ===');
105
+ lines.push(agentDaemon.recentLog(40));
106
+ }
107
+ return { content: [{ type: "text", text: lines.join('\n') }] };
108
+ },
109
+ },
110
+ {
111
+ name: "wyrm_agent_stop",
112
+ description: "Stop the autonomous agent daemon (SIGTERM with grace, then SIGKILL). Goals remain in DB and can be resumed by calling wyrm_agent_init again.",
113
+ inputSchema: {
114
+ type: "object",
115
+ properties: {
116
+ grace_ms: { type: "number", description: "Wait time before SIGKILL (default 3000, max 30000)" },
117
+ },
118
+ },
119
+ annotations: TOOL_ANNOTATIONS["wyrm_agent_stop"],
120
+ aliases: [],
121
+ handler: async (args, ctx) => {
122
+ const { agentDaemon } = ctx;
123
+ const a = args;
124
+ const r = await agentDaemon.stop({ grace_ms: a.grace_ms });
125
+ if (!r.ok) {
126
+ return { content: [{ type: "text", text: `󱅝 Stop failed: ${r.error}` }], isError: true };
127
+ }
128
+ const text = r.was_running
129
+ ? `󱅝 Agent stopped (was pid ${r.pid}). Goals remain in DB — wyrm_agent_init resumes.`
130
+ : `󱅝 Agent was not running. (No-op.)`;
131
+ return { content: [{ type: "text", text }] };
132
+ },
133
+ },
134
+ {
135
+ name: "wyrm_agent_restart",
136
+ description: "Stop the daemon and start it again with optional new settings. Useful when changing interval_seconds or max_steps without manually stopping first.",
137
+ inputSchema: {
138
+ type: "object",
139
+ properties: {
140
+ interval_seconds: { type: "number" },
141
+ max_steps: { type: "number" },
142
+ project_path: { type: "string" },
143
+ verbose: { type: "boolean" },
144
+ },
145
+ },
146
+ annotations: TOOL_ANNOTATIONS["wyrm_agent_restart"],
147
+ aliases: [],
148
+ handler: async (args, ctx) => {
149
+ const { agentDaemon, goals } = ctx;
150
+ const a = args;
151
+ const r = await agentDaemon.restart({
152
+ interval_seconds: a.interval_seconds,
153
+ max_steps: a.max_steps,
154
+ project_path: a.project_path,
155
+ verbose: a.verbose,
156
+ });
157
+ if (!r.ok) {
158
+ return { content: [{ type: "text", text: `󱅝 Restart failed: ${r.error}` }], isError: true };
159
+ }
160
+ const s = r.status;
161
+ return { content: [{ type: "text", text: `󱅝 Agent restarted — pid ${s.pid}. Active goals: ${s.active_goals}.` }] };
162
+ },
163
+ },
164
+ ];
165
+ //# sourceMappingURL=agent.js.map