@versot/vaguspi 0.1.2 → 0.1.4
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/bin.js +302 -25
- package/dist/bin.js.map +3 -3
- package/gui/assets/index-Dl4o9eCz.js +19 -0
- package/gui/assets/settings-V9zrDr2c.js +60 -0
- package/gui/assets/vendor-CkEpMl6t.js +40 -0
- package/gui/index.html +7 -3
- package/package.json +1 -1
- package/gui/assets/index-BC6qaWvB.js +0 -26
- package/gui/assets/index-CBwrpcqx.js +0 -60
- package/gui/assets/vendor-BtP0CW_r.js +0 -32
package/dist/bin.js
CHANGED
|
@@ -8,7 +8,8 @@ import { parseArgs } from "node:util";
|
|
|
8
8
|
import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync6, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { homedir as homedir2 } from "node:os";
|
|
11
|
-
import { join as join8, normalize, resolve as resolve3, sep as
|
|
11
|
+
import { join as join8, normalize, resolve as resolve3, sep as sep3 } from "node:path";
|
|
12
|
+
import { EnvHttpProxyAgent, setGlobalDispatcher } from "undici";
|
|
12
13
|
|
|
13
14
|
// packages/host/config/dist/config-store.js
|
|
14
15
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
@@ -301,8 +302,9 @@ var VagModelsStore = class {
|
|
|
301
302
|
// packages/host/engine/dist/vagus-engine.js
|
|
302
303
|
import { createAgentSession, DefaultResourceLoader, ModelRuntime, SessionManager as SessionManager2, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
303
304
|
import { homedir } from "node:os";
|
|
304
|
-
import { basename, dirname as dirname3, isAbsolute as isAbsolute2, join as join5, resolve } from "node:path";
|
|
305
|
-
import {
|
|
305
|
+
import { basename, dirname as dirname3, isAbsolute as isAbsolute2, join as join5, resolve, sep } from "node:path";
|
|
306
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
307
|
+
import { appendFileSync as appendFileSync2, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync4, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
306
308
|
|
|
307
309
|
// packages/host/engine/dist/display-utils.js
|
|
308
310
|
import * as Diff from "diff";
|
|
@@ -448,6 +450,10 @@ function messageToText(message) {
|
|
|
448
450
|
|
|
449
451
|
// packages/host/engine/dist/usage-stats.js
|
|
450
452
|
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
453
|
+
function dayAt(ts) {
|
|
454
|
+
const d = new Date(ts);
|
|
455
|
+
return new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
|
|
456
|
+
}
|
|
451
457
|
async function aggregateUsageStats() {
|
|
452
458
|
const infos = await SessionManager.listAll();
|
|
453
459
|
let totalTokens = 0;
|
|
@@ -461,11 +467,10 @@ async function aggregateUsageStats() {
|
|
|
461
467
|
const activeDates = /* @__PURE__ */ new Set();
|
|
462
468
|
const DAY = 864e5;
|
|
463
469
|
const dayMap = /* @__PURE__ */ new Map();
|
|
464
|
-
const dayAt = (ts) => Math.floor(ts / DAY);
|
|
465
470
|
const ensureDay = (day) => {
|
|
466
471
|
let p = dayMap.get(day);
|
|
467
472
|
if (!p) {
|
|
468
|
-
p = { tokens: 0, messages: 0, sessions: 0, byModel: /* @__PURE__ */ new Map() };
|
|
473
|
+
p = { tokens: 0, messages: 0, sessions: 0, cost: 0, byModel: /* @__PURE__ */ new Map() };
|
|
469
474
|
dayMap.set(day, p);
|
|
470
475
|
}
|
|
471
476
|
return p;
|
|
@@ -489,12 +494,13 @@ async function aggregateUsageStats() {
|
|
|
489
494
|
if (m.role !== "assistant" || m.usage === void 0)
|
|
490
495
|
continue;
|
|
491
496
|
const u = m.usage;
|
|
492
|
-
const tokens =
|
|
497
|
+
const tokens = Number(u.input ?? 0) + Number(u.output ?? 0);
|
|
493
498
|
const cost = typeof u.cost?.total === "number" ? u.cost.total : 0;
|
|
494
499
|
totalTokens += tokens;
|
|
495
500
|
totalCost += cost;
|
|
496
501
|
sessionTokens += tokens;
|
|
497
502
|
dayPoint.tokens += tokens;
|
|
503
|
+
dayPoint.cost += cost;
|
|
498
504
|
const key = `${String(m.provider ?? "unknown")}/${String(m.model ?? "unknown")}`;
|
|
499
505
|
const cur = byModel.get(key) ?? { tokens: 0, cost: 0 };
|
|
500
506
|
byModel.set(key, { tokens: cur.tokens + tokens, cost: cur.cost + cost });
|
|
@@ -513,8 +519,9 @@ async function aggregateUsageStats() {
|
|
|
513
519
|
}
|
|
514
520
|
}
|
|
515
521
|
const daily = [...dayMap.entries()].toSorted((a, b) => a[0] - b[0]).map(([day, p]) => ({
|
|
516
|
-
ts: day
|
|
522
|
+
ts: day,
|
|
517
523
|
tokens: p.tokens,
|
|
524
|
+
cost: p.cost,
|
|
518
525
|
messages: p.messages,
|
|
519
526
|
sessions: p.sessions,
|
|
520
527
|
byModel: Object.fromEntries(p.byModel)
|
|
@@ -524,14 +531,14 @@ async function aggregateUsageStats() {
|
|
|
524
531
|
let longestStreak = 0;
|
|
525
532
|
let run = 0;
|
|
526
533
|
for (let i = 0; i < dates.length; i++) {
|
|
527
|
-
run = i === 0 || dates[i] === (dates[i - 1] ?? 0) +
|
|
534
|
+
run = i === 0 || dates[i] === (dates[i - 1] ?? 0) + DAY ? run + 1 : 1;
|
|
528
535
|
if (run > longestStreak)
|
|
529
536
|
longestStreak = run;
|
|
530
537
|
}
|
|
531
|
-
const today =
|
|
538
|
+
const today = new Date((/* @__PURE__ */ new Date()).getFullYear(), (/* @__PURE__ */ new Date()).getMonth(), (/* @__PURE__ */ new Date()).getDate()).getTime();
|
|
532
539
|
let currentStreak = 0;
|
|
533
540
|
for (let i = dates.length - 1; i >= 0; i--) {
|
|
534
|
-
if (dates[i] === today - currentStreak) {
|
|
541
|
+
if (dates[i] === today - currentStreak * DAY) {
|
|
535
542
|
currentStreak++;
|
|
536
543
|
} else {
|
|
537
544
|
break;
|
|
@@ -1691,9 +1698,17 @@ var VagusEngine = class {
|
|
|
1691
1698
|
closeSessionForCwd(cwd) {
|
|
1692
1699
|
for (const [sid, session] of this.sessions) {
|
|
1693
1700
|
if (session.sessionManager?.getCwd() === cwd) {
|
|
1694
|
-
|
|
1701
|
+
process.stderr.write(`vagus: disposing session ${sid} (cwd=${cwd})
|
|
1702
|
+
`);
|
|
1703
|
+
try {
|
|
1704
|
+
session.dispose();
|
|
1705
|
+
} catch (err) {
|
|
1706
|
+
process.stderr.write(`vagus: dispose threw: ${err instanceof Error ? err.stack : String(err)}
|
|
1707
|
+
`);
|
|
1708
|
+
}
|
|
1695
1709
|
this.sessions.delete(sid);
|
|
1696
|
-
void this.options.bus.emit("session.closed", { type: "session.closed", sessionId: sid })
|
|
1710
|
+
void this.options.bus.emit("session.closed", { type: "session.closed", sessionId: sid }).catch(() => {
|
|
1711
|
+
});
|
|
1697
1712
|
}
|
|
1698
1713
|
}
|
|
1699
1714
|
}
|
|
@@ -1755,6 +1770,37 @@ var VagusEngine = class {
|
|
|
1755
1770
|
}
|
|
1756
1771
|
}
|
|
1757
1772
|
/** Restores an archived project: moves its session files back under `sessions/`. */
|
|
1773
|
+
/** Restores an archived project by its encoded dir name (unique identity). */
|
|
1774
|
+
async unarchiveProjectByDir(dirKey) {
|
|
1775
|
+
if (!dirKey || dirKey.includes("/") || dirKey.includes("\\") || dirKey.includes(".."))
|
|
1776
|
+
return;
|
|
1777
|
+
const agentDir = this.agentDirPath();
|
|
1778
|
+
const src = join5(agentDir, "archived", dirKey);
|
|
1779
|
+
if (!existsSync3(src))
|
|
1780
|
+
return;
|
|
1781
|
+
let cwd;
|
|
1782
|
+
try {
|
|
1783
|
+
cwd = this.sessionCwd(readdirSync2(src).filter((f) => f.endsWith(".jsonl"))[0] ? join5(src, readdirSync2(src).filter((f) => f.endsWith(".jsonl"))[0]) : "");
|
|
1784
|
+
} catch {
|
|
1785
|
+
cwd = void 0;
|
|
1786
|
+
}
|
|
1787
|
+
if (cwd) {
|
|
1788
|
+
this.closeSessionForCwd(cwd);
|
|
1789
|
+
const dst = join5(agentDir, "sessions", this.encodeCwd(cwd));
|
|
1790
|
+
mkdirSync4(dst, { recursive: true });
|
|
1791
|
+
for (const entry of readdirSync2(src)) {
|
|
1792
|
+
if (!entry.endsWith(".jsonl"))
|
|
1793
|
+
continue;
|
|
1794
|
+
const from = join5(src, entry);
|
|
1795
|
+
try {
|
|
1796
|
+
if (statSync(from).isFile())
|
|
1797
|
+
renameSync(from, join5(dst, entry));
|
|
1798
|
+
} catch {
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
this.rmDirSafe(src);
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1758
1804
|
async restoreProject(cwd) {
|
|
1759
1805
|
const agentDir = this.agentDirPath();
|
|
1760
1806
|
const src = join5(agentDir, "archived", this.encodeCwd(cwd));
|
|
@@ -1773,16 +1819,99 @@ var VagusEngine = class {
|
|
|
1773
1819
|
} catch {
|
|
1774
1820
|
}
|
|
1775
1821
|
}
|
|
1776
|
-
|
|
1822
|
+
this.rmDirSafe(src);
|
|
1823
|
+
}
|
|
1824
|
+
/** Permanently deletes an archived project dir by its encoded dir name.
|
|
1825
|
+
* dirKey (not cwd) is the identity — cwds can repeat across archive dirs. */
|
|
1826
|
+
async deleteArchivedProjectByDir(dirKey) {
|
|
1827
|
+
if (!dirKey || dirKey.includes("/") || dirKey.includes("\\") || dirKey.includes(".."))
|
|
1828
|
+
return;
|
|
1829
|
+
const agentDir = this.agentDirPath();
|
|
1830
|
+
const dir = join5(agentDir, "archived", dirKey);
|
|
1831
|
+
process.stderr.write(`vagus: deleteArchivedProjectByDir dirKey=${dirKey}
|
|
1832
|
+
`);
|
|
1833
|
+
const prefix = dir + sep;
|
|
1834
|
+
for (const [sid, session] of this.sessions) {
|
|
1835
|
+
const file = session.sessionManager?.getSessionFile();
|
|
1836
|
+
if (file && (file === dir || file.startsWith(prefix))) {
|
|
1837
|
+
try {
|
|
1838
|
+
session.dispose();
|
|
1839
|
+
} catch {
|
|
1840
|
+
}
|
|
1841
|
+
this.sessions.delete(sid);
|
|
1842
|
+
void this.options.bus.emit("session.closed", { type: "session.closed", sessionId: sid }).catch(() => {
|
|
1843
|
+
});
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
process.stderr.write(`vagus: sessions closed, removing dir...
|
|
1847
|
+
`);
|
|
1848
|
+
this.rmDirSafe(dir);
|
|
1849
|
+
process.stderr.write(`vagus: archived dir removed OK
|
|
1850
|
+
`);
|
|
1777
1851
|
}
|
|
1778
1852
|
/** Permanently deletes an archived project's session dir (JSONL). */
|
|
1779
1853
|
async deleteArchivedProject(cwd) {
|
|
1780
1854
|
const agentDir = this.agentDirPath();
|
|
1781
1855
|
const dir = join5(agentDir, "archived", this.encodeCwd(cwd));
|
|
1856
|
+
process.stderr.write(`vagus: deleteArchivedProject cwd=${cwd} dir=${dir}
|
|
1857
|
+
`);
|
|
1782
1858
|
this.closeSessionForCwd(cwd);
|
|
1783
|
-
|
|
1859
|
+
process.stderr.write(`vagus: sessions closed, removing dir...
|
|
1860
|
+
`);
|
|
1861
|
+
this.rmDirSafe(dir);
|
|
1862
|
+
process.stderr.write(`vagus: archived dir removed OK
|
|
1863
|
+
`);
|
|
1864
|
+
}
|
|
1865
|
+
/**
|
|
1866
|
+
* rmSync(recursive) has crashed the daemon natively (0xC0000409 fastfail)
|
|
1867
|
+
* on Windows when deleting archived dirs with non-ASCII names — JS guards
|
|
1868
|
+
* cannot catch a native abort. Delete file-by-file instead: plain unlink
|
|
1869
|
+
* calls are individually catchable and skip whatever is locked/unreadable.
|
|
1870
|
+
*
|
|
1871
|
+
* Iterative (explicit stack, deepest-first) — no JS recursion limit, so
|
|
1872
|
+
* arbitrarily deep trees are safe.
|
|
1873
|
+
*/
|
|
1874
|
+
rmDirSafe(root) {
|
|
1875
|
+
const pendingDirs = [{ path: root, expanded: false }];
|
|
1876
|
+
while (pendingDirs.length > 0) {
|
|
1877
|
+
const top = pendingDirs[pendingDirs.length - 1];
|
|
1878
|
+
if (top.expanded) {
|
|
1879
|
+
pendingDirs.pop();
|
|
1880
|
+
try {
|
|
1881
|
+
rmdirSync(top.path);
|
|
1882
|
+
} catch (err) {
|
|
1883
|
+
process.stderr.write(`vagus: rmDirSafe rmdir failed ${top.path}: ${err instanceof Error ? err.message : String(err)}
|
|
1884
|
+
`);
|
|
1885
|
+
}
|
|
1886
|
+
continue;
|
|
1887
|
+
}
|
|
1888
|
+
let entries;
|
|
1889
|
+
try {
|
|
1890
|
+
entries = readdirSync2(top.path);
|
|
1891
|
+
} catch {
|
|
1892
|
+
pendingDirs.pop();
|
|
1893
|
+
continue;
|
|
1894
|
+
}
|
|
1895
|
+
top.expanded = true;
|
|
1896
|
+
for (const entry of entries) {
|
|
1897
|
+
const full = join5(top.path, entry);
|
|
1898
|
+
try {
|
|
1899
|
+
if (statSync(full).isDirectory()) {
|
|
1900
|
+
pendingDirs.push({ path: full, expanded: false });
|
|
1901
|
+
} else {
|
|
1902
|
+
unlinkSync(full);
|
|
1903
|
+
}
|
|
1904
|
+
} catch (err) {
|
|
1905
|
+
process.stderr.write(`vagus: rmDirSafe skipped ${full}: ${err instanceof Error ? err.message : String(err)}
|
|
1906
|
+
`);
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1784
1910
|
}
|
|
1785
1911
|
/** Lists archived projects with their sessions (scans the `archived/` dir). */
|
|
1912
|
+
/** Lists archived projects with their sessions (scans the `archived/` dir).
|
|
1913
|
+
* dirKey is the encoded directory name — the unique identity of an archived
|
|
1914
|
+
* group (cwd from session headers can repeat across dirs). */
|
|
1786
1915
|
async listArchivedProjects() {
|
|
1787
1916
|
const agentDir = this.agentDirPath();
|
|
1788
1917
|
const root = join5(agentDir, "archived");
|
|
@@ -1800,6 +1929,7 @@ var VagusEngine = class {
|
|
|
1800
1929
|
continue;
|
|
1801
1930
|
out.push({
|
|
1802
1931
|
cwd,
|
|
1932
|
+
dirKey: entry,
|
|
1803
1933
|
sessions: infos.map((i) => ({
|
|
1804
1934
|
id: i.id,
|
|
1805
1935
|
path: i.path,
|
|
@@ -1882,6 +2012,54 @@ var VagusEngine = class {
|
|
|
1882
2012
|
const result = await session.navigateTree(targetId, options);
|
|
1883
2013
|
return { editorText: result.editorText, cancelled: result.cancelled };
|
|
1884
2014
|
}
|
|
2015
|
+
/**
|
|
2016
|
+
* Creates a new session forked from a specific user message.
|
|
2017
|
+
* The new session contains only the conversation up to and including that
|
|
2018
|
+
* user message — everything after it is dropped. The original session is
|
|
2019
|
+
* untouched. Returns the new session's info (sessionId, sessionFile, cwd).
|
|
2020
|
+
*/
|
|
2021
|
+
async forkSession(sessionId, entryId) {
|
|
2022
|
+
const session = this.requireSession(sessionId);
|
|
2023
|
+
const sourceFile = session.sessionManager?.getSessionFile();
|
|
2024
|
+
if (!sourceFile)
|
|
2025
|
+
throw new Error("source session has no file path");
|
|
2026
|
+
if (!existsSync3(sourceFile))
|
|
2027
|
+
throw new Error(`source session file not found: ${sourceFile}`);
|
|
2028
|
+
const raw = readFileSync4(sourceFile, "utf8");
|
|
2029
|
+
const lines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
2030
|
+
const entries = lines.map((l) => JSON.parse(l));
|
|
2031
|
+
const header = entries[0];
|
|
2032
|
+
if (!header || header.type !== "session")
|
|
2033
|
+
throw new Error("source session has no header");
|
|
2034
|
+
const targetIdx = entries.findIndex((e) => e.id === entryId);
|
|
2035
|
+
if (targetIdx < 0)
|
|
2036
|
+
throw new Error(`entry ${entryId} not found in source session`);
|
|
2037
|
+
let endIdx = targetIdx - 1;
|
|
2038
|
+
if (endIdx < 1)
|
|
2039
|
+
endIdx = 1;
|
|
2040
|
+
const cwd = session.sessionManager.getCwd() || this.options.cwd;
|
|
2041
|
+
const sessionDir = dirname3(sourceFile);
|
|
2042
|
+
mkdirSync4(sessionDir, { recursive: true });
|
|
2043
|
+
const newSessionId = randomUUID2();
|
|
2044
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2045
|
+
const fileTimestamp = timestamp.replace(/[:.]/g, "-");
|
|
2046
|
+
const newSessionFile = join5(sessionDir, `${fileTimestamp}_${newSessionId}.jsonl`);
|
|
2047
|
+
const newHeader = {
|
|
2048
|
+
type: "session",
|
|
2049
|
+
version: header.version ?? 1,
|
|
2050
|
+
id: newSessionId,
|
|
2051
|
+
timestamp,
|
|
2052
|
+
cwd,
|
|
2053
|
+
parentSession: sourceFile
|
|
2054
|
+
};
|
|
2055
|
+
const outLines = [JSON.stringify(newHeader)];
|
|
2056
|
+
for (let i = 1; i <= endIdx; i++) {
|
|
2057
|
+
outLines.push(JSON.stringify(entries[i]));
|
|
2058
|
+
}
|
|
2059
|
+
writeFileSync3(newSessionFile, outLines.join("\n") + "\n", "utf8");
|
|
2060
|
+
const manager = SessionManager2.open(newSessionFile);
|
|
2061
|
+
return this.startSession({ cwd, sessionManager: manager });
|
|
2062
|
+
}
|
|
1885
2063
|
/**
|
|
1886
2064
|
* Exports the active branch to JSONL (/export). Returns the output path.
|
|
1887
2065
|
*/
|
|
@@ -6557,7 +6735,7 @@ import { WebSocketServer as WsServer } from "ws";
|
|
|
6557
6735
|
|
|
6558
6736
|
// packages/host/rpc/dist/static-server.js
|
|
6559
6737
|
import { createReadStream, existsSync as existsSync4, statSync as statSync2 } from "node:fs";
|
|
6560
|
-
import { extname, join as join6, resolve as resolve2, sep } from "node:path";
|
|
6738
|
+
import { extname, join as join6, resolve as resolve2, sep as sep2 } from "node:path";
|
|
6561
6739
|
var MIME_TYPES = {
|
|
6562
6740
|
".html": "text/html; charset=utf-8",
|
|
6563
6741
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -6576,7 +6754,7 @@ function resolveStaticFile(rootDir, pathname) {
|
|
|
6576
6754
|
const root = resolve2(rootDir);
|
|
6577
6755
|
const name = pathname === "/" ? "index.html" : pathname;
|
|
6578
6756
|
const candidate = resolve2(join6(root, name));
|
|
6579
|
-
if (candidate !== root && !candidate.startsWith(root +
|
|
6757
|
+
if (candidate !== root && !candidate.startsWith(root + sep2)) {
|
|
6580
6758
|
return void 0;
|
|
6581
6759
|
}
|
|
6582
6760
|
if (existsSync4(candidate)) {
|
|
@@ -6901,6 +7079,28 @@ function requireString(value, label) {
|
|
|
6901
7079
|
}
|
|
6902
7080
|
return value;
|
|
6903
7081
|
}
|
|
7082
|
+
function applyHttpProxy() {
|
|
7083
|
+
try {
|
|
7084
|
+
let proxy;
|
|
7085
|
+
const settingsFile = join8(piAgentDir(), "settings.json");
|
|
7086
|
+
if (existsSync6(settingsFile)) {
|
|
7087
|
+
const s = JSON.parse(readFileSync6(settingsFile, "utf8"));
|
|
7088
|
+
if (typeof s.httpProxy === "string" && s.httpProxy.trim()) proxy = s.httpProxy.trim();
|
|
7089
|
+
}
|
|
7090
|
+
proxy ??= process.env.HTTPS_PROXY ?? process.env.HTTP_PROXY;
|
|
7091
|
+
if (proxy) {
|
|
7092
|
+
const normalized = /^https?:\/\//.test(proxy) ? proxy : `http://${proxy}`;
|
|
7093
|
+
process.env.HTTPS_PROXY = normalized;
|
|
7094
|
+
process.env.HTTP_PROXY = normalized;
|
|
7095
|
+
setGlobalDispatcher(new EnvHttpProxyAgent());
|
|
7096
|
+
process.stderr.write(`vagus: http proxy enabled \u2192 ${normalized}
|
|
7097
|
+
`);
|
|
7098
|
+
}
|
|
7099
|
+
} catch (err) {
|
|
7100
|
+
process.stderr.write(`vagus: proxy setup skipped: ${err instanceof Error ? err.message : String(err)}
|
|
7101
|
+
`);
|
|
7102
|
+
}
|
|
7103
|
+
}
|
|
6904
7104
|
function probeRequestFor(apiType, url, apiKey, modelId) {
|
|
6905
7105
|
const headers = { "Content-Type": "application/json" };
|
|
6906
7106
|
const base = url.replace(/\/chat\/completions$/, "");
|
|
@@ -6934,6 +7134,7 @@ function builtinExtensionPaths() {
|
|
|
6934
7134
|
return out;
|
|
6935
7135
|
}
|
|
6936
7136
|
async function runDaemon() {
|
|
7137
|
+
applyHttpProxy();
|
|
6937
7138
|
const stateDir = defaultStateDir();
|
|
6938
7139
|
mkdirSync6(stateDir, { recursive: true });
|
|
6939
7140
|
const config = new ConfigStore({ dir: stateDir });
|
|
@@ -6992,19 +7193,21 @@ async function runDaemon() {
|
|
|
6992
7193
|
return host.archiveProject(cwd);
|
|
6993
7194
|
});
|
|
6994
7195
|
srv.registerMethod("project.unarchive", (params) => {
|
|
6995
|
-
const cwd =
|
|
6996
|
-
return host.
|
|
7196
|
+
const { cwd, dirKey } = params ?? {};
|
|
7197
|
+
if (typeof dirKey === "string" && dirKey) return host.unarchiveProjectByDir(dirKey);
|
|
7198
|
+
return host.restoreProject(requireString(cwd, "cwd"));
|
|
6997
7199
|
});
|
|
6998
7200
|
srv.registerMethod("project.archived", () => host.listArchivedProjects());
|
|
6999
7201
|
srv.registerMethod("project.delete", (params) => {
|
|
7000
|
-
const cwd =
|
|
7001
|
-
return host.
|
|
7202
|
+
const { cwd, dirKey } = params ?? {};
|
|
7203
|
+
if (typeof dirKey === "string" && dirKey) return host.deleteArchivedProjectByDir(dirKey);
|
|
7204
|
+
return host.deleteArchivedProject(requireString(cwd, "cwd"));
|
|
7002
7205
|
});
|
|
7003
7206
|
srv.registerMethod("project.roots", () => {
|
|
7004
7207
|
const home = homedir2();
|
|
7005
7208
|
const places = [
|
|
7006
7209
|
{ name: "Home", path: home, isDirectory: true },
|
|
7007
|
-
{ name: "Root", path:
|
|
7210
|
+
{ name: "Root", path: sep3, isDirectory: true }
|
|
7008
7211
|
];
|
|
7009
7212
|
const candidates = ["Desktop", "Downloads", "Documents", "Pictures", "Music", "Videos"];
|
|
7010
7213
|
for (const name of candidates) {
|
|
@@ -7377,6 +7580,10 @@ async function runDaemon() {
|
|
|
7377
7580
|
const { sessionId } = params ?? {};
|
|
7378
7581
|
return host.listForkPoints(requireString(sessionId, "sessionId"));
|
|
7379
7582
|
});
|
|
7583
|
+
srv.registerMethod("session.fork", async (params) => {
|
|
7584
|
+
const { sessionId, entryId } = params ?? {};
|
|
7585
|
+
return host.forkSession(requireString(sessionId, "sessionId"), requireString(entryId, "entryId"));
|
|
7586
|
+
});
|
|
7380
7587
|
srv.registerMethod("session.tree", (params) => {
|
|
7381
7588
|
const { sessionId } = params ?? {};
|
|
7382
7589
|
return host.getSessionTree(requireString(sessionId, "sessionId"));
|
|
@@ -7479,9 +7686,12 @@ async function runDaemon() {
|
|
|
7479
7686
|
wsHost.broadcast(event);
|
|
7480
7687
|
});
|
|
7481
7688
|
let shuttingDown = false;
|
|
7482
|
-
const shutdown = () => {
|
|
7689
|
+
const shutdown = (signal) => {
|
|
7483
7690
|
if (shuttingDown) return;
|
|
7484
7691
|
shuttingDown = true;
|
|
7692
|
+
process.stderr.write(`vagus: daemon shutting down (${signal})
|
|
7693
|
+
${new Error("shutdown trace").stack}
|
|
7694
|
+
`);
|
|
7485
7695
|
void (async () => {
|
|
7486
7696
|
try {
|
|
7487
7697
|
wsHost.close();
|
|
@@ -7492,11 +7702,27 @@ async function runDaemon() {
|
|
|
7492
7702
|
}
|
|
7493
7703
|
})();
|
|
7494
7704
|
};
|
|
7495
|
-
process.on("SIGINT", shutdown);
|
|
7496
|
-
process.on("SIGTERM", shutdown);
|
|
7705
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
7706
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
7707
|
+
process.on("beforeExit", (code) => {
|
|
7708
|
+
process.stderr.write(`vagus: daemon beforeExit (event loop empty), code=${code}
|
|
7709
|
+
`);
|
|
7710
|
+
});
|
|
7711
|
+
process.on("exit", (code) => {
|
|
7712
|
+
process.stderr.write(`vagus: daemon exit, code=${code}
|
|
7713
|
+
`);
|
|
7714
|
+
});
|
|
7497
7715
|
transport.start();
|
|
7498
7716
|
process.stderr.write(`pi-web daemon ready (state: ${stateDir})
|
|
7499
7717
|
`);
|
|
7718
|
+
process.on("uncaughtException", (err) => {
|
|
7719
|
+
process.stderr.write(`vagus: uncaught exception: ${err?.stack ?? String(err)}
|
|
7720
|
+
`);
|
|
7721
|
+
});
|
|
7722
|
+
process.on("unhandledRejection", (reason) => {
|
|
7723
|
+
process.stderr.write(`vagus: unhandled rejection: ${reason instanceof Error ? reason.stack ?? reason.message : String(reason)}
|
|
7724
|
+
`);
|
|
7725
|
+
});
|
|
7500
7726
|
return new Promise(() => {
|
|
7501
7727
|
});
|
|
7502
7728
|
}
|
|
@@ -7509,8 +7735,38 @@ import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
|
7509
7735
|
|
|
7510
7736
|
// apps/cli/src/daemon.ts
|
|
7511
7737
|
import { spawn } from "node:child_process";
|
|
7738
|
+
import { execSync } from "node:child_process";
|
|
7512
7739
|
import { existsSync as existsSync7 } from "node:fs";
|
|
7513
7740
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
7741
|
+
function detectSystemProxy() {
|
|
7742
|
+
try {
|
|
7743
|
+
if (process.platform === "win32") {
|
|
7744
|
+
const key = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
|
|
7745
|
+
const out = execSync(`reg query "${key}" /v ProxyEnable`, { encoding: "utf8", timeout: 2e3 });
|
|
7746
|
+
if (!/0x1/.test(out)) return void 0;
|
|
7747
|
+
const srv = execSync(`reg query "${key}" /v ProxyServer`, { encoding: "utf8", timeout: 2e3 });
|
|
7748
|
+
const m = srv.match(/ProxyServer\s+REG_SZ\s+(\S+)/);
|
|
7749
|
+
if (!m?.[1]) return void 0;
|
|
7750
|
+
let addr;
|
|
7751
|
+
if (m[1].includes("=")) {
|
|
7752
|
+
const https = m[1].split(";").find((p) => p.startsWith("https="));
|
|
7753
|
+
addr = (https ?? m[1].split(";")[0])?.split("=")[1] || void 0;
|
|
7754
|
+
} else {
|
|
7755
|
+
addr = m[1];
|
|
7756
|
+
}
|
|
7757
|
+
return addr && /^https?:\/\//.test(addr) ? addr : addr ? `http://${addr}` : void 0;
|
|
7758
|
+
}
|
|
7759
|
+
if (process.platform === "darwin") {
|
|
7760
|
+
const out = execSync("scutil --proxy", { encoding: "utf8", timeout: 2e3 });
|
|
7761
|
+
const enabled = /HTTPSEnable\s*:\s*1/.test(out);
|
|
7762
|
+
const host = out.match(/HTTPSProxy\s*:\s*(\S+)/)?.[1];
|
|
7763
|
+
const port = out.match(/HTTPSPort\s*:\s*(\d+)/)?.[1];
|
|
7764
|
+
return enabled && host && port ? `http://${host}:${port}` : void 0;
|
|
7765
|
+
}
|
|
7766
|
+
} catch {
|
|
7767
|
+
}
|
|
7768
|
+
return void 0;
|
|
7769
|
+
}
|
|
7514
7770
|
function daemonEntryPath() {
|
|
7515
7771
|
const base = fileURLToPath2(new URL("./bin", import.meta.url));
|
|
7516
7772
|
const source = `${base}.ts`;
|
|
@@ -7519,9 +7775,27 @@ function daemonEntryPath() {
|
|
|
7519
7775
|
function spawnDaemon(options = {}) {
|
|
7520
7776
|
const entry = daemonEntryPath();
|
|
7521
7777
|
const args = entry.endsWith(".ts") ? ["--import", "tsx", entry, "daemon"] : [entry, "daemon"];
|
|
7778
|
+
const injected = {};
|
|
7779
|
+
injected.NODE_USE_ENV_PROXY = "1";
|
|
7780
|
+
if (!process.env.HTTPS_PROXY && !process.env.HTTP_PROXY) {
|
|
7781
|
+
const sysProxy = detectSystemProxy();
|
|
7782
|
+
if (sysProxy) {
|
|
7783
|
+
injected.HTTPS_PROXY = sysProxy;
|
|
7784
|
+
injected.HTTP_PROXY = sysProxy;
|
|
7785
|
+
injected.NO_PROXY = process.env.NO_PROXY ?? "localhost,127.0.0.1";
|
|
7786
|
+
process.stderr.write(`vagus: following system proxy \u2192 ${sysProxy}
|
|
7787
|
+
`);
|
|
7788
|
+
} else {
|
|
7789
|
+
process.stderr.write(`vagus: no system proxy detected (direct connections)
|
|
7790
|
+
`);
|
|
7791
|
+
}
|
|
7792
|
+
} else if (process.env.HTTPS_PROXY || process.env.HTTP_PROXY) {
|
|
7793
|
+
process.stderr.write(`vagus: proxy env already set (HTTPS_PROXY=${process.env.HTTPS_PROXY ?? process.env.HTTP_PROXY})
|
|
7794
|
+
`);
|
|
7795
|
+
}
|
|
7522
7796
|
return spawn(process.execPath, args, {
|
|
7523
7797
|
stdio: options.stdio ?? ["pipe", "pipe", "pipe"],
|
|
7524
|
-
env: { ...process.env, ...options.env }
|
|
7798
|
+
env: { ...process.env, ...injected, ...options.env }
|
|
7525
7799
|
});
|
|
7526
7800
|
}
|
|
7527
7801
|
|
|
@@ -7574,6 +7848,9 @@ async function runWeb() {
|
|
|
7574
7848
|
child.on("exit", (code, signal) => {
|
|
7575
7849
|
if (!opened && code !== 0) {
|
|
7576
7850
|
process.stderr.write(`pi-web: daemon exited (code ${code ?? "?"}, signal ${signal ?? "none"}) before the UI was ready.
|
|
7851
|
+
`);
|
|
7852
|
+
} else {
|
|
7853
|
+
process.stderr.write(`pi-web: daemon exited (code ${code ?? "?"}, signal ${signal ?? "none"}).
|
|
7577
7854
|
`);
|
|
7578
7855
|
}
|
|
7579
7856
|
process.exit(code ?? 0);
|