agentk8 2.2.6 → 2.3.1

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.
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import { render } from 'ink';
4
4
  import meow from 'meow';
5
5
  import { App } from './components/App.js';
6
6
  import { checkClaudeInstalled } from './lib/claude.js';
7
- const VERSION = '2.2.6';
7
+ const VERSION = '2.3.0';
8
8
  const cli = meow(`
9
9
  Usage
10
10
  $ agentk8 [options]
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from 'react';
2
+ import { useState, useEffect } from 'react';
3
3
  import { Box, Text, useApp, useInput, Static } from 'ink';
4
4
  import { WelcomeBox } from './WelcomeBox.js';
5
5
  import { ChatMessage } from './ChatMessage.js';
@@ -7,9 +7,19 @@ import { Input } from './Input.js';
7
7
  import { StatusBar } from './StatusBar.js';
8
8
  import { ThinkingIndicator } from './ThinkingIndicator.js';
9
9
  import { runClaude } from '../lib/claude.js';
10
+ import { runCouncil, checkCouncilAvailable, getAvailableModels } from '../lib/council.js';
11
+ import { Confirmation } from './Confirmation.js';
12
+ import { QuestionWizard } from './QuestionWizard.js';
10
13
  export const App = ({ mode, version }) => {
11
14
  const { exit } = useApp();
12
- const [messages, setMessages] = useState([]);
15
+ // Initialize with welcome message as permanent first item
16
+ const [messages, setMessages] = useState([{
17
+ id: 'welcome',
18
+ role: 'system',
19
+ content: '',
20
+ timestamp: new Date(),
21
+ isWelcome: true,
22
+ }]);
13
23
  const [isProcessing, setIsProcessing] = useState(false);
14
24
  const [processingStartTime, setProcessingStartTime] = useState(null);
15
25
  const [totalTokens, setTotalTokens] = useState(0);
@@ -18,10 +28,71 @@ export const App = ({ mode, version }) => {
18
28
  const [executionMode, setExecutionMode] = useState('plan');
19
29
  const [activeAgent, setActiveAgent] = useState(undefined);
20
30
  const [completedAgents, setCompletedAgents] = useState([]);
21
- const [pendingPlan, setPendingPlan] = useState(null);
22
- const [awaitingApproval, setAwaitingApproval] = useState(false);
31
+ const [confirmationState, setConfirmationState] = useState(null);
23
32
  const [autoAccept, setAutoAccept] = useState(false);
24
- const [pendingAutoAccept, setPendingAutoAccept] = useState(false);
33
+ const [councilMode, setCouncilMode] = useState('off');
34
+ const [councilAvailable, setCouncilAvailable] = useState(false);
35
+ const [availableModels, setAvailableModels] = useState({});
36
+ const [councilStage, setCouncilStage] = useState(null);
37
+ const [lastEscapeTime, setLastEscapeTime] = useState(0);
38
+ const [showExitHint, setShowExitHint] = useState(false);
39
+ const [questionWizardState, setQuestionWizardState] = useState(null);
40
+ // Clean orchestrator internal tags from response
41
+ const cleanOrchestratorTags = (response) => {
42
+ let cleaned = response;
43
+ // Remove <thinking>...</thinking> blocks
44
+ cleaned = cleaned.replace(/<thinking>[\s\S]*?<\/thinking>/g, '');
45
+ // Remove <task_analysis>...</task_analysis> blocks (keep content inside)
46
+ cleaned = cleaned.replace(/<\/?task_analysis>/g, '');
47
+ // Remove <response>...</response> tags (keep content inside)
48
+ cleaned = cleaned.replace(/<\/?response>/g, '');
49
+ // Clean up multiple consecutive newlines
50
+ cleaned = cleaned.replace(/\n{3,}/g, '\n\n');
51
+ // Clean up leading/trailing whitespace
52
+ cleaned = cleaned.trim();
53
+ return cleaned;
54
+ };
55
+ // Parse questions from orchestrator response
56
+ const parseQuestions = (response) => {
57
+ const questions = [];
58
+ let cleanedResponse = response;
59
+ // Match <question header="...">...</question> blocks
60
+ const questionRegex = /<question\s+header="([^"]+)">\s*([\s\S]*?)\s*<options>\s*([\s\S]*?)\s*<\/options>\s*<\/question>/g;
61
+ let match;
62
+ while ((match = questionRegex.exec(response)) !== null) {
63
+ const header = match[1];
64
+ const questionText = match[2].trim();
65
+ const optionsBlock = match[3];
66
+ // Parse options
67
+ const optionRegex = /<option(?:\s+recommended="true")?>(.*?)<\/option>/g;
68
+ const options = [];
69
+ let optionMatch;
70
+ while ((optionMatch = optionRegex.exec(optionsBlock)) !== null) {
71
+ const isRecommended = optionMatch[0].includes('recommended="true"');
72
+ options.push({
73
+ label: optionMatch[1].trim(),
74
+ recommended: isRecommended,
75
+ });
76
+ }
77
+ if (options.length > 0) {
78
+ questions.push({
79
+ header,
80
+ question: questionText,
81
+ options,
82
+ });
83
+ }
84
+ // Remove this question block from the response
85
+ cleanedResponse = cleanedResponse.replace(match[0], '').trim();
86
+ }
87
+ // Clean orchestrator internal tags from the remaining response
88
+ cleanedResponse = cleanOrchestratorTags(cleanedResponse);
89
+ return { questions, cleanedResponse };
90
+ };
91
+ // Check council availability on mount
92
+ useEffect(() => {
93
+ checkCouncilAvailable().then(setCouncilAvailable);
94
+ getAvailableModels().then(setAvailableModels);
95
+ }, []);
25
96
  // Detect agents mentioned in response
26
97
  const detectMentionedAgents = (content) => {
27
98
  const agents = [];
@@ -42,37 +113,9 @@ export const App = ({ mode, version }) => {
42
113
  handleCommand(input);
43
114
  return;
44
115
  }
45
- // Handle auto-accept confirmation
46
- if (pendingAutoAccept) {
47
- if (input.trim() === '') {
48
- // Enter = confirm auto-accept
49
- setAutoAccept(true);
50
- setPendingAutoAccept(false);
51
- return;
52
- }
53
- // Any other input = cancel and process as normal input
54
- setPendingAutoAccept(false);
55
- }
56
- // Handle approval response
57
- if (awaitingApproval) {
58
- // Enter (empty input) = approve
59
- if (input.trim() === '') {
60
- setAwaitingApproval(false);
61
- if (pendingPlan) {
62
- await executeTask(pendingPlan);
63
- setPendingPlan(null);
64
- }
65
- return;
66
- }
67
- else if (input.toLowerCase() === 'n' || input.toLowerCase() === 'no') {
68
- setAwaitingApproval(false);
69
- setPendingPlan(null);
70
- addSystemMessage('Plan cancelled. What would you like to do instead?');
71
- return;
72
- }
73
- // Any other input cancels and starts new request
74
- setAwaitingApproval(false);
75
- setPendingPlan(null);
116
+ // Handle confirmation response
117
+ if (confirmationState) {
118
+ return; // Input is disabled when confirmation is active
76
119
  }
77
120
  // Add user message
78
121
  const userMessage = {
@@ -82,13 +125,64 @@ export const App = ({ mode, version }) => {
82
125
  timestamp: new Date(),
83
126
  };
84
127
  setMessages(prev => [...prev, userMessage]);
85
- if (executionMode === 'plan') {
128
+ // Use council mode if enabled
129
+ if (councilMode !== 'off') {
130
+ await executeCouncil(input);
131
+ }
132
+ else if (executionMode === 'plan') {
86
133
  await generatePlan(input);
87
134
  }
88
135
  else {
89
136
  await executeTask(input);
90
137
  }
91
138
  };
139
+ // Execute via Council
140
+ const executeCouncil = async (input) => {
141
+ setIsProcessing(true);
142
+ setProcessingStartTime(new Date());
143
+ setActiveAgent('Orchestrator');
144
+ setCompletedAgents([]);
145
+ setError(null);
146
+ setCouncilStage('scout');
147
+ try {
148
+ const result = await runCouncil(input, { mode: councilMode }, (update) => {
149
+ // Update stage indicator
150
+ if (update.stage.startsWith('stage1')) {
151
+ setCouncilStage('stage1');
152
+ setActiveAgent('Orchestrator');
153
+ }
154
+ else if (update.stage.startsWith('stage2')) {
155
+ setCouncilStage('stage2');
156
+ }
157
+ else if (update.stage.startsWith('stage3')) {
158
+ setCouncilStage('stage3');
159
+ }
160
+ });
161
+ setCompletedAgents(['Orchestrator']);
162
+ setActiveAgent(undefined);
163
+ setCouncilStage(null);
164
+ const councilMessage = {
165
+ id: (Date.now() + 1).toString(),
166
+ role: 'agent',
167
+ agentName: 'Council',
168
+ content: result.final_response,
169
+ tokens: result.total_tokens,
170
+ timestamp: new Date(),
171
+ };
172
+ setMessages(prev => [...prev, councilMessage]);
173
+ if (result.total_tokens) {
174
+ setTotalTokens(prev => prev + result.total_tokens.input + result.total_tokens.output);
175
+ }
176
+ }
177
+ catch (err) {
178
+ setError(err instanceof Error ? err.message : 'Council error');
179
+ }
180
+ finally {
181
+ setIsProcessing(false);
182
+ setProcessingStartTime(null);
183
+ setCouncilStage(null);
184
+ }
185
+ };
92
186
  // Generate a plan for approval
93
187
  const generatePlan = async (input) => {
94
188
  setIsProcessing(true);
@@ -104,18 +198,21 @@ Respond with:
104
198
  1. Task Analysis (complexity, scope)
105
199
  2. Agents Required (list which specialists are needed)
106
200
  3. Step-by-Step Plan (numbered steps)
107
- 4. Questions (if any clarification needed)
201
+ 4. Questions (if any clarification needed - use the XML question format)
108
202
 
109
203
  Format your response clearly with headers.`;
110
204
  const result = await runClaude(planPrompt, mode, autoAccept);
111
- const mentioned = detectMentionedAgents(result.response);
205
+ // Check for questions in response
206
+ const { questions, cleanedResponse } = parseQuestions(result.response);
207
+ const mentioned = detectMentionedAgents(cleanedResponse || result.response);
112
208
  setCompletedAgents(['Orchestrator', ...mentioned]);
113
209
  setActiveAgent(undefined);
210
+ // Show the cleaned response (without question XML)
114
211
  const planMessage = {
115
212
  id: (Date.now() + 1).toString(),
116
213
  role: 'agent',
117
214
  agentName: 'Orchestrator',
118
- content: result.response,
215
+ content: cleanedResponse || result.response,
119
216
  tokens: result.tokens,
120
217
  timestamp: new Date(),
121
218
  };
@@ -123,14 +220,58 @@ Format your response clearly with headers.`;
123
220
  if (result.tokens) {
124
221
  setTotalTokens(prev => prev + result.tokens.input + result.tokens.output);
125
222
  }
126
- setPendingPlan(input);
127
- setAwaitingApproval(true);
128
- addSystemMessage('Press Enter to execute, or type "n" to cancel');
223
+ // Stop processing BEFORE showing wizard/confirmation to prevent overlap
224
+ setIsProcessing(false);
225
+ setProcessingStartTime(null);
226
+ // If questions were found, show the wizard
227
+ if (questions.length > 0) {
228
+ setQuestionWizardState({
229
+ questions,
230
+ originalInput: input,
231
+ onComplete: async (answers) => {
232
+ setQuestionWizardState(null);
233
+ // Format answers as follow-up message
234
+ const answerText = answers.map(a => `- ${a.header}: ${a.answer}`).join('\n');
235
+ const followUp = `My answers:\n${answerText}\n\nPlease proceed with these choices.`;
236
+ // Add user message with answers
237
+ const userAnswer = {
238
+ id: Date.now().toString(),
239
+ role: 'user',
240
+ content: followUp,
241
+ timestamp: new Date(),
242
+ };
243
+ setMessages(prev => [...prev, userAnswer]);
244
+ // Continue with the task including answers
245
+ await executeTask(`${input}\n\n${followUp}`);
246
+ },
247
+ });
248
+ }
249
+ else {
250
+ // No questions, show plan approval
251
+ setConfirmationState({
252
+ message: 'Do you want to execute this plan?',
253
+ options: [
254
+ { label: 'Yes, execute plan', value: 'yes', key: 'Enter' },
255
+ { label: 'No, cancel', value: 'no', key: 'Esc' },
256
+ ],
257
+ onSelect: (value) => {
258
+ setConfirmationState(null);
259
+ if (value === 'yes') {
260
+ executeTask(input);
261
+ }
262
+ else {
263
+ addSystemMessage('Plan cancelled.');
264
+ }
265
+ },
266
+ onCancel: () => {
267
+ setConfirmationState(null);
268
+ addSystemMessage('Plan cancelled.');
269
+ },
270
+ });
271
+ }
129
272
  }
130
273
  catch (err) {
131
274
  setError(err instanceof Error ? err.message : 'Unknown error');
132
- }
133
- finally {
134
275
  setIsProcessing(false);
135
276
  setProcessingStartTime(null);
136
277
  }
@@ -144,14 +285,16 @@ Format your response clearly with headers.`;
144
285
  setError(null);
145
286
  try {
146
287
  const result = await runClaude(input, mode, autoAccept);
147
- const mentioned = detectMentionedAgents(result.response);
288
+ // Check for questions in response
289
+ const { questions, cleanedResponse } = parseQuestions(result.response);
290
+ const mentioned = detectMentionedAgents(cleanedResponse || result.response);
148
291
  setCompletedAgents(['Orchestrator', ...mentioned]);
149
292
  setActiveAgent(undefined);
150
293
  const agentMessage = {
151
294
  id: (Date.now() + 1).toString(),
152
295
  role: 'agent',
153
296
  agentName: 'Orchestrator',
154
- content: result.response,
297
+ content: cleanedResponse || result.response,
155
298
  tokens: result.tokens,
156
299
  timestamp: new Date(),
157
300
  };
@@ -159,11 +302,35 @@ Format your response clearly with headers.`;
159
302
  if (result.tokens) {
160
303
  setTotalTokens(prev => prev + result.tokens.input + result.tokens.output);
161
304
  }
305
+ // Stop processing BEFORE showing wizard to prevent overlap
306
+ setIsProcessing(false);
307
+ setProcessingStartTime(null);
308
+ // If questions were found, show the wizard
309
+ if (questions.length > 0) {
310
+ setQuestionWizardState({
311
+ questions,
312
+ originalInput: input,
313
+ onComplete: async (answers) => {
314
+ setQuestionWizardState(null);
315
+ // Format answers as follow-up message
316
+ const answerText = answers.map(a => `- ${a.header}: ${a.answer}`).join('\n');
317
+ const followUp = `My answers:\n${answerText}\n\nPlease proceed with these choices.`;
318
+ // Add user message with answers
319
+ const userAnswer = {
320
+ id: Date.now().toString(),
321
+ role: 'user',
322
+ content: followUp,
323
+ timestamp: new Date(),
324
+ };
325
+ setMessages(prev => [...prev, userAnswer]);
326
+ // Continue with the task including answers
327
+ await executeTask(`${input}\n\n${followUp}`);
328
+ },
329
+ });
330
+ }
162
331
  }
163
332
  catch (err) {
164
333
  setError(err instanceof Error ? err.message : 'Unknown error');
165
- }
166
- finally {
167
334
  setIsProcessing(false);
168
335
  setProcessingStartTime(null);
169
336
  }
@@ -188,7 +355,14 @@ Format your response clearly with headers.`;
188
355
  exit();
189
356
  break;
190
357
  case 'clear':
191
- setMessages([]);
358
+ // Keep welcome message, clear everything else
359
+ setMessages([{
360
+ id: 'welcome',
361
+ role: 'system',
362
+ content: '',
363
+ timestamp: new Date(),
364
+ isWelcome: true,
365
+ }]);
192
366
  setActiveAgent(undefined);
193
367
  setCompletedAgents([]);
194
368
  break;
@@ -197,8 +371,27 @@ Format your response clearly with headers.`;
197
371
  addSystemMessage('Plan mode enabled. I will show plans for approval before executing.');
198
372
  break;
199
373
  case 'auto':
200
- setExecutionMode('auto');
201
- addSystemMessage('Auto mode enabled. I will execute tasks directly without approval.');
374
+ setConfirmationState({
375
+ message: '⚠️ Are you sure you want to enable Auto Mode? This will execute actions without further approval.',
376
+ options: [
377
+ { label: 'Yes, enable auto mode', value: 'yes' },
378
+ { label: 'No, stay in plan mode', value: 'no' },
379
+ ],
380
+ onSelect: (value) => {
381
+ setConfirmationState(null);
382
+ if (value === 'yes') {
383
+ setExecutionMode('auto');
384
+ addSystemMessage('Auto mode enabled. I will execute tasks directly without approval.');
385
+ }
386
+ else {
387
+ addSystemMessage('Kept in plan mode.');
388
+ }
389
+ },
390
+ onCancel: () => {
391
+ setConfirmationState(null);
392
+ addSystemMessage('Kept in plan mode.');
393
+ },
394
+ });
202
395
  break;
203
396
  case 'mode':
204
397
  addSystemMessage(`Current execution mode: ${executionMode}\nUse /plan or /auto to switch.`);
@@ -213,6 +406,42 @@ Format your response clearly with headers.`;
213
406
  addSystemMessage(`Agent Status:\n${status}${completed}`);
214
407
  }
215
408
  break;
409
+ case 'council':
410
+ if (!councilAvailable) {
411
+ addSystemMessage('Council backend not available. Install: pip install -e ./python');
412
+ }
413
+ else if (councilMode === 'off') {
414
+ // Check available models and recommend mode
415
+ const hasMultipleModels = Object.values(availableModels).filter(Boolean).length > 1;
416
+ if (hasMultipleModels) {
417
+ setCouncilMode('council');
418
+ addSystemMessage('Council mode enabled (multi-LLM). Stage 1→2→3 consensus.');
419
+ }
420
+ else {
421
+ setCouncilMode('solo');
422
+ addSystemMessage('Council mode enabled (solo). Multi-Claude personas.');
423
+ }
424
+ }
425
+ else {
426
+ setCouncilMode('off');
427
+ addSystemMessage('Council mode disabled. Using standard orchestrator.');
428
+ }
429
+ break;
430
+ case 'solo':
431
+ if (!councilAvailable) {
432
+ addSystemMessage('Council backend not available. Install: pip install -e ./python');
433
+ }
434
+ else {
435
+ setCouncilMode('solo');
436
+ addSystemMessage('Solo council mode enabled. Uses Claude CLI with personas.');
437
+ }
438
+ break;
439
+ case 'models':
440
+ const modelStatus = Object.entries(availableModels)
441
+ .map(([k, v]) => `${v ? '✓' : '✗'} ${k}`)
442
+ .join('\n');
443
+ addSystemMessage(`Available Models:\n${modelStatus || 'Checking...'}`);
444
+ break;
216
445
  case 'help':
217
446
  const helpMessage = {
218
447
  id: Date.now().toString(),
@@ -226,12 +455,16 @@ Format your response clearly with headers.`;
226
455
  /auto - Enable auto mode (execute directly)
227
456
  /mode - Show current execution mode
228
457
  /agents - Show active agents
458
+ /council - Toggle council mode (multi-LLM consensus)
459
+ /solo - Enable solo council (multi-Claude personas)
460
+ /models - Show available LLM models
229
461
  /exit - Exit AGENT-K
230
462
 
231
463
  Keyboard shortcuts:
232
464
  ↑/↓ - Browse command history
233
465
  Tab - Autocomplete commands
234
- Ctrl+C - Exit
466
+ Shift+Tab - Toggle auto-accept edits
467
+ Esc Esc - Exit
235
468
  Ctrl+U - Clear input line`,
236
469
  timestamp: new Date(),
237
470
  };
@@ -245,9 +478,11 @@ Ctrl+U - Clear input line`,
245
478
  content: `Session Status:
246
479
  ◇ Mode: ${mode === 'dev' ? 'Development' : 'ML Research'}
247
480
  ◇ Execution: ${executionMode === 'plan' ? 'Plan (approval required)' : 'Auto (direct execution)'}
481
+ ◇ Council: ${councilMode === 'off' ? 'Off' : councilMode === 'council' ? 'Multi-LLM' : 'Solo (Multi-Claude)'}
248
482
  ◇ Messages: ${messages.length}
249
483
  ◇ Total Tokens: ${totalTokens}
250
484
  ◇ Active Agent: ${activeAgent || 'None'}
485
+ ◇ Council Stage: ${councilStage || 'N/A'}
251
486
  ◇ Session Time: ${formatElapsed(startTime)}`,
252
487
  timestamp: new Date(),
253
488
  };
@@ -259,8 +494,21 @@ Ctrl+U - Clear input line`,
259
494
  };
