copilot-agent 0.7.0 → 0.8.0

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