agent-theatre 1.3.3 → 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/cli.js CHANGED
@@ -81,11 +81,16 @@ function cleanTarget(t) {
81
81
  if (/^\/dev\//.test(s) || /^&\d/.test(s)) return null;
82
82
  return s;
83
83
  }
84
- function extractWrites(command) {
84
+ function withoutHeredocBodies(command) {
85
+ 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] ?? "");
86
+ }
87
+ var plausibleFile = (t) => /[A-Za-z/.]/.test(t) && !/^\d+$/.test(t) && !/[()]$/.test(t);
88
+ function extractWrites(commandRaw) {
89
+ const command = withoutHeredocBodies(commandRaw);
85
90
  const out = /* @__PURE__ */ new Set();
86
91
  for (const m of command.matchAll(/(?:^|[\s)])\d?>{1,2}\s*([^\s|&;<>]+)/g)) {
87
92
  const t = cleanTarget(m[1] ?? "");
88
- if (t) out.add(t);
93
+ if (t && plausibleFile(t)) out.add(t);
89
94
  }
90
95
  for (const m of command.matchAll(/\btee\s+(?:-[a-z]+\s+)*((?:[^\s|&;<>]+\s*)+)/g)) {
91
96
  for (const f of (m[1] ?? "").split(/\s+/)) {
@@ -369,13 +374,15 @@ function hookPayloadToEvents(payload, opts = {}) {
369
374
  }
370
375
  function noteOf(text) {
371
376
  if (!text) return {};
372
- 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();
377
+ 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();
373
378
  if (!clean) return {};
374
- const sentences = clean.match(/[^.!?]+[.!?]+(?=\s|$)|[^.!?]+$/g)?.map((x) => x.trim()).filter(Boolean) ?? [clean];
379
+ const sentences = clean.split(/(?<=[.!?])\s+(?=["'(A-Z])/).map((x) => x.trim()).filter(Boolean);
380
+ if (!sentences.length) return {};
375
381
  const questions = sentences.filter((x) => x.endsWith("?"));
376
382
  let note = questions.length ? questions[questions.length - 1] : sentences[0];
377
- if (!questions.length) for (const next of sentences.slice(1)) {
378
- if (!/[.!?]$/.test(next) || note.length + next.length + 1 > 160) break;
383
+ if (!questions.length) for (const raw of sentences.slice(1)) {
384
+ const next = /[.!?]$/.test(raw) ? raw : raw.replace(/[^.!?]*$/, "").trim();
385
+ if (!next || note.length + next.length + 1 > 160) break;
379
386
  note = `${note} ${next}`;
380
387
  }
381
388
  if (note.length > 200) note = note.slice(0, 197).replace(/\s+\S*$/, "") + "\u2026";
@@ -924,7 +931,7 @@ var Director = class {
924
931
  this.forge = world.places.find((p) => p.kind === "forge") ?? world.places[0];
925
932
  this.dock = world.places.find((p) => p.kind === "dock") ?? this.forge;
926
933
  this.maxGap = opts.maxGapMs ?? 2500;
927
- this.msPerCell = opts.walkMsPerCell ?? 90;
934
+ this.msPerCell = opts.walkMsPerCell ?? 150;
928
935
  this.mood = new MoodTracker(opts.lostWindowMs ?? 9e4);
929
936
  this.actorAt.set("pip", this.forge.id);
930
937
  }
@@ -1090,7 +1097,7 @@ var Director = class {
1090
1097
  if (fromId === to.id) return;
1091
1098
  const from = this.places.get(fromId) ?? this.forge;
1092
1099
  const cells = Math.hypot(to.gx - from.gx, to.gy - from.gy);
1093
- this.emit(e, actor, { kind: "walk_to", target: to.id }, clamp(Math.round(cells * this.msPerCell), 300, 2500));
1100
+ this.emit(e, actor, { kind: "walk_to", target: to.id }, clamp(Math.round(cells * this.msPerCell), 700, 3500));
1094
1101
  this.actorAt.set(actor, to.id);
1095
1102
  }
1096
1103
  raised = 0;
@@ -1215,6 +1222,7 @@ var Theatre = class {
1215
1222
  lastSessionId = null;
1216
1223
  /** The most recent cwd of a Claude Code session that is NOT this repo (so an empty stage can point the person there). */
1217
1224
  elsewhere = null;
1225
+ openSessions = /* @__PURE__ */ new Set();
1218
1226
  timer = null;
1219
1227
  port = 0;
1220
1228
  routes() {
@@ -1236,7 +1244,13 @@ var Theatre = class {
1236
1244
  }
1237
1245
  const sid = payload.session_id;
1238
1246
  if (typeof sid === "string" && sid) this.lastSessionId = sid;
1239
- const { events } = hookPayloadToEvents(payload, { home: homedir2() });
1247
+ const hookName = payload.hook_event_name;
1248
+ if (typeof sid === "string" && sid) {
1249
+ if (hookName === "SessionEnd") this.openSessions.delete(sid);
1250
+ else this.openSessions.add(sid);
1251
+ }
1252
+ let { events } = hookPayloadToEvents(payload, { home: homedir2() });
1253
+ if (this.openSessions.size > 0) events = events.filter((e) => e.kind !== "session_end");
1240
1254
  if (events.length) this.ingest(events);
1241
1255
  return c.body(null, 204);
1242
1256
  });
@@ -1268,8 +1282,33 @@ var Theatre = class {
1268
1282
  if (!text) return c.json({ ok: false, error: "empty prompt" }, 400);
1269
1283
  if (!bin) return c.json({ ok: false, error: "Claude Code 2.x not found on PATH" }, 503);
1270
1284
  if (!this.lastSessionId) return c.json({ ok: false, error: "no session to resume yet" }, 409);
1271
- const child = spawn(bin, ["-p", "--resume", this.lastSessionId, text], { cwd: this.opts.cwd, stdio: "ignore", detached: true, env: process.env });
1285
+ const logPath = join2(this.opts.home ?? join2(homedir2(), ".agent-theatre"), "prompt.log");
1286
+ const child = spawn(bin, ["-p", "--resume", this.lastSessionId, text], { cwd: this.opts.cwd, stdio: ["ignore", "pipe", "pipe"], detached: true, env: process.env });
1287
+ let err = "";
1288
+ child.stdout?.on("data", (d) => {
1289
+ try {
1290
+ writeFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] out: ${String(d).slice(0, 2e3)}
1291
+ `, { flag: "a" });
1292
+ } catch {
1293
+ }
1294
+ });
1295
+ child.stderr?.on("data", (d) => {
1296
+ err += String(d);
1297
+ try {
1298
+ writeFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] err: ${String(d).slice(0, 2e3)}
1299
+ `, { flag: "a" });
1300
+ } catch {
1301
+ }
1302
+ });
1272
1303
  child.unref();
1304
+ const early = await new Promise((res) => {
1305
+ const t = setTimeout(() => res(null), 2500);
1306
+ child.once("exit", (code) => {
1307
+ clearTimeout(t);
1308
+ res(code ?? 1);
1309
+ });
1310
+ });
1311
+ 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);
1273
1312
  return c.json({ ok: true, session: this.lastSessionId });
1274
1313
  });
1275
1314
  a.get("/healthz", (c) => c.json({ ok: true, port: this.port, events: this.live.events.length }));