copilot-agent 1.0.0 → 1.1.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 +574 -173
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -31,8 +31,8 @@ function listSessions(limit = 20) {
|
|
|
31
31
|
const dirPath = join(SESSION_DIR, entry.name);
|
|
32
32
|
if (!existsSync(join(dirPath, "events.jsonl"))) continue;
|
|
33
33
|
try {
|
|
34
|
-
const
|
|
35
|
-
dirs.push({ id: entry.name, dir: dirPath, mtime:
|
|
34
|
+
const stat2 = statSync(dirPath);
|
|
35
|
+
dirs.push({ id: entry.name, dir: dirPath, mtime: stat2.mtimeMs });
|
|
36
36
|
} catch {
|
|
37
37
|
}
|
|
38
38
|
}
|
|
@@ -55,9 +55,9 @@ function getLatestSessionId() {
|
|
|
55
55
|
let latest = null;
|
|
56
56
|
for (const entry of entries) {
|
|
57
57
|
try {
|
|
58
|
-
const
|
|
59
|
-
if (!latest ||
|
|
60
|
-
latest = { id: entry.name, mtime:
|
|
58
|
+
const stat2 = statSync(join(SESSION_DIR, entry.name));
|
|
59
|
+
if (!latest || stat2.mtimeMs > latest.mtime) {
|
|
60
|
+
latest = { id: entry.name, mtime: stat2.mtimeMs };
|
|
61
61
|
}
|
|
62
62
|
} catch {
|
|
63
63
|
}
|
|
@@ -244,12 +244,12 @@ function listClaudeSessions(limit = 20) {
|
|
|
244
244
|
const filePath = join(projPath, file);
|
|
245
245
|
const sid = basename(file, ".jsonl");
|
|
246
246
|
try {
|
|
247
|
-
const
|
|
247
|
+
const stat2 = statSync(filePath);
|
|
248
248
|
const { lastEvent, complete, summary } = parseClaudeSessionMeta(filePath);
|
|
249
249
|
sessions.push({
|
|
250
250
|
id: sid,
|
|
251
251
|
dir: projPath,
|
|
252
|
-
mtime:
|
|
252
|
+
mtime: stat2.mtimeMs,
|
|
253
253
|
lastEvent,
|
|
254
254
|
premiumRequests: 0,
|
|
255
255
|
summary,
|
|
@@ -319,9 +319,9 @@ function getLatestClaudeSessionId(projectDir) {
|
|
|
319
319
|
try {
|
|
320
320
|
const files = readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
|
|
321
321
|
for (const file of files) {
|
|
322
|
-
const
|
|
323
|
-
if (!latest ||
|
|
324
|
-
latest = { id: basename(file, ".jsonl"), mtime:
|
|
322
|
+
const stat2 = statSync(join(dir, file));
|
|
323
|
+
if (!latest || stat2.mtimeMs > latest.mtime) {
|
|
324
|
+
latest = { id: basename(file, ".jsonl"), mtime: stat2.mtimeMs };
|
|
325
325
|
}
|
|
326
326
|
}
|
|
327
327
|
} catch {
|
|
@@ -1658,6 +1658,427 @@ function renderReport(r) {
|
|
|
1658
1658
|
log("");
|
|
1659
1659
|
}
|
|
1660
1660
|
|
|
1661
|
+
// src/lib/reactive.ts
|
|
1662
|
+
import { EventEmitter } from "events";
|
|
1663
|
+
import { readFile, readdir, stat } from "fs/promises";
|
|
1664
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1665
|
+
import { exec } from "child_process";
|
|
1666
|
+
import { join as join7, basename as basename3 } from "path";
|
|
1667
|
+
import { homedir as homedir5 } from "os";
|
|
1668
|
+
var SESSION_DIR2 = join7(homedir5(), ".copilot", "session-state");
|
|
1669
|
+
var CLAUDE_PROJECTS_DIR2 = join7(homedir5(), ".claude", "projects");
|
|
1670
|
+
function execAsync(cmd) {
|
|
1671
|
+
return new Promise((resolve8, reject) => {
|
|
1672
|
+
exec(cmd, { encoding: "utf-8", maxBuffer: 512 * 1024 }, (err, stdout) => {
|
|
1673
|
+
if (err) reject(err);
|
|
1674
|
+
else resolve8(stdout);
|
|
1675
|
+
});
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
function parseSimpleYaml2(content) {
|
|
1679
|
+
const result = {};
|
|
1680
|
+
for (const line of content.split("\n")) {
|
|
1681
|
+
const idx = line.indexOf(": ");
|
|
1682
|
+
if (idx === -1) continue;
|
|
1683
|
+
const key = line.substring(0, idx).trim();
|
|
1684
|
+
const value = line.substring(idx + 2).trim();
|
|
1685
|
+
if (key) result[key] = value;
|
|
1686
|
+
}
|
|
1687
|
+
return result;
|
|
1688
|
+
}
|
|
1689
|
+
async function parseJsonlChunked(content, handler) {
|
|
1690
|
+
const lines = content.trimEnd().split("\n");
|
|
1691
|
+
const CHUNK = 200;
|
|
1692
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1693
|
+
try {
|
|
1694
|
+
handler(JSON.parse(lines[i]));
|
|
1695
|
+
} catch {
|
|
1696
|
+
}
|
|
1697
|
+
if (i % CHUNK === 0 && i > 0) {
|
|
1698
|
+
await new Promise((r) => setImmediate(r));
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
async function readWorkspaceAsync(sid) {
|
|
1703
|
+
const wsPath = join7(SESSION_DIR2, sid, "workspace.yaml");
|
|
1704
|
+
try {
|
|
1705
|
+
const content = await readFile(wsPath, "utf-8");
|
|
1706
|
+
return parseSimpleYaml2(content);
|
|
1707
|
+
} catch {
|
|
1708
|
+
return {};
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
async function listCopilotSessionsAsync(limit) {
|
|
1712
|
+
if (!existsSync6(SESSION_DIR2)) return [];
|
|
1713
|
+
try {
|
|
1714
|
+
const entries = await readdir(SESSION_DIR2, { withFileTypes: true });
|
|
1715
|
+
const dirs = [];
|
|
1716
|
+
for (const entry of entries) {
|
|
1717
|
+
if (!entry.isDirectory()) continue;
|
|
1718
|
+
const dirPath = join7(SESSION_DIR2, entry.name);
|
|
1719
|
+
const eventsPath = join7(dirPath, "events.jsonl");
|
|
1720
|
+
if (!existsSync6(eventsPath)) continue;
|
|
1721
|
+
try {
|
|
1722
|
+
const s = await stat(dirPath);
|
|
1723
|
+
dirs.push({ id: entry.name, dir: dirPath, mtime: s.mtimeMs });
|
|
1724
|
+
} catch {
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
dirs.sort((a, b) => b.mtime - a.mtime);
|
|
1728
|
+
const top = dirs.slice(0, limit);
|
|
1729
|
+
return Promise.all(top.map(async (s) => {
|
|
1730
|
+
const ws = await readWorkspaceAsync(s.id);
|
|
1731
|
+
let premiumRequests = 0;
|
|
1732
|
+
let complete = false;
|
|
1733
|
+
let lastEvent = "unknown";
|
|
1734
|
+
try {
|
|
1735
|
+
const evPath = join7(SESSION_DIR2, s.id, "events.jsonl");
|
|
1736
|
+
const fileStat = await stat(evPath);
|
|
1737
|
+
const fullContent = await readFile(evPath, "utf-8");
|
|
1738
|
+
const lines = fullContent.trimEnd().split("\n");
|
|
1739
|
+
for (let i = lines.length - 1; i >= Math.max(0, lines.length - 10); i--) {
|
|
1740
|
+
try {
|
|
1741
|
+
const ev = JSON.parse(lines[i]);
|
|
1742
|
+
if (i === lines.length - 1) lastEvent = ev.type ?? "unknown";
|
|
1743
|
+
if (ev.type === "session.shutdown" && ev.data?.totalPremiumRequests != null) {
|
|
1744
|
+
premiumRequests = ev.data.totalPremiumRequests;
|
|
1745
|
+
}
|
|
1746
|
+
if (ev.type === "session.task_complete") complete = true;
|
|
1747
|
+
} catch {
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
if (!complete && fullContent.includes('"session.task_complete"')) complete = true;
|
|
1751
|
+
} catch {
|
|
1752
|
+
}
|
|
1753
|
+
return {
|
|
1754
|
+
id: s.id,
|
|
1755
|
+
dir: s.dir,
|
|
1756
|
+
mtime: s.mtime,
|
|
1757
|
+
lastEvent,
|
|
1758
|
+
premiumRequests,
|
|
1759
|
+
summary: ws.summary ?? "",
|
|
1760
|
+
cwd: ws.cwd ?? "",
|
|
1761
|
+
complete,
|
|
1762
|
+
agent: "copilot"
|
|
1763
|
+
};
|
|
1764
|
+
}));
|
|
1765
|
+
} catch {
|
|
1766
|
+
return [];
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
async function listClaudeSessionsAsync(limit) {
|
|
1770
|
+
if (!existsSync6(CLAUDE_PROJECTS_DIR2)) return [];
|
|
1771
|
+
const sessions = [];
|
|
1772
|
+
try {
|
|
1773
|
+
const projectDirs = await readdir(CLAUDE_PROJECTS_DIR2, { withFileTypes: true });
|
|
1774
|
+
for (const projDir of projectDirs.filter((d) => d.isDirectory())) {
|
|
1775
|
+
const projPath = join7(CLAUDE_PROJECTS_DIR2, projDir.name);
|
|
1776
|
+
const cwd = projDir.name.replace(/^-/, "/").replace(/-/g, "/");
|
|
1777
|
+
const files = (await readdir(projPath)).filter((f) => f.endsWith(".jsonl"));
|
|
1778
|
+
for (const file of files) {
|
|
1779
|
+
const filePath = join7(projPath, file);
|
|
1780
|
+
const sid = basename3(file, ".jsonl");
|
|
1781
|
+
try {
|
|
1782
|
+
const s = await stat(filePath);
|
|
1783
|
+
sessions.push({
|
|
1784
|
+
id: sid,
|
|
1785
|
+
dir: projPath,
|
|
1786
|
+
mtime: s.mtimeMs,
|
|
1787
|
+
lastEvent: "unknown",
|
|
1788
|
+
premiumRequests: 0,
|
|
1789
|
+
summary: "",
|
|
1790
|
+
cwd,
|
|
1791
|
+
complete: false,
|
|
1792
|
+
agent: "claude"
|
|
1793
|
+
});
|
|
1794
|
+
} catch {
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
} catch {
|
|
1799
|
+
}
|
|
1800
|
+
sessions.sort((a, b) => b.mtime - a.mtime);
|
|
1801
|
+
return sessions.slice(0, limit);
|
|
1802
|
+
}
|
|
1803
|
+
async function findProcessesAsync() {
|
|
1804
|
+
try {
|
|
1805
|
+
const output = await execAsync("ps -eo pid,command");
|
|
1806
|
+
const results = [];
|
|
1807
|
+
const myPid = process.pid;
|
|
1808
|
+
const parentPid = process.ppid;
|
|
1809
|
+
for (const line of output.split("\n")) {
|
|
1810
|
+
const trimmed = line.trim();
|
|
1811
|
+
if (!trimmed) continue;
|
|
1812
|
+
const isCopilot = (trimmed.includes("copilot") || trimmed.includes("@githubnext/copilot")) && !trimmed.includes("copilot-agent") && !trimmed.includes("copilot-api");
|
|
1813
|
+
const isClaude = trimmed.includes("claude") && !trimmed.includes("claude-code") && !trimmed.includes("copilot-agent");
|
|
1814
|
+
if (!isCopilot && !isClaude) continue;
|
|
1815
|
+
if (trimmed.includes("ps -eo") || trimmed.includes("grep")) continue;
|
|
1816
|
+
const agent = isClaude ? "claude" : "copilot";
|
|
1817
|
+
const match = trimmed.match(/^(\d+)\s+(.+)$/);
|
|
1818
|
+
if (!match) continue;
|
|
1819
|
+
const pid = parseInt(match[1], 10);
|
|
1820
|
+
if (pid === myPid || pid === parentPid) continue;
|
|
1821
|
+
const cmd = match[2];
|
|
1822
|
+
const sidMatch = agent === "copilot" ? cmd.match(/resume[= ]+([a-f0-9-]{36})/) : cmd.match(/(?:--resume|--session-id)[= ]+([a-f0-9-]{36})/);
|
|
1823
|
+
results.push({ pid, command: cmd, sessionId: sidMatch?.[1], agent });
|
|
1824
|
+
}
|
|
1825
|
+
return results;
|
|
1826
|
+
} catch {
|
|
1827
|
+
return [];
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
async function loadReportAsync(sid, agent) {
|
|
1831
|
+
if (agent === "claude") return loadClaudeReportAsync(sid);
|
|
1832
|
+
return loadCopilotReportAsync(sid);
|
|
1833
|
+
}
|
|
1834
|
+
async function loadCopilotReportAsync(sid) {
|
|
1835
|
+
const eventsPath = join7(SESSION_DIR2, sid, "events.jsonl");
|
|
1836
|
+
if (!existsSync6(eventsPath)) return null;
|
|
1837
|
+
const ws = await readWorkspaceAsync(sid);
|
|
1838
|
+
let content;
|
|
1839
|
+
try {
|
|
1840
|
+
content = await readFile(eventsPath, "utf-8");
|
|
1841
|
+
} catch {
|
|
1842
|
+
return null;
|
|
1843
|
+
}
|
|
1844
|
+
const report = {
|
|
1845
|
+
id: sid,
|
|
1846
|
+
cwd: ws.cwd ?? "",
|
|
1847
|
+
summary: ws.summary ?? "",
|
|
1848
|
+
startTime: "",
|
|
1849
|
+
endTime: "",
|
|
1850
|
+
durationMs: 0,
|
|
1851
|
+
complete: false,
|
|
1852
|
+
userMessages: 0,
|
|
1853
|
+
assistantTurns: 0,
|
|
1854
|
+
outputTokens: 0,
|
|
1855
|
+
premiumRequests: 0,
|
|
1856
|
+
toolUsage: {},
|
|
1857
|
+
gitCommits: [],
|
|
1858
|
+
filesCreated: [],
|
|
1859
|
+
filesEdited: [],
|
|
1860
|
+
errors: [],
|
|
1861
|
+
taskCompletions: [],
|
|
1862
|
+
agent: "copilot"
|
|
1863
|
+
};
|
|
1864
|
+
await parseJsonlChunked(content, (event) => {
|
|
1865
|
+
const type = event.type;
|
|
1866
|
+
const ts = event.timestamp;
|
|
1867
|
+
const data = event.data ?? {};
|
|
1868
|
+
if (ts && !report.startTime) report.startTime = ts;
|
|
1869
|
+
if (ts) report.endTime = ts;
|
|
1870
|
+
switch (type) {
|
|
1871
|
+
case "user.message":
|
|
1872
|
+
report.userMessages++;
|
|
1873
|
+
break;
|
|
1874
|
+
case "assistant.message":
|
|
1875
|
+
report.assistantTurns++;
|
|
1876
|
+
report.outputTokens += data.outputTokens ?? 0;
|
|
1877
|
+
break;
|
|
1878
|
+
case "tool.execution_start": {
|
|
1879
|
+
const toolName = data.toolName;
|
|
1880
|
+
if (toolName) report.toolUsage[toolName] = (report.toolUsage[toolName] ?? 0) + 1;
|
|
1881
|
+
if (toolName === "bash") {
|
|
1882
|
+
const args = data.arguments;
|
|
1883
|
+
const cmd = args?.command ?? "";
|
|
1884
|
+
if (cmd.includes("git") && cmd.includes("commit") && cmd.includes("-m")) {
|
|
1885
|
+
const msgMatch = cmd.match(/-m\s+"([^"]{1,120})/);
|
|
1886
|
+
if (msgMatch) report.gitCommits.push(msgMatch[1]);
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
if (toolName === "create") {
|
|
1890
|
+
const args = data.arguments;
|
|
1891
|
+
if (args?.path) report.filesCreated.push(args.path);
|
|
1892
|
+
}
|
|
1893
|
+
if (toolName === "edit") {
|
|
1894
|
+
const args = data.arguments;
|
|
1895
|
+
if (args?.path && !report.filesEdited.includes(args.path)) report.filesEdited.push(args.path);
|
|
1896
|
+
}
|
|
1897
|
+
break;
|
|
1898
|
+
}
|
|
1899
|
+
case "session.task_complete": {
|
|
1900
|
+
report.taskCompletions.push(data.summary ?? "(task completed)");
|
|
1901
|
+
report.complete = true;
|
|
1902
|
+
break;
|
|
1903
|
+
}
|
|
1904
|
+
case "session.error": {
|
|
1905
|
+
const msg = data.message;
|
|
1906
|
+
if (msg) report.errors.push(msg);
|
|
1907
|
+
break;
|
|
1908
|
+
}
|
|
1909
|
+
case "session.shutdown": {
|
|
1910
|
+
const prem = data.totalPremiumRequests;
|
|
1911
|
+
if (prem != null) report.premiumRequests = prem;
|
|
1912
|
+
break;
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
});
|
|
1916
|
+
if (report.startTime && report.endTime) {
|
|
1917
|
+
report.durationMs = new Date(report.endTime).getTime() - new Date(report.startTime).getTime();
|
|
1918
|
+
}
|
|
1919
|
+
return report;
|
|
1920
|
+
}
|
|
1921
|
+
async function loadClaudeReportAsync(sid) {
|
|
1922
|
+
if (!existsSync6(CLAUDE_PROJECTS_DIR2)) return null;
|
|
1923
|
+
let filePath = null;
|
|
1924
|
+
let cwd = "";
|
|
1925
|
+
try {
|
|
1926
|
+
const projectDirs = await readdir(CLAUDE_PROJECTS_DIR2, { withFileTypes: true });
|
|
1927
|
+
for (const projDir of projectDirs.filter((d) => d.isDirectory())) {
|
|
1928
|
+
const candidate = join7(CLAUDE_PROJECTS_DIR2, projDir.name, `${sid}.jsonl`);
|
|
1929
|
+
if (existsSync6(candidate)) {
|
|
1930
|
+
filePath = candidate;
|
|
1931
|
+
cwd = projDir.name.replace(/^-/, "/").replace(/-/g, "/");
|
|
1932
|
+
break;
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
} catch {
|
|
1936
|
+
}
|
|
1937
|
+
if (!filePath) return null;
|
|
1938
|
+
let content;
|
|
1939
|
+
try {
|
|
1940
|
+
content = await readFile(filePath, "utf-8");
|
|
1941
|
+
} catch {
|
|
1942
|
+
return null;
|
|
1943
|
+
}
|
|
1944
|
+
const report = {
|
|
1945
|
+
id: sid,
|
|
1946
|
+
cwd,
|
|
1947
|
+
summary: "",
|
|
1948
|
+
startTime: "",
|
|
1949
|
+
endTime: "",
|
|
1950
|
+
durationMs: 0,
|
|
1951
|
+
complete: false,
|
|
1952
|
+
userMessages: 0,
|
|
1953
|
+
assistantTurns: 0,
|
|
1954
|
+
outputTokens: 0,
|
|
1955
|
+
premiumRequests: 0,
|
|
1956
|
+
toolUsage: {},
|
|
1957
|
+
gitCommits: [],
|
|
1958
|
+
filesCreated: [],
|
|
1959
|
+
filesEdited: [],
|
|
1960
|
+
errors: [],
|
|
1961
|
+
taskCompletions: [],
|
|
1962
|
+
agent: "claude"
|
|
1963
|
+
};
|
|
1964
|
+
await parseJsonlChunked(content, (event) => {
|
|
1965
|
+
const type = event.type ?? event.role ?? "";
|
|
1966
|
+
const ts = event.timestamp;
|
|
1967
|
+
if (ts && !report.startTime) report.startTime = ts;
|
|
1968
|
+
if (ts) report.endTime = ts;
|
|
1969
|
+
if (type === "human" || type === "user") report.userMessages++;
|
|
1970
|
+
if (type === "assistant") {
|
|
1971
|
+
report.assistantTurns++;
|
|
1972
|
+
if (event.stop_reason === "end_turn") report.complete = true;
|
|
1973
|
+
}
|
|
1974
|
+
if (type === "result") report.complete = true;
|
|
1975
|
+
if (type === "tool_use" || type === "tool_result") {
|
|
1976
|
+
const name = event.name ?? "tool";
|
|
1977
|
+
report.toolUsage[name] = (report.toolUsage[name] ?? 0) + 1;
|
|
1978
|
+
}
|
|
1979
|
+
});
|
|
1980
|
+
if (report.startTime && report.endTime) {
|
|
1981
|
+
report.durationMs = new Date(report.endTime).getTime() - new Date(report.startTime).getTime();
|
|
1982
|
+
}
|
|
1983
|
+
return report;
|
|
1984
|
+
}
|
|
1985
|
+
var DashboardStore = class extends EventEmitter {
|
|
1986
|
+
sessions = [];
|
|
1987
|
+
processes = [];
|
|
1988
|
+
details = /* @__PURE__ */ new Map();
|
|
1989
|
+
limit;
|
|
1990
|
+
sessionsTimer = null;
|
|
1991
|
+
procsTimer = null;
|
|
1992
|
+
detailPending = null;
|
|
1993
|
+
detailDebounce = null;
|
|
1994
|
+
refreshing = { sessions: false, procs: false, detail: false };
|
|
1995
|
+
constructor(limit = 20) {
|
|
1996
|
+
super();
|
|
1997
|
+
this.limit = limit;
|
|
1998
|
+
}
|
|
1999
|
+
// ── Start auto-refresh loops ───────────────────────────────────
|
|
2000
|
+
start(sessionIntervalMs = 2e3, procsIntervalMs = 3e3) {
|
|
2001
|
+
this.refreshSessions();
|
|
2002
|
+
this.refreshProcesses();
|
|
2003
|
+
this.sessionsTimer = setInterval(() => this.refreshSessions(), sessionIntervalMs);
|
|
2004
|
+
this.procsTimer = setInterval(() => this.refreshProcesses(), procsIntervalMs);
|
|
2005
|
+
}
|
|
2006
|
+
stop() {
|
|
2007
|
+
if (this.sessionsTimer) clearInterval(this.sessionsTimer);
|
|
2008
|
+
if (this.procsTimer) clearInterval(this.procsTimer);
|
|
2009
|
+
if (this.detailDebounce) clearTimeout(this.detailDebounce);
|
|
2010
|
+
}
|
|
2011
|
+
// ── Async session refresh (non-blocking) ───────────────────────
|
|
2012
|
+
async refreshSessions() {
|
|
2013
|
+
if (this.refreshing.sessions) return;
|
|
2014
|
+
this.refreshing.sessions = true;
|
|
2015
|
+
try {
|
|
2016
|
+
const [copilot, claude] = await Promise.all([
|
|
2017
|
+
listCopilotSessionsAsync(this.limit),
|
|
2018
|
+
listClaudeSessionsAsync(this.limit)
|
|
2019
|
+
]);
|
|
2020
|
+
const merged = [...copilot, ...claude].sort((a, b) => b.mtime - a.mtime).slice(0, this.limit);
|
|
2021
|
+
this.sessions = merged;
|
|
2022
|
+
this.emit("sessions");
|
|
2023
|
+
} catch {
|
|
2024
|
+
}
|
|
2025
|
+
this.refreshing.sessions = false;
|
|
2026
|
+
}
|
|
2027
|
+
// ── Async process refresh (non-blocking) ───────────────────────
|
|
2028
|
+
async refreshProcesses() {
|
|
2029
|
+
if (this.refreshing.procs) return;
|
|
2030
|
+
this.refreshing.procs = true;
|
|
2031
|
+
try {
|
|
2032
|
+
this.processes = await findProcessesAsync();
|
|
2033
|
+
this.emit("processes");
|
|
2034
|
+
} catch {
|
|
2035
|
+
}
|
|
2036
|
+
this.refreshing.procs = false;
|
|
2037
|
+
}
|
|
2038
|
+
// ── Request detail for a session (debounced 150ms) ─────────────
|
|
2039
|
+
requestDetail(sessionId, agent, force = false) {
|
|
2040
|
+
if (!force && this.details.has(sessionId)) {
|
|
2041
|
+
this.emit("detail", sessionId);
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2044
|
+
this.detailPending = sessionId;
|
|
2045
|
+
if (this.detailDebounce) clearTimeout(this.detailDebounce);
|
|
2046
|
+
this.detailDebounce = setTimeout(() => {
|
|
2047
|
+
this.detailDebounce = null;
|
|
2048
|
+
if (this.detailPending !== sessionId) return;
|
|
2049
|
+
this.loadDetail(sessionId, agent);
|
|
2050
|
+
}, 150);
|
|
2051
|
+
}
|
|
2052
|
+
// ── Force-load detail immediately (Enter key) ──────────────────
|
|
2053
|
+
requestDetailNow(sessionId, agent) {
|
|
2054
|
+
if (this.detailDebounce) clearTimeout(this.detailDebounce);
|
|
2055
|
+
this.detailPending = sessionId;
|
|
2056
|
+
this.loadDetail(sessionId, agent);
|
|
2057
|
+
}
|
|
2058
|
+
// ── Async detail loading (non-blocking JSONL parse) ────────────
|
|
2059
|
+
async loadDetail(sessionId, agent) {
|
|
2060
|
+
if (this.refreshing.detail) return;
|
|
2061
|
+
this.refreshing.detail = true;
|
|
2062
|
+
try {
|
|
2063
|
+
const report = await loadReportAsync(sessionId, agent);
|
|
2064
|
+
this.details.set(sessionId, report);
|
|
2065
|
+
if (this.details.size > 15) {
|
|
2066
|
+
const first = this.details.keys().next().value;
|
|
2067
|
+
if (first && first !== sessionId) this.details.delete(first);
|
|
2068
|
+
}
|
|
2069
|
+
this.emit("detail", sessionId);
|
|
2070
|
+
} catch {
|
|
2071
|
+
}
|
|
2072
|
+
this.refreshing.detail = false;
|
|
2073
|
+
}
|
|
2074
|
+
// ── Force full refresh (r key) ─────────────────────────────────
|
|
2075
|
+
forceRefresh() {
|
|
2076
|
+
this.details.clear();
|
|
2077
|
+
this.refreshSessions();
|
|
2078
|
+
this.refreshProcesses();
|
|
2079
|
+
}
|
|
2080
|
+
};
|
|
2081
|
+
|
|
1661
2082
|
// src/tui/widgets.ts
|
|
1662
2083
|
function agentLabel(agent) {
|
|
1663
2084
|
return agent === "claude" ? "{yellow-fg}claude{/}" : "{cyan-fg}copilot{/}";
|
|
@@ -1779,53 +2200,13 @@ async function loadBlessed() {
|
|
|
1779
2200
|
blessed = mod.default || mod;
|
|
1780
2201
|
}
|
|
1781
2202
|
function registerDashboardCommand(program2) {
|
|
1782
|
-
program2.command("dashboard").alias("tui").description("Real-time terminal dashboard for copilot sessions (htop-style)").option("-r, --refresh <n>", "
|
|
2203
|
+
program2.command("dashboard").alias("tui").description("Real-time terminal dashboard for copilot sessions (htop-style)").option("-r, --refresh <n>", "Session refresh interval in seconds", "2").option("-l, --limit <n>", "Number of sessions to show", "20").action(async (opts) => {
|
|
1783
2204
|
await loadBlessed();
|
|
1784
|
-
|
|
2205
|
+
runReactiveDashboard(parseInt(opts.refresh, 10), parseInt(opts.limit, 10));
|
|
1785
2206
|
});
|
|
1786
2207
|
}
|
|
1787
|
-
function
|
|
1788
|
-
|
|
1789
|
-
}
|
|
1790
|
-
function cacheSessions(cache, limit) {
|
|
1791
|
-
const now = Date.now();
|
|
1792
|
-
if (now - cache.sessionsTs > 2e3) {
|
|
1793
|
-
try {
|
|
1794
|
-
cache.sessions = listAllSessions(limit);
|
|
1795
|
-
} catch {
|
|
1796
|
-
}
|
|
1797
|
-
cache.sessionsTs = now;
|
|
1798
|
-
}
|
|
1799
|
-
return cache.sessions;
|
|
1800
|
-
}
|
|
1801
|
-
function cacheProcs(cache) {
|
|
1802
|
-
const now = Date.now();
|
|
1803
|
-
if (now - cache.procsTs > 3e3) {
|
|
1804
|
-
try {
|
|
1805
|
-
cache.procs = findAgentProcesses();
|
|
1806
|
-
} catch {
|
|
1807
|
-
}
|
|
1808
|
-
cache.procsTs = now;
|
|
1809
|
-
}
|
|
1810
|
-
return cache.procs;
|
|
1811
|
-
}
|
|
1812
|
-
function cacheDetail(cache, s) {
|
|
1813
|
-
const entry = cache.details.get(s.id);
|
|
1814
|
-
if (entry && Date.now() - entry.ts < 1e4) return entry.report;
|
|
1815
|
-
let report = null;
|
|
1816
|
-
try {
|
|
1817
|
-
report = getAgentSessionReport(s.id, s.agent);
|
|
1818
|
-
} catch {
|
|
1819
|
-
}
|
|
1820
|
-
cache.details.set(s.id, { report, ts: Date.now() });
|
|
1821
|
-
if (cache.details.size > 15) {
|
|
1822
|
-
const oldest = [...cache.details.entries()].sort((a, b) => a[1].ts - b[1].ts)[0];
|
|
1823
|
-
if (oldest) cache.details.delete(oldest[0]);
|
|
1824
|
-
}
|
|
1825
|
-
return report;
|
|
1826
|
-
}
|
|
1827
|
-
function runBlessedDashboard(refreshSec, limit) {
|
|
1828
|
-
const cache = createCache();
|
|
2208
|
+
function runReactiveDashboard(refreshSec, limit) {
|
|
2209
|
+
const store = new DashboardStore(limit);
|
|
1829
2210
|
const origTerm = process.env.TERM;
|
|
1830
2211
|
if (origTerm?.includes("256color")) {
|
|
1831
2212
|
process.env.TERM = "xterm";
|
|
@@ -1849,8 +2230,7 @@ function runBlessedDashboard(refreshSec, limit) {
|
|
|
1849
2230
|
height: 3,
|
|
1850
2231
|
tags: true,
|
|
1851
2232
|
style: { fg: FG, bg: "#161b22", border: { fg: BORDER_COLOR } },
|
|
1852
|
-
border: { type: "line" }
|
|
1853
|
-
content: ""
|
|
2233
|
+
border: { type: "line" }
|
|
1854
2234
|
});
|
|
1855
2235
|
const processBox = blessed.box({
|
|
1856
2236
|
parent: screen,
|
|
@@ -1862,8 +2242,7 @@ function runBlessedDashboard(refreshSec, limit) {
|
|
|
1862
2242
|
label: " {cyan-fg}{bold}Active Processes{/} ",
|
|
1863
2243
|
scrollable: true,
|
|
1864
2244
|
style: { fg: FG, bg: BG, border: { fg: BORDER_COLOR }, label: { fg: ACCENT } },
|
|
1865
|
-
border: { type: "line" }
|
|
1866
|
-
content: ""
|
|
2245
|
+
border: { type: "line" }
|
|
1867
2246
|
});
|
|
1868
2247
|
const sessionList = blessed.list({
|
|
1869
2248
|
parent: screen,
|
|
@@ -1905,7 +2284,7 @@ function runBlessedDashboard(refreshSec, limit) {
|
|
|
1905
2284
|
scrollbar: { ch: "\u2502", style: { fg: ACCENT } },
|
|
1906
2285
|
content: "{gray-fg}Select a session with \u2191\u2193 keys{/}"
|
|
1907
2286
|
});
|
|
1908
|
-
|
|
2287
|
+
blessed.box({
|
|
1909
2288
|
parent: screen,
|
|
1910
2289
|
bottom: 0,
|
|
1911
2290
|
left: 0,
|
|
@@ -1916,19 +2295,18 @@ function runBlessedDashboard(refreshSec, limit) {
|
|
|
1916
2295
|
border: { type: "line" },
|
|
1917
2296
|
content: " {bold}\u2191\u2193{/} Navigate {bold}Enter{/} Detail {bold}Tab{/} Switch panel {bold}r{/} Refresh {bold}q{/} Quit"
|
|
1918
2297
|
});
|
|
1919
|
-
let sessions = [];
|
|
1920
|
-
let procs = [];
|
|
1921
2298
|
let selectedIdx = 0;
|
|
1922
2299
|
let focusedPanel = "sessions";
|
|
1923
|
-
let lastDetailId = "";
|
|
1924
2300
|
function renderHeader() {
|
|
1925
2301
|
const time = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-GB");
|
|
2302
|
+
const sessions = store.sessions;
|
|
1926
2303
|
const totalPremium = sessions.reduce((s, x) => s + x.premiumRequests, 0);
|
|
1927
2304
|
const completedCount = sessions.filter((s) => s.complete).length;
|
|
1928
|
-
const stats = headerStats(
|
|
2305
|
+
const stats = headerStats(store.processes.length, sessions.length, totalPremium, completedCount);
|
|
1929
2306
|
headerBox.setContent(` {bold}{cyan-fg}\u26A1 copilot-agent{/} ${stats} {gray-fg}${time}{/}`);
|
|
1930
2307
|
}
|
|
1931
2308
|
function renderProcesses2() {
|
|
2309
|
+
const procs = store.processes;
|
|
1932
2310
|
if (procs.length === 0) {
|
|
1933
2311
|
processBox.setContent(" {gray-fg}No agent processes running{/}");
|
|
1934
2312
|
return;
|
|
@@ -1939,51 +2317,65 @@ function runBlessedDashboard(refreshSec, limit) {
|
|
|
1939
2317
|
${rows.join("\n")}`);
|
|
1940
2318
|
}
|
|
1941
2319
|
function renderSessions() {
|
|
1942
|
-
const items = sessions.map((s) => sessionRow(s));
|
|
2320
|
+
const items = store.sessions.map((s) => sessionRow(s));
|
|
1943
2321
|
sessionList.setItems(items);
|
|
1944
2322
|
if (selectedIdx >= 0 && selectedIdx < items.length) {
|
|
1945
2323
|
sessionList.select(selectedIdx);
|
|
1946
2324
|
}
|
|
1947
|
-
sessionList.setLabel(` {cyan-fg}{bold}Sessions (${sessions.length}){/} `);
|
|
1948
|
-
}
|
|
1949
|
-
function
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
2325
|
+
sessionList.setLabel(` {cyan-fg}{bold}Sessions (${store.sessions.length}){/} `);
|
|
2326
|
+
}
|
|
2327
|
+
function showQuickPreview(s) {
|
|
2328
|
+
const project = s.cwd?.split("/").pop() || s.id.slice(0, 12);
|
|
2329
|
+
const agent = s.agent === "claude" ? "{yellow-fg}claude{/}" : "{cyan-fg}copilot{/}";
|
|
2330
|
+
const status = s.complete ? "{green-fg}\u2714 complete{/}" : "{yellow-fg}\u23F3 incomplete{/}";
|
|
2331
|
+
detailBox.setContent([
|
|
2332
|
+
`{bold}${project}{/} ${agent}`,
|
|
2333
|
+
`{gray-fg}${s.id}{/}`,
|
|
2334
|
+
"",
|
|
2335
|
+
` Status ${status}`,
|
|
2336
|
+
` Premium {yellow-fg}${s.premiumRequests}{/}`,
|
|
2337
|
+
` Activity ${fmtTimeAgo(s.mtime)}`,
|
|
2338
|
+
` Directory {gray-fg}${s.cwd || "\u2014"}{/}`,
|
|
2339
|
+
"",
|
|
2340
|
+
"{gray-fg}Loading\u2026{/}"
|
|
2341
|
+
].join("\n"));
|
|
2342
|
+
detailBox.setLabel(` {cyan-fg}{bold}Detail \u2014 ${s.id.slice(0, 12)}\u2026{/} `);
|
|
2343
|
+
}
|
|
2344
|
+
store.on("sessions", () => {
|
|
2345
|
+
renderHeader();
|
|
2346
|
+
renderSessions();
|
|
2347
|
+
const s = store.sessions[selectedIdx];
|
|
2348
|
+
if (s) store.requestDetail(s.id, s.agent);
|
|
2349
|
+
screen.render();
|
|
2350
|
+
});
|
|
2351
|
+
store.on("processes", () => {
|
|
2352
|
+
renderHeader();
|
|
2353
|
+
renderProcesses2();
|
|
2354
|
+
screen.render();
|
|
2355
|
+
});
|
|
2356
|
+
store.on("detail", (sessionId) => {
|
|
2357
|
+
const currentSession = store.sessions[selectedIdx];
|
|
2358
|
+
if (!currentSession || currentSession.id !== sessionId) return;
|
|
2359
|
+
const report = store.details.get(sessionId);
|
|
1959
2360
|
if (report) {
|
|
1960
2361
|
detailBox.setContent(detailContent(report));
|
|
1961
|
-
detailBox.setLabel(` {cyan-fg}{bold}Detail \u2014 ${
|
|
2362
|
+
detailBox.setLabel(` {cyan-fg}{bold}Detail \u2014 ${sessionId.slice(0, 12)}\u2026{/} `);
|
|
1962
2363
|
} else {
|
|
1963
|
-
detailBox.setContent(`{gray-fg}
|
|
2364
|
+
detailBox.setContent(`{gray-fg}No report data for ${sessionId.slice(0, 8)}\u2026{/}`);
|
|
1964
2365
|
}
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
procs = cacheProcs(cache);
|
|
2366
|
+
screen.render();
|
|
2367
|
+
});
|
|
2368
|
+
const clockTimer = setInterval(() => {
|
|
1969
2369
|
renderHeader();
|
|
1970
|
-
renderProcesses2();
|
|
1971
|
-
renderSessions();
|
|
1972
|
-
renderDetail2();
|
|
1973
2370
|
screen.render();
|
|
1974
|
-
}
|
|
1975
|
-
function forceRefresh() {
|
|
1976
|
-
cache.sessionsTs = 0;
|
|
1977
|
-
cache.procsTs = 0;
|
|
1978
|
-
cache.details.clear();
|
|
1979
|
-
lastDetailId = "";
|
|
1980
|
-
render();
|
|
1981
|
-
}
|
|
2371
|
+
}, 5e3);
|
|
1982
2372
|
screen.key(["q", "C-c"], () => {
|
|
2373
|
+
store.stop();
|
|
2374
|
+
clearInterval(clockTimer);
|
|
1983
2375
|
screen.destroy();
|
|
1984
2376
|
process.exit(0);
|
|
1985
2377
|
});
|
|
1986
|
-
screen.key(["r"], () => forceRefresh());
|
|
2378
|
+
screen.key(["r"], () => store.forceRefresh());
|
|
1987
2379
|
screen.key(["tab"], () => {
|
|
1988
2380
|
if (focusedPanel === "sessions") {
|
|
1989
2381
|
focusedPanel = "detail";
|
|
@@ -1998,29 +2390,40 @@ ${rows.join("\n")}`);
|
|
|
1998
2390
|
}
|
|
1999
2391
|
screen.render();
|
|
2000
2392
|
});
|
|
2393
|
+
function onNavigate() {
|
|
2394
|
+
sessionList.select(selectedIdx);
|
|
2395
|
+
const s = store.sessions[selectedIdx];
|
|
2396
|
+
if (s) {
|
|
2397
|
+
const cached = store.details.get(s.id);
|
|
2398
|
+
if (cached) {
|
|
2399
|
+
detailBox.setContent(detailContent(cached));
|
|
2400
|
+
detailBox.setLabel(` {cyan-fg}{bold}Detail \u2014 ${s.id.slice(0, 12)}\u2026{/} `);
|
|
2401
|
+
} else {
|
|
2402
|
+
showQuickPreview(s);
|
|
2403
|
+
}
|
|
2404
|
+
store.requestDetail(s.id, s.agent);
|
|
2405
|
+
}
|
|
2406
|
+
screen.render();
|
|
2407
|
+
}
|
|
2001
2408
|
sessionList.on("select item", (_item, index) => {
|
|
2002
2409
|
selectedIdx = index;
|
|
2003
|
-
|
|
2004
|
-
screen.render();
|
|
2410
|
+
onNavigate();
|
|
2005
2411
|
});
|
|
2006
2412
|
sessionList.key(["up", "k"], () => {
|
|
2007
2413
|
if (selectedIdx > 0) {
|
|
2008
2414
|
selectedIdx--;
|
|
2009
|
-
|
|
2010
|
-
renderDetail2();
|
|
2011
|
-
screen.render();
|
|
2415
|
+
onNavigate();
|
|
2012
2416
|
}
|
|
2013
2417
|
});
|
|
2014
2418
|
sessionList.key(["down", "j"], () => {
|
|
2015
|
-
if (selectedIdx < sessions.length - 1) {
|
|
2419
|
+
if (selectedIdx < store.sessions.length - 1) {
|
|
2016
2420
|
selectedIdx++;
|
|
2017
|
-
|
|
2018
|
-
renderDetail2();
|
|
2019
|
-
screen.render();
|
|
2421
|
+
onNavigate();
|
|
2020
2422
|
}
|
|
2021
2423
|
});
|
|
2022
2424
|
sessionList.key(["enter"], () => {
|
|
2023
|
-
|
|
2425
|
+
const s = store.sessions[selectedIdx];
|
|
2426
|
+
if (s) store.requestDetailNow(s.id, s.agent);
|
|
2024
2427
|
focusedPanel = "detail";
|
|
2025
2428
|
detailBox.focus();
|
|
2026
2429
|
sessionList.style.border.fg = BORDER_COLOR;
|
|
@@ -2029,9 +2432,7 @@ ${rows.join("\n")}`);
|
|
|
2029
2432
|
});
|
|
2030
2433
|
sessionList.focus();
|
|
2031
2434
|
sessionList.style.border.fg = ACCENT;
|
|
2032
|
-
|
|
2033
|
-
const timer = setInterval(render, refreshSec * 1e3);
|
|
2034
|
-
screen.on("destroy", () => clearInterval(timer));
|
|
2435
|
+
store.start(refreshSec * 1e3, 3e3);
|
|
2035
2436
|
}
|
|
2036
2437
|
|
|
2037
2438
|
// src/commands/web.ts
|
|
@@ -2434,19 +2835,19 @@ ${layoutFoot}`);
|
|
|
2434
2835
|
import chalk from "chalk";
|
|
2435
2836
|
|
|
2436
2837
|
// src/lib/config.ts
|
|
2437
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as
|
|
2438
|
-
import { join as
|
|
2439
|
-
import { homedir as
|
|
2838
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
|
|
2839
|
+
import { join as join8 } from "path";
|
|
2840
|
+
import { homedir as homedir6 } from "os";
|
|
2440
2841
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
2441
2842
|
import { findUpSync } from "find-up";
|
|
2442
|
-
var CONFIG_DIR =
|
|
2443
|
-
var GLOBAL_CONFIG =
|
|
2843
|
+
var CONFIG_DIR = join8(homedir6(), ".copilot-agent");
|
|
2844
|
+
var GLOBAL_CONFIG = join8(CONFIG_DIR, "config.yaml");
|
|
2444
2845
|
var PROJECT_CONFIG_NAME = ".copilot-agent.yaml";
|
|
2445
2846
|
function ensureConfigDir() {
|
|
2446
|
-
if (!
|
|
2847
|
+
if (!existsSync7(CONFIG_DIR)) mkdirSync4(CONFIG_DIR, { recursive: true });
|
|
2447
2848
|
}
|
|
2448
2849
|
function loadGlobalConfig() {
|
|
2449
|
-
if (!
|
|
2850
|
+
if (!existsSync7(GLOBAL_CONFIG)) return {};
|
|
2450
2851
|
try {
|
|
2451
2852
|
return parseYaml(readFileSync4(GLOBAL_CONFIG, "utf-8")) || {};
|
|
2452
2853
|
} catch {
|
|
@@ -2493,7 +2894,7 @@ function deleteConfigValue(key) {
|
|
|
2493
2894
|
saveGlobalConfig(config);
|
|
2494
2895
|
}
|
|
2495
2896
|
function resetConfig() {
|
|
2496
|
-
if (
|
|
2897
|
+
if (existsSync7(GLOBAL_CONFIG)) {
|
|
2497
2898
|
writeFileSync2(GLOBAL_CONFIG, "", "utf-8");
|
|
2498
2899
|
}
|
|
2499
2900
|
}
|
|
@@ -2550,18 +2951,18 @@ function registerConfigCommand(program2) {
|
|
|
2550
2951
|
// src/commands/proxy.ts
|
|
2551
2952
|
import chalk2 from "chalk";
|
|
2552
2953
|
import { execa } from "execa";
|
|
2553
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as
|
|
2554
|
-
import { join as
|
|
2555
|
-
import { homedir as
|
|
2556
|
-
var PID_FILE =
|
|
2954
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync8, mkdirSync as mkdirSync5, unlinkSync } from "fs";
|
|
2955
|
+
import { join as join9 } from "path";
|
|
2956
|
+
import { homedir as homedir7 } from "os";
|
|
2957
|
+
var PID_FILE = join9(homedir7(), ".copilot-agent", "proxy.pid");
|
|
2557
2958
|
var DEFAULT_PORT = 4141;
|
|
2558
2959
|
function ensureDir() {
|
|
2559
|
-
const dir =
|
|
2560
|
-
if (!
|
|
2960
|
+
const dir = join9(homedir7(), ".copilot-agent");
|
|
2961
|
+
if (!existsSync8(dir)) mkdirSync5(dir, { recursive: true });
|
|
2561
2962
|
}
|
|
2562
2963
|
function findCopilotToken() {
|
|
2563
|
-
const appsPath =
|
|
2564
|
-
if (!
|
|
2964
|
+
const appsPath = join9(homedir7(), ".config", "github-copilot", "apps.json");
|
|
2965
|
+
if (!existsSync8(appsPath)) return null;
|
|
2565
2966
|
try {
|
|
2566
2967
|
const apps = JSON.parse(readFileSync5(appsPath, "utf-8"));
|
|
2567
2968
|
for (const key of Object.keys(apps)) {
|
|
@@ -2574,7 +2975,7 @@ function findCopilotToken() {
|
|
|
2574
2975
|
}
|
|
2575
2976
|
}
|
|
2576
2977
|
function readPid() {
|
|
2577
|
-
if (!
|
|
2978
|
+
if (!existsSync8(PID_FILE)) return null;
|
|
2578
2979
|
try {
|
|
2579
2980
|
const pid = parseInt(readFileSync5(PID_FILE, "utf-8").trim(), 10);
|
|
2580
2981
|
return isNaN(pid) ? null : pid;
|
|
@@ -2662,12 +3063,12 @@ function registerProxyCommand(program2) {
|
|
|
2662
3063
|
}
|
|
2663
3064
|
if (!isProcessRunning(pid)) {
|
|
2664
3065
|
console.log(chalk2.dim(` Proxy (PID ${pid}) is not running`));
|
|
2665
|
-
if (
|
|
3066
|
+
if (existsSync8(PID_FILE)) unlinkSync(PID_FILE);
|
|
2666
3067
|
return;
|
|
2667
3068
|
}
|
|
2668
3069
|
try {
|
|
2669
3070
|
process.kill(pid, "SIGTERM");
|
|
2670
|
-
if (
|
|
3071
|
+
if (existsSync8(PID_FILE)) unlinkSync(PID_FILE);
|
|
2671
3072
|
console.log(chalk2.green(` \u2714 Proxy stopped (PID ${pid})`));
|
|
2672
3073
|
} catch (err) {
|
|
2673
3074
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -2801,10 +3202,10 @@ function showDiff(sessionId, opts) {
|
|
|
2801
3202
|
import chalk4 from "chalk";
|
|
2802
3203
|
|
|
2803
3204
|
// src/lib/quota.ts
|
|
2804
|
-
import { join as
|
|
2805
|
-
import { homedir as
|
|
2806
|
-
var DATA_DIR =
|
|
2807
|
-
var USAGE_FILE =
|
|
3205
|
+
import { join as join10 } from "path";
|
|
3206
|
+
import { homedir as homedir8 } from "os";
|
|
3207
|
+
var DATA_DIR = join10(homedir8(), ".copilot-agent");
|
|
3208
|
+
var USAGE_FILE = join10(DATA_DIR, "usage.jsonl");
|
|
2808
3209
|
function buildUsageSummary(days) {
|
|
2809
3210
|
const sessions = listAllSessions(500);
|
|
2810
3211
|
const cutoff = days ? Date.now() - days * 864e5 : 0;
|
|
@@ -2899,12 +3300,12 @@ function registerQuotaCommand(program2) {
|
|
|
2899
3300
|
import chalk5 from "chalk";
|
|
2900
3301
|
|
|
2901
3302
|
// src/lib/compact.ts
|
|
2902
|
-
import { writeFileSync as writeFileSync4, existsSync as
|
|
2903
|
-
import { join as
|
|
2904
|
-
import { homedir as
|
|
2905
|
-
var COMPACT_DIR =
|
|
3303
|
+
import { writeFileSync as writeFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
|
|
3304
|
+
import { join as join11 } from "path";
|
|
3305
|
+
import { homedir as homedir9 } from "os";
|
|
3306
|
+
var COMPACT_DIR = join11(homedir9(), ".copilot-agent", "compacts");
|
|
2906
3307
|
function ensureDir2() {
|
|
2907
|
-
if (!
|
|
3308
|
+
if (!existsSync9(COMPACT_DIR)) mkdirSync6(COMPACT_DIR, { recursive: true });
|
|
2908
3309
|
}
|
|
2909
3310
|
function compactSession(sessionId, agent) {
|
|
2910
3311
|
const report = getAgentSessionReport(sessionId, agent);
|
|
@@ -2997,7 +3398,7 @@ function compactSession(sessionId, agent) {
|
|
|
2997
3398
|
function saveCompact(compact) {
|
|
2998
3399
|
ensureDir2();
|
|
2999
3400
|
const filename = `${compact.sessionId.slice(0, 12)}.md`;
|
|
3000
|
-
const filepath =
|
|
3401
|
+
const filepath = join11(COMPACT_DIR, filename);
|
|
3001
3402
|
writeFileSync4(filepath, compact.markdown, "utf-8");
|
|
3002
3403
|
return filepath;
|
|
3003
3404
|
}
|
|
@@ -3105,17 +3506,17 @@ function showCompact(sessionId, opts) {
|
|
|
3105
3506
|
import chalk6 from "chalk";
|
|
3106
3507
|
|
|
3107
3508
|
// src/lib/hooks.ts
|
|
3108
|
-
import { readFileSync as readFileSync6, existsSync as
|
|
3109
|
-
import { join as
|
|
3110
|
-
import { homedir as
|
|
3509
|
+
import { readFileSync as readFileSync6, existsSync as existsSync10 } from "fs";
|
|
3510
|
+
import { join as join12 } from "path";
|
|
3511
|
+
import { homedir as homedir10 } from "os";
|
|
3111
3512
|
import { parse as parseYaml2 } from "yaml";
|
|
3112
3513
|
import { findUpSync as findUpSync2 } from "find-up";
|
|
3113
3514
|
import { execaCommand } from "execa";
|
|
3114
|
-
var GLOBAL_HOOKS =
|
|
3515
|
+
var GLOBAL_HOOKS = join12(homedir10(), ".copilot-agent", "hooks.yaml");
|
|
3115
3516
|
var PROJECT_HOOKS = ".copilot-agent/hooks.yaml";
|
|
3116
3517
|
function loadHooksConfig(cwd) {
|
|
3117
3518
|
const configs = [];
|
|
3118
|
-
if (
|
|
3519
|
+
if (existsSync10(GLOBAL_HOOKS)) {
|
|
3119
3520
|
try {
|
|
3120
3521
|
const parsed = parseYaml2(readFileSync6(GLOBAL_HOOKS, "utf-8"));
|
|
3121
3522
|
if (parsed) configs.push(parsed);
|
|
@@ -3377,17 +3778,17 @@ function formatDur(ms) {
|
|
|
3377
3778
|
|
|
3378
3779
|
// src/commands/template.ts
|
|
3379
3780
|
import chalk8 from "chalk";
|
|
3380
|
-
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as
|
|
3381
|
-
import { join as
|
|
3382
|
-
import { homedir as
|
|
3781
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync11, mkdirSync as mkdirSync7 } from "fs";
|
|
3782
|
+
import { join as join13 } from "path";
|
|
3783
|
+
import { homedir as homedir11 } from "os";
|
|
3383
3784
|
import { parse as parseYaml3, stringify as stringifyYaml2 } from "yaml";
|
|
3384
|
-
var CONFIG_DIR2 =
|
|
3385
|
-
var TEMPLATES_FILE =
|
|
3785
|
+
var CONFIG_DIR2 = join13(homedir11(), ".copilot-agent");
|
|
3786
|
+
var TEMPLATES_FILE = join13(CONFIG_DIR2, "templates.yaml");
|
|
3386
3787
|
function ensureDir3() {
|
|
3387
|
-
if (!
|
|
3788
|
+
if (!existsSync11(CONFIG_DIR2)) mkdirSync7(CONFIG_DIR2, { recursive: true });
|
|
3388
3789
|
}
|
|
3389
3790
|
function loadTemplates() {
|
|
3390
|
-
if (!
|
|
3791
|
+
if (!existsSync11(TEMPLATES_FILE)) return [];
|
|
3391
3792
|
try {
|
|
3392
3793
|
const data = parseYaml3(readFileSync7(TEMPLATES_FILE, "utf-8"));
|
|
3393
3794
|
return Array.isArray(data) ? data : [];
|
|
@@ -3617,15 +4018,15 @@ function fmtDur2(ms) {
|
|
|
3617
4018
|
}
|
|
3618
4019
|
|
|
3619
4020
|
// src/commands/multi.ts
|
|
3620
|
-
import { existsSync as
|
|
3621
|
-
import { join as
|
|
3622
|
-
import { homedir as
|
|
4021
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
4022
|
+
import { join as join14, resolve as resolve7, basename as basename4 } from "path";
|
|
4023
|
+
import { homedir as homedir12 } from "os";
|
|
3623
4024
|
import chalk10 from "chalk";
|
|
3624
4025
|
import { parse as parseYaml4, stringify as stringifyYaml3 } from "yaml";
|
|
3625
|
-
var CONFIG_DIR3 =
|
|
3626
|
-
var PROJECTS_FILE =
|
|
3627
|
-
var STATUS_FILE =
|
|
3628
|
-
var LOG_DIR =
|
|
4026
|
+
var CONFIG_DIR3 = join14(homedir12(), ".copilot-agent");
|
|
4027
|
+
var PROJECTS_FILE = join14(CONFIG_DIR3, "multi-projects.txt");
|
|
4028
|
+
var STATUS_FILE = join14(CONFIG_DIR3, "multi-status.yaml");
|
|
4029
|
+
var LOG_DIR = join14(CONFIG_DIR3, "multi-logs");
|
|
3629
4030
|
var MAX_CONCURRENCY = 3;
|
|
3630
4031
|
function registerMultiCommand(program2) {
|
|
3631
4032
|
program2.command("multi <action> [args...]").description("Multi-project orchestration \u2014 add/remove/list/run/status/research").option("-a, --agent <type>", "Agent to use (copilot or claude)", "copilot").option("--parallel", "Run projects in parallel (max 3 concurrent)").option("--cooldown <n>", "Cooldown between projects in seconds", "30").option("-s, --steps <n>", "Max steps per task", "10").option("--max-premium <n>", "Premium budget per project", "50").option("--dry-run", "Preview without executing").action(async (action, args, opts) => {
|
|
@@ -3668,7 +4069,7 @@ async function multiCommand(action, args, opts) {
|
|
|
3668
4069
|
function ensureFiles() {
|
|
3669
4070
|
mkdirSync8(LOG_DIR, { recursive: true });
|
|
3670
4071
|
mkdirSync8(CONFIG_DIR3, { recursive: true });
|
|
3671
|
-
if (!
|
|
4072
|
+
if (!existsSync12(PROJECTS_FILE)) writeFileSync7(PROJECTS_FILE, "");
|
|
3672
4073
|
}
|
|
3673
4074
|
function readProjects() {
|
|
3674
4075
|
return readFileSync8(PROJECTS_FILE, "utf-8").split("\n").map((l) => l.trim()).filter(Boolean);
|
|
@@ -3677,7 +4078,7 @@ function writeProjects(projects) {
|
|
|
3677
4078
|
writeFileSync7(PROJECTS_FILE, projects.join("\n") + "\n");
|
|
3678
4079
|
}
|
|
3679
4080
|
function readStatusFile() {
|
|
3680
|
-
if (!
|
|
4081
|
+
if (!existsSync12(STATUS_FILE)) return [];
|
|
3681
4082
|
try {
|
|
3682
4083
|
const raw = readFileSync8(STATUS_FILE, "utf-8");
|
|
3683
4084
|
const parsed = parseYaml4(raw);
|
|
@@ -3711,7 +4112,7 @@ async function addProject(path) {
|
|
|
3711
4112
|
process.exit(1);
|
|
3712
4113
|
}
|
|
3713
4114
|
const resolved = resolve7(path);
|
|
3714
|
-
if (!
|
|
4115
|
+
if (!existsSync12(resolved)) {
|
|
3715
4116
|
fail(`Not found: ${resolved}`);
|
|
3716
4117
|
process.exit(1);
|
|
3717
4118
|
}
|
|
@@ -3753,7 +4154,7 @@ function listProjects() {
|
|
|
3753
4154
|
console.log(chalk10.bold("\nRegistered projects:"));
|
|
3754
4155
|
for (let i = 0; i < projects.length; i++) {
|
|
3755
4156
|
const p = projects[i];
|
|
3756
|
-
const exists =
|
|
4157
|
+
const exists = existsSync12(p);
|
|
3757
4158
|
const icon = exists ? chalk10.green("\u2705") : chalk10.red("\u274C");
|
|
3758
4159
|
const type = exists ? detectProjectType(p) : "?";
|
|
3759
4160
|
const st = statuses.find((s) => s.project === p);
|
|
@@ -3772,7 +4173,7 @@ function showStatus() {
|
|
|
3772
4173
|
for (const s of statuses) {
|
|
3773
4174
|
const icon = s.status === "success" ? chalk10.green("\u2705") : s.status === "failed" ? chalk10.red("\u274C") : chalk10.yellow("\u{1F504}");
|
|
3774
4175
|
console.log(
|
|
3775
|
-
` ${icon} ${chalk10.bold(
|
|
4176
|
+
` ${icon} ${chalk10.bold(basename4(s.project))} \u2014 ${s.agent} \u2014 ${s.tasks} tasks \u2014 ${s.duration} \u2014 ${chalk10.dim(s.lastRun)}`
|
|
3776
4177
|
);
|
|
3777
4178
|
}
|
|
3778
4179
|
console.log();
|
|
@@ -3802,7 +4203,7 @@ function createSemaphore(max) {
|
|
|
3802
4203
|
async function runAll(mode, opts) {
|
|
3803
4204
|
assertAgent(opts.agent);
|
|
3804
4205
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").slice(0, 15);
|
|
3805
|
-
setLogFile(
|
|
4206
|
+
setLogFile(join14(LOG_DIR, `multi-${mode}-${ts}.log`));
|
|
3806
4207
|
const projects = readProjects();
|
|
3807
4208
|
if (projects.length === 0) {
|
|
3808
4209
|
fail("No projects registered. Add: copilot-agent multi add <path>");
|
|
@@ -3811,10 +4212,10 @@ async function runAll(mode, opts) {
|
|
|
3811
4212
|
log(`\u{1F3ED} Multi-project ${mode} \u2014 ${projects.length} projects \u2014 agent: ${opts.agent}${opts.parallel ? " (parallel)" : ""}`);
|
|
3812
4213
|
if (opts.dryRun) {
|
|
3813
4214
|
for (const p of projects) {
|
|
3814
|
-
const type =
|
|
4215
|
+
const type = existsSync12(p) ? detectProjectType(p) : "unknown";
|
|
3815
4216
|
const tasks = getTasksForProject(type);
|
|
3816
4217
|
console.log(`
|
|
3817
|
-
${chalk10.bold(
|
|
4218
|
+
${chalk10.bold(basename4(p))} (${type})`);
|
|
3818
4219
|
for (const t of tasks.slice(0, 3)) {
|
|
3819
4220
|
console.log(` ${chalk10.dim("\u2022")} ${t.title}`);
|
|
3820
4221
|
}
|
|
@@ -3860,8 +4261,8 @@ ${"\u2550".repeat(50)}`);
|
|
|
3860
4261
|
notify(`Multi-${mode}: ${success}/${total} succeeded`, "copilot-agent");
|
|
3861
4262
|
}
|
|
3862
4263
|
async function runSingleProject(project, mode, opts) {
|
|
3863
|
-
const name =
|
|
3864
|
-
if (!
|
|
4264
|
+
const name = existsSync12(project) ? detectProjectName(project) : basename4(project);
|
|
4265
|
+
if (!existsSync12(project)) {
|
|
3865
4266
|
warn(`Skipping (not found): ${project}`);
|
|
3866
4267
|
return { name, success: false, skipped: true };
|
|
3867
4268
|
}
|
|
@@ -4041,17 +4442,17 @@ function registerReviewCommand(program2) {
|
|
|
4041
4442
|
}
|
|
4042
4443
|
|
|
4043
4444
|
// src/commands/schedule.ts
|
|
4044
|
-
import { readFileSync as readFileSync9, writeFileSync as writeFileSync8, existsSync as
|
|
4045
|
-
import { join as
|
|
4046
|
-
import { homedir as
|
|
4445
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync8, existsSync as existsSync13, mkdirSync as mkdirSync9 } from "fs";
|
|
4446
|
+
import { join as join15 } from "path";
|
|
4447
|
+
import { homedir as homedir13 } from "os";
|
|
4047
4448
|
import { parse as parseYaml5, stringify as stringifyYaml4 } from "yaml";
|
|
4048
|
-
var CONFIG_DIR4 =
|
|
4049
|
-
var SCHEDULES_FILE =
|
|
4449
|
+
var CONFIG_DIR4 = join15(homedir13(), ".copilot-agent");
|
|
4450
|
+
var SCHEDULES_FILE = join15(CONFIG_DIR4, "schedules.yaml");
|
|
4050
4451
|
function ensureConfigDir2() {
|
|
4051
|
-
if (!
|
|
4452
|
+
if (!existsSync13(CONFIG_DIR4)) mkdirSync9(CONFIG_DIR4, { recursive: true });
|
|
4052
4453
|
}
|
|
4053
4454
|
function loadSchedules() {
|
|
4054
|
-
if (!
|
|
4455
|
+
if (!existsSync13(SCHEDULES_FILE)) return [];
|
|
4055
4456
|
try {
|
|
4056
4457
|
const raw = parseYaml5(readFileSync9(SCHEDULES_FILE, "utf-8"));
|
|
4057
4458
|
return Array.isArray(raw) ? raw : [];
|
|
@@ -4290,7 +4691,7 @@ function registerScheduleCommand(program2) {
|
|
|
4290
4691
|
|
|
4291
4692
|
// src/index.ts
|
|
4292
4693
|
var program = new Command();
|
|
4293
|
-
program.name("copilot-agent").version("1.
|
|
4694
|
+
program.name("copilot-agent").version("1.1.0").description("Autonomous AI agent manager \u2014 auto-resume, task discovery, overnight runs. Supports GitHub Copilot CLI + Claude Code.");
|
|
4294
4695
|
registerStatusCommand(program);
|
|
4295
4696
|
registerWatchCommand(program);
|
|
4296
4697
|
registerRunCommand(program);
|