attocode 0.1.4 → 0.1.6

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 (130) hide show
  1. package/CHANGELOG.md +36 -1
  2. package/dist/src/agent.d.ts +40 -1
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +492 -37
  5. package/dist/src/agent.js.map +1 -1
  6. package/dist/src/core/index.d.ts +1 -6
  7. package/dist/src/core/index.d.ts.map +1 -1
  8. package/dist/src/core/index.js +4 -7
  9. package/dist/src/core/index.js.map +1 -1
  10. package/dist/src/defaults.d.ts +1 -1
  11. package/dist/src/defaults.d.ts.map +1 -1
  12. package/dist/src/integrations/agent-registry.d.ts.map +1 -1
  13. package/dist/src/integrations/agent-registry.js +8 -1
  14. package/dist/src/integrations/agent-registry.js.map +1 -1
  15. package/dist/src/integrations/codebase-context.d.ts +163 -0
  16. package/dist/src/integrations/codebase-context.d.ts.map +1 -1
  17. package/dist/src/integrations/codebase-context.js +462 -0
  18. package/dist/src/integrations/codebase-context.js.map +1 -1
  19. package/dist/src/integrations/economics.d.ts +94 -2
  20. package/dist/src/integrations/economics.d.ts.map +1 -1
  21. package/dist/src/integrations/economics.js +265 -12
  22. package/dist/src/integrations/economics.js.map +1 -1
  23. package/dist/src/integrations/file-change-tracker.d.ts +1 -0
  24. package/dist/src/integrations/file-change-tracker.d.ts.map +1 -1
  25. package/dist/src/integrations/file-change-tracker.js +30 -12
  26. package/dist/src/integrations/file-change-tracker.js.map +1 -1
  27. package/dist/src/integrations/graph-visualization.d.ts +72 -0
  28. package/dist/src/integrations/graph-visualization.d.ts.map +1 -0
  29. package/dist/src/integrations/graph-visualization.js +383 -0
  30. package/dist/src/integrations/graph-visualization.js.map +1 -0
  31. package/dist/src/integrations/index.d.ts +2 -1
  32. package/dist/src/integrations/index.d.ts.map +1 -1
  33. package/dist/src/integrations/index.js +2 -0
  34. package/dist/src/integrations/index.js.map +1 -1
  35. package/dist/src/integrations/memory.d.ts +8 -0
  36. package/dist/src/integrations/memory.d.ts.map +1 -1
  37. package/dist/src/integrations/memory.js +23 -0
  38. package/dist/src/integrations/memory.js.map +1 -1
  39. package/dist/src/integrations/pending-plan.d.ts +26 -1
  40. package/dist/src/integrations/pending-plan.d.ts.map +1 -1
  41. package/dist/src/integrations/pending-plan.js +131 -3
  42. package/dist/src/integrations/pending-plan.js.map +1 -1
  43. package/dist/src/integrations/planning.d.ts +19 -0
  44. package/dist/src/integrations/planning.d.ts.map +1 -1
  45. package/dist/src/integrations/planning.js +78 -4
  46. package/dist/src/integrations/planning.js.map +1 -1
  47. package/dist/src/integrations/safety.d.ts +4 -0
  48. package/dist/src/integrations/safety.d.ts.map +1 -1
  49. package/dist/src/integrations/safety.js +46 -7
  50. package/dist/src/integrations/safety.js.map +1 -1
  51. package/dist/src/integrations/sqlite-store.d.ts.map +1 -1
  52. package/dist/src/integrations/sqlite-store.js +7 -1
  53. package/dist/src/integrations/sqlite-store.js.map +1 -1
  54. package/dist/src/main.js +0 -0
  55. package/dist/src/modes.d.ts +7 -0
  56. package/dist/src/modes.d.ts.map +1 -1
  57. package/dist/src/modes.js +69 -2
  58. package/dist/src/modes.js.map +1 -1
  59. package/dist/src/providers/llm-resilience.d.ts +8 -0
  60. package/dist/src/providers/llm-resilience.d.ts.map +1 -1
  61. package/dist/src/providers/llm-resilience.js +36 -0
  62. package/dist/src/providers/llm-resilience.js.map +1 -1
  63. package/dist/src/tools/agent.d.ts +20 -0
  64. package/dist/src/tools/agent.d.ts.map +1 -1
  65. package/dist/src/tools/agent.js +114 -0
  66. package/dist/src/tools/agent.js.map +1 -1
  67. package/dist/src/tools/file.d.ts.map +1 -1
  68. package/dist/src/tools/file.js +17 -3
  69. package/dist/src/tools/file.js.map +1 -1
  70. package/dist/src/tracing/trace-collector.d.ts +11 -1
  71. package/dist/src/tracing/trace-collector.d.ts.map +1 -1
  72. package/dist/src/tracing/trace-collector.js +92 -12
  73. package/dist/src/tracing/trace-collector.js.map +1 -1
  74. package/dist/src/tracing/types.d.ts +6 -0
  75. package/dist/src/tracing/types.d.ts.map +1 -1
  76. package/dist/src/tracing/types.js.map +1 -1
  77. package/dist/src/tui/app.d.ts.map +1 -1
  78. package/dist/src/tui/app.js +244 -66
  79. package/dist/src/tui/app.js.map +1 -1
  80. package/dist/src/tui/components/CollapsibleDiffView.d.ts +49 -0
  81. package/dist/src/tui/components/CollapsibleDiffView.d.ts.map +1 -0
  82. package/dist/src/tui/components/CollapsibleDiffView.js +302 -0
  83. package/dist/src/tui/components/CollapsibleDiffView.js.map +1 -0
  84. package/dist/src/tui/components/DiffView.d.ts +55 -0
  85. package/dist/src/tui/components/DiffView.d.ts.map +1 -0
  86. package/dist/src/tui/components/DiffView.js +356 -0
  87. package/dist/src/tui/components/DiffView.js.map +1 -0
  88. package/dist/src/tui/components/FileChangeSummary.d.ts +48 -0
  89. package/dist/src/tui/components/FileChangeSummary.d.ts.map +1 -0
  90. package/dist/src/tui/components/FileChangeSummary.js +152 -0
  91. package/dist/src/tui/components/FileChangeSummary.js.map +1 -0
  92. package/dist/src/tui/components/SideBySideDiff.d.ts +62 -0
  93. package/dist/src/tui/components/SideBySideDiff.d.ts.map +1 -0
  94. package/dist/src/tui/components/SideBySideDiff.js +320 -0
  95. package/dist/src/tui/components/SideBySideDiff.js.map +1 -0
  96. package/dist/src/tui/components/SyntaxText.d.ts +47 -0
  97. package/dist/src/tui/components/SyntaxText.d.ts.map +1 -0
  98. package/dist/src/tui/components/SyntaxText.js +43 -0
  99. package/dist/src/tui/components/SyntaxText.js.map +1 -0
  100. package/dist/src/tui/components/index.d.ts +5 -0
  101. package/dist/src/tui/components/index.d.ts.map +1 -1
  102. package/dist/src/tui/components/index.js +7 -0
  103. package/dist/src/tui/components/index.js.map +1 -1
  104. package/dist/src/tui/syntax/index.d.ts +12 -0
  105. package/dist/src/tui/syntax/index.d.ts.map +1 -0
  106. package/dist/src/tui/syntax/index.js +14 -0
  107. package/dist/src/tui/syntax/index.js.map +1 -0
  108. package/dist/src/tui/syntax/languages/bash.d.ts +8 -0
  109. package/dist/src/tui/syntax/languages/bash.d.ts.map +1 -0
  110. package/dist/src/tui/syntax/languages/bash.js +296 -0
  111. package/dist/src/tui/syntax/languages/bash.js.map +1 -0
  112. package/dist/src/tui/syntax/languages/javascript.d.ts +8 -0
  113. package/dist/src/tui/syntax/languages/javascript.d.ts.map +1 -0
  114. package/dist/src/tui/syntax/languages/javascript.js +253 -0
  115. package/dist/src/tui/syntax/languages/javascript.js.map +1 -0
  116. package/dist/src/tui/syntax/languages/json.d.ts +8 -0
  117. package/dist/src/tui/syntax/languages/json.d.ts.map +1 -0
  118. package/dist/src/tui/syntax/languages/json.js +112 -0
  119. package/dist/src/tui/syntax/languages/json.js.map +1 -0
  120. package/dist/src/tui/syntax/languages/python.d.ts +8 -0
  121. package/dist/src/tui/syntax/languages/python.d.ts.map +1 -0
  122. package/dist/src/tui/syntax/languages/python.js +232 -0
  123. package/dist/src/tui/syntax/languages/python.js.map +1 -0
  124. package/dist/src/tui/syntax/lexer.d.ts +51 -0
  125. package/dist/src/tui/syntax/lexer.d.ts.map +1 -0
  126. package/dist/src/tui/syntax/lexer.js +131 -0
  127. package/dist/src/tui/syntax/lexer.js.map +1 -0
  128. package/dist/src/types.d.ts +56 -0
  129. package/dist/src/types.d.ts.map +1 -1
  130. package/package.json +4 -1
