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.
- package/LICENSE +26 -667
- package/NOTICE +14 -33
- package/dist/activation.d.ts.map +1 -1
- package/dist/activation.js +1 -44
- package/dist/activation.js.map +1 -1
- package/dist/agent-daemon.js +4 -281
- package/dist/agent-loop.js +7 -332
- package/dist/analytics.js +13 -236
- package/dist/attribution.js +1 -49
- package/dist/audit.js +2 -457
- package/dist/auto-capture.js +3 -138
- package/dist/auto-orchestrator.js +1 -325
- package/dist/autoconfig.js +39 -840
- package/dist/buddy-runner.js +1 -109
- package/dist/buddy.js +14 -564
- package/dist/build-flags.js +1 -17
- package/dist/capabilities.js +3 -183
- package/dist/capture.js +1 -56
- package/dist/causality.js +6 -107
- package/dist/cli.js +20 -281
- package/dist/cloud/cli.js +5 -541
- package/dist/cloud/client.js +1 -221
- package/dist/cloud/crypto.js +1 -85
- package/dist/cloud/machine-id.js +2 -113
- package/dist/cloud/recovery.js +1 -60
- package/dist/cloud/sync-engine.js +7 -543
- package/dist/cloud-backup.js +5 -579
- package/dist/cloud-profile.js +1 -138
- package/dist/cloud-sync-entrypoint.js +1 -47
- package/dist/cloud-sync.js +2 -309
- package/dist/constellation.js +12 -168
- package/dist/context-build-budgeted.js +4 -144
- package/dist/context-ranking.js +1 -69
- package/dist/crypto.js +1 -179
- package/dist/daemon-write-endpoint.js +1 -290
- package/dist/daemon-writer.js +2 -406
- package/dist/database.js +43 -1110
- package/dist/deprecations.js +2 -162
- package/dist/design.js +13 -141
- package/dist/event-replication.js +1 -112
- package/dist/events-sse.js +7 -43
- package/dist/events.js +6 -238
- package/dist/failure-patterns.js +42 -659
- package/dist/federation.js +12 -236
- package/dist/goals.js +13 -101
- package/dist/golden.js +3 -355
- package/dist/handlers/agent.js +4 -165
- package/dist/handlers/alias-adapters.js +1 -129
- package/dist/handlers/aliases.js +1 -171
- package/dist/handlers/audit.js +1 -87
- package/dist/handlers/boundary.js +1 -221
- package/dist/handlers/capture.js +73 -1109
- package/dist/handlers/causality.js +7 -114
- package/dist/handlers/cloud.js +85 -382
- package/dist/handlers/companion.js +28 -459
- package/dist/handlers/datalake.js +7 -187
- package/dist/handlers/dispatch-context.js +0 -22
- package/dist/handlers/entity.js +25 -256
- package/dist/handlers/events.js +16 -335
- package/dist/handlers/failure.js +13 -340
- package/dist/handlers/goals.js +4 -296
- package/dist/handlers/intelligence.js +126 -674
- package/dist/handlers/invoicing.js +1 -70
- package/dist/handlers/mcpclient.js +6 -137
- package/dist/handlers/orchestration.js +40 -125
- package/dist/handlers/output-schemas.js +1 -24
- package/dist/handlers/presence.js +3 -99
- package/dist/handlers/project.js +28 -182
- package/dist/handlers/prompts.js +6 -157
- package/dist/handlers/quest.js +4 -224
- package/dist/handlers/recall.js +11 -218
- package/dist/handlers/registry.js +1 -167
- package/dist/handlers/resources.js +1 -288
- package/dist/handlers/review.js +11 -74
- package/dist/handlers/run.js +17 -487
- package/dist/handlers/search.js +15 -326
- package/dist/handlers/session.js +28 -615
- package/dist/handlers/share.js +8 -184
- package/dist/handlers/shims.js +1 -464
- package/dist/handlers/skill.js +67 -449
- package/dist/handlers/survivors.js +1 -120
- package/dist/handlers/symbols.js +8 -109
- package/dist/handlers/syncops.js +4 -302
- package/dist/handlers/types.js +1 -27
- package/dist/harvest.js +5 -191
- package/dist/hours.js +7 -156
- package/dist/http-auth.js +3 -321
- package/dist/http-fast.js +21 -1137
- package/dist/icons.js +1 -47
- package/dist/index.js +2 -924
- package/dist/indexer.js +4 -145
- package/dist/intelligence.js +31 -261
- package/dist/internal-dispatch.js +3 -212
- package/dist/keyset.js +1 -110
- package/dist/knowledge-graph.js +12 -176
- package/dist/license.d.ts +11 -0
- package/dist/license.d.ts.map +1 -1
- package/dist/license.js +2 -414
- package/dist/license.js.map +1 -1
- package/dist/logger.js +2 -199
- package/dist/maintenance.js +2 -148
- package/dist/mcp-client.js +6 -262
- package/dist/memory-artifacts.js +30 -449
- package/dist/migrate-prompt.js +2 -124
- package/dist/migrations.js +40 -655
- package/dist/performance.js +1 -228
- package/dist/presence.js +11 -140
- package/dist/priority-embed.js +5 -164
- package/dist/providers/embedding-provider.js +1 -196
- package/dist/readonly-gate.js +1 -29
- package/dist/rehydration.js +9 -157
- package/dist/reindex.js +1 -88
- package/dist/render-target.js +21 -514
- package/dist/render.js +4 -280
- package/dist/repl-guard.js +1 -173
- package/dist/replication-daemon-entrypoint.js +1 -31
- package/dist/replication-daemon.js +2 -262
- package/dist/resilience.js +1 -591
- package/dist/reverse-bridge.js +5 -360
- package/dist/security.js +1 -244
- package/dist/session-seen.js +3 -51
- package/dist/setup.js +1 -260
- package/dist/skill-author.js +5 -168
- package/dist/spec-kit.js +1 -191
- package/dist/sqlite-busy.js +1 -154
- package/dist/statusline.js +11 -315
- package/dist/sub-agent.js +13 -262
- package/dist/summarizer.js +13 -139
- package/dist/symbols.js +7 -283
- package/dist/sync.js +5 -359
- package/dist/tasks-dispatch.js +1 -84
- package/dist/tasks.js +1 -282
- package/dist/token-budget.js +1 -143
- package/dist/tool-analytics.js +7 -129
- package/dist/tool-annotations.js +1 -365
- package/dist/tool-manifest-v2.json +1 -1
- package/dist/tool-manifest.json +1 -1
- package/dist/tool-profiles.js +1 -75
- package/dist/trace-harvest.js +6 -244
- package/dist/types.js +1 -30
- package/dist/ui-dashboard.js +41 -50
- package/dist/ulid.js +1 -81
- package/dist/validate.js +1 -129
- package/dist/vault.js +1 -534
- package/dist/vectors.js +3 -184
- package/dist/version-check.js +4 -136
- package/dist/visibility.js +19 -155
- package/dist/wyrm-cli.js +98 -2451
- package/dist/wyrm-cli.js.map +1 -1
- package/dist/wyrm-guard.js +14 -424
- package/dist/wyrm-loop.js +3 -150
- package/dist/wyrm-manifest.json +1 -1
- package/dist/wyrm-statusline-daemon.js +1 -11
- package/dist/wyrm-statusline.js +4 -56
- package/dist/wyrm-ui.js +9 -77
- package/package.json +4 -2
package/dist/golden.js
CHANGED
|
@@ -1,355 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
*
|
|
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
|
+
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};
|
package/dist/handlers/agent.js
CHANGED
|
@@ -1,165 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
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,129 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* v7 F3 (T021) — hand-written argument adapters for the alias spine.
|
|
3
|
-
*
|
|
4
|
-
* The spine itself (src/handlers/aliases.ts) is GENERATED from the live
|
|
5
|
-
* ListTools surface by scripts/gen-alias-spine.mjs; this module is the one
|
|
6
|
-
* hand-authored input it consumes: per-alias ROUTE OVERRIDES that map a 6.x
|
|
7
|
-
* name onto a different (surviving) tool with an argument adapter.
|
|
8
|
-
*
|
|
9
|
-
* Adapters TRANSLATE arguments — they never reimplement handler behavior
|
|
10
|
-
* (spec FR-4: "aliases route to the SAME handler code paths with argument
|
|
11
|
-
* adapters — never reimplementations"). Every override is double-verified:
|
|
12
|
-
* - tests/alias-spine.test.ts replays adapter output against the TARGET's
|
|
13
|
-
* advertised inputSchema (the original handler's declared expectations);
|
|
14
|
-
* - tests/golden-replay.test.ts replays the alias's golden fixtures BOTH
|
|
15
|
-
* ways on the live wire (direct name vs adapted route) and asserts
|
|
16
|
-
* equivalent results.
|
|
17
|
-
*
|
|
18
|
-
* Default route for every alias NOT listed here is IDENTITY (the alias's own
|
|
19
|
-
* 6.x dispatcher path) until T022 flips routes to the survivor shims —
|
|
20
|
-
* aliases must be provably equivalent BEFORE anything is hidden.
|
|
21
|
-
*
|
|
22
|
-
* @copyright 2026 Ghost Protocol (Pvt) Ltd.
|
|
23
|
-
* @license AGPL-3.0-or-later
|
|
24
|
-
*/
|
|
25
|
-
import { SHIM_ROUTES } from './shims.js';
|
|
26
|
-
/**
|
|
27
|
-
* T021 buddy fold — the companion params wyrm_buddy contributed to the
|
|
28
|
-
* well-known `buddy` tool as ADDITIVE optional args. Their presence is what
|
|
29
|
-
* switches `buddy` from the Buddy Protocol v1.0 peer reply onto the SAME
|
|
30
|
-
* companion code path wyrm_buddy runs (runCompanionBuddy in index.ts).
|
|
31
|
-
* `size` is deliberately NOT a discriminator: the peer protocol already
|
|
32
|
-
* carries it, and a bare peer call must stay protocol-identical (spec FR-4:
|
|
33
|
-
* "protocol behavior unchanged when absent").
|
|
34
|
-
*/
|
|
35
|
-
export const COMPANION_BUDDY_PARAMS = [
|
|
36
|
-
'project_path',
|
|
37
|
-
'persona',
|
|
38
|
-
'persona_name',
|
|
39
|
-
'mood',
|
|
40
|
-
'federate',
|
|
41
|
-
];
|
|
42
|
-
/** True iff any companion-only param is present — the buddy-fold mode switch. */
|
|
43
|
-
export function isCompanionBuddyCall(args) {
|
|
44
|
-
return COMPANION_BUDDY_PARAMS.some((k) => args[k] !== undefined);
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* wyrm_buddy → buddy argument adapter. Param names are identical on the
|
|
48
|
-
* grown buddy schema, so the only translation is injecting the resolved
|
|
49
|
-
* project_path when absent — exactly the resolution the original handler
|
|
50
|
-
* performs first (`const cwd = bdPath ?? process.cwd()`), hoisted into the
|
|
51
|
-
* adapter so a BARE wyrm_buddy call still discriminates as companion mode.
|
|
52
|
-
* Same process, same value — equivalence holds byte-for-byte.
|
|
53
|
-
*/
|
|
54
|
-
export function adaptWyrmBuddyToBuddy(args) {
|
|
55
|
-
const out = { ...args };
|
|
56
|
-
if (out.project_path === undefined)
|
|
57
|
-
out.project_path = process.cwd();
|
|
58
|
-
return out;
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* v7 F3 (T022) — the noun-shim absorptions, INVERTED from the same
|
|
62
|
-
* SHIM_ROUTES table resolveShimCall() dispatches with (src/handlers/shims.ts).
|
|
63
|
-
* One source of truth, two directions:
|
|
64
|
-
*
|
|
65
|
-
* alias call: wyrm_quest_add(args)
|
|
66
|
-
* → spine adapt: wyrm_quest({ ...args, action: 'add' })
|
|
67
|
-
* → resolveShimCall: wyrm_quest_add(args) // discriminator stripped
|
|
68
|
-
*
|
|
69
|
-
* i.e. every generated override roundtrips to the ORIGINAL dispatcher case
|
|
70
|
-
* with the ORIGINAL args by construction — the same handler code path, never
|
|
71
|
-
* a reimplementation (locked exhaustively by tests/alias-spine.test.ts, which
|
|
72
|
-
* asserts the roundtrip identity for every override on its fixture args).
|
|
73
|
-
*
|
|
74
|
-
* The discriminator is spread LAST so it is authoritative; no absorbed 6.x
|
|
75
|
-
* tool declares its own `action`/`mode`/`source` param (audited against the
|
|
76
|
-
* live schemas at T022 — wyrm_sync_resolve's own `mode` rides through the
|
|
77
|
-
* `action`-discriminated replication route untouched).
|
|
78
|
-
*/
|
|
79
|
-
function invertShimRoutes() {
|
|
80
|
-
const out = {};
|
|
81
|
-
const claim = (aliasName, override) => {
|
|
82
|
-
if (out[aliasName] !== undefined) {
|
|
83
|
-
// fail LOUD at module load — a double absorption would make the
|
|
84
|
-
// disposition table ambiguous and the roundtrip lock meaningless
|
|
85
|
-
throw new Error(`alias-adapters: ${aliasName} absorbed by two shims (${out[aliasName].target} and ${override.target})`);
|
|
86
|
-
}
|
|
87
|
-
out[aliasName] = override;
|
|
88
|
-
};
|
|
89
|
-
for (const [shimName, entry] of Object.entries(SHIM_ROUTES)) {
|
|
90
|
-
for (const [value, route] of Object.entries(entry.routes)) {
|
|
91
|
-
if (typeof route === 'string') {
|
|
92
|
-
if (route === shimName)
|
|
93
|
-
continue; // self-route (capture mode=classify) — not an absorption
|
|
94
|
-
claim(route, {
|
|
95
|
-
target: shimName,
|
|
96
|
-
adapt: (args) => ({ ...args, [entry.discriminator]: value }),
|
|
97
|
-
note: `T022: absorbed by ${shimName} ${entry.discriminator}=${value}; resolveShimCall strips the discriminator back onto the original ${route} dispatcher case — same code path by construction (roundtrip-locked).`,
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
for (const [subValue, target] of Object.entries(route.routes)) {
|
|
102
|
-
claim(target, {
|
|
103
|
-
target: shimName,
|
|
104
|
-
adapt: (args) => ({ ...args, [entry.discriminator]: value, [route.key]: subValue }),
|
|
105
|
-
note: `T022: absorbed by ${shimName} ${entry.discriminator}=${value} ${route.key}=${subValue}; resolveShimCall strips both back onto the original ${target} dispatcher case — same code path by construction (roundtrip-locked).`,
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return out;
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Route overrides keyed by 6.x alias name: the T021 buddy fold plus the T022
|
|
115
|
-
* shim absorptions (generated from SHIM_ROUTES — see invertShimRoutes above).
|
|
116
|
-
* Every key MUST be in the generated alias keyset and every target MUST be a
|
|
117
|
-
* survivor — locked by tests/alias-spine.test.ts.
|
|
118
|
-
*/
|
|
119
|
-
export const ALIAS_ROUTE_OVERRIDES = {
|
|
120
|
-
...invertShimRoutes(),
|
|
121
|
-
wyrm_buddy: {
|
|
122
|
-
target: 'buddy',
|
|
123
|
-
adapt: adaptWyrmBuddyToBuddy,
|
|
124
|
-
note: 'T021 buddy fold: companion params (persona/mood/size/federation) merged into the ' +
|
|
125
|
-
'well-known buddy tool as additive optional args; both names run runCompanionBuddy ' +
|
|
126
|
-
'(index.ts) — one code path, not a duplicate.',
|
|
127
|
-
},
|
|
128
|
-
};
|
|
129
|
-
//# sourceMappingURL=alias-adapters.js.map
|
|
1
|
+
import{SHIM_ROUTES as c}from"./shims.js";const p=["project_path","persona","persona_name","mood","federate"];function f(o){return p.some(e=>o[e]!==void 0)}function u(o){const e={...o};return e.project_path===void 0&&(e.project_path=process.cwd()),e}function m(){const o={},e=(t,r)=>{if(o[t]!==void 0)throw new Error(`alias-adapters: ${t} absorbed by two shims (${o[t].target} and ${r.target})`);o[t]=r};for(const[t,r]of Object.entries(c))for(const[a,n]of Object.entries(r.routes))if(typeof n=="string"){if(n===t)continue;e(n,{target:t,adapt:i=>({...i,[r.discriminator]:a}),note:`T022: absorbed by ${t} ${r.discriminator}=${a}; resolveShimCall strips the discriminator back onto the original ${n} dispatcher case \u2014 same code path by construction (roundtrip-locked).`})}else for(const[i,d]of Object.entries(n.routes))e(d,{target:t,adapt:s=>({...s,[r.discriminator]:a,[n.key]:i}),note:`T022: absorbed by ${t} ${r.discriminator}=${a} ${n.key}=${i}; resolveShimCall strips both back onto the original ${d} dispatcher case \u2014 same code path by construction (roundtrip-locked).`});return o}const h={...m(),wyrm_buddy:{target:"buddy",adapt:u,note:"T021 buddy fold: companion params (persona/mood/size/federation) merged into the well-known buddy tool as additive optional args; both names run runCompanionBuddy (index.ts) \u2014 one code path, not a duplicate."}};export{h as ALIAS_ROUTE_OVERRIDES,p as COMPANION_BUDDY_PARAMS,u as adaptWyrmBuddyToBuddy,f as isCompanionBuddyCall};
|