@liriraid/agentflow-ai 1.0.12 → 1.0.14
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/bin/agentflow.mjs +1 -1
- package/orchestrator.js +140 -50
- package/package.json +1 -1
- package/src/ink/index.mjs +2 -0
- package/templates/en/ORCHESTRATOR.md +26 -18
- package/templates/en/orchestrator.config.json +6 -6
- package/templates/es/ORCHESTRATOR.md +37 -30
- package/templates/es/orchestrator.config.json +8 -8
package/bin/agentflow.mjs
CHANGED
package/orchestrator.js
CHANGED
|
@@ -170,8 +170,7 @@ const TEXT = {
|
|
|
170
170
|
empty: "(vacía)",
|
|
171
171
|
after: "después de",
|
|
172
172
|
quotaLimit: "LÍMITE DE CUOTA",
|
|
173
|
-
retryAt: (time, remaining) =>
|
|
174
|
-
`reintenta a las ${time} (${remaining} min)`,
|
|
173
|
+
retryAt: (time, remaining) => `reintenta a las ${time} (${remaining} min)`,
|
|
175
174
|
log: "REGISTRO",
|
|
176
175
|
controls: "Seguir Pausa Recargar Quitar",
|
|
177
176
|
},
|
|
@@ -221,6 +220,16 @@ const TEXT = {
|
|
|
221
220
|
};
|
|
222
221
|
const L = TEXT[WORKSPACE_LANGUAGE];
|
|
223
222
|
|
|
223
|
+
let lastRenderTime = 0;
|
|
224
|
+
const RENDER_DEBOUNCE_MS = 500;
|
|
225
|
+
|
|
226
|
+
function safeRenderDashboard() {
|
|
227
|
+
const now = Date.now();
|
|
228
|
+
if (now - lastRenderTime < RENDER_DEBOUNCE_MS) return;
|
|
229
|
+
lastRenderTime = now;
|
|
230
|
+
renderDashboard();
|
|
231
|
+
}
|
|
232
|
+
|
|
224
233
|
// CLI args
|
|
225
234
|
const argv = process.argv.slice(2);
|
|
226
235
|
const CLI = {
|
|
@@ -258,8 +267,7 @@ ${L.keyboard}:
|
|
|
258
267
|
const MAX_CONCURRENT = config.maxConcurrent || Object.keys(AGENTS).length;
|
|
259
268
|
const POLL_INTERVAL_MS = (config.pollIntervalSeconds || 30) * 1000;
|
|
260
269
|
const TASK_TIMEOUT_MS = (config.taskTimeoutMinutes || 30) * 60 * 1000;
|
|
261
|
-
const SKIP_PERMISSIONS =
|
|
262
|
-
process.env.SKIP_PERMISSIONS === "true" || CLI.yolo;
|
|
270
|
+
const SKIP_PERMISSIONS = process.env.SKIP_PERMISSIONS === "true" || CLI.yolo;
|
|
263
271
|
const PERMISSION_FLAGS = SKIP_PERMISSIONS
|
|
264
272
|
? ["--dangerously-skip-permissions"]
|
|
265
273
|
: ["--permission-mode", "default"];
|
|
@@ -401,7 +409,10 @@ const dashboard =
|
|
|
401
409
|
|
|
402
410
|
const agentNames = Object.keys(AGENTS);
|
|
403
411
|
const agentBoxes = {};
|
|
404
|
-
const panelWidth = Math.max(
|
|
412
|
+
const panelWidth = Math.max(
|
|
413
|
+
1,
|
|
414
|
+
Math.floor(100 / Math.max(1, agentNames.length)),
|
|
415
|
+
);
|
|
405
416
|
|
|
406
417
|
if (!CLI.headless && screen) {
|
|
407
418
|
agentNames.forEach((name, i) => {
|
|
@@ -480,7 +491,11 @@ function persistState() {
|
|
|
480
491
|
]),
|
|
481
492
|
),
|
|
482
493
|
};
|
|
483
|
-
fs.writeFileSync(
|
|
494
|
+
fs.writeFileSync(
|
|
495
|
+
STATE_FILE,
|
|
496
|
+
JSON.stringify(snapshot, null, 2) + "\n",
|
|
497
|
+
"utf-8",
|
|
498
|
+
);
|
|
484
499
|
}
|
|
485
500
|
|
|
486
501
|
function consumeControlCommand() {
|
|
@@ -590,7 +605,9 @@ function renderDashboard() {
|
|
|
590
605
|
? `{yellow-fg}${L.paused}{/yellow-fg}`
|
|
591
606
|
: `{green-fg}${L.running}{/green-fg}`;
|
|
592
607
|
|
|
593
|
-
lines.push(
|
|
608
|
+
lines.push(
|
|
609
|
+
` ${datestamp()} ${timestamp()} ${WORKSPACE_LANGUAGE === "es" ? "activo" : "active"} ${up} ${cost} ${mode}`,
|
|
610
|
+
);
|
|
594
611
|
lines.push("");
|
|
595
612
|
|
|
596
613
|
for (const [name, ag] of Object.entries(state.agents)) {
|
|
@@ -668,9 +685,7 @@ function renderDashboard() {
|
|
|
668
685
|
lines.push(` {gray-fg}${escBl(entry)}{/gray-fg}`);
|
|
669
686
|
}
|
|
670
687
|
lines.push("");
|
|
671
|
-
lines.push(
|
|
672
|
-
` {cyan-fg}S{/cyan-fg} ${L.controls}`,
|
|
673
|
-
);
|
|
688
|
+
lines.push(` {cyan-fg}S{/cyan-fg} ${L.controls}`);
|
|
674
689
|
|
|
675
690
|
dashboard.setContent(lines.join("\n"));
|
|
676
691
|
|
|
@@ -705,7 +720,10 @@ function parseQueue() {
|
|
|
705
720
|
section = "pending";
|
|
706
721
|
continue;
|
|
707
722
|
}
|
|
708
|
-
if (
|
|
723
|
+
if (
|
|
724
|
+
line.startsWith("## In Progress") ||
|
|
725
|
+
line.startsWith("## En progreso")
|
|
726
|
+
) {
|
|
709
727
|
section = "inprogress";
|
|
710
728
|
continue;
|
|
711
729
|
}
|
|
@@ -747,7 +765,10 @@ function parseCompletedFromFile() {
|
|
|
747
765
|
section = "pending";
|
|
748
766
|
continue;
|
|
749
767
|
}
|
|
750
|
-
if (
|
|
768
|
+
if (
|
|
769
|
+
line.startsWith("## In Progress") ||
|
|
770
|
+
line.startsWith("## En progreso")
|
|
771
|
+
) {
|
|
751
772
|
section = "inprogress";
|
|
752
773
|
continue;
|
|
753
774
|
}
|
|
@@ -798,6 +819,45 @@ function reloadQueue() {
|
|
|
798
819
|
});
|
|
799
820
|
}
|
|
800
821
|
|
|
822
|
+
// ============================================================================
|
|
823
|
+
// INBOX NOTIFICATIONS — written when a task completes so the Orchestrator
|
|
824
|
+
// session can detect it on next interaction without Modo Ausencia active.
|
|
825
|
+
// ============================================================================
|
|
826
|
+
function writeInboxNotification(task, agentName, elapsed) {
|
|
827
|
+
const inboxFile = path.join(WORKSPACE, "INBOX.md");
|
|
828
|
+
const progressFile = `progress/PROGRESS-${agentName}.md`;
|
|
829
|
+
const entry = [
|
|
830
|
+
``,
|
|
831
|
+
`## [${timestamp()}] ${task.id} completada — ${agentName}`,
|
|
832
|
+
``,
|
|
833
|
+
`- **Tarea:** ${task.title}`,
|
|
834
|
+
`- **Duración:** ${formatDuration(elapsed)}`,
|
|
835
|
+
`- **Reporte:** ${progressFile}`,
|
|
836
|
+
`- **Acción:** Lee \`${progressFile}\` y crea las siguientes TASKs si corresponde.`,
|
|
837
|
+
``,
|
|
838
|
+
].join("\n");
|
|
839
|
+
try {
|
|
840
|
+
fs.appendFileSync(inboxFile, entry, "utf-8");
|
|
841
|
+
} catch {}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function writeInboxFailureNotification(task, failedAgent, newAgent, reason) {
|
|
845
|
+
const inboxFile = path.join(WORKSPACE, "INBOX.md");
|
|
846
|
+
const entry = [
|
|
847
|
+
``,
|
|
848
|
+
`## [${timestamp()}] ${task.id} falló — ${failedAgent} → reasignada a ${newAgent}`,
|
|
849
|
+
``,
|
|
850
|
+
`- **Tarea:** ${task.title}`,
|
|
851
|
+
`- **Motivo:** ${reason}`,
|
|
852
|
+
`- **Nuevo agente:** ${newAgent}`,
|
|
853
|
+
`- **Acción:** La TUI reasignó automáticamente. Verifica en QUEUE.md o espera la siguiente notificación de completada.`,
|
|
854
|
+
``,
|
|
855
|
+
].join("\n");
|
|
856
|
+
try {
|
|
857
|
+
fs.appendFileSync(inboxFile, entry, "utf-8");
|
|
858
|
+
} catch {}
|
|
859
|
+
}
|
|
860
|
+
|
|
801
861
|
// ============================================================================
|
|
802
862
|
// BRIEF GENERATOR
|
|
803
863
|
// ============================================================================
|
|
@@ -853,7 +913,13 @@ function generateBrief(task) {
|
|
|
853
913
|
}
|
|
854
914
|
}
|
|
855
915
|
|
|
856
|
-
const
|
|
916
|
+
const hasBackend = REPOS.backend && fs.existsSync(REPOS.backend);
|
|
917
|
+
const hasFrontend = REPOS.frontend && fs.existsSync(REPOS.frontend);
|
|
918
|
+
const isSingleRepo = (hasBackend && hasFrontend &&
|
|
919
|
+
path.resolve(REPOS.backend) === path.resolve(REPOS.frontend)) ||
|
|
920
|
+
(!hasBackend && hasFrontend) || (hasBackend && !hasFrontend);
|
|
921
|
+
const effectiveRepo = isSingleRepo ? "frontend" : (task.repo || agentCfg.defaultRepo);
|
|
922
|
+
const repoDir = REPOS[effectiveRepo] || REPOS[task.repo] || REPOS[agentCfg.defaultRepo] || ".";
|
|
857
923
|
const progressFile = path.join(
|
|
858
924
|
WORKSPACE,
|
|
859
925
|
"progress",
|
|
@@ -863,7 +929,7 @@ function generateBrief(task) {
|
|
|
863
929
|
return `
|
|
864
930
|
# Agent: ${task.agent}
|
|
865
931
|
# Task: ${task.id} — ${task.title}
|
|
866
|
-
# Repository: ${
|
|
932
|
+
# Repository: ${effectiveRepo}
|
|
867
933
|
# CWD: ${repoDir}
|
|
868
934
|
# Priority: ${task.priority}
|
|
869
935
|
# Workspace: ${WORKSPACE}
|
|
@@ -932,25 +998,17 @@ function buildCliCommand(agentCfg, task, prompt) {
|
|
|
932
998
|
case "codex":
|
|
933
999
|
return {
|
|
934
1000
|
cmd: "codex",
|
|
935
|
-
args: [
|
|
936
|
-
"exec",
|
|
937
|
-
...(agentCfg.model ? ["--model", agentCfg.model] : []),
|
|
938
|
-
...(CLI.yolo ? ["--dangerously-bypass-approvals-and-sandbox"] : []),
|
|
939
|
-
"--add-dir",
|
|
940
|
-
WORKSPACE,
|
|
941
|
-
"-",
|
|
942
|
-
],
|
|
1001
|
+
args: ["exec", "--yolo", "--add-dir", WORKSPACE, "-"],
|
|
943
1002
|
};
|
|
944
1003
|
case "opencode":
|
|
945
1004
|
return {
|
|
946
1005
|
cmd: "opencode",
|
|
947
1006
|
args: [
|
|
948
1007
|
"run",
|
|
949
|
-
...(agentCfg.model ? ["--model", agentCfg.model] : []),
|
|
950
1008
|
"--format",
|
|
951
1009
|
"json",
|
|
952
1010
|
"--pure",
|
|
953
|
-
|
|
1011
|
+
"--dangerously-skip-permissions",
|
|
954
1012
|
],
|
|
955
1013
|
};
|
|
956
1014
|
case "gemini":
|
|
@@ -984,7 +1042,7 @@ function buildCliCommand(agentCfg, task, prompt) {
|
|
|
984
1042
|
cmd: "cmd",
|
|
985
1043
|
args: [
|
|
986
1044
|
"/c",
|
|
987
|
-
`type "${promptFile}" | abacusai -p --output-format stream-json --permission-mode
|
|
1045
|
+
`type "${promptFile}" | abacusai -p --output-format stream-json --permission-mode yolo --dangerously-skip-permissions --auto-accept-edits --add-dir "${WORKSPACE}"`,
|
|
988
1046
|
],
|
|
989
1047
|
};
|
|
990
1048
|
}
|
|
@@ -992,7 +1050,7 @@ function buildCliCommand(agentCfg, task, prompt) {
|
|
|
992
1050
|
cmd: "sh",
|
|
993
1051
|
args: [
|
|
994
1052
|
"-c",
|
|
995
|
-
`cat "${promptFile}" | abacusai -p --output-format stream-json --permission-mode
|
|
1053
|
+
`cat "${promptFile}" | abacusai -p --output-format stream-json --permission-mode yolo --dangerously-skip-permissions --auto-accept-edits --add-dir "${WORKSPACE}"`,
|
|
996
1054
|
],
|
|
997
1055
|
};
|
|
998
1056
|
}
|
|
@@ -1217,6 +1275,7 @@ function completeTask(task, agentName) {
|
|
|
1217
1275
|
ag.startTime = null;
|
|
1218
1276
|
ag.lastLine = `Última: ${task.id} completada`;
|
|
1219
1277
|
updateQueueFile(task);
|
|
1278
|
+
writeInboxNotification(task, agentName, elapsed);
|
|
1220
1279
|
scheduleNext();
|
|
1221
1280
|
renderDashboard();
|
|
1222
1281
|
}
|
|
@@ -1302,24 +1361,24 @@ function failTask(task, agentName, code) {
|
|
|
1302
1361
|
ag.process = null;
|
|
1303
1362
|
ag.startTime = null;
|
|
1304
1363
|
|
|
1305
|
-
const
|
|
1364
|
+
const shouldFallback =
|
|
1306
1365
|
["Codex", "OpenCode"].includes(agentName) &&
|
|
1307
1366
|
(failureFlags.exhaustedQuota ||
|
|
1308
1367
|
failureFlags.providerUnavailable ||
|
|
1309
1368
|
retries >= maxRetries);
|
|
1310
1369
|
|
|
1311
|
-
if (
|
|
1370
|
+
if (shouldFallback) {
|
|
1312
1371
|
const reason = failureFlags.exhaustedQuota
|
|
1313
1372
|
? "cuota o límite agotado"
|
|
1314
1373
|
: failureFlags.providerUnavailable
|
|
1315
1374
|
? "proveedor o sesión no disponibles"
|
|
1316
1375
|
: "fallo persistente";
|
|
1317
|
-
if (
|
|
1376
|
+
if (tryFallbackToAlternative(task, agentName, reason)) {
|
|
1377
|
+
writeInboxFailureNotification(task, agentName, task.agent, reason);
|
|
1318
1378
|
setTimeout(() => {
|
|
1319
1379
|
scheduleNext();
|
|
1320
|
-
|
|
1380
|
+
safeRenderDashboard();
|
|
1321
1381
|
}, 3000);
|
|
1322
|
-
renderDashboard();
|
|
1323
1382
|
return;
|
|
1324
1383
|
}
|
|
1325
1384
|
}
|
|
@@ -1351,7 +1410,7 @@ function failTask(task, agentName, code) {
|
|
|
1351
1410
|
setTimeout(
|
|
1352
1411
|
() => {
|
|
1353
1412
|
scheduleNext();
|
|
1354
|
-
|
|
1413
|
+
safeRenderDashboard();
|
|
1355
1414
|
},
|
|
1356
1415
|
Math.max(
|
|
1357
1416
|
Math.min(task._retryAfter - Date.now() + 5000, 3600_000),
|
|
@@ -1361,10 +1420,9 @@ function failTask(task, agentName, code) {
|
|
|
1361
1420
|
} else {
|
|
1362
1421
|
setTimeout(() => {
|
|
1363
1422
|
scheduleNext();
|
|
1364
|
-
|
|
1423
|
+
safeRenderDashboard();
|
|
1365
1424
|
}, 3000);
|
|
1366
1425
|
}
|
|
1367
|
-
renderDashboard();
|
|
1368
1426
|
}
|
|
1369
1427
|
|
|
1370
1428
|
// ============================================================================
|
|
@@ -1406,7 +1464,11 @@ function updateQueueFile(completedTask) {
|
|
|
1406
1464
|
`^${completedTask.id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(\\s|$|\\|)`,
|
|
1407
1465
|
);
|
|
1408
1466
|
const filtered = lines.filter((l) => !idMatcher.test(l.trim()));
|
|
1409
|
-
const idx = filtered.findIndex(
|
|
1467
|
+
const idx = filtered.findIndex(
|
|
1468
|
+
(l) =>
|
|
1469
|
+
l.trim().startsWith("## Completed") ||
|
|
1470
|
+
l.trim().startsWith("## Completadas"),
|
|
1471
|
+
);
|
|
1410
1472
|
if (idx >= 0)
|
|
1411
1473
|
filtered.splice(
|
|
1412
1474
|
idx + 1,
|
|
@@ -1474,6 +1536,19 @@ function detectSupportAgentFailure(agentName) {
|
|
|
1474
1536
|
}
|
|
1475
1537
|
|
|
1476
1538
|
function getClaudeFallbackAgent(task) {
|
|
1539
|
+
const hasBackend = REPOS.backend && fs.existsSync(REPOS.backend);
|
|
1540
|
+
const hasFrontend = REPOS.frontend && fs.existsSync(REPOS.frontend);
|
|
1541
|
+
const isSameRepo = hasBackend && hasFrontend &&
|
|
1542
|
+
path.resolve(REPOS.backend) === path.resolve(REPOS.frontend);
|
|
1543
|
+
|
|
1544
|
+
if (isSameRepo || !hasFrontend) {
|
|
1545
|
+
if (AGENTS["Frontend"]?.cli === "claude") return "Frontend";
|
|
1546
|
+
if (AGENTS["Backend"]?.cli === "claude") return "Backend";
|
|
1547
|
+
}
|
|
1548
|
+
if (!hasBackend && hasFrontend) {
|
|
1549
|
+
if (AGENTS["Frontend"]?.cli === "claude") return "Frontend";
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1477
1552
|
const preferred = task.repo === "frontend" ? "Frontend" : "Backend";
|
|
1478
1553
|
if (AGENTS[preferred]?.cli === "claude") return preferred;
|
|
1479
1554
|
return (
|
|
@@ -1481,13 +1556,31 @@ function getClaudeFallbackAgent(task) {
|
|
|
1481
1556
|
);
|
|
1482
1557
|
}
|
|
1483
1558
|
|
|
1484
|
-
function
|
|
1559
|
+
function getAlternativeSupportAgent(failedAgentName) {
|
|
1560
|
+
if (failedAgentName === "Codex") return "OpenCode";
|
|
1561
|
+
if (failedAgentName === "OpenCode") return "Codex";
|
|
1562
|
+
return null;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
function tryFallbackToAlternative(task, failedAgentName, reason) {
|
|
1485
1566
|
if (!["Codex", "OpenCode"].includes(failedAgentName)) return false;
|
|
1486
|
-
const fallbackAgent = getClaudeFallbackAgent(task);
|
|
1487
|
-
if (!fallbackAgent || fallbackAgent === failedAgentName) return false;
|
|
1488
1567
|
|
|
1489
|
-
|
|
1490
|
-
|
|
1568
|
+
// Step 1: try sibling support agent (Codex → OpenCode, OpenCode → Codex)
|
|
1569
|
+
const siblingAgent = getAlternativeSupportAgent(failedAgentName);
|
|
1570
|
+
const siblingAvailable =
|
|
1571
|
+
siblingAgent &&
|
|
1572
|
+
AGENTS[siblingAgent] &&
|
|
1573
|
+
state.agents[siblingAgent]?.status === "idle" &&
|
|
1574
|
+
!rateLimitedAgents.has(siblingAgent);
|
|
1575
|
+
|
|
1576
|
+
// Step 2: if sibling is also unavailable, fall back to Claude worker (prefer Frontend)
|
|
1577
|
+
const targetAgent = siblingAvailable
|
|
1578
|
+
? siblingAgent
|
|
1579
|
+
: getClaudeFallbackAgent(task);
|
|
1580
|
+
if (!targetAgent || targetAgent === failedAgentName) return false;
|
|
1581
|
+
|
|
1582
|
+
const queueUpdated = updateQueueTaskAgent(task.id, targetAgent);
|
|
1583
|
+
task.agent = targetAgent;
|
|
1491
1584
|
task.status = "pending";
|
|
1492
1585
|
task._retryAfter = Date.now() + 3000;
|
|
1493
1586
|
failedTasks.set(task.id, 0);
|
|
@@ -1495,17 +1588,17 @@ function tryFallbackToClaude(task, failedAgentName, reason) {
|
|
|
1495
1588
|
|
|
1496
1589
|
log(
|
|
1497
1590
|
"FALLBACK",
|
|
1498
|
-
`${task.id}
|
|
1591
|
+
`${task.id} reasignada de ${failedAgentName} a ${targetAgent} (${reason})`,
|
|
1499
1592
|
);
|
|
1500
1593
|
appendToAgent(
|
|
1501
1594
|
failedAgentName,
|
|
1502
|
-
`{yellow-fg}=== REASIGNADA A ${escBl(
|
|
1595
|
+
`{yellow-fg}=== REASIGNADA A ${escBl(targetAgent)} (${escBl(reason)}) ==={/yellow-fg}`,
|
|
1503
1596
|
true,
|
|
1504
1597
|
);
|
|
1505
1598
|
if (!queueUpdated) {
|
|
1506
1599
|
log(
|
|
1507
1600
|
"WARN",
|
|
1508
|
-
`${task.id}
|
|
1601
|
+
`${task.id} reasignada a ${targetAgent}, pero QUEUE.md no pudo actualizarse`,
|
|
1509
1602
|
);
|
|
1510
1603
|
}
|
|
1511
1604
|
return true;
|
|
@@ -1525,17 +1618,17 @@ if (!CLI.headless && screen) {
|
|
|
1525
1618
|
log("INFO", "Reanudado");
|
|
1526
1619
|
}
|
|
1527
1620
|
scheduleNext();
|
|
1528
|
-
|
|
1621
|
+
safeRenderDashboard();
|
|
1529
1622
|
});
|
|
1530
1623
|
screen.key("p", () => {
|
|
1531
1624
|
state.paused = !state.paused;
|
|
1532
1625
|
log("INFO", state.paused ? L.paused : L.resumed);
|
|
1533
|
-
|
|
1626
|
+
safeRenderDashboard();
|
|
1534
1627
|
});
|
|
1535
1628
|
screen.key("r", () => {
|
|
1536
1629
|
reloadQueue();
|
|
1537
1630
|
log("INFO", L.queueReloaded(state.queue.length));
|
|
1538
|
-
|
|
1631
|
+
safeRenderDashboard();
|
|
1539
1632
|
});
|
|
1540
1633
|
}
|
|
1541
1634
|
|
|
@@ -1544,10 +1637,7 @@ if (!CLI.headless && screen) {
|
|
|
1544
1637
|
// ============================================================================
|
|
1545
1638
|
log("INFO", L.starting(PROJECT_NAME));
|
|
1546
1639
|
state.completed = parseCompletedFromFile();
|
|
1547
|
-
log(
|
|
1548
|
-
"INFO",
|
|
1549
|
-
L.loadedCompleted(state.completed.length),
|
|
1550
|
-
);
|
|
1640
|
+
log("INFO", L.loadedCompleted(state.completed.length));
|
|
1551
1641
|
reloadQueue();
|
|
1552
1642
|
log("INFO", `${L.queue}: ${state.queue.length} ${L.tasks}`);
|
|
1553
1643
|
renderDashboard();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liriraid/agentflow-ai",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
4
4
|
"description": "Multi-agent workspace orchestrator with TUI. Coordinates AI coding agents over your real frontend and backend projects.",
|
|
5
5
|
"author": "LiriRaid",
|
|
6
6
|
"homepage": "https://github.com/LiriRaid/agentflow-ai#readme",
|
package/src/ink/index.mjs
CHANGED
|
@@ -20,6 +20,7 @@ const CONTROL_FILE = path.join(ROOT, 'logs', 'orchestrator-control.json');
|
|
|
20
20
|
|
|
21
21
|
const argv = process.argv.slice(2);
|
|
22
22
|
const startPaused = argv.includes('--paused');
|
|
23
|
+
const startYolo = argv.includes('--yolo');
|
|
23
24
|
const TEXT = {
|
|
24
25
|
es: {
|
|
25
26
|
configMissing: root =>
|
|
@@ -266,6 +267,7 @@ function ensureEngine() {
|
|
|
266
267
|
|
|
267
268
|
const childArgs = [ENGINE_FILE, '--headless'];
|
|
268
269
|
if (startPaused) childArgs.push('--paused');
|
|
270
|
+
if (startYolo) childArgs.push('--yolo');
|
|
269
271
|
|
|
270
272
|
pushLocalEvent(
|
|
271
273
|
startPaused ? text.startPaused : text.startRunning
|
|
@@ -36,14 +36,14 @@ When the user requests work after startup:
|
|
|
36
36
|
|
|
37
37
|
1. Do not implement the work in the interactive Claude session.
|
|
38
38
|
2. Convert the request into one or more TASKs in `QUEUE.md`.
|
|
39
|
-
3.
|
|
40
|
-
4. Assign a Claude-Worker only when:
|
|
41
|
-
- Codex
|
|
42
|
-
-
|
|
43
|
-
- the task is highly sensitive and needs Claude as the worker,
|
|
44
|
-
- the user explicitly asks Claude to take the worker task.
|
|
39
|
+
3. Always assign first to `OpenCode` (exploration) and `Codex` (implementation).
|
|
40
|
+
4. Assign a Claude-Worker (`Frontend` or `Backend`) **only** when:
|
|
41
|
+
- **Multiple independent tasks exist** AND Codex + OpenCode are both already occupied, OR
|
|
42
|
+
- A task has **permanently failed** in Codex AND OpenCode — then Claude-Worker takes it as last resort.
|
|
45
43
|
|
|
46
|
-
|
|
44
|
+
The TUI handles automatic fallback: Codex fails → tries OpenCode → tries Claude-Worker. You only need to manually assign Claude-Workers for load balancing (case a) or when the TUI marks a task as permanently `failed` (case b).
|
|
45
|
+
|
|
46
|
+
The `repo` field determines the working directory: `frontend` for UI/client work, `backend` for API/server work. Codex and OpenCode can work in either repo depending on the task.
|
|
47
47
|
|
|
48
48
|
## This Workspace Is NOT the Real Project
|
|
49
49
|
|
|
@@ -66,11 +66,14 @@ When the user says something like `Read ORCHESTRATOR.md and start`, do this:
|
|
|
66
66
|
2. Read `orchestrator.config.json` — identify the real project paths in `repos` (frontend, backend). Those are the paths where the worker agents operate.
|
|
67
67
|
3. Read `<projectName>-plan.md`, `PLAN.md`, or `plan.md` if present.
|
|
68
68
|
4. Read the newest `handoffs/HANDOFF-*.md` if the folder exists.
|
|
69
|
-
5. Read `
|
|
70
|
-
6. Read
|
|
71
|
-
7. Read `
|
|
72
|
-
8.
|
|
73
|
-
9.
|
|
69
|
+
5. **Read `INBOX.md` if it exists** — it contains automatic TUI notifications of completed tasks that require your attention (creating next TASKs, reading agent reports, etc.).
|
|
70
|
+
6. Read `QUEUE.md` to understand pending, active, and completed work.
|
|
71
|
+
7. Read all `progress/PROGRESS-*.md` files if present.
|
|
72
|
+
8. Read `ENGRAM.md` and follow the memory rules.
|
|
73
|
+
9. Use `openspec/` for large or multi-phase changes.
|
|
74
|
+
10. Tell the user the orchestrator is ready and ask what to prioritize.
|
|
75
|
+
|
|
76
|
+
**INBOX rule:** At the start of EACH response, if `INBOX.md` has new entries since your last read, check it first. This is how you know when an agent finished and what to create next — without Away Mode active.
|
|
74
77
|
|
|
75
78
|
Startup is context loading only. Do not create project code changes during startup.
|
|
76
79
|
|
|
@@ -102,14 +105,19 @@ Away Mode limits:
|
|
|
102
105
|
|
|
103
106
|
## Fallback Policy
|
|
104
107
|
|
|
105
|
-
|
|
108
|
+
The TUI handles fallback automatically following this chain:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
Codex fails → tries OpenCode (if idle and not rate-limited)
|
|
112
|
+
↓ (if OpenCode also fails or is blocked)
|
|
113
|
+
→ Frontend (frontend repo) or Backend (backend repo) as last resort
|
|
114
|
+
```
|
|
106
115
|
|
|
107
|
-
|
|
108
|
-
2. Add a clear note in `QUEUE.md`, `TASKS.md`, or a handoff.
|
|
109
|
-
3. Reassign the TASK to a Claude-Worker (`Backend` or `Frontend`, based on the repo).
|
|
110
|
-
4. Include the available context so the Claude-Worker can continue instead of restarting from zero.
|
|
116
|
+
As Orchestrator you do **not** need to manually reassign on failure — the TUI does it. Your role is:
|
|
111
117
|
|
|
112
|
-
|
|
118
|
+
1. Check `INBOX.md` or `QUEUE.md` to confirm the fallback ran correctly.
|
|
119
|
+
2. If the TUI could not resolve it (task marked `failed`), then manually assign to `Frontend` or `Backend` based on the repo.
|
|
120
|
+
3. Leave a note in `QUEUE.md` or `TASKS.md` if the reason is relevant for the session.
|
|
113
121
|
|
|
114
122
|
## Agents
|
|
115
123
|
|
|
@@ -63,22 +63,22 @@
|
|
|
63
63
|
"Codex": {
|
|
64
64
|
"cli": "codex",
|
|
65
65
|
"profile": "codex",
|
|
66
|
-
"defaultRepo": "
|
|
66
|
+
"defaultRepo": "frontend",
|
|
67
67
|
"model": "gpt-5.5",
|
|
68
68
|
"instructionsFile": "agents/CODEX.md"
|
|
69
69
|
},
|
|
70
70
|
"Gemini": {
|
|
71
71
|
"cli": "gemini",
|
|
72
72
|
"profile": "gemini",
|
|
73
|
-
"defaultRepo": "
|
|
74
|
-
"model": "
|
|
73
|
+
"defaultRepo": "frontend",
|
|
74
|
+
"model": "auto",
|
|
75
75
|
"instructionsFile": "agents/GEMINI.md"
|
|
76
76
|
},
|
|
77
77
|
"OpenCode": {
|
|
78
78
|
"cli": "opencode",
|
|
79
79
|
"profile": "opencode",
|
|
80
|
-
"defaultRepo": "
|
|
81
|
-
"model": "
|
|
80
|
+
"defaultRepo": "frontend",
|
|
81
|
+
"model": "auto",
|
|
82
82
|
"instructionsFile": "agents/OPENCODE.md"
|
|
83
83
|
},
|
|
84
84
|
"Cursor": {
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"cli": "abacusai",
|
|
93
93
|
"profile": "abacusai",
|
|
94
94
|
"defaultRepo": "backend",
|
|
95
|
-
"model": "
|
|
95
|
+
"model": "auto",
|
|
96
96
|
"instructionsFile": "agents/ABACUS.md"
|
|
97
97
|
}
|
|
98
98
|
}
|
|
@@ -16,7 +16,16 @@ Hay dos roles distintos que no deben confundirse:
|
|
|
16
16
|
1. **Claude-Orquestador**: la sesión interactiva que lee este archivo, divide el trabajo, edita `QUEUE.md`, revisa resultados y decide siguientes pasos. Este rol no modifica código del proyecto directamente.
|
|
17
17
|
2. **Claude-Worker**: agentes lanzados por la TUI con CLI `claude`, por ejemplo `Backend` y `Frontend`. Estos agentes sí pueden implementar código cuando una TASK se les asigna explícitamente.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
**Prioridad de asignación de trabajo:**
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
OpenCode / Codex → primera opción siempre (exploración e implementación)
|
|
23
|
+
Claude-Worker → último recurso, solo en estos dos casos:
|
|
24
|
+
a) Múltiples TASKs independientes Y Codex + OpenCode ambos ocupados → Claude-Worker toma 1
|
|
25
|
+
b) Codex falló persistentemente Y OpenCode también falló → Claude-Worker toma la tarea
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
El Orquestador NO asigna a Claude-Worker en la primera tanda si solo hay 1 tarea o si Codex/OpenCode están disponibles. La TUI gestiona el fallback automático al fallar un agente.
|
|
20
29
|
|
|
21
30
|
## El workspace NO es el proyecto real
|
|
22
31
|
|
|
@@ -37,11 +46,14 @@ Cuando necesites entender el proyecto para planificar tareas, **lee archivos des
|
|
|
37
46
|
2. Lee `orchestrator.config.json` — identifica las rutas reales en `repos` (frontend, backend). Esas son las rutas del proyecto real donde trabajan los agentes.
|
|
38
47
|
3. Lee `<projectName>-plan.md` (o `PLAN.md` / `plan.md`) si existe; ese es el plan general.
|
|
39
48
|
4. Lee el handoff más reciente en `handoffs/HANDOFF-*.md` si existe la carpeta.
|
|
40
|
-
5. Lee `
|
|
41
|
-
6. Lee
|
|
42
|
-
7. Lee `
|
|
43
|
-
8.
|
|
44
|
-
9.
|
|
49
|
+
5. **Lee `INBOX.md` si existe** — contiene notificaciones automáticas del TUI de tasks completadas que requieren tu atención (crear siguientes TASKs, leer reportes de agentes, etc.).
|
|
50
|
+
6. Lee `QUEUE.md` para ver trabajo activo y pendiente.
|
|
51
|
+
7. Lee todos los archivos `progress/PROGRESS-*.md` que existan para entender el estado actual de cada agente.
|
|
52
|
+
8. Lee `ENGRAM.md` para respetar la convención de memoria persistente del proyecto.
|
|
53
|
+
9. Si existe `openspec/`, úsalo como capa de artefactos para cambios grandes o de varias fases.
|
|
54
|
+
10. Pregunta al usuario qué quiere priorizar; no planifiques toda la sesión automáticamente.
|
|
55
|
+
|
|
56
|
+
**Regla de INBOX:** Al inicio de CADA respuesta, si `INBOX.md` tiene entradas nuevas desde tu última lectura, léelo primero antes de responder al usuario. Así sabrás qué agentes terminaron y qué falta crear.
|
|
45
57
|
|
|
46
58
|
## Restricción operativa por defecto
|
|
47
59
|
|
|
@@ -93,25 +105,19 @@ entonces debes entrar en **Modo Ausencia** durante esa sesión.
|
|
|
93
105
|
|
|
94
106
|
### Fallback por cuota o indisponibilidad
|
|
95
107
|
|
|
96
|
-
|
|
108
|
+
La TUI gestiona el fallback automáticamente siguiendo esta cadena:
|
|
97
109
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
- indisponibilidad temporal del CLI
|
|
104
|
-
|
|
105
|
-
entonces no debes dejar la tarea en bucle indefinidamente.
|
|
106
|
-
|
|
107
|
-
Debes hacer esto:
|
|
110
|
+
```
|
|
111
|
+
Codex falla → intenta OpenCode (si está libre y sin rate limit)
|
|
112
|
+
↓ (si OpenCode también falla o está bloqueado)
|
|
113
|
+
→ Frontend (repo FE) o Backend (repo BE) como último recurso
|
|
114
|
+
```
|
|
108
115
|
|
|
109
|
-
|
|
110
|
-
2. Dejar nota clara del motivo en `QUEUE.md`, `TASKS.md` o handoff si hace falta.
|
|
111
|
-
3. Reasignar la TASK a un **Claude-Worker** (`Backend` o `Frontend`, según el repo) como fallback.
|
|
112
|
-
4. Hacer que Claude-Worker continúe la ejecución con el contexto ya disponible, en vez de abandonar la tarea.
|
|
116
|
+
Como Orquestador, **no necesitas reasignar manualmente** cuando hay un fallo — la TUI lo hace sola. Tu rol en este caso es:
|
|
113
117
|
|
|
114
|
-
|
|
118
|
+
1. Verificar en `INBOX.md` o `QUEUE.md` que el fallback se ejecutó correctamente.
|
|
119
|
+
2. Si la TUI no pudo resolver el fallback (tarea marcada `failed`), entonces sí debes intervenir: asigna explícitamente a `Frontend` o `Backend` según el repo.
|
|
120
|
+
3. Deja nota en `QUEUE.md` o `TASKS.md` del motivo si es relevante para la sesión.
|
|
115
121
|
|
|
116
122
|
### Fin de Modo Ausencia
|
|
117
123
|
|
|
@@ -153,13 +159,13 @@ Revisa `orchestrator.config.json` → `agents`. Cada entrada tiene:
|
|
|
153
159
|
3. (Opcional) Para un brief muy detallado, crea `briefs/TASK-NNN-BRIEF.md`; también se inyecta.
|
|
154
160
|
4. Dependencias: agrega `> after:TASK-NNN` al final de la descripción para bloquear la tarea.
|
|
155
161
|
5. Dile al usuario que presione **R** en la TUI para recargar la cola, o **S** si está pausada.
|
|
156
|
-
6. **
|
|
157
|
-
7.
|
|
158
|
-
-
|
|
159
|
-
-
|
|
160
|
-
-
|
|
161
|
-
8. Si hay más TASKs que agentes
|
|
162
|
-
9.
|
|
162
|
+
6. **Prioriza Codex y OpenCode** para toda implementación y exploración. Claude-Workers solo cuando hay saturación o fallo total de agentes de soporte.
|
|
163
|
+
7. Distribución según cantidad de TASKs independientes:
|
|
164
|
+
- **1 tarea**: OpenCode (exploración) o Codex (implementación). Nunca Claude-Worker en primera instancia.
|
|
165
|
+
- **2 tareas**: OpenCode + Codex, una cada uno.
|
|
166
|
+
- **3+ tareas** y Codex+OpenCode ambos ocupados: el excedente puede ir a `Frontend` (repo FE) o `Backend` (repo BE) según corresponda.
|
|
167
|
+
8. Si hay más TASKs que agentes disponibles, deja el resto en cola con dependencias claras o prioridad menor; no uses Gemini, Cursor ni Abacus salvo permiso explícito.
|
|
168
|
+
9. El campo `repo` determina en qué directorio trabaja el agente. Usa siempre el valor correcto: `frontend` para trabajo de UI/cliente, `backend` para trabajo de API/servidor. Codex y OpenCode pueden trabajar en ambos repos según lo que indique la task.
|
|
163
169
|
|
|
164
170
|
## Reglas
|
|
165
171
|
|
|
@@ -171,7 +177,7 @@ Revisa `orchestrator.config.json` → `agents`. Cada entrada tiene:
|
|
|
171
177
|
6. Al terminar la sesión, escribe un `handoffs/HANDOFF-<fecha>.md` resumiendo qué se hizo y qué sigue.
|
|
172
178
|
7. **Por defecto solo usa Claude, Codex y OpenCode**. No uses Gemini, Cursor ni Abacus salvo instrucción explícita del usuario.
|
|
173
179
|
8. Si el usuario activa **Modo Ausencia**, revisa progreso cada 5 minutos y reasigna nuevas TASKs razonables dentro del alcance actual sin esperar confirmación intermedia.
|
|
174
|
-
9.
|
|
180
|
+
9. La TUI gestiona el fallback automáticamente: Codex falla → OpenCode → Claude-Worker (Frontend/Backend según repo). Solo intervén manualmente si la tarea queda marcada `failed`.
|
|
175
181
|
10. Usa Engram para guardar decisiones, hallazgos, bugs y resúmenes de sesión; no dependas solo del contexto corto de la conversación.
|
|
176
182
|
11. Para cambios grandes, usa `openspec/changes/<change-name>/` para proposal, spec, design, tasks y verify; no dejes todo solo en la conversación.
|
|
177
183
|
12. No asumas bypass total o autoaceptación de cambios en los agentes. Claude debe seguir siendo la autoridad final para validar el resultado esperado antes de que el usuario dé la aprobación definitiva.
|
|
@@ -202,6 +208,7 @@ Actualiza esta sección al inicio y al final de cada sesión:
|
|
|
202
208
|
## Archivos de referencia
|
|
203
209
|
|
|
204
210
|
- **Plan del proyecto:** `<projectName>-plan.md`
|
|
211
|
+
- **Notificaciones de tasks completadas:** `INBOX.md` — el TUI escribe aquí al terminar cada task; léelo al inicio de cada respuesta
|
|
205
212
|
- **Protocolo de agentes:** `AGENT-PROTOCOL.md` (reglas compartidas opcionales)
|
|
206
213
|
- **Instrucciones por agente:** `agents/*.md`
|
|
207
214
|
- **Memoria persistente:** `ENGRAM.md`
|
|
@@ -63,22 +63,22 @@
|
|
|
63
63
|
"Codex": {
|
|
64
64
|
"cli": "codex",
|
|
65
65
|
"profile": "codex",
|
|
66
|
-
"defaultRepo": "
|
|
66
|
+
"defaultRepo": "frontend",
|
|
67
67
|
"model": "gpt-5.5",
|
|
68
68
|
"instructionsFile": "agents/CODEX.md"
|
|
69
69
|
},
|
|
70
|
-
|
|
70
|
+
"Gemini": {
|
|
71
71
|
"cli": "gemini",
|
|
72
72
|
"profile": "gemini",
|
|
73
|
-
"defaultRepo": "
|
|
74
|
-
"model": "
|
|
73
|
+
"defaultRepo": "frontend",
|
|
74
|
+
"model": "auto",
|
|
75
75
|
"instructionsFile": "agents/GEMINI.md"
|
|
76
|
-
},
|
|
76
|
+
},
|
|
77
77
|
"OpenCode": {
|
|
78
78
|
"cli": "opencode",
|
|
79
79
|
"profile": "opencode",
|
|
80
|
-
"defaultRepo": "
|
|
81
|
-
"model": "
|
|
80
|
+
"defaultRepo": "frontend",
|
|
81
|
+
"model": "auto",
|
|
82
82
|
"instructionsFile": "agents/OPENCODE.md"
|
|
83
83
|
},
|
|
84
84
|
"Cursor": {
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"cli": "abacusai",
|
|
93
93
|
"profile": "abacusai",
|
|
94
94
|
"defaultRepo": "backend",
|
|
95
|
-
"model": "
|
|
95
|
+
"model": "auto",
|
|
96
96
|
"instructionsFile": "agents/ABACUS.md"
|
|
97
97
|
}
|
|
98
98
|
}
|