@@ -10,6 +10,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
10
10
  */
11
11
  import { useState, useCallback, useEffect, memo, useRef, useMemo } from 'react';
12
12
  import { Box, Text, useApp, useInput, Static } from 'ink';
13
+ import { DiffView } from './components/DiffView.js';
13
14
  import { getTheme, getThemeNames } from './theme/index.js';
14
15
  import { ControlledCommandPalette } from './input/CommandPalette.js';
15
16
  import { ApprovalDialog } from './components/ApprovalDialog.js';
@@ -114,9 +115,18 @@ const ToolCallItem = memo(function ToolCallItem({ tc, expanded, colors }) {
114
115
  const argsStr = formatToolArgsCompact(tc.args);
115
116
  if (expanded) {
116
117
  const expandedArgs = formatToolArgsExpanded(tc.args);
117
- return (_jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: statusColor, children: icon }), _jsx(Text, { color: "#DDA0DD", bold: true, children: tc.name }), tc.duration ? _jsxs(Text, { color: colors.textMuted, dimColor: true, children: ["(", tc.duration, "ms)"] }) : null] }), expandedArgs.map((argLine, i) => (_jsx(Box, { marginLeft: 3, children: _jsx(Text, { color: "#87CEEB", dimColor: true, children: argLine }) }, i))), tc.status === 'success' && tc.result !== undefined && tc.result !== null ? (_jsx(Box, { marginLeft: 3, children: _jsx(Text, { color: "#98FB98", dimColor: true, children: `-> ${String(tc.result).slice(0, 150)}${String(tc.result).length > 150 ? '...' : ''}` }) })) : null, tc.status === 'error' && tc.error && (_jsx(Box, { marginLeft: 3, children: _jsx(Text, { color: "#FF6B6B", children: `x ${tc.error}` }) }))] }));
118
+ return (_jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: statusColor, children: icon }), _jsx(Text, { color: "#DDA0DD", bold: true, children: tc.name }), tc.duration ? _jsxs(Text, { color: colors.textMuted, dimColor: true, children: ["(", tc.duration, "ms)"] }) : null] }), expandedArgs.map((argLine, i) => (_jsx(Box, { marginLeft: 3, children: _jsx(Text, { color: "#87CEEB", dimColor: true, children: argLine }) }, i))), tc.status === 'success' && tc.result !== undefined && tc.result !== null ? (_jsx(Box, { marginLeft: 3, flexDirection: "column", children: (tc.name === 'edit_file' || tc.name === 'write_file') &&
119
+ typeof tc.result === 'object' && tc.result !== null &&
120
+ 'metadata' in tc.result &&
121
+ typeof tc.result.metadata?.diff === 'string' ? (_jsx(DiffView, { diff: tc.result.metadata.diff, expanded: true, maxLines: 15 })) : (_jsx(Text, { color: "#98FB98", dimColor: true, children: `-> ${String(tc.result).slice(0, 150)}${String(tc.result).length > 150 ? '...' : ''}` })) })) : null, tc.status === 'error' && tc.error && (_jsx(Box, { marginLeft: 3, children: _jsx(Text, { color: "#FF6B6B", children: `x ${tc.error}` }) }))] }));
118
122
  }
