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.
- package/dist/cli.js +90 -19
- package/dist/index.js +90 -19
- 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
|
|
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
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
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
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
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
|
|
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
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
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
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
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.
|
|
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": {
|