@rubytech/create-realagent 1.0.684 → 1.0.685
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 +69 -31
- package/dist/uninstall.js +47 -19
- package/package.json +1 -1
- package/payload/platform/config/brand.json +1 -0
- package/payload/platform/plugins/docs/references/deployment.md +16 -6
- package/payload/platform/templates/systemd/{maxy-edge.service → edge.service.template} +8 -6
- package/payload/platform/templates/systemd/{maxy-ttyd.service → ttyd.service.template} +10 -5
- package/payload/server/maxy-edge.js +2 -2
- package/payload/server/public/assets/{admin-WQxJgaus.js → admin-BFmYXz1V.js} +1 -1
- package/payload/server/public/index.html +1 -1
package/dist/index.js
CHANGED
|
@@ -1731,28 +1731,33 @@ function installTerminalService() {
|
|
|
1731
1731
|
console.error(` WARNING: failed to write ~/.tmux.conf: ${err instanceof Error ? err.message : String(err)}`);
|
|
1732
1732
|
}
|
|
1733
1733
|
}
|
|
1734
|
-
// Install and enable the
|
|
1735
|
-
//
|
|
1736
|
-
//
|
|
1737
|
-
//
|
|
1738
|
-
//
|
|
1734
|
+
// Install and enable the per-brand ttyd --user unit (Task 662). The unit
|
|
1735
|
+
// file name and the loopback port are both brand-scoped so two brands
|
|
1736
|
+
// installed on the same device each run their own ttyd process without
|
|
1737
|
+
// contending for either the filesystem path or the TCP port:
|
|
1738
|
+
// `${BRAND.hostname}-ttyd.service` + 127.0.0.1:${BRAND.ttydPort}
|
|
1739
1739
|
const systemdUserDir = resolve(homeDir, ".config/systemd/user");
|
|
1740
1740
|
mkdirSync(systemdUserDir, { recursive: true });
|
|
1741
|
+
const ttydUnitShort = `${BRAND.hostname}-ttyd`;
|
|
1742
|
+
const ttydUnitName = `${ttydUnitShort}.service`;
|
|
1741
1743
|
// Skip systemd-unit install if the ttyd binary is not in place — enabling
|
|
1742
1744
|
// a unit whose ExecStart points at a missing file just churns systemd with
|
|
1743
1745
|
// restart failures.
|
|
1744
1746
|
if (!ttydReady) {
|
|
1745
|
-
console.error(
|
|
1747
|
+
console.error(` Skipping ${ttydUnitName} install — ttyd binary not present. Admin terminal will be unavailable until remediated.`);
|
|
1746
1748
|
return;
|
|
1747
1749
|
}
|
|
1748
|
-
const ttydUnitTemplate = resolve(INSTALL_DIR, "platform/templates/systemd/
|
|
1749
|
-
const ttydUnitDest = join(systemdUserDir,
|
|
1750
|
+
const ttydUnitTemplate = resolve(INSTALL_DIR, "platform/templates/systemd/ttyd.service.template");
|
|
1751
|
+
const ttydUnitDest = join(systemdUserDir, ttydUnitName);
|
|
1750
1752
|
try {
|
|
1751
1753
|
if (existsSync(ttydUnitTemplate)) {
|
|
1752
|
-
|
|
1754
|
+
const ttydServiceContent = readFileSync(ttydUnitTemplate, "utf-8")
|
|
1755
|
+
.replace(/__TTYD_PORT__/g, String(TTYD_PORT));
|
|
1756
|
+
writeFileSync(ttydUnitDest, ttydServiceContent);
|
|
1757
|
+
logFile(` ${ttydUnitName}: TTYD_PORT=${TTYD_PORT}`);
|
|
1753
1758
|
}
|
|
1754
1759
|
else {
|
|
1755
|
-
console.error(` WARNING:
|
|
1760
|
+
console.error(` WARNING: ttyd.service.template missing at ${ttydUnitTemplate} — admin terminal will not work`);
|
|
1756
1761
|
return;
|
|
1757
1762
|
}
|
|
1758
1763
|
}
|
|
@@ -1761,9 +1766,9 @@ function installTerminalService() {
|
|
|
1761
1766
|
return;
|
|
1762
1767
|
}
|
|
1763
1768
|
spawnSync("systemctl", ["--user", "daemon-reload"], { stdio: "inherit" });
|
|
1764
|
-
spawnSync("systemctl", ["--user", "enable",
|
|
1765
|
-
spawnSync("systemctl", ["--user", "restart",
|
|
1766
|
-
console.log(
|
|
1769
|
+
spawnSync("systemctl", ["--user", "enable", ttydUnitShort], { stdio: "inherit" });
|
|
1770
|
+
spawnSync("systemctl", ["--user", "restart", ttydUnitShort], { stdio: "inherit" });
|
|
1771
|
+
console.log(` ${ttydUnitName} enabled — admin terminal available on 127.0.0.1:${TTYD_PORT}`);
|
|
1767
1772
|
}
|
|
1768
1773
|
function installService() {
|
|
1769
1774
|
log("12", TOTAL, `Starting ${BRAND.productName}...`);
|
|
@@ -1847,10 +1852,12 @@ function installService() {
|
|
|
1847
1852
|
// non-default --port.
|
|
1848
1853
|
const MAXY_UI_INTERNAL_PORT = PORT + 1;
|
|
1849
1854
|
const neo4jServiceDep = NEO4J_DEDICATED ? `neo4j-${BRAND.hostname}.service` : "neo4j.service";
|
|
1855
|
+
const edgeUnitShort = `${BRAND.hostname}-edge`;
|
|
1856
|
+
const edgeUnitName = `${edgeUnitShort}.service`;
|
|
1850
1857
|
const serviceFile = `[Unit]
|
|
1851
1858
|
Description=${BRAND.productName} AI Assistant
|
|
1852
|
-
After=${neo4jServiceDep}
|
|
1853
|
-
Wants
|
|
1859
|
+
After=${neo4jServiceDep} ${edgeUnitName}
|
|
1860
|
+
Wants=${edgeUnitName}
|
|
1854
1861
|
|
|
1855
1862
|
[Service]
|
|
1856
1863
|
Type=notify
|
|
@@ -1879,23 +1886,31 @@ StandardError=append:${persistDir}/logs/server.log
|
|
|
1879
1886
|
WantedBy=default.target
|
|
1880
1887
|
`;
|
|
1881
1888
|
writeFileSync(join(serviceDir, BRAND.serviceName), serviceFile);
|
|
1882
|
-
// Task 647 —
|
|
1883
|
-
//
|
|
1884
|
-
//
|
|
1885
|
-
// from the admin terminal can restart
|
|
1886
|
-
// browser's remote terminal WebSocket.
|
|
1887
|
-
|
|
1889
|
+
// Task 647 — the edge service: always-on front door that owns the public
|
|
1890
|
+
// port (PORT) and the VNC stack (Xtigervnc + websockify). Its lifecycle is
|
|
1891
|
+
// independent of the main brand service, so an in-place upgrade triggered
|
|
1892
|
+
// from the admin terminal can restart the main brand service without
|
|
1893
|
+
// disconnecting the browser's remote terminal WebSocket.
|
|
1894
|
+
//
|
|
1895
|
+
// Task 662: the unit is per-brand so two brands on the same device each
|
|
1896
|
+
// own their own edge listener on their own EDGE_PORT — installing brand B
|
|
1897
|
+
// never rewrites brand A's unit or steals brand A's public port. Upgrades
|
|
1898
|
+
// from pre-662 installs require the manual recovery paragraph in
|
|
1899
|
+
// .docs/deployment.md before re-running this installer; auto-migration is
|
|
1900
|
+
// intentionally scoped out.
|
|
1901
|
+
const edgeTemplatePath = resolve(INSTALL_DIR, "platform/templates/systemd/edge.service.template");
|
|
1888
1902
|
if (existsSync(edgeTemplatePath)) {
|
|
1889
1903
|
const edgeServiceContent = readFileSync(edgeTemplatePath, "utf-8")
|
|
1890
1904
|
.replace(/__INSTALL_DIR__/g, INSTALL_DIR)
|
|
1891
1905
|
.replace(/__EDGE_PORT__/g, String(PORT))
|
|
1892
1906
|
.replace(/__MAXY_UI_PORT__/g, String(MAXY_UI_INTERNAL_PORT))
|
|
1907
|
+
.replace(/__TTYD_PORT__/g, String(TTYD_PORT))
|
|
1893
1908
|
.replace(/__PERSIST_DIR__/g, persistDir);
|
|
1894
|
-
writeFileSync(join(serviceDir,
|
|
1895
|
-
logFile(`
|
|
1909
|
+
writeFileSync(join(serviceDir, edgeUnitName), edgeServiceContent);
|
|
1910
|
+
logFile(` ${edgeUnitName}: EDGE_PORT=${PORT} MAXY_UI_PORT=${MAXY_UI_INTERNAL_PORT} TTYD_PORT=${TTYD_PORT}`);
|
|
1896
1911
|
}
|
|
1897
1912
|
else {
|
|
1898
|
-
console.error(` WARNING:
|
|
1913
|
+
console.error(` WARNING: edge.service.template missing at ${edgeTemplatePath} — remote terminal will disconnect during upgrade`);
|
|
1899
1914
|
}
|
|
1900
1915
|
// Task 560: the unit declares Environment=PATH=%h/.local/bin:... so the graph
|
|
1901
1916
|
// MCP shim's spawn("uvx", ...) resolves against uv's install location. Without
|
|
@@ -1952,17 +1967,17 @@ WantedBy=multi-user.target
|
|
|
1952
1967
|
catch { /* not critical */ }
|
|
1953
1968
|
// Reload and (re)start.
|
|
1954
1969
|
//
|
|
1955
|
-
// Task 647 ordering: on upgrades, the old
|
|
1956
|
-
// port (PORT). Stop it FIRST so
|
|
1957
|
-
//
|
|
1958
|
-
// Fresh installs: the stop is a no-op.
|
|
1970
|
+
// Task 647 ordering: on upgrades, the old main brand service still holds
|
|
1971
|
+
// the public port (PORT). Stop it FIRST so the edge can bind that socket;
|
|
1972
|
+
// starting the edge first would race against the old main brand service
|
|
1973
|
+
// and fail with EADDRINUSE. Fresh installs: the stop is a no-op.
|
|
1959
1974
|
const unitName = BRAND.serviceName.replace(".service", "");
|
|
1960
1975
|
spawnSync("systemctl", ["--user", "stop", unitName], { stdio: "inherit" });
|
|
1961
1976
|
spawnSync("systemctl", ["--user", "daemon-reload"], { stdio: "inherit" });
|
|
1962
|
-
spawnSync("systemctl", ["--user", "enable",
|
|
1977
|
+
spawnSync("systemctl", ["--user", "enable", edgeUnitShort], { stdio: "inherit" });
|
|
1963
1978
|
spawnSync("systemctl", ["--user", "enable", unitName], { stdio: "inherit" });
|
|
1964
|
-
//
|
|
1965
|
-
spawnSync("systemctl", ["--user", "restart",
|
|
1979
|
+
// edge first: binds public port + starts VNC stack. Then main brand service.
|
|
1980
|
+
spawnSync("systemctl", ["--user", "restart", edgeUnitShort], { stdio: "inherit" });
|
|
1966
1981
|
spawnSync("systemctl", ["--user", "restart", unitName], { stdio: "inherit" });
|
|
1967
1982
|
// Wait for the server to come up
|
|
1968
1983
|
console.log(" Waiting for web server...");
|
|
@@ -2312,6 +2327,28 @@ else {
|
|
|
2312
2327
|
}
|
|
2313
2328
|
// Dedicated = port differs from the default shared instance
|
|
2314
2329
|
const NEO4J_DEDICATED = NEO4J_PORT !== DEFAULT_NEO4J_PORT;
|
|
2330
|
+
// ---------------------------------------------------------------------------
|
|
2331
|
+
// TTYD port — per-brand loopback so two brands on the same device each run
|
|
2332
|
+
// their own ttyd unit on a distinct port (Task 662). Edge service proxies
|
|
2333
|
+
// /ttyd to this port via Environment=TTYD_PORT.
|
|
2334
|
+
//
|
|
2335
|
+
// Priority: --ttyd-port flag > BRAND.ttydPort > 7681. Same pattern as
|
|
2336
|
+
// NEO4J_PORT (Task 659).
|
|
2337
|
+
// ---------------------------------------------------------------------------
|
|
2338
|
+
const DEFAULT_TTYD_PORT = 7681;
|
|
2339
|
+
let TTYD_PORT = BRAND.ttydPort ?? DEFAULT_TTYD_PORT;
|
|
2340
|
+
let TTYD_PORT_SOURCE = BRAND.ttydPort ? "brand.json" : "default";
|
|
2341
|
+
const ttydPortIdx = _args.indexOf("--ttyd-port");
|
|
2342
|
+
if (ttydPortIdx !== -1) {
|
|
2343
|
+
const raw = _args[ttydPortIdx + 1];
|
|
2344
|
+
const parsed = raw ? parseInt(raw, 10) : NaN;
|
|
2345
|
+
if (isNaN(parsed) || parsed < 1024 || parsed > 65535) {
|
|
2346
|
+
console.error(`Setup failed: --ttyd-port requires a numeric value between 1024 and 65535 (got: ${raw ?? "nothing"})`);
|
|
2347
|
+
process.exit(1);
|
|
2348
|
+
}
|
|
2349
|
+
TTYD_PORT = parsed;
|
|
2350
|
+
TTYD_PORT_SOURCE = "--ttyd-port flag";
|
|
2351
|
+
}
|
|
2315
2352
|
const PKG_VERSION = JSON.parse(readFileSync(resolve(import.meta.dirname, "../package.json"), "utf-8")).version;
|
|
2316
2353
|
initLogging();
|
|
2317
2354
|
console.log("================================================================");
|
|
@@ -2324,6 +2361,7 @@ if (HOSTNAME_FLAG)
|
|
|
2324
2361
|
console.log(` Display: ${DISPLAY_MODE} (${DISPLAY_MODE_SOURCE})`);
|
|
2325
2362
|
console.log(` Embed model: ${EMBED_MODEL} (${EMBED_DIMS} dims, ${EMBED_SOURCE})`);
|
|
2326
2363
|
console.log(` Neo4j: ${NEO4J_DEDICATED ? "dedicated" : "shared"} on bolt://localhost:${NEO4J_PORT} (${NEO4J_PORT_SOURCE})`);
|
|
2364
|
+
console.log(` ttyd: 127.0.0.1:${TTYD_PORT} (${TTYD_PORT_SOURCE})`);
|
|
2327
2365
|
console.log("");
|
|
2328
2366
|
logDiagnostics("pre-flight");
|
|
2329
2367
|
logFile(` Neo4j instance: ${NEO4J_DEDICATED ? "dedicated" : "shared"} on bolt://localhost:${NEO4J_PORT}`);
|
package/dist/uninstall.js
CHANGED
|
@@ -85,14 +85,23 @@ export function isMaxyInstalled() {
|
|
|
85
85
|
* present — its runtime still depends on those singletons.
|
|
86
86
|
*
|
|
87
87
|
* Detection: any `.service` file in `~/.config/systemd/user/` whose name is
|
|
88
|
-
* not this brand's `BRAND.serviceName
|
|
89
|
-
*
|
|
88
|
+
* not this brand's `BRAND.serviceName` and not this brand's own edge/ttyd
|
|
89
|
+
* unit (Task 662 — each brand owns its own per-brand edge + ttyd unit, so
|
|
90
|
+
* those files must not register as peer evidence).
|
|
91
|
+
*
|
|
92
|
+
* Legacy pre-662 shared `maxy-edge.service` / `maxy-ttyd.service` are
|
|
93
|
+
* intentionally counted as peer evidence on uninstall: on a dual-brand
|
|
94
|
+
* pre-662 device the shared unit belonged to whichever brand last ran the
|
|
95
|
+
* installer — treating it as peer evidence keeps the uninstaller from
|
|
96
|
+
* purging device-wide singletons the other brand still depends on. */
|
|
90
97
|
function peerBrandPresent() {
|
|
91
98
|
const systemdUserDir = resolve(HOME, ".config/systemd/user");
|
|
92
99
|
if (!existsSync(systemdUserDir))
|
|
93
100
|
return false;
|
|
101
|
+
const thisBrandEdge = `${BRAND.hostname}-edge.service`;
|
|
102
|
+
const thisBrandTtyd = `${BRAND.hostname}-ttyd.service`;
|
|
94
103
|
try {
|
|
95
|
-
return readdirSync(systemdUserDir).some((f) => f.endsWith(".service") && f !== BRAND.serviceName &&
|
|
104
|
+
return readdirSync(systemdUserDir).some((f) => f.endsWith(".service") && f !== BRAND.serviceName && f !== thisBrandEdge && f !== thisBrandTtyd);
|
|
96
105
|
}
|
|
97
106
|
catch {
|
|
98
107
|
return false;
|
|
@@ -111,14 +120,25 @@ function stopServices() {
|
|
|
111
120
|
catch {
|
|
112
121
|
console.log(` ${BRAND.serviceName} not running`);
|
|
113
122
|
}
|
|
114
|
-
// Task 647:
|
|
115
|
-
//
|
|
123
|
+
// Task 647: the edge service owns the public port and VNC stack. Stop it
|
|
124
|
+
// after the main brand service so the edge's ExecStopPost (vnc.sh stop)
|
|
125
|
+
// runs cleanly. Task 662: the edge unit is per-brand (`<hostname>-edge`),
|
|
126
|
+
// plus the ttyd unit (`<hostname>-ttyd`) — both must be stopped.
|
|
127
|
+
const edgeUnitShort = `${BRAND.hostname}-edge`;
|
|
128
|
+
const ttydUnitShort = `${BRAND.hostname}-ttyd`;
|
|
116
129
|
try {
|
|
117
|
-
spawnSync("systemctl", ["--user", "stop",
|
|
118
|
-
console.log(
|
|
130
|
+
spawnSync("systemctl", ["--user", "stop", edgeUnitShort], { stdio: "pipe", timeout: 15_000 });
|
|
131
|
+
console.log(` Stopped ${edgeUnitShort}`);
|
|
119
132
|
}
|
|
120
133
|
catch {
|
|
121
|
-
console.log(
|
|
134
|
+
console.log(` ${edgeUnitShort} not running`);
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
spawnSync("systemctl", ["--user", "stop", ttydUnitShort], { stdio: "pipe", timeout: 15_000 });
|
|
138
|
+
console.log(` Stopped ${ttydUnitShort}`);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
console.log(` ${ttydUnitShort} not running`);
|
|
122
142
|
}
|
|
123
143
|
// Stop Neo4j — dedicated branded instance if this brand uses one, else shared.
|
|
124
144
|
// Brand isolation (Task 659): never stop `neo4j.service` when this brand runs
|
|
@@ -551,19 +571,27 @@ function removeSystemdService() {
|
|
|
551
571
|
console.log(` Failed to remove service file: ${err instanceof Error ? err.message : String(err)}`);
|
|
552
572
|
}
|
|
553
573
|
}
|
|
554
|
-
// Task 647: remove
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
const
|
|
560
|
-
|
|
574
|
+
// Task 647 + 662: remove this brand's per-brand edge + ttyd units alongside
|
|
575
|
+
// the main brand unit. Peer brand's edge/ttyd units live at different
|
|
576
|
+
// filenames (`<peer-hostname>-edge.service`) and are never touched here.
|
|
577
|
+
// Legacy pre-662 shared units left on disk by a previous installer are
|
|
578
|
+
// cleaned up by the installer pre-hygiene loop, not here.
|
|
579
|
+
const thisBrandEdge = `${BRAND.hostname}-edge`;
|
|
580
|
+
const thisBrandTtyd = `${BRAND.hostname}-ttyd`;
|
|
581
|
+
for (const unit of [thisBrandEdge, thisBrandTtyd]) {
|
|
561
582
|
try {
|
|
562
|
-
|
|
563
|
-
console.log(" Removed maxy-edge.service");
|
|
583
|
+
spawnSync("systemctl", ["--user", "disable", unit], { stdio: "pipe" });
|
|
564
584
|
}
|
|
565
|
-
catch
|
|
566
|
-
|
|
585
|
+
catch { /* ignore */ }
|
|
586
|
+
const unitFile = resolve(HOME, `.config/systemd/user/${unit}.service`);
|
|
587
|
+
if (existsSync(unitFile)) {
|
|
588
|
+
try {
|
|
589
|
+
rmSync(unitFile);
|
|
590
|
+
console.log(` Removed ${unit}.service`);
|
|
591
|
+
}
|
|
592
|
+
catch (err) {
|
|
593
|
+
console.log(` Failed to remove ${unit}.service: ${err instanceof Error ? err.message : String(err)}`);
|
|
594
|
+
}
|
|
567
595
|
}
|
|
568
596
|
}
|
|
569
597
|
// Reload daemon
|
package/package.json
CHANGED
|
@@ -68,19 +68,29 @@ The logs will show which service failed to start and why. Common causes:
|
|
|
68
68
|
|
|
69
69
|
## Systemd units on each device
|
|
70
70
|
|
|
71
|
-
Each
|
|
71
|
+
Each installed brand runs three per-brand `--user` systemd units (Task 662 — unit filenames are prefixed with the brand's `hostname` so two brands on the same device never share a unit file):
|
|
72
72
|
|
|
73
|
-
- `
|
|
74
|
-
- `
|
|
75
|
-
- `
|
|
73
|
+
- `{hostname}.service` — the admin + public HTTP server on `127.0.0.1:19199`. Restarted by the upgrade flow; short downtime is expected during steps 8→12 of an upgrade.
|
|
74
|
+
- `{hostname}-edge.service` — the always-on public listener on the configured port (default 19200). Reverse-proxies HTTP to the main brand service, handles `/websockify` (VNC) and `/ttyd` (admin terminal) WebSocket upgrades locally. Does NOT restart during an upgrade — the browser WebSocket stays connected by construction.
|
|
75
|
+
- `{hostname}-ttyd.service` — `ttyd` bound to `127.0.0.1:{BRAND.ttydPort}` (7681 Maxy, 7682 Real Agent), running `tmux new-session -A -s maxy-pty`. Owns the byte-stream admin terminal rendered by xterm.js in the header overlay and the Software Update modal (Task 657). Independent of the main brand service and the edge; outlives service restarts so scrollback is preserved.
|
|
76
76
|
|
|
77
|
-
If the admin terminal fails to open, check `sudo tail -n 50
|
|
77
|
+
If the admin terminal fails to open, check `sudo tail -n 50 ~/{configDir}/logs/edge-boot.log` — the `ttyd-ws-upgrade` / `ttyd-proxy-open` / `ttyd-proxy-close` lines carry a `corrId` that ties the full session lifecycle together. For unit health, `systemctl --user status {hostname}-ttyd` + `journalctl --user -u {hostname}-ttyd`.
|
|
78
|
+
|
|
79
|
+
**Pre-Task-662 upgrade** — devices that ran an installer before Task 662 have legacy shared `maxy-edge.service` and `maxy-ttyd.service` units on disk, which will collide with the new per-brand units. Before re-running any installer on such a device:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
systemctl --user stop maxy-edge maxy-ttyd 2>/dev/null || true
|
|
83
|
+
systemctl --user disable maxy-edge maxy-ttyd 2>/dev/null || true
|
|
84
|
+
rm -f ~/.config/systemd/user/maxy-edge.service \
|
|
85
|
+
~/.config/systemd/user/maxy-ttyd.service
|
|
86
|
+
systemctl --user daemon-reload
|
|
87
|
+
```
|
|
78
88
|
|
|
79
89
|
## Running multiple brands on one device
|
|
80
90
|
|
|
81
91
|
A single Pi or laptop can host more than one brand (for example Maxy and Real Agent) side by side. Each brand runs as its own service on its own port, with its own install directory and its own data. Installing one brand does not touch the other.
|
|
82
92
|
|
|
83
|
-
- **Separate:** each brand has its own install folder (`~/maxy/`, `~/realagent/`), its own config folder (`~/.maxy/`, `~/.realagent/`), its own web port, its own Cloudflare tunnel state, and by default its own Neo4j database (Maxy on bolt port 7687, Real Agent on 7688).
|
|
93
|
+
- **Separate:** each brand has its own install folder (`~/maxy/`, `~/realagent/`), its own config folder (`~/.maxy/`, `~/.realagent/`), its own web port, its own Cloudflare tunnel state, its own edge + ttyd systemd units (`maxy-edge.service` + `maxy-ttyd.service` vs `realagent-edge.service` + `realagent-ttyd.service`), its own ttyd loopback port (7681 vs 7682), and by default its own Neo4j database (Maxy on bolt port 7687, Real Agent on 7688).
|
|
84
94
|
- **Shared:** both brands share the system Chromium/VNC stack, the Ollama model server, and the `cloudflared` command itself. Browser automation is serialised — one admin session at a time across both brands.
|
|
85
95
|
|
|
86
96
|
To install a second brand on a device that already runs the first, just run the other installer. No flags needed for isolation:
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
[Unit]
|
|
2
|
-
Description=
|
|
3
|
-
# No ordering dependency on
|
|
4
|
-
#
|
|
5
|
-
# the edge is listening, but this unit has no such reciprocal
|
|
2
|
+
Description=Edge service (public port + VNC + ttyd transport — independent of the main brand service)
|
|
3
|
+
# No ordering dependency on the main brand service: the edge is the long-running
|
|
4
|
+
# front door. The main brand unit declares Wants/After this unit so it only
|
|
5
|
+
# starts once the edge is listening, but this unit has no such reciprocal
|
|
6
|
+
# dependency.
|
|
6
7
|
|
|
7
8
|
[Service]
|
|
8
9
|
Type=simple
|
|
9
10
|
# ExecStartPre owns the VNC stack so an `npx -y @rubytech/create-maxy@latest`
|
|
10
|
-
# run from the admin terminal can restart
|
|
11
|
-
# websockify, or the upgrade shell down with it. Task 647.
|
|
11
|
+
# run from the admin terminal can restart the main brand service without
|
|
12
|
+
# taking Xtigervnc, websockify, or the upgrade shell down with it. Task 647.
|
|
12
13
|
ExecStartPre=/bin/bash __INSTALL_DIR__/platform/scripts/vnc.sh start
|
|
13
14
|
ExecStart=/usr/bin/node __INSTALL_DIR__/server/maxy-edge.js
|
|
14
15
|
ExecStopPost=/bin/bash __INSTALL_DIR__/platform/scripts/vnc.sh stop
|
|
@@ -23,6 +24,7 @@ Environment=MAXY_UI_HOST=127.0.0.1
|
|
|
23
24
|
Environment=MAXY_UI_PORT=__MAXY_UI_PORT__
|
|
24
25
|
Environment=WEBSOCKIFY_HOST=127.0.0.1
|
|
25
26
|
Environment=WEBSOCKIFY_PORT=6080
|
|
27
|
+
Environment=TTYD_PORT=__TTYD_PORT__
|
|
26
28
|
Environment=DISPLAY=:99
|
|
27
29
|
Environment=MAXY_PLATFORM_ROOT=__INSTALL_DIR__/platform
|
|
28
30
|
Environment=PATH=%h/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[Unit]
|
|
2
|
-
Description=
|
|
2
|
+
Description=Admin terminal (ttyd + tmux) — persistent PTY for admin UI
|
|
3
3
|
After=default.target
|
|
4
4
|
|
|
5
5
|
[Service]
|
|
@@ -9,14 +9,19 @@ Type=simple
|
|
|
9
9
|
# because Debian Bookworm's apt does NOT carry a ttyd package. /usr/local/bin
|
|
10
10
|
# is the standard location for binaries installed outside the distro package
|
|
11
11
|
# manager; /usr/bin is reserved for apt-owned files.
|
|
12
|
-
# -p
|
|
12
|
+
# -p __TTYD_PORT__ listen on 127.0.0.1:<per-brand-port> (the edge service
|
|
13
|
+
# proxies /ttyd to it via TTYD_PORT env)
|
|
13
14
|
# -i 127.0.0.1 reject non-loopback connections at the ttyd layer as well
|
|
14
15
|
# -W writable (allow client → server input bytes)
|
|
15
16
|
# tmux new-session -A -s maxy-pty attach if session exists, create otherwise.
|
|
16
|
-
# Lifetime = user session / device lifetime. Outlives
|
|
17
|
-
# this unit has no After
|
|
17
|
+
# Lifetime = user session / device lifetime. Outlives main brand service
|
|
18
|
+
# restarts because this unit has no After/Requires on it — independent.
|
|
19
|
+
# Known shared-user gap: the `maxy-pty` session name is identical across
|
|
20
|
+
# brands, so two brands running as the same linux user share one tmux
|
|
21
|
+
# session. Tracked in Task 663; structurally removed by Task 660 (per-user
|
|
22
|
+
# linux brand isolation).
|
|
18
23
|
# -x 200 -y 50 initial geometry; xterm.js fit-addon drives runtime resizes.
|
|
19
|
-
ExecStart=/usr/local/bin/ttyd -p
|
|
24
|
+
ExecStart=/usr/local/bin/ttyd -p __TTYD_PORT__ -i 127.0.0.1 -W tmux new-session -A -s maxy-pty -x 200 -y 50
|
|
20
25
|
Environment=TERM=xterm-256color
|
|
21
26
|
Restart=always
|
|
22
27
|
RestartSec=2
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
vncLog
|
|
9
9
|
} from "./chunk-3RBKKDHC.js";
|
|
10
10
|
|
|
11
|
-
// server/
|
|
11
|
+
// server/edge.ts
|
|
12
12
|
import { createServer, request as httpRequest } from "http";
|
|
13
13
|
import { createConnection as createConnection3 } from "net";
|
|
14
14
|
import { readFileSync, existsSync, watchFile } from "fs";
|
|
@@ -628,7 +628,7 @@ Content-Length: 0\r
|
|
|
628
628
|
socket.destroy();
|
|
629
629
|
}
|
|
630
630
|
|
|
631
|
-
// server/
|
|
631
|
+
// server/edge.ts
|
|
632
632
|
var PLATFORM_ROOT = process.env.MAXY_PLATFORM_ROOT || "";
|
|
633
633
|
var BRAND_JSON_PATH = PLATFORM_ROOT ? join(PLATFORM_ROOT, "config", "brand.json") : "";
|
|
634
634
|
var BRAND = { configDir: ".maxy" };
|
|
@@ -359,4 +359,4 @@ ${JSON.stringify(u.input,null,2)}`,f=ae.get(r),p=o?void 0:f??t;return(0,V.jsx)(c
|
|
|
359
359
|
\x1B[32m[terminal] reattached\x1B[0m\r
|
|
360
360
|
`),console.info(`[terminal] reattached`)):(l.current=!0,h.current?.());try{n.fit(),e.readyState===WebSocket.OPEN&&e.send(wE(t.cols,t.rows))}catch{}},onClose:e=>{e!==1e3&&l.current&&t.write(`\r
|
|
361
361
|
\x1B[33m[terminal] server restart detected — reconnecting…\x1B[0m\r
|
|
362
|
-
`)},onUnavailable:()=>{_.current?.()}});c.current=r;let i=t.onData(e=>{let t=c.current?.getSocket();!t||t.readyState!==WebSocket.OPEN||t.send(CE(e))}),v=()=>{try{n.fit();let e=c.current?.getSocket();e&&e.readyState===WebSocket.OPEN&&e.send(wE(t.cols,t.rows))}catch{}};return window.addEventListener(`resize`,v),()=>{window.removeEventListener(`resize`,v);try{i.dispose()}catch{}r.dispose();try{t.dispose()}catch{}o.current=null,s.current=null,c.current=null,f.current=0,p.current=0,m.current=0,l.current=!1,d.current=!1,u.current=!1}},[e]),(0,V.jsx)(`div`,{ref:a,className:`remote-terminal`,style:{width:`100%`,height:`100%`,minHeight:360,background:`#0a0a0a`}})});function DE(e){let{show:t,onClose:n,conversationsLoading:r,conversationsError:a,conversationsList:o,setConversationsList:s,sessionKey:c,isStreaming:l,resetConversation:u,setMessages:d,setConversationId:f}=e,[p,m]=(0,B.useState)(null),[g,v]=(0,B.useState)(null),[y,b]=(0,B.useState)(``),[x,S]=(0,B.useState)(!1),C=(0,B.useRef)(null);(0,B.useEffect)(()=>{t||(m(null),v(null),b(``),S(!1))},[t]),(0,B.useEffect)(()=>{g?.phase===`editing`&&(C.current?.focus(),C.current?.select())},[g]);function w(){m(null),v(null),b(``),S(!1)}async function T(e){if(c){w(),m(e),v({phase:`loading`});try{let t=await fetch(`/api/admin/sessions/${encodeURIComponent(e)}/label?session_key=${encodeURIComponent(c)}`,{method:`POST`});if(!t.ok){v({phase:`error`,reason:(await t.json().catch(()=>({}))).error||`Request failed`});return}let n=await t.json();n.label?(v({phase:`editing`}),b(n.label)):v({phase:`error`,reason:`Not enough message content to suggest a name`})}catch(e){console.error(`[admin-modal] label suggestion failed:`,e),v({phase:`error`,reason:e instanceof Error?e.message:String(e)})}}}async function E(e){if(!(!c||!y.trim()||x)){S(!0);try{let t=await fetch(`/api/admin/sessions/${encodeURIComponent(e)}/label`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({label:y.trim(),session_key:c})});if(!t.ok){v({phase:`error`,reason:(await t.json().catch(()=>({}))).error||`Rename failed`}),S(!1);return}s(t=>t.map(t=>t.conversationId===e?{...t,name:y.trim()}:t)),w()}catch(e){console.error(`[admin-modal] label rename failed:`,e),v({phase:`error`,reason:e instanceof Error?e.message:String(e)}),S(!1)}}}return t?(0,V.jsx)(`div`,{className:`claude-info-overlay`,onClick:n,children:(0,V.jsxs)(`div`,{className:`claude-info-modal conversations-modal`,onClick:e=>e.stopPropagation(),children:[(0,V.jsxs)(`div`,{className:`claude-info-header`,children:[(0,V.jsx)(ze,{size:12}),`Conversations`,(0,V.jsx)(`button`,{className:`claude-info-close`,onClick:n,children:`✕`})]}),(0,V.jsxs)(`div`,{className:`conversations-list`,children:[r&&(0,V.jsxs)(`div`,{className:`conversations-empty`,children:[(0,V.jsx)(ie,{size:14,className:`spin`}),` Loading…`]}),a&&(0,V.jsx)(`div`,{className:`conversations-empty conversations-error`,children:a}),!r&&!a&&o.length===0&&(0,V.jsx)(`div`,{className:`conversations-empty`,children:`No recent conversations`}),!r&&!a&&o.map(e=>{let t=p===e.conversationId,r=t&&g?.phase===`loading`,a=t&&g?.phase===`editing`,o=t&&g?.phase===`error`;return(0,V.jsxs)(`div`,{className:`conversations-row`,children:[(0,V.jsxs)(`div`,{className:`conversations-row-info`,children:[r&&(0,V.jsxs)(`span`,{className:`conversations-row-name conversations-edit-loading`,children:[(0,V.jsx)(ie,{size:12,className:`spin`}),` Generating suggestion…`]}),a&&(0,V.jsxs)(`div`,{className:`conversations-edit-inline`,children:[(0,V.jsx)(`input`,{ref:C,type:`text`,className:`conversations-edit-input`,value:y,onChange:e=>b(e.target.value),onKeyDown:t=>{t.key===`Enter`&&y.trim()&&E(e.conversationId),t.key===`Escape`&&w()},maxLength:200}),(0,V.jsx)(`button`,{type:`button`,className:`conversations-action conversations-confirm`,title:`Confirm rename`,disabled:!y.trim()||x,onClick:()=>E(e.conversationId),children:x?(0,V.jsx)(ie,{size:13,className:`spin`}):(0,V.jsx)(h,{size:13})}),(0,V.jsx)(`button`,{type:`button`,className:`conversations-action conversations-cancel`,title:`Cancel`,onClick:w,children:(0,V.jsx)(fe,{size:13})})]}),o&&(0,V.jsxs)(`div`,{className:`conversations-edit-inline`,children:[(0,V.jsx)(`span`,{className:`conversations-edit-error`,children:g.reason}),(0,V.jsx)(`button`,{type:`button`,className:`conversations-action conversations-retry`,title:`Retry`,onClick:()=>T(e.conversationId),children:(0,V.jsx)(de,{size:13})}),(0,V.jsx)(`button`,{type:`button`,className:`conversations-action conversations-cancel`,title:`Cancel`,onClick:w,children:(0,V.jsx)(fe,{size:13})})]}),!t&&(0,V.jsxs)(V.Fragment,{children:[(0,V.jsx)(`span`,{className:`conversations-row-name`,children:e.name||e.conversationId.slice(0,12)+`…`}),(0,V.jsxs)(`span`,{className:`conversations-row-meta`,children:[(0,V.jsx)(`span`,{className:`conversations-row-id`,children:e.conversationId.slice(0,8)}),(0,V.jsxs)(`span`,{className:`conversations-row-time`,children:[(0,V.jsx)(Se,{size:10}),(()=>{try{let t=new Date(e.updatedAt),n=Date.now()-t.getTime();return n<6e4?`just now`:n<36e5?`${Math.floor(n/6e4)}m ago`:n<864e5?`${Math.floor(n/36e5)}h ago`:`${Math.floor(n/864e5)}d ago`}catch{return``}})()]})]})]})]}),(0,V.jsx)(`div`,{className:`conversations-row-actions`,children:!t&&(0,V.jsxs)(V.Fragment,{children:[(0,V.jsx)(`button`,{type:`button`,className:`conversations-action conversations-rename`,title:`Rename conversation`,onClick:()=>T(e.conversationId),children:(0,V.jsx)(qe,{size:13})}),(0,V.jsx)(`button`,{type:`button`,className:`conversations-action conversations-play`,title:`Resume conversation`,disabled:l,onClick:()=>{l||!c||(n(),u(),fetch(`/api/admin/sessions/${encodeURIComponent(e.conversationId)}/resume?session_key=${encodeURIComponent(c)}`,{method:`POST`}).then(e=>e.ok?e.json():Promise.reject(Error(`Failed to resume conversation`))).then(e=>{e.conversationId&&f(e.conversationId);let t=(e.messages??[]).map(e=>({role:e.role===`user`?`admin`:`maxy`,content:e.role===`user`?e.content:void 0,events:e.role===`assistant`?[{type:`text`,content:e.content}]:void 0,timestamp:new Date(e.createdAt).getTime()||0,historical:!0}));t.length>0&&d(t)}).catch(e=>{console.error(`[chat] conversation resume failed:`,e),d([{role:`maxy`,events:[{type:`text`,content:`Could not load conversation history.`}],timestamp:Date.now()-1,historical:!0}])}))},children:(0,V.jsx)(_,{size:13})}),(0,V.jsx)(`button`,{type:`button`,className:`conversations-action conversations-delete`,title:`Delete conversation`,onClick:()=>{s(t=>t.filter(t=>t.conversationId!==e.conversationId)),c&&fetch(`/api/admin/sessions/${encodeURIComponent(e.conversationId)}?session_key=${encodeURIComponent(c)}`,{method:`DELETE`}).catch(e=>console.error(`[chat] conversation delete failed:`,e))},children:(0,V.jsx)(i,{size:13})})]})})]},e.conversationId)})]})]})}):null}function OE({src:e,onClose:t}){return e?(0,V.jsxs)(`div`,{className:`attachment-lightbox`,onClick:t,children:[(0,V.jsx)(`button`,{className:`attachment-lightbox-close`,onClick:t,"aria-label":`Close`,children:`×`}),(0,V.jsx)(`img`,{src:e,alt:`Attachment`,onClick:e=>e.stopPropagation()})]}):null}function kE({show:e,onClose:t,onConfirm:n}){return e?(0,V.jsx)(`div`,{className:`claude-info-overlay`,onClick:t,children:(0,V.jsxs)(`div`,{className:`claude-info-modal`,onClick:e=>e.stopPropagation(),children:[(0,V.jsxs)(`div`,{className:`claude-info-header`,children:[(0,V.jsx)(`span`,{children:`Compact conversation?`}),(0,V.jsx)(`button`,{className:`claude-info-close`,onClick:t,"aria-label":`Close`,children:`✕`})]}),(0,V.jsx)(`div`,{className:`claude-info-section`,style:{padding:`12px 14px`,fontSize:`11px`,color:`var(--text-secondary)`},children:`Summarises the conversation history to free up context window space. The session continues with a compressed summary.`}),(0,V.jsxs)(`div`,{className:`claude-info-section`,style:{display:`flex`,gap:`8px`,padding:`10px 14px`},children:[(0,V.jsx)(T,{variant:`secondary`,size:`sm`,style:{background:`var(--accent)`,flex:1},onClick:()=>{t(),n()},children:`Yes, compact`}),(0,V.jsx)(T,{variant:`secondary`,size:`sm`,style:{flex:1},onClick:t,children:`No`})]})]})}):null}function AE(e){let{show:t,onClose:n,claudeInfo:r,messages:i,sessionElapsed:a,conversationId:o,contextModeHint:s,handleContextModeToggle:c}=e;if(!t)return null;let l=i.flatMap(e=>e.events?.filter(e=>e.type===`usage`)??[]),u=l.at(-1),d=u?.peak_request_pct==null?u?.context_window?Math.round((u.input_tokens+u.cache_creation_tokens+u.cache_read_tokens)/u.context_window*100):0:Math.round(u.peak_request_pct*100),f=l.reduce((e,t)=>e+t.input_tokens+t.cache_creation_tokens+t.cache_read_tokens+t.output_tokens,0),p=i.flatMap(e=>e.events?.filter(e=>e.type===`rate_limit`)??[]).at(-1),m=l.reduce((e,t)=>e+(t.total_cost_usd??0),0),h=r?.account?.subscriptionType,g=e=>{let t=e*1e3-Date.now();if(t<=0)return`now`;let n=Math.floor(t/36e5),r=Math.floor(t%36e5/6e4);return n>0?`${n}h ${r}m`:`${r}m`};return(0,V.jsx)(`div`,{className:`claude-info-overlay`,onClick:n,children:(0,V.jsxs)(`div`,{className:`claude-info-modal`,onClick:e=>e.stopPropagation(),children:[(0,V.jsxs)(`div`,{className:`claude-info-header`,children:[(0,V.jsx)(`img`,{src:`/brand/claude.png`,alt:`Claude`,className:`claude-info-icon`}),(0,V.jsx)(`span`,{children:`Claude Code`}),(0,V.jsx)(`button`,{className:`claude-info-close`,onClick:n,"aria-label":`Close`,children:`✕`})]}),(0,V.jsxs)(`div`,{className:`claude-info-section`,children:[(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Version`}),(0,V.jsx)(`span`,{className:`claude-info-value`,children:r?r.version:`…`})]}),(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Email`}),(0,V.jsx)(`span`,{className:`claude-info-value`,children:r?.account?.email??`…`})]})]}),(h||p||m>0)&&(0,V.jsxs)(`div`,{className:`claude-info-section`,children:[h&&(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Plan`}),(0,V.jsx)(`span`,{className:`claude-info-value`,style:{textTransform:`capitalize`},children:h})]}),p&&(0,V.jsxs)(V.Fragment,{children:[(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Usage`}),(0,V.jsxs)(`span`,{className:`claude-info-value`,children:[Math.round(p.utilization*100),`%`]})]}),(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Resets in`}),(0,V.jsx)(`span`,{className:`claude-info-value`,children:g(p.resetsAt)})]}),p.isUsingOverage&&(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Overage`}),(0,V.jsx)(`span`,{className:`claude-info-value`,children:`Active`})]})]}),m>0&&(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Session cost`}),(0,V.jsxs)(`span`,{className:`claude-info-value`,children:[`$`,m<.01?m.toFixed(4):m.toFixed(2)]})]})]}),(0,V.jsxs)(`div`,{className:`claude-info-section`,children:[(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Model`}),(0,V.jsx)(`span`,{className:`claude-info-value`,children:r?.model??`…`})]}),(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Context mode`}),(0,V.jsxs)(`span`,{className:`claude-info-value claude-info-toggle`,role:`button`,tabIndex:0,onClick:c,onKeyDown:e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),c())},children:[r?.contextMode??`…`,s?` (next turn)`:``]})]}),(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Context used`}),(0,V.jsx)(`span`,{className:`claude-info-value`,children:d>0?`${d}%`:`—`})]}),(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Tokens`}),(0,V.jsx)(`span`,{className:`claude-info-value`,children:f>0?ct(f):`—`})]}),(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Session`}),(0,V.jsx)(`span`,{className:`claude-info-value`,children:st(a)})]}),o&&(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Conversation`}),(0,V.jsx)(`span`,{className:`claude-info-value`,style:{fontFamily:`monospace`,fontSize:10},children:o.slice(0,8)})]})]})]})})}var jE=5e3;function ME({show:e,onClose:t,versionInfo:n,sessionKey:r,onUpgradeComplete:i}){let[a,o]=(0,B.useState)(!1),[s,c]=(0,B.useState)(!1),[l,u]=(0,B.useState)(null),d=(0,B.useRef)(null),f=(0,B.useRef)(null),p=n?.latest??null,m=(0,B.useCallback)(()=>{f.current&&=(clearInterval(f.current),null)},[]);(0,B.useEffect)(()=>{e||(m(),o(!1),c(!1),u(null))},[e,m]),(0,B.useEffect)(()=>m,[m]),(0,B.useEffect)(()=>{if(!(!e||!a||s||!p))return f.current=setInterval(async()=>{try{let e=await fetch(`/api/admin/version`,{cache:`no-store`});if(!e.ok)return;(await e.json()).installed===p&&(m(),c(!0),i(),setTimeout(()=>window.location.reload(),2e3))}catch{}},jE),m},[e,a,s,p,i,m]);let h=(0,B.useRef)(!1),g=(0,B.useCallback)(()=>{!p||a||(h.current=!0,o(!0),u(null),PE(d.current)&&(h.current=!1))},[p,a]),_=(0,B.useCallback)(()=>{h.current&&PE(d.current)&&(h.current=!1)},[]),v=(0,B.useCallback)(()=>{u("Terminal transport unavailable — check `systemctl --user status maxy-ttyd`")},[]);return e?(0,V.jsx)(`div`,{className:`claude-info-overlay`,onClick:()=>{(!a||s)&&t()},children:(0,V.jsxs)(`div`,{className:`claude-info-modal update-modal`,onClick:e=>e.stopPropagation(),children:[(0,V.jsxs)(`div`,{className:`claude-info-header`,children:[(0,V.jsx)(le,{size:12}),`Software Update`,(0,V.jsx)(`button`,{className:`claude-info-close`,onClick:t,children:`✕`})]}),(0,V.jsxs)(`div`,{className:`update-modal-body`,children:[(0,V.jsxs)(`div`,{className:`update-modal-versions`,children:[(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Installed`}),(0,V.jsxs)(`span`,{className:`claude-info-value`,children:[`v`,n?.installed??`…`]})]}),(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Available`}),(0,V.jsxs)(`span`,{className:`claude-info-value update-version-new`,children:[`v`,p??`…`]})]})]}),l&&(0,V.jsx)(`div`,{className:`update-modal-launch-error`,role:`alert`,children:l}),!a&&!s&&(0,V.jsx)(`button`,{type:`button`,className:`update-modal-btn`,onClick:g,disabled:!p,children:l?`Try again`:`Upgrade`}),a&&!s&&(0,V.jsx)(`div`,{className:`update-modal-terminal`,style:{height:360,marginTop:10,background:`#0a0a0a`,borderRadius:4,overflow:`hidden`},children:(0,V.jsx)(EE,{ref:d,corrId:`upgrade-modal`,onReady:_,onUnavailable:v})}),s&&(0,V.jsxs)(`div`,{className:`update-modal-result`,children:[(0,V.jsx)(R,{size:14,className:`update-success-icon`}),(0,V.jsxs)(`span`,{children:[`Upgraded to v`,p,` — reloading…`]})]})]})]})}):null}var NE=`@rubytech/create-maxy`;function PE(e){return e?e.send(`sudo -v\nnpx -y ${NE}@latest\n`):!1}function FE({onClose:e}){ot();let[t,n]=(0,B.useState)(!1),r=(0,B.useRef)(null),i=(0,B.useRef)(null),a=(0,B.useCallback)(()=>{i.current&&=(clearInterval(i.current),null),r.current&&!r.current.closed&&r.current.close(),r.current=null,n(!1)},[]),o=(0,B.useCallback)(()=>{let t=`/vnc-popout.html?title=${encodeURIComponent(v.productName)}`,o=window.open(t,`maxy-vnc-overlay-popout`,`width=1024,height=768`);o&&(r.current=o,n(!0),i.current=setInterval(()=>{r.current?.closed&&(a(),e())},500))},[a,e]),s=(0,B.useCallback)(()=>{a(),e()},[a,e]);(0,B.useEffect)(()=>{if(t)return;let e=e=>{e.key===`Escape`&&s()};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[t,s]),(0,B.useEffect)(()=>()=>a(),[a]);let c=Lt({handlePopout:o,disabled:t});return t?(0,V.jsxs)(`div`,{className:`browser-overlay-popout`,children:[(0,V.jsx)(O,{className:`browser-viewer__icon`}),(0,V.jsx)(`span`,{className:`browser-viewer__title`,children:v.productName}),(0,V.jsx)(`span`,{className:`browser-viewer__popout-label`,children:`Popped out`}),(0,V.jsx)(T,{variant:`ghost`,size:`sm`,icon:We,onClick:()=>{a()},"aria-label":`Pop back in`}),(0,V.jsx)(T,{variant:`ghost`,size:`sm`,icon:fe,onClick:s,"aria-label":`Close`})]}):(0,V.jsxs)(`div`,{className:`browser-viewer-fullscreen`,children:[(0,V.jsxs)(`div`,{className:`browser-viewer-fullscreen__bar`,...c,children:[(0,V.jsx)(O,{className:`browser-viewer__icon`}),(0,V.jsx)(`span`,{className:`browser-viewer-fullscreen__title`,children:v.productName}),(0,V.jsxs)(`div`,{className:`browser-viewer-fullscreen__actions`,children:[(0,V.jsx)(T,{variant:`ghost`,size:`sm`,icon:De,onClick:o,"aria-label":`Pop out`}),(0,V.jsx)(T,{variant:`ghost`,size:`sm`,icon:fe,onClick:s,"aria-label":`Close`})]})]}),(0,V.jsx)(`iframe`,{src:`/vnc-viewer.html`,className:`browser-viewer-fullscreen__iframe`,title:v.productName})]})}function IE({onClose:e}){let t=(0,B.useCallback)(()=>{e()},[e]);return(0,B.useEffect)(()=>{let e=e=>{e.key===`Escape`&&t()};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[t]),(0,V.jsxs)(`div`,{className:`browser-viewer-fullscreen`,children:[(0,V.jsxs)(`div`,{className:`browser-viewer-fullscreen__bar`,children:[(0,V.jsx)(Qe,{className:`browser-viewer__icon`}),(0,V.jsx)(`span`,{className:`browser-viewer-fullscreen__title`,children:v.productName}),(0,V.jsx)(`div`,{className:`browser-viewer-fullscreen__actions`,children:(0,V.jsx)(T,{variant:`ghost`,size:`sm`,icon:fe,onClick:t,"aria-label":`Close`})})]}),(0,V.jsx)(`div`,{className:`browser-viewer-fullscreen__iframe`,style:{padding:8,background:`#0a0a0a`},children:(0,V.jsx)(EE,{corrId:`header-overlay`,onUnavailable:()=>{t()}})})]})}function LE(){ot();let e=(0,B.useRef)(null),[t,n]=(0,B.useState)(null),[r,a]=(0,B.useState)(!1),[o,s]=(0,B.useState)(!1),[c,u]=(0,B.useState)(``),[d,f]=(0,B.useState)(!1),[p,m]=(0,B.useState)(null),[g,_]=(0,B.useState)(!1),[y,b]=(0,B.useState)(null),[x,S]=(0,B.useState)(null),[w,T]=(0,B.useState)(!1),[E,D]=(0,B.useState)([]),[k,A]=(0,B.useState)(!1),[j,M]=(0,B.useState)(null),[N,P]=(0,B.useState)(null),[ee,F]=(0,B.useState)(!1),[te,I]=(0,B.useState)(null),[ne,ae]=(0,B.useState)(!1),[le,ue]=(0,B.useState)(!1),[de,pe]=(0,B.useState)(!1),me=(0,B.useRef)(!1),he=(0,B.useRef)(!1),ge=(0,B.useRef)(!1),_e=(0,B.useRef)(null),ve=l(),ye=lt(),R=ht(),be=wt(),z=Ct({sessionKey:R.sessionKey,setSessionKey:R.setSessionKey,setAppState:R.setAppState,setConversationId:R.setConversationId,startElapsedTimer:ye.startElapsedTimer,stopElapsedTimer:ye.stopElapsedTimer,resetTimerState:ye.resetTimerState,pausedElapsedRef:ye.pausedElapsedRef,expandAllDefaultRef:R.expandAllDefaultRef,setExpandAll:R.setExpandAll,getPendingFiles:()=>be.pendingFiles,clearPendingFiles:be.clearFiles,inputRef:e});(0,B.useEffect)(()=>{u(window.location.hostname.startsWith(`admin.`)?window.location.origin.replace(`admin.`,`public.`):window.location.origin)},[]),(0,B.useEffect)(()=>{function e(e){e.key===`Escape`&&(t?n(null):ve.selectionMode&&ve.exitSelection())}return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[t,ve.selectionMode]),(0,B.useEffect)(()=>{(R.appState===`set-pin`||R.appState===`enter-pin`)&&setTimeout(()=>R.pinInputRef.current?.focus(),100),R.appState===`chat`&&setTimeout(()=>e.current?.focus(),100)},[R.appState,z.greetingGeneration]),(0,B.useEffect)(()=>{if(R.appState!==`chat`)return;history.pushState({maxyChat:!0},``);function e(){console.debug(`[history-guard] popstate absorbed`),history.pushState({maxyChat:!0},``)}return window.addEventListener(`popstate`,e),()=>window.removeEventListener(`popstate`,e)},[R.appState]);let xe=(0,B.useRef)(z.greetingGeneration);return(0,B.useEffect)(()=>{z.greetingGeneration!==xe.current&&(xe.current=z.greetingGeneration,R.onboardingComplete===!1&&(me.current=!1))},[z.greetingGeneration,R.onboardingComplete]),(0,B.useEffect)(()=>{R.appState===`chat`&&R.onboardingComplete===!1&&(me.current||(me.current=!0,z.sendSystemPrompt(`[Begin onboarding.]`)))},[R.appState,R.onboardingComplete,z.greetingGeneration]),(0,B.useEffect)(()=>{if(!d)return;let e=!1;return fetch(`/api/admin/version`).then(e=>e.json()).then(t=>{e||P(t)}).catch(e=>{console.error(`[admin/version] menu-open client fetch failed:`,e)}),()=>{e=!0}},[d]),(0,B.useEffect)(()=>{if(R.appState!==`chat`){he.current=!1,ge.current=!1;return}if(he.current)return;he.current=!0;let e=!1;return fetch(`/api/admin/version`).then(e=>e.json()).then(t=>{e||(P(t),!(!t.updateAvailable||ge.current)&&(I(t),F(!0),fetch(`/api/admin/version/alert-surfaced`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({installed:t.installed,latest:t.latest})}).catch(e=>{console.error(`[admin/version] alert-surfaced signal failed:`,e)})))}).catch(e=>{console.error(`[admin/version] session-start client fetch failed:`,e)}),()=>{e=!0}},[R.appState]),(0,B.useEffect)(()=>{if(!d){m(null),b(null),S(null),P(null);return}function e(e){_e.current&&!_e.current.contains(e.target)&&f(!1)}return document.addEventListener(`mousedown`,e),()=>document.removeEventListener(`mousedown`,e)},[d]),(0,B.useEffect)(()=>{if(typeof BroadcastChannel>`u`)return;let e=new BroadcastChannel(`platform-onboarding`);return e.onmessage=e=>{e.data?.type===`remote-password-set`&&z.sendMessageRef.current(`I've set the remote password`)},()=>e.close()},[]),R.appState===`loading`?(0,V.jsx)(`div`,{className:`chat-page admin-page`,children:(0,V.jsx)(`header`,{className:`chat-header`,children:(0,V.jsx)(`img`,{src:re,alt:v.productName,className:`chat-logo`})})}):R.appState===`set-pin`?(0,V.jsx)(Et,{pin:R.pin,setPin:R.setPin,confirmPin:R.confirmPin,setConfirmPin:R.setConfirmPin,showPin:R.showPin,setShowPin:R.setShowPin,pinLoading:R.pinLoading,pinError:R.pinError,pinInputRef:R.pinInputRef,confirmPinInputRef:R.confirmPinInputRef,setPinFormRef:R.setPinFormRef,onSubmit:R.handleSetPin}):R.appState===`onboarding-choice`?(0,V.jsx)(At,{setAppState:R.setAppState,setOnboardingComplete:R.setOnboardingComplete}):R.appState===`connect-claude`?(0,V.jsx)(kt,{authPolling:R.authPolling,setAuthPolling:R.setAuthPolling,authLoading:R.authLoading,setAuthLoading:R.setAuthLoading,pinError:R.pinError,setPinError:R.setPinError,setAppState:R.setAppState}):R.appState===`enter-pin`?(0,V.jsx)(Dt,{pin:R.pin,setPin:R.setPin,showPin:R.showPin,setShowPin:R.setShowPin,pinLoading:R.pinLoading,pinError:R.pinError,pinInputRef:R.pinInputRef,onSubmit:R.handleLogin,onChangePin:R.handleChangePin}):R.appState===`account-picker`?(0,V.jsx)(Ot,{accounts:R.accounts,loading:R.accountPickerLoading,error:R.pinError,onSelect:R.handleAccountSelect}):(0,V.jsx)(C,{value:{onShowVnc:()=>ae(!0)},children:(0,V.jsxs)(`div`,{className:`chat-page admin-page`,children:[(0,V.jsxs)(`header`,{className:`chat-header`,children:[(0,V.jsx)(`img`,{src:re,alt:v.productName,className:`chat-logo`}),(!v.logoContainsName||R.businessName)&&(0,V.jsx)(`div`,{children:(0,V.jsx)(`h1`,{className:`chat-tagline`,children:R.businessName||v.productName})}),(0,V.jsxs)(`div`,{className:`chat-burger-wrap`,ref:_e,children:[(0,V.jsx)(`button`,{type:`button`,className:`chat-burger`,onClick:()=>f(e=>!e),"aria-label":`Menu`,"aria-haspopup":`true`,"aria-expanded":d,children:(0,V.jsx)(se,{size:20})}),d&&(0,V.jsxs)(`div`,{className:`chat-menu`,children:[(0,V.jsxs)(`button`,{type:`button`,className:`chat-menu-item`,onClick:async()=>{if(!(ne||le)){ue(!0),f(!1);try{let e=await(await fetch(`/api/admin/browser/launch`,{method:`POST`})).json().catch(()=>({ok:!1,error:`Invalid response`}));if(!e.ok)throw Error(e.error??`Failed to launch browser`);ae(!0)}catch(e){console.error(`[browser] Launch failed:`,e),alert(e instanceof Error?e.message:`Failed to launch browser`)}finally{ue(!1)}}},disabled:le,children:[(0,V.jsx)(O,{size:14}),` Browser`,le&&(0,V.jsx)(ie,{size:12,className:`spin`})]}),(0,V.jsxs)(`button`,{type:`button`,className:`chat-menu-item`,onClick:()=>{de||(f(!1),pe(!0))},children:[(0,V.jsx)(Qe,{size:14}),` Terminal`]}),(0,V.jsxs)(`button`,{type:`button`,className:`chat-menu-item`,onClick:()=>{f(!1),window.location.href=`/data`},children:[(0,V.jsx)(oe,{size:14}),` Data`]}),(0,V.jsxs)(`button`,{type:`button`,className:`chat-menu-item`,onClick:()=>{f(!1),window.location.href=`/graph`},children:[(0,V.jsx)(ce,{size:14}),` Graph`]}),(0,V.jsx)(`div`,{className:`chat-menu-divider`}),(0,V.jsxs)(`button`,{type:`button`,className:`chat-menu-item`,onClick:async()=>{if(p!==null){m(null),S(null);return}_(!0),b(null);try{let e=await fetch(`/api/admin/agents`);if(!e.ok)throw Error(`Failed to load agents`);m((await e.json()).agents??[])}catch(e){console.error(`[admin] agent list failed:`,e),b(e instanceof Error?e.message:String(e))}finally{_(!1)}},children:[(0,V.jsx)(De,{size:14}),` Public`,g&&(0,V.jsx)(ie,{size:12,className:`spin`})]}),p!==null&&(0,V.jsxs)(`div`,{className:`chat-menu-agents`,children:[y&&(0,V.jsx)(`span`,{className:`chat-menu-agent-error`,children:y}),p.length===0&&!y&&(0,V.jsx)(`span`,{className:`chat-menu-agent-empty`,children:`No public agents configured`}),p.map(e=>(0,V.jsxs)(`div`,{className:`chat-menu-item chat-menu-agent-item`,children:[(0,V.jsx)(`span`,{className:`agent-status-dot ${e.status}`}),(0,V.jsxs)(`span`,{className:`agent-text`,children:[(0,V.jsx)(`span`,{className:`agent-display-name`,children:e.displayName}),(0,V.jsxs)(`span`,{className:`agent-slug`,children:[`/`,e.slug]})]}),x===e.slug?(0,V.jsxs)(`span`,{className:`agent-actions agent-confirm`,children:[(0,V.jsx)(`button`,{type:`button`,className:`agent-action-btn agent-confirm-yes`,title:`Confirm delete`,onClick:()=>{S(null),m(t=>t?.filter(t=>t.slug!==e.slug)??null),fetch(`/api/admin/agents/${encodeURIComponent(e.slug)}`,{method:`DELETE`}).catch(e=>console.error(`[admin/agents] delete failed:`,e))},children:(0,V.jsx)(h,{size:12})}),(0,V.jsx)(`button`,{type:`button`,className:`agent-action-btn agent-confirm-no`,title:`Cancel`,onClick:()=>S(null),children:(0,V.jsx)(fe,{size:12})})]}):(0,V.jsxs)(`span`,{className:`agent-actions`,children:[(0,V.jsx)(`a`,{href:`${c}/${e.slug}`,target:`_blank`,rel:`noopener noreferrer`,className:`agent-action-btn`,title:`Open agent`,onClick:()=>f(!1),children:(0,V.jsx)(De,{size:12})}),(0,V.jsx)(`button`,{type:`button`,className:`agent-action-btn agent-delete-btn`,title:`Delete agent`,onClick:()=>S(e.slug),children:(0,V.jsx)(i,{size:12})})]})]},e.slug))]}),(0,V.jsx)(`div`,{className:`chat-menu-divider`}),N?.updateAvailable?(0,V.jsxs)(`button`,{type:`button`,className:`chat-menu-version`,onClick:()=>{N.latest&&(I(N),f(!1),F(!0))},children:[(0,V.jsx)(Pe,{size:14}),(0,V.jsxs)(`span`,{className:`version-installed`,children:[`v`,N.installed,(0,V.jsx)(`span`,{className:`version-update-dot`})]})]}):(0,V.jsxs)(`div`,{className:`chat-menu-version chat-menu-version-passive`,children:[(0,V.jsx)(Pe,{size:14}),(0,V.jsxs)(`span`,{className:`version-installed`,children:[N?`v${N.installed}`:`…`,N&&!N.updateAvailable&&(0,V.jsx)(`span`,{className:`version-uptodate-dot`})]})]}),(0,V.jsxs)(`button`,{type:`button`,className:`chat-menu-item`,onClick:()=>{f(!1),R.handleLogout(),z.setMessages([])},children:[(0,V.jsx)(L,{size:14}),` Log out`]})]})]})]}),(0,V.jsxs)(`div`,{className:`chat-content`,children:[(0,V.jsx)(uE,{messages:z.messages,isStreaming:z.isStreaming,elapsedSeconds:ye.elapsedSeconds,sessionTurnStart:ye.sessionTurnStart,expandAll:R.expandAll,setExpandAll:R.setExpandAll,conversationCompacted:z.conversationCompacted,isCompacting:z.isCompacting,setShowCompactConfirm:s,handleComponentSubmit:z.handleComponentSubmit,effectiveSubmitted:z.effectiveSubmitted,selectionMode:ve.selectionMode,selectedItems:ve.selectedItems,toggleSelectItem:ve.toggleSelectItem,sessionKey:R.sessionKey,setLightboxSrc:n,messageQueue:z.messageQueue,messageQueueRef:z.messageQueueRef,setQueue:z.setQueue,sendMessage:z.sendMessage,appState:R.appState,onboardingComplete:R.onboardingComplete}),(0,V.jsx)(dE,{offer:z.topicChangeOffer,onFreshStart:z.handleTopicFreshStart,onContinue:z.handleTopicContinue,disabled:z.isStreaming}),(0,V.jsx)(mE,{selectionMode:ve.selectionMode,selectedItems:ve.selectedItems,copyToast:ve.copyToast||null,exitSelection:ve.exitSelection,enterSelection:ve.enterSelection,copySelected:ve.copySelected,showCopyToast:ve.showCopyToast,input:z.input,setInput:z.setInput,inputRef:e,pendingFiles:be.pendingFiles,setPendingFiles:be.setPendingFiles,isDragOver:be.isDragOver,attachError:be.attachError,fileInputRef:be.fileInputRef,addFiles:be.addFiles,removeFile:be.removeFile,onDragOver:be.onDragOver,onDragLeave:be.onDragLeave,onDrop:be.onDrop,isStreaming:z.isStreaming,wasPaused:z.wasPaused,messages:z.messages,messageQueueRef:z.messageQueueRef,setQueue:z.setQueue,sendMessage:z.sendMessage,stopStreaming:z.stopStreaming,disconnecting:R.disconnecting,handleDisconnect:R.handleDisconnect,claudeInfo:R.claudeInfo,setClaudeInfo:R.setClaudeInfo,setShowClaudeInfo:a,conversationId:R.conversationId,onNewConversation:z.startNewConversation,newConversationDisabled:z.isStreaming||z.isCompacting,onOpenConversations:async()=>{T(!0),A(!0),M(null);try{let e=await fetch(`/api/admin/sessions?session_key=${encodeURIComponent(R.sessionKey)}`);if(!e.ok)throw Error(`Failed to load conversations`);D((await e.json()).sessions??[])}catch(e){console.error(`[admin] conversation list failed:`,e),M(e instanceof Error?e.message:String(e))}finally{A(!1)}}})]}),ve.copyToast&&(0,V.jsx)(`span`,{className:`copy-toast${ve.copyToast===`failed`?` copy-toast-failed`:``}`,children:ve.copyToast===`copied`?`Copied`:`Copy failed`}),(0,V.jsx)(DE,{show:w,onClose:()=>T(!1),conversationsLoading:k,conversationsError:j,conversationsList:E,setConversationsList:D,sessionKey:R.sessionKey,isStreaming:z.isStreaming,resetConversation:z.resetConversation,setMessages:z.setMessages,setConversationId:R.setConversationId}),(0,V.jsx)(OE,{src:t,onClose:()=>n(null)}),(0,V.jsx)(kE,{show:o,onClose:()=>s(!1),onConfirm:z.handleCompactNow}),(0,V.jsx)(AE,{show:r,onClose:()=>{a(!1),R.setClaudeInfo(null)},claudeInfo:R.claudeInfo,messages:z.messages,sessionElapsed:ye.sessionElapsed,conversationId:R.conversationId,contextModeHint:R.contextModeHint,handleContextModeToggle:R.handleContextModeToggle}),(0,V.jsx)(ME,{show:ee,onClose:()=>{F(!1),ge.current=!0},versionInfo:te,sessionKey:R.sessionKey,onUpgradeComplete:()=>P(null)}),ne&&(0,V.jsx)(FE,{onClose:()=>ae(!1)}),de&&(0,V.jsx)(IE,{onClose:()=>pe(!1)})]})})}(0,at.createRoot)(document.getElementById(`root`)).render((0,V.jsx)(LE,{}));
|
|
362
|
+
`)},onUnavailable:()=>{_.current?.()}});c.current=r;let i=t.onData(e=>{let t=c.current?.getSocket();!t||t.readyState!==WebSocket.OPEN||t.send(CE(e))}),v=()=>{try{n.fit();let e=c.current?.getSocket();e&&e.readyState===WebSocket.OPEN&&e.send(wE(t.cols,t.rows))}catch{}};return window.addEventListener(`resize`,v),()=>{window.removeEventListener(`resize`,v);try{i.dispose()}catch{}r.dispose();try{t.dispose()}catch{}o.current=null,s.current=null,c.current=null,f.current=0,p.current=0,m.current=0,l.current=!1,d.current=!1,u.current=!1}},[e]),(0,V.jsx)(`div`,{ref:a,className:`remote-terminal`,style:{width:`100%`,height:`100%`,minHeight:360,background:`#0a0a0a`}})});function DE(e){let{show:t,onClose:n,conversationsLoading:r,conversationsError:a,conversationsList:o,setConversationsList:s,sessionKey:c,isStreaming:l,resetConversation:u,setMessages:d,setConversationId:f}=e,[p,m]=(0,B.useState)(null),[g,v]=(0,B.useState)(null),[y,b]=(0,B.useState)(``),[x,S]=(0,B.useState)(!1),C=(0,B.useRef)(null);(0,B.useEffect)(()=>{t||(m(null),v(null),b(``),S(!1))},[t]),(0,B.useEffect)(()=>{g?.phase===`editing`&&(C.current?.focus(),C.current?.select())},[g]);function w(){m(null),v(null),b(``),S(!1)}async function T(e){if(c){w(),m(e),v({phase:`loading`});try{let t=await fetch(`/api/admin/sessions/${encodeURIComponent(e)}/label?session_key=${encodeURIComponent(c)}`,{method:`POST`});if(!t.ok){v({phase:`error`,reason:(await t.json().catch(()=>({}))).error||`Request failed`});return}let n=await t.json();n.label?(v({phase:`editing`}),b(n.label)):v({phase:`error`,reason:`Not enough message content to suggest a name`})}catch(e){console.error(`[admin-modal] label suggestion failed:`,e),v({phase:`error`,reason:e instanceof Error?e.message:String(e)})}}}async function E(e){if(!(!c||!y.trim()||x)){S(!0);try{let t=await fetch(`/api/admin/sessions/${encodeURIComponent(e)}/label`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({label:y.trim(),session_key:c})});if(!t.ok){v({phase:`error`,reason:(await t.json().catch(()=>({}))).error||`Rename failed`}),S(!1);return}s(t=>t.map(t=>t.conversationId===e?{...t,name:y.trim()}:t)),w()}catch(e){console.error(`[admin-modal] label rename failed:`,e),v({phase:`error`,reason:e instanceof Error?e.message:String(e)}),S(!1)}}}return t?(0,V.jsx)(`div`,{className:`claude-info-overlay`,onClick:n,children:(0,V.jsxs)(`div`,{className:`claude-info-modal conversations-modal`,onClick:e=>e.stopPropagation(),children:[(0,V.jsxs)(`div`,{className:`claude-info-header`,children:[(0,V.jsx)(ze,{size:12}),`Conversations`,(0,V.jsx)(`button`,{className:`claude-info-close`,onClick:n,children:`✕`})]}),(0,V.jsxs)(`div`,{className:`conversations-list`,children:[r&&(0,V.jsxs)(`div`,{className:`conversations-empty`,children:[(0,V.jsx)(ie,{size:14,className:`spin`}),` Loading…`]}),a&&(0,V.jsx)(`div`,{className:`conversations-empty conversations-error`,children:a}),!r&&!a&&o.length===0&&(0,V.jsx)(`div`,{className:`conversations-empty`,children:`No recent conversations`}),!r&&!a&&o.map(e=>{let t=p===e.conversationId,r=t&&g?.phase===`loading`,a=t&&g?.phase===`editing`,o=t&&g?.phase===`error`;return(0,V.jsxs)(`div`,{className:`conversations-row`,children:[(0,V.jsxs)(`div`,{className:`conversations-row-info`,children:[r&&(0,V.jsxs)(`span`,{className:`conversations-row-name conversations-edit-loading`,children:[(0,V.jsx)(ie,{size:12,className:`spin`}),` Generating suggestion…`]}),a&&(0,V.jsxs)(`div`,{className:`conversations-edit-inline`,children:[(0,V.jsx)(`input`,{ref:C,type:`text`,className:`conversations-edit-input`,value:y,onChange:e=>b(e.target.value),onKeyDown:t=>{t.key===`Enter`&&y.trim()&&E(e.conversationId),t.key===`Escape`&&w()},maxLength:200}),(0,V.jsx)(`button`,{type:`button`,className:`conversations-action conversations-confirm`,title:`Confirm rename`,disabled:!y.trim()||x,onClick:()=>E(e.conversationId),children:x?(0,V.jsx)(ie,{size:13,className:`spin`}):(0,V.jsx)(h,{size:13})}),(0,V.jsx)(`button`,{type:`button`,className:`conversations-action conversations-cancel`,title:`Cancel`,onClick:w,children:(0,V.jsx)(fe,{size:13})})]}),o&&(0,V.jsxs)(`div`,{className:`conversations-edit-inline`,children:[(0,V.jsx)(`span`,{className:`conversations-edit-error`,children:g.reason}),(0,V.jsx)(`button`,{type:`button`,className:`conversations-action conversations-retry`,title:`Retry`,onClick:()=>T(e.conversationId),children:(0,V.jsx)(de,{size:13})}),(0,V.jsx)(`button`,{type:`button`,className:`conversations-action conversations-cancel`,title:`Cancel`,onClick:w,children:(0,V.jsx)(fe,{size:13})})]}),!t&&(0,V.jsxs)(V.Fragment,{children:[(0,V.jsx)(`span`,{className:`conversations-row-name`,children:e.name||e.conversationId.slice(0,12)+`…`}),(0,V.jsxs)(`span`,{className:`conversations-row-meta`,children:[(0,V.jsx)(`span`,{className:`conversations-row-id`,children:e.conversationId.slice(0,8)}),(0,V.jsxs)(`span`,{className:`conversations-row-time`,children:[(0,V.jsx)(Se,{size:10}),(()=>{try{let t=new Date(e.updatedAt),n=Date.now()-t.getTime();return n<6e4?`just now`:n<36e5?`${Math.floor(n/6e4)}m ago`:n<864e5?`${Math.floor(n/36e5)}h ago`:`${Math.floor(n/864e5)}d ago`}catch{return``}})()]})]})]})]}),(0,V.jsx)(`div`,{className:`conversations-row-actions`,children:!t&&(0,V.jsxs)(V.Fragment,{children:[(0,V.jsx)(`button`,{type:`button`,className:`conversations-action conversations-rename`,title:`Rename conversation`,onClick:()=>T(e.conversationId),children:(0,V.jsx)(qe,{size:13})}),(0,V.jsx)(`button`,{type:`button`,className:`conversations-action conversations-play`,title:`Resume conversation`,disabled:l,onClick:()=>{l||!c||(n(),u(),fetch(`/api/admin/sessions/${encodeURIComponent(e.conversationId)}/resume?session_key=${encodeURIComponent(c)}`,{method:`POST`}).then(e=>e.ok?e.json():Promise.reject(Error(`Failed to resume conversation`))).then(e=>{e.conversationId&&f(e.conversationId);let t=(e.messages??[]).map(e=>({role:e.role===`user`?`admin`:`maxy`,content:e.role===`user`?e.content:void 0,events:e.role===`assistant`?[{type:`text`,content:e.content}]:void 0,timestamp:new Date(e.createdAt).getTime()||0,historical:!0}));t.length>0&&d(t)}).catch(e=>{console.error(`[chat] conversation resume failed:`,e),d([{role:`maxy`,events:[{type:`text`,content:`Could not load conversation history.`}],timestamp:Date.now()-1,historical:!0}])}))},children:(0,V.jsx)(_,{size:13})}),(0,V.jsx)(`button`,{type:`button`,className:`conversations-action conversations-delete`,title:`Delete conversation`,onClick:()=>{s(t=>t.filter(t=>t.conversationId!==e.conversationId)),c&&fetch(`/api/admin/sessions/${encodeURIComponent(e.conversationId)}?session_key=${encodeURIComponent(c)}`,{method:`DELETE`}).catch(e=>console.error(`[chat] conversation delete failed:`,e))},children:(0,V.jsx)(i,{size:13})})]})})]},e.conversationId)})]})]})}):null}function OE({src:e,onClose:t}){return e?(0,V.jsxs)(`div`,{className:`attachment-lightbox`,onClick:t,children:[(0,V.jsx)(`button`,{className:`attachment-lightbox-close`,onClick:t,"aria-label":`Close`,children:`×`}),(0,V.jsx)(`img`,{src:e,alt:`Attachment`,onClick:e=>e.stopPropagation()})]}):null}function kE({show:e,onClose:t,onConfirm:n}){return e?(0,V.jsx)(`div`,{className:`claude-info-overlay`,onClick:t,children:(0,V.jsxs)(`div`,{className:`claude-info-modal`,onClick:e=>e.stopPropagation(),children:[(0,V.jsxs)(`div`,{className:`claude-info-header`,children:[(0,V.jsx)(`span`,{children:`Compact conversation?`}),(0,V.jsx)(`button`,{className:`claude-info-close`,onClick:t,"aria-label":`Close`,children:`✕`})]}),(0,V.jsx)(`div`,{className:`claude-info-section`,style:{padding:`12px 14px`,fontSize:`11px`,color:`var(--text-secondary)`},children:`Summarises the conversation history to free up context window space. The session continues with a compressed summary.`}),(0,V.jsxs)(`div`,{className:`claude-info-section`,style:{display:`flex`,gap:`8px`,padding:`10px 14px`},children:[(0,V.jsx)(T,{variant:`secondary`,size:`sm`,style:{background:`var(--accent)`,flex:1},onClick:()=>{t(),n()},children:`Yes, compact`}),(0,V.jsx)(T,{variant:`secondary`,size:`sm`,style:{flex:1},onClick:t,children:`No`})]})]})}):null}function AE(e){let{show:t,onClose:n,claudeInfo:r,messages:i,sessionElapsed:a,conversationId:o,contextModeHint:s,handleContextModeToggle:c}=e;if(!t)return null;let l=i.flatMap(e=>e.events?.filter(e=>e.type===`usage`)??[]),u=l.at(-1),d=u?.peak_request_pct==null?u?.context_window?Math.round((u.input_tokens+u.cache_creation_tokens+u.cache_read_tokens)/u.context_window*100):0:Math.round(u.peak_request_pct*100),f=l.reduce((e,t)=>e+t.input_tokens+t.cache_creation_tokens+t.cache_read_tokens+t.output_tokens,0),p=i.flatMap(e=>e.events?.filter(e=>e.type===`rate_limit`)??[]).at(-1),m=l.reduce((e,t)=>e+(t.total_cost_usd??0),0),h=r?.account?.subscriptionType,g=e=>{let t=e*1e3-Date.now();if(t<=0)return`now`;let n=Math.floor(t/36e5),r=Math.floor(t%36e5/6e4);return n>0?`${n}h ${r}m`:`${r}m`};return(0,V.jsx)(`div`,{className:`claude-info-overlay`,onClick:n,children:(0,V.jsxs)(`div`,{className:`claude-info-modal`,onClick:e=>e.stopPropagation(),children:[(0,V.jsxs)(`div`,{className:`claude-info-header`,children:[(0,V.jsx)(`img`,{src:`/brand/claude.png`,alt:`Claude`,className:`claude-info-icon`}),(0,V.jsx)(`span`,{children:`Claude Code`}),(0,V.jsx)(`button`,{className:`claude-info-close`,onClick:n,"aria-label":`Close`,children:`✕`})]}),(0,V.jsxs)(`div`,{className:`claude-info-section`,children:[(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Version`}),(0,V.jsx)(`span`,{className:`claude-info-value`,children:r?r.version:`…`})]}),(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Email`}),(0,V.jsx)(`span`,{className:`claude-info-value`,children:r?.account?.email??`…`})]})]}),(h||p||m>0)&&(0,V.jsxs)(`div`,{className:`claude-info-section`,children:[h&&(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Plan`}),(0,V.jsx)(`span`,{className:`claude-info-value`,style:{textTransform:`capitalize`},children:h})]}),p&&(0,V.jsxs)(V.Fragment,{children:[(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Usage`}),(0,V.jsxs)(`span`,{className:`claude-info-value`,children:[Math.round(p.utilization*100),`%`]})]}),(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Resets in`}),(0,V.jsx)(`span`,{className:`claude-info-value`,children:g(p.resetsAt)})]}),p.isUsingOverage&&(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Overage`}),(0,V.jsx)(`span`,{className:`claude-info-value`,children:`Active`})]})]}),m>0&&(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Session cost`}),(0,V.jsxs)(`span`,{className:`claude-info-value`,children:[`$`,m<.01?m.toFixed(4):m.toFixed(2)]})]})]}),(0,V.jsxs)(`div`,{className:`claude-info-section`,children:[(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Model`}),(0,V.jsx)(`span`,{className:`claude-info-value`,children:r?.model??`…`})]}),(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Context mode`}),(0,V.jsxs)(`span`,{className:`claude-info-value claude-info-toggle`,role:`button`,tabIndex:0,onClick:c,onKeyDown:e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),c())},children:[r?.contextMode??`…`,s?` (next turn)`:``]})]}),(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Context used`}),(0,V.jsx)(`span`,{className:`claude-info-value`,children:d>0?`${d}%`:`—`})]}),(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Tokens`}),(0,V.jsx)(`span`,{className:`claude-info-value`,children:f>0?ct(f):`—`})]}),(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Session`}),(0,V.jsx)(`span`,{className:`claude-info-value`,children:st(a)})]}),o&&(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Conversation`}),(0,V.jsx)(`span`,{className:`claude-info-value`,style:{fontFamily:`monospace`,fontSize:10},children:o.slice(0,8)})]})]})]})})}var jE=5e3;function ME({show:e,onClose:t,versionInfo:n,sessionKey:r,onUpgradeComplete:i}){let[a,o]=(0,B.useState)(!1),[s,c]=(0,B.useState)(!1),[l,u]=(0,B.useState)(null),d=(0,B.useRef)(null),f=(0,B.useRef)(null),p=n?.latest??null,m=(0,B.useCallback)(()=>{f.current&&=(clearInterval(f.current),null)},[]);(0,B.useEffect)(()=>{e||(m(),o(!1),c(!1),u(null))},[e,m]),(0,B.useEffect)(()=>m,[m]),(0,B.useEffect)(()=>{if(!(!e||!a||s||!p))return f.current=setInterval(async()=>{try{let e=await fetch(`/api/admin/version`,{cache:`no-store`});if(!e.ok)return;(await e.json()).installed===p&&(m(),c(!0),i(),setTimeout(()=>window.location.reload(),2e3))}catch{}},jE),m},[e,a,s,p,i,m]);let h=(0,B.useRef)(!1),g=(0,B.useCallback)(()=>{!p||a||(h.current=!0,o(!0),u(null),PE(d.current)&&(h.current=!1))},[p,a]),_=(0,B.useCallback)(()=>{h.current&&PE(d.current)&&(h.current=!1)},[]),y=(0,B.useCallback)(()=>{u(`Terminal transport unavailable — check \`systemctl --user status ${v.hostname}-ttyd\``)},[]);return e?(0,V.jsx)(`div`,{className:`claude-info-overlay`,onClick:()=>{(!a||s)&&t()},children:(0,V.jsxs)(`div`,{className:`claude-info-modal update-modal`,onClick:e=>e.stopPropagation(),children:[(0,V.jsxs)(`div`,{className:`claude-info-header`,children:[(0,V.jsx)(le,{size:12}),`Software Update`,(0,V.jsx)(`button`,{className:`claude-info-close`,onClick:t,children:`✕`})]}),(0,V.jsxs)(`div`,{className:`update-modal-body`,children:[(0,V.jsxs)(`div`,{className:`update-modal-versions`,children:[(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Installed`}),(0,V.jsxs)(`span`,{className:`claude-info-value`,children:[`v`,n?.installed??`…`]})]}),(0,V.jsxs)(`div`,{className:`claude-info-row`,children:[(0,V.jsx)(`span`,{className:`claude-info-label`,children:`Available`}),(0,V.jsxs)(`span`,{className:`claude-info-value update-version-new`,children:[`v`,p??`…`]})]})]}),l&&(0,V.jsx)(`div`,{className:`update-modal-launch-error`,role:`alert`,children:l}),!a&&!s&&(0,V.jsx)(`button`,{type:`button`,className:`update-modal-btn`,onClick:g,disabled:!p,children:l?`Try again`:`Upgrade`}),a&&!s&&(0,V.jsx)(`div`,{className:`update-modal-terminal`,style:{height:360,marginTop:10,background:`#0a0a0a`,borderRadius:4,overflow:`hidden`},children:(0,V.jsx)(EE,{ref:d,corrId:`upgrade-modal`,onReady:_,onUnavailable:y})}),s&&(0,V.jsxs)(`div`,{className:`update-modal-result`,children:[(0,V.jsx)(R,{size:14,className:`update-success-icon`}),(0,V.jsxs)(`span`,{children:[`Upgraded to v`,p,` — reloading…`]})]})]})]})}):null}var NE=`@rubytech/create-maxy`;function PE(e){return e?e.send(`sudo -v\nnpx -y ${NE}@latest\n`):!1}function FE({onClose:e}){ot();let[t,n]=(0,B.useState)(!1),r=(0,B.useRef)(null),i=(0,B.useRef)(null),a=(0,B.useCallback)(()=>{i.current&&=(clearInterval(i.current),null),r.current&&!r.current.closed&&r.current.close(),r.current=null,n(!1)},[]),o=(0,B.useCallback)(()=>{let t=`/vnc-popout.html?title=${encodeURIComponent(v.productName)}`,o=window.open(t,`maxy-vnc-overlay-popout`,`width=1024,height=768`);o&&(r.current=o,n(!0),i.current=setInterval(()=>{r.current?.closed&&(a(),e())},500))},[a,e]),s=(0,B.useCallback)(()=>{a(),e()},[a,e]);(0,B.useEffect)(()=>{if(t)return;let e=e=>{e.key===`Escape`&&s()};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[t,s]),(0,B.useEffect)(()=>()=>a(),[a]);let c=Lt({handlePopout:o,disabled:t});return t?(0,V.jsxs)(`div`,{className:`browser-overlay-popout`,children:[(0,V.jsx)(O,{className:`browser-viewer__icon`}),(0,V.jsx)(`span`,{className:`browser-viewer__title`,children:v.productName}),(0,V.jsx)(`span`,{className:`browser-viewer__popout-label`,children:`Popped out`}),(0,V.jsx)(T,{variant:`ghost`,size:`sm`,icon:We,onClick:()=>{a()},"aria-label":`Pop back in`}),(0,V.jsx)(T,{variant:`ghost`,size:`sm`,icon:fe,onClick:s,"aria-label":`Close`})]}):(0,V.jsxs)(`div`,{className:`browser-viewer-fullscreen`,children:[(0,V.jsxs)(`div`,{className:`browser-viewer-fullscreen__bar`,...c,children:[(0,V.jsx)(O,{className:`browser-viewer__icon`}),(0,V.jsx)(`span`,{className:`browser-viewer-fullscreen__title`,children:v.productName}),(0,V.jsxs)(`div`,{className:`browser-viewer-fullscreen__actions`,children:[(0,V.jsx)(T,{variant:`ghost`,size:`sm`,icon:De,onClick:o,"aria-label":`Pop out`}),(0,V.jsx)(T,{variant:`ghost`,size:`sm`,icon:fe,onClick:s,"aria-label":`Close`})]})]}),(0,V.jsx)(`iframe`,{src:`/vnc-viewer.html`,className:`browser-viewer-fullscreen__iframe`,title:v.productName})]})}function IE({onClose:e}){let t=(0,B.useCallback)(()=>{e()},[e]);return(0,B.useEffect)(()=>{let e=e=>{e.key===`Escape`&&t()};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[t]),(0,V.jsxs)(`div`,{className:`browser-viewer-fullscreen`,children:[(0,V.jsxs)(`div`,{className:`browser-viewer-fullscreen__bar`,children:[(0,V.jsx)(Qe,{className:`browser-viewer__icon`}),(0,V.jsx)(`span`,{className:`browser-viewer-fullscreen__title`,children:v.productName}),(0,V.jsx)(`div`,{className:`browser-viewer-fullscreen__actions`,children:(0,V.jsx)(T,{variant:`ghost`,size:`sm`,icon:fe,onClick:t,"aria-label":`Close`})})]}),(0,V.jsx)(`div`,{className:`browser-viewer-fullscreen__iframe`,style:{padding:8,background:`#0a0a0a`},children:(0,V.jsx)(EE,{corrId:`header-overlay`,onUnavailable:()=>{t()}})})]})}function LE(){ot();let e=(0,B.useRef)(null),[t,n]=(0,B.useState)(null),[r,a]=(0,B.useState)(!1),[o,s]=(0,B.useState)(!1),[c,u]=(0,B.useState)(``),[d,f]=(0,B.useState)(!1),[p,m]=(0,B.useState)(null),[g,_]=(0,B.useState)(!1),[y,b]=(0,B.useState)(null),[x,S]=(0,B.useState)(null),[w,T]=(0,B.useState)(!1),[E,D]=(0,B.useState)([]),[k,A]=(0,B.useState)(!1),[j,M]=(0,B.useState)(null),[N,P]=(0,B.useState)(null),[ee,F]=(0,B.useState)(!1),[te,I]=(0,B.useState)(null),[ne,ae]=(0,B.useState)(!1),[le,ue]=(0,B.useState)(!1),[de,pe]=(0,B.useState)(!1),me=(0,B.useRef)(!1),he=(0,B.useRef)(!1),ge=(0,B.useRef)(!1),_e=(0,B.useRef)(null),ve=l(),ye=lt(),R=ht(),be=wt(),z=Ct({sessionKey:R.sessionKey,setSessionKey:R.setSessionKey,setAppState:R.setAppState,setConversationId:R.setConversationId,startElapsedTimer:ye.startElapsedTimer,stopElapsedTimer:ye.stopElapsedTimer,resetTimerState:ye.resetTimerState,pausedElapsedRef:ye.pausedElapsedRef,expandAllDefaultRef:R.expandAllDefaultRef,setExpandAll:R.setExpandAll,getPendingFiles:()=>be.pendingFiles,clearPendingFiles:be.clearFiles,inputRef:e});(0,B.useEffect)(()=>{u(window.location.hostname.startsWith(`admin.`)?window.location.origin.replace(`admin.`,`public.`):window.location.origin)},[]),(0,B.useEffect)(()=>{function e(e){e.key===`Escape`&&(t?n(null):ve.selectionMode&&ve.exitSelection())}return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[t,ve.selectionMode]),(0,B.useEffect)(()=>{(R.appState===`set-pin`||R.appState===`enter-pin`)&&setTimeout(()=>R.pinInputRef.current?.focus(),100),R.appState===`chat`&&setTimeout(()=>e.current?.focus(),100)},[R.appState,z.greetingGeneration]),(0,B.useEffect)(()=>{if(R.appState!==`chat`)return;history.pushState({maxyChat:!0},``);function e(){console.debug(`[history-guard] popstate absorbed`),history.pushState({maxyChat:!0},``)}return window.addEventListener(`popstate`,e),()=>window.removeEventListener(`popstate`,e)},[R.appState]);let xe=(0,B.useRef)(z.greetingGeneration);return(0,B.useEffect)(()=>{z.greetingGeneration!==xe.current&&(xe.current=z.greetingGeneration,R.onboardingComplete===!1&&(me.current=!1))},[z.greetingGeneration,R.onboardingComplete]),(0,B.useEffect)(()=>{R.appState===`chat`&&R.onboardingComplete===!1&&(me.current||(me.current=!0,z.sendSystemPrompt(`[Begin onboarding.]`)))},[R.appState,R.onboardingComplete,z.greetingGeneration]),(0,B.useEffect)(()=>{if(!d)return;let e=!1;return fetch(`/api/admin/version`).then(e=>e.json()).then(t=>{e||P(t)}).catch(e=>{console.error(`[admin/version] menu-open client fetch failed:`,e)}),()=>{e=!0}},[d]),(0,B.useEffect)(()=>{if(R.appState!==`chat`){he.current=!1,ge.current=!1;return}if(he.current)return;he.current=!0;let e=!1;return fetch(`/api/admin/version`).then(e=>e.json()).then(t=>{e||(P(t),!(!t.updateAvailable||ge.current)&&(I(t),F(!0),fetch(`/api/admin/version/alert-surfaced`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({installed:t.installed,latest:t.latest})}).catch(e=>{console.error(`[admin/version] alert-surfaced signal failed:`,e)})))}).catch(e=>{console.error(`[admin/version] session-start client fetch failed:`,e)}),()=>{e=!0}},[R.appState]),(0,B.useEffect)(()=>{if(!d){m(null),b(null),S(null),P(null);return}function e(e){_e.current&&!_e.current.contains(e.target)&&f(!1)}return document.addEventListener(`mousedown`,e),()=>document.removeEventListener(`mousedown`,e)},[d]),(0,B.useEffect)(()=>{if(typeof BroadcastChannel>`u`)return;let e=new BroadcastChannel(`platform-onboarding`);return e.onmessage=e=>{e.data?.type===`remote-password-set`&&z.sendMessageRef.current(`I've set the remote password`)},()=>e.close()},[]),R.appState===`loading`?(0,V.jsx)(`div`,{className:`chat-page admin-page`,children:(0,V.jsx)(`header`,{className:`chat-header`,children:(0,V.jsx)(`img`,{src:re,alt:v.productName,className:`chat-logo`})})}):R.appState===`set-pin`?(0,V.jsx)(Et,{pin:R.pin,setPin:R.setPin,confirmPin:R.confirmPin,setConfirmPin:R.setConfirmPin,showPin:R.showPin,setShowPin:R.setShowPin,pinLoading:R.pinLoading,pinError:R.pinError,pinInputRef:R.pinInputRef,confirmPinInputRef:R.confirmPinInputRef,setPinFormRef:R.setPinFormRef,onSubmit:R.handleSetPin}):R.appState===`onboarding-choice`?(0,V.jsx)(At,{setAppState:R.setAppState,setOnboardingComplete:R.setOnboardingComplete}):R.appState===`connect-claude`?(0,V.jsx)(kt,{authPolling:R.authPolling,setAuthPolling:R.setAuthPolling,authLoading:R.authLoading,setAuthLoading:R.setAuthLoading,pinError:R.pinError,setPinError:R.setPinError,setAppState:R.setAppState}):R.appState===`enter-pin`?(0,V.jsx)(Dt,{pin:R.pin,setPin:R.setPin,showPin:R.showPin,setShowPin:R.setShowPin,pinLoading:R.pinLoading,pinError:R.pinError,pinInputRef:R.pinInputRef,onSubmit:R.handleLogin,onChangePin:R.handleChangePin}):R.appState===`account-picker`?(0,V.jsx)(Ot,{accounts:R.accounts,loading:R.accountPickerLoading,error:R.pinError,onSelect:R.handleAccountSelect}):(0,V.jsx)(C,{value:{onShowVnc:()=>ae(!0)},children:(0,V.jsxs)(`div`,{className:`chat-page admin-page`,children:[(0,V.jsxs)(`header`,{className:`chat-header`,children:[(0,V.jsx)(`img`,{src:re,alt:v.productName,className:`chat-logo`}),(!v.logoContainsName||R.businessName)&&(0,V.jsx)(`div`,{children:(0,V.jsx)(`h1`,{className:`chat-tagline`,children:R.businessName||v.productName})}),(0,V.jsxs)(`div`,{className:`chat-burger-wrap`,ref:_e,children:[(0,V.jsx)(`button`,{type:`button`,className:`chat-burger`,onClick:()=>f(e=>!e),"aria-label":`Menu`,"aria-haspopup":`true`,"aria-expanded":d,children:(0,V.jsx)(se,{size:20})}),d&&(0,V.jsxs)(`div`,{className:`chat-menu`,children:[(0,V.jsxs)(`button`,{type:`button`,className:`chat-menu-item`,onClick:async()=>{if(!(ne||le)){ue(!0),f(!1);try{let e=await(await fetch(`/api/admin/browser/launch`,{method:`POST`})).json().catch(()=>({ok:!1,error:`Invalid response`}));if(!e.ok)throw Error(e.error??`Failed to launch browser`);ae(!0)}catch(e){console.error(`[browser] Launch failed:`,e),alert(e instanceof Error?e.message:`Failed to launch browser`)}finally{ue(!1)}}},disabled:le,children:[(0,V.jsx)(O,{size:14}),` Browser`,le&&(0,V.jsx)(ie,{size:12,className:`spin`})]}),(0,V.jsxs)(`button`,{type:`button`,className:`chat-menu-item`,onClick:()=>{de||(f(!1),pe(!0))},children:[(0,V.jsx)(Qe,{size:14}),` Terminal`]}),(0,V.jsxs)(`button`,{type:`button`,className:`chat-menu-item`,onClick:()=>{f(!1),window.location.href=`/data`},children:[(0,V.jsx)(oe,{size:14}),` Data`]}),(0,V.jsxs)(`button`,{type:`button`,className:`chat-menu-item`,onClick:()=>{f(!1),window.location.href=`/graph`},children:[(0,V.jsx)(ce,{size:14}),` Graph`]}),(0,V.jsx)(`div`,{className:`chat-menu-divider`}),(0,V.jsxs)(`button`,{type:`button`,className:`chat-menu-item`,onClick:async()=>{if(p!==null){m(null),S(null);return}_(!0),b(null);try{let e=await fetch(`/api/admin/agents`);if(!e.ok)throw Error(`Failed to load agents`);m((await e.json()).agents??[])}catch(e){console.error(`[admin] agent list failed:`,e),b(e instanceof Error?e.message:String(e))}finally{_(!1)}},children:[(0,V.jsx)(De,{size:14}),` Public`,g&&(0,V.jsx)(ie,{size:12,className:`spin`})]}),p!==null&&(0,V.jsxs)(`div`,{className:`chat-menu-agents`,children:[y&&(0,V.jsx)(`span`,{className:`chat-menu-agent-error`,children:y}),p.length===0&&!y&&(0,V.jsx)(`span`,{className:`chat-menu-agent-empty`,children:`No public agents configured`}),p.map(e=>(0,V.jsxs)(`div`,{className:`chat-menu-item chat-menu-agent-item`,children:[(0,V.jsx)(`span`,{className:`agent-status-dot ${e.status}`}),(0,V.jsxs)(`span`,{className:`agent-text`,children:[(0,V.jsx)(`span`,{className:`agent-display-name`,children:e.displayName}),(0,V.jsxs)(`span`,{className:`agent-slug`,children:[`/`,e.slug]})]}),x===e.slug?(0,V.jsxs)(`span`,{className:`agent-actions agent-confirm`,children:[(0,V.jsx)(`button`,{type:`button`,className:`agent-action-btn agent-confirm-yes`,title:`Confirm delete`,onClick:()=>{S(null),m(t=>t?.filter(t=>t.slug!==e.slug)??null),fetch(`/api/admin/agents/${encodeURIComponent(e.slug)}`,{method:`DELETE`}).catch(e=>console.error(`[admin/agents] delete failed:`,e))},children:(0,V.jsx)(h,{size:12})}),(0,V.jsx)(`button`,{type:`button`,className:`agent-action-btn agent-confirm-no`,title:`Cancel`,onClick:()=>S(null),children:(0,V.jsx)(fe,{size:12})})]}):(0,V.jsxs)(`span`,{className:`agent-actions`,children:[(0,V.jsx)(`a`,{href:`${c}/${e.slug}`,target:`_blank`,rel:`noopener noreferrer`,className:`agent-action-btn`,title:`Open agent`,onClick:()=>f(!1),children:(0,V.jsx)(De,{size:12})}),(0,V.jsx)(`button`,{type:`button`,className:`agent-action-btn agent-delete-btn`,title:`Delete agent`,onClick:()=>S(e.slug),children:(0,V.jsx)(i,{size:12})})]})]},e.slug))]}),(0,V.jsx)(`div`,{className:`chat-menu-divider`}),N?.updateAvailable?(0,V.jsxs)(`button`,{type:`button`,className:`chat-menu-version`,onClick:()=>{N.latest&&(I(N),f(!1),F(!0))},children:[(0,V.jsx)(Pe,{size:14}),(0,V.jsxs)(`span`,{className:`version-installed`,children:[`v`,N.installed,(0,V.jsx)(`span`,{className:`version-update-dot`})]})]}):(0,V.jsxs)(`div`,{className:`chat-menu-version chat-menu-version-passive`,children:[(0,V.jsx)(Pe,{size:14}),(0,V.jsxs)(`span`,{className:`version-installed`,children:[N?`v${N.installed}`:`…`,N&&!N.updateAvailable&&(0,V.jsx)(`span`,{className:`version-uptodate-dot`})]})]}),(0,V.jsxs)(`button`,{type:`button`,className:`chat-menu-item`,onClick:()=>{f(!1),R.handleLogout(),z.setMessages([])},children:[(0,V.jsx)(L,{size:14}),` Log out`]})]})]})]}),(0,V.jsxs)(`div`,{className:`chat-content`,children:[(0,V.jsx)(uE,{messages:z.messages,isStreaming:z.isStreaming,elapsedSeconds:ye.elapsedSeconds,sessionTurnStart:ye.sessionTurnStart,expandAll:R.expandAll,setExpandAll:R.setExpandAll,conversationCompacted:z.conversationCompacted,isCompacting:z.isCompacting,setShowCompactConfirm:s,handleComponentSubmit:z.handleComponentSubmit,effectiveSubmitted:z.effectiveSubmitted,selectionMode:ve.selectionMode,selectedItems:ve.selectedItems,toggleSelectItem:ve.toggleSelectItem,sessionKey:R.sessionKey,setLightboxSrc:n,messageQueue:z.messageQueue,messageQueueRef:z.messageQueueRef,setQueue:z.setQueue,sendMessage:z.sendMessage,appState:R.appState,onboardingComplete:R.onboardingComplete}),(0,V.jsx)(dE,{offer:z.topicChangeOffer,onFreshStart:z.handleTopicFreshStart,onContinue:z.handleTopicContinue,disabled:z.isStreaming}),(0,V.jsx)(mE,{selectionMode:ve.selectionMode,selectedItems:ve.selectedItems,copyToast:ve.copyToast||null,exitSelection:ve.exitSelection,enterSelection:ve.enterSelection,copySelected:ve.copySelected,showCopyToast:ve.showCopyToast,input:z.input,setInput:z.setInput,inputRef:e,pendingFiles:be.pendingFiles,setPendingFiles:be.setPendingFiles,isDragOver:be.isDragOver,attachError:be.attachError,fileInputRef:be.fileInputRef,addFiles:be.addFiles,removeFile:be.removeFile,onDragOver:be.onDragOver,onDragLeave:be.onDragLeave,onDrop:be.onDrop,isStreaming:z.isStreaming,wasPaused:z.wasPaused,messages:z.messages,messageQueueRef:z.messageQueueRef,setQueue:z.setQueue,sendMessage:z.sendMessage,stopStreaming:z.stopStreaming,disconnecting:R.disconnecting,handleDisconnect:R.handleDisconnect,claudeInfo:R.claudeInfo,setClaudeInfo:R.setClaudeInfo,setShowClaudeInfo:a,conversationId:R.conversationId,onNewConversation:z.startNewConversation,newConversationDisabled:z.isStreaming||z.isCompacting,onOpenConversations:async()=>{T(!0),A(!0),M(null);try{let e=await fetch(`/api/admin/sessions?session_key=${encodeURIComponent(R.sessionKey)}`);if(!e.ok)throw Error(`Failed to load conversations`);D((await e.json()).sessions??[])}catch(e){console.error(`[admin] conversation list failed:`,e),M(e instanceof Error?e.message:String(e))}finally{A(!1)}}})]}),ve.copyToast&&(0,V.jsx)(`span`,{className:`copy-toast${ve.copyToast===`failed`?` copy-toast-failed`:``}`,children:ve.copyToast===`copied`?`Copied`:`Copy failed`}),(0,V.jsx)(DE,{show:w,onClose:()=>T(!1),conversationsLoading:k,conversationsError:j,conversationsList:E,setConversationsList:D,sessionKey:R.sessionKey,isStreaming:z.isStreaming,resetConversation:z.resetConversation,setMessages:z.setMessages,setConversationId:R.setConversationId}),(0,V.jsx)(OE,{src:t,onClose:()=>n(null)}),(0,V.jsx)(kE,{show:o,onClose:()=>s(!1),onConfirm:z.handleCompactNow}),(0,V.jsx)(AE,{show:r,onClose:()=>{a(!1),R.setClaudeInfo(null)},claudeInfo:R.claudeInfo,messages:z.messages,sessionElapsed:ye.sessionElapsed,conversationId:R.conversationId,contextModeHint:R.contextModeHint,handleContextModeToggle:R.handleContextModeToggle}),(0,V.jsx)(ME,{show:ee,onClose:()=>{F(!1),ge.current=!0},versionInfo:te,sessionKey:R.sessionKey,onUpgradeComplete:()=>P(null)}),ne&&(0,V.jsx)(FE,{onClose:()=>ae(!1)}),de&&(0,V.jsx)(IE,{onClose:()=>pe(!1)})]})})}(0,at.createRoot)(document.getElementById(`root`)).render((0,V.jsx)(LE,{}));
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Real Agent</title>
|
|
7
7
|
<link rel="icon" href="/favicon.ico">
|
|
8
|
-
<script type="module" crossorigin src="/assets/admin-
|
|
8
|
+
<script type="module" crossorigin src="/assets/admin-BFmYXz1V.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/chunk-DD-I1_y5.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/jsx-runtime-BVKWELH6.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/preload-helper-qlgyTAkD.js">
|