119
- return (_jsxs(Box, { marginLeft: 2, gap: 1, children: [_jsx(Text, { color: statusColor, children: icon }), _jsx(Text, { color: "#DDA0DD", bold: true, children: tc.name }), argsStr ? _jsx(Text, { color: colors.textMuted, dimColor: true, children: argsStr }) : null, tc.duration ? _jsxs(Text, { color: colors.textMuted, dimColor: true, children: ["(", tc.duration, "ms)"] }) : null] }));
123
+ // Check if result has diff metadata for collapsed summary
124
+ const hasDiff = tc.status === 'success' &&
125
+ (tc.name === 'edit_file' || tc.name === 'write_file') &&
126
+ typeof tc.result === 'object' && tc.result !== null &&
127
+ 'metadata' in tc.result &&
128
+ typeof tc.result.metadata?.diff === 'string';
129
+ return (_jsxs(Box, { marginLeft: 2, gap: 1, children: [_jsx(Text, { color: statusColor, children: icon }), _jsx(Text, { color: "#DDA0DD", bold: true, children: tc.name }), argsStr ? _jsx(Text, { color: colors.textMuted, dimColor: true, children: argsStr }) : null, hasDiff && (_jsx(DiffView, { diff: tc.result.metadata.diff, expanded: false })), tc.duration ? _jsxs(Text, { color: colors.textMuted, dimColor: true, children: ["(", tc.duration, "ms)"] }) : null] }));
120
130
  });
