indusagi-coding-agent 0.1.22 → 0.1.24

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 (222) hide show
  1. package/CHANGELOG.md +72 -11
  2. package/README.md +2 -36
  3. package/dist/cli/args.d.ts +117 -1
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +231 -64
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/config-selector.d.ts +58 -2
  8. package/dist/cli/config-selector.d.ts.map +1 -1
  9. package/dist/cli/config-selector.js +130 -12
  10. package/dist/cli/config-selector.js.map +1 -1
  11. package/dist/cli/file-processor.d.ts +70 -2
  12. package/dist/cli/file-processor.d.ts.map +1 -1
  13. package/dist/cli/file-processor.js +240 -15
  14. package/dist/cli/file-processor.js.map +1 -1
  15. package/dist/cli/list-models.d.ts +63 -3
  16. package/dist/cli/list-models.d.ts.map +1 -1
  17. package/dist/cli/list-models.js +202 -27
  18. package/dist/cli/list-models.js.map +1 -1
  19. package/dist/cli/login-handler.d.ts +82 -8
  20. package/dist/cli/login-handler.d.ts.map +1 -1
  21. package/dist/cli/login-handler.js +410 -77
  22. package/dist/cli/login-handler.js.map +1 -1
  23. package/dist/cli/session-picker.d.ts +74 -2
  24. package/dist/cli/session-picker.d.ts.map +1 -1
  25. package/dist/cli/session-picker.js +236 -12
  26. package/dist/cli/session-picker.js.map +1 -1
  27. package/dist/core/agent-session.d.ts +214 -9
  28. package/dist/core/agent-session.d.ts.map +1 -1
  29. package/dist/core/agent-session.js +214 -9
  30. package/dist/core/agent-session.js.map +1 -1
  31. package/dist/core/bash-executor.d.ts +302 -12
  32. package/dist/core/bash-executor.d.ts.map +1 -1
  33. package/dist/core/bash-executor.js +302 -12
  34. package/dist/core/bash-executor.js.map +1 -1
  35. package/dist/core/diagnostics.d.ts +191 -0
  36. package/dist/core/diagnostics.d.ts.map +1 -1
  37. package/dist/core/diagnostics.js +142 -0
  38. package/dist/core/diagnostics.js.map +1 -1
  39. package/dist/core/event-bus.d.ts +146 -0
  40. package/dist/core/event-bus.d.ts.map +1 -1
  41. package/dist/core/event-bus.js +93 -0
  42. package/dist/core/event-bus.js.map +1 -1
  43. package/dist/core/export-html/ansi-to-html.d.ts +4 -0
  44. package/dist/core/export-html/ansi-to-html.d.ts.map +1 -1
  45. package/dist/core/export-html/ansi-to-html.js +4 -0
  46. package/dist/core/export-html/ansi-to-html.js.map +1 -1
  47. package/dist/core/export-html/index.d.ts +128 -0
  48. package/dist/core/export-html/index.d.ts.map +1 -1
  49. package/dist/core/export-html/index.js +128 -0
  50. package/dist/core/export-html/index.js.map +1 -1
  51. package/dist/core/export-html/tool-renderer.d.ts +4 -0
  52. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  53. package/dist/core/export-html/tool-renderer.js +4 -0
  54. package/dist/core/export-html/tool-renderer.js.map +1 -1
  55. package/dist/core/keybindings.d.ts +142 -0
  56. package/dist/core/keybindings.d.ts.map +1 -1
  57. package/dist/core/keybindings.js +142 -0
  58. package/dist/core/keybindings.js.map +1 -1
  59. package/dist/core/model-registry.d.ts +98 -1
  60. package/dist/core/model-registry.d.ts.map +1 -1
  61. package/dist/core/model-registry.js +98 -1
  62. package/dist/core/model-registry.js.map +1 -1
  63. package/dist/core/model-resolver.d.ts +99 -1
  64. package/dist/core/model-resolver.d.ts.map +1 -1
  65. package/dist/core/model-resolver.js +99 -1
  66. package/dist/core/model-resolver.js.map +1 -1
  67. package/dist/core/prompt-templates.js.map +1 -1
  68. package/dist/core/sdk.d.ts +1 -1
  69. package/dist/core/sdk.d.ts.map +1 -1
  70. package/dist/core/sdk.js +0 -2
  71. package/dist/core/sdk.js.map +1 -1
  72. package/dist/core/session-manager.d.ts +127 -0
  73. package/dist/core/session-manager.d.ts.map +1 -1
  74. package/dist/core/session-manager.js +125 -0
  75. package/dist/core/session-manager.js.map +1 -1
  76. package/dist/core/skills.js.map +1 -1
  77. package/dist/core/subagents.js.map +1 -1
  78. package/dist/core/tools/bash.d.ts +391 -11
  79. package/dist/core/tools/bash.d.ts.map +1 -1
  80. package/dist/core/tools/bash.js +269 -2
  81. package/dist/core/tools/bash.js.map +1 -1
  82. package/dist/core/tools/edit.d.ts +284 -6
  83. package/dist/core/tools/edit.d.ts.map +1 -1
  84. package/dist/core/tools/edit.js +238 -0
  85. package/dist/core/tools/edit.js.map +1 -1
  86. package/dist/core/tools/find.d.ts +169 -5
  87. package/dist/core/tools/find.d.ts.map +1 -1
  88. package/dist/core/tools/find.js +136 -0
  89. package/dist/core/tools/find.js.map +1 -1
  90. package/dist/core/tools/grep.d.ts +285 -5
  91. package/dist/core/tools/grep.d.ts.map +1 -1
  92. package/dist/core/tools/grep.js +247 -0
  93. package/dist/core/tools/grep.js.map +1 -1
  94. package/dist/core/tools/index.d.ts +0 -18
  95. package/dist/core/tools/index.d.ts.map +1 -1
  96. package/dist/core/tools/index.js +1 -23
  97. package/dist/core/tools/index.js.map +1 -1
  98. package/dist/core/tools/ls.d.ts +6 -0
  99. package/dist/core/tools/ls.d.ts.map +1 -1
  100. package/dist/core/tools/ls.js +6 -0
  101. package/dist/core/tools/ls.js.map +1 -1
  102. package/dist/core/tools/read.d.ts +308 -7
  103. package/dist/core/tools/read.d.ts.map +1 -1
  104. package/dist/core/tools/read.js +231 -0
  105. package/dist/core/tools/read.js.map +1 -1
  106. package/dist/core/tools/webfetch.d.ts +118 -3
  107. package/dist/core/tools/webfetch.d.ts.map +1 -1
  108. package/dist/core/tools/webfetch.js +118 -3
  109. package/dist/core/tools/webfetch.js.map +1 -1
  110. package/dist/core/tools/websearch.d.ts +130 -3
  111. package/dist/core/tools/websearch.d.ts.map +1 -1
  112. package/dist/core/tools/websearch.js +130 -3
  113. package/dist/core/tools/websearch.js.map +1 -1
  114. package/dist/core/tools/write.d.ts +251 -5
  115. package/dist/core/tools/write.d.ts.map +1 -1
  116. package/dist/core/tools/write.js +210 -0
  117. package/dist/core/tools/write.js.map +1 -1
  118. package/dist/modes/interactive/components/assistant-message.d.ts +164 -1
  119. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  120. package/dist/modes/interactive/components/assistant-message.js +164 -1
  121. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  122. package/dist/modes/interactive/components/bash-execution.d.ts +297 -1
  123. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  124. package/dist/modes/interactive/components/bash-execution.js +297 -1
  125. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  126. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  127. package/dist/modes/interactive/components/tool-execution.js +251 -1
  128. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  129. package/dist/modes/interactive/components/user-message.d.ts +186 -1
  130. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  131. package/dist/modes/interactive/components/user-message.js +186 -1
  132. package/dist/modes/interactive/components/user-message.js.map +1 -1
  133. package/dist/modes/interactive/interactive-mode.d.ts +1567 -13
  134. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  135. package/dist/modes/interactive/interactive-mode.js +1567 -13
  136. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  137. package/dist/modes/interactive/theme/theme.d.ts +422 -0
  138. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  139. package/dist/modes/interactive/theme/theme.js +422 -0
  140. package/dist/modes/interactive/theme/theme.js.map +1 -1
  141. package/dist/modes/print-mode.d.ts +538 -5
  142. package/dist/modes/print-mode.d.ts.map +1 -1
  143. package/dist/modes/print-mode.js +538 -5
  144. package/dist/modes/print-mode.js.map +1 -1
  145. package/dist/modes/rpc/rpc-client.d.ts +921 -8
  146. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  147. package/dist/modes/rpc/rpc-client.js +921 -8
  148. package/dist/modes/rpc/rpc-client.js.map +1 -1
  149. package/dist/modes/rpc/rpc-mode.d.ts +802 -9
  150. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  151. package/dist/modes/rpc/rpc-mode.js +802 -9
  152. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  153. package/dist/modes/rpc/rpc-types.d.ts +356 -3
  154. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  155. package/dist/modes/rpc/rpc-types.js +356 -3
  156. package/dist/modes/rpc/rpc-types.js.map +1 -1
  157. package/dist/modes/shared.d.ts +386 -0
  158. package/dist/modes/shared.d.ts.map +1 -0
  159. package/dist/modes/shared.js +543 -0
  160. package/dist/modes/shared.js.map +1 -0
  161. package/dist/utils/array.d.ts +389 -0
  162. package/dist/utils/array.d.ts.map +1 -0
  163. package/dist/utils/array.js +585 -0
  164. package/dist/utils/array.js.map +1 -0
  165. package/dist/utils/color-formatter.d.ts +318 -0
  166. package/dist/utils/color-formatter.d.ts.map +1 -0
  167. package/dist/utils/color-formatter.js +442 -0
  168. package/dist/utils/color-formatter.js.map +1 -0
  169. package/dist/utils/data-transformer.d.ts +326 -0
  170. package/dist/utils/data-transformer.d.ts.map +1 -0
  171. package/dist/utils/data-transformer.js +512 -0
  172. package/dist/utils/data-transformer.js.map +1 -0
  173. package/dist/utils/date-formatter.d.ts +281 -0
  174. package/dist/utils/date-formatter.d.ts.map +1 -0
  175. package/dist/utils/date-formatter.js +503 -0
  176. package/dist/utils/date-formatter.js.map +1 -0
  177. package/dist/utils/error-handler.d.ts +541 -0
  178. package/dist/utils/error-handler.d.ts.map +1 -0
  179. package/dist/utils/error-handler.js +726 -0
  180. package/dist/utils/error-handler.js.map +1 -0
  181. package/dist/utils/file-operations.d.ts +297 -0
  182. package/dist/utils/file-operations.d.ts.map +1 -0
  183. package/dist/utils/file-operations.js +505 -0
  184. package/dist/utils/file-operations.js.map +1 -0
  185. package/dist/utils/frontmatter.d.ts +268 -6
  186. package/dist/utils/frontmatter.d.ts.map +1 -1
  187. package/dist/utils/frontmatter.js +500 -21
  188. package/dist/utils/frontmatter.js.map +1 -1
  189. package/dist/utils/json-formatter.d.ts +259 -0
  190. package/dist/utils/json-formatter.d.ts.map +1 -0
  191. package/dist/utils/json-formatter.js +517 -0
  192. package/dist/utils/json-formatter.js.map +1 -0
  193. package/dist/utils/logger.d.ts +176 -0
  194. package/dist/utils/logger.d.ts.map +1 -0
  195. package/dist/utils/logger.js +346 -0
  196. package/dist/utils/logger.js.map +1 -0
  197. package/dist/utils/markdown-formatter.d.ts +211 -0
  198. package/dist/utils/markdown-formatter.d.ts.map +1 -0
  199. package/dist/utils/markdown-formatter.js +482 -0
  200. package/dist/utils/markdown-formatter.js.map +1 -0
  201. package/dist/utils/path-validator.d.ts +603 -0
  202. package/dist/utils/path-validator.d.ts.map +1 -0
  203. package/dist/utils/path-validator.js +870 -0
  204. package/dist/utils/path-validator.js.map +1 -0
  205. package/dist/utils/string-formatter.d.ts +609 -0
  206. package/dist/utils/string-formatter.d.ts.map +1 -0
  207. package/dist/utils/string-formatter.js +806 -0
  208. package/dist/utils/string-formatter.js.map +1 -0
  209. package/dist/utils/type-guards.d.ts +629 -0
  210. package/dist/utils/type-guards.d.ts.map +1 -0
  211. package/dist/utils/type-guards.js +662 -0
  212. package/dist/utils/type-guards.js.map +1 -0
  213. package/docs/COMPLETE-GUIDE.md +300 -0
  214. package/docs/MODES-ARCHITECTURE.md +565 -0
  215. package/docs/PRINT-MODE-GUIDE.md +456 -0
  216. package/docs/README.md +1 -2
  217. package/docs/RPC-GUIDE.md +705 -0
  218. package/docs/UTILS-IMPLEMENTATION-SUMMARY.md +647 -0
  219. package/docs/UTILS-MODULE-OVERVIEW.md +1480 -0
  220. package/docs/UTILS-QA-CHECKLIST.md +1061 -0
  221. package/docs/UTILS-USAGE-GUIDE.md +1419 -0
  222. package/package.json +3 -3
