agent-theatre 1.3.4 → 1.3.5

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.d.ts CHANGED
@@ -30,6 +30,7 @@ declare class Theatre {
30
30
  private lastSessionId;
31
31
  /** The most recent cwd of a Claude Code session that is NOT this repo (so an empty stage can point the person there). */
32
32
  private elsewhere;
33
+ private readonly openSessions;
33
34
  private timer;
34
35
  port: number;
35
36
  constructor(opts: TheatreOptions);
package/dist/index.js CHANGED
@@ -72,11 +72,16 @@ function cleanTarget(t) {
72
72
  if (/^\/dev\//.test(s) || /^&\d/.test(s)) return null;
73
73
  return s;
74
74
  }
75
- function extractWrites(command) {
75
+ function withoutHeredocBodies(command) {
76
+ return command.replace(/<<-?\s*(['"]?)([A-Za-z_][A-Za-z0-9_]*)\1[^\n]*\n[\s\S]*?\n\s*\2(?=\s*(?:\n|$))/g, (m) => m.split("\n")[0] ?? "");
77
+ }
78
+ var plausibleFile = (t) => /[A-Za-z/.]/.test(t) && !/^\d+$/.test(t) && !/[()]$/.test(t);
79
+ function extractWrites(commandRaw) {
80
+ const command = withoutHeredocBodies(commandRaw);
76
81
  const out = /* @__PURE__ */ new Set();
77
82
  for (const m of command.matchAll(/(?:^|[\s)])\d?>{1,2}\s*([^\s|&;<>]+)/g)) {
78
83
  const t = cleanTarget(m[1] ?? "");
79
- if (t) out.add(t);
84
+ if (t && plausibleFile(t)) out.add(t);
80
85
  }
81
86
  for (const m of command.matchAll(/\btee\s+(?:-[a-z]+\s+)*((?:[^\s|&;<>]+\s*)+)/g)) {
82
87
  for (const f of (m[1] ?? "").split(/\s+/)) {
@@ -360,13 +365,15 @@ function hookPayloadToEvents(payload, opts = {}) {
360
365
  }
361
366
  function noteOf(text) {
362
367
  if (!text) return {};
363
- const clean = text.replace(/```[\s\S]*?```/g, " ").replace(/`([^`]*)`/g, "$1").replace(/\[([^\]]+)\]\([^)]*\)/g, "$1").replace(/\*+|#+|>+/g, "").replace(/(^|\s)_+|_+(?=\s|$)/g, "$1").replace(/^\s*[-•\d.)]+\s+/gm, "").replace(/\s+/g, " ").trim();
368
+ const clean = text.replace(/```[\s\S]*?```/g, " ").replace(/`([^`]*)`/g, "$1").replace(/\[([^\]]+)\]\([^)]*\)/g, "$1").replace(/\*+|#+|>+/g, "").replace(/(^|\s)_+|_+(?=\s|$)/g, "$1").replace(/^\s*(?:[-•]|\d+[.)])\s+/gm, "").replace(/\s+/g, " ").trim();
364
369
  if (!clean) return {};
365
- const sentences = clean.match(/[^.!?]+[.!?]+(?=\s|$)|[^.!?]+$/g)?.map((x) => x.trim()).filter(Boolean) ?? [clean];
370
+ const sentences = clean.split(/(?<=[.!?])\s+(?=["'(A-Z])/).map((x) => x.trim()).filter(Boolean);
371
+ if (!sentences.length) return {};
366
372
  const questions = sentences.filter((x) => x.endsWith("?"));
367
373
  let note = questions.length ? questions[questions.length - 1] : sentences[0];
368
- if (!questions.length) for (const next of sentences.slice(1)) {
369
- if (!/[.!?]$/.test(next) || note.length + next.length + 1 > 160) break;
374
+ if (!questions.length) for (const raw of sentences.slice(1)) {
375
+ const next = /[.!?]$/.test(raw) ? raw : raw.replace(/[^.!?]*$/, "").trim();
376
+ if (!next || note.length + next.length + 1 > 160) break;
370
377
  note = `${note} ${next}`;
371
378
  }
372
379
  if (note.length > 200) note = note.slice(0, 197).replace(/\s+\S*$/, "") + "\u2026";
@@ -915,7 +922,7 @@ var Director = class {
915
922
  this.forge = world.places.find((p) => p.kind === "forge") ?? world.places[0];
916
923
  this.dock = world.places.find((p) => p.kind === "dock") ?? this.forge;
917
924
  this.maxGap = opts.maxGapMs ?? 2500;
918
- this.msPerCell = opts.walkMsPerCell ?? 90;
925
+ this.msPerCell = opts.walkMsPerCell ?? 150;
919
926
  this.mood = new MoodTracker(opts.lostWindowMs ?? 9e4);
920
927
  this.actorAt.set("pip", this.forge.id);
921
928
  }
@@ -1081,7 +1088,7 @@ var Director = class {
1081
1088
  if (fromId === to.id) return;
1082
1089
  const from = this.places.get(fromId) ?? this.forge;
1083
1090
  const cells = Math.hypot(to.gx - from.gx, to.gy - from.gy);
1084
- this.emit(e, actor, { kind: "walk_to", target: to.id }, clamp(Math.round(cells * this.msPerCell), 300, 2500));
1091
+ this.emit(e, actor, { kind: "walk_to", target: to.id }, clamp(Math.round(cells * this.msPerCell), 700, 3500));
1085
1092
  this.actorAt.set(actor, to.id);
1086
1093
  }
1087
1094
  raised = 0;
@@ -1206,6 +1213,7 @@ var Theatre = class {
1206
1213
  lastSessionId = null;
1207
1214
  /** The most recent cwd of a Claude Code session that is NOT this repo (so an empty stage can point the person there). */
1208
1215
  elsewhere = null;
1216
+ openSessions = /* @__PURE__ */ new Set();
1209
1217
  timer = null;
1210
1218
  port = 0;
1211
1219
  routes() {
@@ -1227,7 +1235,13 @@ var Theatre = class {
1227
1235
  }
1228
1236
  const sid = payload.session_id;
1229
1237
  if (typeof sid === "string" && sid) this.lastSessionId = sid;
1230
- const { events } = hookPayloadToEvents(payload, { home: homedir2() });
1238
+ const hookName = payload.hook_event_name;
1239
+ if (typeof sid === "string" && sid) {
1240
+ if (hookName === "SessionEnd") this.openSessions.delete(sid);
1241
+ else this.openSessions.add(sid);
1242
+ }
1243
+ let { events } = hookPayloadToEvents(payload, { home: homedir2() });
1244
+ if (this.openSessions.size > 0) events = events.filter((e) => e.kind !== "session_end");
1231
1245
  if (events.length) this.ingest(events);
1232
1246
  return c.body(null, 204);
1233
1247
  });
@@ -1259,8 +1273,33 @@ var Theatre = class {
1259
1273
  if (!text) return c.json({ ok: false, error: "empty prompt" }, 400);
1260
1274
  if (!bin) return c.json({ ok: false, error: "Claude Code 2.x not found on PATH" }, 503);
1261
1275
  if (!this.lastSessionId) return c.json({ ok: false, error: "no session to resume yet" }, 409);
1262
- const child = spawn(bin, ["-p", "--resume", this.lastSessionId, text], { cwd: this.opts.cwd, stdio: "ignore", detached: true, env: process.env });
1276
+ const logPath = join2(this.opts.home ?? join2(homedir2(), ".agent-theatre"), "prompt.log");
1277
+ const child = spawn(bin, ["-p", "--resume", this.lastSessionId, text], { cwd: this.opts.cwd, stdio: ["ignore", "pipe", "pipe"], detached: true, env: process.env });
1278
+ let err = "";
1279
+ child.stdout?.on("data", (d) => {
1280
+ try {
1281
+ writeFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] out: ${String(d).slice(0, 2e3)}
1282
+ `, { flag: "a" });
1283
+ } catch {
1284
+ }
1285
+ });
1286
+ child.stderr?.on("data", (d) => {
1287
+ err += String(d);
1288
+ try {
1289
+ writeFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] err: ${String(d).slice(0, 2e3)}
1290
+ `, { flag: "a" });
1291
+ } catch {
1292
+ }
1293
+ });
1263
1294
  child.unref();
1295
+ const early = await new Promise((res) => {
1296
+ const t = setTimeout(() => res(null), 2500);
1297
+ child.once("exit", (code) => {
1298
+ clearTimeout(t);
1299
+ res(code ?? 1);
1300
+ });
1301
+ });
1302
+ if (early !== null && early !== 0) return c.json({ ok: false, error: `Claude Code exited ${early}: ${err.trim().split("\n").pop() ?? "see ~/.agent-theatre/prompt.log"}` }, 502);
1264
1303
  return c.json({ ok: true, session: this.lastSessionId });
1265
1304
  });
1266
1305
  a.get("/healthz", (c) => c.json({ ok: true, port: this.port, events: this.live.events.length }));