121
131
  const MemoizedInputArea = memo(function MemoizedInputArea({ onSubmit, disabled, borderColor, textColor, cursorColor, onCtrlC, onCtrlL, onCtrlP, onEscape, onToggleToolExpand, onToggleThinking, onToggleTransparency, onPageUp, onPageDown, onHome, onEnd, commandPaletteOpen, onCommandPaletteInput, approvalDialogOpen, approvalDenyReasonMode, onApprovalApprove, onApprovalAlwaysAllow, onApprovalDeny, onApprovalDenyWithReason, onApprovalCancelDenyReason, onApprovalDenyReasonInput, }) {
122
132
  const [value, setValue] = useState('');
@@ -304,6 +314,8 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
304
314
  const [contextTokens, setContextTokens] = useState(0);
305
315
  const [elapsedTime, setElapsedTime] = useState(0);
306
316
  const processingStartRef = useRef(null);
317
+ const [executionMode, setExecutionMode] = useState('idle');
318
+ const executionModeRef = useRef('idle');
307
319
  // Display toggles
308
320
  const [toolCallsExpanded, setToolCallsExpanded] = useState(false);
309
321
  const [showThinking, setShowThinking] = useState(true);
@@ -400,6 +412,203 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
400
412
  });
401
413
  }
402
414
  }, [approvalBridge, handleApprovalRequest]);
