openaxies 0.6.0 → 1.0.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.
@@ -0,0 +1,6 @@
1
+ The above error occurred in the <AppRoot> component:
2
+
3
+ at AppRoot (file:///storage/emulated/0/agent-open/openaxis/src/App.js:60:22)
4
+ at App (file:///storage/emulated/0/agent-open/openaxis/node_modules/ink/build/components/App.js:17:16)
5
+
6
+ React will try to recreate this component tree from scratch using the error boundary you provided, InternalApp.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openaxies",
3
- "version": "0.6.0",
3
+ "version": "1.0.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
package/src/App.js CHANGED
@@ -1,434 +1,191 @@
1
1
  import React from 'react';
2
- import { Box, Text, useInput, useStdout } from 'ink';
2
+ import { Box, Text, useStdout } from 'ink';
3
+ import TextInput from 'ink-text-input';
3
4
  import { hex } from './config/theme.js';
4
-
5
5
  import { getModels, getModelById, DEFAULT_MODEL_ID } from './config/models.js';
6
6
  import { callModel } from './providers/index.js';
7
- import { createBrandHeader, BRAND_HEIGHT } from './components/BrandHeader.js';
8
- import TextInput from 'ink-text-input';
7
+ import { Typography } from './components/ui/Typography.js';
8
+ import { Separator } from './components/ui/Separator.js';
9
+ import { ActivityItem } from './components/timeline/ActivityItem.js';
10
+ import { TodoTracker } from './components/timeline/TodoTracker.js';
9
11
 
10
12
  const h = React.createElement;
11
13
 
12
- export default AppRoot;
13
-
14
- function buildHistory(messages, newText) {
15
- const h = [];
16
- for (let i = 0; i < messages.length; i++) {
17
- const m = messages[i];
18
- if (m === null || m === undefined || typeof m !== 'object') continue;
19
- if (m.role === 'user' || m.role === 'assistant') {
20
- const c = typeof m.content === 'string' ? m.content : '';
21
- h.push({ role: m.role, content: c });
22
- }
23
- }
24
- if (typeof newText === 'string' && newText.length > 0) {
25
- h.push({ role: 'user', content: newText });
26
- }
27
- return h;
28
- }
29
-
30
- function fmtTime(s) {
31
- if (typeof s !== 'number' || s < 0) return '0.0s';
32
- return s.toFixed(1) + 's';
33
- }
34
-
35
- function hr(cols) {
36
- const n = typeof cols === 'number' && cols > 0 ? cols : 80;
37
- let s = '';
38
- for (let i = 0; i < n; i++) s = s + '\u2500';
39
- return h(Text, { color: '#1A1A28' }, s);
40
- }
41
-
42
- const SPINNER = ['\u25CB', '\u25D4', '\u25D0', '\u25D5', '\u25CF', '\u25D5', '\u25D0', '\u25D4', '\u25D1', '\u25D2', '\u25D3'];
43
-
44
- const COMMANDS = [
45
- { id: 'model', trigger: '/model', desc: 'Switch model' },
46
- { id: 'thoughts', trigger: '/thoughts', desc: 'Toggle thinking visibility' },
47
- { id: 'resume', trigger: '/resume', desc: 'Re-run last query' },
48
- { id: 'clear', trigger: '/clear', desc: 'Clear conversation' },
49
- { id: 'help', trigger: '/help', desc: 'Show commands' },
50
- { id: 'exit', trigger: '/exit', desc: 'Quit' },
14
+ const STARTUP_LOGO = [
15
+ ' \u2588\u2588\u2588\u2588\u2588\u2588\u2557',
16
+ ' \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557',
17
+ ' \u2588\u2588\u2551 \u2588\u2588\u2551',
18
+ ' \u2588\u2588\u2551 \u2588\u2588\u2551',
19
+ ' \u255a\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d',
51
20
  ];
52
21
 
53
- function AppRoot() {
22
+ export default function AppRoot() {
54
23
  const { stdout } = useStdout();
55
24
  const rows = stdout.rows || 24;
56
25
  const cols = stdout.columns || 80;
57
26
 
58
27
  const [messages, setMessages] = React.useState([]);
59
28
  const [activeModel, setActiveModel] = React.useState(DEFAULT_MODEL_ID);
60
- const [streamText, setStreamText] = React.useState('');
29
+ const [inputBuffer, setInputBuffer] = React.useState('');
61
30
  const [streamingActive, setStreamingActive] = React.useState(false);
62
- const [isThinking, setIsThinking] = React.useState(false);
63
- const [spinnerIdx, setSpinnerIdx] = React.useState(0);
31
+ const [streamContent, setStreamContent] = React.useState('');
32
+ const [thoughtContent, setThoughtContent] = React.useState('');
64
33
  const [thinkingElapsed, setThinkingElapsed] = React.useState(0);
65
- const [inputBuffer, setInputBuffer] = React.useState('');
66
- const [showOverlay, setShowOverlay] = React.useState(false);
67
- const [overlayIndex, setOverlayIndex] = React.useState(0);
68
- const [toolInfo, setToolInfo] = React.useState(null);
69
- const [showThoughts, setShowThoughts] = React.useState(true);
70
-
71
- const STATUS_H = 3;
72
- const DOCK_H = 1;
73
- const fixedH = BRAND_HEIGHT + STATUS_H + DOCK_H;
74
- const availLines = Math.max(10, rows - fixedH);
34
+ const [isThinking, setIsThinking] = React.useState(false);
35
+ const [startupDone, setStartupDone] = React.useState(false);
36
+ const [todos, setTodos] = React.useState([]);
75
37
 
76
- const abortRef = React.useRef(null);
77
- const connRef = React.useRef(null);
78
- const spinnerRef = React.useRef(null);
79
- const timerRef = React.useRef(null);
80
- const thinkStartRef = React.useRef(null);
81
- const lastQueryRef = React.useRef('');
38
+ const abortControllerRef = React.useRef(null);
82
39
 
83
- React.useEffect(function () {
84
- if (isThinking === false) return;
85
- let idx = 0;
86
- const si = setInterval(function () { idx = (idx + 1) % SPINNER.length; setSpinnerIdx(idx); }, 120);
87
- const ti = setInterval(function () {
88
- if (thinkStartRef.current !== null) setThinkingElapsed((Date.now() - thinkStartRef.current) / 1000);
89
- }, 100);
90
- spinnerRef.current = si;
91
- timerRef.current = ti;
92
- return function () {
93
- clearInterval(si); clearInterval(ti);
94
- };
40
+ React.useEffect(() => {
41
+ let timer;
42
+ if (isThinking) {
43
+ timer = setInterval(() => {
44
+ setThinkingElapsed(prev => prev + 0.1);
45
+ }, 100);
46
+ }
47
+ return () => clearInterval(timer);
95
48
  }, [isThinking]);
96
49
 
97
- function cycleModel() {
98
- const models = getModels();
99
- setActiveModel(function (prev) {
100
- let found = false;
101
- for (let i = 0; i < models.length; i++) {
102
- if (found) return models[i].id;
103
- if (models[i].id === prev) found = true;
104
- }
105
- return models[0].id;
106
- });
107
- }
108
-
109
- function checkThink(text) {
110
- const o = text.lastIndexOf('<think>');
111
- const c = text.lastIndexOf('</think>');
112
- if (o === -1 && c === -1) return false;
113
- return o > c;
114
- }
50
+ const model = getModelById(activeModel);
51
+ const modelLabel = model ? model.label : 'Unknown Model';
115
52
 
116
53
  async function handleSubmit(text) {
117
- const safe = typeof text === 'string' ? text : '';
118
- if (safe.length === 0 || streamingActive === true) return;
119
- lastQueryRef.current = safe;
54
+ if (!text || streamingActive) return;
55
+ if (!startupDone) setStartupDone(true);
56
+
57
+ const userMsg = { role: 'user', content: text };
58
+ setMessages(prev => [...prev, userMsg]);
59
+ setInputBuffer('');
120
60
  setStreamingActive(true);
121
- setToolInfo(null);
122
- setStreamText(' connecting');
61
+ setStreamContent('');
62
+ setThoughtContent('');
123
63
  setThinkingElapsed(0);
124
- connRef.current = setInterval(function () {
125
- setStreamText(function (p) {
126
- if (p === ' connecting') return ' connecting.';
127
- if (p === ' connecting.') return ' connecting..';
128
- if (p === ' connecting..') return ' connecting...';
129
- return ' connecting';
130
- });
131
- }, 400);
132
- setMessages(function (p) { return p.concat([{ role: 'user', content: safe, id: 'u-' + Date.now() }]); });
133
- setInputBuffer('');
134
- const modelInfo = getModelById(activeModel);
135
- if (modelInfo === null) {
136
- if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null;
137
- setStreamingActive(false); setStreamText('');
138
- setMessages(function (p) { return p.concat([{ role: 'assistant', content: 'No model selected.', id: 'e-' + Date.now() }]); });
139
- return;
140
- }
141
- const history = buildHistory(messages, safe);
142
- const abortController = new AbortController();
143
- abortRef.current = abortController;
64
+
65
+ abortControllerRef.current = new AbortController();
66
+
144
67
  try {
145
- const gen = callModel(modelInfo, history, abortController.signal);
146
- let accumulated = '';
147
- let firstEvent = true;
148
- let thinkStarted = false;
149
- for await (const event of gen) {
150
- if (abortController.signal.aborted === true) break;
151
- if (firstEvent === true) { firstEvent = false; if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null; }
152
- if (event.type === 'tool') { setToolInfo({ tool: event.tool, used: event.used, sites: event.sites, query: event.query }); continue; }
153
- if (event.type === 'thinking' || event.type === 'token') {
154
- const c = typeof event.content === 'string' ? event.content : '';
155
- accumulated = accumulated + c;
156
- const inside = checkThink(accumulated);
157
- setStreamText(accumulated);
158
- setIsThinking(inside);
159
- if (inside === true && thinkStarted === false) { thinkStarted = true; thinkStartRef.current = Date.now(); setThinkingElapsed(0); }
160
- if (inside === false && thinkStarted === true) { thinkStarted = false; thinkStartRef.current = null; setThinkingElapsed(0); }
161
- }
162
- if (event.type === 'done') break;
163
- if (event.type === 'timeout') {
164
- if (accumulated.length > 0) accumulated = accumulated + '\n\n\u2014 stream timed out \u2014';
165
- setStreamText(accumulated); break;
68
+ const history = [...messages, userMsg];
69
+ const stream = callModel({ id: activeModel }, history, abortControllerRef.current.signal);
70
+
71
+ for await (const event of stream) {
72
+ if (event.type === 'tool') {
73
+ setMessages(prev => [...prev, { type: 'tool', tool: event.tool, query: event.query }]);
74
+ } else if (event.type === 'token') {
75
+ const content = event.content;
76
+ if (content.includes('<think>')) {
77
+ setIsThinking(true);
78
+ setThoughtContent(prev => prev + content.replace('<think>', ''));
79
+ } else if (content.includes('</think>')) {
80
+ setIsThinking(false);
81
+ setThoughtContent(prev => prev + content.replace('</think>', ''));
82
+ setMessages(prev => [...prev, { type: 'thought', content: thoughtContent, elapsed: thinkingElapsed.toFixed(1) + 's' }]);
83
+ } else if (isThinking) {
84
+ setThoughtContent(prev => prev + content);
85
+ } else {
86
+ setStreamContent(prev => prev + content);
87
+ }
166
88
  }
167
89
  }
168
- if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null;
169
- setIsThinking(false); thinkStartRef.current = null; setThinkingElapsed(0); setStreamingActive(false);
170
- setStreamText('');
171
- if (accumulated.length > 0) {
172
- setMessages(function (p) { return p.concat([{ role: 'assistant', content: accumulated, id: 'a-' + Date.now() }]); });
90
+ } catch (e) {
91
+ setMessages(prev => [...prev, { type: 'assistant', content: 'Error connecting to model.' }]);
92
+ } finally {
93
+ setStreamingActive(false);
94
+ setIsThinking(false);
95
+ if (streamContent) {
96
+ setMessages(prev => [...prev, { type: 'assistant', content: streamContent }]);
173
97
  }
174
- } catch (err) {
175
- if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null;
176
- setIsThinking(false); thinkStartRef.current = null; setThinkingElapsed(0); setStreamingActive(false); setStreamText('');
177
- const errMsg = err !== null && err !== undefined ? (err.message || String(err)) : 'Unknown error';
178
- setMessages(function (p) { return p.concat([{ role: 'assistant', content: 'Error: ' + errMsg, id: 'er-' + Date.now() }]); });
179
98
  }
180
99
  }
181
100
 
182
- function handleChange(v) {
183
- setInputBuffer(v);
184
- if (v.length === 1 && v[0] === '/') { setShowOverlay(true); setOverlayIndex(0); }
185
- if (showOverlay === true && (v.length === 0 || v[0] !== '/')) { setShowOverlay(false); }
186
- }
187
-
188
- function handleCmd(item) {
189
- if (item === null || item === undefined) return;
190
- const t = item.trigger;
191
- if (typeof t !== 'string') return;
192
- if (t === '/exit') process.exit(0);
193
- if (t === '/clear') { setMessages([]); }
194
- if (t === '/model') cycleModel();
195
- if (t === '/thoughts') { setShowThoughts(function (p) { return !p; }); }
196
- if (t === '/resume') {
197
- const q = lastQueryRef.current;
198
- if (q.length > 0 && streamingActive === false) handleSubmit(q);
101
+ const renderHeader = () => {
102
+ if (!startupDone) return null;
103
+ return h(Box, { height: 1, paddingLeft: 1, paddingRight: 1 },
104
+ h(Typography.Header, 'OpenAxies'),
105
+ h(Typography.Muted, ` \u2022 ${modelLabel}`),
106
+ h(Box, { flexGrow: 1 }),
107
+ h(Typography.Dim, 'local')
108
+ );
109
+ };
110
+
111
+ const renderFooter = () => {
112
+ return h(Box, {
113
+ height: 1,
114
+ paddingLeft: 1,
115
+ paddingRight: 1,
116
+ flexDirection: 'row',
117
+ alignItems: 'center'
118
+ },
119
+ h(Typography.Dim, 'esc interrupt \u2022 ctrl+t thoughts \u2022 ctrl+p models \u2022 ctrl+k commands'),
120
+ h(Box, { flexGrow: 1 }),
121
+ h(Typography.Muted, `${modelLabel.toLowerCase()} \u2022 local`)
122
+ );
123
+ };
124
+
125
+ const renderStartup = () => {
126
+ return h(Box, {
127
+ flexDirection: 'column',
128
+ paddingLeft: 1,
129
+ paddingTop: 2,
130
+ backgroundColor: '#000000'
131
+ },
132
+ ...STARTUP_LOGO.map(line => h(Typography.Accent, line)),
133
+ h(Box, { height: 1 }),
134
+ h(Typography.Header, 'OpenAxies'),
135
+ h(Typography.Muted, 'local ai agent runtime'),
136
+ h(Box, { height: 2 }),
137
+ h(Typography.Dim, 'Press Enter to begin')
138
+ );
139
+ };
140
+
141
+ const renderTimeline = () => {
142
+ const events = [...messages];
143
+ if (isThinking) {
144
+ events.push({ type: 'thought', content: thoughtContent, elapsed: thinkingElapsed.toFixed(1) + 's' });
199
145
  }
200
- if (t === '/help') { setMessages(function (p) { return p.concat([{ role: 'assistant', content: 'Commands: /help /clear /model /thoughts /resume /exit', id: 'h-' + Date.now() }]); }); }
201
- setInputBuffer(''); setShowOverlay(false); setOverlayIndex(0);
202
- }
203
-
204
- useInput(function handleInput(input, key) {
205
- if (showOverlay === true) {
206
- if (key.escape === true) { setShowOverlay(false); setInputBuffer(''); setOverlayIndex(0); return; }
207
- if (key.upArrow === true) { setOverlayIndex(function (p) { return p > 0 ? p - 1 : COMMANDS.length - 1; }); return; }
208
- if (key.downArrow === true) { setOverlayIndex(function (p) { return p < COMMANDS.length - 1 ? p + 1 : 0; }); return; }
209
- if (key.return === true) { const cmd = COMMANDS[overlayIndex]; if (cmd !== null && cmd !== undefined) handleCmd(cmd); return; }
210
- return;
146
+ if (streamingActive && streamContent) {
147
+ events.push({ type: 'assistant', content: streamContent });
211
148
  }
212
- if (key.escape === true) {
213
- if (streamingActive === true) {
214
- if (abortRef.current !== null) { try { abortRef.current.abort(); } catch (_) {} abortRef.current = null; }
215
- setStreamingActive(false); setStreamText(''); setIsThinking(false);
216
- }
217
- return;
218
- }
219
- if (key.return === true) {
220
- const s = typeof inputBuffer === 'string' ? inputBuffer : '';
221
- if (s.length > 0 && streamingActive === false) handleSubmit(s);
222
- return;
223
- }
224
- if (key.ctrl === true) {
225
- if (input === 'c' || input === 'C') {
226
- if (abortRef.current !== null) { try { abortRef.current.abort(); } catch (_) {} abortRef.current = null; }
227
- setStreamingActive(false); setStreamText(''); setIsThinking(false);
228
- process.exit(0);
229
- }
230
- if (input === 't' || input === 'T') { setShowThoughts(function (p) { return !p; }); return; }
231
- if (input === 'p' || input === 'P') { cycleModel(); return; }
232
- if (input === 'r' || input === 'R') {
233
- const q = lastQueryRef.current;
234
- if (q.length > 0 && streamingActive === false) handleSubmit(q);
235
- return;
236
- }
237
- if (input === 'l' || input === 'L') { setMessages([]); return; }
238
- if (input === 'k' || input === 'K') { setInputBuffer('/'); setShowOverlay(true); setOverlayIndex(0); return; }
239
- }
240
- });
241
-
242
- function filterText(t) {
243
- if (showThoughts === true) return t;
244
- return t.split('<think>').join('').split('</think>').join('');
245
- }
246
-
247
- const activeInfo = getModelById(activeModel);
248
- const activeLabel = activeInfo !== null ? activeInfo.label : 'OpenAxies';
249
-
250
- // Build sections
251
- const sections = [];
252
-
253
- // Show overlay items
254
- if (showOverlay === true) {
255
- sections.push({ t: 'sep' });
256
- sections.push({ t: 'header_bright', text: 'Commands' });
257
- for (let i = 0; i < COMMANDS.length; i++) {
258
- const c = COMMANDS[i];
259
- const sel = i === overlayIndex;
260
- sections.push({ t: 'cmd', trigger: c.trigger, desc: c.desc, selected: sel });
261
- }
262
- sections.push({ t: 'sep' });
263
- }
264
-
265
- // Conversation history
266
- for (let i = 0; i < messages.length; i++) {
267
- const msg = messages[i];
268
- if (msg === null || typeof msg !== 'object') continue;
269
- const content = typeof msg.content === 'string' ? msg.content : '';
270
- if (content.length === 0) continue;
271
-
272
- if (msg.role === 'user') {
273
- sections.push({ t: 'sep' });
274
- sections.push({ t: 'user', text: content });
275
- } else {
276
- if (sections.length > 0 && sections[sections.length - 1].t !== 'sep') sections.push({ t: 'sep' });
277
- const display = filterText(content);
278
- sections.push({ t: 'text', text: display });
279
- }
280
- }
281
-
282
- // Streaming section
283
- const hasStream = typeof streamText === 'string' && streamText.length > 0;
284
- if (hasStream === true) {
285
- const clean = filterText(streamText);
286
-
287
- if (toolInfo !== null && toolInfo.used === true && toolInfo.query.length > 0) {
288
- sections.push({ t: 'tool', tool: toolInfo.tool, query: toolInfo.query, sites: toolInfo.sites });
289
- }
290
-
291
- if (isThinking === true) {
292
- sections.push({ t: 'thinking', spin: SPINNER[spinnerIdx], elapsed: fmtTime(thinkingElapsed), content: clean });
293
- } else {
294
- sections.push({ t: 'stream', text: clean });
295
- }
296
- }
297
-
298
- // Idle ready state
299
- if (sections.length === 0) {
300
- sections.push({ t: 'sep' });
301
- sections.push({ t: 'ready', text: 'Ready' });
302
- sections.push({ t: 'sep' });
303
- }
304
-
305
- // Estimate line count per section
306
- function estLines(s) {
307
- if (s.t === 'sep' || s.t === 'ready' || s.t === 'stream' || s.t === 'text' || s.t === 'tool') return 1;
308
- if (s.t === 'cmd') return 1;
309
- if (s.t === 'header_bright') return 1;
310
- if (s.t === 'user') return 2;
311
- if (s.t === 'thinking') return 2;
312
- return 1;
313
- }
314
-
315
- // Limit to available lines - show the LAST (newest) sections
316
- let totalEst = 0;
317
- let lastN = 0;
318
- for (let i = sections.length - 1; i >= 0; i--) {
319
- const l = estLines(sections[i]);
320
- if (totalEst + l > availLines) break;
321
- totalEst += l;
322
- lastN = sections.length - i;
323
- }
324
- const visible = sections.slice(sections.length - lastN);
325
-
326
- // Render
327
- const sectionEls = [];
328
- for (let i = 0; i < visible.length; i++) {
329
- const s = visible[i];
330
- const ki = 's-' + i;
331
-
332
- if (s.t === 'sep') {
333
- sectionEls.push(h(Box, { key: ki, height: 1 }, hr(cols)));
334
- } else if (s.t === 'user') {
335
- sectionEls.push(
336
- h(Box, { key: ki + '-h', height: 1, paddingLeft: 1 },
337
- h(Text, { color: '#888899', bold: true }, 'You')
338
- )
339
- );
340
- sectionEls.push(
341
- h(Box, { key: ki + '-t', height: 1, paddingLeft: 2 },
342
- h(Text, { color: hex.neonBlue, wrap: 'wrap' }, s.text)
343
- )
344
- );
345
- } else if (s.t === 'text' || s.t === 'stream') {
346
- sectionEls.push(
347
- h(Box, { key: ki, height: 1, paddingLeft: 1 },
348
- h(Text, { color: hex.primary, wrap: 'wrap' }, s.text)
349
- )
350
- );
351
- } else if (s.t === 'thinking') {
352
- sectionEls.push(
353
- h(Box, { key: ki + '-h', height: 1, paddingLeft: 1 },
354
- h(Text, { color: '#FF9F43', bold: true }, ' ' + s.spin + ' Thinking \u2022 ' + s.elapsed)
355
- )
356
- );
357
- sectionEls.push(
358
- h(Box, { key: ki + '-c', height: 1, paddingLeft: 2 },
359
- h(Text, { color: '#FF9F43', wrap: 'wrap' }, s.content)
360
- )
361
- );
362
- } else if (s.t === 'tool') {
363
- sectionEls.push(
364
- h(Box, { key: ki, height: 1, paddingLeft: 1 },
365
- h(Text, { color: '#5B5B8A' }, '\u2500\u2500 ' + s.tool + ' \u2014 ' + (s.query || '') + ' (' + (s.sites || 0) + ' sites)')
366
- )
367
- );
368
- } else if (s.t === 'header_bright') {
369
- sectionEls.push(
370
- h(Box, { key: ki, height: 1, paddingLeft: 1 },
371
- h(Text, { color: '#888899', bold: true }, s.text || '')
372
- )
373
- );
374
- } else if (s.t === 'cmd') {
375
- const prefix = s.selected === true ? '\u276F ' : ' ';
376
- const col = s.selected === true ? hex.neonBlue : '#666688';
377
- sectionEls.push(
378
- h(Box, { key: ki, height: 1, paddingLeft: 2 },
379
- h(Text, { color: col }, prefix + s.trigger),
380
- h(Text, { color: '#444466' }, ' ' + (s.desc || ''))
381
- )
382
- );
383
- } else if (s.t === 'ready') {
384
- sectionEls.push(
385
- h(Box, { key: ki, height: 1, paddingLeft: 1 },
386
- h(Text, { color: '#00FF88', bold: true }, s.text)
387
- )
388
- );
389
- }
390
- }
391
-
392
- // Fill remaining lines
393
- while (sectionEls.length < availLines) {
394
- sectionEls.push(h(Box, { key: 'fl-' + sectionEls.length, height: 1 }, h(Text, {}, '')));
395
- }
396
-
397
- const viewport = h(Box, {
398
- flexGrow: 1,
399
- height: availLines,
400
- flexDirection: 'column',
401
- overflow: 'hidden',
402
- }, ...sectionEls);
403
149
 
404
- // Status bar
405
- const statusBar = h(Box, { flexDirection: 'column', height: STATUS_H, flexShrink: 0 },
406
- h(Box, { height: 1 }, hr(cols)),
407
- h(Box, { height: 1, paddingLeft: 2 },
408
- h(Text, { color: '#444466' }, 'esc interrupt ctrl+t thoughts ctrl+p models'),
150
+ return h(Box, {
151
+ flexGrow: 1,
152
+ flexDirection: 'column',
153
+ overflow: 'hidden',
154
+ paddingLeft: 0,
155
+ paddingRight: 0,
156
+ },
157
+ ...events.slice(-15).map((event, i) => h(ActivityItem, { key: i, event }))
158
+ );
159
+ };
160
+
161
+ return h(Box, {
162
+ flexDirection: 'column',
163
+ width: '100%',
164
+ height: rows,
165
+ overflow: 'hidden'
166
+ },
167
+ renderStartup() && !startupDone ? renderStartup() : null,
168
+ startupDone && renderHeader(),
169
+ startupDone && h(Separator),
170
+ startupDone && renderTimeline(),
171
+ startupDone && h(Box, { height: 1 }, h(Separator)),
172
+ startupDone && h(Box, {
173
+ height: 1,
174
+ paddingLeft: 1,
175
+ paddingRight: 1,
176
+ flexDirection: 'row',
177
+ alignItems: 'center'
178
+ },
179
+ h(Typography.Accent, '▌ Ask OpenAxies'),
180
+ h(Box, { flexGrow: 1 }),
181
+ h(TextInput, {
182
+ value: inputBuffer,
183
+ onChange: setInputBuffer,
184
+ onSubmit: handleSubmit,
185
+ focus: true,
186
+ showCursor: true
187
+ })
409
188
  ),
410
- h(Box, { height: 1, paddingLeft: 2 },
411
- h(Text, { color: '#444466' }, 'ctrl+l clear ctrl+r resume ctrl+k commands'),
412
- )
413
- );
414
-
415
- // Prompt
416
- const promptLine = h(Box, { height: DOCK_H, flexShrink: 0, paddingLeft: 2, paddingRight: 2 },
417
- h(Text, { color: hex.greenOnline }, '\u258C'),
418
- h(TextInput, {
419
- value: inputBuffer,
420
- onChange: handleChange,
421
- onSubmit: function () {},
422
- placeholder: '',
423
- focus: showOverlay === false,
424
- showCursor: true,
425
- })
426
- );
427
-
428
- return h(Box, { flexDirection: 'column', width: '100%', height: rows, overflow: 'hidden' },
429
- createBrandHeader(),
430
- viewport,
431
- statusBar,
432
- promptLine
189
+ startupDone && renderFooter()
433
190
  );
434
191
  }
@@ -0,0 +1,46 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import { Typography } from '../ui/Typography.js';
4
+ import { hex } from '../../config/theme.js';
5
+
6
+ const h = React.createElement;
7
+
8
+ export function ActivityItem({ event }) {
9
+ switch (event.type) {
10
+ case 'user':
11
+ return h(Box, { flexDirection: 'column', paddingLeft: 1, paddingRight: 1, marginBottom: 1 },
12
+ h(Typography.Subheader, 'You'),
13
+ h(Box, { paddingLeft: 1 }, h(Typography.Body, { color: hex.neonBlue }, event.content))
14
+ );
15
+ case 'assistant':
16
+ return h(Box, { flexDirection: 'column', paddingLeft: 1, paddingRight: 1, marginBottom: 1 },
17
+ h(Typography.Body, event.content)
18
+ );
19
+ case 'thought':
20
+ return h(Box, {
21
+ flexDirection: 'column',
22
+ paddingLeft: 1,
23
+ paddingRight: 1,
24
+ marginBottom: 1,
25
+ backgroundColor: '#12121A'
26
+ },
27
+ h(Box, { flexDirection: 'row', alignItems: 'center' },
28
+ h(Typography.Thinking, `\u25CB Thinking \u2022 ${event.elapsed || '0.0s'}`)
29
+ ),
30
+ h(Box, { paddingLeft: 1 }, h(Typography.Thinking, event.content))
31
+ );
32
+ case 'tool':
33
+ return h(Box, { paddingLeft: 1, paddingRight: 1, marginBottom: 0.5 },
34
+ h(Typography.Muted, `\u2500\u2500 ${event.tool} ${event.query ? ' \u2014 ' + event.query : ''}`)
35
+ );
36
+ case 'todo':
37
+ const icon = event.status === 'completed' ? '\u2713' : (event.status === 'running' ? '\u25B2' : '\u25CB');
38
+ const color = event.status === 'completed' ? '#00FF88' : (event.status === 'running' ? hex.neonBlue : '#666688');
39
+ return h(Box, { paddingLeft: 1, paddingRight: 1 },
40
+ h(Text, { color }, `${icon} `),
41
+ h(Typography.Muted, event.text)
42
+ );
43
+ default:
44
+ return null;
45
+ }
46
+ }
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import { Box } from 'ink';
3
+ import { ActivityItem } from './ActivityItem.js';
4
+
5
+ const h = React.createElement;
6
+
7
+ export function TodoTracker({ todos }) {
8
+ if (!todos || todos.length === 0) return null;
9
+
10
+ return h(Box, {
11
+ flexDirection: 'column',
12
+ paddingLeft: 1,
13
+ paddingRight: 1,
14
+ marginBottom: 1,
15
+ borderStyle: 'single',
16
+ borderColor: '#1A1A28'
17
+ },
18
+ ...todos.map((todo, i) => h(ActivityItem, {
19
+ key: i,
20
+ event: { type: 'todo', text: todo.text, status: todo.status }
21
+ }))
22
+ );
23
+ }
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+
4
+ const h = React.createElement;
5
+
6
+ export function Separator() {
7
+ return h(Box, { height: 1, width: '100%' },
8
+ h(Text, { color: '#1A1A28' }, '\u2500'.repeat(80))
9
+ );
10
+ }
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ import { Text } from 'ink';
3
+ import { hex } from '../../config/theme.js';
4
+
5
+ const h = React.createElement;
6
+
7
+ export const Typography = {
8
+ Header: ({ children }) => h(Text, { color: '#FFFFFF', bold: true }, children),
9
+ Subheader: ({ children }) => h(Text, { color: '#AAAAAA', bold: true }, children),
10
+ Body: ({ children, color = '#FFFFFF' }) => h(Text, { color, wrap: 'wrap' }, children),
11
+ Muted: ({ children }) => h(Text, { color: '#666688' }, children),
12
+ Dim: ({ children }) => h(Text, { color: '#444466' }, children),
13
+ Accent: ({ children }) => h(Text, { color: hex.neonBlue, bold: true }, children),
14
+ Thinking: ({ children }) => h(Text, { color: '#FF9F43' }, children),
15
+ };
@@ -4,15 +4,12 @@ import { runWebSearchGraph, shouldUseWebSearch } from './websearch.js';
4
4
  const MODEL_ROUTES = Object.freeze({
5
5
  'openaxis/openaxis-llama': Object.freeze([
6
6
  'https://universal-618-clarity-main.hf.space/v1/chat/completions',
7
- 'https://universal-618-clarity-4.hf.space/v1/chat/completions',
8
7
  ]),
9
8
  'openaxis/openaxis-gpt': Object.freeze([
10
9
  'https://universal-618-clarity-2.hf.space/v1/chat/completions',
11
- 'https://universal-618-clarity-5.hf.space/v1/chat/completions',
12
10
  ]),
13
11
  'openaxis/openaxis-deepseek': Object.freeze([
14
12
  'https://universal-618-clarity-3.hf.space/v1/chat/completions',
15
- 'https://universal-618-clarity-6.hf.space/v1/chat/completions',
16
13
  ]),
17
14
  });
18
15
 
@@ -1,5 +1,5 @@
1
- const CONNECT_TIMEOUT = 120000;
2
- const READ_TIMEOUT = 60000;
1
+ const CONNECT_TIMEOUT = 10000;
2
+ const READ_TIMEOUT = 30000;
3
3
 
4
4
  function checkEndpoint(endpoint) {
5
5
  if (typeof endpoint !== 'string') {
@@ -1,36 +0,0 @@
1
- import React from 'react';
2
- import { Box, Text } from 'ink';
3
- import { hex } from '../config/theme.js';
4
-
5
- const h = React.createElement;
6
-
7
- const LOGO = [
8
- '██████╗ ██████╗ ███████╗███╗ ██╗ █████╗ ██╗ ██╗██╗███████╗███████╗',
9
- '██╔═══██╗██╔══██╗██╔════╝████╗ ██║██╔══██╗╚██╗██╔╝██║██╔════╝██╔════╝',
10
- '██║ ██║██████╔╝█████╗ ██╔██╗ ██║███████║ ╚███╔╝ ██║█████╗ ███████╗',
11
- '██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║██╔══██║ ██╔██╗ ██║██╔══╝ ╚════██║',
12
- '╚██████╔╝██║ ███████╗██║ ╚████║██║ ██║██╔╝ ██╗██║███████╗███████║',
13
- ' ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚══════╝╚══════╝',
14
- ];
15
-
16
- export function createBrandHeader() {
17
- const els = [];
18
- for (let r = 0; r < LOGO.length; r++) {
19
- els.push(
20
- h(Text, { key: 'l' + r, color: '#00BBFF', bold: true }, LOGO[r])
21
- );
22
- }
23
- els.push(
24
- h(Text, { key: 'sub', color: '#666688' }, 'OpenAxies Agent Runtime')
25
- );
26
- return h(Box, {
27
- flexDirection: 'column',
28
- width: '100%',
29
- flexShrink: 0,
30
- paddingLeft: 0,
31
- paddingTop: 0,
32
- paddingBottom: 0,
33
- }, ...els);
34
- }
35
-
36
- export const BRAND_HEIGHT = 7;
@@ -1,66 +0,0 @@
1
- import React from 'react';
2
- import { Box, Text, useInput, useStdout } from 'ink';
3
- import TextInput from 'ink-text-input';
4
- import { hex } from '../config/theme.js';
5
-
6
- const h = React.createElement;
7
-
8
- export const DOCK_HEIGHT = 4;
9
-
10
- const PROMPT = '> ';
11
-
12
- export function createComposerDock(config) {
13
- if (config === null || config === undefined || typeof config !== 'object') {
14
- throw new Error('createComposerDock requires a config object');
15
- }
16
-
17
- const value = typeof config.value === 'string' ? config.value : '';
18
- const onChange = config.onChange;
19
- const onSubmit = config.onSubmit;
20
- const inputActive = config.inputActive !== false;
21
- const isStreaming = config.isStreaming === true;
22
- const modelLabel = typeof config.modelLabel === 'string' ? config.modelLabel : '';
23
- const modelStatus = config.modelStatus === true;
24
- const safeCols = typeof config.terminalWidth === 'number' && config.terminalWidth > 0 ? config.terminalWidth : 80;
25
-
26
- const placeholder = isStreaming ? 'Ctrl+C to interrupt...' : 'type a message...';
27
- const innerW = safeCols - 2;
28
-
29
- return h(Box, {
30
- width: '100%',
31
- height: DOCK_HEIGHT,
32
- flexDirection: 'column',
33
- flexShrink: 0,
34
- },
35
- h(Box, { height: 1, paddingLeft: 1 },
36
- h(Text, { color: '#1A1A28' }, '\u2500'.repeat(innerW))
37
- ),
38
- h(Box, { height: 1, paddingLeft: 1, paddingRight: 1 },
39
- h(Text, { color: hex.border }, '\u2502'),
40
- h(Text, { color: inputActive ? hex.neonBlue : '#444466', bold: true }, ' ' + PROMPT),
41
- h(Box, { flexGrow: 1, height: 1, overflow: 'hidden' },
42
- h(TextInput, {
43
- value: value,
44
- onChange: onChange,
45
- onSubmit: onSubmit,
46
- placeholder: placeholder,
47
- focus: inputActive,
48
- showCursor: true,
49
- })
50
- ),
51
- h(Text, { color: '#444466' }, ' '),
52
- h(Text, { color: hex.border }, '\u2502')
53
- ),
54
- h(Box, { height: 1, paddingLeft: 1, paddingRight: 1 },
55
- h(Text, { color: hex.border }, '\u2514'),
56
- h(Box, { flexGrow: 1, height: 1, overflow: 'hidden' },
57
- h(Text, { color: '#333344' }, '\u2500'.repeat(innerW - 2))
58
- ),
59
- h(Text, { color: hex.border }, '\u2518')
60
- ),
61
- h(Box, { height: 1, paddingRight: 1 },
62
- h(Box, { flexGrow: 1 }),
63
- h(Text, { color: '#444466', bold: modelStatus }, modelLabel || '')
64
- )
65
- );
66
- }