agent-board-runner 0.1.24 → 0.1.26

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 +90 -19
  2. package/dist/index.js +90 -19
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -320,7 +320,7 @@ var BOX_DRAWING_PATTERN = /[─-╿]/g;
320
320
  var WORKING_PATTERN = /esc to interrupt/i;
321
321
  var BACKGROUND_SHELL_PATTERN = /\b[1-9]\d*\s+shells?\b/i;
322
322
  function isAgentHookMoment(value) {
323
- return value === "implementing" || value === "tool_active" || value === "waiting_plan_approval" || value === "waiting_prototype_review" || value === "waiting_workflow_answer" || value === "commit_detected" || value === "turn_stopped" || value === "notification";
323
+ return value === "implementing" || value === "tool_active" || value === "waiting_plan_approval" || value === "waiting_prototype_review" || value === "waiting_workflow_answer" || value === "waiting_child_card" || value === "commit_detected" || value === "turn_stopped" || value === "notification";
324
324
  }
325
325
  var USAGE_LIMIT_PATTERN = /(usage limit reached|limit reached[^\n]{0,60}resets|reached your (?:usage|session) limit|out of (?:weekly|session) (?:usage|limit))/i;
326
326
  var PLAN_APPROVAL_PATTERN = /would you like to proceed\??/i;