@@ -1,6 +1,167 @@
1
1
  /**
2
- * Interactive mode for the coding agent.
3
- * Handles TUI rendering and user interaction, delegating business logic to AgentSession.
2
+ * Interactive Mode - Terminal-based UI for interactive agent use
3
+ *
4
+ * ============================================================================
5
+ * PURPOSE
6
+ * ============================================================================
7
+ *
8
+ * Provides a full-featured terminal UI for interactive use of the coding agent.
9
+ * Handles rendering, keyboard input, session management, and real-time updates.
10
+ * This is the primary mode for direct user interaction with the agent.
11
+ *
12
+ * ============================================================================
13
+ * ARCHITECTURE
14
+ * ============================================================================
15
+ *
16
+ * Initialization Phase:
17
+ * - Create AgentSession with language model and tools
18
+ * - Load theme system and keyboard bindings
19
+ * - Initialize TUI (terminal user interface) library
20
+ * - Set up extension system with UI context
21
+ * - Load autocomplete provider with fd tool for file paths
22
+ *
23
+ * Rendering Phase:
24
+ * - Terminal layout: Header (logo/changelog) → Chat → Pending → Status → Widgets → Editor → Footer
25
+ * - Live rendering of agent messages with syntax highlighting
26
+ * - Real-time tool execution display with collapsible output
27
+ * - Thinking block display (can be toggled via settings)
28
+ * - Auto-scrolling to latest message
29
+ *
30
+ * Input Phase:
31
+ * - Keyboard event handling via TUI key system
32
+ * - Command parsing (/command format)
33
+ * - Slash command autocomplete with fuzzy filtering
34
+ * - File path completion via fd tool
35
+ * - Multi-line editor support with Ctrl+J to continue
36
+ *
37
+ * State Management:
38
+ * - Subscribe to AgentSession events (messages, tools, compaction)
39
+ * - Update display on each event
40
+ * - Maintain streaming component state for incremental rendering
41
+ * - Track tool execution components for expansion control
42
+ *
43
+ * ============================================================================
44
+ * DATA FLOW
45
+ * ============================================================================
46
+ *
47
+ * User Input Flow:
48
+ * User Input (keyboard)
49
+ * ↓
50
+ * TUI Key Handler (in setupKeyHandlers)
51
+ * ↓
52
+ * Command Parser (parseSlashCommand or plain text)
53
+ * ↓
54
+ * Command Handler or session.prompt()
55
+ * ↓
56
+ * AgentSession.prompt() executes agent loop
57
+ * ↓
58
+ * Agent emits AgentSessionEvent (message, tool, etc.)
59
+ * ↓
60
+ * Event Subscriber in InteractiveMode (subscribeToAgent)
61
+ * ↓
62
+ * Display Update (render message, tool, status)
63
+ * ↓
64
+ * TUI Invalidate → requestRender()
65
+ * ↓
66
+ * Terminal Output Rendered
67
+ *
68
+ * Event Processing Flow:
69
+ * agent_start → Show loading animation
70
+ * message_streamed → Stream text to AssistantMessageComponent
71
+ * tool_call → Create ToolExecutionComponent, show in pending area
72
+ * tool_output → Update tool component with result
73
+ * message_complete → Move message to chat, hide loading
74
+ * compaction_start → Show compaction loader
75
+ * compaction_complete → Update with summary message
76
+ *
77
+ * ============================================================================
78
+ * KEY COMPONENTS
79
+ * ============================================================================
80
+ *
81
+ * Renderer:
82
+ * - TUI: Core terminal rendering library (from indusagi/tui)
83
+ * - Components: Markdown, Text, Container, ProcessTerminal, etc.
84
+ * - Handles ANSI codes for colors, styles, and terminal control
85
+ * - Manages viewport size and automatic text wrapping
86
+ *
87
+ * InputHandler:
88
+ * - CustomEditor: Multi-line editor with autocomplete
89
+ * - KeybindingsManager: Keyboard shortcut configuration
90
+ * - Autocomplete: Slash commands, file paths, templates
91
+ * - Command Parser: Processes /settings, /model, /fork, etc.
92
+ *
93
+ * DisplayManager:
94
+ * - InteractiveMode class: Orchestrates all display updates
95
+ * - Container hierarchy: ChatContainer, PendingMessagesContainer, StatusContainer
96
+ * - Session display: Shows messages, tools, thinking blocks
97
+ * - Component tracking: streamingComponent, pendingTools, etc.
98
+ *
99
+ * ThemeManager:
100
+ * - Theme system: Colors, styles, layout options
101
+ * - EditorTheme: Syntax highlighting for code
102
+ * - MarkdownTheme: Rendering options for markdown
103
+ * - Theme switching: Hot reload on theme file changes
104
+ * - Custom themes: Load from .indusagi/themes/*.json
105
+ *
106
+ * Extension System:
107
+ * - ExtensionRunner: Manages registered extensions
108
+ * - ExtensionUIContext: TUI-based UI for extensions
109
+ * - Extension shortcuts: Keyboard shortcuts from extensions
110
+ * - Extension widgets: Custom UI above/below editor
111
+ * - Extension commands: Custom slash commands
112
+ *
113
+ * SessionManager:
114
+ * - Session state tracking: Model, messages, tools
115
+ * - Session persistence: Save/load from disk
116
+ * - Branching: Create branches from previous messages
117
+ * - Tree navigation: Switch between branches
118
+ *
119
+ * ============================================================================
120
+ * KEY FEATURES
121
+ * ============================================================================
122
+ *
123
+ * Real-time Streaming:
124
+ * - Stream agent responses token-by-token
125
+ * - Show tool execution in real-time
126
+ * - Display thinking blocks (if enabled)
127
+ * - Syntax highlighting for code blocks
128
+ *
129
+ * Interactive Commands:
130
+ * - /settings: Open settings menu
131
+ * - /model: Select language model
132
+ * - /fork: Create branch from previous message
133
+ * - /tree: Navigate session tree
134
+ * - /export: Export to HTML
135
+ * - /compact: Manually compact session
136
+ * - /new: Start new session
137
+ *
138
+ * Session Management:
139
+ * - Save/load sessions from disk
140
+ * - Branch creation and navigation
141
+ * - Automatic context compaction
142
+ * - Session switching
143
+ *
144
+ * Customization:
145
+ * - Custom themes
146
+ * - Keyboard bindings
147
+ * - Settings (quiet startup, collapse changelog, etc.)
148
+ * - Editor options (padding, tab width, etc.)
149
+ *
150
+ * ============================================================================
151
+ * BASED ON INDUSAGI VENDOR
152
+ * ============================================================================
153
+ *
154
+ * This interactive mode implementation is based on indusagi, a terminal UI
155
+ * toolkit and agent framework maintained at:
156
+ * https://github.com/varunisrani/indusagi-ts
157
+ *
158
+ * Vendor packages used:
159
+ * - indusagi/tui: Terminal UI components and rendering
160
+ * - indusagi/ai: AI model interfaces and chat protocol
161
+ * - indusagi/agent: Core agent session and event system
162
+ *
163
+ * Attribution: Builds on proven TUI patterns and architectural decisions
164
+ * from the original indusagi implementation.
4
165
  */
5
166
  import * as crypto from "node:crypto";
6
167
  import * as fs from "node:fs";
@@ -52,6 +213,357 @@ import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMar
52
213
  function isExpandable(obj) {
53
214
  return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
54
215
  }
