@ddse/acm-aicoder 0.5.0

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 (165) hide show
  1. package/.aicoder/index.json +304 -0
  2. package/AICODER_IMPLEMENTATION_PLAN_PHASE2.md +284 -0
  3. package/LICENSE +21 -0
  4. package/README.md +490 -0
  5. package/bin/interactive.tsx +232 -0
  6. package/dist/bin/interactive.d.ts +3 -0
  7. package/dist/bin/interactive.d.ts.map +1 -0
  8. package/dist/bin/interactive.js +155 -0
  9. package/dist/bin/interactive.js.map +1 -0
  10. package/dist/src/config/providers.d.ts +15 -0
  11. package/dist/src/config/providers.d.ts.map +1 -0
  12. package/dist/src/config/providers.js +142 -0
  13. package/dist/src/config/providers.js.map +1 -0
  14. package/dist/src/config/session.d.ts +25 -0
  15. package/dist/src/config/session.d.ts.map +1 -0
  16. package/dist/src/config/session.js +97 -0
  17. package/dist/src/config/session.js.map +1 -0
  18. package/dist/src/context/bm25.d.ts +68 -0
  19. package/dist/src/context/bm25.d.ts.map +1 -0
  20. package/dist/src/context/bm25.js +131 -0
  21. package/dist/src/context/bm25.js.map +1 -0
  22. package/dist/src/context/code-search.d.ts +30 -0
  23. package/dist/src/context/code-search.d.ts.map +1 -0
  24. package/dist/src/context/code-search.js +150 -0
  25. package/dist/src/context/code-search.js.map +1 -0
  26. package/dist/src/context/context-pack.d.ts +25 -0
  27. package/dist/src/context/context-pack.d.ts.map +1 -0
  28. package/dist/src/context/context-pack.js +92 -0
  29. package/dist/src/context/context-pack.js.map +1 -0
  30. package/dist/src/context/dependency-mapper.d.ts +10 -0
  31. package/dist/src/context/dependency-mapper.d.ts.map +1 -0
  32. package/dist/src/context/dependency-mapper.js +62 -0
  33. package/dist/src/context/dependency-mapper.js.map +1 -0
  34. package/dist/src/context/index.d.ts +8 -0
  35. package/dist/src/context/index.d.ts.map +1 -0
  36. package/dist/src/context/index.js +9 -0
  37. package/dist/src/context/index.js.map +1 -0
  38. package/dist/src/context/symbol-extractor.d.ts +26 -0
  39. package/dist/src/context/symbol-extractor.d.ts.map +1 -0
  40. package/dist/src/context/symbol-extractor.js +129 -0
  41. package/dist/src/context/symbol-extractor.js.map +1 -0
  42. package/dist/src/context/test-mapper.d.ts +16 -0
  43. package/dist/src/context/test-mapper.d.ts.map +1 -0
  44. package/dist/src/context/test-mapper.js +66 -0
  45. package/dist/src/context/test-mapper.js.map +1 -0
  46. package/dist/src/context/types.d.ts +61 -0
  47. package/dist/src/context/types.d.ts.map +1 -0
  48. package/dist/src/context/types.js +3 -0
  49. package/dist/src/context/types.js.map +1 -0
  50. package/dist/src/context/workspace-indexer.d.ts +39 -0
  51. package/dist/src/context/workspace-indexer.d.ts.map +1 -0
  52. package/dist/src/context/workspace-indexer.js +222 -0
  53. package/dist/src/context/workspace-indexer.js.map +1 -0
  54. package/dist/src/index.d.ts +5 -0
  55. package/dist/src/index.d.ts.map +1 -0
  56. package/dist/src/index.js +6 -0
  57. package/dist/src/index.js.map +1 -0
  58. package/dist/src/registries.d.ts +34 -0
  59. package/dist/src/registries.d.ts.map +1 -0
  60. package/dist/src/registries.js +87 -0
  61. package/dist/src/registries.js.map +1 -0
  62. package/dist/src/runtime/budget-manager.d.ts +42 -0
  63. package/dist/src/runtime/budget-manager.d.ts.map +1 -0
  64. package/dist/src/runtime/budget-manager.js +82 -0
  65. package/dist/src/runtime/budget-manager.js.map +1 -0
  66. package/dist/src/runtime/interactive-runtime.d.ts +39 -0
  67. package/dist/src/runtime/interactive-runtime.d.ts.map +1 -0
  68. package/dist/src/runtime/interactive-runtime.js +321 -0
  69. package/dist/src/runtime/interactive-runtime.js.map +1 -0
  70. package/dist/src/tasks-v2/analysis-tasks.d.ts +117 -0
  71. package/dist/src/tasks-v2/analysis-tasks.d.ts.map +1 -0
  72. package/dist/src/tasks-v2/analysis-tasks.js +209 -0
  73. package/dist/src/tasks-v2/analysis-tasks.js.map +1 -0
  74. package/dist/src/tasks-v2/developer-tasks.d.ts +226 -0
  75. package/dist/src/tasks-v2/developer-tasks.d.ts.map +1 -0
  76. package/dist/src/tasks-v2/developer-tasks.js +322 -0
  77. package/dist/src/tasks-v2/developer-tasks.js.map +1 -0
  78. package/dist/src/tasks-v2/index.d.ts +3 -0
  79. package/dist/src/tasks-v2/index.d.ts.map +1 -0
  80. package/dist/src/tasks-v2/index.js +4 -0
  81. package/dist/src/tasks-v2/index.js.map +1 -0
  82. package/dist/src/tools-v2/edit-tools.d.ts +67 -0
  83. package/dist/src/tools-v2/edit-tools.d.ts.map +1 -0
  84. package/dist/src/tools-v2/edit-tools.js +117 -0
  85. package/dist/src/tools-v2/edit-tools.js.map +1 -0
  86. package/dist/src/tools-v2/index.d.ts +6 -0
  87. package/dist/src/tools-v2/index.d.ts.map +1 -0
  88. package/dist/src/tools-v2/index.js +7 -0
  89. package/dist/src/tools-v2/index.js.map +1 -0
  90. package/dist/src/tools-v2/read-tools.d.ts +129 -0
  91. package/dist/src/tools-v2/read-tools.d.ts.map +1 -0
  92. package/dist/src/tools-v2/read-tools.js +216 -0
  93. package/dist/src/tools-v2/read-tools.js.map +1 -0
  94. package/dist/src/tools-v2/search-tools.d.ts +73 -0
  95. package/dist/src/tools-v2/search-tools.d.ts.map +1 -0
  96. package/dist/src/tools-v2/search-tools.js +132 -0
  97. package/dist/src/tools-v2/search-tools.js.map +1 -0
  98. package/dist/src/tools-v2/test-tools.d.ts +59 -0
  99. package/dist/src/tools-v2/test-tools.d.ts.map +1 -0
  100. package/dist/src/tools-v2/test-tools.js +111 -0
  101. package/dist/src/tools-v2/test-tools.js.map +1 -0
  102. package/dist/src/tools-v2/workspace-context.d.ts +65 -0
  103. package/dist/src/tools-v2/workspace-context.d.ts.map +1 -0
  104. package/dist/src/tools-v2/workspace-context.js +336 -0
  105. package/dist/src/tools-v2/workspace-context.js.map +1 -0
  106. package/dist/src/ui/App.d.ts +9 -0
  107. package/dist/src/ui/App.d.ts.map +1 -0
  108. package/dist/src/ui/App.js +257 -0
  109. package/dist/src/ui/App.js.map +1 -0
  110. package/dist/src/ui/components/ChatPane.d.ts +12 -0
  111. package/dist/src/ui/components/ChatPane.d.ts.map +1 -0
  112. package/dist/src/ui/components/ChatPane.js +41 -0
  113. package/dist/src/ui/components/ChatPane.js.map +1 -0
  114. package/dist/src/ui/components/EventsPane.d.ts +12 -0
  115. package/dist/src/ui/components/EventsPane.d.ts.map +1 -0
  116. package/dist/src/ui/components/EventsPane.js +48 -0
  117. package/dist/src/ui/components/EventsPane.js.map +1 -0
  118. package/dist/src/ui/components/GoalsTasksPane.d.ts +18 -0
  119. package/dist/src/ui/components/GoalsTasksPane.d.ts.map +1 -0
  120. package/dist/src/ui/components/GoalsTasksPane.js +83 -0
  121. package/dist/src/ui/components/GoalsTasksPane.js.map +1 -0
  122. package/dist/src/ui/store.d.ts +74 -0
  123. package/dist/src/ui/store.d.ts.map +1 -0
  124. package/dist/src/ui/store.js +260 -0
  125. package/dist/src/ui/store.js.map +1 -0
  126. package/dist/tests/integration.test.d.ts +2 -0
  127. package/dist/tests/integration.test.d.ts.map +1 -0
  128. package/dist/tests/integration.test.js +415 -0
  129. package/dist/tests/integration.test.js.map +1 -0
  130. package/dist/tsconfig.tsbuildinfo +1 -0
  131. package/docs/AICODER.png +0 -0
  132. package/docs/INTERACTIVE_CLI_GUIDE.md +201 -0
  133. package/docs/TUI_MOCKUP.md +180 -0
  134. package/package.json +52 -0
  135. package/src/config/providers.ts +174 -0
  136. package/src/config/session.ts +143 -0
  137. package/src/context/bm25.ts +173 -0
  138. package/src/context/code-search.ts +188 -0
  139. package/src/context/context-pack.ts +133 -0
  140. package/src/context/dependency-mapper.ts +72 -0
  141. package/src/context/index.ts +8 -0
  142. package/src/context/symbol-extractor.ts +149 -0
  143. package/src/context/test-mapper.ts +77 -0
  144. package/src/context/types.ts +69 -0
  145. package/src/context/workspace-indexer.ts +249 -0
  146. package/src/index.ts +5 -0
  147. package/src/registries.ts +118 -0
  148. package/src/runtime/budget-manager.ts +118 -0
  149. package/src/runtime/interactive-runtime.ts +423 -0
  150. package/src/tasks-v2/analysis-tasks.ts +311 -0
  151. package/src/tasks-v2/developer-tasks.ts +437 -0
  152. package/src/tasks-v2/index.ts +3 -0
  153. package/src/tools-v2/edit-tools.ts +153 -0
  154. package/src/tools-v2/index.ts +6 -0
  155. package/src/tools-v2/read-tools.ts +286 -0
  156. package/src/tools-v2/search-tools.ts +175 -0
  157. package/src/tools-v2/test-tools.ts +147 -0
  158. package/src/tools-v2/workspace-context.ts +428 -0
  159. package/src/ui/App.tsx +392 -0
  160. package/src/ui/components/ChatPane.tsx +84 -0
  161. package/src/ui/components/EventsPane.tsx +81 -0
  162. package/src/ui/components/GoalsTasksPane.tsx +149 -0
  163. package/src/ui/store.ts +362 -0
  164. package/tests/integration.test.ts +537 -0
  165. package/tsconfig.json +22 -0
