consensus-cli 0.1.2 → 0.1.5
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/CHANGELOG.md +16 -0
- package/README.md +22 -10
- package/dist/claudeCli.js +125 -0
- package/dist/cli.js +12 -0
- package/dist/codexLogs.js +34 -13
- package/dist/opencodeApi.js +84 -0
- package/dist/opencodeEvents.js +388 -0
- package/dist/opencodeServer.js +91 -0
- package/dist/opencodeState.js +36 -0
- package/dist/opencodeStorage.js +127 -0
- package/dist/scan.js +341 -5
- package/package.json +1 -1
- package/public/app.js +146 -27
- package/public/index.html +3 -0
- package/public/style.css +21 -4
package/dist/scan.js
CHANGED
|
@@ -6,6 +6,12 @@ import { execFile } from "child_process";
|
|
|
6
6
|
import { promisify } from "util";
|
|
7
7
|
import { deriveStateWithHold } from "./activity.js";
|
|
8
8
|
import { listRecentSessions, findSessionById, pickSessionForProcess, resolveCodexHome, summarizeTail, updateTail, } from "./codexLogs.js";
|
|
9
|
+
import { getOpenCodeSessions } from "./opencodeApi.js";
|
|
10
|
+
import { ensureOpenCodeServer } from "./opencodeServer.js";
|
|
11
|
+
import { ensureOpenCodeEventStream, getOpenCodeActivityByPid, getOpenCodeActivityBySession, } from "./opencodeEvents.js";
|
|
12
|
+
import { getOpenCodeSessionForDirectory } from "./opencodeStorage.js";
|
|
13
|
+
import { deriveOpenCodeState } from "./opencodeState.js";
|
|
14
|
+
import { deriveClaudeState, summarizeClaudeCommand } from "./claudeCli.js";
|
|
9
15
|
import { redactText } from "./redact.js";
|
|
10
16
|
const execFileAsync = promisify(execFile);
|
|
11
17
|
const repoCache = new Map();
|
|
@@ -27,13 +33,50 @@ function isCodexProcess(cmd, name, matchRe) {
|
|
|
27
33
|
return true;
|
|
28
34
|
return false;
|
|
29
35
|
}
|
|
36
|
+
function isOpenCodeProcess(cmd, name) {
|
|
37
|
+
if (!cmd && !name)
|
|
38
|
+
return false;
|
|
39
|
+
if (name === "opencode")
|
|
40
|
+
return true;
|
|
41
|
+
if (!cmd)
|
|
42
|
+
return false;
|
|
43
|
+
const firstToken = cmd.trim().split(/\s+/)[0];
|
|
44
|
+
const base = path.basename(firstToken);
|
|
45
|
+
if (base === "opencode")
|
|
46
|
+
return true;
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
function isClaudeProcess(cmd, name) {
|
|
50
|
+
if (!cmd && !name)
|
|
51
|
+
return false;
|
|
52
|
+
if (name === "claude")
|
|
53
|
+
return true;
|
|
54
|
+
if (!cmd)
|
|
55
|
+
return false;
|
|
56
|
+
const firstToken = cmd.trim().split(/\s+/)[0];
|
|
57
|
+
const base = path.basename(firstToken);
|
|
58
|
+
if (base === "claude" || base === "claude.exe")
|
|
59
|
+
return true;
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
30
62
|
function inferKind(cmd) {
|
|
31
63
|
if (cmd.includes(" app-server"))
|
|
32
64
|
return "app-server";
|
|
33
65
|
if (cmd.includes(" exec"))
|
|
34
66
|
return "exec";
|
|
35
|
-
if (cmd.includes(" codex") || cmd.startsWith("codex"))
|
|
67
|
+
if (cmd.includes(" codex") || cmd.startsWith("codex") || cmd.includes("/codex"))
|
|
36
68
|
return "tui";
|
|
69
|
+
if (cmd.includes(" opencode") || cmd.startsWith("opencode") || cmd.includes("/opencode")) {
|
|
70
|
+
if (cmd.includes(" serve") || cmd.includes("--serve") || cmd.includes(" web")) {
|
|
71
|
+
return "opencode-server";
|
|
72
|
+
}
|
|
73
|
+
if (cmd.includes(" run"))
|
|
74
|
+
return "opencode-cli";
|
|
75
|
+
return "opencode-tui";
|
|
76
|
+
}
|
|
77
|
+
const claudeInfo = summarizeClaudeCommand(cmd);
|
|
78
|
+
if (claudeInfo)
|
|
79
|
+
return claudeInfo.kind;
|
|
37
80
|
return "unknown";
|
|
38
81
|
}
|
|
39
82
|
function shortenCmd(cmd, max = 120) {
|
|
@@ -44,6 +87,25 @@ function shortenCmd(cmd, max = 120) {
|
|
|
44
87
|
}
|
|
45
88
|
function parseDoingFromCmd(cmd) {
|
|
46
89
|
const parts = cmd.split(/\s+/g);
|
|
90
|
+
const claudeInfo = summarizeClaudeCommand(cmd);
|
|
91
|
+
if (claudeInfo?.doing)
|
|
92
|
+
return claudeInfo.doing;
|
|
93
|
+
const openIndex = parts.findIndex((part) => part === "opencode" || part.endsWith("/opencode"));
|
|
94
|
+
if (openIndex !== -1) {
|
|
95
|
+
const mode = parts[openIndex + 1];
|
|
96
|
+
if (mode === "serve" || mode === "web")
|
|
97
|
+
return `opencode ${mode}`;
|
|
98
|
+
if (mode === "run") {
|
|
99
|
+
for (let i = openIndex + 2; i < parts.length; i += 1) {
|
|
100
|
+
const part = parts[i];
|
|
101
|
+
if (!part || part.startsWith("-"))
|
|
102
|
+
continue;
|
|
103
|
+
return `opencode run: ${part}`;
|
|
104
|
+
}
|
|
105
|
+
return "opencode run";
|
|
106
|
+
}
|
|
107
|
+
return "opencode";
|
|
108
|
+
}
|
|
47
109
|
const execIndex = parts.indexOf("exec");
|
|
48
110
|
if (execIndex !== -1) {
|
|
49
111
|
for (let i = execIndex + 1; i < parts.length; i += 1) {
|
|
@@ -89,12 +151,43 @@ function extractSessionId(cmd) {
|
|
|
89
151
|
}
|
|
90
152
|
return undefined;
|
|
91
153
|
}
|
|
154
|
+
function extractOpenCodeSessionId(cmd) {
|
|
155
|
+
const parts = cmd.split(/\s+/g);
|
|
156
|
+
const sessionFlag = parts.findIndex((part) => part === "--session" || part === "--session-id" || part === "-s");
|
|
157
|
+
if (sessionFlag !== -1) {
|
|
158
|
+
const token = parts[sessionFlag + 1];
|
|
159
|
+
if (token)
|
|
160
|
+
return token;
|
|
161
|
+
}
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
92
164
|
function normalizeTitle(value) {
|
|
93
165
|
if (!value)
|
|
94
166
|
return undefined;
|
|
95
167
|
return value.replace(/^prompt:\s*/i, "").trim();
|
|
96
168
|
}
|
|
97
|
-
function
|
|
169
|
+
function parseTimestamp(value) {
|
|
170
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
171
|
+
return value < 100000000000 ? value * 1000 : value;
|
|
172
|
+
}
|
|
173
|
+
if (typeof value === "string") {
|
|
174
|
+
const parsed = Date.parse(value);
|
|
175
|
+
if (!Number.isNaN(parsed))
|
|
176
|
+
return parsed;
|
|
177
|
+
}
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
function coerceNumber(value) {
|
|
181
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
182
|
+
return value;
|
|
183
|
+
if (typeof value === "string") {
|
|
184
|
+
const parsed = Number(value);
|
|
185
|
+
if (!Number.isNaN(parsed))
|
|
186
|
+
return parsed;
|
|
187
|
+
}
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
function deriveTitle(doing, repo, pid, kind) {
|
|
98
191
|
if (doing) {
|
|
99
192
|
const trimmed = doing.trim();
|
|
100
193
|
if (trimmed.startsWith("cmd:"))
|
|
@@ -110,7 +203,12 @@ function deriveTitle(doing, repo, pid) {
|
|
|
110
203
|
}
|
|
111
204
|
if (repo)
|
|
112
205
|
return repo;
|
|
113
|
-
|
|
206
|
+
const prefix = kind.startsWith("opencode")
|
|
207
|
+
? "opencode"
|
|
208
|
+
: kind.startsWith("claude")
|
|
209
|
+
? "claude"
|
|
210
|
+
: "codex";
|
|
211
|
+
return `${prefix}#${pid}`;
|
|
114
212
|
}
|
|
115
213
|
function sanitizeSummary(summary) {
|
|
116
214
|
if (!summary)
|
|
@@ -146,6 +244,35 @@ async function getCwdsForPids(pids) {
|
|
|
146
244
|
if (cwd)
|
|
147
245
|
result.set(pid, cwd);
|
|
148
246
|
}
|
|
247
|
+
if (result.size > 0) {
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
// fall through to lsof
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
const { stdout } = await execFileAsync("lsof", [
|
|
256
|
+
"-a",
|
|
257
|
+
"-p",
|
|
258
|
+
pids.join(","),
|
|
259
|
+
"-d",
|
|
260
|
+
"cwd",
|
|
261
|
+
"-Fn",
|
|
262
|
+
]);
|
|
263
|
+
let currentPid = null;
|
|
264
|
+
const lines = stdout.split(/\r?\n/).filter(Boolean);
|
|
265
|
+
for (const line of lines) {
|
|
266
|
+
if (line.startsWith("p")) {
|
|
267
|
+
const pid = Number(line.slice(1));
|
|
268
|
+
currentPid = Number.isNaN(pid) ? null : pid;
|
|
269
|
+
}
|
|
270
|
+
else if (line.startsWith("n") && currentPid) {
|
|
271
|
+
const cwd = line.slice(1).trim();
|
|
272
|
+
if (cwd)
|
|
273
|
+
result.set(currentPid, cwd);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
149
276
|
}
|
|
150
277
|
catch {
|
|
151
278
|
return result;
|
|
@@ -210,7 +337,15 @@ export async function scanCodexProcesses() {
|
|
|
210
337
|
}
|
|
211
338
|
const processes = await psList();
|
|
212
339
|
const codexProcs = processes.filter((proc) => isCodexProcess(proc.cmd, proc.name, matchRe));
|
|
213
|
-
const
|
|
340
|
+
const codexPidSet = new Set(codexProcs.map((proc) => proc.pid));
|
|
341
|
+
const opencodeProcs = processes
|
|
342
|
+
.filter((proc) => isOpenCodeProcess(proc.cmd, proc.name))
|
|
343
|
+
.filter((proc) => !codexPidSet.has(proc.pid));
|
|
344
|
+
const opencodePidSet = new Set(opencodeProcs.map((proc) => proc.pid));
|
|
345
|
+
const claudeProcs = processes
|
|
346
|
+
.filter((proc) => isClaudeProcess(proc.cmd, proc.name))
|
|
347
|
+
.filter((proc) => !codexPidSet.has(proc.pid) && !opencodePidSet.has(proc.pid));
|
|
348
|
+
const pids = Array.from(new Set([...codexProcs, ...opencodeProcs, ...claudeProcs].map((proc) => proc.pid)));
|
|
214
349
|
let usage = {};
|
|
215
350
|
try {
|
|
216
351
|
usage = (await pidusage(pids));
|
|
@@ -222,6 +357,32 @@ export async function scanCodexProcesses() {
|
|
|
222
357
|
const startTimes = await getStartTimesForPids(pids);
|
|
223
358
|
const codexHome = resolveCodexHome();
|
|
224
359
|
const sessions = await listRecentSessions(codexHome);
|
|
360
|
+
const opencodeHost = process.env.CONSENSUS_OPENCODE_HOST || "127.0.0.1";
|
|
361
|
+
const opencodePort = Number(process.env.CONSENSUS_OPENCODE_PORT || 4096);
|
|
362
|
+
const opencodeResult = await getOpenCodeSessions(opencodeHost, opencodePort, {
|
|
363
|
+
silent: true,
|
|
364
|
+
timeoutMs: Number(process.env.CONSENSUS_OPENCODE_TIMEOUT_MS || 1000),
|
|
365
|
+
});
|
|
366
|
+
await ensureOpenCodeServer(opencodeHost, opencodePort, opencodeResult);
|
|
367
|
+
if (opencodeProcs.length) {
|
|
368
|
+
ensureOpenCodeEventStream(opencodeHost, opencodePort);
|
|
369
|
+
}
|
|
370
|
+
const opencodeSessions = opencodeResult.ok ? opencodeResult.sessions : [];
|
|
371
|
+
const opencodeApiAvailable = opencodeResult.ok;
|
|
372
|
+
const opencodeSessionsByPid = new Map();
|
|
373
|
+
const opencodeSessionsByDir = new Map();
|
|
374
|
+
for (const session of opencodeSessions) {
|
|
375
|
+
const pid = coerceNumber(session.pid);
|
|
376
|
+
if (typeof pid === "number") {
|
|
377
|
+
opencodeSessionsByPid.set(pid, session);
|
|
378
|
+
}
|
|
379
|
+
if (typeof session.directory === "string") {
|
|
380
|
+
opencodeSessionsByDir.set(session.directory, session);
|
|
381
|
+
}
|
|
382
|
+
if (typeof session.cwd === "string") {
|
|
383
|
+
opencodeSessionsByDir.set(session.cwd, session);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
225
386
|
const agents = [];
|
|
226
387
|
const seenIds = new Set();
|
|
227
388
|
for (const proc of codexProcs) {
|
|
@@ -287,7 +448,7 @@ export async function scanCodexProcesses() {
|
|
|
287
448
|
const cmdShort = shortenCmd(cmd);
|
|
288
449
|
const kind = inferKind(cmd);
|
|
289
450
|
const startedAt = startMs ? Math.floor(startMs / 1000) : undefined;
|
|
290
|
-
const computedTitle = title || deriveTitle(doing, repoName, proc.pid);
|
|
451
|
+
const computedTitle = title || deriveTitle(doing, repoName, proc.pid, kind);
|
|
291
452
|
const safeSummary = sanitizeSummary(summary);
|
|
292
453
|
agents.push({
|
|
293
454
|
id,
|
|
@@ -310,6 +471,181 @@ export async function scanCodexProcesses() {
|
|
|
310
471
|
events,
|
|
311
472
|
});
|
|
312
473
|
}
|
|
474
|
+
const opencodeEventWindowMs = Number(process.env.CONSENSUS_OPENCODE_EVENT_ACTIVE_MS || 90000);
|
|
475
|
+
const opencodeHoldMs = Number(process.env.CONSENSUS_OPENCODE_ACTIVE_HOLD_MS || 120000);
|
|
476
|
+
const cpuThreshold = Number(process.env.CONSENSUS_CPU_ACTIVE || 1);
|
|
477
|
+
for (const proc of opencodeProcs) {
|
|
478
|
+
const stats = usage[proc.pid] || {};
|
|
479
|
+
const cpu = typeof stats.cpu === "number" ? stats.cpu : 0;
|
|
480
|
+
const mem = typeof stats.memory === "number" ? stats.memory : 0;
|
|
481
|
+
const elapsed = stats.elapsed;
|
|
482
|
+
const startMs = typeof elapsed === "number"
|
|
483
|
+
? Date.now() - elapsed
|
|
484
|
+
: startTimes.get(proc.pid);
|
|
485
|
+
const cmdRaw = proc.cmd || proc.name || "";
|
|
486
|
+
const sessionByPid = opencodeSessionsByPid.get(proc.pid);
|
|
487
|
+
const cwdMatch = cwds.get(proc.pid);
|
|
488
|
+
const sessionByDir = sessionByPid
|
|
489
|
+
? undefined
|
|
490
|
+
: opencodeSessionsByDir.get(cwdMatch || "");
|
|
491
|
+
const session = sessionByPid || sessionByDir;
|
|
492
|
+
const storageSession = !session && cwdMatch ? await getOpenCodeSessionForDirectory(cwdMatch) : undefined;
|
|
493
|
+
const sessionId = session?.id || storageSession?.id || extractOpenCodeSessionId(cmdRaw);
|
|
494
|
+
const sessionTitle = normalizeTitle(session?.title || session?.name || storageSession?.title);
|
|
495
|
+
const sessionCwd = session?.cwd || session?.directory || storageSession?.directory;
|
|
496
|
+
const cwdRaw = sessionCwd || cwds.get(proc.pid);
|
|
497
|
+
const cwd = redactText(cwdRaw) || cwdRaw;
|
|
498
|
+
const repoRoot = cwdRaw ? findRepoRoot(cwdRaw) : null;
|
|
499
|
+
const repoName = repoRoot ? path.basename(repoRoot) : undefined;
|
|
500
|
+
const lastActivityAt = parseTimestamp(session?.lastActivity ||
|
|
501
|
+
session?.lastActivityAt ||
|
|
502
|
+
storageSession?.time?.updated ||
|
|
503
|
+
storageSession?.time?.created ||
|
|
504
|
+
session?.time?.updated ||
|
|
505
|
+
session?.time?.created ||
|
|
506
|
+
session?.updatedAt ||
|
|
507
|
+
session?.updated ||
|
|
508
|
+
session?.createdAt ||
|
|
509
|
+
session?.created);
|
|
510
|
+
const statusRaw = typeof session?.status === "string" ? session.status : undefined;
|
|
511
|
+
const status = statusRaw?.toLowerCase();
|
|
512
|
+
const statusIsError = !!status && /error|failed|failure/.test(status);
|
|
513
|
+
const statusIsIdle = !!status && /idle|stopped|paused/.test(status);
|
|
514
|
+
let hasError = statusIsError;
|
|
515
|
+
const model = typeof session?.model === "string" ? session.model : undefined;
|
|
516
|
+
let doing = sessionTitle;
|
|
517
|
+
let summary;
|
|
518
|
+
let events;
|
|
519
|
+
const eventActivity = getOpenCodeActivityBySession(sessionId) || getOpenCodeActivityByPid(proc.pid);
|
|
520
|
+
let lastEventAt = eventActivity?.lastEventAt;
|
|
521
|
+
let inFlight = eventActivity?.inFlight;
|
|
522
|
+
if (eventActivity) {
|
|
523
|
+
events = eventActivity.events;
|
|
524
|
+
summary = eventActivity.summary || summary;
|
|
525
|
+
lastEventAt = eventActivity.lastEventAt || lastEventAt;
|
|
526
|
+
if (eventActivity.hasError)
|
|
527
|
+
hasError = true;
|
|
528
|
+
if (eventActivity.inFlight)
|
|
529
|
+
inFlight = true;
|
|
530
|
+
if (eventActivity.summary?.current)
|
|
531
|
+
doing = eventActivity.summary.current;
|
|
532
|
+
}
|
|
533
|
+
if (!lastEventAt && statusIsIdle) {
|
|
534
|
+
lastEventAt = undefined;
|
|
535
|
+
}
|
|
536
|
+
if (!doing) {
|
|
537
|
+
doing =
|
|
538
|
+
parseDoingFromCmd(proc.cmd || "") || shortenCmd(proc.cmd || proc.name || "");
|
|
539
|
+
}
|
|
540
|
+
if (doing)
|
|
541
|
+
summary = { current: doing };
|
|
542
|
+
const id = `${proc.pid}`;
|
|
543
|
+
const cached = activityCache.get(id);
|
|
544
|
+
const kind = inferKind(cmdRaw);
|
|
545
|
+
const activity = deriveOpenCodeState({
|
|
546
|
+
cpu,
|
|
547
|
+
hasError,
|
|
548
|
+
lastEventAt: lastEventAt,
|
|
549
|
+
inFlight,
|
|
550
|
+
status,
|
|
551
|
+
isServer: kind === "opencode-server",
|
|
552
|
+
previousActiveAt: cached?.lastActiveAt,
|
|
553
|
+
now,
|
|
554
|
+
cpuThreshold,
|
|
555
|
+
eventWindowMs: opencodeEventWindowMs,
|
|
556
|
+
holdMs: opencodeHoldMs,
|
|
557
|
+
});
|
|
558
|
+
let state = activity.state;
|
|
559
|
+
const hasSignal = statusIsIdle ||
|
|
560
|
+
statusIsError ||
|
|
561
|
+
typeof lastEventAt === "number" ||
|
|
562
|
+
typeof inFlight === "boolean";
|
|
563
|
+
if (!opencodeApiAvailable && !hasSignal)
|
|
564
|
+
state = "idle";
|
|
565
|
+
if (!hasSignal && cpu <= cpuThreshold) {
|
|
566
|
+
state = "idle";
|
|
567
|
+
}
|
|
568
|
+
activityCache.set(id, { lastActiveAt: activity.lastActiveAt, lastSeenAt: now });
|
|
569
|
+
seenIds.add(id);
|
|
570
|
+
const cmd = redactText(cmdRaw) || cmdRaw;
|
|
571
|
+
const cmdShort = shortenCmd(cmd);
|
|
572
|
+
const startedAt = startMs ? Math.floor(startMs / 1000) : undefined;
|
|
573
|
+
const computedTitle = sessionTitle || deriveTitle(doing, repoName, proc.pid, kind);
|
|
574
|
+
const safeSummary = sanitizeSummary(summary);
|
|
575
|
+
agents.push({
|
|
576
|
+
id,
|
|
577
|
+
pid: proc.pid,
|
|
578
|
+
startedAt,
|
|
579
|
+
lastEventAt,
|
|
580
|
+
title: redactText(computedTitle) || computedTitle,
|
|
581
|
+
cmd,
|
|
582
|
+
cmdShort,
|
|
583
|
+
kind,
|
|
584
|
+
cpu,
|
|
585
|
+
mem,
|
|
586
|
+
state,
|
|
587
|
+
doing: redactText(doing) || doing,
|
|
588
|
+
repo: repoName,
|
|
589
|
+
cwd,
|
|
590
|
+
model,
|
|
591
|
+
summary: safeSummary,
|
|
592
|
+
events,
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
for (const proc of claudeProcs) {
|
|
596
|
+
const stats = usage[proc.pid] || {};
|
|
597
|
+
const cpu = typeof stats.cpu === "number" ? stats.cpu : 0;
|
|
598
|
+
const mem = typeof stats.memory === "number" ? stats.memory : 0;
|
|
599
|
+
const elapsed = stats.elapsed;
|
|
600
|
+
const startMs = typeof elapsed === "number"
|
|
601
|
+
? Date.now() - elapsed
|
|
602
|
+
: startTimes.get(proc.pid);
|
|
603
|
+
const cmdRaw = proc.cmd || proc.name || "";
|
|
604
|
+
const claudeInfo = summarizeClaudeCommand(cmdRaw);
|
|
605
|
+
const doing = claudeInfo?.doing ||
|
|
606
|
+
parseDoingFromCmd(cmdRaw) ||
|
|
607
|
+
shortenCmd(cmdRaw || proc.name || "");
|
|
608
|
+
const summary = doing ? { current: doing } : undefined;
|
|
609
|
+
const model = claudeInfo?.model;
|
|
610
|
+
const cwdRaw = cwds.get(proc.pid);
|
|
611
|
+
const cwd = redactText(cwdRaw) || cwdRaw;
|
|
612
|
+
const repoRoot = cwdRaw ? findRepoRoot(cwdRaw) : null;
|
|
613
|
+
const repoName = repoRoot ? path.basename(repoRoot) : undefined;
|
|
614
|
+
const kind = claudeInfo?.kind || inferKind(cmdRaw);
|
|
615
|
+
const id = `${proc.pid}`;
|
|
616
|
+
const cached = activityCache.get(id);
|
|
617
|
+
const activity = deriveClaudeState({
|
|
618
|
+
cpu,
|
|
619
|
+
info: claudeInfo,
|
|
620
|
+
previousActiveAt: cached?.lastActiveAt,
|
|
621
|
+
now,
|
|
622
|
+
});
|
|
623
|
+
const state = activity.state;
|
|
624
|
+
activityCache.set(id, { lastActiveAt: state === "active" ? activity.lastActiveAt : undefined, lastSeenAt: now });
|
|
625
|
+
seenIds.add(id);
|
|
626
|
+
const cmd = redactText(cmdRaw) || cmdRaw;
|
|
627
|
+
const cmdShort = shortenCmd(cmd);
|
|
628
|
+
const startedAt = startMs ? Math.floor(startMs / 1000) : undefined;
|
|
629
|
+
const computedTitle = deriveTitle(doing, repoName, proc.pid, kind);
|
|
630
|
+
const safeSummary = sanitizeSummary(summary);
|
|
631
|
+
agents.push({
|
|
632
|
+
id,
|
|
633
|
+
pid: proc.pid,
|
|
634
|
+
startedAt,
|
|
635
|
+
title: redactText(computedTitle) || computedTitle,
|
|
636
|
+
cmd,
|
|
637
|
+
cmdShort,
|
|
638
|
+
kind,
|
|
639
|
+
cpu,
|
|
640
|
+
mem,
|
|
641
|
+
state,
|
|
642
|
+
doing: redactText(doing) || doing,
|
|
643
|
+
repo: repoName,
|
|
644
|
+
cwd,
|
|
645
|
+
model,
|
|
646
|
+
summary: safeSummary,
|
|
647
|
+
});
|
|
648
|
+
}
|
|
313
649
|
for (const id of activityCache.keys()) {
|
|
314
650
|
if (!seenIds.has(id)) {
|
|
315
651
|
activityCache.delete(id);
|