415
+ // =========================================================================
416
+ // UNIFIED EVENT HANDLER
417
+ // Consolidated handler for all agent events - prevents duplicate messages
418
+ // =========================================================================
419
+ const handleAgentEvent = useCallback((event) => {
420
+ const mode = executionModeRef.current;
421
+ if (mode === 'idle')
422
+ return; // No active execution, ignore events
423
+ // Extract subagent from event if present (not all events have it)
424
+ const eventWithSubagent = event;
425
+ const subagentPrefix = eventWithSubagent.subagent ? `[${eventWithSubagent.subagent}] ` : '';
426
+ // -------------------------------------------------------------------------
427
+ // Approving-only events (plan execution)
428
+ // -------------------------------------------------------------------------
429
+ if (mode === 'approving') {
430
+ if (event.type === 'plan.approved') {
431
+ const e = event;
432
+ addMessage('system', `[PLAN] Executing ${e.changeCount} change(s)...`);
433
+ return;
434
+ }
435
+ if (event.type === 'plan.executing') {
436
+ const e = event;
437
+ setStatus(s => ({ ...s, mode: `executing ${e.changeIndex + 1}/${e.totalChanges}` }));
438
+ return;
439
+ }
440
+ }
441
+ // -------------------------------------------------------------------------
442
+ // Shared events (both processing and approving modes)
443
+ // -------------------------------------------------------------------------
444
+ // Subagent lifecycle events
445
+ if (event.type === 'agent.spawn') {
446
+ const e = event;
447
+ addMessage('system', `[AGENT] Spawning ${e.name}: ${e.task.slice(0, 100)}${e.task.length > 100 ? '...' : ''}`);
448
+ return;
449
+ }
450
+ if (event.type === 'agent.complete') {
451
+ const e = event;
452
+ const statusText = e.success ? 'completed' : 'failed';
453
+ addMessage('system', `[AGENT] ${e.agentId} ${statusText}`);
454
+ // Show output preview if substantive
455
+ if (e.output && e.output.length > 50) {
456
+ const preview = e.output.slice(0, 300);
457
+ addMessage('system', `[AGENT OUTPUT]\n${preview}${e.output.length > 300 ? '\n...(truncated)' : ''}`);
458
+ }
459
+ return;
460
+ }
461
+ if (event.type === 'agent.error') {
462
+ const e = event;
463
+ addMessage('system', `[AGENT] ${e.agentId} error: ${e.error}`);
464
+ return;
465
+ }
466
+ if (event.type === 'agent.pending_plan') {
467
+ const e = event;
468
+ addMessage('system', `[AGENT] ${e.agentId} queued ${e.changes.length} change(s) to pending plan`);
469
+ return;
470
+ }
471
+ // Tool events
472
+ if (event.type === 'tool.start') {
473
+ const e = event;
474
+ const displayName = e.subagent ? `${e.subagent}:${e.tool}` : e.tool;
475
+ setStatus(s => ({ ...s, mode: `calling ${displayName}` }));
476
+ setToolCalls(prev => [...prev.slice(-4), {
477
+ id: `${displayName}-${Date.now()}`,
478
+ name: displayName,
479
+ args: e.args || {},
480
+ status: 'running',
481
+ startTime: new Date(),
482
+ }]);
483
+ return;
484
+ }
485
+ if (event.type === 'tool.complete') {
486
+ const e = event;
487
+ const displayName = e.subagent ? `${e.subagent}:${e.tool}` : e.tool;
488
+ const modeText = e.subagent ? `${e.subagent} thinking` : (mode === 'approving' ? 'executing plan' : 'thinking');
489
+ setStatus(s => ({ ...s, mode: modeText }));
490
+ setToolCalls(prev => prev.map(t => t.name === displayName ? {
491
+ ...t,
492
+ status: 'success',
493
+ result: e.result,
494
+ duration: t.startTime ? Date.now() - t.startTime.getTime() : undefined,
495
+ } : t));
496
+ return;
497
+ }
498
+ if (event.type === 'tool.blocked') {
499
+ const e = event;
500
+ const displayName = e.subagent ? `${e.subagent}:${e.tool}` : e.tool;
501
+ setToolCalls(prev => prev.map(t => t.name === displayName ? {
502
+ ...t,
503
+ status: 'error',
504
+ error: e.reason || 'Blocked',
505
+ } : t));
506
+ return;
507
+ }
508
+ // LLM events
509
+ if (event.type === 'llm.start') {
510
+ const e = event;
511
+ const modeText = e.subagent ? `${e.subagent} thinking` : (mode === 'approving' ? 'executing plan' : 'thinking');
512
+ setStatus(s => ({ ...s, mode: modeText, iter: s.iter + 1 }));
513
+ return;
514
+ }
515
+ if (event.type === 'llm.complete' && eventWithSubagent.subagent && showThinking) {
516
+ const e = event;
517
+ const thinking = e.response?.thinking;
518
+ if (thinking) {
519
+ const preview = thinking.length > 500 ? thinking.slice(0, 500) + '...' : thinking;
520
+ addMessage('system', `[${eventWithSubagent.subagent}] ${preview}`);
521
+ }
522
+ return;
523
+ }
524
+ // Error events
525
+ if (event.type === 'error') {
526
+ const e = event;
527
+ const prefix = e.subagent ? `[${e.subagent} ERROR]` : '[ERROR]';
528
+ const errorMsg = typeof e.error === 'string' ? e.error : e.error?.message || 'Unknown error';
529
+ addMessage('error', `${prefix} ${errorMsg}`);
530
+ return;
531
+ }
532
+ // Insight events
533
+ if (event.type === 'insight.tokens' && showThinking) {
534
+ const e = event;
535
+ addMessage('system', `${subagentPrefix}* ${e.inputTokens.toLocaleString()} in, ${e.outputTokens.toLocaleString()} out${e.cost ? ` $${e.cost.toFixed(6)}` : ''}`);
536
+ return;
537
+ }
538
+ // Resilience events
539
+ if (event.type === 'resilience.retry') {
540
+ const e = event;
541
+ addMessage('system', `[RETRY] ${e.reason} (${e.attempt}/${e.maxAttempts})`);
542
+ return;
543
+ }
544
+ if (event.type === 'resilience.recovered') {
545
+ const e = event;
546
+ addMessage('system', `[RECOVERED] ${e.reason} after ${e.attempts} attempt(s)`);
547
+ return;
548
+ }
549
+ // Subagent visibility events
550
+ if (event.type === 'subagent.iteration') {
551
+ const e = event;
552
+ setStatus(s => ({ ...s, mode: `${e.agentId} iter ${e.iteration}/${e.maxIterations}` }));
553
+ return;
554
+ }
555
+ if (event.type === 'subagent.phase') {
556
+ const e = event;
557
+ setStatus(s => ({ ...s, mode: `${e.agentId} ${e.phase}` }));
558
+ return;
559
+ }
560
+ // -------------------------------------------------------------------------
561
+ // Processing-only events (normal message submission)
562
+ // -------------------------------------------------------------------------
563
+ if (mode === 'processing') {
564
+ if (event.type === 'plan.change.queued') {
565
+ const e = event;
566
+ const summary = e.summary ? `: ${e.summary}` : '';
567
+ const prefix = e.subagent ? `[${e.subagent} PLAN]` : '[PLAN]';
568
+ addMessage('system', `${prefix} Queued ${e.tool}${summary}`);
569
+ return;
570
+ }
571
+ if (event.type === 'plan.change.complete') {
572
+ const e = event;
573
+ if (e.error) {
574
+ addMessage('system', `[PLAN ${e.changeIndex + 1}] ${e.tool} FAILED: ${e.error}`);
575
+ }
576
+ else if (e.tool === 'spawn_agent' && e.result) {
577
+ const output = typeof e.result === 'object' && e.result !== null && 'output' in e.result
578
+ ? String(e.result.output)
579
+ : String(e.result);
580
+ const preview = output.length > 800 ? output.slice(0, 800) + '\n... (truncated)' : output;
581
+ addMessage('system', `[PLAN ${e.changeIndex + 1}] ${e.tool} result:\n${preview}`);
582
+ }
583
+ else {
584
+ addMessage('system', `[PLAN ${e.changeIndex + 1}] ${e.tool} completed`);
585
+ }
586
+ return;
587
+ }
588
+ if (event.type === 'cache.hit' && showThinking) {
589
+ const e = event;
590
+ addMessage('system', `[CACHE HIT] similarity: ${(e.similarity * 100).toFixed(0)}%`);
591
+ return;
592
+ }
593
+ if (event.type === 'cache.miss' && showThinking) {
594
+ addMessage('system', `[CACHE MISS]`);
595
+ return;
596
+ }
597
+ if (event.type === 'compaction.auto') {
598
+ const e = event;
599
+ const before = (e.tokensBefore / 1000).toFixed(1);
600
+ const after = (e.tokensAfter / 1000).toFixed(1);
601
+ addMessage('system', `[COMPACT] ${before}k -> ${after}k tokens (${e.messagesCompacted} messages)`);
602
+ return;
603
+ }
604
+ if (event.type === 'compaction.warning' && showThinking) {
605
+ const e = event;
606
+ const pct = Math.round((e.currentTokens / e.threshold) * 100);
607
+ addMessage('system', `[!] Context at ${pct}% of threshold`);
608
+ return;
609
+ }
610
+ }
611
+ }, [addMessage, showThinking]);
403
612
  // Set up transparency aggregator and subscribe to agent events
