kaizen-agent 1.6.6 → 1.6.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/cli/install.ts CHANGED
@@ -218,10 +218,16 @@ if (wantWeb) {
218
218
  const alive = async () => { try { await fetch(url); return true; } catch { return false; } };
219
219
 
220
220
  if (args.has("--stop")) {
221
- const pid = existsSync(pidFile) ? Number(readFileSync(pidFile, "utf8")) : NaN;
222
- if (!pid) { console.log(" no kaizen web daemon recorded"); process.exit(0); }
223
- try { process.kill(pid, "SIGTERM"); console.log(` stopped kaizen web (pid ${pid})`); }
224
- catch { console.log(` pid ${pid} already gone; cleared the stale record`); }
221
+ // The pid file only knows a --daemon; a board started from the shortcut or a
222
+ // terminal has none, so also ask the OS what listens on the port.
223
+ const recorded = existsSync(pidFile) ? Number(readFileSync(pidFile, "utf8")) : NaN;
224
+ const listening = await pidOnPort(port ?? 7420);
225
+ const pids = [...new Set([recorded, listening].filter((p): p is number => Number.isFinite(p) && p > 0))];
226
+ if (!pids.length) { console.log(" nothing running on the kaizen web port"); process.exit(0); }
227
+ for (const pid of pids) {
228
+ try { process.kill(pid, "SIGTERM"); console.log(` stopped kaizen web (pid ${pid})`); }
229
+ catch { console.log(` pid ${pid} already gone`); }
230
+ }
225
231
  rmSync(pidFile, { force: true });
226
232
  process.exit(0);
227
233
  }
@@ -791,6 +797,19 @@ if (blocked) process.exit(1);
791
797
 
792
798
  // ---------------------------------------------------------------- helpers
793
799
 
800
+ // Whoever holds the board's port, by the OS's own accounting.
801
+ async function pidOnPort(port: number): Promise<number | null> {
802
+ try {
803
+ if (process.platform === "win32") {
804
+ const out = await Bun.$`netstat -ano -p tcp`.text();
805
+ const m = new RegExp(`127\\.0\\.0\\.1:${port}\\s+\\S+\\s+LISTENING\\s+(\\d+)`).exec(out);
806
+ return m ? Number(m[1]) : null;
807
+ }
808
+ const out = await Bun.$`lsof -t -iTCP:${port} -sTCP:LISTEN`.quiet().nothrow().text();
809
+ return Number(out.trim().split("\n")[0]) || null;
810
+ } catch { return null; }
811
+ }
812
+
794
813
  function step(msg: string) { console.log(` ${c.green("+")} ${msg}`); }
795
814
 
796
815
  // Runs the upgrade and reports what it actually did, version to version, so the
package/cli/web.ts CHANGED
@@ -83,14 +83,24 @@ export async function web(repoDir: string, opts: Opts = {}) {
83
83
  const BOOT = Date.now().toString(36);
84
84
  const ping = setInterval(() => { for (const c of clients) { try { c.enqueue(": ping\n\n"); } catch { clients.delete(c); } } }, 8000);
85
85
  // A launcher clicked while a board is already up should show that board, not die.
86
- const port = opts.port ?? DEFAULT_PORT;
87
- const server = (() => { try { return serve(); } catch (e) {
88
- if ((e as { code?: string }).code !== "EADDRINUSE") throw e;
89
- return null;
90
- } })();
91
- if (!server) { const url = `http://127.0.0.1:${port}/`; console.log(`\n kaizen web already at ${url}\n`); if (opts.open !== false) await openApp(url); return; }
92
-
93
- function serve() { return Bun.serve({
86
+ // A bind can fail because a board is already there, or because the OS will not
87
+ // hand out the port at all (Windows reserves whole ranges for Hyper-V and WSL,
88
+ // and reports either as "in use"). Only a board that answers is "already
89
+ // running"; otherwise walk up to the next port that binds.
90
+ let port = opts.port ?? DEFAULT_PORT;
91
+ let server: ReturnType<typeof serve> | null = null;
92
+ for (let tries = 0; tries < 10 && !server; tries++, port++) {
93
+ try { server = serve(port); } catch (e) {
94
+ if (!["EADDRINUSE", "EACCES"].includes((e as { code?: string }).code ?? "")) throw e;
95
+ const url = `http://127.0.0.1:${port}/`;
96
+ const up = await fetch(url, { signal: AbortSignal.timeout(1500) }).then((r) => r.ok, () => false);
97
+ if (up) { console.log(`\n kaizen web already at ${url}\n`); if (opts.open !== false) await openApp(url); return; }
98
+ console.log(` port ${port} refused by the OS and nothing answers there; trying ${port + 1}`);
99
+ }
100
+ }
101
+ if (!server) { console.log(`\n kaizen web could not bind any port from ${opts.port ?? DEFAULT_PORT}\n`); return; }
102
+
103
+ function serve(port: number) { return Bun.serve({
94
104
  hostname: "127.0.0.1",
95
105
  port,
96
106
  async fetch(req) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kaizen-agent",
3
- "version": "1.6.6",
3
+ "version": "1.6.8",
4
4
  "description": "Staged workflow for AI agents — code, writing, research, operations: plan, human approval, build, independent review, bounded fix loop.",
5
5
  "type": "module",
6
6
  "bin": {