@blackbelt-technology/pi-agent-dashboard 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +342 -0
- package/README.md +619 -0
- package/docs/architecture.md +646 -0
- package/package.json +92 -0
- package/packages/extension/package.json +33 -0
- package/packages/extension/src/__tests__/ask-user-tool.test.ts +85 -0
- package/packages/extension/src/__tests__/command-handler.test.ts +712 -0
- package/packages/extension/src/__tests__/connection.test.ts +344 -0
- package/packages/extension/src/__tests__/credentials-updated.test.ts +26 -0
- package/packages/extension/src/__tests__/dev-build.test.ts +79 -0
- package/packages/extension/src/__tests__/event-forwarder.test.ts +89 -0
- package/packages/extension/src/__tests__/git-info.test.ts +112 -0
- package/packages/extension/src/__tests__/git-link-builder.test.ts +102 -0
- package/packages/extension/src/__tests__/openspec-activity-detector.test.ts +232 -0
- package/packages/extension/src/__tests__/openspec-poller.test.ts +119 -0
- package/packages/extension/src/__tests__/process-metrics.test.ts +47 -0
- package/packages/extension/src/__tests__/process-scanner.test.ts +202 -0
- package/packages/extension/src/__tests__/prompt-expander.test.ts +54 -0
- package/packages/extension/src/__tests__/server-auto-start.test.ts +167 -0
- package/packages/extension/src/__tests__/server-launcher.test.ts +44 -0
- package/packages/extension/src/__tests__/server-probe.test.ts +25 -0
- package/packages/extension/src/__tests__/session-switch.test.ts +139 -0
- package/packages/extension/src/__tests__/session-sync.test.ts +55 -0
- package/packages/extension/src/__tests__/source-detector.test.ts +73 -0
- package/packages/extension/src/__tests__/stats-extractor.test.ts +92 -0
- package/packages/extension/src/__tests__/ui-proxy.test.ts +583 -0
- package/packages/extension/src/__tests__/watchdog.test.ts +161 -0
- package/packages/extension/src/ask-user-tool.ts +63 -0
- package/packages/extension/src/bridge-context.ts +64 -0
- package/packages/extension/src/bridge.ts +926 -0
- package/packages/extension/src/command-handler.ts +538 -0
- package/packages/extension/src/connection.ts +204 -0
- package/packages/extension/src/dev-build.ts +39 -0
- package/packages/extension/src/event-forwarder.ts +40 -0
- package/packages/extension/src/flow-event-wiring.ts +102 -0
- package/packages/extension/src/git-info.ts +65 -0
- package/packages/extension/src/git-link-builder.ts +112 -0
- package/packages/extension/src/model-tracker.ts +56 -0
- package/packages/extension/src/pi-env.d.ts +23 -0
- package/packages/extension/src/process-metrics.ts +70 -0
- package/packages/extension/src/process-scanner.ts +396 -0
- package/packages/extension/src/prompt-expander.ts +87 -0
- package/packages/extension/src/provider-register.ts +276 -0
- package/packages/extension/src/server-auto-start.ts +87 -0
- package/packages/extension/src/server-launcher.ts +82 -0
- package/packages/extension/src/server-probe.ts +33 -0
- package/packages/extension/src/session-sync.ts +154 -0
- package/packages/extension/src/source-detector.ts +26 -0
- package/packages/extension/src/ui-proxy.ts +269 -0
- package/packages/extension/tsconfig.json +11 -0
- package/packages/server/package.json +37 -0
- package/packages/server/src/__tests__/auth-plugin.test.ts +117 -0
- package/packages/server/src/__tests__/auth.test.ts +224 -0
- package/packages/server/src/__tests__/auto-attach.test.ts +246 -0
- package/packages/server/src/__tests__/auto-resume.test.ts +135 -0
- package/packages/server/src/__tests__/auto-shutdown.test.ts +136 -0
- package/packages/server/src/__tests__/browse-endpoint.test.ts +104 -0
- package/packages/server/src/__tests__/bulk-archive-handler.test.ts +15 -0
- package/packages/server/src/__tests__/cli-parse.test.ts +73 -0
- package/packages/server/src/__tests__/client-discovery.test.ts +39 -0
- package/packages/server/src/__tests__/config-api.test.ts +104 -0
- package/packages/server/src/__tests__/cors.test.ts +48 -0
- package/packages/server/src/__tests__/directory-service.test.ts +240 -0
- package/packages/server/src/__tests__/editor-detection.test.ts +60 -0
- package/packages/server/src/__tests__/editor-endpoints.test.ts +26 -0
- package/packages/server/src/__tests__/editor-manager.test.ts +73 -0
- package/packages/server/src/__tests__/editor-registry.test.ts +151 -0
- package/packages/server/src/__tests__/event-status-extraction-flow.test.ts +55 -0
- package/packages/server/src/__tests__/event-status-extraction.test.ts +58 -0
- package/packages/server/src/__tests__/extension-register.test.ts +61 -0
- package/packages/server/src/__tests__/file-endpoint.test.ts +49 -0
- package/packages/server/src/__tests__/force-kill-handler.test.ts +109 -0
- package/packages/server/src/__tests__/git-operations.test.ts +251 -0
- package/packages/server/src/__tests__/headless-pid-registry.test.ts +233 -0
- package/packages/server/src/__tests__/headless-shutdown-fallback.test.ts +109 -0
- package/packages/server/src/__tests__/health-endpoint.test.ts +35 -0
- package/packages/server/src/__tests__/heartbeat-ack.test.ts +63 -0
- package/packages/server/src/__tests__/json-store.test.ts +70 -0
- package/packages/server/src/__tests__/localhost-guard.test.ts +149 -0
- package/packages/server/src/__tests__/memory-event-store.test.ts +260 -0
- package/packages/server/src/__tests__/memory-session-manager.test.ts +80 -0
- package/packages/server/src/__tests__/meta-persistence.test.ts +107 -0
- package/packages/server/src/__tests__/migrate-persistence.test.ts +180 -0
- package/packages/server/src/__tests__/npm-search-proxy.test.ts +153 -0
- package/packages/server/src/__tests__/oauth-callback-server.test.ts +165 -0
- package/packages/server/src/__tests__/openspec-archive.test.ts +87 -0
- package/packages/server/src/__tests__/package-manager-wrapper.test.ts +163 -0
- package/packages/server/src/__tests__/package-routes.test.ts +172 -0
- package/packages/server/src/__tests__/pending-fork-registry.test.ts +69 -0
- package/packages/server/src/__tests__/pending-load-manager.test.ts +144 -0
- package/packages/server/src/__tests__/pending-resume-registry.test.ts +130 -0
- package/packages/server/src/__tests__/pi-resource-scanner.test.ts +235 -0
- package/packages/server/src/__tests__/preferences-store.test.ts +108 -0
- package/packages/server/src/__tests__/process-manager.test.ts +184 -0
- package/packages/server/src/__tests__/provider-auth-handlers.test.ts +93 -0
- package/packages/server/src/__tests__/provider-auth-routes.test.ts +143 -0
- package/packages/server/src/__tests__/provider-auth-storage.test.ts +114 -0
- package/packages/server/src/__tests__/resolve-path.test.ts +38 -0
- package/packages/server/src/__tests__/ring-buffer.test.ts +45 -0
- package/packages/server/src/__tests__/server-pid.test.ts +89 -0
- package/packages/server/src/__tests__/session-api.test.ts +244 -0
- package/packages/server/src/__tests__/session-diff.test.ts +138 -0
- package/packages/server/src/__tests__/session-file-dedup.test.ts +102 -0
- package/packages/server/src/__tests__/session-file-reader.test.ts +85 -0
- package/packages/server/src/__tests__/session-lifecycle-logging.test.ts +138 -0
- package/packages/server/src/__tests__/session-order-manager.test.ts +135 -0
- package/packages/server/src/__tests__/session-ordering-integration.test.ts +102 -0
- package/packages/server/src/__tests__/session-scanner.test.ts +199 -0
- package/packages/server/src/__tests__/shutdown-endpoint.test.ts +42 -0
- package/packages/server/src/__tests__/skip-wipe.test.ts +123 -0
- package/packages/server/src/__tests__/sleep-aware-heartbeat.test.ts +126 -0
- package/packages/server/src/__tests__/smoke-integration.test.ts +175 -0
- package/packages/server/src/__tests__/spa-fallback.test.ts +68 -0
- package/packages/server/src/__tests__/subscription-handler.test.ts +155 -0
- package/packages/server/src/__tests__/terminal-gateway.test.ts +61 -0
- package/packages/server/src/__tests__/terminal-manager.test.ts +257 -0
- package/packages/server/src/__tests__/trusted-networks-config.test.ts +84 -0
- package/packages/server/src/__tests__/tunnel.test.ts +206 -0
- package/packages/server/src/__tests__/ws-ping-pong.test.ts +112 -0
- package/packages/server/src/auth-plugin.ts +302 -0
- package/packages/server/src/auth.ts +323 -0
- package/packages/server/src/browse.ts +55 -0
- package/packages/server/src/browser-gateway.ts +495 -0
- package/packages/server/src/browser-handlers/directory-handler.ts +137 -0
- package/packages/server/src/browser-handlers/handler-context.ts +45 -0
- package/packages/server/src/browser-handlers/session-action-handler.ts +271 -0
- package/packages/server/src/browser-handlers/session-meta-handler.ts +95 -0
- package/packages/server/src/browser-handlers/subscription-handler.ts +154 -0
- package/packages/server/src/browser-handlers/terminal-handler.ts +37 -0
- package/packages/server/src/cli.ts +347 -0
- package/packages/server/src/config-api.ts +130 -0
- package/packages/server/src/directory-service.ts +162 -0
- package/packages/server/src/editor-detection.ts +60 -0
- package/packages/server/src/editor-manager.ts +352 -0
- package/packages/server/src/editor-proxy.ts +134 -0
- package/packages/server/src/editor-registry.ts +108 -0
- package/packages/server/src/event-status-extraction.ts +131 -0
- package/packages/server/src/event-wiring.ts +589 -0
- package/packages/server/src/extension-register.ts +92 -0
- package/packages/server/src/git-operations.ts +200 -0
- package/packages/server/src/headless-pid-registry.ts +207 -0
- package/packages/server/src/idle-timer.ts +61 -0
- package/packages/server/src/json-store.ts +32 -0
- package/packages/server/src/localhost-guard.ts +117 -0
- package/packages/server/src/memory-event-store.ts +193 -0
- package/packages/server/src/memory-session-manager.ts +123 -0
- package/packages/server/src/meta-persistence.ts +64 -0
- package/packages/server/src/migrate-persistence.ts +195 -0
- package/packages/server/src/npm-search-proxy.ts +143 -0
- package/packages/server/src/oauth-callback-server.ts +177 -0
- package/packages/server/src/openspec-archive.ts +60 -0
- package/packages/server/src/package-manager-wrapper.ts +200 -0
- package/packages/server/src/pending-fork-registry.ts +53 -0
- package/packages/server/src/pending-load-manager.ts +110 -0
- package/packages/server/src/pending-resume-registry.ts +69 -0
- package/packages/server/src/pi-gateway.ts +419 -0
- package/packages/server/src/pi-resource-scanner.ts +369 -0
- package/packages/server/src/preferences-store.ts +116 -0
- package/packages/server/src/process-manager.ts +311 -0
- package/packages/server/src/provider-auth-handlers.ts +438 -0
- package/packages/server/src/provider-auth-storage.ts +200 -0
- package/packages/server/src/resolve-path.ts +12 -0
- package/packages/server/src/routes/editor-routes.ts +86 -0
- package/packages/server/src/routes/file-routes.ts +116 -0
- package/packages/server/src/routes/git-routes.ts +89 -0
- package/packages/server/src/routes/openspec-routes.ts +99 -0
- package/packages/server/src/routes/package-routes.ts +172 -0
- package/packages/server/src/routes/provider-auth-routes.ts +244 -0
- package/packages/server/src/routes/provider-routes.ts +101 -0
- package/packages/server/src/routes/route-deps.ts +23 -0
- package/packages/server/src/routes/session-routes.ts +91 -0
- package/packages/server/src/routes/system-routes.ts +271 -0
- package/packages/server/src/server-pid.ts +84 -0
- package/packages/server/src/server.ts +554 -0
- package/packages/server/src/session-api.ts +330 -0
- package/packages/server/src/session-bootstrap.ts +80 -0
- package/packages/server/src/session-diff.ts +178 -0
- package/packages/server/src/session-discovery.ts +134 -0
- package/packages/server/src/session-file-reader.ts +135 -0
- package/packages/server/src/session-order-manager.ts +73 -0
- package/packages/server/src/session-scanner.ts +233 -0
- package/packages/server/src/session-stats-reader.ts +99 -0
- package/packages/server/src/terminal-gateway.ts +51 -0
- package/packages/server/src/terminal-manager.ts +241 -0
- package/packages/server/src/tunnel.ts +329 -0
- package/packages/server/tsconfig.json +11 -0
- package/packages/shared/package.json +15 -0
- package/packages/shared/src/__tests__/config.test.ts +358 -0
- package/packages/shared/src/__tests__/deriveChangeState.test.ts +95 -0
- package/packages/shared/src/__tests__/mdns-discovery.test.ts +80 -0
- package/packages/shared/src/__tests__/protocol.test.ts +243 -0
- package/packages/shared/src/__tests__/resolve-jiti.test.ts +17 -0
- package/packages/shared/src/__tests__/server-identity.test.ts +73 -0
- package/packages/shared/src/__tests__/session-meta.test.ts +125 -0
- package/packages/shared/src/archive-types.ts +11 -0
- package/packages/shared/src/browser-protocol.ts +534 -0
- package/packages/shared/src/config.ts +245 -0
- package/packages/shared/src/diff-types.ts +41 -0
- package/packages/shared/src/editor-types.ts +18 -0
- package/packages/shared/src/mdns-discovery.ts +248 -0
- package/packages/shared/src/openspec-activity-detector.ts +109 -0
- package/packages/shared/src/openspec-poller.ts +96 -0
- package/packages/shared/src/protocol.ts +369 -0
- package/packages/shared/src/resolve-jiti.ts +43 -0
- package/packages/shared/src/rest-api.ts +255 -0
- package/packages/shared/src/server-identity.ts +51 -0
- package/packages/shared/src/session-meta.ts +86 -0
- package/packages/shared/src/state-replay.ts +174 -0
- package/packages/shared/src/stats-extractor.ts +54 -0
- package/packages/shared/src/terminal-types.ts +18 -0
- package/packages/shared/src/types.ts +351 -0
- package/packages/shared/tsconfig.json +8 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process manager for spawning pi sessions via tmux or headless (RPC mode).
|
|
3
|
+
*/
|
|
4
|
+
import { execSync, spawn, type ChildProcess } from "node:child_process";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import os from "node:os";
|
|
8
|
+
import type { SpawnStrategy } from "@blackbelt-technology/pi-dashboard-shared/config.js";
|
|
9
|
+
|
|
10
|
+
/** Path to managed install bin directory */
|
|
11
|
+
const MANAGED_BIN = path.join(os.homedir(), ".pi-dashboard", "node_modules", ".bin");
|
|
12
|
+
|
|
13
|
+
/** Common user bin directories that may not be on PATH when launched from
|
|
14
|
+
* a desktop .desktop file or Electron app (shell profiles not sourced). */
|
|
15
|
+
function getUserBinDirs(): string[] {
|
|
16
|
+
const home = os.homedir();
|
|
17
|
+
const dirs = [
|
|
18
|
+
path.join(home, ".local", "bin"), // pip, install.sh scripts
|
|
19
|
+
path.join(home, ".npm-global", "bin"), // npm config prefix
|
|
20
|
+
"/usr/local/bin", // brew, manual installs
|
|
21
|
+
];
|
|
22
|
+
return dirs.filter(d => existsSync(d));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Build env with managed install bin + current node binary dir prepended to PATH.
|
|
26
|
+
* Ensures `pi`, `node`, and user-installed tools (code-server) are findable.
|
|
27
|
+
* This is critical when launched from Electron (no shell profile sourced).
|
|
28
|
+
*/
|
|
29
|
+
export function buildSpawnEnv(baseEnv: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
|
|
30
|
+
const currentPath = baseEnv.PATH || "";
|
|
31
|
+
const parts: string[] = [];
|
|
32
|
+
|
|
33
|
+
// Always ensure managed bin is on PATH (for pi, openspec, tsx)
|
|
34
|
+
if (!currentPath.includes(MANAGED_BIN)) {
|
|
35
|
+
parts.push(MANAGED_BIN);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Always ensure the current node binary's directory is on PATH.
|
|
39
|
+
// Handles bundled Node.js (e.g., /usr/lib/pi-dashboard/resources/node/bin/node)
|
|
40
|
+
// where spawned scripts use #!/usr/bin/env node.
|
|
41
|
+
const nodeBinDir = path.dirname(process.execPath);
|
|
42
|
+
if (!currentPath.includes(nodeBinDir)) {
|
|
43
|
+
parts.push(nodeBinDir);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Add common user bin directories that Electron apps miss
|
|
47
|
+
// (desktop launchers don't source ~/.bashrc / ~/.profile)
|
|
48
|
+
for (const dir of getUserBinDirs()) {
|
|
49
|
+
if (!currentPath.includes(dir)) {
|
|
50
|
+
parts.push(dir);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (parts.length === 0) return baseEnv;
|
|
55
|
+
return { ...baseEnv, PATH: `${parts.join(path.delimiter)}${path.delimiter}${currentPath}` };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface PlatformInfo {
|
|
59
|
+
strategy: "tmux" | "wsl" | "cmd";
|
|
60
|
+
platform: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function detectPlatform(platform?: string): PlatformInfo {
|
|
64
|
+
const p = platform ?? process.platform;
|
|
65
|
+
|
|
66
|
+
if (p === "darwin" || p === "linux") {
|
|
67
|
+
return { strategy: "tmux", platform: p };
|
|
68
|
+
}
|
|
69
|
+
if (p === "win32") {
|
|
70
|
+
return { strategy: "wsl", platform: p };
|
|
71
|
+
}
|
|
72
|
+
return { strategy: "tmux", platform: p };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface SessionOptions {
|
|
76
|
+
sessionFile?: string;
|
|
77
|
+
mode?: "continue" | "fork";
|
|
78
|
+
strategy?: SpawnStrategy;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function buildTmuxCommand(cwd: string, sessionExists: boolean, options?: SessionOptions): string {
|
|
82
|
+
const safeCwd = shellEscape(cwd);
|
|
83
|
+
let piCmd = `cd ${safeCwd} && pi`;
|
|
84
|
+
|
|
85
|
+
if (options?.sessionFile && options?.mode === "continue") {
|
|
86
|
+
piCmd = `cd ${safeCwd} && pi --session ${shellEscape(options.sessionFile)}`;
|
|
87
|
+
} else if (options?.sessionFile && options?.mode === "fork") {
|
|
88
|
+
piCmd = `cd ${safeCwd} && pi --fork ${shellEscape(options.sessionFile)}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (sessionExists) {
|
|
92
|
+
return `tmux new-window -t pi-dashboard -c ${safeCwd} "${piCmd}"`;
|
|
93
|
+
}
|
|
94
|
+
return `tmux new-session -d -s pi-dashboard -c ${safeCwd} "${piCmd}"`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function isTmuxAvailable(): boolean {
|
|
98
|
+
try {
|
|
99
|
+
execSync("which tmux", { stdio: "ignore" });
|
|
100
|
+
return true;
|
|
101
|
+
} catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function dashboardSessionExists(): boolean {
|
|
107
|
+
try {
|
|
108
|
+
execSync("tmux has-session -t pi-dashboard 2>/dev/null", { stdio: "ignore" });
|
|
109
|
+
return true;
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface SpawnResult {
|
|
116
|
+
success: boolean;
|
|
117
|
+
message: string;
|
|
118
|
+
pid?: number;
|
|
119
|
+
process?: ChildProcess;
|
|
120
|
+
/** True when spawned from the dashboard (for writing session meta) */
|
|
121
|
+
dashboardSpawned?: boolean;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function buildHeadlessArgs(options?: SessionOptions): string[] {
|
|
125
|
+
const args = ["--mode", "rpc"];
|
|
126
|
+
|
|
127
|
+
if (options?.sessionFile && options?.mode === "continue") {
|
|
128
|
+
args.push("--session", options.sessionFile);
|
|
129
|
+
} else if (options?.sessionFile && options?.mode === "fork") {
|
|
130
|
+
args.push("--fork", options.sessionFile);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return args;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Resolve the pi command as [command, ...prefixArgs].
|
|
137
|
+
* On Unix: returns ["path/to/pi"]
|
|
138
|
+
* On Windows: returns ["path/to/node.exe", "path/to/pi/dist/cli.mjs"] to avoid .cmd
|
|
139
|
+
*/
|
|
140
|
+
function resolvePiCommand(): string[] | null {
|
|
141
|
+
if (process.platform === "win32") {
|
|
142
|
+
// Try to resolve pi's CLI entry point directly (avoids .cmd → no cmd window)
|
|
143
|
+
const piCli = path.join(MANAGED_BIN, "..", "@mariozechner", "pi-coding-agent", "dist", "cli.js");
|
|
144
|
+
if (existsSync(piCli)) {
|
|
145
|
+
// Find node.exe: process.execPath is the current node binary
|
|
146
|
+
return [process.execPath, piCli];
|
|
147
|
+
}
|
|
148
|
+
// Fallback to .cmd
|
|
149
|
+
const managed = path.join(MANAGED_BIN, "pi.cmd");
|
|
150
|
+
if (existsSync(managed)) return [managed];
|
|
151
|
+
} else {
|
|
152
|
+
const managed = path.join(MANAGED_BIN, "pi");
|
|
153
|
+
if (existsSync(managed)) return [managed];
|
|
154
|
+
}
|
|
155
|
+
// System PATH
|
|
156
|
+
try {
|
|
157
|
+
const whichCmd = process.platform === "win32" ? "where" : "which";
|
|
158
|
+
const result = execSync(`${whichCmd} pi`, {
|
|
159
|
+
encoding: "utf-8",
|
|
160
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
161
|
+
env: buildSpawnEnv(),
|
|
162
|
+
}).trim();
|
|
163
|
+
if (result) return [result.split("\n")[0]];
|
|
164
|
+
} catch { /* not found */ }
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function spawnHeadless(cwd: string, options?: SessionOptions): SpawnResult {
|
|
169
|
+
try {
|
|
170
|
+
const args = buildHeadlessArgs(options);
|
|
171
|
+
const env = buildSpawnEnv();
|
|
172
|
+
|
|
173
|
+
// Pre-check: verify pi binary exists
|
|
174
|
+
const piCmd_ = resolvePiCommand();
|
|
175
|
+
if (!piCmd_) {
|
|
176
|
+
return {
|
|
177
|
+
success: false,
|
|
178
|
+
message: `pi binary not found. Checked: ${MANAGED_BIN}/pi and system PATH.`,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (process.platform === "win32") {
|
|
183
|
+
// Windows: spawn node.exe directly with pi's CLI entry point (no .cmd, no cmd window)
|
|
184
|
+
const [bin, ...prefixArgs] = piCmd_;
|
|
185
|
+
const needsShell = bin.endsWith(".cmd");
|
|
186
|
+
const spawnBin = needsShell ? `"${bin}"` : bin;
|
|
187
|
+
const spawnArgs = needsShell
|
|
188
|
+
? [...prefixArgs, ...args].map(a => `"${a}"`)
|
|
189
|
+
: [...prefixArgs, ...args];
|
|
190
|
+
const child = spawn(spawnBin, spawnArgs, {
|
|
191
|
+
cwd,
|
|
192
|
+
detached: false,
|
|
193
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
194
|
+
env,
|
|
195
|
+
shell: needsShell,
|
|
196
|
+
windowsHide: true,
|
|
197
|
+
});
|
|
198
|
+
child.unref();
|
|
199
|
+
(child.stdin as any)?.unref();
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
success: true,
|
|
203
|
+
dashboardSpawned: true,
|
|
204
|
+
message: `Pi session spawned headless (pid ${child.pid})`,
|
|
205
|
+
pid: child.pid,
|
|
206
|
+
process: child,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Unix (macOS / Linux / WSL): wrap with "tail -f /dev/null | pi" so stdin
|
|
211
|
+
// is an internal pipe that survives GC and server restarts.
|
|
212
|
+
// detached: true creates a new process group; we kill via -pid later.
|
|
213
|
+
const piBin = piCmd_[0];
|
|
214
|
+
const piCmd = [shellEscape(piBin), ...args.map(shellEscape)].join(" ");
|
|
215
|
+
// Use "tail -f /dev/null" to keep stdin pipe open for pi.
|
|
216
|
+
// Unlike "sleep N", tail -f /dev/null works correctly even when
|
|
217
|
+
// the outer shell's stdin is /dev/null (stdio:"ignore"),
|
|
218
|
+
// which breaks "sleep | pi" on some Linux systems.
|
|
219
|
+
const child = spawn("sh", ["-c", `tail -f /dev/null | ${piCmd}`], {
|
|
220
|
+
cwd,
|
|
221
|
+
detached: true,
|
|
222
|
+
stdio: "ignore",
|
|
223
|
+
env,
|
|
224
|
+
});
|
|
225
|
+
child.unref();
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
success: true,
|
|
229
|
+
dashboardSpawned: true,
|
|
230
|
+
message: `Pi session spawned headless (pid ${child.pid})`,
|
|
231
|
+
pid: child.pid,
|
|
232
|
+
process: child,
|
|
233
|
+
};
|
|
234
|
+
} catch (err: any) {
|
|
235
|
+
return {
|
|
236
|
+
success: false,
|
|
237
|
+
message: `Failed to spawn headless session: ${err.message}`,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Escape a string for safe use in a shell command. */
|
|
243
|
+
export function shellEscape(s: string): string {
|
|
244
|
+
if (/^[a-zA-Z0-9_./:=@-]+$/.test(s)) return s;
|
|
245
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export async function spawnPiSession(cwd: string, options?: SessionOptions & { electronMode?: boolean }): Promise<SpawnResult> {
|
|
249
|
+
if (!existsSync(cwd)) {
|
|
250
|
+
return {
|
|
251
|
+
success: false,
|
|
252
|
+
message: `Directory does not exist: ${cwd}`,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Electron mode always forces headless, skipping tmux detection entirely
|
|
257
|
+
if (options?.electronMode || options?.strategy === "headless") {
|
|
258
|
+
return spawnHeadless(cwd, options);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const platform = detectPlatform();
|
|
262
|
+
|
|
263
|
+
if (platform.strategy === "tmux") {
|
|
264
|
+
if (!isTmuxAvailable()) {
|
|
265
|
+
return {
|
|
266
|
+
success: false,
|
|
267
|
+
message: "tmux is not installed. Install it to spawn sessions from the dashboard.",
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const exists = dashboardSessionExists();
|
|
272
|
+
const cmd = buildTmuxCommand(cwd, exists, options);
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
execSync(cmd, { stdio: "ignore" });
|
|
276
|
+
return {
|
|
277
|
+
success: true,
|
|
278
|
+
dashboardSpawned: true,
|
|
279
|
+
message: `Pi session spawned in tmux (${exists ? "new window" : "new session"})`,
|
|
280
|
+
};
|
|
281
|
+
} catch (err: any) {
|
|
282
|
+
return {
|
|
283
|
+
success: false,
|
|
284
|
+
message: `Failed to spawn session: ${err.message}`,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (platform.strategy === "wsl") {
|
|
290
|
+
try {
|
|
291
|
+
// Try WSL tmux first
|
|
292
|
+
execSync("wsl which tmux", { stdio: "ignore" });
|
|
293
|
+
const cmd = `wsl ${buildTmuxCommand(cwd, false)}`;
|
|
294
|
+
execSync(cmd, { stdio: "ignore" });
|
|
295
|
+
return { success: true, dashboardSpawned: true, message: "Pi session spawned via WSL tmux" };
|
|
296
|
+
} catch {
|
|
297
|
+
// Fallback to cmd
|
|
298
|
+
try {
|
|
299
|
+
spawn("cmd", ["/c", `cd /d "${cwd}" && pi`], {
|
|
300
|
+
detached: true,
|
|
301
|
+
stdio: "ignore",
|
|
302
|
+
}).unref();
|
|
303
|
+
return { success: true, dashboardSpawned: true, message: "Pi session spawned via cmd" };
|
|
304
|
+
} catch (err: any) {
|
|
305
|
+
return { success: false, message: `Failed to spawn: ${err.message}` };
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return { success: false, message: "Unsupported platform" };
|
|
311
|
+
}
|