404
613
  useEffect(() => {
405
614
  const aggregator = new TransparencyAggregator();
@@ -411,12 +620,13 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
411
620
  // Subscribe to agent events
412
621
  const unsubscribeAgent = agent.subscribe((event) => {
413
622
  aggregator.processEvent(event);
623
+ handleAgentEvent(event); // Unified event handler for TUI display
414
624
  });
415
625
  return () => {
416
626
  unsubscribeAggregator();
417
627
  unsubscribeAgent();
418
628
  };
419
- }, [agent]);
629
+ }, [agent, handleAgentEvent]);
420
630
  // =========================================================================
421
631
  // COMMAND HANDLER
422
632
  // =========================================================================
@@ -809,16 +1019,32 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
809
1019
  addMessage('system', 'No plan to approve.');
810
1020
  return;
811
1021
  }
812
- const count = args[0] ? parseInt(args[0], 10) : undefined;
813
- const result = await agent.approvePlan(count);
814
- if (result.success) {
815
- addMessage('system', `[OK] Executed ${result.executed} change(s)`);
1022
+ // Set execution mode - unified event handler will display events
1023
+ setIsProcessing(true);
1024
+ executionModeRef.current = 'approving';
1025
+ setExecutionMode('approving');
1026
+ setStatus(s => ({ ...s, mode: 'executing plan' }));
1027
+ try {
1028
+ const count = args[0] ? parseInt(args[0], 10) : undefined;
1029
+ const result = await agent.approvePlan(count);
1030
+ if (result.success) {
1031
+ addMessage('system', `[OK] Executed ${result.executed} change(s)`);
1032
+ }
1033
+ else {
1034
+ addMessage('system', `[!] ${result.executed} done, ${result.errors.length} errors:\n${result.errors.join('\n')}`);
1035
+ }
816
1036
  }
817
- else {
818
- addMessage('system', `[!] ${result.executed} done, ${result.errors.length} errors`);
1037
+ catch (e) {
1038
+ addMessage('error', `Plan execution failed: ${e.message}`);
819
1039
  }
820
- if (agent.getMode() === 'plan') {
821
- agent.setMode('build');
1040
+ finally {
1041
+ executionModeRef.current = 'idle';
1042
+ setExecutionMode('idle');
1043
+ setIsProcessing(false);
1044
+ setToolCalls([]);
1045
+ if (agent.getMode() === 'plan') {
1046
+ agent.setMode('build');
1047
+ }
822
1048
  setStatus(s => ({ ...s, mode: 'ready' }));
823
1049
  }
824
1050
  return;
@@ -1111,7 +1337,7 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
1111
1337
  default:
1112
1338
  addMessage('system', `Unknown: /${cmd}. Try /help`);
1113
1339
  }
1114
- }, [addMessage, exit, agent, mcpClient, lspManager, sessionStore, compactor, model, currentThemeName, currentSessionId, formatSessionsTable, saveCheckpointToStore]);
1340
+ }, [addMessage, exit, agent, mcpClient, lspManager, sessionStore, compactor, model, currentThemeName, currentSessionId, formatSessionsTable, saveCheckpointToStore, showThinking]);
1115
1341
  // =========================================================================
