@volter/supercode-health 0.1.0 → 0.1.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/lib/darwin.mjs +13 -7
- package/lib/format.mjs +6 -2
- package/lib/linux.mjs +2 -2
- package/lib/mail.mjs +1 -1
- package/lib/sample.mjs +59 -22
- package/package.json +13 -4
package/lib/darwin.mjs
CHANGED
|
@@ -156,14 +156,20 @@ export async function diskIO() {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
/** The user's launchd jobs: label → { pid, lastExit }. */
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
/** The user's launchd jobs: label → { pid, lastExit, runs }. `runs` counts every spawn since the job was loaded,
|
|
160
|
+
* so a KeepAlive job that crashes and respawns between two readings shows as runs rising with a failed last exit,
|
|
161
|
+
* where `launchctl list` alone shows a pid and looks running (12 ms per `launchctl print`). */
|
|
162
|
+
export async function services(labels = []) {
|
|
162
163
|
const map = new Map();
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
const uid = process.getuid?.();
|
|
165
|
+
await Promise.all(labels.map(async (label) => {
|
|
166
|
+
const r = await run('/bin/launchctl', ['print', `gui/${uid}/${label}`], { timeoutMs: 5000 });
|
|
167
|
+
if (!r.ok) return; // not loaded
|
|
168
|
+
const field = (name) => new RegExp(`^\\t${name} = (.+)$`, 'm').exec(r.stdout)?.[1];
|
|
169
|
+
const pid = Number(field('pid')) || null;
|
|
170
|
+
const lastExit = field('last exit code');
|
|
171
|
+
map.set(label, { pid, lastExit: lastExit == null || /never exited/.test(lastExit) ? 0 : Number.parseInt(lastExit, 10), runs: Number(field('runs')) || 0 });
|
|
172
|
+
}));
|
|
167
173
|
return map;
|
|
168
174
|
}
|
|
169
175
|
|
package/lib/format.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// The snapshot as a person reads it: alarms first, then each reading, then which features are off and why.
|
|
2
2
|
const n = (v, unit = '') => (v == null ? '-' : `${v}${unit}`);
|
|
3
|
-
const who = (p) => (p?.session?.address ? ` ← ${p.session.address}` : p?.session?.harness ? ` ← ${p.session.harness} pid ${p.session.agentPid}` : '');
|
|
3
|
+
const who = (p) => (p?.session?.address ? ` ← ${p.session.address}` : p?.session?.harness ? ` ← ${p.session.harness} pid ${p.session.agentPid}` : p?.session?.note ? ` ← (${p.session.note})` : p?.session?.inherited ? ` ← (no agent above; inherited ${p.session.inherited})` : '');
|
|
4
4
|
|
|
5
5
|
export function formatStatus(s, { all = false } = {}) {
|
|
6
6
|
if (!s) return 'machine health: no reading yet (is the probe running? `supercode health run`)';
|
|
@@ -25,6 +25,10 @@ export function formatStatus(s, { all = false } = {}) {
|
|
|
25
25
|
if (r.renderers) {
|
|
26
26
|
out.push(`renderers ${r.renderers.count} using ${r.renderers.totalMB} MB${r.renderers.orphanedHeadless.length ? `; ${r.renderers.orphanedHeadless.length} orphaned headless browser(s): ${r.renderers.orphanedHeadless.map((p) => p.pid).join(', ')}` : ''}`);
|
|
27
27
|
for (const p of r.renderers.top.slice(0, 3)) out.push(` ${String(p.cpuPct ?? '-').padStart(4)}% ${String(p.footprintMB).padStart(6)} MB ${p.pid}${who(p)}${p.tab ? ` · ${p.tab.url.slice(0, 60)} · ${p.tab.owner}${who(p.tab.server)}` : ' · tab not matched'}`);
|
|
28
|
+
// Chrome names no renderer per tab (CDP's TargetInfo has no pid): a renderer that holds no storage bucket
|
|
29
|
+
// cannot be matched, so the tabs no renderer was matched to are listed with their owners instead.
|
|
30
|
+
const unmatched = (r.browserTabs?.tabs ?? []).filter((t) => !t.renderers.length);
|
|
31
|
+
if (r.renderers.top.slice(0, 3).some((p) => !p.tab) && unmatched.length) out.push(` unmatched tabs (one may be served by an unmatched renderer above): ${unmatched.map((t) => `${t.url.slice(0, 50)} · ${t.owner}${who(t.server)}`).join(' | ')}`);
|
|
28
32
|
}
|
|
29
33
|
if (r.browserTabs) {
|
|
30
34
|
out.push(`tabs ${r.browserTabs.count} open${Object.keys(r.browserTabs.byHolder).length ? ` (${Object.entries(r.browserTabs.byHolder).map(([h, c]) => `${h} ${c}`).join(', ')})` : ''}`);
|
|
@@ -41,7 +45,7 @@ export function formatStatus(s, { all = false } = {}) {
|
|
|
41
45
|
for (const g of t.timeWaitByTarget.slice(0, 3)) out.push(` TIME_WAIT ${g.count} (${g.pct}%) against ${g.target}${g.listener ? ` · pid ${g.listener.pid} ${g.listener.command.slice(0, 60)}${who(g.listener)}` : ''}`);
|
|
42
46
|
}
|
|
43
47
|
if (r.files) out.push(`files ${r.files.open} of ${r.files.max} open (${r.files.pct}%)`);
|
|
44
|
-
if (r.services) for (const sv of r.services) out.push(`service ${sv.name ?? sv.label}: ${sv.running ? `running pid ${sv.pid}` : `NOT running (last exit ${sv.lastExit})`}${sv.logAgeMin != null ? ` · log written ${sv.logAgeMin} min ago` : ''}`);
|
|
48
|
+
if (r.services) for (const sv of r.services) out.push(`service ${sv.name ?? sv.label}: ${sv.crashing ? `CRASH-LOOPING: ${sv.restarts} restarts since the last reading, last exit ${sv.lastExit}, ${sv.runs} runs` : sv.running ? `running pid ${sv.pid}` : `NOT running (last exit ${sv.lastExit})`}${sv.logAgeMin != null ? ` · log written ${sv.logAgeMin} min ago` : ''}`);
|
|
45
49
|
if (r.supercode) {
|
|
46
50
|
const c = r.supercode;
|
|
47
51
|
out.push(`supercode connector ${c.connector ? `${c.connector.running ? 'running' : 'NOT running'}, server sees ${c.connector.connection ?? '-'}${c.connector.notSyncing ? `, ${c.connector.notSyncing} sessions not syncing (${c.connector.lastError})` : ''}` : (c.statusError ?? 'unknown')} · daemon ${c.daemon.answered ? `answered in ${c.daemon.ms} ms` : `NOT answering (${c.daemon.error})`} · launches ${c.launches.read ? `${c.launches.recent} in 24 h, ${c.launches.stuck.length} stuck, ${c.launches.failed.length} failed` : `unread (${c.launches.error})`}`);
|
package/lib/linux.mjs
CHANGED
|
@@ -135,11 +135,11 @@ export async function diskIO() {
|
|
|
135
135
|
export async function services(names = []) {
|
|
136
136
|
const map = new Map();
|
|
137
137
|
if (!names.length) return map;
|
|
138
|
-
const r = await run('systemctl', ['--user', 'show', '-p', 'Id,MainPID,ExecMainStatus,ActiveState', ...names], { timeoutMs: 5000 });
|
|
138
|
+
const r = await run('systemctl', ['--user', 'show', '-p', 'Id,MainPID,ExecMainStatus,ActiveState,NRestarts', ...names], { timeoutMs: 5000 });
|
|
139
139
|
if (!r.ok) throw new Error(r.error);
|
|
140
140
|
for (const block of r.stdout.split('\n\n')) {
|
|
141
141
|
const kv = Object.fromEntries(block.split('\n').filter(Boolean).map((l) => [l.slice(0, l.indexOf('=')), l.slice(l.indexOf('=') + 1)]));
|
|
142
|
-
if (kv.Id) map.set(kv.Id.replace(/\.service$/, ''), { pid: Number(kv.MainPID) || null, lastExit: Number(kv.ExecMainStatus) || 0, active: kv.ActiveState });
|
|
142
|
+
if (kv.Id) map.set(kv.Id.replace(/\.service$/, ''), { pid: Number(kv.MainPID) || null, lastExit: Number(kv.ExecMainStatus) || 0, active: kv.ActiveState, runs: (Number(kv.NRestarts) || 0) + 1 });
|
|
143
143
|
}
|
|
144
144
|
return map;
|
|
145
145
|
}
|
package/lib/mail.mjs
CHANGED
|
@@ -44,7 +44,7 @@ export class Outbox {
|
|
|
44
44
|
const LEVEL = { warn: 'WARN', crit: 'CRIT', none: 'clear' };
|
|
45
45
|
function line(t) {
|
|
46
46
|
const d = t.detail;
|
|
47
|
-
const who = d?.session?.address ? `; session ${d.session.address}` : d?.session?.harness ? `; under ${d.session.harness} pid ${d.session.agentPid}` : d?.pid ? '; no agent session above it' : '';
|
|
47
|
+
const who = d?.session?.address ? `; session ${d.session.address}` : d?.session?.harness ? `; under ${d.session.harness} pid ${d.session.agentPid}` : d?.session?.note ? `; ${d.session.note}` : d?.session?.inherited ? `; no agent above it (environment inherited from ${d.session.inherited}, unverified)` : d?.pid ? '; no agent session above it' : '';
|
|
48
48
|
const proc = d?.pid ? ` [pid ${d.pid}: ${d.command}${who}]` : d?.listener ? ` [listener pid ${d.listener.pid}: ${d.listener.command}${d.listener.session?.address ? `; session ${d.listener.session.address}` : ''}]` : '';
|
|
49
49
|
return `${LEVEL[t.to] ?? t.to}${t.from !== 'none' && t.to !== 'none' ? ` (was ${t.from})` : ''} ${t.key}: ${t.summary}${proc}`;
|
|
50
50
|
}
|
package/lib/sample.mjs
CHANGED
|
@@ -18,6 +18,11 @@ const HARNESS = { claude: 'claude-code', codex: 'codex', gemini: 'gemini', goose
|
|
|
18
18
|
const RENDERER = /Helper \(Renderer\)|--type=renderer/;
|
|
19
19
|
const BROWSER_MAIN = /(Google Chrome|Chromium|chrome-headless-shell|chrome)(\s|$)/i;
|
|
20
20
|
const LOOPBACK = /^(127\.|::1$|localhost$)/;
|
|
21
|
+
/** The session an agent names on its own command line: `--resume <id>`, `--session-id <id>`, `-r <id>`, `codex resume <id>`. */
|
|
22
|
+
function argvSession(command) {
|
|
23
|
+
return /(?:--resume|--session-id|\s-r)[=\s]+([0-9a-f]{8}-[0-9a-f-]{27,})/.exec(command)?.[1]
|
|
24
|
+
?? /\bresume\s+([0-9a-zA-Z][0-9a-zA-Z-]{7,})/.exec(command)?.[1] ?? null;
|
|
25
|
+
}
|
|
21
26
|
// The slow set's readings and alarm families, by the feature that produces them.
|
|
22
27
|
const SLOW_FEATURE = { disk: 'disk', services: 'services', service: 'services', supercode: 'supercode', browserTabs: 'browser-tabs', tabs: 'browser-tabs', power: 'power' };
|
|
23
28
|
// Every alarm family, by the feature that observes it.
|
|
@@ -32,6 +37,8 @@ export class Sampler {
|
|
|
32
37
|
this.swapRing = []; // [[at, swapouts]] for the 30-minute sum
|
|
33
38
|
this.diskRing = new Map(); // mount → [[at, freeBytes]] for the fill rate
|
|
34
39
|
this.sessionCache = new Map(); // pid:command → session
|
|
40
|
+
this.serviceRuns = new Map(); // service label → spawn count at the last reading
|
|
41
|
+
this.recentListeners = new Map(); // port → when it was last seen listening (its TIME_WAITs outlive it)
|
|
35
42
|
this.topFoot = { at: 0, map: new Map() };
|
|
36
43
|
this.slow = { at: 0, readings: {}, observations: [], families: new Set(), errors: {} };
|
|
37
44
|
this.slowStarted = 0;
|
|
@@ -110,8 +117,11 @@ export class Sampler {
|
|
|
110
117
|
r.footprintSource = ru ? 'footprint' : top != null ? 'footprint (top)' : 'rss';
|
|
111
118
|
r.writeBps = same && dt && ru && prev.written != null && ru.diskWrittenBytes >= prev.written ? (ru.diskWrittenBytes - prev.written) / dt : null;
|
|
112
119
|
nextPrev.set(r.pid, { cpuSec: r.cpuSec, command: r.command, written: ru?.diskWrittenBytes ?? null });
|
|
113
|
-
|
|
114
|
-
|
|
120
|
+
// Growth compares like with like: a footprint read through a different instrument than the last reading
|
|
121
|
+
// (another user's process alternates between top's 5-minute map and RSS) starts a new history.
|
|
122
|
+
let hist = same ? (this.footHistory.get(r.pid) ?? []) : [];
|
|
123
|
+
if (hist.length && hist.at(-1)[2] !== r.footprintSource) hist = [];
|
|
124
|
+
hist.push([now, r.footprintBytes, r.footprintSource]);
|
|
115
125
|
while (hist.length && now - hist[0][0] > 130_000) hist.shift();
|
|
116
126
|
this.footHistory.set(r.pid, hist);
|
|
117
127
|
const ago = hist.findLast?.(([at]) => now - at >= 55_000) ?? [...hist].reverse().find(([at]) => now - at >= 55_000);
|
|
@@ -225,6 +235,10 @@ export class Sampler {
|
|
|
225
235
|
if (!['*', '0.0.0.0', '::', '[::]'].includes(s.local.addr)) local.add(s.local.addr);
|
|
226
236
|
}
|
|
227
237
|
const inRange = (port) => port >= range.first && port <= range.last;
|
|
238
|
+
for (const port of listening) this.recentListeners.set(port, now);
|
|
239
|
+
const hold = Math.max(4 * timeWaitSec * 1000, 120_000);
|
|
240
|
+
for (const [port, at] of this.recentListeners) if (now - at > hold) this.recentListeners.delete(port);
|
|
241
|
+
const wasListening = (port) => this.recentListeners.has(port);
|
|
228
242
|
const wildcard = new Set();
|
|
229
243
|
const used = new Map();
|
|
230
244
|
const add = (addr, port) => { if (!used.has(addr)) used.set(addr, new Set()); used.get(addr).add(port); };
|
|
@@ -236,9 +250,11 @@ export class Sampler {
|
|
|
236
250
|
if (local.has(s.foreign.addr) && inRange(s.foreign.port)) add(s.foreign.addr, s.foreign.port);
|
|
237
251
|
if (s.state !== 'TIME_WAIT') continue;
|
|
238
252
|
// Group each TIME_WAIT by the endpoint it churned against: a listener here (either side), else the remote.
|
|
253
|
+
// A listener that exited still owns its TIME_WAITs until they drain, so a port counts as a listener for
|
|
254
|
+
// a while after it was last seen listening; otherwise its alarm would clear while the sockets remain.
|
|
239
255
|
let target;
|
|
240
|
-
if (
|
|
241
|
-
else if ((local.has(s.foreign.addr) || LOOPBACK.test(s.foreign.addr)) &&
|
|
256
|
+
if (wasListening(s.local.port)) target = `:${s.local.port}`;
|
|
257
|
+
else if ((local.has(s.foreign.addr) || LOOPBACK.test(s.foreign.addr)) && wasListening(s.foreign.port)) target = `:${s.foreign.port}`;
|
|
242
258
|
else target = `${s.foreign.addr}:${s.foreign.port}`;
|
|
243
259
|
targets.set(target, (targets.get(target) ?? 0) + 1);
|
|
244
260
|
}
|
|
@@ -255,12 +271,13 @@ export class Sampler {
|
|
|
255
271
|
} else owners = this.owners ?? new Map();
|
|
256
272
|
const timeWaitByTarget = top.map(([target, count]) => {
|
|
257
273
|
const owner = target.startsWith(':') ? owners.get(Number(target.slice(1))) : null;
|
|
258
|
-
|
|
274
|
+
const exited = target.startsWith(':') && !listening.has(Number(target.slice(1)));
|
|
275
|
+
return { target, count, pct: round((count / size) * 100), ...(exited ? { exited: true } : {}), ...(owner ? { listener: { pid: owner.pid, command: short(owner.command) } } : {}) };
|
|
259
276
|
});
|
|
260
277
|
readings.tcp = { states, timeWait: states.TIME_WAIT ?? 0, range: { ...range, size }, timeWaitSec, ephemeral, timeWaitByTarget };
|
|
261
278
|
families.add('tcp');
|
|
262
279
|
for (const e of ephemeral) observations.push({ key: `tcp.ephemeral:${e.addr}`, family: 'tcp', def: alarm('tcp.ephemeral'), value: e.pct, unit: '% of ports', summary: `${e.used} of ${size} ephemeral ports in use on ${e.addr}` });
|
|
263
|
-
for (const t of timeWaitByTarget) observations.push({ key: `tcp.timeWaitTarget:${t.target}`, family: 'tcp', def, value: t.pct, unit: '% of ports', summary: `${t.count} TIME_WAIT against ${t.target.startsWith(':') ? `listener ${t.target}` : t.target}${t.listener ? ` (pid ${t.listener.pid})` : ''}; range ${size}, TIME_WAIT lasts ${timeWaitSec} s`, detail: t });
|
|
280
|
+
for (const t of timeWaitByTarget) observations.push({ key: `tcp.timeWaitTarget:${t.target}`, family: 'tcp', def, value: t.pct, unit: '% of ports', summary: `${t.count} TIME_WAIT against ${t.target.startsWith(':') ? `listener ${t.target}${t.exited ? ' (listener has exited; its TIME_WAITs are draining)' : ''}` : t.target}${t.listener ? ` (pid ${t.listener.pid})` : ''}; range ${size}, TIME_WAIT lasts ${timeWaitSec} s`, detail: t });
|
|
264
281
|
});
|
|
265
282
|
|
|
266
283
|
// ---- the slow set ---------------------------------------------------------------------------------
|
|
@@ -327,12 +344,18 @@ export class Sampler {
|
|
|
327
344
|
const log = s.log?.replace(/^~(?=\/)/, homedir());
|
|
328
345
|
if (log) try { logAgeMin = Math.max(0, round((now - statSync(log).mtimeMs) / 60_000)); } catch { logAgeMin = null; }
|
|
329
346
|
const running = Boolean(job?.pid);
|
|
330
|
-
|
|
331
|
-
|
|
347
|
+
// Down (ADR 0006): not loaded, not running, or failing. A KeepAlive job that crashes and is respawned
|
|
348
|
+
// always has a pid when read, so failing is runs rising since the last reading with a non-zero last exit.
|
|
349
|
+
const before = this.serviceRuns.get(s.label);
|
|
350
|
+
const restarts = job && before != null && job.runs >= before ? job.runs - before : 0;
|
|
351
|
+
if (job) this.serviceRuns.set(s.label, job.runs);
|
|
352
|
+
const crashing = Boolean(job && restarts > 0 && job.lastExit !== 0);
|
|
353
|
+
const down = !job || (!running && (job.lastExit !== 0 || s.keepAlive !== false)) || crashing;
|
|
354
|
+
const why = !job ? 'not loaded' : !running ? `not running (last exit ${job.lastExit})` : crashing ? `crash-looping: ${restarts} restart${restarts === 1 ? '' : 's'} since the last reading, last exit ${job.lastExit} (${job.runs} runs since loaded)` : null;
|
|
332
355
|
if (down) slow.observations.push({ key: `service.down:${s.label}`, family: 'service', def: alarm('service.down'), value: 1, met: { warn: true, crit: false, clears: false }, summary: `service ${s.name ?? s.label} is ${why}` });
|
|
333
356
|
else slow.observations.push({ key: `service.down:${s.label}`, family: 'service', def: alarm('service.down'), value: 0, met: { warn: false, crit: false, clears: true }, summary: `service ${s.name ?? s.label} running` });
|
|
334
357
|
if (s.log && s.maxSilentMin) slow.observations.push({ key: `service.deaf:${s.label}`, family: 'service', def: { warn: s.maxSilentMin, forSec: 0 }, value: logAgeMin ?? Infinity, unit: 'min silent', summary: logAgeMin == null ? `service ${s.name ?? s.label}: its log ${log} is missing` : `service ${s.name ?? s.label} has written nothing to its log for ${logAgeMin} min` });
|
|
335
|
-
return { label: s.label, name: s.name ?? null, running, pid: job?.pid ?? null, lastExit: job?.lastExit ?? null, ...(s.log ? { logAgeMin } : {}) };
|
|
358
|
+
return { label: s.label, name: s.name ?? null, running, pid: job?.pid ?? null, lastExit: job?.lastExit ?? null, runs: job?.runs ?? null, restarts, crashing, ...(s.log ? { logAgeMin } : {}) };
|
|
336
359
|
});
|
|
337
360
|
});
|
|
338
361
|
|
|
@@ -435,27 +458,44 @@ export class Sampler {
|
|
|
435
458
|
for (let i = 0; i < 40 && p > 1; i += 1) { const r = byPid.get(p); if (!r) break; out.push(r); p = r.ppid; }
|
|
436
459
|
return out;
|
|
437
460
|
};
|
|
461
|
+
// Who owns a process is the agent above it, named by the agent itself: the session id on its own command
|
|
462
|
+
// line (`--resume`/`--session-id`, `codex resume`), else the id it sets in its own children's environment.
|
|
463
|
+
// An environment is never trusted across a tmux server: every pane inherits the variables of whichever
|
|
464
|
+
// session started that server (measured 2026-10-02: one tmux server credited one Claude session with
|
|
465
|
+
// dozens of unrelated panes). Above no agent, an inherited id is reported as inherited, never as owner.
|
|
438
466
|
const need = new Set();
|
|
439
467
|
const plan = new Map();
|
|
468
|
+
const childOf = (pid) => this.procs.rows.find((x) => x.ppid === pid && !AGENT.test(base(x.command)));
|
|
440
469
|
for (const pid of wanted) {
|
|
441
470
|
const r = byPid.get(pid);
|
|
442
471
|
if (!r) continue;
|
|
443
472
|
const cacheKey = `${pid}:${r.command}`;
|
|
444
473
|
if (this.sessionCache.has(cacheKey)) { plan.set(pid, { cacheKey }); continue; }
|
|
445
474
|
const up = chain(pid);
|
|
446
|
-
const
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
|
|
475
|
+
const stop = up.findIndex((x) => AGENT.test(base(x.command)) || base(x.command) === 'tmux');
|
|
476
|
+
const top = stop >= 0 ? up[stop] : null;
|
|
477
|
+
const agent = top && AGENT.test(base(top.command)) ? top : null;
|
|
478
|
+
// The agent's direct child on this lineage carries the variables the agent set; for the agent itself, any child.
|
|
479
|
+
const witness = agent ? (stop > 0 ? up[stop - 1] : childOf(agent.pid)) : (top ? null : r);
|
|
480
|
+
if (witness) need.add(witness.pid);
|
|
481
|
+
plan.set(pid, { cacheKey, agent, witness, pane: Boolean(top && !agent) });
|
|
450
482
|
}
|
|
451
483
|
const env = need.size ? await this.#environments([...need]) : new Map();
|
|
452
484
|
for (const [pid, p] of plan) {
|
|
453
485
|
if (!this.sessionCache.has(p.cacheKey)) {
|
|
454
|
-
let
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
486
|
+
let session = null;
|
|
487
|
+
if (p.agent) {
|
|
488
|
+
const harness = HARNESS[base(p.agent.command)] ?? base(p.agent.command);
|
|
489
|
+
const seen = p.witness ? env.get(p.witness.pid) : null;
|
|
490
|
+
const id = argvSession(p.agent.command) ?? (harness === 'codex' ? seen?.codex : harness === 'claude-code' ? seen?.claude : null) ?? null;
|
|
491
|
+
session = { harness, id, address: id ? `sc:${this.machine}:${harness}:${id}` : null, agentPid: p.agent.pid };
|
|
492
|
+
} else if (p.pane) {
|
|
493
|
+
session = { harness: null, id: null, address: null, agentPid: null, note: 'in a tmux pane with no agent above it' };
|
|
494
|
+
} else {
|
|
495
|
+
const seen = p.witness ? env.get(p.witness.pid) : null;
|
|
496
|
+
const inherited = seen?.supercode ?? (seen?.claude ? `sc:${this.machine}:claude-code:${seen.claude}` : seen?.codex ? `sc:${this.machine}:codex:${seen.codex}` : null);
|
|
497
|
+
if (inherited) session = { harness: null, id: null, address: null, agentPid: null, inherited };
|
|
498
|
+
}
|
|
459
499
|
this.sessionCache.set(p.cacheKey, session);
|
|
460
500
|
if (this.sessionCache.size > 5000) this.sessionCache.delete(this.sessionCache.keys().next().value);
|
|
461
501
|
}
|
|
@@ -471,10 +511,7 @@ export class Sampler {
|
|
|
471
511
|
const supercode = /(?:^|\s|\0)SUPERCODE_CALLER=(sc:[^\s\0]+)/.exec(text)?.[1];
|
|
472
512
|
const claude = /(?:^|\s|\0)CLAUDE_CODE_SESSION_ID=([0-9a-f-]{36})/.exec(text)?.[1];
|
|
473
513
|
const codex = /(?:^|\s|\0)CODEX_THREAD_ID=([0-9a-zA-Z-]{8,})/.exec(text)?.[1];
|
|
474
|
-
|
|
475
|
-
if (codex) return { harness: 'codex', id: codex };
|
|
476
|
-
if (supercode) { const [, , harness, id] = supercode.split(':'); return { harness, id, address: supercode }; }
|
|
477
|
-
return null;
|
|
514
|
+
return supercode || claude || codex ? { supercode, claude, codex } : null;
|
|
478
515
|
};
|
|
479
516
|
if (this.platform === 'linux') {
|
|
480
517
|
for (const pid of pids) { try { const found = pick(readFileSync(`/proc/${pid}/environ`, 'utf8')); if (found) map.set(pid, found); } catch { /* another user's */ } }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-health",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Optional machine pack: a cheap, deterministic probe of a machine's vitals that mails the machine's maintainer when a threshold is crossed. Each feature sits behind its own permission barrier.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -10,8 +10,17 @@
|
|
|
10
10
|
"./format": "./lib/format.mjs",
|
|
11
11
|
"./paths": "./lib/paths.mjs"
|
|
12
12
|
},
|
|
13
|
-
"bin": {
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
"bin": {
|
|
14
|
+
"supercode-health": "./bin/health.mjs"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"index.mjs",
|
|
18
|
+
"lib",
|
|
19
|
+
"bin",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20"
|
|
24
|
+
},
|
|
16
25
|
"dependencies": {}
|
|
17
26
|
}
|