@venturewild/workspace 0.1.6 → 0.1.8
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/package.json
CHANGED
|
@@ -710,12 +710,16 @@ async function main() {
|
|
|
710
710
|
} catch {}
|
|
711
711
|
}
|
|
712
712
|
|
|
713
|
+
// Hard safety net: even if stop() ever wedges, never leave the user staring at
|
|
714
|
+
// "shutting down…". Unref'd so the timer itself doesn't keep us alive.
|
|
715
|
+
const forceExitSoon = () => { setTimeout(() => process.exit(0), 3000).unref(); };
|
|
713
716
|
process.on('SIGINT', async () => {
|
|
714
717
|
console.log('\nshutting down…');
|
|
715
|
-
|
|
718
|
+
forceExitSoon();
|
|
719
|
+
try { await server.stop(); } catch {}
|
|
716
720
|
process.exit(0);
|
|
717
721
|
});
|
|
718
|
-
process.on('SIGTERM', async () => { await server.stop(); process.exit(0); });
|
|
722
|
+
process.on('SIGTERM', async () => { forceExitSoon(); try { await server.stop(); } catch {} process.exit(0); });
|
|
719
723
|
}
|
|
720
724
|
|
|
721
725
|
main().catch((err) => {
|
package/server/src/agent.mjs
CHANGED
|
@@ -19,21 +19,51 @@ import { spawn } from 'node:child_process';
|
|
|
19
19
|
import { promisify } from 'node:util';
|
|
20
20
|
import { execFile as execFileCb } from 'node:child_process';
|
|
21
21
|
import { EventEmitter } from 'node:events';
|
|
22
|
+
import os from 'node:os';
|
|
23
|
+
import fs from 'node:fs';
|
|
22
24
|
import { DEFAULT_AGENTS } from './config.mjs';
|
|
23
25
|
|
|
24
26
|
const execFile = promisify(execFileCb);
|
|
25
27
|
|
|
26
28
|
const PATH_LOOKUP_TIMEOUT_MS = 1500;
|
|
27
29
|
|
|
30
|
+
// Where `claude` (and other user-installed CLIs) actually live on macOS/Linux.
|
|
31
|
+
// The native installer drops it in ~/.local/bin; Homebrew uses /opt/homebrew/bin
|
|
32
|
+
// (Apple Silicon) or /usr/local/bin; our no-sudo npm prefix is ~/.npm-global/bin.
|
|
33
|
+
function toolDirs(home = os.homedir()) {
|
|
34
|
+
return [`${home}/.local/bin`, '/opt/homebrew/bin', '/usr/local/bin', `${home}/.npm-global/bin`];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// GUI / launchd-launched processes inherit a MINIMAL PATH that omits all of the
|
|
38
|
+
// above and never reads ~/.zshrc — so a server started by the macOS always-on
|
|
39
|
+
// LaunchAgent can't find `claude` (spawn ENOENT). Idempotently add the real tool
|
|
40
|
+
// dirs so detection + spawn work regardless of how the process was launched.
|
|
41
|
+
export function ensureToolPath(env = process.env, { platform = process.platform, home = os.homedir() } = {}) {
|
|
42
|
+
if (platform === 'win32') return env.PATH; // HKCU\Run inherits the full user PATH
|
|
43
|
+
const have = (env.PATH || '').split(':').filter(Boolean);
|
|
44
|
+
const add = toolDirs(home).filter((d) => !have.includes(d));
|
|
45
|
+
if (add.length) env.PATH = [...have, ...add].join(':');
|
|
46
|
+
return env.PATH;
|
|
47
|
+
}
|
|
48
|
+
|
|
28
49
|
async function isOnPath(binary) {
|
|
50
|
+
ensureToolPath(); // make sure the tool dirs are on PATH before we look
|
|
29
51
|
const probe = process.platform === 'win32' ? 'where.exe' : 'which';
|
|
30
52
|
try {
|
|
31
53
|
const { stdout } = await execFile(probe, [binary], { timeout: PATH_LOOKUP_TIMEOUT_MS });
|
|
32
54
|
const lines = stdout.split(/\r?\n/).filter(Boolean);
|
|
33
|
-
|
|
34
|
-
} catch {
|
|
35
|
-
|
|
55
|
+
if (lines.length > 0) return lines[0].trim();
|
|
56
|
+
} catch { /* fall through to a direct probe */ }
|
|
57
|
+
// which/where can still miss a freshly-installed binary in a stripped launchd
|
|
58
|
+
// environment — probe the known install dirs directly and return an ABSOLUTE
|
|
59
|
+
// path, which spawn uses verbatim (no PATH needed at spawn time).
|
|
60
|
+
if (process.platform !== 'win32') {
|
|
61
|
+
for (const dir of toolDirs()) {
|
|
62
|
+
const candidate = `${dir}/${binary}`;
|
|
63
|
+
try { fs.accessSync(candidate, fs.constants.X_OK); return candidate; } catch { /* next */ }
|
|
64
|
+
}
|
|
36
65
|
}
|
|
66
|
+
return null;
|
|
37
67
|
}
|
|
38
68
|
|
|
39
69
|
export async function detectAgents(candidates = DEFAULT_AGENTS) {
|
package/server/src/index.mjs
CHANGED
|
@@ -1328,7 +1328,14 @@ export async function createServer(overrides = {}) {
|
|
|
1328
1328
|
// The daemon is deliberately NOT stopped here — it is detached so sync
|
|
1329
1329
|
// keeps running after wild-workspace closes. `wild-workspace daemon
|
|
1330
1330
|
// stop` is the explicit off-switch.
|
|
1331
|
+
// Terminate live WebSockets first — wss.close() stops the server accepting
|
|
1332
|
+
// new connections but leaves existing client sockets open, and those keep
|
|
1333
|
+
// httpServer.close() hanging forever ("stuck shutting down" on Ctrl+C).
|
|
1334
|
+
try { wss.clients.forEach((c) => { try { c.terminate(); } catch {} }); } catch {}
|
|
1331
1335
|
try { wss.close(); } catch {}
|
|
1336
|
+
// Drop lingering keep-alive HTTP sockets too (Node 18.2+) so close resolves
|
|
1337
|
+
// promptly instead of waiting on idle browser connections.
|
|
1338
|
+
try { httpServer.closeAllConnections?.(); } catch {}
|
|
1332
1339
|
await new Promise((resolve) => httpServer.close(resolve));
|
|
1333
1340
|
},
|
|
1334
1341
|
};
|