@@ -374,6 +374,8 @@ var AgentActivityWatcher = class {
374
374
  prototypeReviewPending = false;
375
375
  /** Same shape as prototypeReviewPending, for ask_workflow_question (see docs/ai-knowledges/workflows-visuais-mecanismo.md). */
376
376
  workflowAnswerPending = false;
377
+ /** Same shape as workflowAnswerPending, for wait_for_card_completion (see docs/ai-knowledges/workflows-visuais-aja-orquestra-card.md) — only ever latched on a card-less workflow shell pty. */
378
+ childCardPending = false;
377
379
  toolCallPending = false;
378
380
  commitDetected = false;
379
381
  timer;
@@ -417,6 +419,7 @@ var AgentActivityWatcher = class {
417
419
  this.planApprovalPending = false;
418
420
  this.prototypeReviewPending = false;
419
421
  this.workflowAnswerPending = false;
422
+ this.childCardPending = false;
420
423
  this.commitDetected = false;
421
424
  this.setState("implementing");
422
425
  break;
@@ -436,6 +439,10 @@ var AgentActivityWatcher = class {
436
439
  this.workflowAnswerPending = true;
437
440
  this.setState("waiting_workflow_answer");
438
441
  break;
442
+ case "waiting_child_card":
443
+ this.childCardPending = true;
444
+ this.setState("waiting_child_card");
445
+ break;
439
446
  case "commit_detected":
440
447
  this.commitDetected = true;
441
448
  break;
@@ -443,6 +450,7 @@ var AgentActivityWatcher = class {
443
450
  if (this.usageLimited) break;
444
451
  if (this.prototypeReviewPending) break;
445
452
  if (this.workflowAnswerPending) break;
453
+ if (this.childCardPending) break;
446
454
  if (BACKGROUND_SHELL_PATTERN.test(this.recentOutput())) {
447
455
  this.setState("waiting_background");
448
456
  break;
@@ -1280,6 +1288,12 @@ function detectMoment(input) {
1280
1288
  // \u2014 see the workflowAnswerPending latch in agentActivity.ts's reportHookMoment.
1281
1289
  if (eventName === "PostToolUse" && input.tool_name === "mcp__agent-board__ask_workflow_question") return "waiting_workflow_answer";
1282
1290
 
1291
+ // Same shape as ask_workflow_question above, but for a workflow graph's "act" node (see
1292
+ // wait_for_card_completion in boardTools.ts and
1293
+ // docs/ai-knowledges/workflows-visuais-aja-orquestra-card.md) \u2014 see the childCardPending latch
1294
+ // in agentActivity.ts's reportHookMoment.
1295
+ if (eventName === "PostToolUse" && input.tool_name === "mcp__agent-board__wait_for_card_completion") return "waiting_child_card";
1296
+
1283
1297
  // A commit that actually succeeded, read off the Bash tool's own structured result instead
1284
1298
  // of guessing from scrollback text (exact, and immune to locale/color/format changes to
1285
1299
  // git's own banner). Any other Bash completion still reports "tool_active" below, same as
@@ -4413,14 +4427,22 @@ async function completeWorkflowExecution(supabase, executionId, status) {
4413
4427
  logger.runner.error("WORKFLOW_SHELL", `Failed to record ${status} status for workflow execution ${executionId}:`, error);
4414
4428
  }
4415
4429
  }
4430
+ var intentionallyStoppedExecutionIds = /* @__PURE__ */ new Set();
4431
+ async function finalizeWorkflowExecution(supabase, executionId, status) {
4432
+ intentionallyStoppedExecutionIds.add(executionId);
4433
+ await completeWorkflowExecution(supabase, executionId, status);
4434
+ stopPtySession(executionId);
4435
+ }
4416
4436
  async function handleWorkflowShellExit(supabase, executionId, exitCode) {
4417
4437
  unregisterAgentActivityWatcher(executionId);
4438
+ if (intentionallyStoppedExecutionIds.delete(executionId)) return;
4418
4439
  await completeWorkflowExecution(supabase, executionId, exitCode === 0 ? "done" : "failed");
4440
+ stopPtySession(executionId);
4419
4441
  }
4420
4442
  function watchWorkflowShellActivity(supabase, executionId, pty) {
4421
4443
  const watcher = new AgentActivityWatcher((state) => {
4422
4444
  if (state === "done" || state === "waiting_user") {
4423
- void completeWorkflowExecution(supabase, executionId, "done");
4445
+ void finalizeWorkflowExecution(supabase, executionId, "done");
4424
4446
  }
4425
4447
  });
4426
4448
  pty.onData((chunk) => watcher.push(chunk));
@@ -4428,7 +4450,7 @@ function watchWorkflowShellActivity(supabase, executionId, pty) {
4428
4450
  return watcher;
4429
4451
  }
4430
4452
  async function reconcileOrphanedWorkflowExecutions(supabase, config) {
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"]);
4453
+ 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", "waiting_child_card"]);
4432
4454
  if (error) {
4433
4455
  logger.runner.error("WORKFLOW_SHELL", "Failed to scan orphaned workflow executions:", error.message);
4434
4456
  return;
@@ -4436,23 +4458,68 @@ async function reconcileOrphanedWorkflowExecutions(supabase, config) {
4436
4458
  for (const execution of data ?? []) {
4437
4459
  const { id: executionId, project_id: projectId, claude_session_id: claudeSessionId, shell_ticket: shellTicket } = execution;
4438
4460
  if (getPtySession(executionId)) continue;
4439
- if (!claudeSessionId || !shellTicket) {
4440
- logger.runner.warn("WORKFLOW_SHELL", `Workflow execution ${executionId} is missing claude_session_id/shell_ticket \u2014 cannot resume it.`);
4441
- continue;
4442
- }
4443
- const registered = config.registeredProjects.find((project) => project.id === projectId);
4444
- if (!registered) {
4445
- logger.runner.warn("WORKFLOW_SHELL", `Workflow execution ${executionId}: project ${projectId} is not registered on this runner \u2014 skipping.`);
4446
- continue;
4461
+ const pty = reattachWorkflowShellPty(supabase, config, executionId, projectId, claudeSessionId, shellTicket);
4462
+ if (pty) logger.runner.info("WORKFLOW_SHELL", `Re-spawned orphaned workflow execution ${executionId} (runner restarted while it was still active).`);
4463
+ }
4464
+ }
4465
+ function reattachWorkflowShellPty(supabase, config, executionId, projectId, claudeSessionId, shellTicket) {
4466
+ if (!claudeSessionId || !shellTicket) {
4467
+ logger.runner.warn("WORKFLOW_SHELL", `Workflow execution ${executionId} is missing claude_session_id/shell_ticket \u2014 cannot resume it.`);
4468
+ return null;
4469
+ }
4470
+ const registered = config.registeredProjects.find((project) => project.id === projectId);
4471
+ if (!registered) {
4472
+ logger.runner.warn("WORKFLOW_SHELL", `Workflow execution ${executionId}: project ${projectId} is not registered on this runner \u2014 skipping.`);
4473
+ return null;
4474
+ }
4475
+ const pty = resumeAgentPty(registered.repoPath, claudeSessionId, executionId, shellTicket);
4476
+ registerPtySession(executionId, pty, shellTicket);
4477
+ const activityWatcher = watchWorkflowShellActivity(supabase, executionId, pty);
4478
+ pty.onExit(({ exitCode }) => {
4479
+ activityWatcher.stop();
4480
+ void handleWorkflowShellExit(supabase, executionId, exitCode);
4481
+ });
4482
+ return pty;
4483
+ }
4484
+ async function resumeWorkflowExecutionWaitingOnChildCard(supabase, config, card, { viaCancellation }) {
4485
+ const { data, error } = await supabase.from("workflow_executions").select("id, project_id, claude_session_id, shell_ticket").eq("waiting_card_id", card.id).eq("status", "waiting_child_card").eq("runner_id", config.runnerId).maybeSingle();
4486
+ if (error) {
4487
+ logger.runner.error("WORKFLOW_SHELL", `Failed to look up a workflow execution waiting on card ${card.id}:`, error.message);
4488
+ return;
4489
+ }
4490
+ const execution = data;
4491
+ if (!execution) return;
4492
+ const { data: lastComment } = await supabase.from("card_comments").select("body").eq("card_id", card.id).order("created_at", { ascending: false }).limit(1).maybeSingle();
4493
+ const lastCommentBody = lastComment?.body ?? null;
4494
+ const resumeText = viaCancellation ? `Card "${card.title}" (id: ${card.id}) foi CANCELADO/ARQUIVADO, n\xE3o conclu\xEDdo normalmente.
4495
+
4496
+ Prossiga com o workflow a partir do passo seguinte, levando isso em conta.` : `Card "${card.title}" (id: ${card.id}) chegou em "deploy" \u2014 implementado, revisado e empacotado (o push final para "fim" \xE9 manual, fora do controle de qualquer agente).
4497
+
4498
+ Resumo: "${lastCommentBody ?? "(nenhum coment\xE1rio encontrado no card)"}"
4499
+
4500
+ Prossiga com o workflow a partir do passo seguinte.`;
4501
+ let handle = getPtySession(execution.id);
4502
+ if (!handle || handle.exited) {
4503
+ const pty = reattachWorkflowShellPty(supabase, config, execution.id, execution.project_id, execution.claude_session_id, execution.shell_ticket);
4504
+ if (!pty) {
4505
+ logger.runner.error("WORKFLOW_SHELL", `Workflow execution ${execution.id} has no live pty and could not be reattached \u2014 cannot resume it with child card ${card.id}'s result.`);
4506
+ return;
4447
4507
  }
4448
- const pty = resumeAgentPty(registered.repoPath, claudeSessionId, executionId, shellTicket);
4449
- registerPtySession(executionId, pty, shellTicket);
4450
- const activityWatcher = watchWorkflowShellActivity(supabase, executionId, pty);
4451
- pty.onExit(({ exitCode }) => {
4452
- activityWatcher.stop();
4453
- void handleWorkflowShellExit(supabase, executionId, exitCode);
4454
- });
4455
- logger.runner.info("WORKFLOW_SHELL", `Re-spawned orphaned workflow execution ${executionId} (runner restarted while it was still active).`);
4508
+ handle = getPtySession(execution.id);
4509
+ }
4510
+ if (!handle) return;
4511
+ const { error: resumeError } = await supabase.rpc("resume_workflow_execution_after_child_card", {
4512
+ p_execution_id: execution.id,
4513
+ p_child_card_id: card.id
4514
+ });
4515
+ if (resumeError) {
4516
+ logger.runner.error("WORKFLOW_SHELL", `Failed to resume workflow execution ${execution.id} after child card ${card.id}:`, resumeError.message);
4517
+ return;
4518
+ }
4519
+ try {
4520
+ writePasteAndSubmit(handle.pty, execution.id, resumeText, "child card result", logger.runner);
4521
+ } catch (err) {
4522
+ logger.runner.error("WORKFLOW_SHELL", `Failed to send child card result to workflow execution ${execution.id}:`, err);
4456
4523
  }
4457
4524
  }
4458
4525
 
@@ -4622,8 +4689,12 @@ async function startRunner() {
4622
4689
  if (previousColumn === "revisao" && card.column_name !== "revisao") {
4623
4690
  releaseCard(card.id);
4624
4691
  }
4692
+ if (previousColumn !== "deploy" && card.column_name === "deploy") {
4693
+ void resumeWorkflowExecutionWaitingOnChildCard(supabase, config, card, { viaCancellation: false });
4694
+ }
4625
4695
  if (card.column_name === "fim" && previousColumn !== "fim") {
4626
4696
  void handleCardReachedFim(supabase, config, card);
4697
+ if (previousColumn !== "deploy") void resumeWorkflowExecutionWaitingOnChildCard(supabase, config, card, { viaCancellation: true });
4627
4698
  }
4628
4699
  const previousRequeueRequestedAt = lastKnownRequeueRequestedAt.get(card.id);
4629
4700
  lastKnownRequeueRequestedAt.set(card.id, card.requeue_requested_at);
package/dist/index.js CHANGED
@@ -40,7 +40,7 @@ var BOX_DRAWING_PATTERN = /[─-╿]/g;
40
40
  var WORKING_PATTERN = /esc to interrupt/i;
41
41
  var BACKGROUND_SHELL_PATTERN = /\b[1-9]\d*\s+shells?\b/i;
42
42
  function isAgentHookMoment(value) {
43
- return value === "implementing" || value === "tool_active" || value === "waiting_plan_approval" || value === "waiting_prototype_review" || value === "waiting_workflow_answer" || value === "commit_detected" || value === "turn_stopped" || value === "notification";
43
+ return value === "implementing" || value === "tool_active" || value === "waiting_plan_approval" || value === "waiting_prototype_review" || value === "waiting_workflow_answer" || value === "waiting_child_card" || value === "commit_detected" || value === "turn_stopped" || value === "notification";
44
44
  }
45
45
  var USAGE_LIMIT_PATTERN = /(usage limit reached|limit reached[^\n]{0,60}resets|reached your (?:usage|session) limit|out of (?:weekly|session) (?:usage|limit))/i;
46
46
  var PLAN_APPROVAL_PATTERN = /would you like to proceed\??/i;
@@ -94,6 +94,8 @@ var AgentActivityWatcher = class {
94
94
  prototypeReviewPending = false;
95
95
  /** Same shape as prototypeReviewPending, for ask_workflow_question (see docs/ai-knowledges/workflows-visuais-mecanismo.md). */
96
96
  workflowAnswerPending = false;
97
+ /** Same shape as workflowAnswerPending, for wait_for_card_completion (see docs/ai-knowledges/workflows-visuais-aja-orquestra-card.md) — only ever latched on a card-less workflow shell pty. */
98
+ childCardPending = false;
97
99
  toolCallPending = false;
98
100
  commitDetected = false;
99
101
  timer;
@@ -137,6 +139,7 @@ var AgentActivityWatcher = class {
137
139
  this.planApprovalPending = false;
138
140
  this.prototypeReviewPending = false;
139
141
  this.workflowAnswerPending = false;
142
+ this.childCardPending = false;
140
143
  this.commitDetected = false;
141
144
  this.setState("implementing");
142
145
  break;
@@ -156,6 +159,10 @@ var AgentActivityWatcher = class {
156
159
  this.workflowAnswerPending = true;
157
160
  this.setState("waiting_workflow_answer");
158
161
  break;
162
+ case "waiting_child_card":
163
+ this.childCardPending = true;
164
+ this.setState("waiting_child_card");
165
+ break;
159
166
  case "commit_detected":
160
167
  this.commitDetected = true;
161
168
  break;
@@ -163,6 +170,7 @@ var AgentActivityWatcher = class {
163
170
  if (this.usageLimited) break;
164
171
  if (this.prototypeReviewPending) break;
165
172
  if (this.workflowAnswerPending) break;
173
+ if (this.childCardPending) break;
166
174
  if (BACKGROUND_SHELL_PATTERN.test(this.recentOutput())) {
167
175
  this.setState("waiting_background");
168
176
  break;
@@ -1000,6 +1008,12 @@ function detectMoment(input) {
1000
1008
  // \u2014 see the workflowAnswerPending latch in agentActivity.ts's reportHookMoment.
1001
1009
  if (eventName === "PostToolUse" && input.tool_name === "mcp__agent-board__ask_workflow_question") return "waiting_workflow_answer";
1002
1010
 
1011
+ // Same shape as ask_workflow_question above, but for a workflow graph's "act" node (see
1012
+ // wait_for_card_completion in boardTools.ts and
1013
+ // docs/ai-knowledges/workflows-visuais-aja-orquestra-card.md) \u2014 see the childCardPending latch
1014
+ // in agentActivity.ts's reportHookMoment.
1015
+ if (eventName === "PostToolUse" && input.tool_name === "mcp__agent-board__wait_for_card_completion") return "waiting_child_card";
1016
+
1003
1017
  // A commit that actually succeeded, read off the Bash tool's own structured result instead
1004
1018
  // of guessing from scrollback text (exact, and immune to locale/color/format changes to
1005
1019
  // git's own banner). Any other Bash completion still reports "tool_active" below, same as
@@ -4301,14 +4315,22 @@ async function completeWorkflowExecution(supabase, executionId, status) {
4301
4315
  logger.runner.error("WORKFLOW_SHELL", `Failed to record ${status} status for workflow execution ${executionId}:`, error);
4302
4316
  }
4303
4317
  }
4318
+ var intentionallyStoppedExecutionIds = /* @__PURE__ */ new Set();
4319
+ async function finalizeWorkflowExecution(supabase, executionId, status) {
4320
+ intentionallyStoppedExecutionIds.add(executionId);
4321
+ await completeWorkflowExecution(supabase, executionId, status);
4322
+ stopPtySession(executionId);
4323
+ }
4304
4324
  async function handleWorkflowShellExit(supabase, executionId, exitCode) {
4305
4325
  unregisterAgentActivityWatcher(executionId);
4326
+ if (intentionallyStoppedExecutionIds.delete(executionId)) return;
4306
4327
  await completeWorkflowExecution(supabase, executionId, exitCode === 0 ? "done" : "failed");
4328
+ stopPtySession(executionId);
4307
4329
  }
4308
4330
  function watchWorkflowShellActivity(supabase, executionId, pty) {
4309
4331
  const watcher = new AgentActivityWatcher((state) => {
4310
4332
  if (state === "done" || state === "waiting_user") {
4311
- void completeWorkflowExecution(supabase, executionId, "done");
4333
+ void finalizeWorkflowExecution(supabase, executionId, "done");
4312
4334
  }
4313
4335
  });
4314
4336
  pty.onData((chunk) => watcher.push(chunk));
@@ -4316,7 +4338,7 @@ function watchWorkflowShellActivity(supabase, executionId, pty) {
4316
4338
  return watcher;
4317
4339
  }
4318
4340
  async function reconcileOrphanedWorkflowExecutions(supabase, config) {
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"]);
4341
+ 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", "waiting_child_card"]);
4320
4342
  if (error) {
4321
4343
  logger.runner.error("WORKFLOW_SHELL", "Failed to scan orphaned workflow executions:", error.message);
4322
4344
  return;
@@ -4324,23 +4346,68 @@ async function reconcileOrphanedWorkflowExecutions(supabase, config) {
4324
4346
  for (const execution of data ?? []) {
4325
4347
  const { id: executionId, project_id: projectId, claude_session_id: claudeSessionId, shell_ticket: shellTicket } = execution;
4326
4348
  if (getPtySession(executionId)) continue;
4327
- if (!claudeSessionId || !shellTicket) {
4328
- logger.runner.warn("WORKFLOW_SHELL", `Workflow execution ${executionId} is missing claude_session_id/shell_ticket \u2014 cannot resume it.`);
4329
- continue;
4330
- }
4331
- const registered = config.registeredProjects.find((project) => project.id === projectId);
4332
- if (!registered) {
4333
- logger.runner.warn("WORKFLOW_SHELL", `Workflow execution ${executionId}: project ${projectId} is not registered on this runner \u2014 skipping.`);
4334
- continue;
4349
+ const pty = reattachWorkflowShellPty(supabase, config, executionId, projectId, claudeSessionId, shellTicket);
4350
+ if (pty) logger.runner.info("WORKFLOW_SHELL", `Re-spawned orphaned workflow execution ${executionId} (runner restarted while it was still active).`);
4351
+ }
4352
+ }
4353
+ function reattachWorkflowShellPty(supabase, config, executionId, projectId, claudeSessionId, shellTicket) {
4354
+ if (!claudeSessionId || !shellTicket) {
4355
+ logger.runner.warn("WORKFLOW_SHELL", `Workflow execution ${executionId} is missing claude_session_id/shell_ticket \u2014 cannot resume it.`);
4356
+ return null;
4357
+ }
4358
+ const registered = config.registeredProjects.find((project) => project.id === projectId);
4359
+ if (!registered) {
4360
+ logger.runner.warn("WORKFLOW_SHELL", `Workflow execution ${executionId}: project ${projectId} is not registered on this runner \u2014 skipping.`);
4361
+ return null;
4362
+ }
4363
+ const pty = resumeAgentPty(registered.repoPath, claudeSessionId, executionId, shellTicket);
4364
+ registerPtySession(executionId, pty, shellTicket);
4365
+ const activityWatcher = watchWorkflowShellActivity(supabase, executionId, pty);
4366
+ pty.onExit(({ exitCode }) => {
4367
+ activityWatcher.stop();
4368
+ void handleWorkflowShellExit(supabase, executionId, exitCode);
4369
+ });
4370
+ return pty;
4371
+ }
4372
+ async function resumeWorkflowExecutionWaitingOnChildCard(supabase, config, card, { viaCancellation }) {
4373
+ const { data, error } = await supabase.from("workflow_executions").select("id, project_id, claude_session_id, shell_ticket").eq("waiting_card_id", card.id).eq("status", "waiting_child_card").eq("runner_id", config.runnerId).maybeSingle();
4374
+ if (error) {
4375
+ logger.runner.error("WORKFLOW_SHELL", `Failed to look up a workflow execution waiting on card ${card.id}:`, error.message);
4376
+ return;
4377
+ }
4378
+ const execution = data;
4379
+ if (!execution) return;
4380
+ const { data: lastComment } = await supabase.from("card_comments").select("body").eq("card_id", card.id).order("created_at", { ascending: false }).limit(1).maybeSingle();
4381
+ const lastCommentBody = lastComment?.body ?? null;
4382
+ const resumeText = viaCancellation ? `Card "${card.title}" (id: ${card.id}) foi CANCELADO/ARQUIVADO, n\xE3o conclu\xEDdo normalmente.
4383
+
4384
+ Prossiga com o workflow a partir do passo seguinte, levando isso em conta.` : `Card "${card.title}" (id: ${card.id}) chegou em "deploy" \u2014 implementado, revisado e empacotado (o push final para "fim" \xE9 manual, fora do controle de qualquer agente).
4385
+
4386
+ Resumo: "${lastCommentBody ?? "(nenhum coment\xE1rio encontrado no card)"}"
4387
+
4388
+ Prossiga com o workflow a partir do passo seguinte.`;
4389
+ let handle = getPtySession(execution.id);
4390
+ if (!handle || handle.exited) {
4391
+ const pty = reattachWorkflowShellPty(supabase, config, execution.id, execution.project_id, execution.claude_session_id, execution.shell_ticket);
4392
+ if (!pty) {
4393
+ logger.runner.error("WORKFLOW_SHELL", `Workflow execution ${execution.id} has no live pty and could not be reattached \u2014 cannot resume it with child card ${card.id}'s result.`);
4394
+ return;
4335
4395
  }
4336
- const pty = resumeAgentPty(registered.repoPath, claudeSessionId, executionId, shellTicket);
4337
- registerPtySession(executionId, pty, shellTicket);
4338
- const activityWatcher = watchWorkflowShellActivity(supabase, executionId, pty);
4339
- pty.onExit(({ exitCode }) => {
4340
- activityWatcher.stop();
4341
- void handleWorkflowShellExit(supabase, executionId, exitCode);
4342
- });
4343
- logger.runner.info("WORKFLOW_SHELL", `Re-spawned orphaned workflow execution ${executionId} (runner restarted while it was still active).`);
4396
+ handle = getPtySession(execution.id);
4397
+ }
4398
+ if (!handle) return;
4399
+ const { error: resumeError } = await supabase.rpc("resume_workflow_execution_after_child_card", {
4400
+ p_execution_id: execution.id,
4401
+ p_child_card_id: card.id
4402
+ });
4403
+ if (resumeError) {
4404
+ logger.runner.error("WORKFLOW_SHELL", `Failed to resume workflow execution ${execution.id} after child card ${card.id}:`, resumeError.message);
4405
+ return;
4406
+ }
4407
+ try {
4408
+ writePasteAndSubmit(handle.pty, execution.id, resumeText, "child card result", logger.runner);
4409
+ } catch (err) {
4410
+ logger.runner.error("WORKFLOW_SHELL", `Failed to send child card result to workflow execution ${execution.id}:`, err);
4344
4411
  }
4345
4412
  }
4346
4413
 
@@ -4510,8 +4577,12 @@ async function startRunner() {
4510
4577
  if (previousColumn === "revisao" && card.column_name !== "revisao") {
4511
4578
  releaseCard(card.id);
4512
4579
  }
4580
+ if (previousColumn !== "deploy" && card.column_name === "deploy") {
4581
+ void resumeWorkflowExecutionWaitingOnChildCard(supabase, config, card, { viaCancellation: false });
4582
+ }
4513
4583
  if (card.column_name === "fim" && previousColumn !== "fim") {
4514
4584
  void handleCardReachedFim(supabase, config, card);
4585
+ if (previousColumn !== "deploy") void resumeWorkflowExecutionWaitingOnChildCard(supabase, config, card, { viaCancellation: true });
4515
4586
  }
4516
4587
  const previousRequeueRequestedAt = lastKnownRequeueRequestedAt.get(card.id);
4517
4588
  lastKnownRequeueRequestedAt.set(card.id, card.requeue_requested_at);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-board-runner",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
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": {