1116
1342
  // SUBMIT HANDLER
1117
1343
  // =========================================================================
@@ -1125,60 +1351,11 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
1125
1351
  await handleCommand(parts[0], parts.slice(1));
1126
1352
  return;
1127
1353
  }
1354
+ // Set execution mode - unified event handler will display events
1128
1355
  setIsProcessing(true);
1356
+ executionModeRef.current = 'processing';
1357
+ setExecutionMode('processing');
1129
1358
  setStatus(s => ({ ...s, mode: 'thinking' }));
1130
- const unsub = agent.subscribe((event) => {
1131
- // Check if event is from a subagent
1132
- const subagentPrefix = event.subagent ? `[${event.subagent}] ` : '';
1133
- if (event.type === 'agent.spawn') {
1134
- // A subagent is starting
1135
- addMessage('system', `[AGENT] Spawning ${event.name}: ${event.task.slice(0, 100)}${event.task.length > 100 ? '...' : ''}`);
1136
- }
1137
- else if (event.type === 'agent.complete') {
1138
- // A subagent finished
1139
- addMessage('system', `[AGENT] ${event.agentId} ${event.success ? 'completed' : 'failed'}`);
1140
- }
1141
- else if (event.type === 'tool.start') {
1142
- const displayName = event.subagent ? `${event.subagent}:${event.tool}` : event.tool;
1143
- setStatus(s => ({ ...s, mode: `calling ${displayName}` }));
1144
- setToolCalls(prev => [...prev.slice(-4), {
1145
- id: `${displayName}-${Date.now()}`,
1146
- name: displayName,
1147
- args: event.args || {},
1148
- status: 'running',
1149
- startTime: new Date(),
1150
- }]);
1151
- }
1152
- else if (event.type === 'tool.complete') {
1153
- const displayName = event.subagent ? `${event.subagent}:${event.tool}` : event.tool;
1154
- setStatus(s => ({ ...s, mode: event.subagent ? `${event.subagent} thinking` : 'thinking' }));
1155
- setToolCalls(prev => prev.map(t => t.name === displayName ? {
1156
- ...t,
1157
- status: 'success',
1158
- result: event.result,
1159
- duration: t.startTime ? Date.now() - t.startTime.getTime() : undefined,
1160
- } : t));
1161
- }
1162
- else if (event.type === 'tool.blocked') {
1163
- const displayName = event.subagent ? `${event.subagent}:${event.tool}` : event.tool;
1164
- setToolCalls(prev => prev.map(t => t.name === displayName ? {
1165
- ...t,
1166
- status: 'error',
1167
- error: event.reason || 'Blocked',
1168
- } : t));
1169
- }
1170
- else if (event.type === 'llm.start') {
1171
- setStatus(s => ({ ...s, mode: event.subagent ? `${event.subagent} thinking` : 'thinking', iter: s.iter + 1 }));
1172
- }
1173
- else if (event.type === 'insight.tokens' && showThinking) {
1174
- const e = event;
1175
- addMessage('system', `${subagentPrefix}* ${e.inputTokens.toLocaleString()} in, ${e.outputTokens.toLocaleString()} out${e.cost ? ` $${e.cost.toFixed(6)}` : ''}`);
1176
- }
1177
- else if (event.type === 'plan.change.queued') {
1178
- const summary = event.summary ? `: ${event.summary}` : '';
1179
- addMessage('system', `[PLAN] Queued ${event.tool}${summary}`);
1180
- }
1181
- });
1182
1359
  try {
1183
1360
  const result = await agent.run(trimmed);
1184
1361
  const metrics = agent.getMetrics();
@@ -1231,11 +1408,12 @@ export function TUIApp({ agent, sessionStore, mcpClient, compactor, lspManager,
1231
1408
  addMessage('error', e.message);
1232
1409
  }
1233
1410
  finally {
1234
- unsub();
1411
+ executionModeRef.current = 'idle';
1412
+ setExecutionMode('idle');
1235
1413
  setIsProcessing(false);
1236
1414
  setToolCalls([]);
1237
1415
  }
1238
- }, [addMessage, handleCommand, agent, sessionStore, saveCheckpointToStore, persistenceDebug, showThinking]);
1416
+ }, [addMessage, handleCommand, agent, sessionStore, saveCheckpointToStore, persistenceDebug]);
1239
1417
  // =========================================================================
1240
1418
  // COMMAND PALETTE ITEMS
1241
1419
  // =========================================================================