debuganything 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/.claude-plugin/plugin.json +15 -0
  2. package/DISCLAIMER.md +101 -0
  3. package/GEMINI.md +97 -0
  4. package/LICENSE +21 -0
  5. package/README.md +283 -2
  6. package/assets/README.md +9 -0
  7. package/assets/debuganything-logo.png +0 -0
  8. package/assets/debuganything-mark-512.png +0 -0
  9. package/dist/chunk-32IDKAZK.js +9 -0
  10. package/dist/chunk-32IDKAZK.js.map +1 -0
  11. package/dist/chunk-5BXTDASF.js +135 -0
  12. package/dist/chunk-5BXTDASF.js.map +1 -0
  13. package/dist/chunk-5RAKTYW5.js +292 -0
  14. package/dist/chunk-5RAKTYW5.js.map +1 -0
  15. package/dist/chunk-7MMXFUUN.js +79 -0
  16. package/dist/chunk-7MMXFUUN.js.map +1 -0
  17. package/dist/chunk-D742WJPT.js +436 -0
  18. package/dist/chunk-D742WJPT.js.map +1 -0
  19. package/dist/chunk-EMR42ZJQ.js +102 -0
  20. package/dist/chunk-EMR42ZJQ.js.map +1 -0
  21. package/dist/chunk-MLDOO673.js +1416 -0
  22. package/dist/chunk-MLDOO673.js.map +1 -0
  23. package/dist/chunk-NFL22T3H.js +309 -0
  24. package/dist/chunk-NFL22T3H.js.map +1 -0
  25. package/dist/chunk-STFC7C5C.js +245 -0
  26. package/dist/chunk-STFC7C5C.js.map +1 -0
  27. package/dist/chunk-VGYXHXTY.js +1015 -0
  28. package/dist/chunk-VGYXHXTY.js.map +1 -0
  29. package/dist/chunk-YY6M5RIJ.js +215 -0
  30. package/dist/chunk-YY6M5RIJ.js.map +1 -0
  31. package/dist/cli.js +254 -0
  32. package/dist/cli.js.map +1 -0
  33. package/dist/docker-ORXYMES4.js +23 -0
  34. package/dist/docker-ORXYMES4.js.map +1 -0
  35. package/dist/hook-QYLLBUNK.js +14 -0
  36. package/dist/hook-QYLLBUNK.js.map +1 -0
  37. package/dist/import-CT55AEEE.js +268 -0
  38. package/dist/import-CT55AEEE.js.map +1 -0
  39. package/dist/investigate-2GFD6EWB.js +102 -0
  40. package/dist/investigate-2GFD6EWB.js.map +1 -0
  41. package/dist/lsof-GBLZJBJR.js +22 -0
  42. package/dist/lsof-GBLZJBJR.js.map +1 -0
  43. package/dist/psmem-UB3VCMG3.js +20 -0
  44. package/dist/psmem-UB3VCMG3.js.map +1 -0
  45. package/dist/report_html-2ENM7HLC.js +99 -0
  46. package/dist/report_html-2ENM7HLC.js.map +1 -0
  47. package/dist/server.js +190 -0
  48. package/dist/server.js.map +1 -0
  49. package/dist/setup-NTPRUTMT.js +265 -0
  50. package/dist/setup-NTPRUTMT.js.map +1 -0
  51. package/dist/target-WJE4WFYA.js +36 -0
  52. package/dist/target-WJE4WFYA.js.map +1 -0
  53. package/gemini-extension.json +9 -0
  54. package/hooks/debuganything-hook.mjs +36 -0
  55. package/hooks/hooks.json +10 -0
  56. package/package.json +85 -3
  57. package/server.json +30 -0
  58. package/skills/debug-anything/SKILL.md +102 -0
  59. package/skills/inspect-product/SKILL.md +138 -0