260
495
  // Handle keyboard shortcuts
261
496
  useInput((input, key) => {
262
- if (key.ctrl && input === 'c') {
263
- exit();
497
+ // Double-escape to exit (like Claude Code)
498
+ if (key.escape) {
499
+ const now = Date.now();
500
+ if (now - lastEscapeTime < 500) {
501
+ // Double escape - exit
502
+ exit();
503
+ }
504
+ else {
505
+ // First escape - show hint and record time
506
+ setLastEscapeTime(now);
507
+ if (!questionWizardState && !confirmationState) {
508
+ setShowExitHint(true);
509
+ setTimeout(() => setShowExitHint(false), 1500);
510
+ }
511
+ }
264
512
  }
265
513
  // Shift+Tab to toggle auto-accept
266
514
  if (key.shift && key.tab) {
@@ -270,20 +518,32 @@ Ctrl+U - Clear input line`,
270
518
  }
271
519
  else {
272
520
  // Show confirmation prompt
273
- setPendingAutoAccept(true);
521
+ setConfirmationState({
522
+ message: 'Enable auto-accept for code edits?',
523
+ options: [
524
+ { label: 'Yes, enable auto-accept', value: 'yes' },
525
+ { label: 'No, keep asking', value: 'no' },
526
+ ],
527
+ onSelect: (value) => {
528
+ setConfirmationState(null);
529
+ if (value === 'yes') {
530
+ setAutoAccept(true);
531
+ }
532
+ },
533
+ onCancel: () => setConfirmationState(null),
534
+ });
274
535
  }
275
536
  }
276
537
  });
277
- // Prepare items for Static (include welcome box as first item)
278
- const staticItems = messages.length === 0
279
- ? [{ id: 'welcome', isWelcome: true, role: 'system', content: '', timestamp: new Date() }]
280
- : messages;
281
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: staticItems, children: (item) => {
538
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: messages, children: (item) => {
282
539
  if ('isWelcome' in item && item.isWelcome) {
283
540
  return _jsx(WelcomeBox, { version: version, mode: mode }, "welcome");
284
541
  }
285
542
  return (_jsx(ChatMessage, { role: item.role, agentName: item.agentName, content: item.content, tokens: item.tokens }, item.id));
286
- } }), isProcessing && processingStartTime && (_jsx(ThinkingIndicator, { startTime: processingStartTime })), error && (_jsx(Box, { marginY: 1, marginLeft: 1, children: _jsxs(Text, { color: "#e53e3e", children: ["\u2717 Error: ", error] }) })), _jsx(Input, { onSubmit: handleSubmit, disabled: isProcessing, placeholder: pendingAutoAccept ? "Auto-accept: edits applied without asking. Enter to confirm" : awaitingApproval ? "Press Enter to execute, n to cancel" : 'Try "build a password validator"' }), _jsx(StatusBar, { mode: mode, tokens: totalTokens, startTime: startTime, isProcessing: isProcessing, activeAgent: activeAgent, completedAgents: completedAgents, autoAccept: autoAccept })] }));
543
+ } }), isProcessing && processingStartTime && (_jsx(ThinkingIndicator, { startTime: processingStartTime })), error && (_jsx(Box, { marginY: 1, marginLeft: 1, children: _jsxs(Text, { color: "#e53e3e", children: ["\u2717 Error: ", error] }) })), questionWizardState ? (_jsx(QuestionWizard, { questions: questionWizardState.questions, onComplete: questionWizardState.onComplete, onCancel: () => {
544
+ setQuestionWizardState(null);
545
+ addSystemMessage('Questions cancelled.');
546
+ } }, `wizard-${questionWizardState.questions.map(q => q.header).join('-')}`)) : confirmationState ? (_jsx(Confirmation, { message: confirmationState.message, options: confirmationState.options, onSelect: confirmationState.onSelect, onCancel: confirmationState.onCancel })) : (_jsx(Input, { onSubmit: handleSubmit, disabled: isProcessing, placeholder: 'Try "build a password validator"' })), _jsx(StatusBar, { mode: mode, executionMode: executionMode, tokens: totalTokens, startTime: startTime, isProcessing: isProcessing, activeAgent: activeAgent, completedAgents: completedAgents, autoAccept: autoAccept, councilMode: councilMode, councilStage: councilStage, availableModels: availableModels, showExitHint: showExitHint })] }));
287
547
  };
288
548
  function formatElapsed(start) {
289
549
  const secs = Math.floor((Date.now() - start.getTime()) / 1000);
@@ -10,21 +10,113 @@ const theme = {
10
10
  user: '#9f7aea', // Purple for user
11
11
  agent: '#4fd1c5', // Teal for agent
12
12
  system: '#f6e05e', // Yellow for system
13
+ code: '#f6ad55', // Orange for inline code
14
+ header: '#63b3ed', // Blue for headers
13
15
  };
16
+ // Parse inline markdown and return React elements
17
+ function parseInlineMarkdown(text, defaultColor) {
18
+ const elements = [];
19
+ let remaining = text;
20
+ let key = 0;
21
+ while (remaining.length > 0) {
22
+ // Check for bold **text**
23
+ const boldMatch = remaining.match(/^\*\*(.+?)\*\*/);
24
+ if (boldMatch) {
25
+ elements.push(_jsx(Text, { color: defaultColor, bold: true, children: boldMatch[1] }, key++));
26
+ remaining = remaining.slice(boldMatch[0].length);
27
+ continue;
28
+ }
29
+ // Check for inline code `code`
30
+ const codeMatch = remaining.match(/^`([^`]+)`/);
31
+ if (codeMatch) {
32
+ elements.push(_jsx(Text, { color: theme.code, children: codeMatch[1] }, key++));
33
+ remaining = remaining.slice(codeMatch[0].length);
34
+ continue;
35
+ }
36
+ // Check for italic *text* or _text_
37
+ const italicMatch = remaining.match(/^(\*|_)([^*_]+)\1/);
38
+ if (italicMatch) {
39
+ elements.push(_jsx(Text, { color: defaultColor, italic: true, children: italicMatch[2] }, key++));
40
+ remaining = remaining.slice(italicMatch[0].length);
41
+ continue;
42
+ }
43
+ // Find next special character
44
+ const nextSpecial = remaining.search(/[\*`_]/);
45
+ if (nextSpecial === -1) {
46
+ // No more special chars, add rest as plain text
47
+ elements.push(_jsx(Text, { color: defaultColor, children: remaining }, key++));
48
+ break;
49
+ }
50
+ else if (nextSpecial === 0) {
51
+ // Special char at start but didn't match pattern - treat as literal
52
+ elements.push(_jsx(Text, { color: defaultColor, children: remaining[0] }, key++));
53
+ remaining = remaining.slice(1);
54
+ }
55
+ else {
56
+ // Add text before special char
57
+ elements.push(_jsx(Text, { color: defaultColor, children: remaining.slice(0, nextSpecial) }, key++));
58
+ remaining = remaining.slice(nextSpecial);
59
+ }
60
+ }
61
+ return elements;
62
+ }
63
+ // Render a single line with markdown formatting
64
+ function renderLine(line, index, defaultColor) {
65
+ // Header detection (## Header)
66
+ const headerMatch = line.match(/^(#{1,3})\s+(.+)$/);
67
+ if (headerMatch) {
68
+ const level = headerMatch[1].length;
69
+ const content = headerMatch[2];
70
+ return (_jsx(Box, { children: _jsx(Text, { color: theme.header, bold: true, children: content }) }, index));
71
+ }
72
+ // List item detection (- item or * item)
73
+ const listMatch = line.match(/^(\s*)([-*])\s+(.+)$/);
74
+ if (listMatch) {
75
+ const indent = listMatch[1];
76
+ const content = listMatch[3];
77
+ return (_jsxs(Box, { children: [_jsxs(Text, { color: defaultColor, children: [indent, "\u2022 "] }), parseInlineMarkdown(content, defaultColor)] }, index));
78
+ }
79
+ // Numbered list detection (1. item)
80
+ const numberedMatch = line.match(/^(\s*)(\d+)\.\s+(.+)$/);
81
+ if (numberedMatch) {
82
+ const indent = numberedMatch[1];
83
+ const num = numberedMatch[2];
84
+ const content = numberedMatch[3];
85
+ return (_jsxs(Box, { children: [_jsxs(Text, { color: defaultColor, children: [indent, num, ". "] }), parseInlineMarkdown(content, defaultColor)] }, index));
86
+ }
87
+ // Table row detection (| col | col |)
88
+ if (line.includes('|') && (line.trim().startsWith('|') || line.includes(' | '))) {
89
+ // Keep table formatting as-is but with subtle styling
90
+ const isSeparator = line.match(/^[\s|:-]+$/);
91
+ if (isSeparator) {
92
+ return (_jsx(Box, { children: _jsx(Text, { color: theme.dim, children: line }) }, index));
93
+ }
94
+ return (_jsx(Box, { children: _jsx(Text, { color: defaultColor, children: line }) }, index));
95
+ }
96
+ // Regular line with inline markdown
97
+ return (_jsx(Box, { children: parseInlineMarkdown(line, defaultColor) }, index));
98
+ }
14
99
  export const ChatMessage = ({ role, agentName = 'Agent', content, tokens, }) => {
15
100
  const isUser = role === 'user';
16
101
  const isSystem = role === 'system';
17
102
  const symbolColor = isUser ? theme.user : isSystem ? theme.system : theme.agent;
18
103
  const title = isUser ? 'You' : agentName;
104
+ const textColor = theme.text;
19
105
  const termWidth = process.stdout.columns || 80;
20
106
  const contentWidth = termWidth - 6;
107
+ // Split content into lines and wrap long lines
21
108
  const lines = wrapText(content, contentWidth);
22
- return (_jsxs(Box, { flexDirection: "column", marginY: 1, marginLeft: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: symbolColor, children: '◆ ' }), _jsx(Text, { color: symbolColor, bold: true, children: title })] }), _jsx(Box, { flexDirection: "column", marginLeft: 2, children: lines.map((line, i) => (_jsx(Text, { color: theme.text, children: line }, i))) }), tokens && tokens.input + tokens.output > 0 && (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: theme.dim, children: ["\u2192 ", tokens.input + tokens.output, " tokens"] }) }))] }));
109
+ return (_jsxs(Box, { flexDirection: "column", marginY: 1, marginLeft: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: symbolColor, children: '◆ ' }), _jsx(Text, { color: symbolColor, bold: true, children: title })] }), _jsx(Box, { flexDirection: "column", marginLeft: 2, children: lines.map((line, i) => renderLine(line, i, textColor)) }), tokens && tokens.input + tokens.output > 0 && (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: theme.dim, children: ["\u2192 ", tokens.input + tokens.output, " tokens"] }) }))] }));
23
110
  };
24
111
  function wrapText(text, width) {
25
112
  const lines = [];
26
113
  const paragraphs = text.split('\n');
27
114
  for (const para of paragraphs) {
115
+ // Don't wrap table lines or lines with special formatting
116
+ if (para.includes('|') || para.match(/^#{1,3}\s/) || para.match(/^[\s]*[-*]\s/) || para.match(/^[\s]*\d+\.\s/)) {
117
+ lines.push(para);
118
+ continue;
119
+ }
28
120
  if (para.length <= width) {
29
121
  lines.push(para);
30
122
  }