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 +502 -162
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
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
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
738
|
+
function showActive(agentFilter) {
|
|
739
|
+
const procs = findAgentProcesses(agentFilter);
|
|
475
740
|
if (procs.length === 0) {
|
|
476
|
-
log(`${DIM}No active
|
|
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(
|
|
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
|
-
`${
|
|
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 =
|
|
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(
|
|
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(
|
|
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",
|
|
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(
|
|
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
|
-
|
|
843
|
+
assertAgent(opts.agent);
|
|
537
844
|
if (!sid) {
|
|
538
|
-
|
|
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
|
|
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
|
|
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
|
|
951
|
+
return basename2(resolve3(dir));
|
|
643
952
|
}
|
|
644
953
|
function detectMainBranch(dir) {
|
|
645
954
|
try {
|
|
646
|
-
const ref =
|
|
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 =
|
|
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
|
|
1130
|
+
import { execSync as execSync5 } from "child_process";
|
|
822
1131
|
function gitExec(dir, cmd) {
|
|
823
1132
|
try {
|
|
824
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
939
|
-
|
|
940
|
-
|
|
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
|
-
() =>
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
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
|
-
() =>
|
|
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
|
-
|
|
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
|
-
() =>
|
|
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
|
|
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 =
|
|
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 =
|
|
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 -
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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)
|
|
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 =
|
|
1762
|
-
const reports = sessions.map((s) =>
|
|
1763
|
-
const processes =
|
|
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 =
|
|
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
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
event: "
|
|
1784
|
-
|
|
1785
|
-
}
|
|
1786
|
-
|
|
1787
|
-
event: "
|
|
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 =
|
|
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.
|
|
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);
|