package/src/ui/App.tsx ADDED
@@ -0,0 +1,392 @@
1
+ // Main TUI Application Component
2
+ // Three-column layout with command input
3
+
4
+ import React, { useState, useEffect, useMemo, useCallback } from 'react';
5
+ import { Box, Text, useInput, useApp } from 'ink';
6
+ import TextInput from 'ink-text-input';
7
+ import { ChatPane } from './components/ChatPane.js';
8
+ import { GoalsTasksPane } from './components/GoalsTasksPane.js';
9
+ import { EventsPane } from './components/EventsPane.js';
10
+ import type { AppStore } from './store.js';
11
+
12
+ type Pane = 'chat' | 'tasks' | 'events';
13
+
14
+ interface WindowMeta {
15
+ startIndex: number;
16
+ endIndex: number;
17
+ clampedOffset: number;
18
+ maxOffset: number;
19
+ capacity: number;
20
+ canScrollUp: boolean;
21
+ canScrollDown: boolean;
22
+ }
23
+
24
+ const paneLabels: Record<Pane, string> = {
25
+ chat: 'Chat',
26
+ tasks: 'Goal/Tasks',
27
+ events: 'Events',
28
+ };
29
+
30
+ const paneOrder: Pane[] = ['chat', 'tasks', 'events'];
31
+
32
+ const computeWindow = (total: number, capacity: number, requestedOffset: number): WindowMeta => {
33
+ const normalizedCapacity = Math.max(1, capacity);
34
+ const maxOffset = Math.max(0, total - normalizedCapacity);
35
+ const clampedOffset = Math.min(Math.max(requestedOffset, 0), maxOffset);
36
+ const startIndex = Math.max(0, total - normalizedCapacity - clampedOffset);
37
+ const endIndex = Math.min(total, startIndex + normalizedCapacity);
38
+
39
+ return {
40
+ startIndex,
41
+ endIndex,
42
+ clampedOffset,
43
+ maxOffset,
44
+ capacity: normalizedCapacity,
45
+ canScrollUp: clampedOffset < maxOffset,
46
+ canScrollDown: clampedOffset > 0,
47
+ };
48
+ };
49
+
50
+ interface AppProps {
51
+ store: AppStore;
52
+ onCommand: (command: string) => void;
53
+ }
54
+
55
+ export const App: React.FC<AppProps> = ({ store, onCommand }) => {
56
+ const [state, setState] = useState(store.getState());
57
+ const [input, setInput] = useState('');
58
+ const [focusedPane, setFocusedPane] = useState<Pane>('chat');
59
+ const [scrollOffsets, setScrollOffsets] = useState<{ chat: number; tasks: number; events: number }>(
60
+ { chat: 0, tasks: 0, events: 0 }
61
+ );
62
+ const { exit } = useApp();
63
+
64
+ useEffect(() => {
65
+ const handleUpdate = () => {
66
+ setState(store.getState());
67
+ };
68
+
69
+ store.on('update', handleUpdate);
70
+ return () => {
71
+ store.off('update', handleUpdate);
72
+ };
73
+ }, [store]);
74
+
75
+ const handleSubmit = (value: string) => {
76
+ if (!value.trim()) return;
77
+
78
+ // Handle built-in commands
79
+ if (value === '/exit' || value === '/quit') {
80
+ exit();
81
+ return;
82
+ }
83
+
84
+ if (value === '/help') {
85
+ store.addMessage('system', `
86
+ Available commands:
87
+ /exit, /quit - Exit the application
88
+ /help - Show this help
89
+ /retry - Retry the last failed task
90
+ /skip - Skip the current task
91
+ /context - Show current context info
92
+ /budget - Show token budget details
93
+ /reset - Reset session (clear goal and tasks)
94
+ /reasoning - Toggle nucleus reasoning messages
95
+
96
+ Pane controls:
97
+ Tab - Switch focused column
98
+ Ctrl+↑ / Ctrl+↓ - Scroll active column
99
+ PgUp / PgDn - Page through active column
100
+ Ctrl+C - Exit immediately
101
+
102
+ File mentions:
103
+ Use #path/to/file to reference files in your workspace
104
+
105
+ Type your goal or request to start planning.
106
+ `.trim());
107
+ setInput('');
108
+ return;
109
+ }
110
+
111
+ if (value === '/reset') {
112
+ store.reset();
113
+ setInput('');
114
+ return;
115
+ }
116
+
117
+ if (value === '/budget' && state.budgetStatus) {
118
+ const bs = state.budgetStatus;
119
+ store.addMessage('system',
120
+ `Token Usage:\n` +
121
+ ` Total Tokens: ${bs.totalTokens}\n` +
122
+ ` Input Tokens: ${bs.totalInputTokens}\n` +
123
+ ` Output Tokens: ${bs.totalOutputTokens}\n` +
124
+ (bs.maxTokens !== undefined ? ` Allowance: ${bs.maxTokens}\n` : '') +
125
+ (bs.remainingTokens !== undefined ? ` Remaining: ${bs.remainingTokens}\n` : '') +
126
+ ` API Calls: ${bs.callCount}`
127
+ );
128
+ setInput('');
129
+ return;
130
+ }
131
+
132
+ if (value === '/context' && state.currentContext) {
133
+ store.addMessage('system',
134
+ `Current Context:\n` +
135
+ ` ID: ${state.currentContext.id}\n` +
136
+ ` Facts: ${Object.keys(state.currentContext.facts || {}).length} entries`
137
+ );
138
+ setInput('');
139
+ return;
140
+ }
141
+
142
+ if (value === '/reasoning') {
143
+ const enabled = store.toggleReasoningVisible();
144
+ store.addMessage(
145
+ 'system',
146
+ `Nucleus reasoning messages ${enabled ? 'enabled' : 'hidden'} (Ctrl+R to toggle).`
147
+ );
148
+ setInput('');
149
+ return;
150
+ }
151
+
152
+ // Pass command to handler
153
+ onCommand(value);
154
+ setInput('');
155
+ };
156
+
157
+ // Calculate terminal dimensions (approximate)
158
+ const termHeight = process.stdout.rows || 24;
159
+ const termWidth = process.stdout.columns || 80;
160
+
161
+ const mainHeight = termHeight - 4; // Leave room for input
162
+ const col1Width = Math.floor(termWidth * 0.40);
163
+ const col2Width = Math.floor(termWidth * 0.30);
164
+ const col3Width = termWidth - col1Width - col2Width - 4;
165
+
166
+ const visibleMessages = useMemo(
167
+ () =>
168
+ state.showReasoning
169
+ ? state.messages
170
+ : state.messages.filter(message => message.role !== 'nucleus'),
171
+ [state.messages, state.showReasoning]
172
+ );
173
+
174
+ const chatCapacity = Math.max(1, mainHeight - 2);
175
+ const chatWindow = useMemo(
176
+ () => computeWindow(visibleMessages.length, chatCapacity, scrollOffsets.chat),
177
+ [visibleMessages.length, chatCapacity, scrollOffsets.chat]
178
+ );
179
+ const chatMessages = useMemo(
180
+ () => visibleMessages.slice(chatWindow.startIndex, chatWindow.endIndex),
181
+ [visibleMessages, chatWindow]
182
+ );
183
+
184
+ const tasksTotalEntries = useMemo(() => {
185
+ const goalEntry = state.currentGoal ? 1 : 0;
186
+ const summaryEntry = state.goalSummary ? 1 : 0;
187
+ const tasksHeader = state.tasks.length > 0 ? 1 : 0;
188
+ const taskEntries = state.tasks.length;
189
+ const contextHeader = state.currentContext ? 1 : 0;
190
+ const contextLines = state.currentContext ? 2 : 0;
191
+ const placeholderEntry = state.currentGoal ? 0 : 1;
192
+ return (
193
+ goalEntry +
194
+ summaryEntry +
195
+ tasksHeader +
196
+ taskEntries +
197
+ contextHeader +
198
+ contextLines +
199
+ placeholderEntry
200
+ );
201
+ }, [state.currentGoal, state.goalSummary, state.tasks, state.currentContext]);
202
+ const tasksCapacity = Math.max(1, mainHeight - 2);
203
+ const tasksWindow = useMemo(
204
+ () => computeWindow(tasksTotalEntries, tasksCapacity, scrollOffsets.tasks),
205
+ [tasksTotalEntries, tasksCapacity, scrollOffsets.tasks]
206
+ );
207
+
208
+ const eventsCapacity = Math.max(1, mainHeight - 2);
209
+ const eventsWindow = useMemo(
210
+ () => computeWindow(state.events.length, eventsCapacity, scrollOffsets.events),
211
+ [state.events.length, eventsCapacity, scrollOffsets.events]
212
+ );
213
+ const eventEntries = useMemo(
214
+ () => state.events.slice(eventsWindow.startIndex, eventsWindow.endIndex),
215
+ [state.events, eventsWindow]
216
+ );
217
+
218
+ const windowByPane = useMemo(
219
+ () => ({ chat: chatWindow, tasks: tasksWindow, events: eventsWindow }),
220
+ [chatWindow, tasksWindow, eventsWindow]
221
+ );
222
+
223
+ useEffect(() => {
224
+ if (scrollOffsets.chat !== chatWindow.clampedOffset) {
225
+ setScrollOffsets(prev =>
226
+ prev.chat === chatWindow.clampedOffset
227
+ ? prev
228
+ : { ...prev, chat: chatWindow.clampedOffset }
229
+ );
230
+ }
231
+ }, [chatWindow.clampedOffset, scrollOffsets.chat]);
232
+
233
+ useEffect(() => {
234
+ if (scrollOffsets.tasks !== tasksWindow.clampedOffset) {
235
+ setScrollOffsets(prev =>
236
+ prev.tasks === tasksWindow.clampedOffset
237
+ ? prev
238
+ : { ...prev, tasks: tasksWindow.clampedOffset }
239
+ );
240
+ }
241
+ }, [tasksWindow.clampedOffset, scrollOffsets.tasks]);
242
+
243
+ useEffect(() => {
244
+ if (scrollOffsets.events !== eventsWindow.clampedOffset) {
245
+ setScrollOffsets(prev =>
246
+ prev.events === eventsWindow.clampedOffset
247
+ ? prev
248
+ : { ...prev, events: eventsWindow.clampedOffset }
249
+ );
250
+ }
251
+ }, [eventsWindow.clampedOffset, scrollOffsets.events]);
252
+
253
+ const setPaneOffset = useCallback(
254
+ (pane: Pane, offset: number) => {
255
+ const meta = windowByPane[pane];
256
+ if (!meta) return;
257
+ const clamped = Math.min(Math.max(offset, 0), meta.maxOffset);
258
+ setScrollOffsets(prev =>
259
+ prev[pane] === clamped ? prev : { ...prev, [pane]: clamped }
260
+ );
261
+ },
262
+ [windowByPane]
263
+ );
264
+
265
+ const adjustScroll = useCallback(
266
+ (pane: Pane, delta: number) => {
267
+ const meta = windowByPane[pane];
268
+ if (!meta) return;
269
+ setPaneOffset(pane, meta.clampedOffset + delta);
270
+ },
271
+ [setPaneOffset, windowByPane]
272
+ );
273
+
274
+ useInput((inputKey, key) => {
275
+ if (key.tab) {
276
+ setFocusedPane(prev => {
277
+ const idx = paneOrder.indexOf(prev);
278
+ const nextIdx = key.shift
279
+ ? (idx - 1 + paneOrder.length) % paneOrder.length
280
+ : (idx + 1) % paneOrder.length;
281
+ return paneOrder[nextIdx];
282
+ });
283
+ return;
284
+ }
285
+
286
+ if (key.ctrl && inputKey && inputKey.toLowerCase() === 'r') {
287
+ const enabled = store.toggleReasoningVisible();
288
+ store.addMessage(
289
+ 'system',
290
+ `Nucleus reasoning messages ${enabled ? 'enabled' : 'hidden'} (Ctrl+R to toggle).`
291
+ );
292
+ return;
293
+ }
294
+
295
+ const meta = windowByPane[focusedPane];
296
+ if (!meta) return;
297
+
298
+ const lineScrollModifier = key.ctrl || key.meta || (('alt' in key) && (key as any).alt);
299
+
300
+ if ((lineScrollModifier || key.shift) && key.upArrow) {
301
+ adjustScroll(focusedPane, 1);
302
+ return;
303
+ }
304
+
305
+ if ((lineScrollModifier || key.shift) && key.downArrow) {
306
+ adjustScroll(focusedPane, -1);
307
+ return;
308
+ }
309
+
310
+ if (key.pageUp || (key.shift && key.upArrow)) {
311
+ adjustScroll(focusedPane, meta.capacity);
312
+ return;
313
+ }
314
+
315
+ if (key.pageDown || (key.shift && key.downArrow)) {
316
+ adjustScroll(focusedPane, -meta.capacity);
317
+ return;
318
+ }
319
+ });
320
+
321
+ return (
322
+ <Box flexDirection="column" height={termHeight}>
323
+ {/* Header */}
324
+ <Box borderStyle="double" borderColor="cyan" paddingX={1}>
325
+ <Text bold color="cyan">ACM AI Coder - Interactive Mode</Text>
326
+ <Box marginLeft={2}>
327
+ <Text color="cyan">Focus: {paneLabels[focusedPane]}</Text>
328
+ <Text>
329
+ {' '}
330
+ <Text color="gray">Tab</Text> = Switch
331
+ {' '}
332
+ <Text color="gray">Shift+Tab</Text> = Reverse
333
+ {' '}
334
+ <Text color="gray">Ctrl+R</Text> = Reasoning Toggle
335
+ {' '}
336
+ <Text color="gray">Ctrl+↑/↓</Text> = Scroll
337
+ {' '}
338
+ <Text color="gray">PgUp/PgDn</Text> = Page
339
+ {' '}
340
+ <Text color="gray">Ctrl+C</Text> = Exit
341
+ </Text>
342
+ </Box>
343
+ </Box>
344
+
345
+ <Box flexGrow={1} flexDirection="row" gap={2} paddingX={1}>
346
+ <Box width={col1Width}>
347
+ <ChatPane
348
+ messages={chatMessages}
349
+ height={mainHeight}
350
+ canScrollUp={chatWindow.canScrollUp}
351
+ canScrollDown={chatWindow.canScrollDown}
352
+ focused={focusedPane === 'chat'}
353
+ />
354
+ </Box>
355
+ <Box width={col2Width}>
356
+ <GoalsTasksPane
357
+ goal={state.currentGoal}
358
+ plan={state.currentPlan}
359
+ context={state.currentContext}
360
+ tasks={state.tasks}
361
+ goalSummary={state.goalSummary}
362
+ height={mainHeight}
363
+ scrollOffset={tasksWindow.clampedOffset}
364
+ canScrollUp={tasksWindow.canScrollUp}
365
+ canScrollDown={tasksWindow.canScrollDown}
366
+ focused={focusedPane === 'tasks'}
367
+ />
368
+ </Box>
369
+ <Box width={col3Width}>
370
+ <EventsPane
371
+ events={eventEntries}
372
+ height={mainHeight}
373
+ canScrollUp={eventsWindow.canScrollUp}
374
+ canScrollDown={eventsWindow.canScrollDown}
375
+ focused={focusedPane === 'events'}
376
+ />
377
+ </Box>
378
+ </Box>
379
+
380
+ {/* Command input */}
381
+ <Box borderStyle="single" borderColor="cyan" paddingX={1}>
382
+ <Text color="cyan" bold>&gt; </Text>
383
+ <TextInput
384
+ value={input}
385
+ onChange={setInput}
386
+ onSubmit={handleSubmit}
387
+ placeholder="Type your goal or command (e.g., /help)..."
388
+ />
389
+ </Box>
390
+ </Box>
391
+ );
392
+ };
@@ -0,0 +1,84 @@
1
+ // Chat Pane Component
2
+ // Displays user messages, planner reasoning, and nucleus thoughts
3
+
4
+ import React from 'react';
5
+ import { Box, Text } from 'ink';
6
+ import type { ChatMessage } from '../store.js';
7
+
8
+ interface ChatPaneProps {
9
+ messages: ChatMessage[];
10
+ height: number;
11
+ canScrollUp: boolean;
12
+ canScrollDown: boolean;
13
+ focused: boolean;
14
+ }
15
+
16
+ export const ChatPane: React.FC<ChatPaneProps> = ({
17
+ messages,
18
+ height,
19
+ canScrollUp,
20
+ canScrollDown,
21
+ focused,
22
+ }) => {
23
+
24
+ return (
25
+ <Box
26
+ flexDirection="column"
27
+ height={height}
28
+ borderStyle="single"
29
+ borderColor={focused ? 'white' : 'cyan'}
30
+ >
31
+ <Box paddingX={1} borderStyle="single" borderColor={focused ? 'white' : 'cyan'}>
32
+ <Text bold color={focused ? 'white' : 'cyan'}>Chat</Text>
33
+ <Box flexGrow={1} />
34
+ <ScrollIndicator
35
+ up={canScrollUp}
36
+ down={canScrollDown}
37
+ color={focused ? 'white' : 'cyan'}
38
+ />
39
+ </Box>
40
+ <Box flexDirection="column" paddingX={1} flexGrow={1}>
41
+ {messages.length === 0 && (
42
+ <Text color="gray">Conversation will appear here.</Text>
43
+ )}
44
+ {messages.map(msg => (
45
+ <ChatMessageItem key={msg.id} message={msg} />
46
+ ))}
47
+ </Box>
48
+ </Box>
49
+ );
50
+ };
51
+
52
+ const ChatMessageItem: React.FC<{ message: ChatMessage }> = ({ message }) => {
53
+ const roleColors = {
54
+ user: 'white',
55
+ planner: 'yellow',
56
+ nucleus: 'magenta',
57
+ system: 'gray',
58
+ };
59
+
60
+ const roleLabels = {
61
+ user: 'You',
62
+ planner: 'Planner',
63
+ nucleus: 'Nucleus',
64
+ system: 'System',
65
+ };
66
+
67
+ const color = roleColors[message.role] || 'white';
68
+ const label = roleLabels[message.role] || message.role;
69
+
70
+ return (
71
+ <Box flexDirection="column" marginY={0}>
72
+ <Text color={color} bold>
73
+ {label}{message.streaming ? ' (streaming...)' : ''}:
74
+ </Text>
75
+ <Text color={color}>
76
+ {message.content || '...'}
77
+ </Text>
78
+ </Box>
79
+ );
80
+ };
81
+
82
+ const ScrollIndicator: React.FC<{ up: boolean; down: boolean; color: string }> = ({ up, down, color }) => (
83
+ <Text color={color}>{up ? '˄' : ' '} {down ? '˅' : ' '}</Text>
84
+ );
@@ -0,0 +1,81 @@
1
+ // Event Stream Pane Component
2
+ // Shows ledger events, tool calls, context updates, policy decisions
3
+
4
+ import React from 'react';
5
+ import { Box, Text } from 'ink';
6
+ import type { EventEntry } from '../store.js';
7
+
8
+ interface EventsPaneProps {
9
+ events: EventEntry[];
10
+ height: number;
11
+ canScrollUp: boolean;
12
+ canScrollDown: boolean;
13
+ focused: boolean;
14
+ }
15
+
16
+ export const EventsPane: React.FC<EventsPaneProps> = ({
17
+ events,
18
+ height,
19
+ canScrollUp,
20
+ canScrollDown,
21
+ focused,
22
+ }) => {
23
+
24
+ return (
25
+ <Box
26
+ flexDirection="column"
27
+ height={height}
28
+ borderStyle="single"
29
+ borderColor={focused ? 'white' : 'blue'}
30
+ >
31
+ <Box paddingX={1} borderStyle="single" borderColor={focused ? 'white' : 'blue'}>
32
+ <Text bold color={focused ? 'white' : 'blue'}>Event Stream</Text>
33
+ <Box flexGrow={1} />
34
+ <ScrollIndicator
35
+ up={canScrollUp}
36
+ down={canScrollDown}
37
+ color={focused ? 'white' : 'blue'}
38
+ />
39
+ </Box>
40
+ <Box flexDirection="column" paddingX={1} flexGrow={1}>
41
+ {events.map(event => (
42
+ <EventItem key={event.id} event={event} />
43
+ ))}
44
+ {events.length === 0 && (
45
+ <Text color="gray">No events yet. Events will appear here as actions occur.</Text>
46
+ )}
47
+ </Box>
48
+ </Box>
49
+ );
50
+ };
51
+
52
+ const EventItem: React.FC<{ event: EventEntry }> = ({ event }) => {
53
+ const color = event.color || 'white';
54
+
55
+ // Format event data for display
56
+ const formatData = (data: any): string => {
57
+ if (typeof data === 'string') return data;
58
+ if (typeof data === 'object') {
59
+ // Show a summary for objects
60
+ if (data.message) return data.message;
61
+ if (data.status) return `status: ${data.status}`;
62
+ if (data.name) return data.name;
63
+ return JSON.stringify(data).slice(0, 50);
64
+ }
65
+ return String(data);
66
+ };
67
+
68
+ const timestamp = new Date(event.timestamp).toLocaleTimeString();
69
+
70
+ return (
71
+ <Box>
72
+ <Text color="gray">[{timestamp}] </Text>
73
+ <Text color={color} bold>{event.type}</Text>
74
+ <Text>: {formatData(event.data)}</Text>
75
+ </Box>
76
+ );
77
+ };
78
+
79
+ const ScrollIndicator: React.FC<{ up: boolean; down: boolean; color: string }> = ({ up, down, color }) => (
80
+ <Text color={color}>{up ? '˄' : ' '} {down ? '˅' : ' '}</Text>
81
+ );
@@ -0,0 +1,149 @@
1
+ // Goals and Tasks Pane Component
2
+ // Shows current goal, plan, tasks with status, and budget info
3
+
4
+ import React from 'react';
5
+ import { Box, Text } from 'ink';
6
+ import type { Goal, Plan, Context } from '@ddse/acm-sdk';
7
+ import type { TaskState } from '../store.js';
8
+
9
+ interface GoalsTasksPaneProps {
10
+ goal?: Goal;
11
+ plan?: Plan;
12
+ context?: Context;
13
+ tasks: TaskState[];
14
+ goalSummary?: string;
15
+ height: number;
16
+ scrollOffset: number;
17
+ canScrollUp: boolean;
18
+ canScrollDown: boolean;
19
+ focused: boolean;
20
+ }
21
+
22
+ export const GoalsTasksPane: React.FC<GoalsTasksPaneProps> = ({
23
+ goal,
24
+ plan,
25
+ context,
26
+ tasks,
27
+ goalSummary,
28
+ height,
29
+ scrollOffset,
30
+ canScrollUp,
31
+ canScrollDown,
32
+ focused,
33
+ }) => {
34
+ const rows: React.ReactNode[] = [];
35
+
36
+ if (goal) {
37
+ rows.push(
38
+ <Box flexDirection="column" marginBottom={1} key="goal">
39
+ <Text bold color="green">Goal:</Text>
40
+ <Text>{goal.intent || goal.id}</Text>
41
+ {goalSummary && (
42
+ <Box marginTop={1} flexDirection="column">
43
+ <Text bold color="cyan">Summary</Text>
44
+ <Text color="cyan">{goalSummary}</Text>
45
+ </Box>
46
+ )}
47
+ </Box>
48
+ );
49
+ }
50
+
51
+ if (tasks.length > 0) {
52
+ rows.push(
53
+ <Box flexDirection="column" marginBottom={1} key="tasks-header">
54
+ <Text bold color="green">Tasks:</Text>
55
+ </Box>
56
+ );
57
+
58
+ tasks.forEach(task => {
59
+ rows.push(<TaskItem key={task.id} task={task} />);
60
+ });
61
+ }
62
+
63
+ if (context) {
64
+ const factsCount = Object.keys(context.facts ?? {}).length;
65
+ const augmentationCount = context.augmentations?.length ?? 0;
66
+ const augmentationLabel = augmentationCount === 1 ? 'time' : 'times';
67
+
68
+ rows.push(
69
+ <Box flexDirection="column" marginBottom={1} key="context-info">
70
+ <Text bold color="green">Context Information:</Text>
71
+ <Text color="gray"> - current size: {factsCount}</Text>
72
+ <Text color="gray"> - augmented {augmentationCount} {augmentationLabel}</Text>
73
+ </Box>
74
+ );
75
+ }
76
+
77
+ if (!goal) {
78
+ rows.push(
79
+ <Box key="no-goal">
80
+ <Text color="gray">No active goal. Type a command to start.</Text>
81
+ </Box>
82
+ );
83
+ }
84
+
85
+ const capacity = Math.max(1, height - 2);
86
+ const maxOffset = Math.max(0, rows.length - capacity);
87
+ const effectiveOffset = Math.min(Math.max(scrollOffset, 0), maxOffset);
88
+ const start = Math.max(0, rows.length - capacity - effectiveOffset);
89
+ const visibleRows = rows.slice(start, start + capacity);
90
+
91
+ return (
92
+ <Box
93
+ flexDirection="column"
94
+ height={height}
95
+ borderStyle="single"
96
+ borderColor={focused ? 'white' : 'green'}
97
+ >
98
+ <Box paddingX={1} borderStyle="single" borderColor={focused ? 'white' : 'green'}>
99
+ <Text bold color={focused ? 'white' : 'green'}>Goal / Tasks / Progress</Text>
100
+ <Box flexGrow={1} />
101
+ <ScrollIndicator
102
+ up={canScrollUp}
103
+ down={canScrollDown}
104
+ color={focused ? 'white' : 'green'}
105
+ />
106
+ </Box>
107
+ <Box flexDirection="column" paddingX={1} flexGrow={1}>
108
+ {visibleRows}
109
+ </Box>
110
+ </Box>
111
+ );
112
+ };
113
+
114
+ const TaskItem: React.FC<{ task: TaskState }> = ({ task }) => {
115
+ const statusIcons = {
116
+ pending: '○',
117
+ running: '◐',
118
+ succeeded: '●',
119
+ failed: '✗',
120
+ retrying: '⟳',
121
+ };
122
+
123
+ const statusColors = {
124
+ pending: 'gray',
125
+ running: 'yellow',
126
+ succeeded: 'green',
127
+ failed: 'red',
128
+ retrying: 'yellow',
129
+ };
130
+
131
+ const icon = statusIcons[task.status] || '○';
132
+ const color = statusColors[task.status] || 'white';
133
+
134
+ const label = (task.objective && task.objective.trim().length > 0)
135
+ ? task.objective.trim()
136
+ : (task.title ?? task.name);
137
+
138
+ return (
139
+ <Box>
140
+ <Text color={color}>
141
+ {icon} {label}
142
+ </Text>
143
+ </Box>
144
+ );
145
+ };
146
+
147
+ const ScrollIndicator: React.FC<{ up: boolean; down: boolean; color: string }> = ({ up, down, color }) => (
148
+ <Text color={color}>{up ? '˄' : ' '} {down ? '˅' : ' '}</Text>
149
+ );