agent-board-runner 0.1.15 → 0.1.17
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 +80 -11
- package/dist/index.js +80 -11
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -897,12 +897,37 @@ async function getRunningSessionForProject(supabase, projectId, runnerId) {
|
|
|
897
897
|
const row = data;
|
|
898
898
|
return { id: row.id, cardId: row.card_id, agentState: row.agent_state };
|
|
899
899
|
}
|
|
900
|
+
function compareQueueOrder(a, b) {
|
|
901
|
+
const orderA = a.cards.dev_queue_order;
|
|
902
|
+
const orderB = b.cards.dev_queue_order;
|
|
903
|
+
if (orderA !== null && orderB !== null) return orderA - orderB;
|
|
904
|
+
if (orderA !== null) return -1;
|
|
905
|
+
if (orderB !== null) return 1;
|
|
906
|
+
return new Date(a.created_at).getTime() - new Date(b.created_at).getTime();
|
|
907
|
+
}
|
|
908
|
+
function applyDevQueueOrder(queued) {
|
|
909
|
+
const byProject = /* @__PURE__ */ new Map();
|
|
910
|
+
for (const entry of queued) {
|
|
911
|
+
const list = byProject.get(entry.cards.project_id) ?? [];
|
|
912
|
+
list.push(entry);
|
|
913
|
+
byProject.set(entry.cards.project_id, list);
|
|
914
|
+
}
|
|
915
|
+
for (const entries of byProject.values()) entries.sort(compareQueueOrder);
|
|
916
|
+
const seenProjects = /* @__PURE__ */ new Set();
|
|
917
|
+
const result = [];
|
|
918
|
+
for (const entry of queued) {
|
|
919
|
+
if (seenProjects.has(entry.cards.project_id)) continue;
|
|
920
|
+
seenProjects.add(entry.cards.project_id);
|
|
921
|
+
result.push(...byProject.get(entry.cards.project_id));
|
|
922
|
+
}
|
|
923
|
+
return result;
|
|
924
|
+
}
|
|
900
925
|
async function listQueuedSessionsForRunner(supabase, runnerId) {
|
|
901
926
|
const { data, error } = await supabase.from("sessions").select(
|
|
902
|
-
"id, card_id, claude_session_id, agent_state, created_at, cards!card_id!inner(project_id, title, instructions, file_references, plan_first, prerequisite_card_id, prototype_first, prototype_url, prototype_approved)"
|
|
927
|
+
"id, card_id, claude_session_id, agent_state, created_at, cards!card_id!inner(project_id, title, instructions, file_references, plan_first, prerequisite_card_id, prototype_first, prototype_url, prototype_approved, dev_queue_order)"
|
|
903
928
|
).eq("runner_id", runnerId).eq("status", "queued").order("created_at", { ascending: true });
|
|
904
929
|
if (error) throw error;
|
|
905
|
-
return data;
|
|
930
|
+
return applyDevQueueOrder(data);
|
|
906
931
|
}
|
|
907
932
|
async function listPausedSessionsForRunner(supabase, runnerId) {
|
|
908
933
|
const { data, error } = await supabase.from("sessions").select("card_id, cards!card_id!inner(project_id)").eq("runner_id", runnerId).eq("status", "paused");
|
|
@@ -1010,7 +1035,8 @@ async function updateCard(supabase, cardId, changes) {
|
|
|
1010
1035
|
...changes.customActionStatus !== void 0 ? { custom_action_status: changes.customActionStatus } : {},
|
|
1011
1036
|
...changes.customActionOutput !== void 0 ? { custom_action_output: changes.customActionOutput } : {},
|
|
1012
1037
|
...changes.requeueRequestedAt !== void 0 ? { requeue_requested_at: changes.requeueRequestedAt } : {},
|
|
1013
|
-
...changes.restartRequestedAt !== void 0 ? { restart_requested_at: changes.restartRequestedAt } : {}
|
|
1038
|
+
...changes.restartRequestedAt !== void 0 ? { restart_requested_at: changes.restartRequestedAt } : {},
|
|
1039
|
+
...changes.returnToBacklogRequestedAt !== void 0 ? { return_to_backlog_requested_at: changes.returnToBacklogRequestedAt } : {}
|
|
1014
1040
|
}).eq("id", cardId);
|
|
1015
1041
|
if (error) throw error;
|
|
1016
1042
|
}
|
|
@@ -2840,6 +2866,7 @@ var OUTPUT_TAIL_CHARS = 2e4;
|
|
|
2840
2866
|
var COMMAND_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
2841
2867
|
var MAX_BUFFER = 10 * 1024 * 1024;
|
|
2842
2868
|
var SECRET_PLACEHOLDER_RE = /\{\{secret:([A-Z][A-Z0-9_]*)\}\}/g;
|
|
2869
|
+
var SECRET_ENV_PLACEHOLDER_RE = /^\{\{secret_env:([A-Z][A-Z0-9_]*)\}\}$/;
|
|
2843
2870
|
function isRunProjectCommandPayload(value) {
|
|
2844
2871
|
if (!value || typeof value !== "object") return false;
|
|
2845
2872
|
const candidate = value;
|
|
@@ -2855,14 +2882,26 @@ async function runProjectCommand(supabase, config, payload) {
|
|
|
2855
2882
|
if (!registered) throw new Error("This runner does not have that project registered.");
|
|
2856
2883
|
const secretsEnv = await resolveProjectSecretsEnv(supabase, projectId, null);
|
|
2857
2884
|
const referencedSecretNames = /* @__PURE__ */ new Set();
|
|
2858
|
-
const resolvedArgv =
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2885
|
+
const resolvedArgv = [];
|
|
2886
|
+
for (const part of argv) {
|
|
2887
|
+
const envOnlyMatch = part.match(SECRET_ENV_PLACEHOLDER_RE);
|
|
2888
|
+
if (envOnlyMatch) {
|
|
2889
|
+
const name = envOnlyMatch[1];
|
|
2890
|
+
if (secretsEnv[name] === void 0) throw new Error(`Secret "${name}" referenced by this command is no longer configured for this project.`);
|
|
2862
2891
|
referencedSecretNames.add(name);
|
|
2863
|
-
|
|
2864
|
-
}
|
|
2865
|
-
|
|
2892
|
+
continue;
|
|
2893
|
+
}
|
|
2894
|
+
resolvedArgv.push(
|
|
2895
|
+
part.replace(SECRET_PLACEHOLDER_RE, (_match, name) => {
|
|
2896
|
+
const value = secretsEnv[name];
|
|
2897
|
+
if (value === void 0) throw new Error(`Secret "${name}" referenced by this command is no longer configured for this project.`);
|
|
2898
|
+
referencedSecretNames.add(name);
|
|
2899
|
+
return value;
|
|
2900
|
+
})
|
|
2901
|
+
);
|
|
2902
|
+
}
|
|
2903
|
+
const secretEnv = { ...process.env };
|
|
2904
|
+
for (const name of referencedSecretNames) secretEnv[name] = secretsEnv[name];
|
|
2866
2905
|
function redact(text) {
|
|
2867
2906
|
let redacted = text;
|
|
2868
2907
|
for (const name of referencedSecretNames) {
|
|
@@ -2874,7 +2913,7 @@ async function runProjectCommand(supabase, config, payload) {
|
|
|
2874
2913
|
try {
|
|
2875
2914
|
const { stdout, stderr } = await execFileAsync4(resolvedArgv[0], resolvedArgv.slice(1), {
|
|
2876
2915
|
cwd: registered.repoPath,
|
|
2877
|
-
env:
|
|
2916
|
+
env: secretEnv,
|
|
2878
2917
|
timeout: COMMAND_TIMEOUT_MS,
|
|
2879
2918
|
maxBuffer: MAX_BUFFER
|
|
2880
2919
|
});
|
|
@@ -3400,6 +3439,24 @@ async function restartCardSession(supabase, config, card) {
|
|
|
3400
3439
|
await updateCard(supabase, card.id, { restartRequestedAt: null });
|
|
3401
3440
|
await enqueueCard(supabase, config, card);
|
|
3402
3441
|
}
|
|
3442
|
+
async function returnCardToBacklog(supabase, config, card) {
|
|
3443
|
+
const sessionId = activeSessionByCard.get(card.id) ?? (await getActiveSessionForCard(supabase, card.id))?.id;
|
|
3444
|
+
if (sessionId) {
|
|
3445
|
+
intentionallyStoppedSessionIds.add(sessionId);
|
|
3446
|
+
stopPtySession(sessionId);
|
|
3447
|
+
cancelUsageResume(sessionId);
|
|
3448
|
+
activeSessionByCard.delete(card.id);
|
|
3449
|
+
await updateSession(
|
|
3450
|
+
supabase,
|
|
3451
|
+
sessionId,
|
|
3452
|
+
{ status: "done", endedAt: (/* @__PURE__ */ new Date()).toISOString() },
|
|
3453
|
+
{ projectId: card.project_id, cardId: card.id, userId: config.ownerUserId }
|
|
3454
|
+
);
|
|
3455
|
+
logger.card.info(card.id, `Session ${sessionId} stopped by "voltar ao backlog".`);
|
|
3456
|
+
}
|
|
3457
|
+
await updateCard(supabase, card.id, { column: "backlog", returnToBacklogRequestedAt: null });
|
|
3458
|
+
await dispatchRunnerQueue(supabase, config);
|
|
3459
|
+
}
|
|
3403
3460
|
|
|
3404
3461
|
// src/columnActions.ts
|
|
3405
3462
|
var execAsync = promisify5(exec);
|
|
@@ -4193,6 +4250,8 @@ async function startRunner() {
|
|
|
4193
4250
|
const lastKnownColumn = /* @__PURE__ */ new Map();
|
|
4194
4251
|
const lastKnownRequeueRequestedAt = /* @__PURE__ */ new Map();
|
|
4195
4252
|
const lastKnownRestartRequestedAt = /* @__PURE__ */ new Map();
|
|
4253
|
+
const lastKnownDevQueueOrder = /* @__PURE__ */ new Map();
|
|
4254
|
+
const lastKnownReturnToBacklogRequestedAt = /* @__PURE__ */ new Map();
|
|
4196
4255
|
await syncRegisteredProjects(supabase, config, registeredPaths).catch((err) => logger.runner.error("PROJECT_SYNC", "syncRegisteredProjects failed:", err));
|
|
4197
4256
|
logger.runner.info("BOOT", `Agent Board runner "${config.runnerName}" starting (${registeredPaths.size} project(s) registered).`);
|
|
4198
4257
|
for (const project of config.registeredProjects) {
|
|
@@ -4298,6 +4357,16 @@ async function startRunner() {
|
|
|
4298
4357
|
if (card.restart_requested_at && card.restart_requested_at !== previousRestartRequestedAt) {
|
|
4299
4358
|
void restartCardSession(supabase, config, card);
|
|
4300
4359
|
}
|
|
4360
|
+
const previousReturnToBacklogRequestedAt = lastKnownReturnToBacklogRequestedAt.get(card.id);
|
|
4361
|
+
lastKnownReturnToBacklogRequestedAt.set(card.id, card.return_to_backlog_requested_at);
|
|
4362
|
+
if (card.return_to_backlog_requested_at && card.return_to_backlog_requested_at !== previousReturnToBacklogRequestedAt) {
|
|
4363
|
+
void returnCardToBacklog(supabase, config, card);
|
|
4364
|
+
}
|
|
4365
|
+
const previousDevQueueOrder = lastKnownDevQueueOrder.get(card.id);
|
|
4366
|
+
lastKnownDevQueueOrder.set(card.id, card.dev_queue_order);
|
|
4367
|
+
if (card.column_name === "desenvolvimento" && card.dev_queue_order !== previousDevQueueOrder) {
|
|
4368
|
+
void dispatchRunnerQueue(supabase, config);
|
|
4369
|
+
}
|
|
4301
4370
|
if (previousColumn !== void 0 && previousColumn !== card.column_name) {
|
|
4302
4371
|
void clearColumnActionStatus(supabase, card);
|
|
4303
4372
|
void handleColumnEntered(supabase, config, card);
|
package/dist/index.js
CHANGED
|
@@ -617,12 +617,37 @@ async function getRunningSessionForProject(supabase, projectId, runnerId) {
|
|
|
617
617
|
const row = data;
|
|
618
618
|
return { id: row.id, cardId: row.card_id, agentState: row.agent_state };
|
|
619
619
|
}
|
|
620
|
+
function compareQueueOrder(a, b) {
|
|
621
|
+
const orderA = a.cards.dev_queue_order;
|
|
622
|
+
const orderB = b.cards.dev_queue_order;
|
|
623
|
+
if (orderA !== null && orderB !== null) return orderA - orderB;
|
|
624
|
+
if (orderA !== null) return -1;
|
|
625
|
+
if (orderB !== null) return 1;
|
|
626
|
+
return new Date(a.created_at).getTime() - new Date(b.created_at).getTime();
|
|
627
|
+
}
|
|
628
|
+
function applyDevQueueOrder(queued) {
|
|
629
|
+
const byProject = /* @__PURE__ */ new Map();
|
|
630
|
+
for (const entry of queued) {
|
|
631
|
+
const list = byProject.get(entry.cards.project_id) ?? [];
|
|
632
|
+
list.push(entry);
|
|
633
|
+
byProject.set(entry.cards.project_id, list);
|
|
634
|
+
}
|
|
635
|
+
for (const entries of byProject.values()) entries.sort(compareQueueOrder);
|
|
636
|
+
const seenProjects = /* @__PURE__ */ new Set();
|
|
637
|
+
const result = [];
|
|
638
|
+
for (const entry of queued) {
|
|
639
|
+
if (seenProjects.has(entry.cards.project_id)) continue;
|
|
640
|
+
seenProjects.add(entry.cards.project_id);
|
|
641
|
+
result.push(...byProject.get(entry.cards.project_id));
|
|
642
|
+
}
|
|
643
|
+
return result;
|
|
644
|
+
}
|
|
620
645
|
async function listQueuedSessionsForRunner(supabase, runnerId) {
|
|
621
646
|
const { data, error } = await supabase.from("sessions").select(
|
|
622
|
-
"id, card_id, claude_session_id, agent_state, created_at, cards!card_id!inner(project_id, title, instructions, file_references, plan_first, prerequisite_card_id, prototype_first, prototype_url, prototype_approved)"
|
|
647
|
+
"id, card_id, claude_session_id, agent_state, created_at, cards!card_id!inner(project_id, title, instructions, file_references, plan_first, prerequisite_card_id, prototype_first, prototype_url, prototype_approved, dev_queue_order)"
|
|
623
648
|
).eq("runner_id", runnerId).eq("status", "queued").order("created_at", { ascending: true });
|
|
624
649
|
if (error) throw error;
|
|
625
|
-
return data;
|
|
650
|
+
return applyDevQueueOrder(data);
|
|
626
651
|
}
|
|
627
652
|
async function listPausedSessionsForRunner(supabase, runnerId) {
|
|
628
653
|
const { data, error } = await supabase.from("sessions").select("card_id, cards!card_id!inner(project_id)").eq("runner_id", runnerId).eq("status", "paused");
|
|
@@ -730,7 +755,8 @@ async function updateCard(supabase, cardId, changes) {
|
|
|
730
755
|
...changes.customActionStatus !== void 0 ? { custom_action_status: changes.customActionStatus } : {},
|
|
731
756
|
...changes.customActionOutput !== void 0 ? { custom_action_output: changes.customActionOutput } : {},
|
|
732
757
|
...changes.requeueRequestedAt !== void 0 ? { requeue_requested_at: changes.requeueRequestedAt } : {},
|
|
733
|
-
...changes.restartRequestedAt !== void 0 ? { restart_requested_at: changes.restartRequestedAt } : {}
|
|
758
|
+
...changes.restartRequestedAt !== void 0 ? { restart_requested_at: changes.restartRequestedAt } : {},
|
|
759
|
+
...changes.returnToBacklogRequestedAt !== void 0 ? { return_to_backlog_requested_at: changes.returnToBacklogRequestedAt } : {}
|
|
734
760
|
}).eq("id", cardId);
|
|
735
761
|
if (error) throw error;
|
|
736
762
|
}
|
|
@@ -2728,6 +2754,7 @@ var OUTPUT_TAIL_CHARS = 2e4;
|
|
|
2728
2754
|
var COMMAND_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
2729
2755
|
var MAX_BUFFER = 10 * 1024 * 1024;
|
|
2730
2756
|
var SECRET_PLACEHOLDER_RE = /\{\{secret:([A-Z][A-Z0-9_]*)\}\}/g;
|
|
2757
|
+
var SECRET_ENV_PLACEHOLDER_RE = /^\{\{secret_env:([A-Z][A-Z0-9_]*)\}\}$/;
|
|
2731
2758
|
function isRunProjectCommandPayload(value) {
|
|
2732
2759
|
if (!value || typeof value !== "object") return false;
|
|
2733
2760
|
const candidate = value;
|
|
@@ -2743,14 +2770,26 @@ async function runProjectCommand(supabase, config, payload) {
|
|
|
2743
2770
|
if (!registered) throw new Error("This runner does not have that project registered.");
|
|
2744
2771
|
const secretsEnv = await resolveProjectSecretsEnv(supabase, projectId, null);
|
|
2745
2772
|
const referencedSecretNames = /* @__PURE__ */ new Set();
|
|
2746
|
-
const resolvedArgv =
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2773
|
+
const resolvedArgv = [];
|
|
2774
|
+
for (const part of argv) {
|
|
2775
|
+
const envOnlyMatch = part.match(SECRET_ENV_PLACEHOLDER_RE);
|
|
2776
|
+
if (envOnlyMatch) {
|
|
2777
|
+
const name = envOnlyMatch[1];
|
|
2778
|
+
if (secretsEnv[name] === void 0) throw new Error(`Secret "${name}" referenced by this command is no longer configured for this project.`);
|
|
2750
2779
|
referencedSecretNames.add(name);
|
|
2751
|
-
|
|
2752
|
-
}
|
|
2753
|
-
|
|
2780
|
+
continue;
|
|
2781
|
+
}
|
|
2782
|
+
resolvedArgv.push(
|
|
2783
|
+
part.replace(SECRET_PLACEHOLDER_RE, (_match, name) => {
|
|
2784
|
+
const value = secretsEnv[name];
|
|
2785
|
+
if (value === void 0) throw new Error(`Secret "${name}" referenced by this command is no longer configured for this project.`);
|
|
2786
|
+
referencedSecretNames.add(name);
|
|
2787
|
+
return value;
|
|
2788
|
+
})
|
|
2789
|
+
);
|
|
2790
|
+
}
|
|
2791
|
+
const secretEnv = { ...process.env };
|
|
2792
|
+
for (const name of referencedSecretNames) secretEnv[name] = secretsEnv[name];
|
|
2754
2793
|
function redact(text) {
|
|
2755
2794
|
let redacted = text;
|
|
2756
2795
|
for (const name of referencedSecretNames) {
|
|
@@ -2762,7 +2801,7 @@ async function runProjectCommand(supabase, config, payload) {
|
|
|
2762
2801
|
try {
|
|
2763
2802
|
const { stdout, stderr } = await execFileAsync4(resolvedArgv[0], resolvedArgv.slice(1), {
|
|
2764
2803
|
cwd: registered.repoPath,
|
|
2765
|
-
env:
|
|
2804
|
+
env: secretEnv,
|
|
2766
2805
|
timeout: COMMAND_TIMEOUT_MS,
|
|
2767
2806
|
maxBuffer: MAX_BUFFER
|
|
2768
2807
|
});
|
|
@@ -3288,6 +3327,24 @@ async function restartCardSession(supabase, config, card) {
|
|
|
3288
3327
|
await updateCard(supabase, card.id, { restartRequestedAt: null });
|
|
3289
3328
|
await enqueueCard(supabase, config, card);
|
|
3290
3329
|
}
|
|
3330
|
+
async function returnCardToBacklog(supabase, config, card) {
|
|
3331
|
+
const sessionId = activeSessionByCard.get(card.id) ?? (await getActiveSessionForCard(supabase, card.id))?.id;
|
|
3332
|
+
if (sessionId) {
|
|
3333
|
+
intentionallyStoppedSessionIds.add(sessionId);
|
|
3334
|
+
stopPtySession(sessionId);
|
|
3335
|
+
cancelUsageResume(sessionId);
|
|
3336
|
+
activeSessionByCard.delete(card.id);
|
|
3337
|
+
await updateSession(
|
|
3338
|
+
supabase,
|
|
3339
|
+
sessionId,
|
|
3340
|
+
{ status: "done", endedAt: (/* @__PURE__ */ new Date()).toISOString() },
|
|
3341
|
+
{ projectId: card.project_id, cardId: card.id, userId: config.ownerUserId }
|
|
3342
|
+
);
|
|
3343
|
+
logger.card.info(card.id, `Session ${sessionId} stopped by "voltar ao backlog".`);
|
|
3344
|
+
}
|
|
3345
|
+
await updateCard(supabase, card.id, { column: "backlog", returnToBacklogRequestedAt: null });
|
|
3346
|
+
await dispatchRunnerQueue(supabase, config);
|
|
3347
|
+
}
|
|
3291
3348
|
|
|
3292
3349
|
// src/columnActions.ts
|
|
3293
3350
|
var execAsync = promisify5(exec);
|
|
@@ -4081,6 +4138,8 @@ async function startRunner() {
|
|
|
4081
4138
|
const lastKnownColumn = /* @__PURE__ */ new Map();
|
|
4082
4139
|
const lastKnownRequeueRequestedAt = /* @__PURE__ */ new Map();
|
|
4083
4140
|
const lastKnownRestartRequestedAt = /* @__PURE__ */ new Map();
|
|
4141
|
+
const lastKnownDevQueueOrder = /* @__PURE__ */ new Map();
|
|
4142
|
+
const lastKnownReturnToBacklogRequestedAt = /* @__PURE__ */ new Map();
|
|
4084
4143
|
await syncRegisteredProjects(supabase, config, registeredPaths).catch((err) => logger.runner.error("PROJECT_SYNC", "syncRegisteredProjects failed:", err));
|
|
4085
4144
|
logger.runner.info("BOOT", `Agent Board runner "${config.runnerName}" starting (${registeredPaths.size} project(s) registered).`);
|
|
4086
4145
|
for (const project of config.registeredProjects) {
|
|
@@ -4186,6 +4245,16 @@ async function startRunner() {
|
|
|
4186
4245
|
if (card.restart_requested_at && card.restart_requested_at !== previousRestartRequestedAt) {
|
|
4187
4246
|
void restartCardSession(supabase, config, card);
|
|
4188
4247
|
}
|
|
4248
|
+
const previousReturnToBacklogRequestedAt = lastKnownReturnToBacklogRequestedAt.get(card.id);
|
|
4249
|
+
lastKnownReturnToBacklogRequestedAt.set(card.id, card.return_to_backlog_requested_at);
|
|
4250
|
+
if (card.return_to_backlog_requested_at && card.return_to_backlog_requested_at !== previousReturnToBacklogRequestedAt) {
|
|
4251
|
+
void returnCardToBacklog(supabase, config, card);
|
|
4252
|
+
}
|
|
4253
|
+
const previousDevQueueOrder = lastKnownDevQueueOrder.get(card.id);
|
|
4254
|
+
lastKnownDevQueueOrder.set(card.id, card.dev_queue_order);
|
|
4255
|
+
if (card.column_name === "desenvolvimento" && card.dev_queue_order !== previousDevQueueOrder) {
|
|
4256
|
+
void dispatchRunnerQueue(supabase, config);
|
|
4257
|
+
}
|
|
4189
4258
|
if (previousColumn !== void 0 && previousColumn !== card.column_name) {
|
|
4190
4259
|
void clearColumnActionStatus(supabase, card);
|
|
4191
4260
|
void handleColumnEntered(supabase, config, card);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-board-runner",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
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": {
|