agent-theatre 1.3.0 → 1.3.1

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 CHANGED
@@ -1306,7 +1306,26 @@ var Theatre = class {
1306
1306
  this.port = typeof addr === "object" && addr ? addr.port : port;
1307
1307
  const home = this.opts.home ?? join2(homedir2(), ".agent-theatre");
1308
1308
  mkdirSync(home, { recursive: true });
1309
+ try {
1310
+ const old = parseInt(readFileSync2(join2(home, "port"), "utf8").trim(), 10);
1311
+ if (old > 0 && old !== this.port && portAlive(old) && !existsSync(join2(home, "ports", String(old)))) {
1312
+ mkdirSync(join2(home, "ports"), { recursive: true });
1313
+ writeFileSync(join2(home, "ports", String(old)), "(started by an older build)");
1314
+ }
1315
+ } catch {
1316
+ }
1309
1317
  writeFileSync(join2(home, "port"), String(this.port));
1318
+ mkdirSync(join2(home, "ports"), { recursive: true });
1319
+ for (const f of readdirSync2(join2(home, "ports"))) {
1320
+ const n = parseInt(f, 10);
1321
+ if (n > 0 && n !== this.port && !portAlive(n)) {
1322
+ try {
1323
+ rmSync(join2(home, "ports", f));
1324
+ } catch {
1325
+ }
1326
+ }
1327
+ }
1328
+ writeFileSync(join2(home, "ports", String(this.port)), this.opts.cwd);
1310
1329
  const lostAfter = this.opts.lostAfterMs ?? 9e4;
1311
1330
  this.timer = setInterval(() => {
1312
1331
  const l = this.live;
@@ -1325,6 +1344,10 @@ var Theatre = class {
1325
1344
  if (readFileSync2(join2(home, "port"), "utf8").trim() === String(this.port)) rmSync(join2(home, "port"));
1326
1345
  } catch {
1327
1346
  }
1347
+ try {
1348
+ rmSync(join2(home, "ports", String(this.port)));
1349
+ } catch {
1350
+ }
1328
1351
  for (const ws of this.wss?.clients ?? []) ws.close();
1329
1352
  this.wss?.close();
1330
1353
  await new Promise((res) => this.server ? this.server.close(() => res()) : res());
@@ -1368,6 +1391,12 @@ function claudeBinary() {
1368
1391
  }
1369
1392
  return null;
1370
1393
  }
1394
+ function portAlive(port) {
1395
+ const r = spawnSync("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"], { encoding: "utf8", timeout: 2e3 });
1396
+ if (r.status === 0 && r.stdout.trim()) return true;
1397
+ if (r.error) return true;
1398
+ return false;
1399
+ }
1371
1400
 
1372
1401
  // src/scan.ts
1373
1402
  import { readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
@@ -1502,20 +1531,22 @@ function hasOurHooks(existing) {
1502
1531
  // src/hooks/script.ts
1503
1532
  var HOOK_SCRIPT = `#!/usr/bin/env node
1504
1533
  // Agent Theatre hook \u2014 forwards Claude Code events to the local theatre. Never blocks the agent.
1505
- import { readFileSync } from "node:fs";
1534
+ import { readFileSync, readdirSync } from "node:fs";
1506
1535
  import { homedir } from "node:os";
1507
1536
  import { join } from "node:path";
1508
- const portFile = join(process.env.AGENT_THEATRE_HOME || join(homedir(), ".agent-theatre"), "port");
1509
- let port = 0;
1510
- try { port = parseInt(readFileSync(portFile, "utf8").trim(), 10); } catch { process.exit(0); }
1511
- if (!port) process.exit(0);
1537
+ // Every running stage registers itself in ports/<port>; the event goes to all of them (browser, VS Code, a second repo\u2026).
1538
+ const home = process.env.AGENT_THEATRE_HOME || join(homedir(), ".agent-theatre");
1539
+ const ports = new Set();
1540
+ try { for (const f of readdirSync(join(home, "ports"))) { const n = parseInt(f, 10); if (n > 0) ports.add(n); } } catch { /* no registry yet */ }
1541
+ try { const n = parseInt(readFileSync(join(home, "port"), "utf8").trim(), 10); if (n > 0) ports.add(n); } catch { /* no port file */ }
1542
+ if (!ports.size) process.exit(0);
1512
1543
  let body = "";
1513
1544
  process.stdin.setEncoding("utf8");
1514
1545
  process.stdin.on("data", (c) => { body += c; if (body.length > 2_000_000) body = body.slice(0, 2_000_000); });
1515
1546
  process.stdin.on("end", async () => {
1516
1547
  try {
1517
- await fetch(\`http://127.0.0.1:\${port}/hook\`, { method: "POST", headers: { "content-type": "application/json" }, body, signal: AbortSignal.timeout(300) });
1518
- } catch { /* theatre not running or slow: that is fine */ }
1548
+ await Promise.allSettled([...ports].map((port) => fetch(\`http://127.0.0.1:\${port}/hook\`, { method: "POST", headers: { "content-type": "application/json" }, body, signal: AbortSignal.timeout(300) })));
1549
+ } catch { /* a theatre not running or slow: that is fine */ }
1519
1550
  process.exit(0);
1520
1551
  });
1521
1552
  setTimeout(() => process.exit(0), 600).unref();