@liriraid/agentflow-ai 1.0.22 → 1.0.23

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/ink/index.mjs +23 -25
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liriraid/agentflow-ai",
3
- "version": "1.0.22",
3
+ "version": "1.0.23",
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
@@ -100,6 +100,7 @@ let quitRequested = false;
100
100
  let resizeTimer = null;
101
101
  let isResizing = false;
102
102
  let lastColumns = process.stdout?.columns ?? 0;
103
+ let isRendering = false;
103
104
 
104
105
  function normalizeInlineMessage(message) {
105
106
  return String(message ?? '')
@@ -324,21 +325,24 @@ function ensureEngine() {
324
325
  }
325
326
 
326
327
  function refresh() {
327
- if (isResizing) return;
328
-
329
- const snapshot = buildSnapshot();
330
- lastRenderedSnapshot = snapshot;
331
- ensureStateWatcher();
332
- if (!inkApp) {
333
- inkApp = render(React.createElement(App, {snapshot, onAction: requestAction}), {
334
- exitOnCtrlC: false,
335
- patchConsole: false,
336
- alternateScreen: true
337
- });
338
- return;
328
+ if (isResizing || isRendering) return;
329
+ isRendering = true;
330
+ try {
331
+ const snapshot = buildSnapshot();
332
+ lastRenderedSnapshot = snapshot;
333
+ ensureStateWatcher();
334
+ if (!inkApp) {
335
+ inkApp = render(React.createElement(App, {snapshot, onAction: requestAction}), {
336
+ exitOnCtrlC: false,
337
+ patchConsole: false,
338
+ alternateScreen: true
339
+ });
340
+ return;
341
+ }
342
+ inkApp.rerender(React.createElement(App, {snapshot, onAction: requestAction}));
343
+ } finally {
344
+ isRendering = false;
339
345
  }
340
-
341
- inkApp.rerender(React.createElement(App, {snapshot, onAction: requestAction}));
342
346
  }
343
347
 
344
348
  function clearTerminal() {
@@ -415,31 +419,25 @@ function ensureStateWatcher() {
415
419
  } catch { stateWatcher = null; }
416
420
  }
417
421
 
418
- // Tick independiente del reloj: actualiza activeLabel y timestamp cada 1s
419
- // sin leer el STATE_FILE, usando la última snapshot cacheada.
422
+ // Tick del reloj: solo actualiza lastRenderedSnapshot en memoria.
423
+ // scheduleRefresh es la única fuente de rerender para evitar renders concurrentes.
420
424
  function startClockTick() {
421
425
  clockInterval = setInterval(() => {
422
- if (!inkApp || !lastRenderedSnapshot?.startedAt) return;
426
+ if (!lastRenderedSnapshot?.startedAt) return;
423
427
  const activeSeconds = Math.max(0, Math.round((Date.now() - lastRenderedSnapshot.startedAt) / 1000));
424
- const updated = {
428
+ lastRenderedSnapshot = {
425
429
  ...lastRenderedSnapshot,
426
430
  activeLabel: formatDuration(activeSeconds),
427
431
  timestamp: new Date().toLocaleString(getLocale(), {hour12: false})
428
432
  };
429
- lastRenderedSnapshot = updated;
430
- inkApp.rerender(React.createElement(App, {snapshot: updated, onAction: requestAction}));
431
433
  }, 1000);
432
434
  }
433
435
 
434
436
  function scheduleRefresh() {
435
- const engineState = readEngineState();
436
- const busy = engineState && Object.values(engineState.agents || {}).some(a => a.status === 'busy');
437
- const hasWork = engineState && ((engineState.queue || []).length > 0 || (engineState.inProgress || []).length > 0);
438
- const ms = (busy || hasWork) ? 1000 : 4000;
439
437
  refreshTimer = setTimeout(() => {
440
438
  refresh();
441
439
  scheduleRefresh();
442
- }, ms);
440
+ }, 1000);
443
441
  }
444
442
 
445
443
  function mount() {