agent-theatre 1.1.4 → 1.2.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/cli.js CHANGED
@@ -9,9 +9,10 @@ import pc from "picocolors";
9
9
 
10
10
  // src/server.ts
11
11
  import { createServer } from "http";
12
- import { existsSync, mkdirSync, readFileSync as readFileSync2, rmSync, writeFileSync, realpathSync } from "fs";
12
+ import { existsSync, mkdirSync, readFileSync as readFileSync2, readdirSync as readdirSync2, rmSync, writeFileSync, realpathSync } from "fs";
13
13
  import { homedir as homedir2 } from "os";
14
14
  import { join as join2, basename as basename2, resolve, sep } from "path";
15
+ import { spawn, spawnSync } from "child_process";
15
16
  import { Hono } from "hono";
16
17
  import { getRequestListener } from "@hono/node-server";
17
18
  import { WebSocketServer } from "ws";
@@ -1195,6 +1196,8 @@ var Theatre = class {
1195
1196
  wss = null;
1196
1197
  live;
1197
1198
  signal = "ok";
1199
+ /** The most recent Claude Code session that spoke to this stage; what a prompt from the stage resumes. */
1200
+ lastSessionId = null;
1198
1201
  timer = null;
1199
1202
  port = 0;
1200
1203
  routes() {
@@ -1208,6 +1211,8 @@ var Theatre = class {
1208
1211
  }
1209
1212
  const cwd = typeof payload?.cwd === "string" ? payload.cwd : "";
1210
1213
  if (cwd && !isWithin(cwd, this.opts.cwd)) return c.body(null, 204);
1214
+ const sid = payload.session_id;
1215
+ if (typeof sid === "string" && sid) this.lastSessionId = sid;
1211
1216
  const { events } = hookPayloadToEvents(payload, { home: homedir2() });
1212
1217
  if (events.length) this.ingest(events);
1213
1218
  return c.body(null, 204);
@@ -1232,6 +1237,18 @@ var Theatre = class {
1232
1237
  const beats = direct(events, world);
1233
1238
  return c.json({ mode: "replay", repoId: this.opts.repoId, title: s.title, world, events, beats, warnings: warnings.length });
1234
1239
  });
1240
+ a.get("/api/prompt", (c) => c.json({ available: !!claudeBinary() && !!this.lastSessionId, session: this.lastSessionId }));
1241
+ a.post("/api/prompt", async (c) => {
1242
+ const body = await c.req.json().catch(() => ({}));
1243
+ const text = typeof body.text === "string" ? body.text.trim() : "";
1244
+ const bin = claudeBinary();
1245
+ if (!text) return c.json({ ok: false, error: "empty prompt" }, 400);
1246
+ if (!bin) return c.json({ ok: false, error: "Claude Code 2.x not found on PATH" }, 503);
1247
+ if (!this.lastSessionId) return c.json({ ok: false, error: "no session to resume yet" }, 409);
1248
+ const child = spawn(bin, ["-p", "--resume", this.lastSessionId, text], { cwd: this.opts.cwd, stdio: "ignore", detached: true, env: process.env });
1249
+ child.unref();
1250
+ return c.json({ ok: true, session: this.lastSessionId });
1251
+ });
1235
1252
  a.get("/healthz", (c) => c.json({ ok: true, port: this.port, events: this.live.events.length }));
1236
1253
  if (this.opts.webDir) {
1237
1254
  const dir = this.opts.webDir;
@@ -1264,7 +1281,15 @@ var Theatre = class {
1264
1281
  this.broadcast({ type: "beats", beats: out, events });
1265
1282
  return out;
1266
1283
  }
1284
+ /** In-process subscribers (the VS Code extension relays these to its webview). */
1285
+ listeners = /* @__PURE__ */ new Set();
1267
1286
  broadcast(msg) {
1287
+ for (const l of this.listeners) {
1288
+ try {
1289
+ l(msg);
1290
+ } catch {
1291
+ }
1292
+ }
1268
1293
  const data = JSON.stringify(msg);
1269
1294
  for (const ws of this.wss?.clients ?? []) if (ws.readyState === 1) ws.send(data);
1270
1295
  }
@@ -1307,6 +1332,10 @@ var Theatre = class {
1307
1332
  this.wss?.close();
1308
1333
  await new Promise((res) => this.server ? this.server.close(() => res()) : res());
1309
1334
  }
1335
+ /** The live scene as /api/scene returns it. */
1336
+ scene() {
1337
+ return { mode: "live", repoId: this.opts.repoId, world: this.live.world, events: this.live.events, beats: this.live.beats, signal: this.signal };
1338
+ }
1310
1339
  get url() {
1311
1340
  return `http://127.0.0.1:${this.port}/`;
1312
1341
  }
@@ -1325,9 +1354,26 @@ function isWithin(dir, root) {
1325
1354
  const d = norm3(dir), r = norm3(root);
1326
1355
  return d === r || d.startsWith(r.endsWith(sep) ? r : r + sep);
1327
1356
  }
1357
+ function claudeBinary() {
1358
+ const cands = [process.env.AGENT_THEATRE_CLAUDE, "claude"].filter((x) => !!x);
1359
+ try {
1360
+ const ext = join2(homedir2(), ".vscode", "extensions");
1361
+ for (const d of (existsSync(ext) ? readdirSync2(ext) : []).filter((n) => n.startsWith("anthropic.claude-code-")).sort().reverse()) cands.push(join2(ext, d, "resources", "native-binary", "claude"));
1362
+ } catch {
1363
+ }
1364
+ for (const c of cands) {
1365
+ try {
1366
+ const r = spawnSync(c, ["--version"], { encoding: "utf8", timeout: 4e3 });
1367
+ const m = /^(\d+)\./.exec(r.stdout.trim());
1368
+ if (r.status === 0 && m && Number(m[1]) >= 2) return c;
1369
+ } catch {
1370
+ }
1371
+ }
1372
+ return null;
1373
+ }
1328
1374
 
1329
1375
  // src/scan.ts
1330
- import { readdirSync as readdirSync2, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
1376
+ import { readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
1331
1377
  import { join as join3, relative } from "path";
1332
1378
  function scanRepo(root, opts = {}) {
1333
1379
  const maxFiles = opts.maxFiles ?? 2e4, maxBytes = opts.maxBytesForLoc ?? 512e3;
@@ -1336,7 +1382,7 @@ function scanRepo(root, opts = {}) {
1336
1382
  if (files.length >= maxFiles) return;
1337
1383
  let ents;
1338
1384
  try {
1339
- ents = readdirSync2(dir, { withFileTypes: true });
1385
+ ents = readdirSync3(dir, { withFileTypes: true });
1340
1386
  } catch {
1341
1387
  return;
1342
1388
  }