@liriraid/agentflow-ai 1.0.16 → 1.0.18

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/orchestrator.js CHANGED
@@ -1949,7 +1949,7 @@ function startQueueWatcher() {
1949
1949
  log("INFO", WORKSPACE_LANGUAGE === "es"
1950
1950
  ? `Nueva tarea detectada en QUEUE.md`
1951
1951
  : `New task detected in QUEUE.md`);
1952
- }, 400);
1952
+ }, 50);
1953
1953
  });
1954
1954
  watcher.on('error', () => {});
1955
1955
  } catch {}
@@ -2045,7 +2045,7 @@ function startInboxWatcher() {
2045
2045
  const watcher = fs.watch(INBOX_FILE, {persistent: false}, (eventType) => {
2046
2046
  if (eventType !== 'change') return;
2047
2047
  if (_inboxDebounce) clearTimeout(_inboxDebounce);
2048
- _inboxDebounce = setTimeout(dispatchInboxClaude, 600);
2048
+ _inboxDebounce = setTimeout(dispatchInboxClaude, 100);
2049
2049
  });
2050
2050
  watcher.on('error', () => {});
2051
2051
  } catch {}
@@ -2118,7 +2118,7 @@ function runAwayModeCheck() {
2118
2118
  const stateCtx = lines.join('\n');
2119
2119
  const monitorPrompt = lang === 'es'
2120
2120
  ? `Modo Ausencia activo — revisión automática cada 5 minutos.\n\nEstado del orquestador:\n${stateCtx}\n\nInstrucciones:\n1. Lee INBOX.md — si hay análisis completados sin tarea de implementación en QUEUE.md, créala\n2. Lee QUEUE.md — si hay tareas fallidas no reasignadas, reasígnalas al siguiente agente\n3. Si hay tareas pendientes sin asignar a ningún agente (agent = >0 o vacío), asígnalas a un agente idle (Codex u OpenCode)\n4. Si hay agentes idle y tareas pendientes sin procesar, revisa bloqueos y resuélvelos\n5. Si todo avanza, no hagas nada y responde brevemente "TodoOK"\n\nNo hagas commit ni push. No inventes tareas nuevas.`
2121
- : `Away Mode active — automatic check every 5 minutes.\n\nOrchestrator state:\n${stateCtx}\n\nInstructions:\n1. Read INBOX.md — if there are completed analyses without implementation tasks in QUEUE.md, create them\n2. Read QUEUE.md — if there are failed tasks not reassigned, reassign to next available agent\n3. If there are pending tasks with no agent assigned (agent = >0 or empty), assign them to an idle agent (Codex or OpenCode)\n4. If there are idle agents and pending tasks not being processed, check for blocking issues\n5. If everything is progressing, do nothing and respond briefly "AllGood"\n\nDo not commit or push. Do not invent new tasks.`;
2121
+ : `Away Mode active — automatic check every 5 minutes.\n\nOrchestrator state:\n${stateCtx}\n\nInstructions:\n1. Read INBOX.md — if there are completed analyses without implementation tasks in QUEUE.md, create them\n2. Read QUEUE.md — if there are failed tasks not reassigned, reassign to next available agent\n3. If there are pending tasks with no agent assigned (agent = >0 or empty), assign them to an idle agent (Codex or OpenCode)\n4. If there are idle agents and pending tasks not being processed, check for blocking issues\n5. If everything is progressing, do nothing and respond briefly "AllGood"\n\nDo not commit or push. Do not invent new tasks.`;
2122
2122
 
2123
2123
  const logPath = path.join(LOG_DIR, `away-check-${Date.now()}.log`);
2124
2124
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liriraid/agentflow-ai",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
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/app.mjs CHANGED
@@ -33,6 +33,7 @@ const TEXT = {
33
33
  paused: 'Pausado',
34
34
  preview: 'Explorando Ink',
35
35
  shortcuts: 'Atajos',
36
+ start: 'reanudar',
36
37
  pause: 'pausar',
37
38
  quit: 'salir y matar agentes',
38
39
  summary: 'Resumen',
@@ -57,6 +58,7 @@ const TEXT = {
57
58
  paused: 'Paused',
58
59
  preview: 'Exploring Ink',
59
60
  shortcuts: 'Shortcuts',
61
+ start: 'resume',
60
62
  pause: 'pause',
61
63
  quit: 'quit and stop agents',
62
64
  summary: 'Summary',
@@ -137,6 +139,7 @@ export function App({snapshot, paused = false, onAction}) {
137
139
  }
138
140
 
139
141
  const normalized = input.toLowerCase();
142
+ if (normalized === 's') onAction?.('start');
140
143
  if (normalized === 'p') onAction?.('pause');
141
144
  if (normalized === 'q') onAction?.('quit');
142
145
  });
@@ -160,11 +163,6 @@ export function App({snapshot, paused = false, onAction}) {
160
163
  `${snapshot.timestamp} | ${snapshot.stateLabel || (paused ? text.paused : text.preview)} | ${text.active.toLowerCase()} ${liveActiveLabel}`,
161
164
  heroWidth
162
165
  );
163
- const shortcutRest = truncate(
164
- `S ${text.start} P ${text.pause} R ${text.reload} Q ${text.quit}`,
165
- Math.max(0, shortcutWidth - 8)
166
- );
167
-
168
166
  return h(
169
167
  Box,
170
168
  {flexDirection: 'column', paddingX: 1, paddingY: 0},
@@ -185,10 +183,12 @@ export function App({snapshot, paused = false, onAction}) {
185
183
  Box,
186
184
  {marginBottom: 1},
187
185
  h(Text, {color: COLORS.warning}, `${text.shortcuts}: `),
186
+ h(Text, {bold: true}, 'S'),
187
+ h(Text, {color: COLORS.muted}, ` ${text.start} `),
188
188
  h(Text, {bold: true}, 'P'),
189
- h(Text, {color: COLORS.muted}, truncate(` ${text.pause} `, Math.max(0, shortcutWidth - 55))),
189
+ h(Text, {color: COLORS.muted}, ` ${text.pause} `),
190
190
  h(Text, {bold: true}, 'Q'),
191
- h(Text, {color: COLORS.muted}, truncate(` ${text.quit}`, Math.max(0, shortcutWidth - 70)))
191
+ h(Text, {color: COLORS.muted}, ` ${text.quit}`)
192
192
  ),
193
193
  h(
194
194
  Box,
package/src/ink/index.mjs CHANGED
@@ -90,6 +90,10 @@ if (fs.existsSync(CONTROL_FILE)) {
90
90
 
91
91
  let inkApp = null;
92
92
  let refreshTimer = null;
93
+ let clockInterval = null;
94
+ let stateWatcher = null;
95
+ let stateWatchDebounce = null;
96
+ let lastRenderedSnapshot = null;
93
97
  let spawnedEngine = null;
94
98
  let localEvents = [];
95
99
  let quitRequested = false;
@@ -323,6 +327,8 @@ function refresh() {
323
327
  if (isResizing) return;
324
328
 
325
329
  const snapshot = buildSnapshot();
330
+ lastRenderedSnapshot = snapshot;
331
+ ensureStateWatcher();
326
332
  if (!inkApp) {
327
333
  inkApp = render(React.createElement(App, {snapshot, onAction: requestAction}), {
328
334
  exitOnCtrlC: false,
@@ -371,6 +377,9 @@ function requestAction(action) {
371
377
  function shutdown() {
372
378
  if (refreshTimer) clearTimeout(refreshTimer);
373
379
  if (resizeTimer) clearTimeout(resizeTimer);
380
+ if (clockInterval) clearInterval(clockInterval);
381
+ if (stateWatchDebounce) clearTimeout(stateWatchDebounce);
382
+ if (stateWatcher) { try { stateWatcher.close(); } catch {} stateWatcher = null; }
374
383
  if (spawnedEngine && !spawnedEngine.killed && quitRequested) {
375
384
  try {
376
385
  spawnedEngine.kill('SIGTERM');
@@ -383,6 +392,40 @@ function shutdown() {
383
392
  try { fs.unlinkSync(STATE_FILE); } catch {}
384
393
  }
385
394
 
395
+ // Reactivo: dispara un refresh inmediato cuando el engine escribe el STATE_FILE.
396
+ // Esto elimina el lag de polling para cambios de cola y estado de agentes.
397
+ function ensureStateWatcher() {
398
+ if (stateWatcher || !fs.existsSync(STATE_FILE)) return;
399
+ try {
400
+ stateWatcher = fs.watch(STATE_FILE, {persistent: false}, () => {
401
+ if (stateWatchDebounce) clearTimeout(stateWatchDebounce);
402
+ stateWatchDebounce = setTimeout(() => {
403
+ if (quitRequested) return;
404
+ if (refreshTimer) { clearTimeout(refreshTimer); refreshTimer = null; }
405
+ refresh();
406
+ scheduleRefresh();
407
+ }, 50);
408
+ });
409
+ stateWatcher.on('error', () => { stateWatcher = null; });
410
+ } catch { stateWatcher = null; }
411
+ }
412
+
413
+ // Tick independiente del reloj: actualiza activeLabel y timestamp cada 1s
414
+ // sin leer el STATE_FILE, usando la última snapshot cacheada.
415
+ function startClockTick() {
416
+ clockInterval = setInterval(() => {
417
+ if (!inkApp || !lastRenderedSnapshot?.startedAt) return;
418
+ const activeSeconds = Math.max(0, Math.round((Date.now() - lastRenderedSnapshot.startedAt) / 1000));
419
+ const updated = {
420
+ ...lastRenderedSnapshot,
421
+ activeLabel: formatDuration(activeSeconds),
422
+ timestamp: new Date().toLocaleString(getLocale(), {hour12: false})
423
+ };
424
+ lastRenderedSnapshot = updated;
425
+ inkApp.rerender(React.createElement(App, {snapshot: updated, onAction: requestAction}));
426
+ }, 1000);
427
+ }
428
+
386
429
  function scheduleRefresh() {
387
430
  const engineState = readEngineState();
388
431
  const busy = engineState && Object.values(engineState.agents || {}).some(a => a.status === 'busy');
@@ -398,6 +441,7 @@ function mount() {
398
441
  ensureEngine();
399
442
  refresh();
400
443
  scheduleRefresh();
444
+ startClockTick();
401
445
 
402
446
  if (process.stdout.isTTY) {
403
447
  process.stdout.on('resize', () => {