@@ -0,0 +1,1015 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ dockerAdapter
4
+ } from "./chunk-YY6M5RIJ.js";
5
+ import {
6
+ psmemAdapter,
7
+ snapshotMemoryGeneric
8
+ } from "./chunk-EMR42ZJQ.js";
9
+ import {
10
+ fsUsageAdapter,
11
+ gdbAdapter,
12
+ jvmAdapter,
13
+ lldbAdapter,
14
+ mergeResults,
15
+ perfAdapter,
16
+ pyspyAdapter,
17
+ sampleMacosAdapter,
18
+ straceAdapter,
19
+ tcpdumpAdapter
20
+ } from "./chunk-MLDOO673.js";
21
+ import {
22
+ lsofAdapter
23
+ } from "./chunk-NFL22T3H.js";
24
+ import {
25
+ ResultBuilder,
26
+ failedResult,
27
+ humanBytes
28
+ } from "./chunk-5BXTDASF.js";
29
+ import {
30
+ listProcesses,
31
+ resolveTarget
32
+ } from "./chunk-5RAKTYW5.js";
33
+ import {
34
+ ToolArgs,
35
+ ToolName,
36
+ createSession,
37
+ detachSession,
38
+ loadRun,
39
+ loadSession,
40
+ platform,
41
+ recordRun,
42
+ requireAttached,
43
+ saveSession,
44
+ sessionsDir
45
+ } from "./chunk-D742WJPT.js";
46
+ import {
47
+ VERSION
48
+ } from "./chunk-32IDKAZK.js";
49
+ import {
50
+ ConsentRequiredError,
51
+ isRoot,
52
+ redact,
53
+ requireConsent
54
+ } from "./chunk-STFC7C5C.js";
55
+
56
+ // src/tools.ts
57
+ import { join as join2 } from "path";
58
+ import { writeFile } from "fs/promises";
59
+
60
+ // src/readiness.ts
61
+ import { readFile as readFile2 } from "fs/promises";
62
+ import { existsSync } from "fs";
63
+ import { delimiter, join } from "path";
64
+ import { arch, release } from "os";
65
+ import { execa } from "execa";
66
+
67
+ // src/adapters/heap_node.ts
68
+ import { createWriteStream } from "fs";
69
+ import { readFile } from "fs/promises";
70
+ function summarizeHeapSnapshot(json, topN = 15) {
71
+ const snap = JSON.parse(json);
72
+ const nf = snap.snapshot.meta.node_fields;
73
+ const ef = snap.snapshot.meta.edge_fields;
74
+ const nodeTypes = snap.snapshot.meta.node_types[0];
75
+ const edgeTypes = snap.snapshot.meta.edge_types[0];
76
+ const NF = nf.length;
77
+ const EF = ef.length;
78
+ const iType = nf.indexOf("type");
79
+ const iName = nf.indexOf("name");
80
+ const iSize = nf.indexOf("self_size");
81
+ const iEdges = nf.indexOf("edge_count");
82
+ const eType = ef.indexOf("type");
83
+ const eName = ef.indexOf("name_or_index");
84
+ const eTo = ef.indexOf("to_node");
85
+ const nodes = snap.nodes;
86
+ const edges = snap.edges;
87
+ const strings = snap.strings;
88
+ const n = nodes.length / NF;
89
+ const firstEdge = new Uint32Array(n + 1);
90
+ for (let i = 0, e = 0; i < n; i++) {
91
+ firstEdge[i] = e;
92
+ e += nodes[i * NF + iEdges] * EF;
93
+ }
94
+ firstEdge[n] = edges.length;
95
+ const byType = /* @__PURE__ */ new Map();
96
+ const byName = /* @__PURE__ */ new Map();
97
+ let total = 0;
98
+ for (let i = 0; i < n; i++) {
99
+ const t = nodeTypes[nodes[i * NF + iType]] ?? "?";
100
+ const nm = strings[nodes[i * NF + iName]] ?? "?";
101
+ const s = nodes[i * NF + iSize];
102
+ total += s;
103
+ const bt = byType.get(t) ?? { count: 0, self_size: 0 };
104
+ bt.count++;
105
+ bt.self_size += s;
106
+ byType.set(t, bt);
107
+ const key = t === "object" || t === "closure" || t === "array" ? nm : `(${t})`;
108
+ const bn = byName.get(key) ?? { type: t, count: 0, self_size: 0 };
109
+ bn.count++;
110
+ bn.self_size += s;
111
+ byName.set(key, bn);
112
+ }
113
+ const weakIdx = edgeTypes.indexOf("weak");
114
+ const shortcutIdx = edgeTypes.indexOf("shortcut");
115
+ const traversable = (etIdx) => etIdx !== weakIdx && etIdx !== shortcutIdx;
116
+ const strong = new Set(["property", "element", "internal", "hidden", "shortcut", "context"].map((t) => edgeTypes.indexOf(t)).filter((i) => i >= 0));
117
+ const typeOf = (i) => nodeTypes[nodes[i * NF + iType]] ?? "?";
118
+ const nameOf = (i) => strings[nodes[i * NF + iName]] ?? "?";
119
+ const reach = (skip) => {
120
+ const visited = new Uint8Array(n);
121
+ const queue = new Int32Array(n);
122
+ let head = 0;
123
+ let tail = 0;
124
+ queue[tail++] = 0;
125
+ visited[0] = 1;
126
+ let bytes = 0;
127
+ let count = 0;
128
+ while (head < tail) {
129
+ const i = queue[head++];
130
+ count++;
131
+ bytes += nodes[i * NF + iSize];
132
+ for (let e = firstEdge[i]; e < firstEdge[i + 1]; e += EF) {
133
+ if (!traversable(edges[e + eType])) continue;
134
+ const to = edges[e + eTo] / NF;
135
+ if (to === skip || visited[to]) continue;
136
+ visited[to] = 1;
137
+ queue[tail++] = to;
138
+ }
139
+ }
140
+ return { count, bytes };
141
+ };
142
+ const full = reach(-1);
143
+ const candidates = /* @__PURE__ */ new Set();
144
+ const bySelf = Array.from({ length: n }, (_, i) => i).sort((a, b) => nodes[b * NF + iSize] - nodes[a * NF + iSize]).slice(0, 30);
145
+ for (const i of bySelf) candidates.add(i);
146
+ const byEdges = Array.from({ length: n }, (_, i) => i).sort((a, b) => nodes[b * NF + iEdges] - nodes[a * NF + iEdges]).slice(0, 30);
147
+ for (const i of byEdges) candidates.add(i);
148
+ const retained = [];
149
+ const skipTypes = /* @__PURE__ */ new Set(["synthetic", "hidden", "array", "code", "object shape", "number", "bigint", "symbol", "regexp"]);
150
+ for (const start of candidates) {
151
+ if (start === 0 || skipTypes.has(typeOf(start))) continue;
152
+ if (nameOf(start).startsWith("(") && typeOf(start) !== "native") continue;
153
+ const without = reach(start);
154
+ const sum = Math.max(nodes[start * NF + iSize], full.bytes - without.bytes);
155
+ retained.push({ name: nameOf(start).slice(0, 80) || `(${typeOf(start)})`, type: typeOf(start), retained_bytes: sum, self_size: nodes[start * NF + iSize], idx: start });
156
+ }
157
+ retained.sort((a, b) => b.retained_bytes - a.retained_bytes);
158
+ const top = retained.slice(0, topN);
159
+ const wanted = /* @__PURE__ */ new Map();
160
+ for (const r of top) if (r.idx !== void 0) wanted.set(r.idx, r);
161
+ const best = /* @__PURE__ */ new Map();
162
+ for (let i = 0; i < n && wanted.size; i++) {
163
+ for (let e = firstEdge[i]; e < firstEdge[i + 1]; e += EF) {
164
+ const to = edges[e + eTo] / NF;
165
+ const r = wanted.get(to);
166
+ if (!r) continue;
167
+ const etIdx = edges[e + eType];
168
+ if (!strong.has(etIdx)) continue;
169
+ const et = edgeTypes[etIdx];
170
+ const en = et === "element" || et === "hidden" ? `[${edges[e + eName]}]` : strings[edges[e + eName]] ?? "?";
171
+ const ownerName = nameOf(i) || `(${typeOf(i)})`;
172
+ const score = (et === "context" ? 3 : et === "property" ? 2 : et === "internal" ? 1 : 0) + (ownerName.startsWith("(") ? 0 : 1);
173
+ const prev = best.get(to);
174
+ if (!prev || score > prev.score) best.set(to, { score, label: `${ownerName.slice(0, 40)}.${en}` });
175
+ }
176
+ }
177
+ for (const r of top) if (r.idx !== void 0) r.retainer = best.get(r.idx)?.label;
178
+ for (const r of top) delete r.idx;
179
+ return {
180
+ node_count: n,
181
+ edge_count: edges.length / EF,
182
+ total_size: total,
183
+ byType: [...byType].map(([type, v]) => ({ type, ...v })).sort((a, b) => b.self_size - a.self_size),
184
+ byName: [...byName].map(([name, v]) => ({ name, ...v })).sort((a, b) => b.self_size - a.self_size).slice(0, 25),
185
+ topRetained: top
186
+ };
187
+ }
188
+ async function listInspectorTargets(port = 9229, host = "127.0.0.1") {
189
+ const ctrl = new AbortController();
190
+ const t = setTimeout(() => ctrl.abort(), 2e3);
191
+ try {
192
+ const r = await fetch(`http://${host}:${port}/json/list`, { signal: ctrl.signal });
193
+ if (!r.ok) return [];
194
+ return await r.json();
195
+ } catch {
196
+ return [];
197
+ } finally {
198
+ clearTimeout(t);
199
+ }
200
+ }
201
+ var Cdp = class _Cdp {
202
+ ws;
203
+ id = 0;
204
+ pending = /* @__PURE__ */ new Map();
205
+ events = /* @__PURE__ */ new Map();
206
+ constructor(ws) {
207
+ this.ws = ws;
208
+ ws.addEventListener("message", (ev) => {
209
+ const msg = JSON.parse(String(ev.data));
210
+ if (msg.id !== void 0) {
211
+ const p = this.pending.get(msg.id);
212
+ this.pending.delete(msg.id);
213
+ if (!p) return;
214
+ if (msg.error) p.reject(new Error(msg.error.message));
215
+ else p.resolve(msg.result);
216
+ } else if (msg.method) {
217
+ for (const h of this.events.get(msg.method) ?? []) h(msg.params);
218
+ }
219
+ });
220
+ }
221
+ static connect(url, timeoutMs = 5e3) {
222
+ return new Promise((resolve, reject) => {
223
+ const ws = new WebSocket(url);
224
+ const t = setTimeout(() => {
225
+ ws.close();
226
+ reject(new Error("inspector connect timeout"));
227
+ }, timeoutMs);
228
+ ws.addEventListener("open", () => {
229
+ clearTimeout(t);
230
+ resolve(new _Cdp(ws));
231
+ });
232
+ ws.addEventListener("error", () => {
233
+ clearTimeout(t);
234
+ reject(new Error("inspector websocket error"));
235
+ });
236
+ });
237
+ }
238
+ on(method, h) {
239
+ this.events.set(method, [...this.events.get(method) ?? [], h]);
240
+ }
241
+ send(method, params = {}, timeoutMs = 3e4) {
242
+ const id = ++this.id;
243
+ this.ws.send(JSON.stringify({ id, method, params }));
244
+ return new Promise((resolve, reject) => {
245
+ const t = setTimeout(() => {
246
+ this.pending.delete(id);
247
+ reject(new Error(`${method} timeout`));
248
+ }, timeoutMs);
249
+ this.pending.set(id, {
250
+ resolve: (v) => {
251
+ clearTimeout(t);
252
+ resolve(v);
253
+ },
254
+ reject: (e) => {
255
+ clearTimeout(t);
256
+ reject(e);
257
+ }
258
+ });
259
+ });
260
+ }
261
+ close() {
262
+ try {
263
+ this.ws.close();
264
+ } catch {
265
+ }
266
+ }
267
+ };
268
+ async function findInspector(pid, b, allowSignal) {
269
+ const ports = [9229, 9230, 9231, 9222];
270
+ const probe = async () => {
271
+ for (const port of ports) {
272
+ const list = await listInspectorTargets(port);
273
+ const t = list.find((x) => x.webSocketDebuggerUrl);
274
+ if (t) return { url: t.webSocketDebuggerUrl, port };
275
+ }
276
+ return void 0;
277
+ };
278
+ let found = await probe();
279
+ if (found) return found;
280
+ if (!allowSignal) return void 0;
281
+ try {
282
+ process.kill(pid, "SIGUSR1");
283
+ b.limitation(`sent SIGUSR1 to pid ${pid} to activate Node's inspector on 127.0.0.1:9229 (it stays enabled until the process exits)`);
284
+ } catch (err) {
285
+ b.limitation(`could not send SIGUSR1 to pid ${pid}: ${err.message}`);
286
+ return void 0;
287
+ }
288
+ for (let i = 0; i < 10 && !found; i++) {
289
+ await new Promise((r) => setTimeout(r, 300));
290
+ found = await probe();
291
+ }
292
+ return found;
293
+ }
294
+ async function takeHeapSnapshot(url, outPath) {
295
+ const cdp = await Cdp.connect(url);
296
+ const file = createWriteStream(outPath);
297
+ let bytes = 0;
298
+ let chunks = 0;
299
+ cdp.on("HeapProfiler.addHeapSnapshotChunk", (p) => {
300
+ const chunk = p.chunk;
301
+ bytes += chunk.length;
302
+ chunks++;
303
+ file.write(chunk);
304
+ });
305
+ try {
306
+ await cdp.send("HeapProfiler.enable");
307
+ await cdp.send("HeapProfiler.takeHeapSnapshot", { reportProgress: false, treatGlobalObjectsAsRoots: true, captureNumericValue: false }, 55e3);
308
+ } finally {
309
+ cdp.close();
310
+ await new Promise((r) => file.end(() => r()));
311
+ }
312
+ return { bytes, chunks };
313
+ }
314
+ async function snapshotMemory(ctx, args) {
315
+ const base = await snapshotMemoryGeneric(ctx, args);
316
+ if (args.heap === false) return base;
317
+ const { session, target } = ctx;
318
+ const b = new ResultBuilder("snapshot_memory", session, target);
319
+ const pid = target.pid;
320
+ const insp = await findInspector(pid, b, true);
321
+ if (!insp) {
322
+ b.limitation("no Node inspector reachable on 127.0.0.1:9229-9231 and SIGUSR1 did not open one; start the target with --inspect for heap snapshots");
323
+ return mergeResults(base, b.build(), []);
324
+ }
325
+ const outPath = b.artifactPath("heap.heapsnapshot");
326
+ let took;
327
+ try {
328
+ took = await takeHeapSnapshot(insp.url, outPath);
329
+ b.limitation(`heap snapshot: ${took.chunks} chunks, ${Math.round(took.bytes / 1024 / 1024)} MB JSON`);
330
+ } catch (err) {
331
+ b.limitation(`heap snapshot failed: ${err.message}`);
332
+ return mergeResults(base, b.build(), []);
333
+ }
334
+ await b.addArtifact(outPath, "heapsnapshot", { description: `V8 heap snapshot via inspector :${insp.port} (open in Chrome DevTools \u2192 Memory \u2192 Load)` });
335
+ let summary;
336
+ try {
337
+ summary = summarizeHeapSnapshot(await readFile(outPath, "utf8"));
338
+ } catch (err) {
339
+ b.limitation(`could not parse heap snapshot: ${err.message}`);
340
+ return mergeResults(base, b.build(), []);
341
+ }
342
+ const sumEv = b.evidenceOf("heap_summary", `heap ${humanBytes(summary.total_size)} in ${summary.node_count} objects; by type: ${summary.byType.slice(0, 4).map((t) => `${t.type} ${humanBytes(t.self_size)}`).join(", ")}`, { name: "heap_total", bytes: summary.total_size, objects: summary.node_count, by_type: summary.byType.slice(0, 8) }, { artifact: outPath });
343
+ const nameEv = b.evidenceOf("heap_summary", `by constructor (self size): ${summary.byName.slice(0, 5).map((x) => `${x.name} ${humanBytes(x.self_size)}\xD7${x.count}`).join(", ")}`, summary.byName.map((x) => ({ name: x.name, type: x.type, count: x.count, bytes: x.self_size })), { artifact: outPath });
344
+ const retEv = b.evidenceOf("heap_retainer", `top retainers: ${summary.topRetained.slice(0, 4).map((r) => `${r.name} (${r.type}) retains ${humanBytes(r.retained_bytes)}${r.retainer ? ` via ${r.retainer}` : ""}`).join("; ")}`, summary.topRetained.map((r) => ({ name: r.name, type: r.type, retained_bytes: r.retained_bytes, self_size: r.self_size, retainer: r.retainer })), { artifact: outPath });
345
+ const top = summary.topRetained[0];
346
+ if (top && top.retained_bytes > summary.total_size * 0.2 && top.type !== "synthetic") {
347
+ b.finding("warn", `${top.name} (${top.type}) retains ${humanBytes(top.retained_bytes)} = ${Math.round(100 * top.retained_bytes / summary.total_size)}% of the heap${top.retainer ? `, held by ${top.retainer}` : ""}`, [retEv, sumEv], "observed", "Take another snapshot_memory after load and diff_runs: if this retainer grows, it is the leak.");
348
+ }
349
+ const bigArrays = summary.byName.find((x) => x.type === "array" || x.name === "Array");
350
+ if (bigArrays && bigArrays.self_size > summary.total_size * 0.25) b.finding("info", `arrays hold ${humanBytes(bigArrays.self_size)} (${Math.round(100 * bigArrays.self_size / summary.total_size)}% of heap)`, [nameEv]);
351
+ b.limitation(`retained sizes are a reachability approximation (shared subgraphs are over-counted); open the .heapsnapshot in Chrome DevTools for exact dominator-tree numbers`);
352
+ b.next("snapshot_memory (again, after load)", "diff_runs");
353
+ return mergeResults(base, b.build(), []);
354
+ }
355
+ async function dumpStacks(ctx) {
356
+ const { session, target } = ctx;
357
+ const b = new ResultBuilder("dump_stacks", session, target);
358
+ const pid = target.pid;
359
+ const insp = await findInspector(pid, b, true);
360
+ if (!insp) {
361
+ b.limitation("no Node inspector reachable; falling back needs gdb/lldb (native frames only)");
362
+ return b.fail("node inspector not available; cannot attach").build();
363
+ }
364
+ const cdp = await Cdp.connect(insp.url);
365
+ try {
366
+ const paused = new Promise((resolve) => cdp.on("Debugger.paused", resolve));
367
+ await cdp.send("Debugger.enable");
368
+ const t0 = Date.now();
369
+ await cdp.send("Debugger.pause");
370
+ const p = await Promise.race([paused, new Promise((r) => setTimeout(() => r(void 0), 3e3))]);
371
+ const pauseLatency = Date.now() - t0;
372
+ if (p) await cdp.send("Debugger.resume").catch(() => void 0);
373
+ else await cdp.send("Debugger.disable").catch(() => void 0);
374
+ const frames = (p?.callFrames ?? []).map((f) => ({ func: f.functionName || "(anonymous)", file: f.url.replace(/^file:\/\//, "") || "(internal)", line: f.location.lineNumber + 1 }));
375
+ const userFrames = frames.filter((f) => !/^node:|^\(internal\)$/.test(f.file));
376
+ const raw = frames.map((f) => `${f.func} (${f.file}:${f.line})`).join("\n") || "(event loop idle: no JS frames on the stack)";
377
+ const art = await b.writeArtifact("node-stack.txt", raw, "raw_text", "Node main-thread JS stack via inspector Debugger.pause");
378
+ const ev = b.evidenceOf(
379
+ "stack",
380
+ frames.length ? `main thread (pause latency ${pauseLatency} ms): ${frames.slice(0, 4).map((f) => `${f.func}@${f.file.split("/").pop()}:${f.line}`).join(" \u2190 ")}` : `main thread idle in the event loop (no JS frames after ${pauseLatency} ms)`,
381
+ { thread: "main", pause_latency_ms: pauseLatency, frames, user_frames: userFrames.length },
382
+ { artifact: art }
383
+ );
384
+ if (userFrames.length && pauseLatency < 100) {
385
+ const f = userFrames[0];
386
+ b.finding("warn", `Node main thread is executing ${f.func} (${f.file.split("/").pop()}:${f.line}) \u2014 a synchronous task is running on the event loop`, [ev], "observed", `Hot JS stack: ${userFrames.slice(0, 5).map((x) => `${x.func}@${x.file.split("/").pop()}:${x.line}`).join(" \u2190 ")}. Handoff: attach a step debugger (e.g. mcp-debugger) at ${f.file}:${f.func}. Run profile_cpu to see if it is CPU-bound.`);
387
+ } else if (userFrames.length) {
388
+ b.finding("info", `caught the event loop in user code ${userFrames[0].func} (${userFrames[0].file.split("/").pop()}:${userFrames[0].line}) after ${pauseLatency} ms \u2014 a tick was running, not necessarily blocked`, [ev], "observed");
389
+ } else {
390
+ b.finding("info", `Node main thread was idle in the event loop (first JS tick after ${pauseLatency} ms was ${frames[0] ? `${frames[0].func} in ${frames[0].file}` : "none"}) \u2014 not stuck in JS; if callers hang, the work is pending on I/O or timers (inspect_sockets, trace_syscalls)`, [ev], "inferred");
391
+ }
392
+ b.limitation("JS main thread only; worker threads and libuv threadpool are not shown (use gdb/lldb for native threads)");
393
+ b.next("profile_cpu", "trace_syscalls", "inspect_sockets");
394
+ return b.build();
395
+ } finally {
396
+ cdp.close();
397
+ }
398
+ }
399
+ var heapNodeAdapter = {
400
+ name: "node-inspector",
401
+ tools: ["snapshot_memory", "dump_stacks"],
402
+ platforms: ["linux", "darwin"],
403
+ requires: [],
404
+ runtimes: ["node"],
405
+ priority: 20,
406
+ check(base) {
407
+ base.notes.push("uses Node's inspector protocol; sends SIGUSR1 to enable it if the target was not started with --inspect");
408
+ return base;
409
+ },
410
+ async run(tool, args, ctx) {
411
+ if (tool === "snapshot_memory") return snapshotMemory(ctx, args);
412
+ if (tool === "dump_stacks") return dumpStacks(ctx);
413
+ throw new Error(`node-inspector adapter cannot run ${tool}`);
414
+ }
415
+ };
416
+
417
+ // src/adapters/bpftrace.ts
418
+ var bpftraceAdapter = {
419
+ name: "bpftrace",
420
+ tools: [],
421
+ platforms: ["linux"],
422
+ requires: ["bpftrace"],
423
+ install: { linux: "sudo apt install bpftrace" },
424
+ async run() {
425
+ throw new Error("bpftrace adapter is not implemented in v0");
426
+ }
427
+ };
428
+
429
+ // src/adapters/index.ts
430
+ var adapters = [
431
+ jvmAdapter,
432
+ lsofAdapter,
433
+ pyspyAdapter,
434
+ straceAdapter,
435
+ fsUsageAdapter,
436
+ gdbAdapter,
437
+ lldbAdapter,
438
+ heapNodeAdapter,
439
+ psmemAdapter,
440
+ perfAdapter,
441
+ sampleMacosAdapter,
442
+ tcpdumpAdapter,
443
+ dockerAdapter,
444
+ bpftraceAdapter
445
+ ];
446
+ function candidatesFor(tool, target, readiness) {
447
+ const ready = [];
448
+ const blocked = [];
449
+ for (const a of adapters) {
450
+ if (!a.tools.includes(tool)) continue;
451
+ const status = readiness.providers.find((p) => p.name === a.name);
452
+ if (a.runtimes && target.runtime && !a.runtimes.includes(target.runtime)) {
453
+ blocked.push({ adapter: a, reason: `serves ${a.runtimes.join("/")} targets, target is ${target.runtime}` });
454
+ continue;
455
+ }
456
+ if (!status || status.status !== "ready") {
457
+ blocked.push({ adapter: a, reason: `${status?.status ?? "unknown"}${status?.install_hint ? ` \u2014 ${status.install_hint}` : ""}` });
458
+ continue;
459
+ }
460
+ ready.push(a);
461
+ }
462
+ ready.sort((x, y) => (y.priority ?? 0) - (x.priority ?? 0));
463
+ return { ready, blocked };
464
+ }
465
+
466
+ // src/readiness.ts
467
+ function which(binary) {
468
+ const paths = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
469
+ const extra = platform() === "darwin" ? ["/usr/sbin", "/sbin", "/opt/homebrew/bin", "/usr/local/bin"] : ["/usr/sbin", "/sbin", "/usr/local/bin"];
470
+ for (const p of [...paths, ...extra]) {
471
+ const full = join(p, binary);
472
+ if (existsSync(full)) return full;
473
+ }
474
+ return null;
475
+ }
476
+ async function readIntFile(path) {
477
+ try {
478
+ const v = parseInt((await readFile2(path, "utf8")).trim(), 10);
479
+ return Number.isFinite(v) ? v : null;
480
+ } catch {
481
+ return null;
482
+ }
483
+ }
484
+ async function sudoNonInteractive() {
485
+ if (!which("sudo")) return false;
486
+ try {
487
+ const r = await execa("sudo", ["-n", "true"], { reject: false, timeout: 3e3 });
488
+ return r.exitCode === 0;
489
+ } catch {
490
+ return false;
491
+ }
492
+ }
493
+ async function sipEnabled() {
494
+ if (platform() !== "darwin") return null;
495
+ try {
496
+ const r = await execa("csrutil", ["status"], { reject: false, timeout: 3e3 });
497
+ if (/enabled/i.test(r.stdout)) return true;
498
+ if (/disabled/i.test(r.stdout)) return false;
499
+ return null;
500
+ } catch {
501
+ return null;
502
+ }
503
+ }
504
+ async function developerMode() {
505
+ if (platform() !== "darwin") return null;
506
+ try {
507
+ const r = await execa("DevToolsSecurity", ["-status"], { reject: false, timeout: 3e3 });
508
+ if (/enabled/i.test(r.stdout)) return true;
509
+ if (/disabled/i.test(r.stdout)) return false;
510
+ return null;
511
+ } catch {
512
+ return null;
513
+ }
514
+ }
515
+ async function linuxCapabilities() {
516
+ if (platform() !== "linux") return [];
517
+ try {
518
+ const status = await readFile2("/proc/self/status", "utf8");
519
+ const m = /CapEff:\s*([0-9a-f]+)/i.exec(status);
520
+ if (!m) return [];
521
+ const eff = BigInt("0x" + m[1]);
522
+ const caps = [];
523
+ if (eff & 1n << 19n) caps.push("CAP_SYS_PTRACE");
524
+ if (eff & 1n << 13n) caps.push("CAP_NET_RAW");
525
+ if (eff & 1n << 12n) caps.push("CAP_NET_ADMIN");
526
+ if (eff & 1n << 21n) caps.push("CAP_SYS_ADMIN");
527
+ if (eff & 1n << 38n) caps.push("CAP_PERFMON");
528
+ if (eff & 1n << 39n) caps.push("CAP_BPF");
529
+ return caps;
530
+ } catch {
531
+ return [];
532
+ }
533
+ }
534
+ async function probeReadiness() {
535
+ const os = platform();
536
+ const root = isRoot();
537
+ const [ptraceScope, perfParanoid, sudo, sip, dev, caps] = await Promise.all([
538
+ os === "linux" ? readIntFile("/proc/sys/kernel/yama/ptrace_scope") : Promise.resolve(null),
539
+ os === "linux" ? readIntFile("/proc/sys/kernel/perf_event_paranoid") : Promise.resolve(null),
540
+ sudoNonInteractive(),
541
+ sipEnabled(),
542
+ developerMode(),
543
+ linuxCapabilities()
544
+ ]);
545
+ const providers = [];
546
+ for (const a of adapters) {
547
+ const requires = a.requires.map((b) => ({ binary: b, path: which(b) }));
548
+ const missing = requires.filter((r) => !r.path).map((r) => r.binary);
549
+ let status = "ready";
550
+ const notes = [];
551
+ if (!a.platforms.includes(os)) status = "unsupported_platform";
552
+ else if (a.tools.length === 0) status = "not_implemented";
553
+ else if (missing.length) status = "missing";
554
+ let base = {
555
+ name: a.name,
556
+ tools: a.tools,
557
+ status,
558
+ requires,
559
+ install_hint: status === "missing" ? a.install?.[os] : void 0,
560
+ notes
561
+ };
562
+ if (a.check && status === "ready") {
563
+ base = await a.check(base, { platform: os, isRoot: root, ptraceScope, perfParanoid });
564
+ }
565
+ if (a.runtimes?.length) base.notes.push(`serves ${a.runtimes.join("/")} targets`);
566
+ providers.push(base);
567
+ }
568
+ const available = /* @__PURE__ */ new Set();
569
+ const unavailable = /* @__PURE__ */ new Map();
570
+ for (const t of ToolName.options) {
571
+ if (["readiness", "attach", "detach", "list_processes", "diff_runs", "report", "import_artifact"].includes(t)) {
572
+ available.add(t);
573
+ continue;
574
+ }
575
+ const serving = providers.filter((p) => p.tools.includes(t));
576
+ if (serving.some((p) => p.status === "ready")) available.add(t);
577
+ else {
578
+ const reasons = serving.map((p) => `${p.name}: ${p.status}${p.install_hint ? ` (${p.install_hint})` : ""}`);
579
+ unavailable.set(t, reasons.join("; ") || "no adapter on this platform");
580
+ }
581
+ }
582
+ return {
583
+ os,
584
+ arch: arch(),
585
+ kernel: release(),
586
+ node: process.version,
587
+ dea_version: VERSION,
588
+ is_root: root,
589
+ has_sudo_noninteractive: sudo,
590
+ permissions: {
591
+ ptrace_scope: ptraceScope,
592
+ capabilities: caps,
593
+ sip_enabled: sip,
594
+ developer_mode: dev,
595
+ perf_event_paranoid: perfParanoid
596
+ },
597
+ providers,
598
+ tools_available: [...available],
599
+ tools_unavailable: [...unavailable].map(([tool, reason]) => ({ tool, reason })),
600
+ sessions_dir: sessionsDir()
601
+ };
602
+ }
603
+
604
+ // src/diff.ts
605
+ var KEY_FIELDS = ["name", "frame", "function", "syscall", "remote", "type", "path", "fd", "state", "constructor", "key", "thread", "metric", "label"];
606
+ var NUM_FIELDS = ["value", "retained_bytes", "self_size", "bytes", "rss_kb", "vsz_kb", "total_ms", "self", "total", "count", "packets", "errors", "objects", "fds", "samples", "self_pct", "total_pct"];
607
+ function keyOf(row) {
608
+ for (const k of KEY_FIELDS) {
609
+ const v = row[k];
610
+ if (typeof v === "string" || typeof v === "number") return `${k}=${v}`;
611
+ }
612
+ return void 0;
613
+ }
614
+ function numericFields(row) {
615
+ const out = {};
616
+ for (const k of NUM_FIELDS) if (typeof row[k] === "number") out[k] = row[k];
617
+ return out;
618
+ }
619
+ function diffEvidence(a, b) {
620
+ const byKind = {};
621
+ const metrics = [];
622
+ const kinds = /* @__PURE__ */ new Set([...a.map((e) => e.kind), ...b.map((e) => e.kind)]);
623
+ for (const kind of kinds) {
624
+ const rowsA = /* @__PURE__ */ new Map();
625
+ const rowsB = /* @__PURE__ */ new Map();
626
+ const collect = (list, into) => {
627
+ for (const e of list.filter((x) => x.kind === kind)) {
628
+ if (Array.isArray(e.data)) {
629
+ for (const r of e.data) {
630
+ if (r && typeof r === "object") {
631
+ const k = keyOf(r);
632
+ if (k) into.set(k, r);
633
+ }
634
+ }
635
+ } else if (e.data && typeof e.data === "object") {
636
+ const r = e.data;
637
+ const nums = numericFields(r);
638
+ if (Object.keys(nums).length) into.set(keyOf(r) ?? e.summary.split(":")[0], r);
639
+ }
640
+ }
641
+ };
642
+ collect(a, rowsA);
643
+ collect(b, rowsB);
644
+ const entries = [];
645
+ for (const [k, rb] of rowsB) {
646
+ const ra = rowsA.get(k);
647
+ const nb = numericFields(rb);
648
+ if (!ra) {
649
+ const f = Object.keys(nb)[0];
650
+ entries.push({ key: k, status: "appeared", field: f, b: f ? nb[f] : void 0 });
651
+ continue;
652
+ }
653
+ const na = numericFields(ra);
654
+ const field = Object.keys(nb).find((f) => f in na);
655
+ if (!field) continue;
656
+ const delta = nb[field] - na[field];
657
+ entries.push({ key: k, status: delta > 0 ? "grew" : delta < 0 ? "shrank" : "same", field, a: na[field], b: nb[field], delta });
658
+ }
659
+ for (const [k, ra] of rowsA) {
660
+ if (!rowsB.has(k)) {
661
+ const na = numericFields(ra);
662
+ const f = Object.keys(na)[0];
663
+ entries.push({ key: k, status: "disappeared", field: f, a: f ? na[f] : void 0 });
664
+ }
665
+ }
666
+ entries.sort((x, y) => Math.abs(y.delta ?? y.b ?? y.a ?? 0) - Math.abs(x.delta ?? x.b ?? x.a ?? 0));
667
+ if (entries.length) {
668
+ if (kind === "metric" || kind === "metric_series") metrics.push(...entries);
669
+ else byKind[kind] = entries;
670
+ }
671
+ }
672
+ return { byKind, metrics };
673
+ }
674
+ async function diffRuns(session, a, b) {
675
+ const rb = new ResultBuilder("diff_runs", session, session.target);
676
+ if (a.tool !== b.tool) rb.limitation(`comparing different tools (${a.tool} vs ${b.tool}); only overlapping evidence kinds are diffed`);
677
+ const { byKind, metrics } = diffEvidence(a.evidence, b.evidence);
678
+ const header = rb.evidenceOf("diff", `${a.run_id ?? a.tool} (${a.started_at ?? "?"}) \u2192 ${b.run_id ?? b.tool} (${b.started_at ?? "?"})`, { a: a.run_id, b: b.run_id, a_started: a.started_at, b_started: b.started_at });
679
+ const isBytes = (f) => Boolean(f && /bytes|rss|vsz/.test(f));
680
+ const fmt = (v, f) => v === void 0 ? "?" : isBytes(f) ? f?.includes("kb") ? humanBytes(v * 1024) : humanBytes(v) : String(Math.round(v * 100) / 100);
681
+ if (metrics.length) {
682
+ const ev = rb.evidenceOf("diff", `metrics: ${metrics.slice(0, 6).map((m) => `${m.key} ${fmt(m.a, m.field)}\u2192${fmt(m.b, m.field)}`).join(", ")}`, metrics.slice(0, 40));
683
+ for (const m of metrics) {
684
+ if (m.status === "grew" && m.a && m.delta / m.a > 0.2) {
685
+ rb.finding("warn", `${m.key} grew ${fmt(m.a, m.field)} \u2192 ${fmt(m.b, m.field)} (+${(m.delta / m.a * 100).toFixed(0)}%)`, [ev, header]);
686
+ }
687
+ }
688
+ }
689
+ for (const [kind, entries] of Object.entries(byKind)) {
690
+ const grew = entries.filter((e) => e.status === "grew").slice(0, 10);
691
+ const appeared = entries.filter((e) => e.status === "appeared").slice(0, 10);
692
+ const gone = entries.filter((e) => e.status === "disappeared").slice(0, 10);
693
+ const shrank = entries.filter((e) => e.status === "shrank").slice(0, 10);
694
+ const ev = rb.evidenceOf("diff", `${kind}: ${grew.length} grew, ${shrank.length} shrank, ${appeared.length} appeared, ${gone.length} disappeared`, { grew, shrank, appeared, disappeared: gone });
695
+ const top = grew[0];
696
+ if (top && top.delta && top.a !== void 0 && (top.a === 0 || top.delta / top.a > 0.1)) {
697
+ rb.finding(
698
+ kind === "heap_retainer" || kind === "heap_summary" ? "high" : "warn",
699
+ `${kind}: ${top.key} grew ${fmt(top.a, top.field)} \u2192 ${fmt(top.b, top.field)}${grew.length > 1 ? ` (and ${grew.length - 1} more grew)` : ""}`,
700
+ [ev, header]
701
+ );
702
+ }
703
+ if (appeared.length && !top) rb.finding("info", `${kind}: ${appeared.length} new item(s), e.g. ${appeared[0].key}`, [ev, header]);
704
+ }
705
+ if (!metrics.length && Object.keys(byKind).length === 0) {
706
+ rb.finding("info", "no comparable numeric evidence between the two runs", [header]);
707
+ rb.limitation("diff_runs compares evidence rows keyed by name/frame/syscall/remote/path/etc with numeric fields; these runs had none in common");
708
+ }
709
+ rb.next("report");
710
+ return rb.build();
711
+ }
712
+
713
+ // src/report.ts
714
+ import { readFile as readFile3 } from "fs/promises";
715
+ var SEV_ICON = { high: "\u{1F534}", warn: "\u{1F7E0}", info: "\u{1F535}" };
716
+ async function renderReport(session) {
717
+ const runs = [];
718
+ for (const r of session.runs) {
719
+ try {
720
+ runs.push(JSON.parse(await readFile3(r.file, "utf8")));
721
+ } catch {
722
+ }
723
+ }
724
+ return renderReportFromRuns(session, runs);
725
+ }
726
+ function renderReportFromRuns(session, runs) {
727
+ const t = session.target;
728
+ const lines = [];
729
+ lines.push(`# Debug Anything session ${session.id}`);
730
+ lines.push("");
731
+ lines.push(`- **Target:** ${t.kind} \`${t.id}\`${t.pid ? ` (pid ${t.pid}` + (t.comm ? `, ${t.comm}` : "") + ")" : ""}${t.runtime ? ` \u2014 runtime ${t.runtime}` : ""}`);
732
+ lines.push(`- **Host:** ${t.host} (${session.platform})`);
733
+ lines.push(`- **Created:** ${session.created_at}${session.detached_at ? ` \xB7 **Detached:** ${session.detached_at}` : ""}`);
734
+ lines.push(`- **Runs:** ${runs.length} \xB7 **Artifacts dir:** \`${session.dir}\``);
735
+ lines.push("");
736
+ const all = runs.flatMap((r) => r.findings.map((f) => ({ run: r, f })));
737
+ const order = { high: 0, warn: 1, info: 2 };
738
+ all.sort((a, b) => order[a.f.severity] - order[b.f.severity]);
739
+ lines.push("## Findings");
740
+ lines.push("");
741
+ if (all.length === 0) lines.push("_No findings recorded._");
742
+ for (const { run, f } of all) {
743
+ lines.push(`- ${SEV_ICON[f.severity]} **${f.severity.toUpperCase()}** ${redact(f.summary)} `);
744
+ lines.push(` _${f.confidence}_ \xB7 ${run.run_id ?? run.tool} \xB7 evidence: ${f.evidence_ids.map((e) => `\`${run.run_id ?? run.tool}/${e}\``).join(", ")}`);
745
+ if (f.detail) lines.push(` ${redact(f.detail)}`);
746
+ }
747
+ lines.push("");
748
+ lines.push("## Runs");
749
+ lines.push("");
750
+ for (const r of runs) {
751
+ lines.push(`### ${r.run_id ?? r.tool} \u2014 ${r.ok ? "ok" : "FAILED"} (${r.duration_ms} ms)`);
752
+ lines.push("");
753
+ if (r.error) lines.push(`**Error:** ${redact(r.error)}
754
+ `);
755
+ if (r.evidence.length) {
756
+ lines.push("| id | kind | summary | source |");
757
+ lines.push("|---|---|---|---|");
758
+ for (const e of r.evidence) {
759
+ const src = e.artifact ? `\`${shortPath(e.artifact)}\`${e.lines ? ` L${e.lines[0]}\u2013${e.lines[1]}` : ""}` : "";
760
+ lines.push(`| ${e.id} | ${e.kind} | ${escapeCell(redact(e.summary))} | ${src} |`);
761
+ }
762
+ lines.push("");
763
+ }
764
+ if (r.limitations.length) {
765
+ lines.push("**Limitations:**");
766
+ for (const l of r.limitations) lines.push(`- ${redact(l)}`);
767
+ lines.push("");
768
+ }
769
+ if (r.artifacts.length) {
770
+ lines.push("**Artifacts:**");
771
+ for (const a of r.artifacts) lines.push(`- \`${a.path}\` (${a.kind}, ${a.bytes} bytes${a.truncated ? ", truncated" : ""})`);
772
+ lines.push("");
773
+ }
774
+ if (r.suggested_next.length) lines.push(`_Suggested next:_ ${r.suggested_next.join(", ")}
775
+ `);
776
+ }
777
+ lines.push("---");
778
+ lines.push("_Generated by DEA (Debug Anything). Every finding above cites evidence ids; raw artifacts are unredacted on disk._");
779
+ return lines.join("\n") + "\n";
780
+ }
781
+ function shortPath(p) {
782
+ const i = p.lastIndexOf("/");
783
+ return i >= 0 ? p.slice(i + 1) : p;
784
+ }
785
+ function escapeCell(s) {
786
+ return s.replace(/\|/g, "\\|").replace(/\n/g, " ");
787
+ }
788
+
789
+ // src/tools.ts
790
+ var NO_TARGET = { kind: "pid", id: "-", host: "local" };
791
+ var readinessCache = null;
792
+ async function cachedReadiness(maxAgeMs = 3e4) {
793
+ if (readinessCache && Date.now() - readinessCache.at < maxAgeMs) return readinessCache.value;
794
+ const value = await probeReadiness();
795
+ readinessCache = { at: Date.now(), value };
796
+ return value;
797
+ }
798
+ var TOOL_DESCRIPTIONS = {
799
+ readiness: "Report OS, arch, which providers (strace, py-spy, perf, lsof, tcpdump, gdb/lldb, docker...) are usable, what is missing and how to install it, and permission status (ptrace_scope, sudo, SIP). Always call this first.",
800
+ attach: "Resolve a target (pid, container, k8s pod, or a command to spawn under capture) and open a session. Returns session_id used by every other tool. Read-only: nothing is modified.",
801
+ detach: "Stop any running captures for the session, finalize the report, keep artifacts under ~/.debuganything/sessions/<id>/.",
802
+ list_processes: "ps-style list (pid, comm, cpu, mem, user, container) for picking a target. Optional substring filter.",
803
+ trace_syscalls: "Linux strace of the target for N seconds: top syscalls by total time and count, slowest individual calls, error counts (ENOENT/EAGAIN/ECONNREFUSED...), blocked waits. Raw log saved as artifact.",
804
+ profile_cpu: "CPU profile for N seconds: py-spy (Python), perf (native/Node on Linux), sample (macOS). Returns top frames (self/total), hottest stacks, flamegraph.svg artifact.",
805
+ dump_stacks: "Instant snapshot of every thread's stack (py-spy dump / gdb / lldb / Node inspector). The key tool for hangs and deadlocks; flags threads waiting on the same lock.",
806
+ inspect_sockets: "lsof/ss view of the target: listening ports, established connections grouped by remote, states (CLOSE_WAIT pileups, TIME_WAIT counts), fd count vs ulimit.",
807
+ inspect_files: "Open file descriptors by type, growth over a short window, largest open files, deleted-but-open files (disk full but nothing there).",
808
+ capture_traffic: "tcpdump scoped to the target's ports for N seconds (max 30). Per-remote bytes/packets, retransmits, RSTs, DNS queries, TLS SNI. .pcap artifact. REQUIRES consent: true \u2014 ask the user first.",
809
+ snapshot_memory: "RSS/VSZ sampled over a window; Node: heap snapshot with top retainers by retained size; Python: py-spy dump with locals. Take two and diff_runs them to find what grows.",
810
+ diff_runs: "Diff two prior runs of the same tool in this session (e.g. snapshot_memory#1 vs snapshot_memory#2, profile_cpu#1 vs #2): what grew, appeared, disappeared.",
811
+ container_info: "docker inspect/stats/top/logs tail for the target container, host pid mapping, cgroup limits: throttled? OOM-killed before? restarts?",
812
+ report: "Markdown report of the whole session: findings, evidence excerpts, artifact paths. Optionally written to a file.",
813
+ import_artifact: "Analyze raw tool output you captured yourself (e.g. `sudo strace \u2026`, `sudo fs_usage \u2026`, `perf script`, `py-spy dump`, `lsof -F`, `tcpdump -nn -tt -r`) and turn it into the same evidence and findings Debug Anything would produce. Use when Debug Anything could not run the tool (no root, macOS)."
814
+ };
815
+ async function runTool(name, rawArgs) {
816
+ const schema = ToolArgs[name];
817
+ if (!schema) return failedResult(name, null, NO_TARGET, `unknown tool: ${name}`);
818
+ const parsed = schema.safeParse(rawArgs);
819
+ if (!parsed.success) {
820
+ return failedResult(name, null, NO_TARGET, `invalid arguments: ${parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
821
+ }
822
+ const args = parsed.data;
823
+ try {
824
+ switch (name) {
825
+ case "readiness":
826
+ return await toolReadiness();
827
+ case "attach":
828
+ return await toolAttach(args);
829
+ case "detach":
830
+ return await toolDetach(args);
831
+ case "list_processes":
832
+ return await toolListProcesses(args);
833
+ case "diff_runs":
834
+ return await toolDiffRuns(args);
835
+ case "report":
836
+ return await toolReport(args);
837
+ case "import_artifact": {
838
+ const session = await loadSession(String(args.session_id));
839
+ const { importArtifact } = await import("./import-CT55AEEE.js");
840
+ return await recordRun(session, await importArtifact(session, args));
841
+ }
842
+ default:
843
+ return await dispatchAdapter(name, args);
844
+ }
845
+ } catch (err) {
846
+ const msg = err instanceof Error ? err.message : String(err);
847
+ const sessionId = typeof args.session_id === "string" ? args.session_id : "";
848
+ const limitations = err instanceof ConsentRequiredError ? ["consent required \u2014 ask the user, then pass consent: true"] : [];
849
+ const r = failedResult(name, null, NO_TARGET, msg, limitations);
850
+ return { ...r, session_id: sessionId };
851
+ }
852
+ }
853
+ async function toolReadiness() {
854
+ const r = await probeReadiness();
855
+ readinessCache = { at: Date.now(), value: r };
856
+ const b = new ResultBuilder("readiness", null, NO_TARGET);
857
+ const sys = b.evidenceOf("readiness", `${r.os}/${r.arch} node=${r.node} root=${r.is_root} sudo-n=${r.has_sudo_noninteractive} ptrace_scope=${r.permissions.ptrace_scope ?? "n/a"} sip=${r.permissions.sip_enabled ?? "n/a"}`, {
858
+ os: r.os,
859
+ arch: r.arch,
860
+ kernel: r.kernel,
861
+ node: r.node,
862
+ dea_version: r.dea_version,
863
+ is_root: r.is_root,
864
+ has_sudo_noninteractive: r.has_sudo_noninteractive,
865
+ permissions: r.permissions,
866
+ sessions_dir: r.sessions_dir
867
+ });
868
+ const prov = b.evidenceOf(
869
+ "readiness",
870
+ `${r.providers.filter((p) => p.status === "ready").length}/${r.providers.length} providers ready: ${r.providers.map((p) => `${p.name}=${p.status}`).join(" ")}`,
871
+ r.providers
872
+ );
873
+ const tools = b.evidenceOf("readiness", `tools available: ${r.tools_available.join(", ")}`, { available: r.tools_available, unavailable: r.tools_unavailable });
874
+ b.finding("info", `${r.tools_available.length} of 14 tools available on ${r.os}`, [tools, prov]);
875
+ for (const u of r.tools_unavailable) b.limitation(`${u.tool}: ${u.reason}`);
876
+ if (r.os === "linux" && r.permissions.ptrace_scope !== null && r.permissions.ptrace_scope >= 1 && !r.is_root) {
877
+ b.finding("warn", `ptrace_scope=${r.permissions.ptrace_scope}: strace/gdb/py-spy can only attach to your own child processes without root`, [sys], "observed", "Fix for this boot: sudo sysctl kernel.yama.ptrace_scope=0 (Debug Anything will not run this for you)");
878
+ }
879
+ if (r.os === "linux" && r.permissions.perf_event_paranoid !== null && r.permissions.perf_event_paranoid > 1 && !r.is_root) {
880
+ b.finding("warn", `perf_event_paranoid=${r.permissions.perf_event_paranoid}: perf cannot profile other users' processes without root`, [sys], "observed", "Fix for this boot: sudo sysctl kernel.perf_event_paranoid=1");
881
+ }
882
+ if (r.os === "darwin" && !r.is_root) {
883
+ b.limitation("macOS: attaching py-spy/lldb/sample to processes you did not start usually needs sudo; tcpdump always needs sudo or membership in the access_bpf group");
884
+ }
885
+ if (r.os === "win32") b.limitation("Windows is not supported yet (planned: ETW / procdump based adapters)");
886
+ b.next("list_processes", "attach");
887
+ return b.build();
888
+ }
889
+ async function toolAttach(args) {
890
+ if (platform() === "win32") return failedResult("attach", null, NO_TARGET, "Windows is not supported yet");
891
+ const isCommand = (args.kind ?? (await import("./target-WJE4WFYA.js")).guessKind(args.target)) === "command";
892
+ let target;
893
+ let session;
894
+ if (isCommand) {
895
+ session = await createSession({ kind: "command", id: args.target, host: "local" });
896
+ const spawned = await (await import("./target-WJE4WFYA.js")).spawnCommand(args.target, session.dir);
897
+ const { spawned: sp, ...t } = spawned;
898
+ target = t;
899
+ session.target = target;
900
+ session.spawned = sp;
901
+ await saveSession(session);
902
+ } else {
903
+ target = await resolveTarget(args.target, { kind: args.kind });
904
+ session = await createSession(target);
905
+ }
906
+ const b = new ResultBuilder("attach", session, target);
907
+ const ev = b.evidenceOf("process", `attached to ${target.kind} ${target.id}: pid=${target.pid ?? "?"} comm=${target.comm ?? "?"} runtime=${target.runtime ?? "?"} children=${target.pids?.length ?? 0}`, target);
908
+ b.finding("info", `session ${session.id} attached to pid ${target.pid ?? "?"} (${target.comm ?? "unknown"}, runtime ${target.runtime ?? "unknown"})`, [ev]);
909
+ if (!target.pid) b.limitation("no host pid for this target on this platform; pid-based tools (strace, py-spy, gdb, lsof) will not work. container_info still works.");
910
+ if (session.spawned) b.limitation(`spawned command; stdout/stderr tee'd to ${session.spawned.stdout} and ${session.spawned.stderr}`);
911
+ const readiness = await cachedReadiness();
912
+ const next = ["inspect_sockets", "inspect_files", "dump_stacks", "profile_cpu", "snapshot_memory", "trace_syscalls", "container_info"].filter((t) => readiness.tools_available.includes(t));
913
+ if (target.kind !== "container" && target.kind !== "pod") next.splice(next.indexOf("container_info"), next.includes("container_info") ? 1 : 0);
914
+ b.next(...next, "detach");
915
+ const result = b.build();
916
+ return recordRun(session, result);
917
+ }
918
+ async function toolDetach(args) {
919
+ const session = await loadSession(args.session_id);
920
+ await detachSession(session);
921
+ const b = new ResultBuilder("detach", session, session.target);
922
+ const md = await renderReport(session);
923
+ const reportPath = join2(session.dir, "report.md");
924
+ await writeFile(reportPath, md);
925
+ await b.addArtifact(reportPath, "markdown", { description: "session report" });
926
+ const ev = b.evidenceOf("note", `session ${session.id} detached after ${session.runs.length} run(s); report at ${reportPath}`, { runs: session.runs.map((r) => r.run_id), report: reportPath });
927
+ b.finding("info", `detached; ${session.runs.length} run(s) kept under ${session.dir}`, [ev]);
928
+ return b.build();
929
+ }
930
+ async function toolListProcesses(args) {
931
+ const b = new ResultBuilder("list_processes", null, NO_TARGET);
932
+ const procs = await listProcesses(args.filter, args.limit);
933
+ const ev = b.evidenceOf(
934
+ "process_list",
935
+ `${procs.length} process(es)${args.filter ? ` matching "${args.filter}"` : ""}, sorted by cpu then rss`,
936
+ procs.map((p) => ({ pid: p.pid, ppid: p.ppid, user: p.user, cpu: p.cpu, mem: p.mem, rss_kb: p.rss_kb, comm: p.comm, args: p.args.slice(0, 160), container: p.container }))
937
+ );
938
+ if (procs.length === 0) b.finding("info", `no processes match "${args.filter ?? ""}"`, [ev]);
939
+ else {
940
+ const top = procs[0];
941
+ b.finding("info", `top by cpu: pid ${top.pid} ${top.comm} (${top.cpu}% cpu, ${Math.round(top.rss_kb / 1024)} MB rss)`, [ev]);
942
+ }
943
+ b.next("attach");
944
+ return b.build();
945
+ }
946
+ async function loadRunRef(session, ref) {
947
+ const m = ref.match(/^([\w-]{6,}):(.+)$/);
948
+ if (m) {
949
+ const other = await loadSession(m[1]);
950
+ return { run: await loadRun(other, m[2]), from: other.id };
951
+ }
952
+ return { run: await loadRun(session, ref) };
953
+ }
954
+ async function toolDiffRuns(args) {
955
+ const session = await loadSession(args.session_id);
956
+ const [ra, rb] = await Promise.all([loadRunRef(session, args.a), loadRunRef(session, args.b)]);
957
+ const result = await diffRuns(session, ra.run, rb.run);
958
+ if (ra.from || rb.from) result.limitations.push(`cross-session diff: a=${ra.from ?? session.id}, b=${rb.from ?? session.id} \u2014 a healthy baseline vs this run; environment may differ between sessions`);
959
+ return recordRun(session, result);
960
+ }
961
+ async function toolReport(args) {
962
+ const session = await loadSession(args.session_id);
963
+ const md = await renderReport(session);
964
+ const b = new ResultBuilder("report", session, session.target);
965
+ const path = join2(session.dir, "report.md");
966
+ await writeFile(path, md);
967
+ await b.addArtifact(path, "markdown", { description: "session report" });
968
+ if (args.html) {
969
+ const { renderHtmlReport } = await import("./report_html-2ENM7HLC.js");
970
+ const htmlPath = args.out ? args.out.replace(/\.(md|html)$/, "") + ".html" : join2(session.dir, "report.html");
971
+ await writeFile(htmlPath, await renderHtmlReport(session));
972
+ await b.addArtifact(htmlPath, "log", { description: "self-contained HTML report (shareable)" });
973
+ }
974
+ if (args.out) {
975
+ await writeFile(args.out, md);
976
+ await b.addArtifact(args.out, "markdown", { description: "copy of session report" });
977
+ }
978
+ const allFindings = [];
979
+ for (const r of session.runs) {
980
+ try {
981
+ const tr = await loadRun(session, r.run_id);
982
+ for (const f of tr.findings) allFindings.push({ run: r.run_id, severity: f.severity, summary: f.summary });
983
+ } catch {
984
+ }
985
+ }
986
+ const ev = b.evidenceOf("note", `report with ${allFindings.length} finding(s) across ${session.runs.length} run(s)`, { findings: allFindings.slice(0, 50), markdown_preview: md.slice(0, 3e3) });
987
+ b.finding("info", `report written to ${path}`, [ev]);
988
+ return b.build();
989
+ }
990
+ async function dispatchAdapter(tool, args) {
991
+ const session = await requireAttached(String(args.session_id));
992
+ requireConsent(tool, args);
993
+ const readiness = await cachedReadiness();
994
+ const { ready, blocked } = candidatesFor(tool, session.target, readiness);
995
+ if (ready.length === 0) {
996
+ const reasons = blocked.map((c) => `${c.adapter.name}: ${c.reason}`);
997
+ const r = failedResult(tool, session, session.target, `no ready adapter for ${tool} on ${readiness.os}${session.target.runtime ? ` (runtime ${session.target.runtime})` : ""}`, reasons.length ? reasons : ["no adapter declares this tool for this platform"], ["readiness"]);
998
+ return recordRun(session, r);
999
+ }
1000
+ let last;
1001
+ for (const adapter of ready) {
1002
+ const result = await adapter.run(tool, args, { session, target: session.target, readiness });
1003
+ last = result;
1004
+ if (result.ok) break;
1005
+ if (!/cannot attach|not supported|requires|unsupported|needs/i.test(result.error ?? "")) break;
1006
+ }
1007
+ return recordRun(session, last);
1008
+ }
1009
+
1010
+ export {
1011
+ cachedReadiness,
1012
+ TOOL_DESCRIPTIONS,
1013
+ runTool
1014
+ };
1015
+ //# sourceMappingURL=chunk-VGYXHXTY.js.map