copilot-agent 1.0.1 → 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 +576 -211
- 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,71 +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
|
-
let detailDebounce = null;
|
|
1925
|
-
function showQuickPreview(s) {
|
|
1926
|
-
const project = s.cwd?.split("/").pop() || s.id.slice(0, 12);
|
|
1927
|
-
const agent = s.agent === "claude" ? "{yellow-fg}claude{/}" : "{cyan-fg}copilot{/}";
|
|
1928
|
-
const status = s.complete ? "{green-fg}\u2714 complete{/}" : "{yellow-fg}\u23F3 incomplete{/}";
|
|
1929
|
-
const lines = [
|
|
1930
|
-
`{bold}${project}{/} ${agent}`,
|
|
1931
|
-
`{gray-fg}${s.id}{/}`,
|
|
1932
|
-
"",
|
|
1933
|
-
` Status ${status}`,
|
|
1934
|
-
` Premium {yellow-fg}${s.premiumRequests}{/}`,
|
|
1935
|
-
` Activity ${fmtTimeAgo(s.mtime)}`,
|
|
1936
|
-
` Directory {gray-fg}${s.cwd || "\u2014"}{/}`,
|
|
1937
|
-
"",
|
|
1938
|
-
"{gray-fg}Loading full report\u2026{/}"
|
|
1939
|
-
];
|
|
1940
|
-
detailBox.setContent(lines.join("\n"));
|
|
1941
|
-
detailBox.setLabel(` {cyan-fg}{bold}Detail \u2014 ${s.id.slice(0, 12)}\u2026{/} `);
|
|
1942
|
-
}
|
|
1943
|
-
function scheduleDetailRender(force = false) {
|
|
1944
|
-
if (detailDebounce) clearTimeout(detailDebounce);
|
|
1945
|
-
if (sessions.length === 0 || selectedIdx < 0 || selectedIdx >= sessions.length) {
|
|
1946
|
-
detailBox.setContent("{gray-fg}No session selected{/}");
|
|
1947
|
-
lastDetailId = "";
|
|
1948
|
-
return;
|
|
1949
|
-
}
|
|
1950
|
-
const s = sessions[selectedIdx];
|
|
1951
|
-
const cached = cache.details.get(s.id);
|
|
1952
|
-
if (!force && cached && Date.now() - cached.ts < 1e4 && cached.report) {
|
|
1953
|
-
lastDetailId = s.id;
|
|
1954
|
-
detailBox.setContent(detailContent(cached.report));
|
|
1955
|
-
detailBox.setLabel(` {cyan-fg}{bold}Detail \u2014 ${s.id.slice(0, 12)}\u2026{/} `);
|
|
1956
|
-
return;
|
|
1957
|
-
}
|
|
1958
|
-
if (s.id !== lastDetailId) {
|
|
1959
|
-
showQuickPreview(s);
|
|
1960
|
-
}
|
|
1961
|
-
detailDebounce = setTimeout(() => {
|
|
1962
|
-
detailDebounce = null;
|
|
1963
|
-
if (selectedIdx < 0 || selectedIdx >= sessions.length) return;
|
|
1964
|
-
const current = sessions[selectedIdx];
|
|
1965
|
-
lastDetailId = current.id;
|
|
1966
|
-
const report = cacheDetail(cache, current);
|
|
1967
|
-
if (report) {
|
|
1968
|
-
detailBox.setContent(detailContent(report));
|
|
1969
|
-
detailBox.setLabel(` {cyan-fg}{bold}Detail \u2014 ${current.id.slice(0, 12)}\u2026{/} `);
|
|
1970
|
-
} else {
|
|
1971
|
-
detailBox.setContent(`{gray-fg}No report data for ${current.id.slice(0, 8)}\u2026{/}`);
|
|
1972
|
-
}
|
|
1973
|
-
screen.render();
|
|
1974
|
-
}, 150);
|
|
1975
|
-
}
|
|
1976
2300
|
function renderHeader() {
|
|
1977
2301
|
const time = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-GB");
|
|
2302
|
+
const sessions = store.sessions;
|
|
1978
2303
|
const totalPremium = sessions.reduce((s, x) => s + x.premiumRequests, 0);
|
|
1979
2304
|
const completedCount = sessions.filter((s) => s.complete).length;
|
|
1980
|
-
const stats = headerStats(
|
|
2305
|
+
const stats = headerStats(store.processes.length, sessions.length, totalPremium, completedCount);
|
|
1981
2306
|
headerBox.setContent(` {bold}{cyan-fg}\u26A1 copilot-agent{/} ${stats} {gray-fg}${time}{/}`);
|
|
1982
2307
|
}
|
|
1983
2308
|
function renderProcesses2() {
|
|
2309
|
+
const procs = store.processes;
|
|
1984
2310
|
if (procs.length === 0) {
|
|
1985
2311
|
processBox.setContent(" {gray-fg}No agent processes running{/}");
|
|
1986
2312
|
return;
|
|
@@ -1991,35 +2317,65 @@ function runBlessedDashboard(refreshSec, limit) {
|
|
|
1991
2317
|
${rows.join("\n")}`);
|
|
1992
2318
|
}
|
|
1993
2319
|
function renderSessions() {
|
|
1994
|
-
const items = sessions.map((s) => sessionRow(s));
|
|
2320
|
+
const items = store.sessions.map((s) => sessionRow(s));
|
|
1995
2321
|
sessionList.setItems(items);
|
|
1996
2322
|
if (selectedIdx >= 0 && selectedIdx < items.length) {
|
|
1997
2323
|
sessionList.select(selectedIdx);
|
|
1998
2324
|
}
|
|
1999
|
-
sessionList.setLabel(` {cyan-fg}{bold}Sessions (${sessions.length}){/} `);
|
|
2325
|
+
sessionList.setLabel(` {cyan-fg}{bold}Sessions (${store.sessions.length}){/} `);
|
|
2000
2326
|
}
|
|
2001
|
-
function
|
|
2002
|
-
|
|
2003
|
-
|
|
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", () => {
|
|
2004
2345
|
renderHeader();
|
|
2005
|
-
renderProcesses2();
|
|
2006
2346
|
renderSessions();
|
|
2007
|
-
|
|
2347
|
+
const s = store.sessions[selectedIdx];
|
|
2348
|
+
if (s) store.requestDetail(s.id, s.agent);
|
|
2008
2349
|
screen.render();
|
|
2009
|
-
}
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
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);
|
|
2360
|
+
if (report) {
|
|
2361
|
+
detailBox.setContent(detailContent(report));
|
|
2362
|
+
detailBox.setLabel(` {cyan-fg}{bold}Detail \u2014 ${sessionId.slice(0, 12)}\u2026{/} `);
|
|
2363
|
+
} else {
|
|
2364
|
+
detailBox.setContent(`{gray-fg}No report data for ${sessionId.slice(0, 8)}\u2026{/}`);
|
|
2365
|
+
}
|
|
2366
|
+
screen.render();
|
|
2367
|
+
});
|
|
2368
|
+
const clockTimer = setInterval(() => {
|
|
2369
|
+
renderHeader();
|
|
2370
|
+
screen.render();
|
|
2371
|
+
}, 5e3);
|
|
2017
2372
|
screen.key(["q", "C-c"], () => {
|
|
2018
|
-
|
|
2373
|
+
store.stop();
|
|
2374
|
+
clearInterval(clockTimer);
|
|
2019
2375
|
screen.destroy();
|
|
2020
2376
|
process.exit(0);
|
|
2021
2377
|
});
|
|
2022
|
-
screen.key(["r"], () => forceRefresh());
|
|
2378
|
+
screen.key(["r"], () => store.forceRefresh());
|
|
2023
2379
|
screen.key(["tab"], () => {
|
|
2024
2380
|
if (focusedPanel === "sessions") {
|
|
2025
2381
|
focusedPanel = "detail";
|
|
@@ -2034,29 +2390,40 @@ ${rows.join("\n")}`);
|
|
|
2034
2390
|
}
|
|
2035
2391
|
screen.render();
|
|
2036
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
|
+
}
|
|
2037
2408
|
sessionList.on("select item", (_item, index) => {
|
|
2038
2409
|
selectedIdx = index;
|
|
2039
|
-
|
|
2040
|
-
screen.render();
|
|
2410
|
+
onNavigate();
|
|
2041
2411
|
});
|
|
2042
2412
|
sessionList.key(["up", "k"], () => {
|
|
2043
2413
|
if (selectedIdx > 0) {
|
|
2044
2414
|
selectedIdx--;
|
|
2045
|
-
|
|
2046
|
-
scheduleDetailRender();
|
|
2047
|
-
screen.render();
|
|
2415
|
+
onNavigate();
|
|
2048
2416
|
}
|
|
2049
2417
|
});
|
|
2050
2418
|
sessionList.key(["down", "j"], () => {
|
|
2051
|
-
if (selectedIdx < sessions.length - 1) {
|
|
2419
|
+
if (selectedIdx < store.sessions.length - 1) {
|
|
2052
2420
|
selectedIdx++;
|
|
2053
|
-
|
|
2054
|
-
scheduleDetailRender();
|
|
2055
|
-
screen.render();
|
|
2421
|
+
onNavigate();
|
|
2056
2422
|
}
|
|
2057
2423
|
});
|
|
2058
2424
|
sessionList.key(["enter"], () => {
|
|
2059
|
-
|
|
2425
|
+
const s = store.sessions[selectedIdx];
|
|
2426
|
+
if (s) store.requestDetailNow(s.id, s.agent);
|
|
2060
2427
|
focusedPanel = "detail";
|
|
2061
2428
|
detailBox.focus();
|
|
2062
2429
|
sessionList.style.border.fg = BORDER_COLOR;
|
|
@@ -2065,9 +2432,7 @@ ${rows.join("\n")}`);
|
|
|
2065
2432
|
});
|
|
2066
2433
|
sessionList.focus();
|
|
2067
2434
|
sessionList.style.border.fg = ACCENT;
|
|
2068
|
-
|
|
2069
|
-
const timer = setInterval(render, refreshSec * 1e3);
|
|
2070
|
-
screen.on("destroy", () => clearInterval(timer));
|
|
2435
|
+
store.start(refreshSec * 1e3, 3e3);
|
|
2071
2436
|
}
|
|
2072
2437
|
|
|
2073
2438
|
// src/commands/web.ts
|
|
@@ -2470,19 +2835,19 @@ ${layoutFoot}`);
|
|
|
2470
2835
|
import chalk from "chalk";
|
|
2471
2836
|
|
|
2472
2837
|
// src/lib/config.ts
|
|
2473
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as
|
|
2474
|
-
import { join as
|
|
2475
|
-
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";
|
|
2476
2841
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
2477
2842
|
import { findUpSync } from "find-up";
|
|
2478
|
-
var CONFIG_DIR =
|
|
2479
|
-
var GLOBAL_CONFIG =
|
|
2843
|
+
var CONFIG_DIR = join8(homedir6(), ".copilot-agent");
|
|
2844
|
+
var GLOBAL_CONFIG = join8(CONFIG_DIR, "config.yaml");
|
|
2480
2845
|
var PROJECT_CONFIG_NAME = ".copilot-agent.yaml";
|
|
2481
2846
|
function ensureConfigDir() {
|
|
2482
|
-
if (!
|
|
2847
|
+
if (!existsSync7(CONFIG_DIR)) mkdirSync4(CONFIG_DIR, { recursive: true });
|
|
2483
2848
|
}
|
|
2484
2849
|
function loadGlobalConfig() {
|
|
2485
|
-
if (!
|
|
2850
|
+
if (!existsSync7(GLOBAL_CONFIG)) return {};
|
|
2486
2851
|
try {
|
|
2487
2852
|
return parseYaml(readFileSync4(GLOBAL_CONFIG, "utf-8")) || {};
|
|
2488
2853
|
} catch {
|
|
@@ -2529,7 +2894,7 @@ function deleteConfigValue(key) {
|
|
|
2529
2894
|
saveGlobalConfig(config);
|
|
2530
2895
|
}
|
|
2531
2896
|
function resetConfig() {
|
|
2532
|
-
if (
|
|
2897
|
+
if (existsSync7(GLOBAL_CONFIG)) {
|
|
2533
2898
|
writeFileSync2(GLOBAL_CONFIG, "", "utf-8");
|
|
2534
2899
|
}
|
|
2535
2900
|
}
|
|
@@ -2586,18 +2951,18 @@ function registerConfigCommand(program2) {
|
|
|
2586
2951
|
// src/commands/proxy.ts
|
|
2587
2952
|
import chalk2 from "chalk";
|
|
2588
2953
|
import { execa } from "execa";
|
|
2589
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as
|
|
2590
|
-
import { join as
|
|
2591
|
-
import { homedir as
|
|
2592
|
-
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");
|
|
2593
2958
|
var DEFAULT_PORT = 4141;
|
|
2594
2959
|
function ensureDir() {
|
|
2595
|
-
const dir =
|
|
2596
|
-
if (!
|
|
2960
|
+
const dir = join9(homedir7(), ".copilot-agent");
|
|
2961
|
+
if (!existsSync8(dir)) mkdirSync5(dir, { recursive: true });
|
|
2597
2962
|
}
|
|
2598
2963
|
function findCopilotToken() {
|
|
2599
|
-
const appsPath =
|
|
2600
|
-
if (!
|
|
2964
|
+
const appsPath = join9(homedir7(), ".config", "github-copilot", "apps.json");
|
|
2965
|
+
if (!existsSync8(appsPath)) return null;
|
|
2601
2966
|
try {
|
|
2602
2967
|
const apps = JSON.parse(readFileSync5(appsPath, "utf-8"));
|
|
2603
2968
|
for (const key of Object.keys(apps)) {
|
|
@@ -2610,7 +2975,7 @@ function findCopilotToken() {
|
|
|
2610
2975
|
}
|
|
2611
2976
|
}
|
|
2612
2977
|
function readPid() {
|
|
2613
|
-
if (!
|
|
2978
|
+
if (!existsSync8(PID_FILE)) return null;
|
|
2614
2979
|
try {
|
|
2615
2980
|
const pid = parseInt(readFileSync5(PID_FILE, "utf-8").trim(), 10);
|
|
2616
2981
|
return isNaN(pid) ? null : pid;
|
|
@@ -2698,12 +3063,12 @@ function registerProxyCommand(program2) {
|
|
|
2698
3063
|
}
|
|
2699
3064
|
if (!isProcessRunning(pid)) {
|
|
2700
3065
|
console.log(chalk2.dim(` Proxy (PID ${pid}) is not running`));
|
|
2701
|
-
if (
|
|
3066
|
+
if (existsSync8(PID_FILE)) unlinkSync(PID_FILE);
|
|
2702
3067
|
return;
|
|
2703
3068
|
}
|
|
2704
3069
|
try {
|
|
2705
3070
|
process.kill(pid, "SIGTERM");
|
|
2706
|
-
if (
|
|
3071
|
+
if (existsSync8(PID_FILE)) unlinkSync(PID_FILE);
|
|
2707
3072
|
console.log(chalk2.green(` \u2714 Proxy stopped (PID ${pid})`));
|
|
2708
3073
|
} catch (err) {
|
|
2709
3074
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -2837,10 +3202,10 @@ function showDiff(sessionId, opts) {
|
|
|
2837
3202
|
import chalk4 from "chalk";
|
|
2838
3203
|
|
|
2839
3204
|
// src/lib/quota.ts
|
|
2840
|
-
import { join as
|
|
2841
|
-
import { homedir as
|
|
2842
|
-
var DATA_DIR =
|
|
2843
|
-
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");
|
|
2844
3209
|
function buildUsageSummary(days) {
|
|
2845
3210
|
const sessions = listAllSessions(500);
|
|
2846
3211
|
const cutoff = days ? Date.now() - days * 864e5 : 0;
|
|
@@ -2935,12 +3300,12 @@ function registerQuotaCommand(program2) {
|
|
|
2935
3300
|
import chalk5 from "chalk";
|
|
2936
3301
|
|
|
2937
3302
|
// src/lib/compact.ts
|
|
2938
|
-
import { writeFileSync as writeFileSync4, existsSync as
|
|
2939
|
-
import { join as
|
|
2940
|
-
import { homedir as
|
|
2941
|
-
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");
|
|
2942
3307
|
function ensureDir2() {
|
|
2943
|
-
if (!
|
|
3308
|
+
if (!existsSync9(COMPACT_DIR)) mkdirSync6(COMPACT_DIR, { recursive: true });
|
|
2944
3309
|
}
|
|
2945
3310
|
function compactSession(sessionId, agent) {
|
|
2946
3311
|
const report = getAgentSessionReport(sessionId, agent);
|
|
@@ -3033,7 +3398,7 @@ function compactSession(sessionId, agent) {
|
|
|
3033
3398
|
function saveCompact(compact) {
|
|
3034
3399
|
ensureDir2();
|
|
3035
3400
|
const filename = `${compact.sessionId.slice(0, 12)}.md`;
|
|
3036
|
-
const filepath =
|
|
3401
|
+
const filepath = join11(COMPACT_DIR, filename);
|
|
3037
3402
|
writeFileSync4(filepath, compact.markdown, "utf-8");
|
|
3038
3403
|
return filepath;
|
|
3039
3404
|
}
|
|
@@ -3141,17 +3506,17 @@ function showCompact(sessionId, opts) {
|
|
|
3141
3506
|
import chalk6 from "chalk";
|
|
3142
3507
|
|
|
3143
3508
|
// src/lib/hooks.ts
|
|
3144
|
-
import { readFileSync as readFileSync6, existsSync as
|
|
3145
|
-
import { join as
|
|
3146
|
-
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";
|
|
3147
3512
|
import { parse as parseYaml2 } from "yaml";
|
|
3148
3513
|
import { findUpSync as findUpSync2 } from "find-up";
|
|
3149
3514
|
import { execaCommand } from "execa";
|
|
3150
|
-
var GLOBAL_HOOKS =
|
|
3515
|
+
var GLOBAL_HOOKS = join12(homedir10(), ".copilot-agent", "hooks.yaml");
|
|
3151
3516
|
var PROJECT_HOOKS = ".copilot-agent/hooks.yaml";
|
|
3152
3517
|
function loadHooksConfig(cwd) {
|
|
3153
3518
|
const configs = [];
|
|
3154
|
-
if (
|
|
3519
|
+
if (existsSync10(GLOBAL_HOOKS)) {
|
|
3155
3520
|
try {
|
|
3156
3521
|
const parsed = parseYaml2(readFileSync6(GLOBAL_HOOKS, "utf-8"));
|
|
3157
3522
|
if (parsed) configs.push(parsed);
|
|
@@ -3413,17 +3778,17 @@ function formatDur(ms) {
|
|
|
3413
3778
|
|
|
3414
3779
|
// src/commands/template.ts
|
|
3415
3780
|
import chalk8 from "chalk";
|
|
3416
|
-
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as
|
|
3417
|
-
import { join as
|
|
3418
|
-
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";
|
|
3419
3784
|
import { parse as parseYaml3, stringify as stringifyYaml2 } from "yaml";
|
|
3420
|
-
var CONFIG_DIR2 =
|
|
3421
|
-
var TEMPLATES_FILE =
|
|
3785
|
+
var CONFIG_DIR2 = join13(homedir11(), ".copilot-agent");
|
|
3786
|
+
var TEMPLATES_FILE = join13(CONFIG_DIR2, "templates.yaml");
|
|
3422
3787
|
function ensureDir3() {
|
|
3423
|
-
if (!
|
|
3788
|
+
if (!existsSync11(CONFIG_DIR2)) mkdirSync7(CONFIG_DIR2, { recursive: true });
|
|
3424
3789
|
}
|
|
3425
3790
|
function loadTemplates() {
|
|
3426
|
-
if (!
|
|
3791
|
+
if (!existsSync11(TEMPLATES_FILE)) return [];
|
|
3427
3792
|
try {
|
|
3428
3793
|
const data = parseYaml3(readFileSync7(TEMPLATES_FILE, "utf-8"));
|
|
3429
3794
|
return Array.isArray(data) ? data : [];
|
|
@@ -3653,15 +4018,15 @@ function fmtDur2(ms) {
|
|
|
3653
4018
|
}
|
|
3654
4019
|
|
|
3655
4020
|
// src/commands/multi.ts
|
|
3656
|
-
import { existsSync as
|
|
3657
|
-
import { join as
|
|
3658
|
-
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";
|
|
3659
4024
|
import chalk10 from "chalk";
|
|
3660
4025
|
import { parse as parseYaml4, stringify as stringifyYaml3 } from "yaml";
|
|
3661
|
-
var CONFIG_DIR3 =
|
|
3662
|
-
var PROJECTS_FILE =
|
|
3663
|
-
var STATUS_FILE =
|
|
3664
|
-
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");
|
|
3665
4030
|
var MAX_CONCURRENCY = 3;
|
|
3666
4031
|
function registerMultiCommand(program2) {
|
|
3667
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) => {
|
|
@@ -3704,7 +4069,7 @@ async function multiCommand(action, args, opts) {
|
|
|
3704
4069
|
function ensureFiles() {
|
|
3705
4070
|
mkdirSync8(LOG_DIR, { recursive: true });
|
|
3706
4071
|
mkdirSync8(CONFIG_DIR3, { recursive: true });
|
|
3707
|
-
if (!
|
|
4072
|
+
if (!existsSync12(PROJECTS_FILE)) writeFileSync7(PROJECTS_FILE, "");
|
|
3708
4073
|
}
|
|
3709
4074
|
function readProjects() {
|
|
3710
4075
|
return readFileSync8(PROJECTS_FILE, "utf-8").split("\n").map((l) => l.trim()).filter(Boolean);
|
|
@@ -3713,7 +4078,7 @@ function writeProjects(projects) {
|
|
|
3713
4078
|
writeFileSync7(PROJECTS_FILE, projects.join("\n") + "\n");
|
|
3714
4079
|
}
|
|
3715
4080
|
function readStatusFile() {
|
|
3716
|
-
if (!
|
|
4081
|
+
if (!existsSync12(STATUS_FILE)) return [];
|
|
3717
4082
|
try {
|
|
3718
4083
|
const raw = readFileSync8(STATUS_FILE, "utf-8");
|
|
3719
4084
|
const parsed = parseYaml4(raw);
|
|
@@ -3747,7 +4112,7 @@ async function addProject(path) {
|
|
|
3747
4112
|
process.exit(1);
|
|
3748
4113
|
}
|
|
3749
4114
|
const resolved = resolve7(path);
|
|
3750
|
-
if (!
|
|
4115
|
+
if (!existsSync12(resolved)) {
|
|
3751
4116
|
fail(`Not found: ${resolved}`);
|
|
3752
4117
|
process.exit(1);
|
|
3753
4118
|
}
|
|
@@ -3789,7 +4154,7 @@ function listProjects() {
|
|
|
3789
4154
|
console.log(chalk10.bold("\nRegistered projects:"));
|
|
3790
4155
|
for (let i = 0; i < projects.length; i++) {
|
|
3791
4156
|
const p = projects[i];
|
|
3792
|
-
const exists =
|
|
4157
|
+
const exists = existsSync12(p);
|
|
3793
4158
|
const icon = exists ? chalk10.green("\u2705") : chalk10.red("\u274C");
|
|
3794
4159
|
const type = exists ? detectProjectType(p) : "?";
|
|
3795
4160
|
const st = statuses.find((s) => s.project === p);
|
|
@@ -3808,7 +4173,7 @@ function showStatus() {
|
|
|
3808
4173
|
for (const s of statuses) {
|
|
3809
4174
|
const icon = s.status === "success" ? chalk10.green("\u2705") : s.status === "failed" ? chalk10.red("\u274C") : chalk10.yellow("\u{1F504}");
|
|
3810
4175
|
console.log(
|
|
3811
|
-
` ${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)}`
|
|
3812
4177
|
);
|
|
3813
4178
|
}
|
|
3814
4179
|
console.log();
|
|
@@ -3838,7 +4203,7 @@ function createSemaphore(max) {
|
|
|
3838
4203
|
async function runAll(mode, opts) {
|
|
3839
4204
|
assertAgent(opts.agent);
|
|
3840
4205
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").slice(0, 15);
|
|
3841
|
-
setLogFile(
|
|
4206
|
+
setLogFile(join14(LOG_DIR, `multi-${mode}-${ts}.log`));
|
|
3842
4207
|
const projects = readProjects();
|
|
3843
4208
|
if (projects.length === 0) {
|
|
3844
4209
|
fail("No projects registered. Add: copilot-agent multi add <path>");
|
|
@@ -3847,10 +4212,10 @@ async function runAll(mode, opts) {
|
|
|
3847
4212
|
log(`\u{1F3ED} Multi-project ${mode} \u2014 ${projects.length} projects \u2014 agent: ${opts.agent}${opts.parallel ? " (parallel)" : ""}`);
|
|
3848
4213
|
if (opts.dryRun) {
|
|
3849
4214
|
for (const p of projects) {
|
|
3850
|
-
const type =
|
|
4215
|
+
const type = existsSync12(p) ? detectProjectType(p) : "unknown";
|
|
3851
4216
|
const tasks = getTasksForProject(type);
|
|
3852
4217
|
console.log(`
|
|
3853
|
-
${chalk10.bold(
|
|
4218
|
+
${chalk10.bold(basename4(p))} (${type})`);
|
|
3854
4219
|
for (const t of tasks.slice(0, 3)) {
|
|
3855
4220
|
console.log(` ${chalk10.dim("\u2022")} ${t.title}`);
|
|
3856
4221
|
}
|
|
@@ -3896,8 +4261,8 @@ ${"\u2550".repeat(50)}`);
|
|
|
3896
4261
|
notify(`Multi-${mode}: ${success}/${total} succeeded`, "copilot-agent");
|
|
3897
4262
|
}
|
|
3898
4263
|
async function runSingleProject(project, mode, opts) {
|
|
3899
|
-
const name =
|
|
3900
|
-
if (!
|
|
4264
|
+
const name = existsSync12(project) ? detectProjectName(project) : basename4(project);
|
|
4265
|
+
if (!existsSync12(project)) {
|
|
3901
4266
|
warn(`Skipping (not found): ${project}`);
|
|
3902
4267
|
return { name, success: false, skipped: true };
|
|
3903
4268
|
}
|
|
@@ -4077,17 +4442,17 @@ function registerReviewCommand(program2) {
|
|
|
4077
4442
|
}
|
|
4078
4443
|
|
|
4079
4444
|
// src/commands/schedule.ts
|
|
4080
|
-
import { readFileSync as readFileSync9, writeFileSync as writeFileSync8, existsSync as
|
|
4081
|
-
import { join as
|
|
4082
|
-
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";
|
|
4083
4448
|
import { parse as parseYaml5, stringify as stringifyYaml4 } from "yaml";
|
|
4084
|
-
var CONFIG_DIR4 =
|
|
4085
|
-
var SCHEDULES_FILE =
|
|
4449
|
+
var CONFIG_DIR4 = join15(homedir13(), ".copilot-agent");
|
|
4450
|
+
var SCHEDULES_FILE = join15(CONFIG_DIR4, "schedules.yaml");
|
|
4086
4451
|
function ensureConfigDir2() {
|
|
4087
|
-
if (!
|
|
4452
|
+
if (!existsSync13(CONFIG_DIR4)) mkdirSync9(CONFIG_DIR4, { recursive: true });
|
|
4088
4453
|
}
|
|
4089
4454
|
function loadSchedules() {
|
|
4090
|
-
if (!
|
|
4455
|
+
if (!existsSync13(SCHEDULES_FILE)) return [];
|
|
4091
4456
|
try {
|
|
4092
4457
|
const raw = parseYaml5(readFileSync9(SCHEDULES_FILE, "utf-8"));
|
|
4093
4458
|
return Array.isArray(raw) ? raw : [];
|
|
@@ -4326,7 +4691,7 @@ function registerScheduleCommand(program2) {
|
|
|
4326
4691
|
|
|
4327
4692
|
// src/index.ts
|
|
4328
4693
|
var program = new Command();
|
|
4329
|
-
program.name("copilot-agent").version("1.0
|
|
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.");
|
|
4330
4695
|
registerStatusCommand(program);
|
|
4331
4696
|
registerWatchCommand(program);
|
|
4332
4697
|
registerRunCommand(program);
|