wyrm-mcp 7.2.0 → 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 (156) hide show
  1. package/LICENSE +26 -667
  2. package/NOTICE +14 -33
  3. package/dist/activation.d.ts.map +1 -1
  4. package/dist/activation.js +1 -44
  5. package/dist/activation.js.map +1 -1
  6. package/dist/agent-daemon.js +4 -281
  7. package/dist/agent-loop.js +7 -332
  8. package/dist/analytics.js +13 -236
  9. package/dist/attribution.js +1 -49
  10. package/dist/audit.js +2 -457
  11. package/dist/auto-capture.js +3 -138
  12. package/dist/auto-orchestrator.js +1 -325
  13. package/dist/autoconfig.js +39 -840
  14. package/dist/buddy-runner.js +1 -109
  15. package/dist/buddy.js +14 -564
  16. package/dist/build-flags.js +1 -17
  17. package/dist/capabilities.js +3 -183
  18. package/dist/capture.js +1 -56
  19. package/dist/causality.js +6 -107
  20. package/dist/cli.js +20 -281
  21. package/dist/cloud/cli.js +5 -541
  22. package/dist/cloud/client.js +1 -221
  23. package/dist/cloud/crypto.js +1 -85
  24. package/dist/cloud/machine-id.js +2 -113
  25. package/dist/cloud/recovery.js +1 -60
  26. package/dist/cloud/sync-engine.js +7 -543
  27. package/dist/cloud-backup.js +5 -579
  28. package/dist/cloud-profile.js +1 -138
  29. package/dist/cloud-sync-entrypoint.js +1 -47
  30. package/dist/cloud-sync.js +2 -309
  31. package/dist/constellation.js +12 -168
  32. package/dist/context-build-budgeted.js +4 -144
  33. package/dist/context-ranking.js +1 -69
  34. package/dist/crypto.js +1 -179
  35. package/dist/daemon-write-endpoint.js +1 -290
  36. package/dist/daemon-writer.js +2 -406
  37. package/dist/database.js +43 -1110
  38. package/dist/deprecations.js +2 -162
  39. package/dist/design.js +13 -141
  40. package/dist/event-replication.js +1 -112
  41. package/dist/events-sse.js +7 -43
  42. package/dist/events.js +6 -238
  43. package/dist/failure-patterns.js +42 -659
  44. package/dist/federation.js +12 -236
  45. package/dist/goals.js +13 -101
  46. package/dist/golden.js +3 -355
  47. package/dist/handlers/agent.js +4 -165
  48. package/dist/handlers/alias-adapters.js +1 -129
  49. package/dist/handlers/aliases.js +1 -171
  50. package/dist/handlers/audit.js +1 -87
  51. package/dist/handlers/boundary.js +1 -221
  52. package/dist/handlers/capture.js +73 -1109
  53. package/dist/handlers/causality.js +7 -114
  54. package/dist/handlers/cloud.js +85 -382
  55. package/dist/handlers/companion.js +28 -459
  56. package/dist/handlers/datalake.js +7 -187
  57. package/dist/handlers/dispatch-context.js +0 -22
  58. package/dist/handlers/entity.js +25 -256
  59. package/dist/handlers/events.js +16 -335
  60. package/dist/handlers/failure.js +13 -340
  61. package/dist/handlers/goals.js +4 -296
  62. package/dist/handlers/intelligence.js +126 -674
  63. package/dist/handlers/invoicing.js +1 -70
  64. package/dist/handlers/mcpclient.js +6 -137
  65. package/dist/handlers/orchestration.js +40 -125
  66. package/dist/handlers/output-schemas.js +1 -24
  67. package/dist/handlers/presence.js +3 -99
  68. package/dist/handlers/project.js +28 -182
  69. package/dist/handlers/prompts.js +6 -157
  70. package/dist/handlers/quest.js +4 -224
  71. package/dist/handlers/recall.js +11 -218
  72. package/dist/handlers/registry.js +1 -167
  73. package/dist/handlers/resources.js +1 -288
  74. package/dist/handlers/review.js +11 -74
  75. package/dist/handlers/run.js +17 -487
  76. package/dist/handlers/search.js +15 -326
  77. package/dist/handlers/session.js +28 -615
  78. package/dist/handlers/share.js +8 -184
  79. package/dist/handlers/shims.js +1 -464
  80. package/dist/handlers/skill.js +67 -449
  81. package/dist/handlers/survivors.js +1 -120
  82. package/dist/handlers/symbols.js +8 -109
  83. package/dist/handlers/syncops.js +4 -302
  84. package/dist/handlers/types.js +1 -27
  85. package/dist/harvest.js +5 -191
  86. package/dist/hours.js +7 -156
  87. package/dist/http-auth.js +3 -321
  88. package/dist/http-fast.js +21 -1137
  89. package/dist/icons.js +1 -47
  90. package/dist/index.js +2 -924
  91. package/dist/indexer.js +4 -145
  92. package/dist/intelligence.js +31 -261
  93. package/dist/internal-dispatch.js +3 -212
  94. package/dist/keyset.js +1 -110
  95. package/dist/knowledge-graph.js +12 -176
  96. package/dist/license.d.ts +11 -0
  97. package/dist/license.d.ts.map +1 -1
  98. package/dist/license.js +2 -414
  99. package/dist/license.js.map +1 -1
  100. package/dist/logger.js +2 -199
  101. package/dist/maintenance.js +2 -148
  102. package/dist/mcp-client.js +6 -262
  103. package/dist/memory-artifacts.js +30 -449
  104. package/dist/migrate-prompt.js +2 -124
  105. package/dist/migrations.js +40 -655
  106. package/dist/performance.js +1 -228
  107. package/dist/presence.js +11 -140
  108. package/dist/priority-embed.js +5 -164
  109. package/dist/providers/embedding-provider.js +1 -196
  110. package/dist/readonly-gate.js +1 -29
  111. package/dist/rehydration.js +9 -157
  112. package/dist/reindex.js +1 -88
  113. package/dist/render-target.js +21 -514
  114. package/dist/render.js +4 -280
  115. package/dist/repl-guard.js +1 -173
  116. package/dist/replication-daemon-entrypoint.js +1 -31
  117. package/dist/replication-daemon.js +2 -262
  118. package/dist/resilience.js +1 -591
  119. package/dist/reverse-bridge.js +5 -360
  120. package/dist/security.js +1 -244
  121. package/dist/session-seen.js +3 -51
  122. package/dist/setup.js +1 -260
  123. package/dist/skill-author.js +5 -168
  124. package/dist/spec-kit.js +1 -191
  125. package/dist/sqlite-busy.js +1 -154
  126. package/dist/statusline.js +11 -315
  127. package/dist/sub-agent.js +13 -262
  128. package/dist/summarizer.js +13 -139
  129. package/dist/symbols.js +7 -283
  130. package/dist/sync.js +5 -359
  131. package/dist/tasks-dispatch.js +1 -84
  132. package/dist/tasks.js +1 -282
  133. package/dist/token-budget.js +1 -143
  134. package/dist/tool-analytics.js +7 -129
  135. package/dist/tool-annotations.js +1 -365
  136. package/dist/tool-manifest-v2.json +1 -1
  137. package/dist/tool-manifest.json +1 -1
  138. package/dist/tool-profiles.js +1 -75
  139. package/dist/trace-harvest.js +6 -244
  140. package/dist/types.js +1 -30
  141. package/dist/ui-dashboard.js +41 -50
  142. package/dist/ulid.js +1 -81
  143. package/dist/validate.js +1 -129
  144. package/dist/vault.js +1 -534
  145. package/dist/vectors.js +3 -184
  146. package/dist/version-check.js +4 -136
  147. package/dist/visibility.js +19 -155
  148. package/dist/wyrm-cli.js +98 -2451
  149. package/dist/wyrm-cli.js.map +1 -1
  150. package/dist/wyrm-guard.js +14 -424
  151. package/dist/wyrm-loop.js +3 -150
  152. package/dist/wyrm-manifest.json +1 -1
  153. package/dist/wyrm-statusline-daemon.js +1 -11
  154. package/dist/wyrm-statusline.js +4 -56
  155. package/dist/wyrm-ui.js +9 -77
  156. package/package.json +4 -2
