copilot-agent 0.7.0 → 0.8.1
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/README.md +66 -48
- package/dist/index.js +504 -162
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
1
3
|
// src/index.ts
|
|
2
4
|
import { Command } from "commander";
|
|
3
5
|
|
|
@@ -8,9 +10,11 @@ import {
|
|
|
8
10
|
readFileSync,
|
|
9
11
|
statSync
|
|
10
12
|
} from "fs";
|
|
11
|
-
import { join, resolve } from "path";
|
|
13
|
+
import { join, resolve, basename } from "path";
|
|
12
14
|
import { homedir } from "os";
|
|
13
15
|
var SESSION_DIR = join(homedir(), ".copilot", "session-state");
|
|
16
|
+
var CLAUDE_DIR = join(homedir(), ".claude");
|
|
17
|
+
var CLAUDE_PROJECTS_DIR = join(CLAUDE_DIR, "projects");
|
|
14
18
|
function validateSession(sid) {
|
|
15
19
|
const events = join(SESSION_DIR, sid, "events.jsonl");
|
|
16
20
|
try {
|
|
@@ -41,7 +45,8 @@ function listSessions(limit = 20) {
|
|
|
41
45
|
premiumRequests: getSessionPremium(s.id),
|
|
42
46
|
summary: getSessionSummary(s.id),
|
|
43
47
|
cwd: getSessionCwd(s.id),
|
|
44
|
-
complete: hasTaskComplete(s.id)
|
|
48
|
+
complete: hasTaskComplete(s.id),
|
|
49
|
+
agent: "copilot"
|
|
45
50
|
}));
|
|
46
51
|
}
|
|
47
52
|
function getLatestSessionId() {
|
|
@@ -123,13 +128,6 @@ function getSessionSummary(sid) {
|
|
|
123
128
|
function getSessionCwd(sid) {
|
|
124
129
|
return readWorkspace(sid).cwd ?? "";
|
|
125
130
|
}
|
|
126
|
-
function findLatestIncomplete() {
|
|
127
|
-
const sessions = listSessions(50);
|
|
128
|
-
for (const s of sessions) {
|
|
129
|
-
if (!s.complete) return s.id;
|
|
130
|
-
}
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
131
|
function getSessionReport(sid) {
|
|
134
132
|
if (!validateSession(sid)) return null;
|
|
135
133
|
const ws = readWorkspace(sid);
|
|
@@ -156,7 +154,8 @@ function getSessionReport(sid) {
|
|
|
156
154
|
filesCreated: [],
|
|
157
155
|
filesEdited: [],
|
|
158
156
|
errors: [],
|
|
159
|
-
taskCompletions: []
|
|
157
|
+
taskCompletions: [],
|
|
158
|
+
agent: "copilot"
|
|
160
159
|
};
|
|
161
160
|
for (const line of lines) {
|
|
162
161
|
let event;
|
|
@@ -226,6 +225,245 @@ function getSessionReport(sid) {
|
|
|
226
225
|
}
|
|
227
226
|
return report;
|
|
228
227
|
}
|
|
228
|
+
function decodeClaudePath(encoded) {
|
|
229
|
+
return encoded.replace(/^-/, "/").replace(/-/g, "/");
|
|
230
|
+
}
|
|
231
|
+
function encodeClaudePath(fsPath) {
|
|
232
|
+
return fsPath.replace(/\//g, "-");
|
|
233
|
+
}
|
|
234
|
+
function listClaudeSessions(limit = 20) {
|
|
235
|
+
if (!existsSync(CLAUDE_PROJECTS_DIR)) return [];
|
|
236
|
+
const sessions = [];
|
|
237
|
+
try {
|
|
238
|
+
const projectDirs = readdirSync(CLAUDE_PROJECTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
239
|
+
for (const projDir of projectDirs) {
|
|
240
|
+
const projPath = join(CLAUDE_PROJECTS_DIR, projDir.name);
|
|
241
|
+
const cwd = decodeClaudePath(projDir.name);
|
|
242
|
+
const files = readdirSync(projPath).filter((f) => f.endsWith(".jsonl"));
|
|
243
|
+
for (const file of files) {
|
|
244
|
+
const filePath = join(projPath, file);
|
|
245
|
+
const sid = basename(file, ".jsonl");
|
|
246
|
+
try {
|
|
247
|
+
const stat = statSync(filePath);
|
|
248
|
+
const { lastEvent, complete, summary } = parseClaudeSessionMeta(filePath);
|
|
249
|
+
sessions.push({
|
|
250
|
+
id: sid,
|
|
251
|
+
dir: projPath,
|
|
252
|
+
mtime: stat.mtimeMs,
|
|
253
|
+
lastEvent,
|
|
254
|
+
premiumRequests: 0,
|
|
255
|
+
summary,
|
|
256
|
+
cwd,
|
|
257
|
+
complete,
|
|
258
|
+
agent: "claude"
|
|
259
|
+
});
|
|
260
|
+
} catch {
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
} catch {
|
|
265
|
+
}
|
|
266
|
+
sessions.sort((a, b) => b.mtime - a.mtime);
|
|
267
|
+
return sessions.slice(0, limit);
|
|
268
|
+
}
|
|
269
|
+
function parseClaudeSessionMeta(filePath) {
|
|
270
|
+
try {
|
|
271
|
+
const content = readFileSync(filePath, "utf-8").trimEnd();
|
|
272
|
+
const lines = content.split("\n");
|
|
273
|
+
let lastEvent = "unknown";
|
|
274
|
+
let complete = false;
|
|
275
|
+
let summary = "";
|
|
276
|
+
for (let i = lines.length - 1; i >= Math.max(0, lines.length - 5); i--) {
|
|
277
|
+
try {
|
|
278
|
+
const event = JSON.parse(lines[i]);
|
|
279
|
+
if (i === lines.length - 1) {
|
|
280
|
+
lastEvent = event.type ?? event.role ?? "unknown";
|
|
281
|
+
}
|
|
282
|
+
if (event.type === "result" || event.type === "assistant" && event.stop_reason === "end_turn") {
|
|
283
|
+
complete = true;
|
|
284
|
+
}
|
|
285
|
+
} catch {
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
for (const line of lines.slice(0, 10)) {
|
|
289
|
+
try {
|
|
290
|
+
const event = JSON.parse(line);
|
|
291
|
+
if ((event.type === "human" || event.role === "human") && event.message) {
|
|
292
|
+
summary = typeof event.message === "string" ? event.message.slice(0, 100) : JSON.stringify(event.message).slice(0, 100);
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
} catch {
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return { lastEvent, complete, summary };
|
|
299
|
+
} catch {
|
|
300
|
+
return { lastEvent: "error", complete: false, summary: "" };
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function getLatestClaudeSessionId(projectDir) {
|
|
304
|
+
if (!existsSync(CLAUDE_PROJECTS_DIR)) return null;
|
|
305
|
+
let searchDirs;
|
|
306
|
+
if (projectDir) {
|
|
307
|
+
const encoded = encodeClaudePath(resolve(projectDir));
|
|
308
|
+
const projPath = join(CLAUDE_PROJECTS_DIR, encoded);
|
|
309
|
+
searchDirs = existsSync(projPath) ? [projPath] : [];
|
|
310
|
+
} else {
|
|
311
|
+
try {
|
|
312
|
+
searchDirs = readdirSync(CLAUDE_PROJECTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join(CLAUDE_PROJECTS_DIR, d.name));
|
|
313
|
+
} catch {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
let latest = null;
|
|
318
|
+
for (const dir of searchDirs) {
|
|
319
|
+
try {
|
|
320
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
|
|
321
|
+
for (const file of files) {
|
|
322
|
+
const stat = statSync(join(dir, file));
|
|
323
|
+
if (!latest || stat.mtimeMs > latest.mtime) {
|
|
324
|
+
latest = { id: basename(file, ".jsonl"), mtime: stat.mtimeMs };
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
} catch {
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return latest?.id ?? null;
|
|
331
|
+
}
|
|
332
|
+
function getClaudeSessionCwd(sid) {
|
|
333
|
+
if (!existsSync(CLAUDE_PROJECTS_DIR)) return "";
|
|
334
|
+
try {
|
|
335
|
+
const projectDirs = readdirSync(CLAUDE_PROJECTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
336
|
+
for (const projDir of projectDirs) {
|
|
337
|
+
const filePath = join(CLAUDE_PROJECTS_DIR, projDir.name, `${sid}.jsonl`);
|
|
338
|
+
if (existsSync(filePath)) {
|
|
339
|
+
return decodeClaudePath(projDir.name);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
} catch {
|
|
343
|
+
}
|
|
344
|
+
return "";
|
|
345
|
+
}
|
|
346
|
+
function getClaudeSessionReport(sid) {
|
|
347
|
+
if (!existsSync(CLAUDE_PROJECTS_DIR)) return null;
|
|
348
|
+
let filePath = null;
|
|
349
|
+
let cwd = "";
|
|
350
|
+
try {
|
|
351
|
+
const projectDirs = readdirSync(CLAUDE_PROJECTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
352
|
+
for (const projDir of projectDirs) {
|
|
353
|
+
const candidate = join(CLAUDE_PROJECTS_DIR, projDir.name, `${sid}.jsonl`);
|
|
354
|
+
if (existsSync(candidate)) {
|
|
355
|
+
filePath = candidate;
|
|
356
|
+
cwd = decodeClaudePath(projDir.name);
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
} catch {
|
|
361
|
+
}
|
|
362
|
+
if (!filePath) return null;
|
|
363
|
+
let lines;
|
|
364
|
+
try {
|
|
365
|
+
lines = readFileSync(filePath, "utf-8").trimEnd().split("\n");
|
|
366
|
+
} catch {
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
const report = {
|
|
370
|
+
id: sid,
|
|
371
|
+
cwd,
|
|
372
|
+
summary: "",
|
|
373
|
+
startTime: "",
|
|
374
|
+
endTime: "",
|
|
375
|
+
durationMs: 0,
|
|
376
|
+
complete: false,
|
|
377
|
+
userMessages: 0,
|
|
378
|
+
assistantTurns: 0,
|
|
379
|
+
outputTokens: 0,
|
|
380
|
+
premiumRequests: 0,
|
|
381
|
+
toolUsage: {},
|
|
382
|
+
gitCommits: [],
|
|
383
|
+
filesCreated: [],
|
|
384
|
+
filesEdited: [],
|
|
385
|
+
errors: [],
|
|
386
|
+
taskCompletions: [],
|
|
387
|
+
agent: "claude"
|
|
388
|
+
};
|
|
389
|
+
for (const line of lines) {
|
|
390
|
+
let event;
|
|
391
|
+
try {
|
|
392
|
+
event = JSON.parse(line);
|
|
393
|
+
} catch {
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
const type = event.type ?? event.role ?? "";
|
|
397
|
+
const ts = event.timestamp;
|
|
398
|
+
if (ts && !report.startTime) report.startTime = ts;
|
|
399
|
+
if (ts) report.endTime = ts;
|
|
400
|
+
if (type === "human" || type === "user") {
|
|
401
|
+
report.userMessages++;
|
|
402
|
+
if (!report.summary) {
|
|
403
|
+
const msg = event.message ?? event.content;
|
|
404
|
+
report.summary = (typeof msg === "string" ? msg : JSON.stringify(msg ?? "")).slice(0, 100);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (type === "assistant") {
|
|
408
|
+
report.assistantTurns++;
|
|
409
|
+
const usage = event.usage;
|
|
410
|
+
if (usage?.output_tokens) report.outputTokens += usage.output_tokens;
|
|
411
|
+
if (event.stop_reason === "end_turn") report.complete = true;
|
|
412
|
+
}
|
|
413
|
+
if (type === "tool_use") {
|
|
414
|
+
const toolName = event.name ?? event.tool ?? "unknown";
|
|
415
|
+
report.toolUsage[toolName] = (report.toolUsage[toolName] ?? 0) + 1;
|
|
416
|
+
if (toolName === "Bash" || toolName === "bash") {
|
|
417
|
+
const input = event.input ?? "";
|
|
418
|
+
const cmd = typeof input === "string" ? input : input?.command ?? "";
|
|
419
|
+
if (cmd.includes("git") && cmd.includes("commit") && cmd.includes("-m")) {
|
|
420
|
+
const msgMatch = cmd.match(/-m\s+"([^"]{1,120})/);
|
|
421
|
+
if (msgMatch) report.gitCommits.push(msgMatch[1]);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (toolName === "Write" || toolName === "Create") {
|
|
425
|
+
const path = event.input?.file_path ?? event.input?.path;
|
|
426
|
+
if (path) report.filesCreated.push(path);
|
|
427
|
+
}
|
|
428
|
+
if (toolName === "Edit") {
|
|
429
|
+
const path = event.input?.file_path ?? event.input?.path;
|
|
430
|
+
if (path && !report.filesEdited.includes(path)) report.filesEdited.push(path);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
if (type === "result") {
|
|
434
|
+
report.complete = true;
|
|
435
|
+
const result = event.result ?? event.content ?? "";
|
|
436
|
+
if (result) report.taskCompletions.push(typeof result === "string" ? result.slice(0, 200) : "(completed)");
|
|
437
|
+
}
|
|
438
|
+
if (type === "error") {
|
|
439
|
+
const msg = event.error ?? event.message ?? "unknown error";
|
|
440
|
+
report.errors.push(typeof msg === "string" ? msg : JSON.stringify(msg));
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (report.startTime && report.endTime) {
|
|
444
|
+
report.durationMs = new Date(report.endTime).getTime() - new Date(report.startTime).getTime();
|
|
445
|
+
}
|
|
446
|
+
return report;
|
|
447
|
+
}
|
|
448
|
+
function listAllSessions(limit = 20, agentFilter) {
|
|
449
|
+
const copilot = agentFilter === "claude" ? [] : listSessions(limit);
|
|
450
|
+
const claude = agentFilter === "copilot" ? [] : listClaudeSessions(limit);
|
|
451
|
+
const all = [...copilot, ...claude];
|
|
452
|
+
all.sort((a, b) => b.mtime - a.mtime);
|
|
453
|
+
return all.slice(0, limit);
|
|
454
|
+
}
|
|
455
|
+
function getAgentSessionReport(sid, agent) {
|
|
456
|
+
if (agent === "claude") return getClaudeSessionReport(sid);
|
|
457
|
+
if (agent === "copilot") return getSessionReport(sid);
|
|
458
|
+
return getSessionReport(sid) ?? getClaudeSessionReport(sid);
|
|
459
|
+
}
|
|
460
|
+
function findLatestIncompleteForAgent(agent) {
|
|
461
|
+
const sessions = listAllSessions(50, agent);
|
|
462
|
+
for (const s of sessions) {
|
|
463
|
+
if (!s.complete) return { id: s.id, agent: s.agent };
|
|
464
|
+
}
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
229
467
|
|
|
230
468
|
// src/lib/process.ts
|
|
231
469
|
import { execSync as execSync2, spawn } from "child_process";
|
|
@@ -308,21 +546,7 @@ function notify(message, title = "copilot-agent") {
|
|
|
308
546
|
}
|
|
309
547
|
|
|
310
548
|
// src/lib/process.ts
|
|
311
|
-
function
|
|
312
|
-
try {
|
|
313
|
-
execSync2("which copilot", { stdio: "pipe", encoding: "utf-8" });
|
|
314
|
-
return true;
|
|
315
|
-
} catch {
|
|
316
|
-
return false;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
function assertCopilot() {
|
|
320
|
-
if (!isCopilotInstalled()) {
|
|
321
|
-
fail("copilot CLI not found. Install with: npm i -g @githubnext/copilot");
|
|
322
|
-
process.exit(1);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
function findCopilotProcesses() {
|
|
549
|
+
function findAgentProcesses(agentFilter) {
|
|
326
550
|
try {
|
|
327
551
|
const output = execSync2("ps -eo pid,command", { encoding: "utf-8" });
|
|
328
552
|
const results = [];
|
|
@@ -331,52 +555,56 @@ function findCopilotProcesses() {
|
|
|
331
555
|
for (const line of output.split("\n")) {
|
|
332
556
|
const trimmed = line.trim();
|
|
333
557
|
if (!trimmed) continue;
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
558
|
+
const isCopilot = (trimmed.includes("copilot") || trimmed.includes("@githubnext/copilot")) && !trimmed.includes("copilot-agent") && !trimmed.includes("copilot-api");
|
|
559
|
+
const isClaude = trimmed.includes("claude") && !trimmed.includes("claude-code") && !trimmed.includes("copilot-agent");
|
|
560
|
+
if (!isCopilot && !isClaude) continue;
|
|
561
|
+
if (trimmed.includes("ps -eo") || trimmed.includes("grep")) continue;
|
|
562
|
+
const agent = isClaude ? "claude" : "copilot";
|
|
563
|
+
if (agentFilter && agent !== agentFilter) continue;
|
|
564
|
+
const match = trimmed.match(/^(\d+)\s+(.+)$/);
|
|
565
|
+
if (!match) continue;
|
|
566
|
+
const pid = parseInt(match[1], 10);
|
|
567
|
+
if (pid === myPid || pid === parentPid) continue;
|
|
568
|
+
const cmd = match[2];
|
|
569
|
+
const sidMatch = agent === "copilot" ? cmd.match(/resume[= ]+([a-f0-9-]{36})/) : cmd.match(/(?:--resume|--session-id)[= ]+([a-f0-9-]{36})/);
|
|
570
|
+
let cwd;
|
|
571
|
+
try {
|
|
572
|
+
cwd = execSync2(`lsof -p ${pid} -Fn 2>/dev/null | grep '^n/' | head -1`, {
|
|
573
|
+
encoding: "utf-8",
|
|
574
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
575
|
+
}).trim().slice(1) || void 0;
|
|
576
|
+
} catch {
|
|
577
|
+
}
|
|
578
|
+
const sid = sidMatch?.[1];
|
|
579
|
+
if (!cwd && sid) {
|
|
580
|
+
cwd = (agent === "copilot" ? getSessionCwd(sid) : getClaudeSessionCwd(sid)) || void 0;
|
|
355
581
|
}
|
|
582
|
+
results.push({ pid, command: cmd, sessionId: sid, cwd, agent });
|
|
356
583
|
}
|
|
357
584
|
return results;
|
|
358
585
|
} catch {
|
|
359
586
|
return [];
|
|
360
587
|
}
|
|
361
588
|
}
|
|
362
|
-
function findPidForSession(sid) {
|
|
363
|
-
const procs =
|
|
589
|
+
function findPidForSession(sid, agent) {
|
|
590
|
+
const procs = findAgentProcesses(agent);
|
|
364
591
|
const matching = procs.filter((p) => p.command.includes(sid)).sort((a, b) => b.pid - a.pid);
|
|
365
592
|
return matching[0]?.pid ?? null;
|
|
366
593
|
}
|
|
367
|
-
async function
|
|
594
|
+
async function waitForAgentInDir(dir, agent, timeoutMs = 144e5, pollMs = 1e4) {
|
|
368
595
|
const targetDir = resolve2(dir);
|
|
369
596
|
const start = Date.now();
|
|
370
597
|
let warned = false;
|
|
598
|
+
const label = agent ?? "agent";
|
|
371
599
|
while (Date.now() - start < timeoutMs) {
|
|
372
|
-
const procs =
|
|
600
|
+
const procs = findAgentProcesses(agent);
|
|
373
601
|
const conflicting = procs.filter((p) => {
|
|
374
602
|
if (!p.cwd) return false;
|
|
375
603
|
return resolve2(p.cwd) === targetDir;
|
|
376
604
|
});
|
|
377
605
|
if (conflicting.length === 0) return;
|
|
378
606
|
if (!warned) {
|
|
379
|
-
warn(`Waiting for
|
|
607
|
+
warn(`Waiting for ${label} in ${targetDir} to finish...`);
|
|
380
608
|
for (const p of conflicting) {
|
|
381
609
|
log(` PID ${p.pid}: ${p.command.slice(0, 80)}`);
|
|
382
610
|
}
|
|
@@ -384,12 +612,13 @@ async function waitForCopilotInDir(dir, timeoutMs = 144e5, pollMs = 1e4) {
|
|
|
384
612
|
}
|
|
385
613
|
await sleep(pollMs);
|
|
386
614
|
}
|
|
387
|
-
warn(
|
|
615
|
+
warn(`Timeout waiting for ${label} to finish in directory`);
|
|
388
616
|
}
|
|
389
|
-
function assertSessionNotRunning(sid) {
|
|
390
|
-
const pid = findPidForSession(sid);
|
|
617
|
+
function assertSessionNotRunning(sid, agent) {
|
|
618
|
+
const pid = findPidForSession(sid, agent);
|
|
391
619
|
if (pid) {
|
|
392
|
-
|
|
620
|
+
const label = agent ?? "agent";
|
|
621
|
+
fail(`Session ${sid.slice(0, 8)}\u2026 already has ${label} running (PID ${pid}). Cannot resume.`);
|
|
393
622
|
process.exit(1);
|
|
394
623
|
}
|
|
395
624
|
}
|
|
@@ -407,9 +636,8 @@ async function waitForExit(pid, timeoutMs = 144e5) {
|
|
|
407
636
|
}
|
|
408
637
|
async function runCopilot(args, options) {
|
|
409
638
|
const dir = options?.cwd ?? process.cwd();
|
|
410
|
-
if (options?.useWorktree) {
|
|
411
|
-
|
|
412
|
-
await waitForCopilotInDir(dir);
|
|
639
|
+
if (!options?.useWorktree) {
|
|
640
|
+
await waitForAgentInDir(dir, "copilot");
|
|
413
641
|
}
|
|
414
642
|
return new Promise((resolve7) => {
|
|
415
643
|
const child = spawn("copilot", args, {
|
|
@@ -421,11 +649,7 @@ async function runCopilot(args, options) {
|
|
|
421
649
|
await sleep(3e3);
|
|
422
650
|
const sid = getLatestSessionId();
|
|
423
651
|
const premium = sid ? getSessionPremium(sid) : 0;
|
|
424
|
-
resolve7({
|
|
425
|
-
exitCode: code ?? 1,
|
|
426
|
-
sessionId: sid,
|
|
427
|
-
premium
|
|
428
|
-
});
|
|
652
|
+
resolve7({ exitCode: code ?? 1, sessionId: sid, premium });
|
|
429
653
|
});
|
|
430
654
|
child.on("error", () => {
|
|
431
655
|
resolve7({ exitCode: 1, sessionId: null, premium: 0 });
|
|
@@ -433,7 +657,7 @@ async function runCopilot(args, options) {
|
|
|
433
657
|
});
|
|
434
658
|
}
|
|
435
659
|
function runCopilotResume(sid, steps, message, cwd) {
|
|
436
|
-
assertSessionNotRunning(sid);
|
|
660
|
+
assertSessionNotRunning(sid, "copilot");
|
|
437
661
|
const args = [
|
|
438
662
|
`--resume=${sid}`,
|
|
439
663
|
"--autopilot",
|
|
@@ -456,38 +680,82 @@ function runCopilotTask(prompt, steps, cwd, useWorktree) {
|
|
|
456
680
|
"--no-ask-user"
|
|
457
681
|
], { cwd, useWorktree });
|
|
458
682
|
}
|
|
683
|
+
async function runClaude(args, options) {
|
|
684
|
+
const dir = options?.cwd ?? process.cwd();
|
|
685
|
+
if (!options?.useWorktree) {
|
|
686
|
+
await waitForAgentInDir(dir, "claude");
|
|
687
|
+
}
|
|
688
|
+
return new Promise((resolve7) => {
|
|
689
|
+
const child = spawn("claude", args, {
|
|
690
|
+
cwd: options?.cwd,
|
|
691
|
+
stdio: "inherit",
|
|
692
|
+
env: { ...process.env }
|
|
693
|
+
});
|
|
694
|
+
child.on("close", async (code) => {
|
|
695
|
+
await sleep(2e3);
|
|
696
|
+
const sid = getLatestClaudeSessionId(options?.cwd);
|
|
697
|
+
resolve7({ exitCode: code ?? 1, sessionId: sid, premium: 0 });
|
|
698
|
+
});
|
|
699
|
+
child.on("error", () => {
|
|
700
|
+
resolve7({ exitCode: 1, sessionId: null, premium: 0 });
|
|
701
|
+
});
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
function runClaudeResume(sid, _steps, message, cwd) {
|
|
705
|
+
assertSessionNotRunning(sid, "claude");
|
|
706
|
+
const args = ["--resume", sid, "--dangerously-skip-permissions"];
|
|
707
|
+
if (message) args.push(message);
|
|
708
|
+
return runClaude(args, { cwd });
|
|
709
|
+
}
|
|
710
|
+
function runClaudeTask(prompt, _steps, cwd, useWorktree) {
|
|
711
|
+
return runClaude([
|
|
712
|
+
"--print",
|
|
713
|
+
"--dangerously-skip-permissions",
|
|
714
|
+
"--output-format",
|
|
715
|
+
"text",
|
|
716
|
+
prompt
|
|
717
|
+
], { cwd, useWorktree });
|
|
718
|
+
}
|
|
719
|
+
function runAgentTask(agent, prompt, steps, cwd, useWorktree) {
|
|
720
|
+
return agent === "claude" ? runClaudeTask(prompt, steps, cwd, useWorktree) : runCopilotTask(prompt, steps, cwd, useWorktree);
|
|
721
|
+
}
|
|
722
|
+
function runAgentResume(agent, sid, steps, message, cwd) {
|
|
723
|
+
return agent === "claude" ? runClaudeResume(sid, steps, message, cwd) : runCopilotResume(sid, steps, message, cwd);
|
|
724
|
+
}
|
|
459
725
|
function sleep(ms) {
|
|
460
726
|
return new Promise((r) => setTimeout(r, ms));
|
|
461
727
|
}
|
|
462
728
|
|
|
463
729
|
// src/commands/status.ts
|
|
464
730
|
function registerStatusCommand(program2) {
|
|
465
|
-
program2.command("status").description("Show
|
|
731
|
+
program2.command("status").description("Show agent session status (copilot + claude)").option("-l, --limit <n>", "Number of sessions to show", "10").option("-a, --active", "Show only active (running) processes").option("-i, --incomplete", "Only show incomplete sessions").option("--agent <type>", "Filter by agent: copilot or claude").action((opts) => {
|
|
732
|
+
const agentFilter = opts.agent;
|
|
466
733
|
if (opts.active) {
|
|
467
|
-
showActive();
|
|
734
|
+
showActive(agentFilter);
|
|
468
735
|
} else {
|
|
469
|
-
showRecent(parseInt(opts.limit, 10), opts.incomplete ?? false);
|
|
736
|
+
showRecent(parseInt(opts.limit, 10), opts.incomplete ?? false, agentFilter);
|
|
470
737
|
}
|
|
471
738
|
});
|
|
472
739
|
}
|
|
473
|
-
function showActive() {
|
|
474
|
-
const procs =
|
|
740
|
+
function showActive(agentFilter) {
|
|
741
|
+
const procs = findAgentProcesses(agentFilter);
|
|
475
742
|
if (procs.length === 0) {
|
|
476
|
-
log(`${DIM}No active
|
|
743
|
+
log(`${DIM}No active agent processes.${RESET}`);
|
|
477
744
|
return;
|
|
478
745
|
}
|
|
479
746
|
log(`
|
|
480
|
-
${BOLD}${"PID".padEnd(8)} ${"Session".padEnd(40)} Command${RESET}`);
|
|
481
|
-
log("\u2500".repeat(
|
|
747
|
+
${BOLD}${"Agent".padEnd(9)} ${"PID".padEnd(8)} ${"Session".padEnd(40)} Command${RESET}`);
|
|
748
|
+
log("\u2500".repeat(118));
|
|
482
749
|
for (const p of procs) {
|
|
750
|
+
const agentLabel = p.agent === "claude" ? `${CYAN}claude${RESET} ` : `${GREEN}copilot${RESET}`;
|
|
483
751
|
log(
|
|
484
|
-
`${
|
|
752
|
+
`${agentLabel.padEnd(9 + 9)} ${String(p.pid).padEnd(8)} ${(p.sessionId ?? "\u2014").padEnd(40)} ${truncate(p.command, 50)}`
|
|
485
753
|
);
|
|
486
754
|
}
|
|
487
755
|
log("");
|
|
488
756
|
}
|
|
489
|
-
function showRecent(limit, incompleteOnly) {
|
|
490
|
-
let sessions =
|
|
757
|
+
function showRecent(limit, incompleteOnly, agentFilter) {
|
|
758
|
+
let sessions = listAllSessions(limit, agentFilter);
|
|
491
759
|
if (incompleteOnly) {
|
|
492
760
|
sessions = sessions.filter((s) => !s.complete);
|
|
493
761
|
}
|
|
@@ -497,15 +765,16 @@ function showRecent(limit, incompleteOnly) {
|
|
|
497
765
|
}
|
|
498
766
|
log(
|
|
499
767
|
`
|
|
500
|
-
${BOLD}${"Status".padEnd(10)} ${"Premium".padEnd(10)} ${"Last Event".padEnd(
|
|
768
|
+
${BOLD}${"Agent".padEnd(9)} ${"Status".padEnd(10)} ${"Premium".padEnd(10)} ${"Last Event".padEnd(22)} ${"Summary".padEnd(35)} ID${RESET}`
|
|
501
769
|
);
|
|
502
|
-
log("\u2500".repeat(
|
|
770
|
+
log("\u2500".repeat(130));
|
|
503
771
|
for (const s of sessions) {
|
|
772
|
+
const agentLabel = s.agent === "claude" ? `${CYAN}claude${RESET} ` : `${GREEN}copilot${RESET}`;
|
|
504
773
|
const status = s.complete ? `${GREEN}\u2714 done${RESET}` : `${YELLOW}\u23F8 stop${RESET}`;
|
|
505
|
-
const premium = String(s.premiumRequests);
|
|
506
|
-
const summary = truncate(s.summary || "\u2014",
|
|
774
|
+
const premium = s.agent === "claude" ? "\u2014" : String(s.premiumRequests);
|
|
775
|
+
const summary = truncate(s.summary || "\u2014", 33);
|
|
507
776
|
log(
|
|
508
|
-
`${status.padEnd(10 + 9)} ${premium.padEnd(10)} ${s.lastEvent.padEnd(
|
|
777
|
+
`${agentLabel.padEnd(9 + 9)} ${status.padEnd(10 + 9)} ${premium.padEnd(10)} ${s.lastEvent.padEnd(22)} ${summary.padEnd(35)} ${DIM}${s.id.slice(0, 12)}\u2026${RESET}`
|
|
509
778
|
);
|
|
510
779
|
}
|
|
511
780
|
log(`
|
|
@@ -516,15 +785,55 @@ function truncate(s, max) {
|
|
|
516
785
|
return s.substring(0, max - 1) + "\u2026";
|
|
517
786
|
}
|
|
518
787
|
|
|
788
|
+
// src/lib/provider.ts
|
|
789
|
+
import { execSync as execSync3 } from "child_process";
|
|
790
|
+
function detectAvailableAgents() {
|
|
791
|
+
const agents = [];
|
|
792
|
+
try {
|
|
793
|
+
execSync3("which copilot", { stdio: "pipe" });
|
|
794
|
+
agents.push("copilot");
|
|
795
|
+
} catch {
|
|
796
|
+
}
|
|
797
|
+
try {
|
|
798
|
+
execSync3("which claude", { stdio: "pipe" });
|
|
799
|
+
agents.push("claude");
|
|
800
|
+
} catch {
|
|
801
|
+
}
|
|
802
|
+
return agents;
|
|
803
|
+
}
|
|
804
|
+
function resolveAgent(explicit) {
|
|
805
|
+
if (explicit === "copilot" || explicit === "claude") return explicit;
|
|
806
|
+
const available = detectAvailableAgents();
|
|
807
|
+
if (available.includes("copilot")) return "copilot";
|
|
808
|
+
if (available.includes("claude")) return "claude";
|
|
809
|
+
return "copilot";
|
|
810
|
+
}
|
|
811
|
+
function isAgentInstalled(agent) {
|
|
812
|
+
try {
|
|
813
|
+
execSync3(`which ${agent}`, { stdio: "pipe" });
|
|
814
|
+
return true;
|
|
815
|
+
} catch {
|
|
816
|
+
return false;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
function assertAgent(agent) {
|
|
820
|
+
if (!isAgentInstalled(agent)) {
|
|
821
|
+
const installHint = agent === "copilot" ? "npm i -g @githubnext/copilot" : "npm i -g @anthropic-ai/claude-code";
|
|
822
|
+
console.error(`\u2716 ${agent} CLI not found. Install with: ${installHint}`);
|
|
823
|
+
process.exit(1);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
519
827
|
// src/commands/watch.ts
|
|
520
828
|
function registerWatchCommand(program2) {
|
|
521
|
-
program2.command("watch [session-id]").description("Watch a session and auto-resume when it stops").option("-s, --steps <n>", "Max autopilot continues per resume", "30").option("-r, --max-resumes <n>", "Max number of resumes", "10").option("-c, --cooldown <n>", "Seconds between resumes", "10").option("-m, --message <msg>", "Message to send on resume").action(async (sid, opts) => {
|
|
829
|
+
program2.command("watch [session-id]").description("Watch a session and auto-resume when it stops").option("-s, --steps <n>", "Max autopilot continues per resume", "30").option("-r, --max-resumes <n>", "Max number of resumes", "10").option("-c, --cooldown <n>", "Seconds between resumes", "10").option("-m, --message <msg>", "Message to send on resume").option("-a, --agent <type>", "Agent to use: copilot or claude").action(async (sid, opts) => {
|
|
522
830
|
try {
|
|
523
831
|
await watchCommand(sid, {
|
|
524
832
|
steps: parseInt(opts.steps, 10),
|
|
525
833
|
maxResumes: parseInt(opts.maxResumes, 10),
|
|
526
834
|
cooldown: parseInt(opts.cooldown, 10),
|
|
527
|
-
message: opts.message
|
|
835
|
+
message: opts.message,
|
|
836
|
+
agent: resolveAgent(opts.agent)
|
|
528
837
|
});
|
|
529
838
|
} catch (err) {
|
|
530
839
|
fail(`Watch error: ${err instanceof Error ? err.message : err}`);
|
|
@@ -533,14 +842,15 @@ function registerWatchCommand(program2) {
|
|
|
533
842
|
});
|
|
534
843
|
}
|
|
535
844
|
async function watchCommand(sid, opts) {
|
|
536
|
-
|
|
845
|
+
assertAgent(opts.agent);
|
|
537
846
|
if (!sid) {
|
|
538
|
-
|
|
847
|
+
const result = findLatestIncompleteForAgent(opts.agent);
|
|
848
|
+
sid = result?.id;
|
|
539
849
|
if (!sid) {
|
|
540
850
|
fail("No incomplete session found.");
|
|
541
851
|
process.exit(1);
|
|
542
852
|
}
|
|
543
|
-
info(`Auto-detected incomplete session: ${CYAN}${sid}${RESET}`);
|
|
853
|
+
info(`Auto-detected incomplete ${opts.agent} session: ${CYAN}${sid}${RESET}`);
|
|
544
854
|
}
|
|
545
855
|
if (!validateSession(sid)) {
|
|
546
856
|
fail(`Invalid session: ${sid}`);
|
|
@@ -552,9 +862,9 @@ async function watchCommand(sid, opts) {
|
|
|
552
862
|
}
|
|
553
863
|
let resumes = 0;
|
|
554
864
|
while (resumes < opts.maxResumes) {
|
|
555
|
-
const pid = findPidForSession(sid);
|
|
865
|
+
const pid = findPidForSession(sid, opts.agent);
|
|
556
866
|
if (pid) {
|
|
557
|
-
info(`Watching PID ${pid} for session ${CYAN}${sid.slice(0, 8)}${RESET}\u2026`);
|
|
867
|
+
info(`Watching PID ${pid} for ${opts.agent} session ${CYAN}${sid.slice(0, 8)}${RESET}\u2026`);
|
|
558
868
|
const exited = await waitForExit(pid);
|
|
559
869
|
if (!exited) {
|
|
560
870
|
warn("Timeout waiting for process exit.");
|
|
@@ -574,7 +884,8 @@ async function watchCommand(sid, opts) {
|
|
|
574
884
|
await sleep2(opts.cooldown * 1e3);
|
|
575
885
|
}
|
|
576
886
|
const cwd = getSessionCwd(sid) || void 0;
|
|
577
|
-
const result = await
|
|
887
|
+
const result = await runAgentResume(
|
|
888
|
+
opts.agent,
|
|
578
889
|
sid,
|
|
579
890
|
opts.steps,
|
|
580
891
|
opts.message ?? "Continue remaining work. Pick up where you left off and complete the task.",
|
|
@@ -594,8 +905,8 @@ function sleep2(ms) {
|
|
|
594
905
|
|
|
595
906
|
// src/lib/detect.ts
|
|
596
907
|
import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
|
|
597
|
-
import { join as join2, basename, resolve as resolve3 } from "path";
|
|
598
|
-
import { execSync as
|
|
908
|
+
import { join as join2, basename as basename2, resolve as resolve3 } from "path";
|
|
909
|
+
import { execSync as execSync4 } from "child_process";
|
|
599
910
|
function detectProjectType(dir) {
|
|
600
911
|
const exists = (f) => existsSync2(join2(dir, f));
|
|
601
912
|
if (exists("build.gradle.kts") || exists("build.gradle")) {
|
|
@@ -639,11 +950,11 @@ function detectProjectName(dir) {
|
|
|
639
950
|
if (pkg.name) return pkg.name;
|
|
640
951
|
} catch {
|
|
641
952
|
}
|
|
642
|
-
return
|
|
953
|
+
return basename2(resolve3(dir));
|
|
643
954
|
}
|
|
644
955
|
function detectMainBranch(dir) {
|
|
645
956
|
try {
|
|
646
|
-
const ref =
|
|
957
|
+
const ref = execSync4("git symbolic-ref refs/remotes/origin/HEAD", {
|
|
647
958
|
cwd: dir,
|
|
648
959
|
encoding: "utf-8",
|
|
649
960
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -652,7 +963,7 @@ function detectMainBranch(dir) {
|
|
|
652
963
|
} catch {
|
|
653
964
|
}
|
|
654
965
|
try {
|
|
655
|
-
const branch =
|
|
966
|
+
const branch = execSync4("git branch --show-current", {
|
|
656
967
|
cwd: dir,
|
|
657
968
|
encoding: "utf-8",
|
|
658
969
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -818,10 +1129,10 @@ async function withLock(name, fn) {
|
|
|
818
1129
|
// src/lib/git.ts
|
|
819
1130
|
import { existsSync as existsSync4 } from "fs";
|
|
820
1131
|
import { join as join4, resolve as resolve4 } from "path";
|
|
821
|
-
import { execSync as
|
|
1132
|
+
import { execSync as execSync5 } from "child_process";
|
|
822
1133
|
function gitExec(dir, cmd) {
|
|
823
1134
|
try {
|
|
824
|
-
return
|
|
1135
|
+
return execSync5(cmd, {
|
|
825
1136
|
cwd: dir,
|
|
826
1137
|
encoding: "utf-8",
|
|
827
1138
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -873,14 +1184,15 @@ function removeWorktree(repoDir, worktreePath) {
|
|
|
873
1184
|
|
|
874
1185
|
// src/commands/run.ts
|
|
875
1186
|
function registerRunCommand(program2) {
|
|
876
|
-
program2.command("run [dir]").description("Discover and fix issues in a project").option("-s, --steps <n>", "Max autopilot continues per task", "30").option("-t, --max-tasks <n>", "Max number of tasks to run", "5").option("-p, --max-premium <n>", "Max total premium requests", "50").option("--dry-run", "Show tasks without executing").option("--worktree", "Use git worktree for parallel execution (default: wait for idle)").action(async (dir, opts) => {
|
|
1187
|
+
program2.command("run [dir]").description("Discover and fix issues in a project").option("-s, --steps <n>", "Max autopilot continues per task", "30").option("-t, --max-tasks <n>", "Max number of tasks to run", "5").option("-p, --max-premium <n>", "Max total premium requests", "50").option("--dry-run", "Show tasks without executing").option("--worktree", "Use git worktree for parallel execution (default: wait for idle)").option("-a, --agent <type>", "Agent to use: copilot or claude").action(async (dir, opts) => {
|
|
877
1188
|
try {
|
|
878
1189
|
await runCommand(dir ?? process.cwd(), {
|
|
879
1190
|
steps: parseInt(opts.steps, 10),
|
|
880
1191
|
maxTasks: parseInt(opts.maxTasks, 10),
|
|
881
1192
|
maxPremium: parseInt(opts.maxPremium, 10),
|
|
882
1193
|
dryRun: opts.dryRun ?? false,
|
|
883
|
-
useWorktree: opts.worktree ?? false
|
|
1194
|
+
useWorktree: opts.worktree ?? false,
|
|
1195
|
+
agent: resolveAgent(opts.agent)
|
|
884
1196
|
});
|
|
885
1197
|
} catch (err) {
|
|
886
1198
|
fail(`Run error: ${err instanceof Error ? err.message : err}`);
|
|
@@ -889,11 +1201,11 @@ function registerRunCommand(program2) {
|
|
|
889
1201
|
});
|
|
890
1202
|
}
|
|
891
1203
|
async function runCommand(dir, opts) {
|
|
892
|
-
|
|
1204
|
+
assertAgent(opts.agent);
|
|
893
1205
|
const projectType = detectProjectType(dir);
|
|
894
1206
|
const name = detectProjectName(dir);
|
|
895
1207
|
const mainBranch = isGitRepo(dir) ? detectMainBranch(dir) : null;
|
|
896
|
-
info(`Project: ${CYAN}${name}${RESET} (${projectType})`);
|
|
1208
|
+
info(`Project: ${CYAN}${name}${RESET} (${projectType}) \u2014 agent: ${opts.agent}`);
|
|
897
1209
|
if (mainBranch) info(`Main branch: ${mainBranch}`);
|
|
898
1210
|
const tasks = getTasksForProject(projectType).slice(0, opts.maxTasks);
|
|
899
1211
|
if (tasks.length === 0) {
|
|
@@ -935,9 +1247,14 @@ ${"\u2550".repeat(60)}`);
|
|
|
935
1247
|
let worktreeCreated = false;
|
|
936
1248
|
if (opts.useWorktree && isGitRepo(dir)) {
|
|
937
1249
|
try {
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1250
|
+
const wt = createWorktree(dir, branchName);
|
|
1251
|
+
if (wt) {
|
|
1252
|
+
taskDir = wt;
|
|
1253
|
+
worktreeCreated = true;
|
|
1254
|
+
info(`Created worktree: ${taskDir}`);
|
|
1255
|
+
} else {
|
|
1256
|
+
warn("Worktree creation returned null, falling back to main dir");
|
|
1257
|
+
}
|
|
941
1258
|
} catch (err) {
|
|
942
1259
|
warn(`Worktree creation failed, falling back to main dir: ${err}`);
|
|
943
1260
|
taskDir = dir;
|
|
@@ -945,7 +1262,7 @@ ${"\u2550".repeat(60)}`);
|
|
|
945
1262
|
}
|
|
946
1263
|
const result = await withLock(
|
|
947
1264
|
"copilot-run",
|
|
948
|
-
() =>
|
|
1265
|
+
() => runAgentTask(opts.agent, task.prompt, opts.steps, taskDir, opts.useWorktree)
|
|
949
1266
|
);
|
|
950
1267
|
const commitRef = worktreeCreated ? taskDir : dir;
|
|
951
1268
|
const commits = mainBranch ? gitCountCommits(commitRef, mainBranch, "HEAD") : 0;
|
|
@@ -973,7 +1290,7 @@ ${BOLD}\u2550\u2550\u2550 Run Summary \u2550\u2550\u2550${RESET}`);
|
|
|
973
1290
|
import { join as join5 } from "path";
|
|
974
1291
|
import { homedir as homedir3 } from "os";
|
|
975
1292
|
function registerOvernightCommand(program2) {
|
|
976
|
-
program2.command("overnight [dir]").description("Run tasks continuously until a deadline").option("-u, --until <HH>", "Stop at this hour (24h format)", "07").option("-s, --steps <n>", "Max autopilot continues per task", "50").option("-c, --cooldown <n>", "Seconds between tasks", "15").option("-p, --max-premium <n>", "Max premium requests budget", "300").option("--dry-run", "Show plan without executing").option("--worktree", "Use git worktree for parallel execution (default: wait for idle)").action(async (dir, opts) => {
|
|
1293
|
+
program2.command("overnight [dir]").description("Run tasks continuously until a deadline").option("-u, --until <HH>", "Stop at this hour (24h format)", "07").option("-s, --steps <n>", "Max autopilot continues per task", "50").option("-c, --cooldown <n>", "Seconds between tasks", "15").option("-p, --max-premium <n>", "Max premium requests budget", "300").option("--dry-run", "Show plan without executing").option("--worktree", "Use git worktree for parallel execution (default: wait for idle)").option("-a, --agent <type>", "Agent to use: copilot or claude").action(async (dir, opts) => {
|
|
977
1294
|
try {
|
|
978
1295
|
await overnightCommand(dir ?? process.cwd(), {
|
|
979
1296
|
until: parseInt(opts.until, 10),
|
|
@@ -981,7 +1298,8 @@ function registerOvernightCommand(program2) {
|
|
|
981
1298
|
cooldown: parseInt(opts.cooldown, 10),
|
|
982
1299
|
maxPremium: parseInt(opts.maxPremium, 10),
|
|
983
1300
|
dryRun: opts.dryRun ?? false,
|
|
984
|
-
useWorktree: opts.worktree ?? false
|
|
1301
|
+
useWorktree: opts.worktree ?? false,
|
|
1302
|
+
agent: resolveAgent(opts.agent)
|
|
985
1303
|
});
|
|
986
1304
|
} catch (err) {
|
|
987
1305
|
fail(`Overnight error: ${err instanceof Error ? err.message : err}`);
|
|
@@ -994,14 +1312,14 @@ function isPastDeadline(untilHour) {
|
|
|
994
1312
|
return hour >= untilHour && hour < 20;
|
|
995
1313
|
}
|
|
996
1314
|
async function overnightCommand(dir, opts) {
|
|
997
|
-
|
|
1315
|
+
assertAgent(opts.agent);
|
|
998
1316
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").slice(0, 15);
|
|
999
1317
|
const logPath = join5(homedir3(), ".copilot", "auto-resume-logs", `overnight-${ts}.log`);
|
|
1000
1318
|
setLogFile(logPath);
|
|
1001
1319
|
const name = detectProjectName(dir);
|
|
1002
1320
|
const projectType = detectProjectType(dir);
|
|
1003
1321
|
const mainBranch = isGitRepo(dir) ? detectMainBranch(dir) : null;
|
|
1004
|
-
info(`Overnight runner for ${CYAN}${name}${RESET} (${projectType})`);
|
|
1322
|
+
info(`Overnight runner for ${CYAN}${name}${RESET} (${projectType}) \u2014 agent: ${opts.agent}`);
|
|
1005
1323
|
info(`Deadline: ${String(opts.until).padStart(2, "0")}:00`);
|
|
1006
1324
|
info(`Max premium: ${opts.maxPremium}, Steps: ${opts.steps}`);
|
|
1007
1325
|
info(`Log: ${logPath}`);
|
|
@@ -1012,18 +1330,20 @@ Would run ${tasks.length} tasks:`);
|
|
|
1012
1330
|
for (const t of tasks) log(` ${DIM}\u2022${RESET} ${t.title}`);
|
|
1013
1331
|
return;
|
|
1014
1332
|
}
|
|
1015
|
-
const
|
|
1333
|
+
const existingResult = findLatestIncompleteForAgent(opts.agent);
|
|
1334
|
+
const existingSession = existingResult?.id;
|
|
1016
1335
|
if (existingSession && validateSession(existingSession)) {
|
|
1017
1336
|
info(`Found incomplete session: ${existingSession}`);
|
|
1018
|
-
const pid = findPidForSession(existingSession);
|
|
1337
|
+
const pid = findPidForSession(existingSession, opts.agent);
|
|
1019
1338
|
if (pid) {
|
|
1020
|
-
info(`Waiting for running
|
|
1339
|
+
info(`Waiting for running ${opts.agent} (PID ${pid})...`);
|
|
1021
1340
|
await waitForExit(pid);
|
|
1022
1341
|
}
|
|
1023
1342
|
if (!hasTaskComplete(existingSession) && !isPastDeadline(opts.until)) {
|
|
1024
1343
|
info("Resuming incomplete session...");
|
|
1025
1344
|
const cwd = getSessionCwd(existingSession) || dir;
|
|
1026
|
-
await
|
|
1345
|
+
await runAgentResume(
|
|
1346
|
+
opts.agent,
|
|
1027
1347
|
existingSession,
|
|
1028
1348
|
opts.steps,
|
|
1029
1349
|
"Continue remaining work. Complete the task.",
|
|
@@ -1060,9 +1380,14 @@ ${"\u2550".repeat(60)}`);
|
|
|
1060
1380
|
let worktreeCreated = false;
|
|
1061
1381
|
if (opts.useWorktree && isGitRepo(dir)) {
|
|
1062
1382
|
try {
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1383
|
+
const wt = createWorktree(dir, branchName);
|
|
1384
|
+
if (wt) {
|
|
1385
|
+
taskDir = wt;
|
|
1386
|
+
worktreeCreated = true;
|
|
1387
|
+
info(`Created worktree: ${taskDir}`);
|
|
1388
|
+
} else {
|
|
1389
|
+
warn("Worktree creation returned null, falling back to main dir");
|
|
1390
|
+
}
|
|
1066
1391
|
} catch (err) {
|
|
1067
1392
|
warn(`Worktree creation failed, falling back to main dir: ${err}`);
|
|
1068
1393
|
taskDir = dir;
|
|
@@ -1071,7 +1396,7 @@ ${"\u2550".repeat(60)}`);
|
|
|
1071
1396
|
try {
|
|
1072
1397
|
const result = await withLock(
|
|
1073
1398
|
"copilot-overnight",
|
|
1074
|
-
() =>
|
|
1399
|
+
() => runAgentTask(opts.agent, task.prompt, opts.steps, taskDir, opts.useWorktree)
|
|
1075
1400
|
);
|
|
1076
1401
|
const commitRef = worktreeCreated ? taskDir : dir;
|
|
1077
1402
|
const commits = mainBranch ? gitCountCommits(commitRef, mainBranch, "HEAD") : 0;
|
|
@@ -1117,10 +1442,12 @@ import { existsSync as existsSync5, copyFileSync, mkdirSync as mkdirSync3 } from
|
|
|
1117
1442
|
import { join as join6, resolve as resolve5 } from "path";
|
|
1118
1443
|
import { homedir as homedir4 } from "os";
|
|
1119
1444
|
function registerResearchCommand(program2) {
|
|
1120
|
-
program2.command("research [project]").description("Research improvements or a specific topic").option("-s, --steps <n>", "Max autopilot continues", "50").action(async (project, opts) => {
|
|
1445
|
+
program2.command("research [project]").description("Research improvements or a specific topic").option("-s, --steps <n>", "Max autopilot continues", "50").option("-a, --agent <type>", "Agent to use: copilot or claude").action(async (project, opts) => {
|
|
1121
1446
|
try {
|
|
1447
|
+
const agent = resolveAgent(opts.agent);
|
|
1122
1448
|
await researchCommand(project ?? process.cwd(), {
|
|
1123
|
-
steps: parseInt(opts.steps, 10)
|
|
1449
|
+
steps: parseInt(opts.steps, 10),
|
|
1450
|
+
agent
|
|
1124
1451
|
});
|
|
1125
1452
|
} catch (err) {
|
|
1126
1453
|
fail(`Research error: ${err instanceof Error ? err.message : err}`);
|
|
@@ -1149,15 +1476,15 @@ For each proposal, include:
|
|
|
1149
1476
|
Write RESEARCH-PROPOSALS.md in the project root.`;
|
|
1150
1477
|
}
|
|
1151
1478
|
async function researchCommand(dir, opts) {
|
|
1152
|
-
|
|
1479
|
+
assertAgent(opts.agent);
|
|
1153
1480
|
const projectDir = resolve5(dir);
|
|
1154
1481
|
const projectType = detectProjectType(projectDir);
|
|
1155
1482
|
const projectName = detectProjectName(projectDir);
|
|
1156
|
-
info(`Researching: ${CYAN}${projectName}${RESET} (${projectType})`);
|
|
1483
|
+
info(`Researching: ${CYAN}${projectName}${RESET} (${projectType}) \u2014 agent: ${opts.agent}`);
|
|
1157
1484
|
const prompt = buildResearchPrompt(projectType, projectName);
|
|
1158
1485
|
const result = await withLock(
|
|
1159
1486
|
"copilot-research",
|
|
1160
|
-
() =>
|
|
1487
|
+
() => runAgentTask(opts.agent, prompt, opts.steps, projectDir)
|
|
1161
1488
|
);
|
|
1162
1489
|
log(`Copilot exited with code ${result.exitCode}`);
|
|
1163
1490
|
const proposalsFile = join6(projectDir, "RESEARCH-PROPOSALS.md");
|
|
@@ -1178,12 +1505,12 @@ async function researchCommand(dir, opts) {
|
|
|
1178
1505
|
// src/commands/report.ts
|
|
1179
1506
|
import { resolve as resolve6 } from "path";
|
|
1180
1507
|
function registerReportCommand(program2) {
|
|
1181
|
-
program2.command("report [session-id]").description("Show what
|
|
1508
|
+
program2.command("report [session-id]").description("Show what an agent did \u2014 timeline, tools, commits, files changed").option("-l, --limit <n>", "Number of recent sessions to report (when no ID given)", "1").option("--project <dir>", "Filter sessions by project directory").option("--json", "Output raw JSON").option("-a, --agent <type>", "Filter by agent: copilot or claude").action((sessionId, opts) => {
|
|
1182
1509
|
try {
|
|
1183
1510
|
if (sessionId) {
|
|
1184
|
-
reportSingle(sessionId, opts.json ?? false);
|
|
1511
|
+
reportSingle(sessionId, opts.json ?? false, opts.agent);
|
|
1185
1512
|
} else {
|
|
1186
|
-
reportRecent(parseInt(opts.limit, 10), opts.project, opts.json ?? false);
|
|
1513
|
+
reportRecent(parseInt(opts.limit, 10), opts.project, opts.json ?? false, opts.agent);
|
|
1187
1514
|
}
|
|
1188
1515
|
} catch (err) {
|
|
1189
1516
|
fail(`Report error: ${err instanceof Error ? err.message : err}`);
|
|
@@ -1191,8 +1518,8 @@ function registerReportCommand(program2) {
|
|
|
1191
1518
|
}
|
|
1192
1519
|
});
|
|
1193
1520
|
}
|
|
1194
|
-
function reportRecent(limit, projectDir, json = false) {
|
|
1195
|
-
let sessions =
|
|
1521
|
+
function reportRecent(limit, projectDir, json = false, agentFilter) {
|
|
1522
|
+
let sessions = listAllSessions(limit * 3, agentFilter);
|
|
1196
1523
|
if (projectDir) {
|
|
1197
1524
|
const target = resolve6(projectDir);
|
|
1198
1525
|
sessions = sessions.filter((s) => s.cwd && resolve6(s.cwd) === target);
|
|
@@ -1203,11 +1530,11 @@ function reportRecent(limit, projectDir, json = false) {
|
|
|
1203
1530
|
return;
|
|
1204
1531
|
}
|
|
1205
1532
|
for (const s of sessions) {
|
|
1206
|
-
reportSingle(s.id, json);
|
|
1533
|
+
reportSingle(s.id, json, s.agent);
|
|
1207
1534
|
}
|
|
1208
1535
|
}
|
|
1209
|
-
function reportSingle(sid, json = false) {
|
|
1210
|
-
const report =
|
|
1536
|
+
function reportSingle(sid, json = false, agent) {
|
|
1537
|
+
const report = getAgentSessionReport(sid, agent);
|
|
1211
1538
|
if (!report) {
|
|
1212
1539
|
warn(`Session ${sid} not found or invalid.`);
|
|
1213
1540
|
return;
|
|
@@ -1244,7 +1571,7 @@ function renderReport(r) {
|
|
|
1244
1571
|
const projectName = r.cwd.split("/").pop() ?? r.cwd;
|
|
1245
1572
|
log("");
|
|
1246
1573
|
log(`${BOLD}\u2554${"\u2550".repeat(62)}\u2557${RESET}`);
|
|
1247
|
-
log(`${BOLD}\u2551${RESET} \u{1F4CB} Session Report: ${CYAN}${r.id.slice(0, 8)}\u2026${RESET}${" ".repeat(62 -
|
|
1574
|
+
log(`${BOLD}\u2551${RESET} \u{1F4CB} Session Report: ${CYAN}${r.id.slice(0, 8)}\u2026${RESET} (${r.agent})${" ".repeat(Math.max(0, 62 - 32 - r.id.slice(0, 8).length - r.agent.length))}${BOLD}\u2551${RESET}`);
|
|
1248
1575
|
log(`${BOLD}\u255A${"\u2550".repeat(62)}\u255D${RESET}`);
|
|
1249
1576
|
log("");
|
|
1250
1577
|
log(`${BOLD} Overview${RESET}`);
|
|
@@ -1384,20 +1711,21 @@ function buildScreen(limit) {
|
|
|
1384
1711
|
lines.push(` ${BOLD}${CYAN}\u2502${RESET} \u{1F916} ${BOLD}Copilot Agent Dashboard${RESET}${" ".repeat(Math.max(0, cols - 37 - timeStr.length))}${DIM}${timeStr}${RESET} ${BOLD}${CYAN}\u2502${RESET}`);
|
|
1385
1712
|
lines.push(` ${BOLD}${CYAN}\u2514${"\u2500".repeat(cols - 6)}\u2518${RESET}`);
|
|
1386
1713
|
lines.push("");
|
|
1387
|
-
const procs =
|
|
1714
|
+
const procs = findAgentProcesses();
|
|
1388
1715
|
lines.push(` ${BOLD}${GREEN}\u25CF Active Processes (${procs.length})${RESET}`);
|
|
1389
1716
|
lines.push(` ${"\u2500".repeat(Math.min(cols - 4, 70))}`);
|
|
1390
1717
|
if (procs.length === 0) {
|
|
1391
|
-
lines.push(` ${DIM}No
|
|
1718
|
+
lines.push(` ${DIM}No agent processes running${RESET}`);
|
|
1392
1719
|
} else {
|
|
1393
1720
|
for (const p of procs) {
|
|
1721
|
+
const agentTag = p.agent === "claude" ? `${CYAN}[claude]${RESET}` : `${GREEN}[copilot]${RESET}`;
|
|
1394
1722
|
const sid = p.sessionId ? p.sessionId.slice(0, 8) + "\u2026" : "\u2014";
|
|
1395
1723
|
const cwdShort = p.cwd ? "~/" + p.cwd.split("/").slice(-2).join("/") : "\u2014";
|
|
1396
|
-
lines.push(` ${GREEN}\u2B24${RESET} PID ${BOLD}${p.pid}${RESET} ${CYAN}${sid}${RESET} ${DIM}${cwdShort}${RESET}`);
|
|
1724
|
+
lines.push(` ${GREEN}\u2B24${RESET} ${agentTag} PID ${BOLD}${p.pid}${RESET} ${CYAN}${sid}${RESET} ${DIM}${cwdShort}${RESET}`);
|
|
1397
1725
|
}
|
|
1398
1726
|
}
|
|
1399
1727
|
lines.push("");
|
|
1400
|
-
const sessions =
|
|
1728
|
+
const sessions = listAllSessions(limit);
|
|
1401
1729
|
lines.push(` ${BOLD}${CYAN}\u25CF Recent Sessions (${sessions.length})${RESET}`);
|
|
1402
1730
|
lines.push(` ${"\u2500".repeat(Math.min(cols - 4, 70))}`);
|
|
1403
1731
|
const headerCols = [
|
|
@@ -1409,7 +1737,7 @@ function buildScreen(limit) {
|
|
|
1409
1737
|
];
|
|
1410
1738
|
lines.push(` ${DIM}${headerCols.join(" ")}${RESET}`);
|
|
1411
1739
|
for (const s of sessions) {
|
|
1412
|
-
const report =
|
|
1740
|
+
const report = getAgentSessionReport(s.id, s.agent);
|
|
1413
1741
|
const statusIcon = s.complete ? `${GREEN}\u2714 done ${RESET}` : `${YELLOW}\u23F8 stop ${RESET}`;
|
|
1414
1742
|
const premium = pad(String(s.premiumRequests), 8);
|
|
1415
1743
|
const duration = report ? pad(formatDuration2(report.durationMs), 10) : pad("\u2014", 10);
|
|
@@ -1419,7 +1747,7 @@ function buildScreen(limit) {
|
|
|
1419
1747
|
}
|
|
1420
1748
|
lines.push("");
|
|
1421
1749
|
if (sessions.length > 0) {
|
|
1422
|
-
const latest =
|
|
1750
|
+
const latest = getAgentSessionReport(sessions[0].id, sessions[0].agent);
|
|
1423
1751
|
if (latest) {
|
|
1424
1752
|
lines.push(` ${BOLD}${CYAN}\u25CF Latest Session Detail${RESET} ${DIM}${latest.id.slice(0, 8)}\u2026${RESET}`);
|
|
1425
1753
|
lines.push(` ${"\u2500".repeat(Math.min(cols - 4, 70))}`);
|
|
@@ -1545,6 +1873,8 @@ body{background:var(--bg);color:var(--text);font-family:var(--font-sans);font-si
|
|
|
1545
1873
|
.badge{font-size:10px;padding:1px 6px;border-radius:4px;font-weight:600;font-family:var(--font-mono);text-transform:uppercase}
|
|
1546
1874
|
.badge-done{background:rgba(63,185,80,.12);color:var(--green)}
|
|
1547
1875
|
.badge-stop{background:rgba(210,153,34,.12);color:var(--yellow)}
|
|
1876
|
+
.badge-claude{background:rgba(217,119,6,.15);color:#f59e0b}
|
|
1877
|
+
.badge-copilot{background:rgba(56,189,248,.12);color:#38bdf8}
|
|
1548
1878
|
.detail{padding:20px;overflow-y:auto}
|
|
1549
1879
|
.detail-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px}
|
|
1550
1880
|
.detail-title{font-size:18px;font-weight:700}
|
|
@@ -1572,8 +1902,6 @@ body{background:var(--bg);color:var(--text);font-family:var(--font-sans);font-si
|
|
|
1572
1902
|
.error-list li{padding:5px 0;font-size:12px;color:var(--red)}
|
|
1573
1903
|
.more{font-size:11px;color:var(--text3);padding:4px 0}
|
|
1574
1904
|
.empty-detail{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text3)}
|
|
1575
|
-
.htmx-settling{opacity:0}
|
|
1576
|
-
.htmx-added{opacity:0;transition:opacity .3s ease}
|
|
1577
1905
|
@media(max-width:768px){.main{grid-template-columns:1fr}.stats{grid-template-columns:repeat(2,1fr)}}
|
|
1578
1906
|
`;
|
|
1579
1907
|
var layoutHead = `<!DOCTYPE html>
|
|
@@ -1643,11 +1971,13 @@ function renderStats(sessions) {
|
|
|
1643
1971
|
).join("");
|
|
1644
1972
|
}
|
|
1645
1973
|
function renderProcesses(procs) {
|
|
1646
|
-
if (procs.length === 0) return '<div class="empty">No active
|
|
1974
|
+
if (procs.length === 0) return '<div class="empty">No active agent processes</div>';
|
|
1647
1975
|
return procs.map((p) => {
|
|
1648
1976
|
const sid = p.sessionId ? p.sessionId.slice(0, 8) + "\u2026" : "\u2014";
|
|
1977
|
+
const agentBadge = p.agent === "claude" ? '<span class="badge badge-claude">claude</span>' : '<span class="badge badge-copilot">copilot</span>';
|
|
1649
1978
|
return `<div class="proc">
|
|
1650
1979
|
<div class="proc-dot"></div>
|
|
1980
|
+
${agentBadge}
|
|
1651
1981
|
<span class="proc-pid">PID ${p.pid}</span>
|
|
1652
1982
|
<span class="proc-sid">${esc(sid)}</span>
|
|
1653
1983
|
<span class="proc-cwd">${esc(p.cwd ?? "")}</span>
|
|
@@ -1658,16 +1988,17 @@ function renderSessionList(sessions, selectedId) {
|
|
|
1658
1988
|
return sessions.map((s) => {
|
|
1659
1989
|
const proj = (s.cwd ?? "").split("/").pop() ?? "\u2014";
|
|
1660
1990
|
const isActive = s.id === selectedId;
|
|
1991
|
+
const agentBadge = s.agent === "claude" ? '<span class="badge badge-claude">claude</span>' : '<span class="badge badge-copilot">copilot</span>';
|
|
1661
1992
|
return `<div class="s-item${isActive ? " active" : ""}"
|
|
1662
1993
|
hx-get="/partial/detail/${s.id}" hx-target="#detail" hx-swap="innerHTML"
|
|
1663
1994
|
onclick="document.querySelectorAll('.s-item').forEach(e=>e.classList.remove('active'));this.classList.add('active')">
|
|
1664
1995
|
<div class="s-row">
|
|
1665
1996
|
<span class="s-icon">${s.complete ? "\u2705" : "\u23F8\uFE0F"}</span>
|
|
1666
1997
|
<div class="s-info">
|
|
1667
|
-
<div class="s-title">${esc(proj)} \u2014 ${esc(s.summary || "(no summary)")}</div>
|
|
1998
|
+
<div class="s-title">${agentBadge} ${esc(proj)} \u2014 ${esc(s.summary || "(no summary)")}</div>
|
|
1668
1999
|
<div class="s-meta">
|
|
1669
2000
|
<span>${fmtDur(s.durationMs)}</span>
|
|
1670
|
-
<span>${fmt(s.premiumRequests)
|
|
2001
|
+
<span>${s.agent === "claude" ? "" : fmt(s.premiumRequests) + " premium"}</span>
|
|
1671
2002
|
<span>${fmtAgo(s.endTime)}</span>
|
|
1672
2003
|
<span class="badge ${s.complete ? "badge-done" : "badge-stop"}">${s.complete ? "done" : "stopped"}</span>
|
|
1673
2004
|
</div>
|
|
@@ -1682,8 +2013,9 @@ function renderDetail(s) {
|
|
|
1682
2013
|
const toolEntries = Object.entries(s.toolUsage ?? {}).sort((a, b) => b[1] - a[1]);
|
|
1683
2014
|
const maxTool = toolEntries[0]?.[1] ?? 1;
|
|
1684
2015
|
let html = "";
|
|
2016
|
+
const agentBadge = s.agent === "claude" ? '<span class="badge badge-claude" style="margin-left:8px">claude</span>' : '<span class="badge badge-copilot" style="margin-left:8px">copilot</span>';
|
|
1685
2017
|
html += `<div class="detail-head">
|
|
1686
|
-
<div class="detail-title">${esc(proj)}</div>
|
|
2018
|
+
<div class="detail-title">${esc(proj)}${agentBadge}</div>
|
|
1687
2019
|
<div class="detail-id">${s.id}</div>
|
|
1688
2020
|
</div>`;
|
|
1689
2021
|
html += `<div class="detail-time">${fmtTime(s.startTime)} \u2192 ${fmtTime(s.endTime)}</div>`;
|
|
@@ -1758,41 +2090,51 @@ function registerWebCommand(program2) {
|
|
|
1758
2090
|
});
|
|
1759
2091
|
}
|
|
1760
2092
|
function getData() {
|
|
1761
|
-
const sessions =
|
|
1762
|
-
const reports = sessions.map((s) =>
|
|
1763
|
-
const processes =
|
|
2093
|
+
const sessions = listAllSessions(20);
|
|
2094
|
+
const reports = sessions.map((s) => getAgentSessionReport(s.id, s.agent)).filter((r) => r !== null);
|
|
2095
|
+
const processes = findAgentProcesses();
|
|
1764
2096
|
return { sessions: reports, processes };
|
|
1765
2097
|
}
|
|
2098
|
+
function simpleHash(s) {
|
|
2099
|
+
let h = 0;
|
|
2100
|
+
for (let i = 0; i < s.length; i++) {
|
|
2101
|
+
h = (h << 5) - h + s.charCodeAt(i) | 0;
|
|
2102
|
+
}
|
|
2103
|
+
return h.toString(36);
|
|
2104
|
+
}
|
|
1766
2105
|
function startWebServer(port, autoOpen) {
|
|
1767
2106
|
const app = new Hono();
|
|
1768
2107
|
app.get("/api/sessions", (c) => c.json(getData()));
|
|
1769
2108
|
app.get("/api/session/:id", (c) => {
|
|
1770
|
-
const report =
|
|
2109
|
+
const report = getAgentSessionReport(c.req.param("id"));
|
|
1771
2110
|
if (!report) return c.json({ error: "Not found" }, 404);
|
|
1772
2111
|
return c.json(report);
|
|
1773
2112
|
});
|
|
1774
2113
|
app.get("/events", (c) => {
|
|
1775
2114
|
return streamSSE(c, async (stream) => {
|
|
2115
|
+
let prevStatsHash = "";
|
|
2116
|
+
let prevProcsHash = "";
|
|
1776
2117
|
while (true) {
|
|
1777
2118
|
const { sessions, processes } = getData();
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
event: "
|
|
1784
|
-
|
|
1785
|
-
}
|
|
1786
|
-
|
|
1787
|
-
event: "
|
|
1788
|
-
data: String(processes.length)
|
|
1789
|
-
|
|
2119
|
+
const statsHtml = renderStats(sessions);
|
|
2120
|
+
const procsHtml = renderProcesses(processes);
|
|
2121
|
+
const statsHash = simpleHash(statsHtml);
|
|
2122
|
+
const procsHash = simpleHash(procsHtml);
|
|
2123
|
+
if (statsHash !== prevStatsHash) {
|
|
2124
|
+
await stream.writeSSE({ event: "stats", data: statsHtml });
|
|
2125
|
+
prevStatsHash = statsHash;
|
|
2126
|
+
}
|
|
2127
|
+
if (procsHash !== prevProcsHash) {
|
|
2128
|
+
await stream.writeSSE({ event: "procs", data: procsHtml });
|
|
2129
|
+
await stream.writeSSE({ event: "proc-count", data: String(processes.length) });
|
|
2130
|
+
prevProcsHash = procsHash;
|
|
2131
|
+
}
|
|
1790
2132
|
await stream.sleep(5e3);
|
|
1791
2133
|
}
|
|
1792
2134
|
});
|
|
1793
2135
|
});
|
|
1794
2136
|
app.get("/partial/detail/:id", (c) => {
|
|
1795
|
-
const report =
|
|
2137
|
+
const report = getAgentSessionReport(c.req.param("id"));
|
|
1796
2138
|
if (!report) return c.html('<div class="empty-detail">Session not found</div>');
|
|
1797
2139
|
return c.html(renderDetail(report));
|
|
1798
2140
|
});
|
|
@@ -1815,17 +2157,17 @@ function startWebServer(port, autoOpen) {
|
|
|
1815
2157
|
|
|
1816
2158
|
<div class="stats"
|
|
1817
2159
|
sse-swap="stats"
|
|
1818
|
-
hx-swap="innerHTML">
|
|
2160
|
+
hx-swap="innerHTML settle:0s swap:0s">
|
|
1819
2161
|
${renderStats(sessions)}
|
|
1820
2162
|
</div>
|
|
1821
2163
|
|
|
1822
2164
|
<div class="procs">
|
|
1823
2165
|
<div class="procs-header">
|
|
1824
|
-
\u2B24 Active Processes <span class="count" sse-swap="proc-count" hx-swap="innerHTML">${processes.length}</span>
|
|
2166
|
+
\u2B24 Active Processes <span class="count" sse-swap="proc-count" hx-swap="innerHTML settle:0s swap:0s">${processes.length}</span>
|
|
1825
2167
|
</div>
|
|
1826
2168
|
<div class="procs-body"
|
|
1827
2169
|
sse-swap="procs"
|
|
1828
|
-
hx-swap="innerHTML">
|
|
2170
|
+
hx-swap="innerHTML settle:0s swap:0s">
|
|
1829
2171
|
${renderProcesses(processes)}
|
|
1830
2172
|
</div>
|
|
1831
2173
|
</div>
|
|
@@ -1869,7 +2211,7 @@ ${layoutFoot}`);
|
|
|
1869
2211
|
|
|
1870
2212
|
// src/index.ts
|
|
1871
2213
|
var program = new Command();
|
|
1872
|
-
program.name("copilot-agent").version("0.
|
|
2214
|
+
program.name("copilot-agent").version("0.8.0").description("Autonomous AI agent manager \u2014 auto-resume, task discovery, overnight runs. Supports GitHub Copilot CLI + Claude Code.");
|
|
1873
2215
|
registerStatusCommand(program);
|
|
1874
2216
|
registerWatchCommand(program);
|
|
1875
2217
|
registerRunCommand(program);
|