216
+ /**
217
+ * ============================================================================
218
+ * INTERACTIVE MODE - RENDERING ORCHESTRATION & PIPELINE
219
+ * ============================================================================
220
+ *
221
+ * COMPREHENSIVE RENDERING DOCUMENTATION:
222
+ * ============================================================================
223
+ *
224
+ * This section documents the complete rendering pipeline from session state
225
+ * to terminal output. The InteractiveMode class manages the TUI layout,
226
+ * message rendering, and real-time updates for the interactive agent interface.
227
+ *
228
+ * RENDERING PIPELINE OVERVIEW:
229
+ * ============================================================================
230
+ * End-to-End Flow (from user input to terminal display):
231
+ *
232
+ * User Input (keyboard event)
233
+ * ↓
234
+ * TUI key handler → setupKeyHandlers()
235
+ * ↓
236
+ * Command parser (parseSlashCommand or plaintext)
237
+ * ↓
238
+ * Handler: session.prompt(text) or special command
239
+ * ↓
240
+ * AgentSession processes (calls LLM, tools, etc.)
241
+ * ↓
242
+ * Agent emits events: message_streamed, tool_output, etc.
243
+ * ↓
244
+ * Event handler: subscribeToAgent() listeners
245
+ * ↓
246
+ * Display update: addMessageToChat() or component.updateResult()
247
+ * ↓
248
+ * TUI invalidate() → ui.requestRender()
249
+ * ↓
250
+ * TUI render() → compute layout (width, height, wrapping)
251
+ * ↓
252
+ * Terminal output: ANSI codes to terminal device
253
+ * ↓
254
+ * User sees updated chat with new message
255
+ *
256
+ * LAYOUT STRUCTURE:
257
+ * ============================================================================
258
+ * Terminal Layout (top to bottom):
259
+ *
260
+ * ┌─────────────────────────────────────────────────────┐
261
+ * │ [Logo/Changelog] (conditionally shown at startup) │ 1-10 lines
262
+ * ├─────────────────────────────────────────────────────┤
263
+ * │ │
264
+ * │ Chat Messages (scrollable, grows downward) │ N lines
265
+ * │ ├─ User message (background colored) │
266
+ * │ ├─ Assistant message │
267
+ * │ ├─ Tool execution (pending or complete) │
268
+ * │ └─ ...more messages... │
269
+ * │ │
270
+ * ├─────────────────────────────────────────────────────┤
271
+ * │ Pending Messages (tools being executed) │ 1-20 lines
272
+ * │ ├─ Tool call (pending background) │
273
+ * │ └─ ...more pending tools... │
274
+ * ├─────────────────────────────────────────────────────┤
275
+ * │ Status Line (operation status) │ 1 line
276
+ * │ "Thinking..." or "Reading files..." or "Complete" │
277
+ * ├─────────────────────────────────────────────────────┤
278
+ * │ [Widgets] (optional, from extensions) │ 0-5 lines
279
+ * ├─────────────────────────────────────────────────────┤
280
+ * │ [Editor] (multi-line input) │ 3-10 lines
281
+ * │ > User typing here... │
282
+ * │ > Can span multiple lines │
283
+ * ├─────────────────────────────────────────────────────┤
284
+ * │ [Footer] (status, model, etc.) │ 1 line
285
+ * │ Model: GPT-4 | Tokens: 4,251 | Compaction: ... │
286
+ * └─────────────────────────────────────────────────────┘
287
+ *
288
+ * Container Hierarchy:
289
+ * - mainContainer: Root container for entire UI
290
+ * ├── logoContainer: Logo/changelog (conditional)
291
+ * ├── chatContainer: All completed messages
292
+ * ├── pendingMessagesContainer: Tools being executed
293
+ * ├── statusContainer: Status line
294
+ * ├── widgetsContainer: Extension widgets
295
+ * ├── editorContainer: Multi-line input
296
+ * └── footerComponent: Status bar at bottom
297
+ *
298
+ * RENDERING MODES:
299
+ * ============================================================================
300
+ *
301
+ * 1. INITIAL LOAD (renderInitialMessages):
302
+ * Purpose: Display all messages when session loads
303
+ * Process:
304
+ * a. Session manager builds aligned message context
305
+ * b. Iterate through all messages (oldest to newest)
306
+ * c. For each assistant message:
307
+ * - Create AssistantMessageComponent
308
+ * - For each tool call in message:
309
+ * - Create ToolExecutionComponent (no result yet)
310
+ * - Render to chatContainer
311
+ * d. For each tool result message:
312
+ * - Find matching pending tool
313
+ * - Call component.updateResult()
314
+ * - Remove from pending map
315
+ * e. Update footer (token count, context info)
316
+ * f. Add user messages to editor history (for Up arrow)
317
+ * g. Call ui.requestRender()
318
+ *
319
+ * 2. STREAMING UPDATES (handleEvent):
320
+ * Purpose: Update display as agent processes real-time
321
+ * Process:
322
+ * a. Agent emits message_streamed event
323
+ * b. Extract message from event.message
324
+ * c. Create/update component:
325
+ * - First event: Create AssistantMessageComponent
326
+ * - Subsequent events: Call component.updateContent()
327
+ * d. Add to chatContainer if first event
328
+ * e. Update footer status (show operation in progress)
329
+ * f. TUI invalidate() and requestRender()
330
+ * Optimization:
331
+ * - Streaming component cached (one per assistant message)
332
+ * - Only call updateContent() on new content (not full rebuild)
333
+ * - Markdown rendering is lazy-cached by TUI library
334
+ *
335
+ * 3. TOOL EXECUTION (handleEvent + renderSessionContext):
336
+ * Purpose: Show tool calls and their results
337
+ * Process:
338
+ * a. Agent emits message_update (tool call started)
339
+ * b. Render tool call with pending background
340
+ * c. Tool executes (could take seconds/minutes)
341
+ * d. Agent emits tool_output event
342
+ * e. Find tool component in pendingTools map
343
+ * f. Call component.updateResult()
344
+ * g. Pending background changes to success/error
345
+ * h. Remove from pendingTools (completed)
346
+ * i. User can expand/collapse output via toolOutputExpanded setting
347
+ *
348
+ * 4. REAL-TIME BASH (handleBashCommand):
349
+ * Purpose: Stream bash output line-by-line
350
+ * Process:
351
+ * a. User types !! command
352
+ * b. Create BashExecutionComponent
353
+ * c. Start bash execution (async)
354
+ * d. Agent streams stdout chunks
355
+ * e. For each chunk:
356
+ * - Call component.appendOutput()
357
+ * - Component appends to outputLines
358
+ * - Call updateDisplay() and requestRender()
359
+ * f. When done, call component.setComplete()
360
+ * g. Show exit code or cancellation message
361
+ *
362
+ * 5. REBUILD (rebuildChatFromMessages):
363
+ * Purpose: Clear and re-render after settings change or recovery
364
+ * Process:
365
+ * a. Clear chatContainer
366
+ * b. Call renderInitialMessages()
367
+ * c. All messages re-created with new settings
368
+ * d. Useful for: thinking block toggle, theme change, etc.
369
+ *
370
+ * STYLING & COLOR SCHEME:
371
+ * ============================================================================
372
+ * Message Type Colors:
373
+ * - User message: userMessageBg + userMessageText (background + text)
374
+ * - Assistant: Normal markdown colors (no background)
375
+ * - Tool pending: toolPendingBg (neutral during execution)
376
+ * - Tool success: toolSuccessBg (green tint after success)
377
+ * - Tool error: toolErrorBg (red tint after error)
378
+ * - Bash: bashMode color (border + command)
379
+ * - Error: theme.fg("error", text) (red)
380
+ * - Warning: theme.fg("warning", text) (yellow)
381
+ * - Muted: theme.fg("muted", text) (dim/gray)
382
+ *
383
+ * Code Highlighting:
384
+ * - Markdown: Full markdown with syntax-highlighted code blocks
385
+ * - Code inline: Monospace with color
386
+ * - Code block: Full syntax highlighting via highlight.js
387
+ * - Languages detected from file extensions
388
+ * - Theme provides editor theme for highlighting
389
+ *
390
+ * Spacing & Layout:
391
+ * - Line height: 1 logical line per message (no double spacing)
392
+ * - Margins: Spacer(1) before each message type
393
+ * - Padding: 1 char left/right within backgrounds
394
+ * - Terminal width: Auto-wrap text to terminal width
395
+ * - Viewport: Scrollable (auto-scroll to bottom on new message)
396
+ *
397
+ * PERFORMANCE CONSIDERATIONS:
398
+ * ============================================================================
399
+ *
400
+ * Streaming Performance:
401
+ * - Streaming component cached: Only one active at a time
402
+ * - Markdown incremental: TUI library caches rendering
403
+ * - Invalidate efficient: Only re-renders changed container
404
+ * - Suitable for: 100+ stream updates without lag
405
+ *
406
+ * Large Conversations:
407
+ * - Tool caching: pendingTools map (fast lookup by ID)
408
+ * - Lazy rendering: Messages only rendered when visible
409
+ * - No 1000+ message overhead with proper viewport management
410
+ * - Compaction system: Auto-summarizes old messages to save memory
411
+ *
412
+ * Image Handling:
413
+ * - Async conversion: Non-blocking PNG conversion for Kitty protocol
414
+ * - Lazy loading: Images loaded when result arrives
415
+ * - Format detection: MIME type checked, converted if needed
416
+ * - Display control: showImages setting filters images
417
+ *
418
+ * Diff Computation:
419
+ * - Async preview: Edit diff computed before tool runs
420
+ * - Result override: Post-execution diff takes priority
421
+ * - Caching: Results cached to avoid re-computation
422
+ * - Race handling: File modifications between preview/execution
423
+ *
424
+ * INTERACTIVE FEATURES:
425
+ * ============================================================================
426
+ *
427
+ * Keyboard Controls:
428
+ * - Enter: Send message
429
+ * - Ctrl-J: Continue multi-line message
430
+ * - Arrow Up: Recall previous message from history
431
+ * - /settings: Open settings dialog
432
+ * - /model: Select language model
433
+ * - Tab: Autocomplete
434
+ * - Ctrl-C: Cancel current operation or exit
435
+ *
436
+ * Expandable Elements:
437
+ * - Tool output: Expand to see full output (toolOutputExpanded setting)
438
+ * - Code blocks: Expand long code to show all lines
439
+ * - Tool list: Expand to show all matching tools
440
+ * - Bash output: Expand from preview (20 lines) to full
441
+ *
442
+ * Real-time Status:
443
+ * - Status line: Shows current operation (thinking, reading, writing)
444
+ * - Footer: Token count, model name, session info
445
+ * - Loader: Animated spinner for long operations
446
+ * - Progress: Some operations show progress indicator
447
+ *
448
+ * Session Management:
449
+ * - Fork: Create branch from previous message
450
+ * - Tree: Navigate between branches
451
+ * - Save: Auto-save to .indusagi/sessions/
452
+ * - Resume: Load previous session on startup
453
+ *
454
+ * EXAMPLE RENDERING SEQUENCES:
455
+ * ============================================================================
456
+ *
457
+ * Sequence 1: User Prompt → Assistant Response
458
+ * ─────────────────────────────────────────────
459
+ *
460
+ * 1. User types "read src/main.ts" and presses Enter
461
+ * 2. CustomEditor captures text and clears
462
+ * 3. session.prompt(text) called
463
+ * 4. Agent starts processing
464
+ * 5. Agent emits message_streamed with assistant message (empty)
465
+ * → CreateAssistantMessageComponent, add to chatContainer
466
+ * → Show status "Thinking..."
467
+ * 6. Agent streams text tokens "I'll read the file..."
468
+ * → component.updateContent(), add text to message
469
+ * → invalidate() and requestRender()
470
+ * 7. Agent detects tool call (read)
471
+ * → Emit message_update with tool call
472
+ * → CreateToolExecutionComponent, add to chatContainer
473
+ * → pendingTools["id"] = component
474
+ * 8. Tool executes on server
475
+ * 9. Agent emits tool_output with result
476
+ * → Find component in pendingTools["id"]
477
+ * → component.updateResult(result)
478
+ * → Remove from pendingTools
479
+ * → Background changes from pending to success
480
+ * 10. Agent completes
481
+ * → Hide status line
482
+ * → Show footer with token count
483
+ * 11. Entire sequence rendered to terminal
484
+ *
485
+ * Sequence 2: Real-time Bash Output
486
+ * ──────────────────────────────────
487
+ *
488
+ * 1. User types "!!ls -la /src" and presses Enter
489
+ * 2. handleBashCommand() parses and creates BashExecutionComponent
490
+ * 3. Component shows header and loader
491
+ * 4. Shell command starts (async)
492
+ * 5. Shell outputs "total 128\ndrwx..." chunk 1
493
+ * → component.appendOutput(chunk)
494
+ * → outputLines = ["total 128", "drwx..."]
495
+ * → updateDisplay() and requestRender()
496
+ * 6. Shell outputs "-rw-r--..." chunk 2
497
+ * → component.appendOutput(chunk)
498
+ * → outputLines += ["-rw-r--..."]
499
+ * → updateDisplay() and requestRender()
500
+ * 7. Shell outputs "(exit 0)" and completes
501
+ * → component.setComplete(0, false, ...)
502
+ * → Loader stops, exit code shown
503
+ * → updateDisplay() and requestRender()
504
+ * 8. Final output rendered to terminal
505
+ *
506
+ * TERMINAL DIMENSIONS & WRAPPING:
507
+ * ============================================================================
508
+ *
509
+ * Width Handling:
510
+ * - Terminal width from ui.terminal.columns
511
+ * - TUI library auto-wraps text at this width
512
+ * - Code blocks may overflow (user can scroll horizontally)
513
+ * - Table rendering respects width (may abbreviate cells)
514
+ *
515
+ * Height Handling:
516
+ * - Terminal height from ui.terminal.rows
517
+ * - mainContainer grows as needed (scrollable)
518
+ * - Chat area expands to fill available space
519
+ * - Footer always at bottom (fixed)
520
+ * - Editor always above footer (fixed)
521
+ *
522
+ * Dynamic Resizing:
523
+ * - TUI detects terminal resize (SIGWINCH)
524
+ * - All components re-render with new dimensions
525
+ * - invalidate() called on all containers
526
+ * - Visual line truncation recached (width-aware)
527
+ * - Auto-scroll adjusted to show latest message
528
+ *
529
+ * Text Truncation:
530
+ * - Long lines: Wrapped automatically by TUI
531
+ * - Visual truncation: truncateToVisualLines() respects width
532
+ * - Ellipsis: "..." used to indicate truncation
533
+ * - Hints: Show "(to expand)" for cut-off content
534
+ *
535
+ * INTEGRATION WITH EXTENSIONS:
536
+ * ============================================================================
537
+ *
538
+ * Custom Renderers:
539
+ * - Extension provides renderCall() and renderResult()
540
+ * - Called from ToolExecutionComponent.updateDisplay()
541
+ * - Custom components rendered within tool box
542
+ * - Falls back to built-in rendering if custom fails
543
+ *
544
+ * Custom Commands:
545
+ * - Extension registers slash command handler
546
+ * - Command handler called from InteractiveMode.handleCommand()
547
+ * - Can show dialogs, update display, etc.
548
+ * - Can access session and TUI for rendering
549
+ *
550
+ * Widgets:
551
+ * - Extension provides widget component
552
+ * - Rendered in widgetsContainer above editor
553
+ * - Can show status, buttons, controls, etc.
554
+ * - Multiple widgets stack vertically
555
+ *
556
+ * SEE ALSO:
557
+ * ============================================================================
558
+ * - AssistantMessageComponent: Text/thinking rendering
559
+ * - UserMessageComponent: User prompt rendering
560
+ * - ToolExecutionComponent: Tool call/result rendering
561
+ * - BashExecutionComponent: Real-time bash output
562
+ * - renderInitialMessages(): Full session rendering
563
+ * - renderSessionContext(): Message-by-message rendering
564
+ * - handleEvent(): Real-time event processing
565
+ * - subscribeToAgent(): Event subscription setup
566
+ */
55
567
  export class InteractiveMode {
56
568
  // Convenience accessors
57
569
  get agent() {
@@ -114,8 +626,47 @@ export class InteractiveMode {
114
626
  // Custom header from extension (undefined = use built-in header)
115
627
  this.customHeader = undefined;
116
628
  /**
117
- * Gracefully shutdown the agent.
118
- * Emits shutdown event to extensions, then exits.
629
+ * Gracefully shutdown the interactive mode and exit.
630
+ *
631
+ * PURPOSE:
632
+ * Clean shutdown sequence. Gives extensions opportunity to clean up,
633
+ * waits for pending renders, then exits the process.
634
+ *
635
+ * PROCESS:
636
+ * 1. Check if already shutting down (prevent double shutdown)
637
+ * 2. Emit session_shutdown event to extensions
638
+ * - Extensions can clean up resources, save state, etc.
639
+ * - Waits for handlers to complete
640
+ * 3. Wait one tick for pending renders to complete
641
+ * 4. Call stop() to clean up UI resources:
642
+ * a. Stop loading animation
643
+ * b. Dispose footer
644
+ * c. Unsubscribe from agent events
645
+ * d. Stop TUI (restore terminal)
646
+ * 5. Exit process with code 0
647
+ *
648
+ * TRIGGERS:
649
+ * - User presses Ctrl+D (when editor empty)
650
+ * - User presses Ctrl+C twice within 500ms
651
+ * - User enters /quit or /exit
652
+ * - Extension calls shutdown() via context
653
+ *
654
+ * PARAMETERS:
655
+ * None
656
+ *
657
+ * RETURNS:
658
+ * Promise<void> - never resolves (process.exit is called)
659
+ *
660
+ * NOTES:
661
+ * - Sets this.isShuttingDown flag to prevent double-shutdown
662
+ * - Emits session_shutdown event (not awaited if no handlers)
663
+ * - Extensions can catch this event with .on("session_shutdown")
664
+ * - Clean exits prevent terminal corruption
665
+ * - TUI stop() restores cursor and clear screen
666
+ *
667
+ * SEE ALSO:
668
+ * - stop() which does actual cleanup
669
+ * - checkShutdownRequested() which triggers shutdown on agent_end
119
670
  */
120
671
  this.isShuttingDown = false;
121
672
  this.session = session;
@@ -218,6 +769,44 @@ export class InteractiveMode {
218
769
  rebuildAutocomplete() {
219
770
  this.setupAutocomplete(this.fdPath);
220
771
  }
772
+ /**
773
+ * Initialize the interactive mode UI and extensions.
774
+ *
775
+ * PURPOSE:
776
+ * Sets up the terminal UI, keyboard handlers, event subscriptions, and extension system.
777
+ * This prepares the interactive mode for user interaction. Must be called before run().
778
+ *
779
+ * PROCESS:
780
+ * 1. Load changelog entries and check for initial messages
781
+ * 2. Ensure fd tool is available for file path autocomplete
782
+ * 3. Build autocomplete provider (slash commands, templates, extensions, skills)
783
+ * 4. Initialize TUI with header (logo, keybinding hints, changelog)
784
+ * 5. Add layout components (chat, pending, status, widgets, editor, footer)
785
+ * 6. Set up keyboard handlers (key bindings, editor events)
786
+ * 7. Start the TUI (terminal raw mode, enable mouse, etc.)
787
+ * 8. Initialize extension system with TUI-based UI context
788
+ * 9. Subscribe to agent events (messages, tools, compaction, retry)
789
+ * 10. Set up theme file watcher for hot reload
790
+ * 11. Set up git branch watcher for footer
791
+ * 12. Initialize available provider count for display
792
+ *
793
+ * PARAMETERS:
794
+ * None - uses constructor options for initial messages and warnings
795
+ *
796
+ * RETURNS:
797
+ * Promise<void> - resolves when initialization is complete
798
+ *
799
+ * STATE CHANGES:
800
+ * - Sets isInitialized flag to true
801
+ * - Starts TUI (terminal is in raw mode after this)
802
+ * - Sets terminal title with session name
803
+ * - Subscribes to agent events and theme changes
804
+ *
805
+ * NOTES:
806
+ * - Must be idempotent (calling twice does nothing on second call)
807
+ * - Extension system is fully initialized here
808
+ * - Changelog is only shown for new sessions, not resumed ones
809
+ */
221
810
  async init() {
222
811
  if (this.isInitialized)
223
812
  return;
@@ -337,8 +926,45 @@ export class InteractiveMode {
337
926
  }
338
927
  }
339
928
  /**
340
- * Run the interactive mode. This is the main entry point.
341
- * Initializes the UI, shows warnings, processes initial messages, and starts the interactive loop.
929
+ * Run the interactive mode main loop.
930
+ *
931
+ * PURPOSE:
932
+ * Main entry point for interactive mode. Initializes UI, displays startup info,
933
+ * processes initial messages, and enters the interactive user input loop.
934
+ * Never returns under normal operation (runs until user exits with /quit, Ctrl+D, etc.).
935
+ *
936
+ * PROCESS:
937
+ * 1. Call init() to set up UI, keyboard handlers, extensions
938
+ * 2. Start version check asynchronously (shows notification if newer version available)
939
+ * 3. Render initial session messages from previous session (if resumed)
940
+ * 4. Show startup warnings:
941
+ * - Migrated provider credentials
942
+ * - models.json parsing errors
943
+ * - Model fallback message (if model couldn't be restored)
944
+ * 5. Process initial messages from options (--message, --initial-message):
945
+ * - Send each via session.prompt()
946
+ * - Catch and display errors
947
+ * 6. Start interactive loop:
948
+ * a. Wait for user input (getUserInput waits for editor.onSubmit)
949
+ * b. Send input to session.prompt()
950
+ * c. Loop continues until shutdown (process.exit)
951
+ *
952
+ * PARAMETERS:
953
+ * None - uses constructor options for initial messages and warnings
954
+ *
955
+ * RETURNS:
956
+ * Promise<void> - never resolves (runs until process.exit is called)
957
+ *
958
+ * STATE CHANGES:
959
+ * - Initializes all UI components
960
+ * - Starts the interactive input loop
961
+ * - Updates footer/display as agent processes messages
962
+ * - May create new sessions, fork, navigate tree, etc. based on commands
963
+ *
964
+ * NOTES:
965
+ * - Must call init() first
966
+ * - Handles all errors from session.prompt() and displays them
967
+ * - Runs forever - shutdown happens via /quit, Ctrl+D, or extension shutdown signal
342
968
  */
343
969
  async run() {
344
970
  await this.init();
@@ -782,6 +1408,63 @@ export class InteractiveMode {
782
1408
  }
783
1409
  /**
784
1410
  * Initialize the extension system with TUI-based UI context.
1411
+ *
1412
+ * PURPOSE:
1413
+ * Set up extensions with UI capabilities. Creates ExtensionUIContext for
1414
+ * extensions to use for dialogs, widgets, status messages, etc.
1415
+ * Binds command context actions, error handlers, and shutdown handler.
1416
+ *
1417
+ * PROCESS:
1418
+ * 1. If no extension runner: show loaded resources and return
1419
+ * 2. Create ExtensionUIContext with:
1420
+ * a. Dialog methods (select, confirm, input, notify)
1421
+ * b. Status/title setters
1422
+ * c. Widget and footer/header setters
1423
+ * d. Editor interaction methods
1424
+ * e. Theme access and switching
1425
+ * 3. Call session.bindExtensions() with:
1426
+ * a. uiContext - for TUI dialogs
1427
+ * b. commandContextActions - for /fork, /tree, /new actions
1428
+ * c. shutdownHandler - called when extension wants to exit
1429
+ * d. Error handlers - for extension and hook errors
1430
+ * 4. Set up extension shortcuts (keyboard bindings from extensions)
1431
+ * 5. Show loaded resources (extensions, skills, prompts, themes)
1432
+ *
1433
+ * EXTENSION CAPABILITIES:
1434
+ * - Show dialogs (selectors, confirmations, inputs)
1435
+ * - Display persistent widgets above/below editor
1436
+ * - Set custom footer and header
1437
+ * - Customize editor component
1438
+ * - Register keyboard shortcuts
1439
+ * - Request shutdown
1440
+ * - Access theme system
1441
+ *
1442
+ * COMMAND CONTEXT ACTIONS:
1443
+ * - waitForIdle(): Wait until agent is idle
1444
+ * - newSession(): Start a new session
1445
+ * - fork(): Create branch from previous message
1446
+ * - navigateTree(): Switch to different point in tree
1447
+ *
1448
+ * PARAMETERS:
1449
+ * None
1450
+ *
1451
+ * RETURNS:
1452
+ * Promise<void> - completes when extensions are initialized
1453
+ *
1454
+ * STATE CHANGES:
1455
+ * - Binds extensions to session
1456
+ * - Sets up shortcuts on defaultEditor
1457
+ * - Displays loaded resources in chat
1458
+ *
1459
+ * NOTES:
1460
+ * - Called from init() after TUI is started
1461
+ * - Extensions can't affect UI until this completes
1462
+ * - Error/hook errors displayed but don't stop initialization
1463
+ * - Resources shown only if verbose or not quietStartup
1464
+ *
1465
+ * SEE ALSO:
1466
+ * - createExtensionUIContext() for UI API details
1467
+ * - setupExtensionShortcuts() for keyboard bindings
785
1468
  */
786
1469
  async initExtensions() {
787
1470
  const extensionRunner = this.session.extensionRunner;
@@ -1428,6 +2111,100 @@ export class InteractiveMode {
1428
2111
  // =========================================================================
1429
2112
  // Key Handlers
1430
2113
  // =========================================================================
2114
+ /**
2115
+ * Set up keyboard event handlers for all key bindings.
2116
+ *
2117
+ * PURPOSE:
2118
+ * Registers all keyboard shortcuts and special key handlers on the editor.
2119
+ * Handles direct key events (Escape, Ctrl+D, etc.) and action dispatching
2120
+ * for keybinding-mapped actions (clear, suspend, cycle models, etc.).
2121
+ *
2122
+ * KEY BINDINGS CONFIGURED:
2123
+ *
2124
+ * **Escape**:
2125
+ * - If loading: restore queued messages and abort agent
2126
+ * - Else if bash running: abort bash
2127
+ * - Else if bash mode (!): clear input and exit bash mode
2128
+ * - Else if editor has text: clear editor
2129
+ * - Else double-escape with empty: open tree selector or fork selector
2130
+ * (based on settings.doubleEscapeAction)
2131
+ *
2132
+ * **Ctrl+C** (mapped to "clear" action):
2133
+ * - First press: clear editor
2134
+ * - Second press within 500ms: exit application
2135
+ *
2136
+ * **Ctrl+D** (mapped via onCtrlD):
2137
+ * - Only when editor is empty
2138
+ * - Exit application immediately
2139
+ *
2140
+ * **Ctrl+Z** (mapped to "suspend" action):
2141
+ * - Suspend terminal to background
2142
+ * - Set up SIGCONT handler to restore TUI on resume
2143
+ *
2144
+ * **Ctrl+M** (mapped to "cycleThinkingLevel" action):
2145
+ * - Cycle thinking level: off → low → medium → high → off
2146
+ * - Update border color and footer
2147
+ * - Show status message
2148
+ *
2149
+ * **Ctrl+N** (mapped to "cycleModelForward" action):
2150
+ * - Cycle to next model in scope or registry
2151
+ * - Update footer and border color
2152
+ * - Show status with new model name
2153
+ *
2154
+ * **Ctrl+P** (mapped to "cycleModelBackward" action):
2155
+ * - Cycle to previous model
2156
+ *
2157
+ * **Ctrl+L** (mapped to "selectModel" action):
2158
+ * - Open model selector dialog
2159
+ *
2160
+ * **Ctrl+E** (mapped to "expandTools" action):
2161
+ * - Toggle expansion state of all tool output components
2162
+ * - Affects current and future tool displays
2163
+ *
2164
+ * **Ctrl+K** (mapped to "toggleThinking" action):
2165
+ * - Toggle visibility of thinking blocks in all messages
2166
+ * - Rebuild chat with updated visibility
2167
+ *
2168
+ * **Ctrl+X** (mapped to "externalEditor" action):
2169
+ * - Open current editor content in $VISUAL or $EDITOR
2170
+ * - Replace content on save
2171
+ *
2172
+ * **Alt+Enter** (mapped to "followUp" action):
2173
+ * - If streaming: queue message to send after agent finishes
2174
+ * - If idle: acts like regular Enter (trigger onSubmit)
2175
+ *
2176
+ * **Ctrl+J** (mapped to "dequeue" action):
2177
+ * - Restore all queued messages back to editor
2178
+ * - Show count of restored messages
2179
+ *
2180
+ * **Alt+Debug** (global on TUI, not editor):
2181
+ * - Write debug log with terminal render state and agent messages
2182
+ *
2183
+ * **onChange**:
2184
+ * - Track if text starts with ! to enter bash mode
2185
+ * - Update editor border color (blue for bash, accent for thinking)
2186
+ *
2187
+ * **onPasteImage**:
2188
+ * - Handle Ctrl+V clipboard image paste
2189
+ * - Write to temp file, insert path in editor
2190
+ *
2191
+ * PARAMETERS:
2192
+ * None - configures this.defaultEditor handlers
2193
+ *
2194
+ * RETURNS:
2195
+ * void - side effects only
2196
+ *
2197
+ * STATE CHANGES:
2198
+ * - Sets this.defaultEditor.onEscape, onSubmit, onChange, onCtrlD, onPasteImage
2199
+ * - Sets this.defaultEditor action handlers (clear, suspend, etc.)
2200
+ * - Sets this.ui.onDebug for global debug shortcut
2201
+ *
2202
+ * NOTES:
2203
+ * - Handlers use this.editor (which may be custom from extension)
2204
+ * - All handlers use updateEditorBorderColor() to reflect state
2205
+ * - Extension shortcuts can override action handlers via registerShortcut()
2206
+ * - Most handlers call updatePendingMessagesDisplay() to sync UI
2207
+ */
1431
2208
  setupKeyHandlers() {
1432
2209
  // Set up handlers on defaultEditor - they use this.editor for text access
1433
2210
  // so they work correctly regardless of which editor is active
@@ -1511,6 +2288,93 @@ export class InteractiveMode {
1511
2288
  // Silently ignore clipboard errors (may not have permission, etc.)
1512
2289
  }
1513
2290
  }
2291
+ /**
2292
+ * Set up the editor submit handler for user input.
2293
+ *
2294
+ * PURPOSE:
2295
+ * Configures the editor's onSubmit callback to process user input.
2296
+ * This is called when the user presses Enter in the editor.
2297
+ * Handles command parsing, slash commands, bash execution, and message queuing.
2298
+ *
2299
+ * INPUT TYPES PROCESSED:
2300
+ *
2301
+ * **Slash Commands** (commands starting with /):
2302
+ * - /settings: Open settings selector menu
2303
+ * - /scoped-models: Open model scoping selector
2304
+ * - /model [search]: Open model selector or find exact match
2305
+ * - /export [path]: Export session to HTML file
2306
+ * - /share: Share session as secret GitHub gist
2307
+ * - /copy: Copy last agent message to clipboard
2308
+ * - /name [name]: Set or display session name
2309
+ * - /session: Show session info and stats (messages, tokens, cost)
2310
+ * - /changelog: Show full changelog entries
2311
+ * - /hotkeys: Show all keyboard shortcuts
2312
+ * - /fork: Show fork selector (pick message to branch from)
2313
+ * - /tree: Show tree selector (navigate session DAG)
2314
+ * - /login: Open OAuth or API key login selector
2315
+ * - /logout: Open logout selector for saved credentials
2316
+ * - /new, /clear: Start a new session
2317
+ * - /compact [instructions]: Manually compact context
2318
+ * - /reload: Reload extensions, skills, prompts, themes
2319
+ * - /resume: Show session selector to switch sessions
2320
+ * - /quit, /exit: Exit the application
2321
+ *
2322
+ * **Bash Commands** (text starting with ! or !!):
2323
+ * - !command: Execute bash command, include output in context
2324
+ * - !!command: Execute bash command, exclude from context (use !!)
2325
+ * - Shows BashExecutionComponent with real-time output
2326
+ * - Can be queued during streaming (moved to chat on next submit)
2327
+ *
2328
+ * **Prompt Templates** (custom /name commands):
2329
+ * - Registered template names become slash commands
2330
+ * - Can have placeholders and argument completion
2331
+ * - Expanded and sent as regular messages
2332
+ *
2333
+ * **Extension Commands**:
2334
+ * - Custom commands registered by extensions
2335
+ * - Execute immediately even during streaming
2336
+ * - Can provide argument completion
2337
+ *
2338
+ * **Regular Messages**:
2339
+ * - If streaming: queue with streaming behavior (steer/followUp)
2340
+ * - "steer": interrupt immediately
2341
+ * - "followUp": wait until response finishes
2342
+ * - If compacting: queue in compactionQueuedMessages
2343
+ * - If idle: send immediately
2344
+ * - Can include @file references for context injection
2345
+ *
2346
+ * COMMAND PARSING:
2347
+ * 1. Trim whitespace
2348
+ * 2. Check if it's a slash command (/command or /command arg)
2349
+ * 3. Check if it's a bash command (!cmd or !!cmd)
2350
+ * 4. Otherwise treat as regular message
2351
+ *
2352
+ * QUEUEING BEHAVIOR:
2353
+ * - During streaming: prompt() with streamingBehavior option
2354
+ * - During compaction: compactionQueuedMessages array
2355
+ * - Queued messages shown in pendingMessagesContainer
2356
+ * - Can be dequeued with Ctrl+J and edited
2357
+ *
2358
+ * PARAMETERS:
2359
+ * @param text - The submitted editor text (trimmed)
2360
+ *
2361
+ * RETURNS:
2362
+ * void - async, calls callbacks but doesn't return a value
2363
+ *
2364
+ * STATE CHANGES:
2365
+ * - Clears editor text on submit
2366
+ * - Adds to editor history (for Up arrow to recall)
2367
+ * - Updates pendingMessagesDisplay when queuing
2368
+ * - Moves pendingBashComponents to chat
2369
+ * - Calls session.prompt() or command handlers
2370
+ *
2371
+ * NOTES:
2372
+ * - Empty input is ignored
2373
+ * - All errors are caught and displayed via showError()
2374
+ * - Some commands suppress editor.addToHistory() (reload, etc.)
2375
+ * - Bash commands check if bash is already running and warn
2376
+ * - Must check isBashMode before allowing bash command execution
2377
+ */
1514
2378
  setupEditorSubmitHandler() {
1515
2379
  this.defaultEditor.onSubmit = async (text) => {
1516
2380
  text = text.trim();
@@ -1672,11 +2536,132 @@ export class InteractiveMode {
1672
2536
  this.editor.addToHistory?.(text);
1673
2537
  };
1674
2538
  }
2539
+ /**
2540
+ * Subscribe to agent session events for UI updates.
2541
+ *
2542
+ * PURPOSE:
2543
+ * Sets up the event subscription to agent session. All agent activity (messages,
2544
+ * tool execution, compaction, retry, etc.) flows through here to update the display.
2545
+ * This is the bridge between the agent execution layer and the TUI rendering layer.
2546
+ *
2547
+ * PROCESS:
2548
+ * 1. Call session.subscribe() with an async event handler
2549
+ * 2. Save unsubscribe function for cleanup on shutdown
2550
+ * 3. Handler receives AgentSessionEvent objects and calls handleEvent()
2551
+ * 4. handleEvent() dispatches based on event type to update UI components
2552
+ *
2553
+ * EVENT TYPES HANDLED:
2554
+ * - `agent_start`: Show loading animation with working message
2555
+ * - `message_start`: Create new message component (user/assistant/custom)
2556
+ * - `message_update`: Stream content to message component, add tool calls
2557
+ * - `message_end`: Mark message complete, update tool results
2558
+ * - `tool_execution_start`: Create tool execution component in chat
2559
+ * - `tool_execution_update`: Stream partial tool results
2560
+ * - `tool_execution_end`: Mark tool complete with final result
2561
+ * - `agent_end`: Hide loading animation, check for shutdown request
2562
+ * - `auto_compaction_start`: Show compaction loader, allow escape to abort
2563
+ * - `auto_compaction_end`: Hide loader, rebuild chat, flush queued messages
2564
+ * - `auto_retry_start`: Show retry countdown loader
2565
+ * - `auto_retry_end`: Hide loader, show error only on failure
2566
+ *
2567
+ * PARAMETERS:
2568
+ * None - subscribes to this.session
2569
+ *
2570
+ * RETURNS:
2571
+ * void - side effects only (sets this.unsubscribe)
2572
+ *
2573
+ * NOTES:
2574
+ * - Called from init() after TUI is started
2575
+ * - Handles streaming content incrementally
2576
+ * - Must save unsubscribe function for cleanup in stop()
2577
+ * - Events are processed sequentially (not concurrent)
2578
+ */
1675
2579
  subscribeToAgent() {
1676
2580
  this.unsubscribe = this.session.subscribe(async (event) => {
1677
2581
  await this.handleEvent(event);
1678
2582
  });
1679
2583
  }
2584
+ /**
2585
+ * Handle a single agent session event and update the UI.
2586
+ *
2587
+ * PURPOSE:
2588
+ * Processes agent events and translates them into UI updates. This is the main
2589
+ * event dispatcher that receives all agent activity and renders it to the terminal.
2590
+ *
2591
+ * PROCESS FOR EACH EVENT TYPE:
2592
+ *
2593
+ * **agent_start**:
2594
+ * - Stop any existing loading animation
2595
+ * - Create new Loader component in statusContainer
2596
+ * - Apply pending working message if queued
2597
+ * - Request render
2598
+ *
2599
+ * **message_start**:
2600
+ * - User messages: render to chat immediately
2601
+ * - Assistant messages: create streaming component, add to chat
2602
+ * - Custom messages: render with extension renderer
2603
+ *
2604
+ * **message_update**:
2605
+ * - Stream new content to streamingComponent
2606
+ * - For each tool call: create or update ToolExecutionComponent
2607
+ * - Track tool components by ID for later result updates
2608
+ *
2609
+ * **message_end**:
2610
+ * - Mark streaming complete
2611
+ * - If aborted/error: set error message, mark all pending tools as error
2612
+ * - Otherwise: trigger diff computation for edit tools
2613
+ * - Clear streaming component reference
2614
+ *
2615
+ * **tool_execution_start/update/end**:
2616
+ * - Create or update ToolExecutionComponent in chat
2617
+ * - Stream partial results incrementally
2618
+ * - On completion: mark expanded state and remove from pending
2619
+ *
2620
+ * **agent_end**:
2621
+ * - Stop loading animation
2622
+ * - Clear streaming component from chat
2623
+ * - Clear pending tools
2624
+ * - Check for shutdown request (if extension called shutdown())
2625
+ *
2626
+ * **auto_compaction_start**:
2627
+ * - Show compaction loader with reason (overflow, etc.)
2628
+ * - Replace main escape handler to allow abort
2629
+ * - Editor stays active for queueing messages
2630
+ *
2631
+ * **auto_compaction_end**:
2632
+ * - Hide loader
2633
+ * - Rebuild chat to show compacted state
2634
+ * - Add compaction summary component
2635
+ * - Flush queued messages
2636
+ * - Handle completion, cancellation, or errors
2637
+ *
2638
+ * **auto_retry_start**:
2639
+ * - Show retry countdown (attempt N/M in delay seconds)
2640
+ * - Replace main escape handler to allow abort
2641
+ *
2642
+ * **auto_retry_end**:
2643
+ * - Hide loader
2644
+ * - Show error only on final failure (success shows normal response)
2645
+ *
2646
+ * PARAMETERS:
2647
+ * @param event - AgentSessionEvent (message, tool, compaction, retry, etc.)
2648
+ *
2649
+ * RETURNS:
2650
+ * Promise<void> - completes when UI update is done
2651
+ *
2652
+ * STATE CHANGES:
2653
+ * - Updates streamingComponent and streamingMessage
2654
+ * - Updates pendingTools map
2655
+ * - Updates loading animations (loadingAnimation, autoCompactionLoader, retryLoader)
2656
+ * - Updates UI via this.ui.requestRender()
2657
+ * - May call checkShutdownRequested() on agent_end
2658
+ *
2659
+ * NOTES:
2660
+ * - Initializes if not yet initialized (lazy init)
2661
+ * - Handles streaming content incremental rendering
2662
+ * - Events are sequential, not concurrent
2663
+ * - Tool components maintain expanded state across events
2664
+ */
1680
2665
  async handleEvent(event) {
1681
2666
  if (!this.isInitialized) {
1682
2667
  await this.init();
@@ -1956,6 +2941,77 @@ export class InteractiveMode {
1956
2941
  this.lastStatusText = text;
1957
2942
  this.ui.requestRender();
1958
2943
  }
2944
+ /**
2945
+ * Render a single message to the chat display.
2946
+ *
2947
+ * PURPOSE:
2948
+ * Converts an AgentMessage into one or more UI components and adds to chatContainer.
2949
+ * Handles all message types with appropriate rendering, tool display, and formatting.
2950
+ *
2951
+ * MESSAGE TYPES HANDLED:
2952
+ *
2953
+ * **user**:
2954
+ * - Extract text content (ignore images/other content)
2955
+ * - Check for skill blocks (```skill block with instructions)
2956
+ * - If skill block found:
2957
+ * a. Render SkillInvocationMessageComponent (collapsible)
2958
+ * b. Render user message separately if non-skill text exists
2959
+ * - Otherwise render UserMessageComponent with full text
2960
+ * - Optionally add to editor history for Up arrow recall
2961
+ *
2962
+ * **assistant**:
2963
+ * - Create AssistantMessageComponent with streaming content
2964
+ * - Respect hideThinkingBlock setting for visibility
2965
+ * - Tool calls are handled separately in message_update event
2966
+ *
2967
+ * **custom**:
2968
+ * - Check message.display flag (if false, skip rendering)
2969
+ * - Use extension message renderer if available
2970
+ * - Render as CustomMessageComponent with markdown theme
2971
+ *
2972
+ * **toolResult**:
2973
+ * - Rendered inline with tool calls (not standalone)
2974
+ * - Handled during renderSessionContext() matching with tool calls
2975
+ *
2976
+ * **bashExecution**:
2977
+ * - Create BashExecutionComponent
2978
+ * - Show command and real-time output
2979
+ * - Mark complete with exit code and optional truncation info
2980
+ *
2981
+ * **compactionSummary**:
2982
+ * - Show how many tokens were saved during compaction
2983
+ * - Display summary of what was compacted
2984
+ * - Component respects toolOutputExpanded setting
2985
+ *
2986
+ * **branchSummary**:
2987
+ * - Show branch point with label
2988
+ * - Display custom instructions for navigation
2989
+ * - Component respects toolOutputExpanded setting
2990
+ *
2991
+ * PARAMETERS:
2992
+ * @param message - AgentMessage to render (any role type)
2993
+ * @param options.populateHistory - Add user text to editor history (default: false)
2994
+ *
2995
+ * RETURNS:
2996
+ * void - side effects only (adds components to chatContainer)
2997
+ *
2998
+ * STATE CHANGES:
2999
+ * - Adds components to chatContainer
3000
+ * - May update editor history
3001
+ * - Updates pendingTools map (indirectly in renderSessionContext)
3002
+ *
3003
+ * NOTES:
3004
+ * - Uses getMarkdownThemeWithSettings() for consistent rendering
3005
+ * - Respects current display settings (hideThinkingBlock, showImages, etc.)
3006
+ * - Tool calls are created separately during message_update event
3007
+ * - Skill blocks use parseSkillBlock() to extract instructions
3008
+ * - Images in messages are handled by markdown renderer
3009
+ * - Does NOT call ui.requestRender() (caller does)
3010
+ *
3011
+ * SEE ALSO:
3012
+ * - renderSessionContext() which calls this for all messages
3013
+ * - handleEvent() which creates components during streaming
3014
+ */
1959
3015
  addMessageToChat(message, options) {
1960
3016
  switch (message.role) {
1961
3017
  case "bashExecution": {
@@ -2029,10 +3085,53 @@ export class InteractiveMode {
2029
3085
  }
2030
3086
  }
2031
3087
  /**
2032
- * Render session context to chat. Used for initial load and rebuild after compaction.
2033
- * @param sessionContext Session context to render
2034
- * @param options.updateFooter Update footer state
2035
- * @param options.populateHistory Add user messages to editor history
3088
+ * Render a complete session context (all messages) to the chat display.
3089
+ *
3090
+ * PURPOSE:
3091
+ * Renders all aligned messages from a SessionContext to the chat display.
3092
+ * Handles message-to-tool-result matching and streaming state management.
3093
+ * Used during initial load, after compaction, and during rebuilds.
3094
+ *
3095
+ * PROCESS:
3096
+ * 1. Clear pendingTools map (reset tracking)
3097
+ * 2. Iterate through all messages in context:
3098
+ * - Assistant messages: render message + create tool components for each tool call
3099
+ * - Tool result messages: match to tool component, update result, remove from pending
3100
+ * - Other messages: render via addMessageToChat()
3101
+ * 3. Final clear of pendingTools (shouldn't have any left)
3102
+ * 4. Call ui.requestRender() to display changes
3103
+ *
3104
+ * TOOL CALL/RESULT MATCHING:
3105
+ * Tool calls and results are matched by ID:
3106
+ * 1. When rendering assistant message, create ToolExecutionComponent for each tool call
3107
+ * 2. Store component in pendingTools[toolCallId]
3108
+ * 3. When rendering tool result: look up component, update it, remove from pending
3109
+ * 4. If tool errored/aborted: mark component as error, don't add to pending
3110
+ *
3111
+ * PARAMETERS:
3112
+ * @param sessionContext - SessionContext from sessionManager.buildSessionContext()
3113
+ * @param options.updateFooter - Call footer.invalidate() and updateEditorBorderColor() (default: false)
3114
+ * @param options.populateHistory - Add user message text to editor history (default: false)
3115
+ *
3116
+ * RETURNS:
3117
+ * void - side effects only
3118
+ *
3119
+ * STATE CHANGES:
3120
+ * - Clears and repopulates chatContainer
3121
+ * - Updates pendingTools map (should be empty after completion)
3122
+ * - May call footer.invalidate() if updateFooter is true
3123
+ * - Calls ui.requestRender()
3124
+ *
3125
+ * NOTES:
3126
+ * - Used internally by renderInitialMessages() and rebuildChatFromMessages()
3127
+ * - All messages must be aligned (via sessionManager.buildSessionContext())
3128
+ * - Tool calls/results must already be matched and in correct order
3129
+ * - Error state is preserved from messages (stopReason, errorMessage)
3130
+ * - Tool components track expanded state (respects this.toolOutputExpanded)
3131
+ *
3132
+ * SEE ALSO:
3133
+ * - addMessageToChat() for individual message rendering
3134
+ * - handleEvent() for streaming message handling
2036
3135
  */
2037
3136
  renderSessionContext(sessionContext, options = {}) {
2038
3137
  this.pendingTools.clear();
@@ -2086,6 +3185,56 @@ export class InteractiveMode {
2086
3185
  this.pendingTools.clear();
2087
3186
  this.ui.requestRender();
2088
3187
  }
3188
+ /**
3189
+ * Render all session messages to the chat display.
3190
+ *
3191
+ * PURPOSE:
3192
+ * Displays all messages from the current session context in the chat area.
3193
+ * Used when initializing the UI, resuming a session, or after compaction.
3194
+ * Handles special message types: assistant, user, tool calls/results, compaction, branches.
3195
+ *
3196
+ * PROCESS:
3197
+ * 1. Get aligned session messages from sessionManager.buildSessionContext()
3198
+ * 2. Call renderSessionContext() to render all messages
3199
+ * 3. Show compaction count if session was compacted (0+ times)
3200
+ * 4. Update footer state (model, tokens, etc.)
3201
+ * 5. Populate editor history with user messages for Up arrow
3202
+ *
3203
+ * MESSAGE TYPES RENDERED:
3204
+ * - User messages: with text content and optional skill blocks
3205
+ * - Assistant messages: with streaming content, thinking blocks, tool calls
3206
+ * - Tool calls: inline with ToolExecutionComponent showing args and results
3207
+ * - Tool results: rendered within tool components (not standalone)
3208
+ * - Bash execution: with command and output
3209
+ * - Compaction summary: shows how many tokens were saved
3210
+ * - Branch summary: shows custom branch label/instructions
3211
+ * - Custom messages: rendered via extension message renderer
3212
+ *
3213
+ * PARAMETERS:
3214
+ * None - uses this.session and this.sessionManager
3215
+ *
3216
+ * RETURNS:
3217
+ * void - side effects only (adds components to chatContainer)
3218
+ *
3219
+ * STATE CHANGES:
3220
+ * - Populates chatContainer with all message components
3221
+ * - Populates editor history
3222
+ * - Updates footer.invalidate()
3223
+ * - Updates pendingTools map for tracking tool components
3224
+ * - Clears previous chat display
3225
+ *
3226
+ * NOTES:
3227
+ * - Called from init() after UI setup
3228
+ * - Called from run() before entering interactive loop
3229
+ * - Called from resume/fork/tree navigation
3230
+ * - Rebuilds entire chat, clearing previous messages
3231
+ * - Compaction entries show as status messages
3232
+ * - Only shows compaction info if compaction was performed
3233
+ *
3234
+ * SEE ALSO:
3235
+ * - renderSessionContext() for detailed rendering logic
3236
+ * - addMessageToChat() for rendering individual messages
3237
+ */
2089
3238
  renderInitialMessages() {
2090
3239
  // Get aligned messages and entries from session context
2091
3240
  const context = this.sessionManager.buildSessionContext();
@@ -2109,6 +3258,43 @@ export class InteractiveMode {
2109
3258
  };
2110
3259
  });
2111
3260
  }
3261
+ /**
3262
+ * Rebuild chat display from scratch, clearing and re-rendering all messages.
3263
+ *
3264
+ * PURPOSE:
3265
+ * Clears the entire chat display and re-renders all messages from session context.
3266
+ * Used after toggling thinking block visibility, compaction completion, or theme changes.
3267
+ * Ensures the display matches the current session state and settings.
3268
+ *
3269
+ * PROCESS:
3270
+ * 1. Clear chatContainer (remove all rendered components)
3271
+ * 2. Get fresh session context from sessionManager
3272
+ * 3. Call renderSessionContext() to re-render all messages
3273
+ * 4. All messages respect current settings (hideThinkingBlock, showImages, etc.)
3274
+ *
3275
+ * PARAMETERS:
3276
+ * None - uses this.session and this.settingsManager
3277
+ *
3278
+ * RETURNS:
3279
+ * void - side effects only
3280
+ *
3281
+ * STATE CHANGES:
3282
+ * - Clears and repopulates chatContainer
3283
+ * - Resets pendingTools map
3284
+ * - Updates streaming/non-streaming component visibility
3285
+ * - Respects current display settings
3286
+ *
3287
+ * NOTES:
3288
+ * - Much faster than renderInitialMessages() (no footer update or history)
3289
+ * - Used internally by toggleThinkingBlockVisibility()
3290
+ * - Used after compaction to show new state
3291
+ * - Does NOT scroll to top (chat scrolls naturally)
3292
+ * - Triggers UI render request in renderSessionContext()
3293
+ *
3294
+ * SEE ALSO:
3295
+ * - renderInitialMessages() for full initialization with footer/history
3296
+ * - toggleThinkingBlockVisibility() which calls this
3297
+ */
2112
3298
  rebuildChatFromMessages() {
2113
3299
  this.chatContainer.clear();
2114
3300
  const context = this.sessionManager.buildSessionContext();
@@ -2149,7 +3335,39 @@ export class InteractiveMode {
2149
3335
  process.exit(0);
2150
3336
  }
2151
3337
  /**
2152
- * Check if shutdown was requested and perform shutdown if so.
3338
+ * Check if shutdown was requested and perform it if so.
3339
+ *
3340
+ * PURPOSE:
3341
+ * Called after agent completes (agent_end event) to check if an extension
3342
+ * has requested shutdown. Allows shutdown to be queued and executed cleanly
3343
+ * after current operation completes.
3344
+ *
3345
+ * PROCESS:
3346
+ * 1. Check this.shutdownRequested flag
3347
+ * 2. If false: return (no shutdown requested)
3348
+ * 3. If true: call shutdown()
3349
+ *
3350
+ * USAGE:
3351
+ * - Extensions call context.shutdown() to request shutdown
3352
+ * - Set this.shutdownRequested = true
3353
+ * - After agent finishes, agent_end event triggers this check
3354
+ * - Shutdown happens cleanly after current message completes
3355
+ *
3356
+ * PARAMETERS:
3357
+ * None
3358
+ *
3359
+ * RETURNS:
3360
+ * Promise<void> - never resolves (process.exit is called)
3361
+ *
3362
+ * NOTES:
3363
+ * - Called from handleEvent() on agent_end
3364
+ * - Defers shutdown until agent is idle
3365
+ * - Allows current streaming/compaction to complete
3366
+ * - Much cleaner than immediate exit
3367
+ *
3368
+ * SEE ALSO:
3369
+ * - shutdown() which does the actual cleanup
3370
+ * - handleEvent() which calls this on agent_end
2153
3371
  */
2154
3372
  async checkShutdownRequested() {
2155
3373
  if (!this.shutdownRequested)
@@ -2312,15 +3530,73 @@ export class InteractiveMode {
2312
3530
  // =========================================================================
2313
3531
  // UI helpers
2314
3532
  // =========================================================================
3533
+ /**
3534
+ * Clear the editor and request re-render.
3535
+ *
3536
+ * PURPOSE:
3537
+ * Empties the editor text area. Used when user presses Escape with text.
3538
+ */
2315
3539
  clearEditor() {
2316
3540
  this.editor.setText("");
2317
3541
  this.ui.requestRender();
2318
3542
  }
3543
+ /**
3544
+ * Show an error message in the chat.
3545
+ *
3546
+ * PURPOSE:
3547
+ * Display an error message to the user. Used for command failures,
3548
+ * parsing errors, and unexpected exceptions.
3549
+ *
3550
+ * PARAMETERS:
3551
+ * @param errorMessage - The error message text (without "Error:" prefix)
3552
+ *
3553
+ * RETURNS:
3554
+ * void - side effects only
3555
+ *
3556
+ * FORMATTING:
3557
+ * - Adds 1-line spacer before message
3558
+ * - Prefixes with "Error: "
3559
+ * - Uses error color from theme
3560
+ * - Adds to bottom of chat
3561
+ *
3562
+ * NOTES:
3563
+ * - Auto-calls ui.requestRender()
3564
+ * - Should be used for all errors displayed to user
3565
+ * - Does not throw (safe to use in error handlers)
3566
+ */
2319
3567
  showError(errorMessage) {
2320
3568
  this.chatContainer.addChild(new Spacer(1));
2321
3569
  this.chatContainer.addChild(new Text(theme.fg("error", `Error: ${errorMessage}`), 1, 0));
2322
3570
  this.ui.requestRender();
2323
3571
  }
3572
+ /**
3573
+ * Show a warning message in the chat.
3574
+ *
3575
+ * PURPOSE:
3576
+ * Display a warning message to the user. Used for non-fatal issues,
3577
+ * deprecated features, and important notifications.
3578
+ *
3579
+ * PARAMETERS:
3580
+ * @param warningMessage - The warning message text (without "Warning:" prefix)
3581
+ *
3582
+ * RETURNS:
3583
+ * void - side effects only
3584
+ *
3585
+ * FORMATTING:
3586
+ * - Adds 1-line spacer before message
3587
+ * - Prefixes with "Warning: "
3588
+ * - Uses warning color from theme
3589
+ * - Adds to bottom of chat
3590
+ *
3591
+ * NOTES:
3592
+ * - Auto-calls ui.requestRender()
3593
+ * - Similar to showError() but with different severity
3594
+ * - Used for resource loading issues, model fallbacks, etc.
3595
+ *
3596
+ * SEE ALSO:
3597
+ * - showError() for fatal errors
3598
+ * - showStatus() for informational messages
3599
+ */
2324
3600
  showWarning(warningMessage) {
2325
3601
  this.chatContainer.addChild(new Spacer(1));
2326
3602
  this.chatContainer.addChild(new Text(theme.fg("warning", `Warning: ${warningMessage}`), 1, 0));
@@ -2508,8 +3784,58 @@ export class InteractiveMode {
2508
3784
  // Selectors
2509
3785
  // =========================================================================
2510
3786
  /**
2511
- * Shows a selector component in place of the editor.
2512
- * @param create Factory that receives a `done` callback and returns the component and focus target
3787
+ * Show a selector component, replacing the editor temporarily.
3788
+ *
3789
+ * PURPOSE:
3790
+ * Generic helper to display selector dialogs (/settings, /model, /tree, etc.).
3791
+ * Manages switching between the editor and selector, and restoring the editor.
3792
+ *
3793
+ * PROCESS:
3794
+ * 1. Create done callback that:
3795
+ * a. Clears editorContainer
3796
+ * b. Restores this.editor as the focused component
3797
+ * c. Requests UI render
3798
+ * 2. Call factory(done) to create the selector component
3799
+ * 3. Factory returns { component, focus } where focus is initial focus target
3800
+ * 4. Clear editorContainer and add selector component
3801
+ * 5. Set focus to the focus component (usually the selector itself)
3802
+ * 6. Request UI render
3803
+ * 7. Selector calls done() when user makes selection or presses escape
3804
+ *
3805
+ * SELECTOR TYPES:
3806
+ * - SettingsSelectorComponent: /settings - change settings
3807
+ * - ModelSelectorComponent: /model - select a model
3808
+ * - UserMessageSelectorComponent: /fork - pick message to branch from
3809
+ * - TreeSelectorComponent: /tree - navigate session DAG
3810
+ * - SessionSelectorComponent: /resume - switch to different session
3811
+ * - OAuthSelectorComponent: /login or /logout - manage credentials
3812
+ * - ExtensionSelectorComponent: custom dialogs from extensions
3813
+ *
3814
+ * PARAMETERS:
3815
+ * @param create - Factory function that:
3816
+ * - Receives done() callback (selector calls when finished)
3817
+ * - Returns { component, focus } where component is the selector UI
3818
+ * - focus is the component to set focus to (keyboard input target)
3819
+ *
3820
+ * RETURNS:
3821
+ * void - done callback handles restoration
3822
+ *
3823
+ * STATE CHANGES:
3824
+ * - Temporarily replaces editor with selector in editorContainer
3825
+ * - Sets keyboard focus to selector
3826
+ * - On done: restores editor and focus
3827
+ *
3828
+ * NOTES:
3829
+ * - Editor content is preserved (not cleared) during selector
3830
+ * - Selector can be closed by pressing Escape
3831
+ * - Factory receives done callback for flexible completion handling
3832
+ * - Used by all major selectors (model, settings, fork, tree, etc.)
3833
+ *
3834
+ * SEE ALSO:
3835
+ * - showModelSelector() for model selection
3836
+ * - showSettingsSelector() for settings
3837
+ * - showUserMessageSelector() for forking
3838
+ * - showTreeSelector() for tree navigation
2513
3839
  */
2514
3840
  showSelector(create) {
2515
3841
  const done = () => {
@@ -2631,6 +3957,45 @@ export class InteractiveMode {
2631
3957
  return { component: selector, focus: selector.getSettingsList() };
2632
3958
  });
2633
3959
  }
3960
+ /**
3961
+ * Handle the /model command - select or switch models.
3962
+ *
3963
+ * PURPOSE:
3964
+ * Process /model commands. If search term provided, find exact match.
3965
+ * Otherwise show interactive selector. Supports both quick switch and browse.
3966
+ *
3967
+ * PROCESS:
3968
+ * If searchTerm provided:
3969
+ * 1. Call findExactModelMatch(searchTerm)
3970
+ * 2. If found: switch to model, update footer/border, show status
3971
+ * 3. If not found: show selector with search term as initial filter
3972
+ *
3973
+ * If no searchTerm:
3974
+ * 1. Show model selector dialog
3975
+ * 2. User picks a model
3976
+ * 3. Model is switched
3977
+ *
3978
+ * SEARCH FORMAT:
3979
+ * - "model-id": Match by model ID only
3980
+ * - "provider/model-id": Match provider and model ID
3981
+ * - Case-insensitive matching
3982
+ *
3983
+ * PARAMETERS:
3984
+ * @param searchTerm - Optional model ID or "provider/model-id" to search for
3985
+ *
3986
+ * RETURNS:
3987
+ * Promise<void> - completes when model is selected
3988
+ *
3989
+ * STATE CHANGES:
3990
+ * - May call session.setModel() to switch
3991
+ * - Updates footer.invalidate()
3992
+ * - Updates editor border color (thinking level)
3993
+ * - Shows status message
3994
+ *
3995
+ * SEE ALSO:
3996
+ * - findExactModelMatch() for model lookup
3997
+ * - showModelSelector() for interactive selection
3998
+ */
2634
3999
  async handleModelCommand(searchTerm) {
2635
4000
  if (!searchTerm) {
2636
4001
  this.showModelSelector();
@@ -3805,6 +5170,46 @@ export class InteractiveMode {
3805
5170
  this.chatContainer.addChild(new Text(`${theme.fg("accent", "✓ New session started")}`, 1, 1));
3806
5171
  this.ui.requestRender();
3807
5172
  }
5173
+ /**
5174
+ * Handle the /debug command - write diagnostic information to file.
5175
+ *
5176
+ * PURPOSE:
5177
+ * Generates a comprehensive debug log of the current terminal rendering state
5178
+ * and agent message history. Used for troubleshooting rendering issues or
5179
+ * unusual terminal behavior.
5180
+ *
5181
+ * PROCESS:
5182
+ * 1. Get current terminal dimensions (columns x rows)
5183
+ * 2. Call ui.render() to get all rendered lines at that width
5184
+ * 3. Calculate visible width of each line (accounting for ANSI codes)
5185
+ * 4. Collect all agent messages in JSONL format (one per line)
5186
+ * 5. Write everything to debug log file (~/.indusagi/debug.log or similar)
5187
+ * 6. Display success message with file path
5188
+ *
5189
+ * DEBUG LOG CONTENTS:
5190
+ * - Timestamp of debug generation
5191
+ * - Terminal dimensions (WxH)
5192
+ * - Total rendered lines
5193
+ * - All rendered lines with visible widths and ANSI-escaped content
5194
+ * - All agent messages in JSONL format (one message object per line)
5195
+ *
5196
+ * PARAMETERS:
5197
+ * None
5198
+ *
5199
+ * RETURNS:
5200
+ * void - side effects only (writes file, shows status)
5201
+ *
5202
+ * STATE CHANGES:
5203
+ * - Writes to disk (debug log file)
5204
+ * - Adds status message to chat display
5205
+ *
5206
+ * NOTES:
5207
+ * - File path: getDebugLogPath()
5208
+ * - Creates parent directories if needed
5209
+ * - Useful for reporting rendering bugs
5210
+ * - Shows actual ANSI codes (escaped as JSON strings)
5211
+ * - One message per line in JSONL section for easy parsing
5212
+ */
3808
5213
  handleDebugCommand() {
3809
5214
  const width = this.ui.terminal.columns;
3810
5215
  const height = this.ui.terminal.rows;
@@ -3837,6 +5242,61 @@ export class InteractiveMode {
3837
5242
  this.chatContainer.addChild(new ArminComponent(this.ui));
3838
5243
  this.ui.requestRender();
3839
5244
  }
5245
+ /**
5246
+ * Handle bash command execution (! or !! prefix).
5247
+ *
5248
+ * PURPOSE:
5249
+ * Executes a shell command and displays the output in a BashExecutionComponent.
5250
+ * Supports including/excluding from context. Can be intercepted by extensions.
5251
+ *
5252
+ * PROCESS:
5253
+ * 1. Emit user_bash event to extensions (if loaded)
5254
+ * 2. If extension returns a result, use it (extension may intercept)
5255
+ * - Extensions can run custom operations or modify commands
5256
+ * 3. Otherwise execute bash command normally:
5257
+ * a. Create BashExecutionComponent (shows command)
5258
+ * b. If streaming: add to pendingMessagesContainer, queue for chat
5259
+ * c. If idle: add directly to chatContainer
5260
+ * 4. Stream output chunks to component in real-time
5261
+ * 5. Mark complete with exit code and truncation status
5262
+ * 6. Record result in session context
5263
+ *
5264
+ * CONTEXT HANDLING:
5265
+ * - excludeFromContext=false (!): Output included in context for agent
5266
+ * - excludeFromContext=true (!!): Output excluded from context
5267
+ * - Extensions can define custom "operations" to transform output
5268
+ *
5269
+ * PARAMETERS:
5270
+ * @param command - The bash command string (without ! or !! prefix)
5271
+ * @param excludeFromContext - If true, mark output as excluded (!! prefix)
5272
+ *
5273
+ * RETURNS:
5274
+ * Promise<void> - completes when command finishes
5275
+ *
5276
+ * STATE CHANGES:
5277
+ * - Creates BashExecutionComponent
5278
+ * - Adds to chatContainer or pendingMessagesContainer
5279
+ * - Adds to pendingBashComponents if queued
5280
+ * - Records bash result in session
5281
+ * - Updates ui on each output chunk
5282
+ *
5283
+ * ERROR HANDLING:
5284
+ * - Command failures caught and displayed as error message
5285
+ * - Bash execution may be aborted by user (Esc)
5286
+ * - Output may be truncated if too large
5287
+ * - Truncation info shown with link to full output file
5288
+ *
5289
+ * NOTES:
5290
+ * - User can check if bash is already running before executing
5291
+ * - Extension can provide custom operations (e.g., parsing specific formats)
5292
+ * - Output is streamed, so large outputs appear incrementally
5293
+ * - Bash components moved from pending to chat on next regular message
5294
+ * - Session.executeBash() handles actual execution and truncation
5295
+ *
5296
+ * SEE ALSO:
5297
+ * - session.executeBash() for actual command execution
5298
+ * - BashExecutionComponent for rendering
5299
+ */
3840
5300
  async handleBashCommand(command, excludeFromContext = false) {
3841
5301
  const extensionRunner = this.session.extensionRunner;
3842
5302
  // Emit user_bash event to let extensions intercept
@@ -3904,6 +5364,33 @@ export class InteractiveMode {
3904
5364
  this.bashComponent = undefined;
3905
5365
  this.ui.requestRender();
3906
5366
  }
5367
+ /**
5368
+ * Handle the /compact command - manually trigger context compaction.
5369
+ *
5370
+ * PURPOSE:
5371
+ * Allows user to manually compact the session context to reduce token usage.
5372
+ * Can include custom instructions to guide the compaction process.
5373
+ *
5374
+ * PROCESS:
5375
+ * 1. Count messages in session
5376
+ * 2. If less than 2 messages: show warning and return (nothing to compact)
5377
+ * 3. Otherwise call executeCompaction() with custom instructions
5378
+ *
5379
+ * PARAMETERS:
5380
+ * @param customInstructions - Optional custom instructions for compaction process
5381
+ *
5382
+ * RETURNS:
5383
+ * Promise<void> - completes when compaction is done
5384
+ *
5385
+ * NOTES:
5386
+ * - User can press Esc during compaction to abort
5387
+ * - Compaction may queue incoming messages
5388
+ * - Custom instructions merged with default compaction prompt
5389
+ *
5390
+ * SEE ALSO:
5391
+ * - executeCompaction() which does the actual work
5392
+ * - auto-compaction (happens automatically based on context size)
5393
+ */
3907
5394
  async handleCompactCommand(customInstructions) {
3908
5395
  const entries = this.sessionManager.getEntries();
3909
5396
  const messageCount = entries.filter((e) => e.type === "message").length;
@@ -3913,6 +5400,73 @@ export class InteractiveMode {
3913
5400
  }
3914
5401
  await this.executeCompaction(customInstructions, false);
3915
5402
  }
5403
+ /**
5404
+ * Execute context compaction (manual or automatic).
5405
+ *
5406
+ * PURPOSE:
5407
+ * Compacts the session context to reduce token usage by summarizing old messages.
5408
+ * Used both for manual /compact commands and automatic overflow-triggered compaction.
5409
+ *
5410
+ * PROCESS:
5411
+ * 1. Stop and clear any existing loading animation
5412
+ * 2. Replace escape handler to allow abort (Esc cancels compaction)
5413
+ * 3. Show "Compacting context..." loader with estimated time
5414
+ * 4. Call session.compact() with optional custom instructions
5415
+ * 5. On success:
5416
+ * a. Rebuild chat to show compacted state
5417
+ * b. Add CompactionSummaryMessageComponent showing tokens saved
5418
+ * c. Invalidate footer (update token counts)
5419
+ * 6. On abort/error:
5420
+ * a. Show error message
5421
+ * b. Chat state unchanged (compaction failed)
5422
+ * 7. Flush queued messages that arrived during compaction
5423
+ * 8. Restore original escape handler
5424
+ * 9. Clear loader
5425
+ *
5426
+ * CUSTOM INSTRUCTIONS:
5427
+ * - Optional guidance for compaction algorithm
5428
+ * - Examples: "focus on recent messages", "preserve code context", etc.
5429
+ * - Merged with default compaction system prompt
5430
+ *
5431
+ * AUTO vs MANUAL:
5432
+ * - isAuto=false (/compact command): shows "Compacting context..." label
5433
+ * - isAuto=true (automatic): shows "Auto-compacting..." label
5434
+ * - auto-compaction triggered by context overflow (token limit)
5435
+ *
5436
+ * QUEUED MESSAGES:
5437
+ * - Messages sent during compaction are queued in compactionQueuedMessages
5438
+ * - After compaction completes, queued messages are flushed
5439
+ * - First message becomes next prompt, rest queued as steer/followUp
5440
+ *
5441
+ * PARAMETERS:
5442
+ * @param customInstructions - Optional custom instructions for compaction
5443
+ * @param isAuto - If true, shows "Auto-compacting..." (default: false)
5444
+ *
5445
+ * RETURNS:
5446
+ * Promise<CompactionResult | undefined> - returns result or undefined on error/abort
5447
+ *
5448
+ * STATE CHANGES:
5449
+ * - Rebuilds chatContainer
5450
+ * - Updates footer
5451
+ * - Clears status container
5452
+ * - Restores escape handler
5453
+ * - Flushes compaction queue
5454
+ *
5455
+ * ERROR HANDLING:
5456
+ * - Compaction cancelled: shows "Compaction cancelled" error
5457
+ * - Compaction failed: shows detailed error message
5458
+ * - Messages queued during compaction are preserved and flushed
5459
+ *
5460
+ * NOTES:
5461
+ * - User can press Esc to abort
5462
+ * - Loader shows "Ctrl+C to cancel" hint
5463
+ * - Compaction happens asynchronously, UI stays responsive
5464
+ * - Very large contexts may take several seconds
5465
+ *
5466
+ * SEE ALSO:
5467
+ * - handleCompactCommand() for /compact processing
5468
+ * - auto-compaction in handleEvent() for automatic triggering
5469
+ */
3916
5470
  async executeCompaction(customInstructions, isAuto = false) {
3917
5471
  // Stop loading animation
3918
5472
  if (this.loadingAnimation) {