package/dist/tasks.js CHANGED
@@ -1,282 +1 @@
1
- /**
2
- * MCP Tasks (T037) — capability-gated, SYNCHRONOUS-fallback task lifecycle for
3
- * the long ops of the tools that REMAIN on MCP (Article V: vendor-neutral,
4
- * capability-gated; Article VII: admin-gated long ops are off by default).
5
- *
6
- * WHY this exists. Two of Wyrm's retained tools have ops that can run long on a
7
- * large corpus and are the ones a curated client NEEDS on the MCP wire:
8
- * - wyrm_maintenance / wyrm_reindex / wyrm_vector_setup — the admin upkeep +
9
- * vector (re)build. This is the fix for the curated-client "can't reindex
10
- * the last 17% of vectors" wound — these stay on MCP precisely so a
11
- * resource-constrained client CAN drive a backfill from the agent.
12
- * - harvest / auto_capture extraction (modes of wyrm_capture) — corpus scans.
13
- *
14
- * HOW it composes with the SDK. The MCP Tasks RC's server-side `tools/call`
15
- * augmentation works like this (server/index.js): when the request carries a
16
- * `task` param, the SDK REQUIRES the CallTool handler to return a
17
- * `CreateTaskResult` (`{task:{taskId,status,…}}`) — NOT a CallToolResult — and
18
- * the actual payload is fetched later via `tasks/result`. That deferred-result
19
- * machinery is the part the spec flags as not-yet-stable. Wyrm's faithful,
20
- * "synchronous fallback until the RC stabilizes" implementation runs the op
21
- * IMMEDIATELY and never truly defers:
22
- * 1. Detect the client's `tasks` capability (negotiated at initialize) AND the
23
- * request's `task` augmentation param (`TaskAugmentedRequestParamsSchema`).
24
- * 2. When BOTH are present: run the op SYNCHRONOUSLY, store the completed
25
- * CallToolResult, and return a `CreateTaskResult` whose task is already
26
- * `completed` (or `failed`). The client fetches the payload via
27
- * `tasks/result` (served from the store). No work is deferred — the op
28
- * already ran — so a client that never polls still triggered the op.
29
- * 3. Otherwise (the default for every client today, and any client that omits
30
- * `task`): run the op inline and return the CallToolResult directly. This
31
- * is the synchronous fallback and the always-correct path; no data lost.
32
- *
33
- * The whole module is deterministic and no-network (Article III): the only
34
- * non-determinism is the task id + timestamps, confined to the task record and
35
- * the CreateTaskResult envelope — never the stored CallToolResult bytes.
36
- */
37
- import { randomUUID } from 'node:crypto';
38
- import { renderErrorResponse } from './render.js';
39
- /** Default retention for a completed task record (ms). */
40
- export const DEFAULT_TASK_TTL_MS = 5 * 60 * 1000; // 5 minutes
41
- /** Hard cap so a hostile/buggy `task.ttl` can never pin memory (Article VII). */
42
- export const MAX_TASK_TTL_MS = 60 * 60 * 1000; // 1 hour
43
- /** Suggested client poll cadence advertised on the task record (ms). */
44
- export const DEFAULT_POLL_INTERVAL_MS = 500;
45
- /** Hard cap on retained task records — bounds memory; oldest evicted first. */
46
- export const MAX_RETAINED_TASKS = 256;
47
- /**
48
- * True when the connected client advertised the `tasks` capability. Vendor-
49
- * neutral (Article V): reads the negotiated capability, never a client name.
50
- */
51
- export function clientSupportsTasks(tasksCapability) {
52
- return tasksCapability != null && typeof tasksCapability === 'object';
53
- }
54
- /**
55
- * The `task` augmentation a client attaches to a CallTool request when it wants
56
- * a task created (`TaskAugmentedRequestParamsSchema.task`). Reads it off the
57
- * raw request params; returns the requested ttl (clamped) or null when absent.
58
- * Total on adversarial input — never throws (an invalid ttl degrades to the
59
- * default, a missing augmentation yields null = synchronous path).
60
- */
61
- export function readTaskAugmentation(params) {
62
- const task = params && typeof params === 'object' ? params.task : undefined;
63
- if (task == null || typeof task !== 'object')
64
- return { requested: false };
65
- const rawTtl = task.ttl;
66
- let ttlMs = DEFAULT_TASK_TTL_MS;
67
- if (typeof rawTtl === 'number' && Number.isFinite(rawTtl) && rawTtl > 0) {
68
- ttlMs = Math.min(Math.floor(rawTtl), MAX_TASK_TTL_MS);
69
- }
70
- return { requested: true, ttlMs };
71
- }
72
- /**
73
- * In-memory task store. Single-process (the stdio server is one process); the
74
- * MCP RC has no persistence requirement and a restarted server starts clean.
75
- * Bounded by MAX_RETAINED_TASKS with oldest-first eviction.
76
- */
77
- export class TaskStore {
78
- tasks = new Map();
79
- /** Create a `working` task record and return its id. */
80
- create(tool, ttlMs, nowMs = Date.now()) {
81
- this.evictExpired(nowMs);
82
- if (this.tasks.size >= MAX_RETAINED_TASKS)
83
- this.evictOldest();
84
- const iso = new Date(nowMs).toISOString();
85
- const record = {
86
- taskId: randomUUID(),
87
- status: 'working',
88
- ttl: ttlMs,
89
- createdAt: iso,
90
- lastUpdatedAt: iso,
91
- pollInterval: DEFAULT_POLL_INTERVAL_MS,
92
- tool,
93
- _createdAtMs: nowMs,
94
- };
95
- this.tasks.set(record.taskId, record);
96
- return record;
97
- }
98
- /** Mark a task terminal (completed/failed/cancelled) with its result. */
99
- settle(taskId, status, result, statusMessage, nowMs = Date.now()) {
100
- const record = this.tasks.get(taskId);
101
- if (!record)
102
- return;
103
- record.status = status;
104
- record.lastUpdatedAt = new Date(nowMs).toISOString();
105
- if (result !== undefined)
106
- record.result = result;
107
- if (statusMessage !== undefined)
108
- record.statusMessage = statusMessage;
109
- }
110
- /** Read a task record (after evicting any whose ttl has elapsed). */
111
- get(taskId, nowMs = Date.now()) {
112
- this.evictExpired(nowMs);
113
- return this.tasks.get(taskId);
114
- }
115
- /** List the live task records, newest first (after eviction). */
116
- list(nowMs = Date.now()) {
117
- this.evictExpired(nowMs);
118
- return [...this.tasks.values()].sort((a, b) => b._createdAtMs - a._createdAtMs);
119
- }
120
- /** Cancel a still-working task; no-op if unknown or already terminal. */
121
- cancel(taskId, nowMs = Date.now()) {
122
- const record = this.tasks.get(taskId);
123
- if (!record || record.status !== 'working')
124
- return false;
125
- record.status = 'cancelled';
126
- record.lastUpdatedAt = new Date(nowMs).toISOString();
127
- return true;
128
- }
129
- size() {
130
- return this.tasks.size;
131
- }
132
- /** Drop records whose (createdAt + ttl) is in the past. */
133
- evictExpired(nowMs) {
134
- for (const [id, rec] of this.tasks) {
135
- if (rec.ttl != null && nowMs - rec._createdAtMs > rec.ttl)
136
- this.tasks.delete(id);
137
- }
138
- }
139
- /** Drop the single oldest record (Map preserves insertion order). */
140
- evictOldest() {
141
- const oldest = this.tasks.keys().next();
142
- if (!oldest.done)
143
- this.tasks.delete(oldest.value);
144
- }
145
- }
146
- /** Project a TaskRecord to the on-wire shape (strips Wyrm internals). */
147
- export function toWireTask(record) {
148
- const wire = {
149
- taskId: record.taskId,
150
- status: record.status,
151
- ttl: record.ttl,
152
- createdAt: record.createdAt,
153
- lastUpdatedAt: record.lastUpdatedAt,
154
- };
155
- if (record.pollInterval !== undefined)
156
- wire.pollInterval = record.pollInterval;
157
- if (record.statusMessage !== undefined)
158
- wire.statusMessage = record.statusMessage;
159
- return wire;
160
- }
161
- /**
162
- * Resolve whether a given CallTool should run as a task.
163
- *
164
- * The decisive signal is the request's `task` augmentation, because the MCP SDK
165
- * (server/index.js) UNCONDITIONALLY requires the CallTool handler to return a
166
- * `CreateTaskResult` whenever `params.task` is present — it does NOT consult the
167
- * negotiated client capability. So a request bearing `task` MUST run the task
168
- * path or the SDK rejects the response with -32602. The negotiated `tasks`
169
- * capability is recorded as advisory context (a well-behaved client only sends
170
- * `task` after negotiating it), not a suppressor — suppressing it here would
171
- * desync from the SDK and crash the very clients the gate is meant to serve.
172
- *
173
- * No `task` augmentation → the synchronous fallback (a normal CallToolResult),
174
- * the default for every client today.
175
- */
176
- export function resolveTaskGate(tasksCapability, params) {
177
- const capabilityNegotiated = clientSupportsTasks(tasksCapability);
178
- const aug = readTaskAugmentation(params);
179
- if (aug.requested)
180
- return { asTask: true, ttlMs: aug.ttlMs, capabilityNegotiated };
181
- return { asTask: false, ttlMs: DEFAULT_TASK_TTL_MS, capabilityNegotiated };
182
- }
183
- /**
184
- * Run a long op under the task gate. ALWAYS runs the op SYNCHRONOUSLY (no
185
- * deferral) regardless of the gate — the op having run is the spine.
186
- *
187
- * - gate.asTask=false → returns {kind:'inline'} with the CallToolResult; the
188
- * dispatcher returns it directly. This is the synchronous fallback.
189
- * - gate.asTask=true → stores the completed CallToolResult and returns
190
- * {kind:'task'} with a CreateTaskResult whose task is already `completed`
191
- * (or `failed`). The dispatcher returns the CreateTaskResult on the wire;
192
- * the client fetches the payload via tasks/result (served from the store).
193
- *
194
- * A throwing op records a `failed` task and re-throws (the dispatcher's error
195
- * boundary owns the wire error shape — we never swallow it).
196
- */
197
- export async function runLongOp(tool, gate, store, run, nowMs = Date.now()) {
198
- if (!gate.asTask) {
199
- return { kind: 'inline', result: await run() };
200
- }
201
- const record = store.create(tool, gate.ttlMs, nowMs);
202
- let result;
203
- try {
204
- result = await run();
205
- }
206
- catch (err) {
207
- store.settle(record.taskId, 'failed', undefined, String(err));
208
- throw err;
209
- }
210
- store.settle(record.taskId, result?.isError ? 'failed' : 'completed', result);
211
- // re-read so the wire task reflects the settled status/timestamp
212
- const settled = store.get(record.taskId, nowMs) ?? record;
213
- return { kind: 'task', createTask: { task: toWireTask(settled) } };
214
- }
215
- /**
216
- * Fetch the stored CallToolResult payload for a completed task (the
217
- * tasks/result backend). Returns undefined for an unknown/expired/unsettled id.
218
- */
219
- export function taskPayload(store, taskId, nowMs = Date.now()) {
220
- return store.get(taskId, nowMs)?.result;
221
- }
222
- // ── Admin gate (Article VII: destructive long ops off by default) ───────────
223
- //
224
- // `wyrm_maintenance` is destructiveHint:true and `wyrm_reindex`/
225
- // `wyrm_vector_setup` rebuild the embedding store — operations a stray fleet
226
- // agent should not be able to trigger over MCP unless the operator has opted
227
- // in. The gate is a single off-by-default env flag (`WYRM_ADMIN=1`), evaluated
228
- // per call so it can be flipped without a restart in tests. The tools STILL
229
- // remain on MCP (that is the whole point of T037 — the curated client must be
230
- // able to drive a reindex); the gate just requires the operator's intent.
231
- /** The admin-gated tools — the destructive/rebuild long ops on the wire. */
232
- export const ADMIN_GATED_TOOLS = new Set([
233
- 'wyrm_maintenance',
234
- 'wyrm_reindex',
235
- 'wyrm_vector_setup',
236
- ]);
237
- /**
238
- * The RESOLVED dispatcher names the MCP-Tasks affordance TARGETS — the long ops
239
- * of tools that REMAIN on MCP (spec T037): the admin upkeep/vector tools (also
240
- * WYRM_ADMIN-gated) + the extraction modes of wyrm_capture, which resolve
241
- * through the noun shim to wyrm_harvest (mode=artifacts) / wyrm_auto_capture
242
- * (mode=extract). Note: a task augmentation on ANY tool is still honored by the
243
- * dispatcher (the SDK forces a CreateTaskResult whenever params.task is set) —
244
- * this set documents where a task is actually USEFUL, not the only place the
245
- * shape is produced.
246
- */
247
- export const LONG_OP_TOOLS = new Set([
248
- ...ADMIN_GATED_TOOLS,
249
- 'wyrm_harvest',
250
- 'wyrm_auto_capture',
251
- ]);
252
- /** Machine-readable code for an admin-gate rejection (SEP-1303 family). */
253
- export const WYRM_ADMIN_REQUIRED_CODE = 'WYRM_ADMIN_REQUIRED';
254
- /** True when the operator has opted into admin-gated long ops. */
255
- export function adminGateOpen(env = process.env) {
256
- const v = env.WYRM_ADMIN;
257
- return v === '1' || v === 'true' || v === 'yes';
258
- }
259
- /** True when `tool` is admin-gated and the operator has NOT opted in. */
260
- export function adminGateBlocks(tool, env = process.env) {
261
- return ADMIN_GATED_TOOLS.has(tool) && !adminGateOpen(env);
262
- }
263
- /**
264
- * The structured (SEP-1303-shaped) response for an admin-gate rejection.
265
- * `isError:true` with a non-retryable {error,expected} body so a fleet subagent
266
- * does not loop — the operator must set WYRM_ADMIN, not the agent.
267
- */
268
- export function adminDeniedResponse(tool) {
269
- return renderErrorResponse({
270
- error: {
271
- code: WYRM_ADMIN_REQUIRED_CODE,
272
- message: `${tool} is an admin-gated maintenance op (it rebuilds or vacuums the local memory store) ` +
273
- `and is disabled by default.`,
274
- retryable: false,
275
- },
276
- expected: `Set the WYRM_ADMIN=1 environment variable on the Wyrm MCP server process to enable ${tool}, ` +
277
- `then call it again. This rejection is deterministic — do NOT retry unchanged. The operator ` +
278
- `controls this flag, not the agent: it exists so a stray fleet call cannot vacuum/reindex the ` +
279
- `store. The equivalent local CLI (\`wyrm maintenance\` / \`wyrm index rebuild\`) is always available.`,
280
- });
281
- }
282
- //# sourceMappingURL=tasks.js.map
1
+ import{randomUUID as u}from"node:crypto";import{renderErrorResponse as p}from"./render.js";const l=300*1e3,f=3600*1e3,k=500,_=256;function h(e){return e!=null&&typeof e=="object"}function m(e){const t=e&&typeof e=="object"?e.task:void 0;if(t==null||typeof t!="object")return{requested:!1};const s=t.ttl;let a=l;return typeof s=="number"&&Number.isFinite(s)&&s>0&&(a=Math.min(Math.floor(s),f)),{requested:!0,ttlMs:a}}class w{tasks=new Map;create(t,s,a=Date.now()){this.evictExpired(a),this.tasks.size>=_&&this.evictOldest();const i=new Date(a).toISOString(),n={taskId:u(),status:"working",ttl:s,createdAt:i,lastUpdatedAt:i,pollInterval:k,tool:t,_createdAtMs:a};return this.tasks.set(n.taskId,n),n}settle(t,s,a,i,n=Date.now()){const r=this.tasks.get(t);r&&(r.status=s,r.lastUpdatedAt=new Date(n).toISOString(),a!==void 0&&(r.result=a),i!==void 0&&(r.statusMessage=i))}get(t,s=Date.now()){return this.evictExpired(s),this.tasks.get(t)}list(t=Date.now()){return this.evictExpired(t),[...this.tasks.values()].sort((s,a)=>a._createdAtMs-s._createdAtMs)}cancel(t,s=Date.now()){const a=this.tasks.get(t);return!a||a.status!=="working"?!1:(a.status="cancelled",a.lastUpdatedAt=new Date(s).toISOString(),!0)}size(){return this.tasks.size}evictExpired(t){for(const[s,a]of this.tasks)a.ttl!=null&&t-a._createdAtMs>a.ttl&&this.tasks.delete(s)}evictOldest(){const t=this.tasks.keys().next();t.done||this.tasks.delete(t.value)}}function A(e){const t={taskId:e.taskId,status:e.status,ttl:e.ttl,createdAt:e.createdAt,lastUpdatedAt:e.lastUpdatedAt};return e.pollInterval!==void 0&&(t.pollInterval=e.pollInterval),e.statusMessage!==void 0&&(t.statusMessage=e.statusMessage),t}function I(e,t){const s=h(e),a=m(t);return a.requested?{asTask:!0,ttlMs:a.ttlMs,capabilityNegotiated:s}:{asTask:!1,ttlMs:l,capabilityNegotiated:s}}async function g(e,t,s,a,i=Date.now()){if(!t.asTask)return{kind:"inline",result:await a()};const n=s.create(e,t.ttlMs,i);let r;try{r=await a()}catch(o){throw s.settle(n.taskId,"failed",void 0,String(o)),o}s.settle(n.taskId,r?.isError?"failed":"completed",r);const d=s.get(n.taskId,i)??n;return{kind:"task",createTask:{task:A(d)}}}function D(e,t,s=Date.now()){return e.get(t,s)?.result}const c=new Set(["wyrm_maintenance","wyrm_reindex","wyrm_vector_setup"]),y=new Set([...c,"wyrm_harvest","wyrm_auto_capture"]),T="WYRM_ADMIN_REQUIRED";function x(e=process.env){const t=e.WYRM_ADMIN;return t==="1"||t==="true"||t==="yes"}function S(e,t=process.env){return c.has(e)&&!x(t)}function E(e){return p({error:{code:T,message:`${e} is an admin-gated maintenance op (it rebuilds or vacuums the local memory store) and is disabled by default.`,retryable:!1},expected:`Set the WYRM_ADMIN=1 environment variable on the Wyrm MCP server process to enable ${e}, then call it again. This rejection is deterministic \u2014 do NOT retry unchanged. The operator controls this flag, not the agent: it exists so a stray fleet call cannot vacuum/reindex the store. The equivalent local CLI (\`wyrm maintenance\` / \`wyrm index rebuild\`) is always available.`})}export{c as ADMIN_GATED_TOOLS,k as DEFAULT_POLL_INTERVAL_MS,l as DEFAULT_TASK_TTL_MS,y as LONG_OP_TOOLS,_ as MAX_RETAINED_TASKS,f as MAX_TASK_TTL_MS,w as TaskStore,T as WYRM_ADMIN_REQUIRED_CODE,E as adminDeniedResponse,S as adminGateBlocks,x as adminGateOpen,h as clientSupportsTasks,m as readTaskAugmentation,I as resolveTaskGate,g as runLongOp,D as taskPayload,A as toWireTask};
@@ -1,143 +1 @@
1
- /**
2
- * Token budget primitive (spec 014).
3
- *
4
- * Pure functions over typed items. No DB, no I/O. Two responsibilities:
5
- *
6
- * 1. Estimate the token cost of a string. Phase 1 uses a deterministic
7
- * 4-characters-per-token approximation (round up — slightly
8
- * over-counts so we stay under budget). A future spec replaces this
9
- * with `tiktoken` for exact `cl100k_base` counts.
10
- *
11
- * 2. Apply a token budget to a ranked list of items: rank-by-score,
12
- * fill until budget, elide the rest as stubs. Items already shown in
13
- * the session are demoted to a "seen" elision bucket.
14
- *
15
- * @copyright 2026 Ghost Protocol (Pvt) Ltd.
16
- * @license AGPL-3.0-or-later — dual-licensed; commercial terms: ghosts.lk@proton.me. See LICENSE.
17
- */
18
- export function makeEstimator() {
19
- // Phase 1: 4-chars-per-token approximation. English averages ~4 cpt
20
- // in cl100k_base; code is closer to 3. Wyrm content is mixed; rounding
21
- // up keeps us under budget rather than over.
22
- return {
23
- source: 'approx-4cpt',
24
- count(text) {
25
- if (!text)
26
- return 0;
27
- return Math.ceil(text.length / 4);
28
- },
29
- };
30
- }
31
- /**
32
- * Apply a token budget to a list of candidate items.
33
- *
34
- * Algorithm:
35
- * 1. Sort by score desc (stable on tie).
36
- * 2. For each item:
37
- * - If key is in `alreadySeen`, elide with reason='seen', count stubCost.
38
- * - Else if inlineCost fits in remaining budget, render inline.
39
- * - Else elide with reason='budget', count stubCost.
40
- * 3. Return both lists + token totals.
41
- *
42
- * Stubs are always emitted (operator can deref via wyrm_recall). The
43
- * `reserved` knob lets callers carve out budget for a stable preamble
44
- * block before items start consuming.
45
- */
46
- export function applyBudget(items, opts) {
47
- const seen = opts.alreadySeen ?? new Set();
48
- const reserved = Math.max(0, opts.reserved ?? 0);
49
- const usable = Math.max(0, opts.budget - reserved);
50
- // Stable sort by score desc. JavaScript Array.prototype.sort is stable
51
- // since ES2019 — relied on here for tie-break determinism.
52
- const sorted = [...items].sort((a, b) => b.score - a.score);
53
- const inline = [];
54
- const elided = [];
55
- let tokensInline = 0;
56
- let tokensStubs = 0;
57
- for (const candidate of sorted) {
58
- if (seen.has(candidate.key)) {
59
- elided.push({
60
- item: candidate.item,
61
- key: candidate.key,
62
- reason: 'seen',
63
- stubCost: candidate.stubCost,
64
- });
65
- tokensStubs += candidate.stubCost;
66
- continue;
67
- }
68
- if (tokensInline + candidate.inlineCost <= usable) {
69
- inline.push(candidate.item);
70
- tokensInline += candidate.inlineCost;
71
- }
72
- else {
73
- elided.push({
74
- item: candidate.item,
75
- key: candidate.key,
76
- reason: 'budget',
77
- stubCost: candidate.stubCost,
78
- });
79
- tokensStubs += candidate.stubCost;
80
- }
81
- }
82
- return {
83
- inline,
84
- elided,
85
- tokensInline,
86
- tokensStubs,
87
- budget: opts.budget,
88
- };
89
- }
90
- // -----------------------------------------------------------------------------
91
- // Model-tier defaults (D1: auto-detect from MCP clientInfo.name; spec 014)
92
- // -----------------------------------------------------------------------------
93
- const FALLBACK_BUDGET = 4096;
94
- /**
95
- * Map a known MCP client name to its sensible default context budget.
96
- * Precedence at call site: call-arg > env > auto > fallback.
97
- */
98
- export function budgetForClient(clientName) {
99
- if (!clientName)
100
- return FALLBACK_BUDGET;
101
- const n = clientName.toLowerCase();
102
- // Anthropic surfaces
103
- if (n.includes('mythos'))
104
- return 4096; // future-proof — same as opus until we know
105
- if (n.includes('opus'))
106
- return 4096;
107
- if (n.includes('sonnet'))
108
- return 8192;
109
- if (n.includes('haiku'))
110
- return 12288;
111
- if (n === 'claude-code' || n.startsWith('claude-code'))
112
- return 4096; // assume opus
113
- if (n === 'claude-desktop' || n.startsWith('claude-desktop'))
114
- return 4096;
115
- // Other clients — assume mid-tier
116
- if (n === 'cursor' || n.includes('cursor'))
117
- return 8192;
118
- if (n === 'windsurf' || n.includes('windsurf'))
119
- return 8192;
120
- if (n === 'zed' || n.includes('zed'))
121
- return 8192;
122
- if (n === 'continue' || n.includes('continue'))
123
- return 8192;
124
- if (n === 'copilot' || n.includes('copilot'))
125
- return 8192;
126
- return FALLBACK_BUDGET;
127
- }
128
- /**
129
- * Resolve the effective budget for a context_build call.
130
- *
131
- * call-arg > WYRM_CONTEXT_TOKEN_BUDGET env > clientName-derived > FALLBACK
132
- */
133
- export function resolveBudget(opts) {
134
- if (opts.callArg && opts.callArg > 0)
135
- return opts.callArg;
136
- if (opts.envValue) {
137
- const n = parseInt(opts.envValue, 10);
138
- if (!Number.isNaN(n) && n > 0)
139
- return n;
140
- }
141
- return budgetForClient(opts.clientName);
142
- }
143
- //# sourceMappingURL=token-budget.js.map
1
+ function h(){return{source:"approx-4cpt",count(t){return t?Math.ceil(t.length/4):0}}}function g(t,e){const c=e.alreadySeen??new Set,l=Math.max(0,e.reserved??0),d=Math.max(0,e.budget-l),a=[...t].sort((n,f)=>f.score-n.score),i=[],r=[];let u=0,s=0;for(const n of a){if(c.has(n.key)){r.push({item:n.item,key:n.key,reason:"seen",stubCost:n.stubCost}),s+=n.stubCost;continue}u+n.inlineCost<=d?(i.push(n.item),u+=n.inlineCost):(r.push({item:n.item,key:n.key,reason:"budget",stubCost:n.stubCost}),s+=n.stubCost)}return{inline:i,elided:r,tokensInline:u,tokensStubs:s,budget:e.budget}}const o=4096;function b(t){if(!t)return o;const e=t.toLowerCase();return e.includes("mythos")||e.includes("opus")?4096:e.includes("sonnet")?8192:e.includes("haiku")?12288:e==="claude-code"||e.startsWith("claude-code")||e==="claude-desktop"||e.startsWith("claude-desktop")?4096:e==="cursor"||e.includes("cursor")||e==="windsurf"||e.includes("windsurf")||e==="zed"||e.includes("zed")||e==="continue"||e.includes("continue")||e==="copilot"||e.includes("copilot")?8192:o}function k(t){if(t.callArg&&t.callArg>0)return t.callArg;if(t.envValue){const e=parseInt(t.envValue,10);if(!Number.isNaN(e)&&e>0)return e}return b(t.clientName)}export{g as applyBudget,b as budgetForClient,h as makeEstimator,k as resolveBudget};
@@ -1,84 +1,8 @@
1
- /**
2
- * Tool Call Analytics — Wyrm watches Wyrm.
3
- *
4
- * Every wyrm_* tool call gets logged here: tool name, args summary,
5
- * success/failure, latency, optional project context. From this stream
6
- * we derive:
7
- *
8
- * - usage histogram (which tools are most-called)
9
- * - error rate per tool
10
- * - p50/p95 latency per tool
11
- * - args-pattern summarisation (what args do callers pass)
12
- * - recall→action correlation (was a returned memory followed by an action?)
13
- *
14
- * Operators see "Claude calls wyrm_recall 80% of the time but the result
15
- * is unused 60% of the time → ranking is bad → tune retrieval".
16
- *
17
- * Privacy: args_summary is truncated and stripped of values longer than
18
- * 100 chars per field — we keep keys and short values for pattern
19
- * analysis, never full payloads.
20
- *
21
- * @copyright 2026 Ghost Protocol (Pvt) Ltd.
22
- * @license AGPL-3.0-or-later — dual-licensed; commercial terms: ghosts.lk@proton.me. See LICENSE.
23
- */
24
- export class ToolAnalytics {
25
- db;
26
- constructor(db) {
27
- this.db = db;
28
- }
29
- /** Truncate args for logging — drop long values, keep keys + short values. */
30
- static summarizeArgs(args) {
31
- if (args == null)
32
- return '';
33
- if (typeof args !== 'object')
34
- return String(args).slice(0, 100);
35
- try {
36
- const o = args;
37
- const out = {};
38
- for (const [k, v] of Object.entries(o)) {
39
- if (v == null) {
40
- out[k] = null;
41
- continue;
42
- }
43
- if (typeof v === 'string') {
44
- out[k] = v.length > 100 ? `<${v.length}ch>` : v;
45
- }
46
- else if (typeof v === 'number' || typeof v === 'boolean') {
47
- out[k] = v;
48
- }
49
- else if (Array.isArray(v)) {
50
- out[k] = `<array:${v.length}>`;
51
- }
52
- else if (typeof v === 'object') {
53
- out[k] = `<object>`;
54
- }
55
- }
56
- return JSON.stringify(out).slice(0, 500);
57
- }
58
- catch {
59
- return '<unserializable>';
60
- }
61
- }
62
- /** Log a single tool call. Designed to be fire-and-forget — swallows errors
63
- * so logging never breaks the tool's own response path. */
64
- log(input) {
65
- try {
66
- this.db.prepare(`
1
+ class _{db;constructor(t){this.db=t}static summarizeArgs(t){if(t==null)return"";if(typeof t!="object")return String(t).slice(0,100);try{const s=t,e={};for(const[a,o]of Object.entries(s)){if(o==null){e[a]=null;continue}typeof o=="string"?e[a]=o.length>100?`<${o.length}ch>`:o:typeof o=="number"||typeof o=="boolean"?e[a]=o:Array.isArray(o)?e[a]=`<array:${o.length}>`:typeof o=="object"&&(e[a]="<object>")}return JSON.stringify(e).slice(0,500)}catch{return"<unserializable>"}}log(t){try{this.db.prepare(`
67
2
  INSERT INTO tool_call_log
68
3
  (tool_name, project_id, args_summary, success, error_message, latency_ms)
69
4
  VALUES (?, ?, ?, ?, ?, ?)
70
- `).run(input.tool_name, input.project_id ?? null, input.args !== undefined ? ToolAnalytics.summarizeArgs(input.args) : null, input.success ? 1 : 0, input.error_message ?? null, Math.max(0, Math.round(input.latency_ms)));
71
- }
72
- catch {
73
- // Logging must never break the tool path.
74
- }
75
- }
76
- /** Per-tool usage statistics over the last `days` days (default 30). */
77
- usageStats(opts) {
78
- const days = opts?.days ?? 30;
79
- const limit = opts?.limit ?? 100;
80
- // Get aggregate stats per tool
81
- const aggregates = this.db.prepare(`
5
+ `).run(t.tool_name,t.project_id??null,t.args!==void 0?_.summarizeArgs(t.args):null,t.success?1:0,t.error_message??null,Math.max(0,Math.round(t.latency_ms)))}catch{}}usageStats(t){const s=t?.days??30,e=t?.limit??100,a=this.db.prepare(`
82
6
  SELECT
83
7
  tool_name,
84
8
  COUNT(*) AS total,
@@ -90,49 +14,18 @@ export class ToolAnalytics {
90
14
  GROUP BY tool_name
91
15
  ORDER BY total DESC
92
16
  LIMIT ?
93
- `).all(days, limit);
94
- // For each tool, compute p50/p95 latency (small enough to do in-process)
95
- const results = [];
96
- for (const a of aggregates) {
97
- const latencies = this.db.prepare(`
17
+ `).all(s,e),o=[];for(const l of a){const c=this.db.prepare(`
98
18
  SELECT latency_ms FROM tool_call_log
99
19
  WHERE tool_name = ?
100
20
  AND called_at >= datetime('now', '-' || ? || ' days')
101
21
  ORDER BY latency_ms
102
- `).all(a.tool_name, days).map(r => r.latency_ms);
103
- const p = (arr, pct) => {
104
- if (arr.length === 0)
105
- return 0;
106
- const idx = Math.min(arr.length - 1, Math.floor((arr.length - 1) * pct));
107
- return arr[idx];
108
- };
109
- results.push({
110
- tool_name: a.tool_name,
111
- total: a.total,
112
- success_count: a.success_count,
113
- error_count: a.error_count,
114
- error_rate: a.total > 0 ? Math.round((a.error_count / a.total) * 1000) / 1000 : 0,
115
- p50_ms: p(latencies, 0.5),
116
- p95_ms: p(latencies, 0.95),
117
- last_called: a.last_called,
118
- });
119
- }
120
- return results;
121
- }
122
- /** Recent error sample for a tool — useful when error_rate > 0. */
123
- recentErrors(toolName, limit = 10) {
124
- return this.db.prepare(`
22
+ `).all(l.tool_name,s).map(r=>r.latency_ms),n=(r,u)=>{if(r.length===0)return 0;const i=Math.min(r.length-1,Math.floor((r.length-1)*u));return r[i]};o.push({tool_name:l.tool_name,total:l.total,success_count:l.success_count,error_count:l.error_count,error_rate:l.total>0?Math.round(l.error_count/l.total*1e3)/1e3:0,p50_ms:n(c,.5),p95_ms:n(c,.95),last_called:l.last_called})}return o}recentErrors(t,s=10){return this.db.prepare(`
125
23
  SELECT called_at, error_message, args_summary
126
24
  FROM tool_call_log
127
25
  WHERE tool_name = ? AND success = 0
128
26
  ORDER BY called_at DESC
129
27
  LIMIT ?
130
- `).all(toolName, limit);
131
- }
132
- /** Total volume + success rate over the whole window. */
133
- overview(opts) {
134
- const days = opts?.days ?? 30;
135
- const row = this.db.prepare(`
28
+ `).all(t,s)}overview(t){const s=t?.days??30,e=this.db.prepare(`
136
29
  SELECT
137
30
  COUNT(*) AS total,
138
31
  SUM(success) AS success_count,
@@ -140,22 +33,7 @@ export class ToolAnalytics {
140
33
  COUNT(DISTINCT tool_name) AS distinct_tools
141
34
  FROM tool_call_log
142
35
  WHERE called_at >= datetime('now', '-' || ? || ' days')
143
- `).get(days);
144
- return {
145
- days,
146
- total_calls: row.total || 0,
147
- success_count: row.success_count || 0,
148
- error_count: row.error_count || 0,
149
- success_rate: row.total > 0 ? Math.round((row.success_count / row.total) * 1000) / 1000 : 0,
150
- distinct_tools: row.distinct_tools || 0,
151
- };
152
- }
153
- /** Prune rows older than N days. Returns count deleted. Run via maintenance. */
154
- prune(olderThanDays = 90) {
155
- return this.db.prepare(`
36
+ `).get(s);return{days:s,total_calls:e.total||0,success_count:e.success_count||0,error_count:e.error_count||0,success_rate:e.total>0?Math.round(e.success_count/e.total*1e3)/1e3:0,distinct_tools:e.distinct_tools||0}}prune(t=90){return this.db.prepare(`
156
37
  DELETE FROM tool_call_log
157
38
  WHERE called_at < datetime('now', '-' || ? || ' days')
158
- `).run(olderThanDays).changes;
159
- }
160
- }
161
- //# sourceMappingURL=tool-analytics.js.map
39
+ `).run(t).changes}}export{_ as ToolAnalytics};