agent-board-runner 0.1.21 → 0.1.23

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.
Files changed (3) hide show
  1. package/dist/cli.js +51 -10
  2. package/dist/index.js +51 -10
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -572,7 +572,15 @@ async function savePastedImage(sessionId, extCode, bytes) {
572
572
  return path;
573
573
  }
574
574
  function registerPtySession(sessionId, pty, ticket) {
575
- const handle = { pty, ticket, outputBuffer: [], sockets: /* @__PURE__ */ new Set(), readOnly: false, exited: false };
575
+ const handle = {
576
+ pty,
577
+ ticket,
578
+ outputBuffer: [],
579
+ sockets: /* @__PURE__ */ new Set(),
580
+ readOnly: false,
581
+ exited: false,
582
+ lastAssistantMessage: null
583
+ };
576
584
  pty.onExit(() => {
577
585
  handle.exited = true;
578
586
  });
@@ -588,6 +596,10 @@ function registerPtySession(sessionId, pty, ticket) {
588
596
  function getPtySession(sessionId) {
589
597
  return registry.get(sessionId);
590
598
  }
599
+ function recordLastAssistantMessage(sessionId, message) {
600
+ const handle = registry.get(sessionId);
601
+ if (handle) handle.lastAssistantMessage = message;
602
+ }
591
603
  function setPtySessionReadOnly(sessionId, readOnly) {
592
604
  const handle = registry.get(sessionId);
593
605
  if (handle) handle.readOnly = readOnly;
@@ -1297,10 +1309,10 @@ function detectMoment(input) {
1297
1309
  return null;
1298
1310
  }
1299
1311
 
1300
- function reportToRunner(sessionId, ticket, port, moment) {
1312
+ function reportToRunner(sessionId, ticket, port, moment, lastAssistantMessage) {
1301
1313
  return new Promise((resolve) => {
1302
1314
  try {
1303
- const body = JSON.stringify({ sessionId, ticket, moment });
1315
+ const body = JSON.stringify({ sessionId, ticket, moment, lastAssistantMessage });
1304
1316
  const req = http.request(
1305
1317
  {
1306
1318
  host: "127.0.0.1",
@@ -1349,7 +1361,10 @@ function reportToRunner(sessionId, ticket, port, moment) {
1349
1361
  const sessionId = process.env.AGENT_BOARD_SESSION_ID;
1350
1362
  const ticket = process.env.AGENT_BOARD_STATUSLINE_TICKET;
1351
1363
  const port = Number(process.env.AGENT_BOARD_STATUSLINE_PORT);
1352
- if (sessionId && ticket && port) await reportToRunner(sessionId, ticket, port, moment);
1364
+ if (sessionId && ticket && port) {
1365
+ const lastAssistantMessage = moment === "turn_stopped" && typeof input.lastAssistantMessage === "string" ? input.lastAssistantMessage : undefined;
1366
+ await reportToRunner(sessionId, ticket, port, moment, lastAssistantMessage);
1367
+ }
1353
1368
  }
1354
1369
  } catch {
1355
1370
  // A malformed/unexpected hook payload must never block the agent's own turn.
@@ -2088,6 +2103,7 @@ async function handleStatuslineReport(req, res, supabase, config) {
2088
2103
  sendJson(res, 500, { error: "Erro interno do runner." });
2089
2104
  }
2090
2105
  }
2106
+ var MAX_LAST_ASSISTANT_MESSAGE_CHARS = 4e3;
2091
2107
  async function handleAgentHookReport(req, res) {
2092
2108
  let body;
2093
2109
  try {
@@ -2106,6 +2122,9 @@ async function handleAgentHookReport(req, res) {
2106
2122
  sendJson(res, 403, { error: "Ticket inv\xE1lido para esta sess\xE3o." });
2107
2123
  return;
2108
2124
  }
2125
+ if (typeof body.lastAssistantMessage === "string" && body.lastAssistantMessage.length > 0) {
2126
+ recordLastAssistantMessage(body.sessionId, body.lastAssistantMessage.slice(0, MAX_LAST_ASSISTANT_MESSAGE_CHARS));
2127
+ }
2109
2128
  handleAgentHookEvent(body.sessionId, body.moment);
2110
2129
  res.writeHead(204, { "Access-Control-Allow-Origin": "*" });
2111
2130
  res.end();
@@ -4373,7 +4392,11 @@ async function openWorkflowShell(supabase, config, payload) {
4373
4392
  }
4374
4393
  );
4375
4394
  registerPtySession(executionId, pty, shellTicket);
4376
- pty.onExit(({ exitCode }) => void handleWorkflowShellExit(supabase, executionId, exitCode));
4395
+ const activityWatcher = watchWorkflowShellActivity(supabase, executionId, pty);
4396
+ pty.onExit(({ exitCode }) => {
4397
+ activityWatcher.stop();
4398
+ void handleWorkflowShellExit(supabase, executionId, exitCode);
4399
+ });
4377
4400
  waitForIdleThenRun(pty, () => {
4378
4401
  try {
4379
4402
  writePasteAndSubmit(pty, executionId, instructions, "the workflow runbook", logger.runner);
@@ -4383,13 +4406,27 @@ async function openWorkflowShell(supabase, config, payload) {
4383
4406
  });
4384
4407
  return { claudeSessionId, shellTicket };
4385
4408
  }
4386
- async function handleWorkflowShellExit(supabase, executionId, exitCode) {
4387
- const status = exitCode === 0 ? "done" : "failed";
4388
- const { error } = await supabase.rpc("complete_workflow_execution", { p_execution_id: executionId, p_status: status });
4409
+ async function completeWorkflowExecution(supabase, executionId, status) {
4410
+ const summary = getPtySession(executionId)?.lastAssistantMessage ?? null;
4411
+ const { error } = await supabase.rpc("complete_workflow_execution", { p_execution_id: executionId, p_status: status, p_summary: summary });
4389
4412
  if (error) {
4390
- logger.runner.error("WORKFLOW_SHELL", `Failed to record exit status for workflow execution ${executionId}:`, error);
4413
+ logger.runner.error("WORKFLOW_SHELL", `Failed to record ${status} status for workflow execution ${executionId}:`, error);
4391
4414
  }
4392
4415
  }
4416
+ async function handleWorkflowShellExit(supabase, executionId, exitCode) {
4417
+ unregisterAgentActivityWatcher(executionId);
4418
+ await completeWorkflowExecution(supabase, executionId, exitCode === 0 ? "done" : "failed");
4419
+ }
4420
+ function watchWorkflowShellActivity(supabase, executionId, pty) {
4421
+ const watcher = new AgentActivityWatcher((state) => {
4422
+ if (state === "done" || state === "waiting_user") {
4423
+ void completeWorkflowExecution(supabase, executionId, "done");
4424
+ }
4425
+ });
4426
+ pty.onData((chunk) => watcher.push(chunk));
4427
+ registerAgentActivityWatcher(executionId, watcher);
4428
+ return watcher;
4429
+ }
4393
4430
  async function reconcileOrphanedWorkflowExecutions(supabase, config) {
4394
4431
  const { data, error } = await supabase.from("workflow_executions").select("id, project_id, claude_session_id, shell_ticket").eq("runner_id", config.runnerId).in("status", ["running", "paused_on_question"]);
4395
4432
  if (error) {
@@ -4410,7 +4447,11 @@ async function reconcileOrphanedWorkflowExecutions(supabase, config) {
4410
4447
  }
4411
4448
  const pty = resumeAgentPty(registered.repoPath, claudeSessionId, executionId, shellTicket);
4412
4449
  registerPtySession(executionId, pty, shellTicket);
4413
- pty.onExit(({ exitCode }) => void handleWorkflowShellExit(supabase, executionId, exitCode));
4450
+ const activityWatcher = watchWorkflowShellActivity(supabase, executionId, pty);
4451
+ pty.onExit(({ exitCode }) => {
4452
+ activityWatcher.stop();
4453
+ void handleWorkflowShellExit(supabase, executionId, exitCode);
4454
+ });
4414
4455
  logger.runner.info("WORKFLOW_SHELL", `Re-spawned orphaned workflow execution ${executionId} (runner restarted while it was still active).`);
4415
4456
  }
4416
4457
  }
package/dist/index.js CHANGED
@@ -292,7 +292,15 @@ async function savePastedImage(sessionId, extCode, bytes) {
292
292
  return path;
293
293
  }
294
294
  function registerPtySession(sessionId, pty, ticket) {
295
- const handle = { pty, ticket, outputBuffer: [], sockets: /* @__PURE__ */ new Set(), readOnly: false, exited: false };
295
+ const handle = {
296
+ pty,
297
+ ticket,
298
+ outputBuffer: [],
299
+ sockets: /* @__PURE__ */ new Set(),
300
+ readOnly: false,
301
+ exited: false,
302
+ lastAssistantMessage: null
303
+ };
296
304
  pty.onExit(() => {
297
305
  handle.exited = true;
298
306
  });
@@ -308,6 +316,10 @@ function registerPtySession(sessionId, pty, ticket) {
308
316
  function getPtySession(sessionId) {
309
317
  return registry.get(sessionId);
310
318
  }
319
+ function recordLastAssistantMessage(sessionId, message) {
320
+ const handle = registry.get(sessionId);
321
+ if (handle) handle.lastAssistantMessage = message;
322
+ }
311
323
  function setPtySessionReadOnly(sessionId, readOnly) {
312
324
  const handle = registry.get(sessionId);
313
325
  if (handle) handle.readOnly = readOnly;
@@ -1017,10 +1029,10 @@ function detectMoment(input) {
1017
1029
  return null;
1018
1030
  }
1019
1031
 
1020
- function reportToRunner(sessionId, ticket, port, moment) {
1032
+ function reportToRunner(sessionId, ticket, port, moment, lastAssistantMessage) {
1021
1033
  return new Promise((resolve) => {
1022
1034
  try {
1023
- const body = JSON.stringify({ sessionId, ticket, moment });
1035
+ const body = JSON.stringify({ sessionId, ticket, moment, lastAssistantMessage });
1024
1036
  const req = http.request(
1025
1037
  {
1026
1038
  host: "127.0.0.1",
@@ -1069,7 +1081,10 @@ function reportToRunner(sessionId, ticket, port, moment) {
1069
1081
  const sessionId = process.env.AGENT_BOARD_SESSION_ID;
1070
1082
  const ticket = process.env.AGENT_BOARD_STATUSLINE_TICKET;
1071
1083
  const port = Number(process.env.AGENT_BOARD_STATUSLINE_PORT);
1072
- if (sessionId && ticket && port) await reportToRunner(sessionId, ticket, port, moment);
1084
+ if (sessionId && ticket && port) {
1085
+ const lastAssistantMessage = moment === "turn_stopped" && typeof input.lastAssistantMessage === "string" ? input.lastAssistantMessage : undefined;
1086
+ await reportToRunner(sessionId, ticket, port, moment, lastAssistantMessage);
1087
+ }
1073
1088
  }
1074
1089
  } catch {
1075
1090
  // A malformed/unexpected hook payload must never block the agent's own turn.
@@ -1808,6 +1823,7 @@ async function handleStatuslineReport(req, res, supabase, config) {
1808
1823
  sendJson(res, 500, { error: "Erro interno do runner." });
1809
1824
  }
1810
1825
  }
1826
+ var MAX_LAST_ASSISTANT_MESSAGE_CHARS = 4e3;
1811
1827
  async function handleAgentHookReport(req, res) {
1812
1828
  let body;
1813
1829
  try {
@@ -1826,6 +1842,9 @@ async function handleAgentHookReport(req, res) {
1826
1842
  sendJson(res, 403, { error: "Ticket inv\xE1lido para esta sess\xE3o." });
1827
1843
  return;
1828
1844
  }
1845
+ if (typeof body.lastAssistantMessage === "string" && body.lastAssistantMessage.length > 0) {
1846
+ recordLastAssistantMessage(body.sessionId, body.lastAssistantMessage.slice(0, MAX_LAST_ASSISTANT_MESSAGE_CHARS));
1847
+ }
1829
1848
  handleAgentHookEvent(body.sessionId, body.moment);
1830
1849
  res.writeHead(204, { "Access-Control-Allow-Origin": "*" });
1831
1850
  res.end();
@@ -4261,7 +4280,11 @@ async function openWorkflowShell(supabase, config, payload) {
4261
4280
  }
4262
4281
  );
4263
4282
  registerPtySession(executionId, pty, shellTicket);
4264
- pty.onExit(({ exitCode }) => void handleWorkflowShellExit(supabase, executionId, exitCode));
4283
+ const activityWatcher = watchWorkflowShellActivity(supabase, executionId, pty);
4284
+ pty.onExit(({ exitCode }) => {
4285
+ activityWatcher.stop();
4286
+ void handleWorkflowShellExit(supabase, executionId, exitCode);
4287
+ });
4265
4288
  waitForIdleThenRun(pty, () => {
4266
4289
  try {
4267
4290
  writePasteAndSubmit(pty, executionId, instructions, "the workflow runbook", logger.runner);
@@ -4271,13 +4294,27 @@ async function openWorkflowShell(supabase, config, payload) {
4271
4294
  });
4272
4295
  return { claudeSessionId, shellTicket };
4273
4296
  }
4274
- async function handleWorkflowShellExit(supabase, executionId, exitCode) {
4275
- const status = exitCode === 0 ? "done" : "failed";
4276
- const { error } = await supabase.rpc("complete_workflow_execution", { p_execution_id: executionId, p_status: status });
4297
+ async function completeWorkflowExecution(supabase, executionId, status) {
4298
+ const summary = getPtySession(executionId)?.lastAssistantMessage ?? null;
4299
+ const { error } = await supabase.rpc("complete_workflow_execution", { p_execution_id: executionId, p_status: status, p_summary: summary });
4277
4300
  if (error) {
4278
- logger.runner.error("WORKFLOW_SHELL", `Failed to record exit status for workflow execution ${executionId}:`, error);
4301
+ logger.runner.error("WORKFLOW_SHELL", `Failed to record ${status} status for workflow execution ${executionId}:`, error);
4279
4302
  }
4280
4303
  }
4304
+ async function handleWorkflowShellExit(supabase, executionId, exitCode) {
4305
+ unregisterAgentActivityWatcher(executionId);
4306
+ await completeWorkflowExecution(supabase, executionId, exitCode === 0 ? "done" : "failed");
4307
+ }
4308
+ function watchWorkflowShellActivity(supabase, executionId, pty) {
4309
+ const watcher = new AgentActivityWatcher((state) => {
4310
+ if (state === "done" || state === "waiting_user") {
4311
+ void completeWorkflowExecution(supabase, executionId, "done");
4312
+ }
4313
+ });
4314
+ pty.onData((chunk) => watcher.push(chunk));
4315
+ registerAgentActivityWatcher(executionId, watcher);
4316
+ return watcher;
4317
+ }
4281
4318
  async function reconcileOrphanedWorkflowExecutions(supabase, config) {
4282
4319
  const { data, error } = await supabase.from("workflow_executions").select("id, project_id, claude_session_id, shell_ticket").eq("runner_id", config.runnerId).in("status", ["running", "paused_on_question"]);
4283
4320
  if (error) {
@@ -4298,7 +4335,11 @@ async function reconcileOrphanedWorkflowExecutions(supabase, config) {
4298
4335
  }
4299
4336
  const pty = resumeAgentPty(registered.repoPath, claudeSessionId, executionId, shellTicket);
4300
4337
  registerPtySession(executionId, pty, shellTicket);
4301
- pty.onExit(({ exitCode }) => void handleWorkflowShellExit(supabase, executionId, exitCode));
4338
+ const activityWatcher = watchWorkflowShellActivity(supabase, executionId, pty);
4339
+ pty.onExit(({ exitCode }) => {
4340
+ activityWatcher.stop();
4341
+ void handleWorkflowShellExit(supabase, executionId, exitCode);
4342
+ });
4302
4343
  logger.runner.info("WORKFLOW_SHELL", `Re-spawned orphaned workflow execution ${executionId} (runner restarted while it was still active).`);
4303
4344
  }
4304
4345
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-board-runner",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "description": "Local runner for Agent Board — signs in with your own Supabase account (no shared secrets) and drives Claude Code against repos under your local repos root.",
5
5
  "type": "module",
6
6
  "bin": {