orquesta-cli 0.2.45 → 0.2.46

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 (38) hide show
  1. package/dist/agents/planner/index.js +2 -1
  2. package/dist/cli.js +17 -16
  3. package/dist/constants.d.ts +1 -1
  4. package/dist/constants.js +1 -1
  5. package/dist/core/commands/clear.d.ts +3 -0
  6. package/dist/core/commands/clear.js +22 -0
  7. package/dist/core/commands/compact.d.ts +3 -0
  8. package/dist/core/commands/compact.js +45 -0
  9. package/dist/core/commands/help.d.ts +3 -0
  10. package/dist/core/commands/help.js +50 -0
  11. package/dist/core/commands/index.d.ts +3 -0
  12. package/dist/core/commands/index.js +11 -0
  13. package/dist/core/commands/memory.d.ts +3 -0
  14. package/dist/core/commands/memory.js +40 -0
  15. package/dist/core/commands/registry.d.ts +11 -0
  16. package/dist/core/commands/registry.js +25 -0
  17. package/dist/core/commands/types.d.ts +10 -0
  18. package/dist/core/commands/types.js +2 -0
  19. package/dist/core/event-bus.d.ts +20 -0
  20. package/dist/core/event-bus.js +35 -0
  21. package/dist/core/git-context.d.ts +11 -0
  22. package/dist/core/git-context.js +62 -0
  23. package/dist/core/ignore-filter.d.ts +4 -0
  24. package/dist/core/ignore-filter.js +50 -0
  25. package/dist/core/llm/llm-client.d.ts +1 -0
  26. package/dist/core/llm/llm-client.js +118 -40
  27. package/dist/core/onboarding.d.ts +3 -0
  28. package/dist/core/onboarding.js +48 -0
  29. package/dist/core/slash-command-handler.js +8 -135
  30. package/dist/orchestration/plan-executor.js +77 -71
  31. package/dist/prompts/shared/tool-usage.js +0 -1
  32. package/dist/prompts/system/plan-execute.js +50 -57
  33. package/dist/tools/llm/simple/file-tools.js +12 -1
  34. package/dist/tools/llm/simple/final-response-tool.js +7 -11
  35. package/dist/tools/registry.js +63 -10
  36. package/dist/ui/components/PlanExecuteApp.d.ts +1 -0
  37. package/dist/ui/components/PlanExecuteApp.js +59 -22
  38. package/package.json +8 -4
@@ -1,83 +1,76 @@
1
1
  import { LANGUAGE_PRIORITY_RULE } from '../shared/language-rules.js';
2
- import { AVAILABLE_TOOLS_WITH_TODO, TOOL_REASON_GUIDE } from '../shared/tool-usage.js';
3
2
  import { CODEBASE_FIRST_RULE } from '../shared/codebase-rules.js';
4
- export const PLAN_EXECUTE_SYSTEM_PROMPT = `You are an AI assistant executing a TODO-based plan.
3
+ export const PLAN_EXECUTE_SYSTEM_PROMPT = `You are Orquesta, an expert AI coding assistant working in the user's terminal. You write correct, production-quality code and help with any development task.
5
4
 
6
5
  ${LANGUAGE_PRIORITY_RULE}
7
6
 
8
- ## TODO Workflow
7
+ ## How You Work
9
8
 
10
- 1. Work through TODOs systematically
11
- 2. Update status using \`write_todos\` (include ALL todos with current status)
12
- 3. **DONE when ALL TODOs are "completed"**
9
+ 1. **Understand first** Read relevant code before modifying it. Never guess file contents.
10
+ 2. **Act, don't describe** Use tools to do the work. Don't say "I would do X", just do X.
11
+ 3. **Verify your changes** After edits, run the build/tests if available to confirm nothing broke.
12
+ 4. **Be concise** — Short answers for simple questions. Thorough work for complex tasks.
13
+ 5. **Match the user's intent** — Do what was asked, no more. Don't add unrequested features or refactors.
13
14
 
14
- **CRITICAL: Keep TODO status in sync with your actual progress!**
15
- - When starting a task → mark it "in_progress" IMMEDIATELY
16
- - When finishing a task → mark it "completed" IMMEDIATELY
17
- - The user sees the TODO list in real-time - mismatched status is confusing
18
- - Call \`write_todos\` FREQUENTLY, not just at the end
15
+ ## Decision Framework
19
16
 
20
- ${AVAILABLE_TOOLS_WITH_TODO}
17
+ - **Simple questions** (what is X, explain Y): Respond directly with knowledge.
18
+ - **Code tasks** (fix, add, edit, refactor): Read → Edit → Verify. Use tools.
19
+ - **Investigation** (why is this failing, what does X do): Read code, search, then explain.
20
+ - **Ambiguous requests**: Infer the most useful action and proceed. Only ask if truly blocked.
21
21
 
22
- ${TOOL_REASON_GUIDE}
22
+ ## Tool Usage
23
23
 
24
- ## Execution Rules
24
+ Use tools for all file operations, commands, and code changes. Your available tools:
25
+ - **read_file**: Always read before editing
26
+ - **edit_file**: Modify existing files (match exact content for old_string)
27
+ - **create_file**: Create new files
28
+ - **list_files** / **find_files** / **search_content**: Navigate the codebase
29
+ - **bash**: Run commands (build, test, git, etc.)
30
+ - **tell_to_user**: Show progress updates
31
+ - **write_todos**: Track task progress (for multi-step work)
25
32
 
26
- 1. **Read before modify** - Always read existing code first
27
- 2. **Use tools** - Perform actual work, don't just describe
28
- 3. **Retry on error** - Up to 3 attempts before marking "failed"
29
- 4. **Stay focused** - Only work on TODOs, no unrelated features
33
+ Every tool has a "reason" parameter shown to the user. Write it naturally in the user's language.
30
34
 
31
- ${CODEBASE_FIRST_RULE}
32
-
33
- ## CRITICAL: Tool Error Handling
34
-
35
- **If a tool returns an error, you MUST retry the same tool with corrected parameters.**
35
+ ## Code Quality Rules
36
36
 
37
- 1. STOP - Read the error message carefully
38
- 2. Investigate - Use \`read_file\` to check actual file content
39
- 3. **RETRY THE SAME TOOL** with corrected parameters (DO NOT skip or move on)
40
- 4. Repeat until success or 3 failures
37
+ - Write minimal, correct code that solves the problem
38
+ - Follow existing project conventions (style, naming, patterns)
39
+ - Use secure coding practices by default
40
+ - Don't introduce new dependencies unless necessary
41
+ - Include error handling where appropriate
41
42
 
42
- **You are NOT allowed to:**
43
- - Skip the failed tool and move to next task
44
- - Say "I'll try a different approach" without actually retrying
45
- - Mark TODO as complete if the tool failed
43
+ ## Error Handling
46
44
 
47
- Example flow:
48
- 1. \`edit_file\` fails: "Line 77 content does not match"
49
- 2. Call \`read_file\` to see actual content
50
- 3. **Call \`edit_file\` again** with correct \`old_string\`
51
- 4. Only proceed after edit succeeds
45
+ If a tool fails:
46
+ 1. Read the error carefully
47
+ 2. Investigate (read_file to check actual content)
48
+ 3. Retry with corrected parameters
49
+ 4. Only give up after 3 failed attempts
52
50
 
53
- ## CRITICAL: When to Respond
51
+ Common edit_file failures: wrong old_string → re-read file, copy exact text, retry.
54
52
 
55
- **ONLY respond when ALL TODOs are "completed" or "failed".**
56
-
57
- - Responding early = execution ends prematurely
58
- - Use \`tell_to_user\` to communicate progress during execution
59
- - \`write_todos\` only updates internal state
60
-
61
- **Before final response, verify:**
62
- - All TODOs completed?
63
- - All tool calls successful?
64
- - User's request fulfilled?
53
+ ${CODEBASE_FIRST_RULE}
65
54
 
66
- ## CRITICAL: Final Response
55
+ ## Response Style
67
56
 
68
- Your final response MUST contain the **actual answer or result**:
69
- - Question Answer with information found
70
- - Task Summarize what was done
57
+ - Direct and concise. No filler phrases.
58
+ - Code in markdown blocks with language tags.
59
+ - When summarizing completed work: state what was done in 1-3 sentences.
60
+ - Match the user's language (if they write in Spanish, respond in Spanish).
71
61
 
72
- **DO NOT** just say "Task complete" or give task statistics.
62
+ ## TODO Workflow (for multi-step tasks)
73
63
 
74
- Example:
75
- - User: "What's the project name?" "This project is **LOCAL-CLI**."
76
- - User: "Add a debug function""Added debug function to logger.ts."
64
+ When working on a plan with TODOs:
65
+ - Update status via write_todos as you progress
66
+ - Mark "in_progress" when starting, "completed" when done
67
+ - Stay focused on the current task
77
68
 
78
- ## Loop Detection
69
+ ## IMPORTANT
79
70
 
80
- If TODO context keeps repeating but work is done IMMEDIATELY mark all as "completed".
71
+ - You can respond directly without using any tool do so for simple questions or when you're done working.
72
+ - After completing all requested work, give a brief summary of what was done.
73
+ - Never fabricate file contents, paths, or command outputs. If unsure, investigate first.
81
74
  `;
82
75
  export default PLAN_EXECUTE_SYSTEM_PROMPT;
83
76
  //# sourceMappingURL=plan-execute.js.map
@@ -1,6 +1,7 @@
1
1
  import * as fs from 'fs/promises';
2
2
  import * as path from 'path';
3
3
  import { logger } from '../../../utils/logger.js';
4
+ import { shouldIgnore } from '../../../core/ignore-filter.js';
4
5
  const EXCLUDED_DIRS = new Set([
5
6
  'node_modules',
6
7
  '.git',
@@ -451,6 +452,9 @@ async function getFilesRecursively(dirPath, baseDir = dirPath, depth = 0, fileCo
451
452
  }
452
453
  const fullPath = path.join(dirPath, entry.name);
453
454
  const relativePath = path.relative(baseDir, fullPath);
455
+ if (shouldIgnore(fullPath)) {
456
+ continue;
457
+ }
454
458
  if (entry.isDirectory()) {
455
459
  if (EXCLUDED_DIRS.has(entry.name)) {
456
460
  continue;
@@ -491,7 +495,9 @@ async function _executeListFilesInternal(args) {
491
495
  }
492
496
  else {
493
497
  const entries = await fs.readdir(resolvedPath, { withFileTypes: true });
494
- const files = entries.map((entry) => ({
498
+ const files = entries
499
+ .filter(entry => !shouldIgnore(path.join(resolvedPath, entry.name)))
500
+ .map((entry) => ({
495
501
  name: entry.name,
496
502
  type: entry.isDirectory() ? 'directory' : 'file',
497
503
  path: path.join(directoryPath, entry.name),
@@ -580,6 +586,9 @@ async function findFilesRecursively(dirPath, regex, baseDir, depth = 0, fileCoun
580
586
  continue;
581
587
  }
582
588
  const fullPath = path.join(dirPath, entry.name);
589
+ if (shouldIgnore(fullPath)) {
590
+ continue;
591
+ }
583
592
  if (entry.isDirectory()) {
584
593
  if (EXCLUDED_DIRS.has(entry.name)) {
585
594
  continue;
@@ -700,6 +709,8 @@ async function searchContentRecursively(dirPath, searchRegex, baseDir, fileRegex
700
709
  if (entry.name.startsWith('.'))
701
710
  continue;
702
711
  const fullPath = path.join(dirPath, entry.name);
712
+ if (shouldIgnore(fullPath))
713
+ continue;
703
714
  if (entry.isDirectory()) {
704
715
  if (EXCLUDED_DIRS.has(entry.name))
705
716
  continue;
@@ -1,4 +1,5 @@
1
1
  import { logger } from '../../../utils/logger.js';
2
+ import { eventBus, Events } from '../../../core/event-bus.js';
2
3
  let getTodosCallback = null;
3
4
  let finalResponseCallback = null;
4
5
  let markTodosCompletedCallback = null;
@@ -28,23 +29,16 @@ const FINAL_RESPONSE_DEFINITION = {
28
29
  type: 'function',
29
30
  function: {
30
31
  name: 'final_response',
31
- description: `Use this tool to deliver your final response to the user after completing all tasks.
32
+ description: `Deliver a final summary to the user after completing work. Optional — you can also respond directly without this tool.
32
33
 
33
- IMPORTANT:
34
- - You MUST complete all TODOs before calling this tool
35
- - If any TODO is not completed, this tool will return an error
36
- - After all tasks are done, use this tool to summarize what was accomplished
37
-
38
- Example:
39
- {
40
- "message": "I've completed all the requested tasks:\\n\\n1. Fixed the bug in the login form\\n2. Added input validation\\n3. Updated the tests\\n\\nAll changes have been committed."
41
- }`,
34
+ Use this when you want to explicitly signal task completion with a summary.
35
+ Any incomplete TODOs will be auto-marked as done.`,
42
36
  parameters: {
43
37
  type: 'object',
44
38
  properties: {
45
39
  message: {
46
40
  type: 'string',
47
- description: 'Your final response message to the user. Summarize what was accomplished.',
41
+ description: 'Your final response message to the user.',
48
42
  },
49
43
  },
50
44
  required: ['message'],
@@ -138,6 +132,7 @@ async function executeFinalResponse(args) {
138
132
  if (finalResponseCallback) {
139
133
  finalResponseCallback(message);
140
134
  }
135
+ eventBus.emit(Events.FINAL_RESPONSE, message);
141
136
  return {
142
137
  success: true,
143
138
  result: message,
@@ -163,6 +158,7 @@ async function executeFinalResponse(args) {
163
158
  if (finalResponseCallback) {
164
159
  finalResponseCallback(message);
165
160
  }
161
+ eventBus.emit(Events.FINAL_RESPONSE, message);
166
162
  return {
167
163
  success: true,
168
164
  result: message,
@@ -9,10 +9,33 @@ import { PLANNING_TOOLS } from './llm/simple/planning-tools.js';
9
9
  import { FinalResponseTool } from './llm/simple/final-response-tool.js';
10
10
  import { RemotePhoneTool } from './llm/simple/remote-phone-tool.js';
11
11
  import { getShellTools } from './llm/simple/index.js';
12
- import { BROWSER_TOOLS, startBrowserServer, shutdownBrowserServer } from './browser/index.js';
13
- import { WORD_TOOLS, EXCEL_TOOLS, POWERPOINT_TOOLS } from './office/index.js';
12
+ let _browserTools = null;
13
+ let _startBrowserServer = null;
14
+ let _shutdownBrowserServer = null;
15
+ async function getBrowserModule() {
16
+ if (!_browserTools) {
17
+ const mod = await import('./browser/index.js');
18
+ _browserTools = mod.BROWSER_TOOLS;
19
+ _startBrowserServer = mod.startBrowserServer;
20
+ _shutdownBrowserServer = mod.shutdownBrowserServer;
21
+ }
22
+ return { BROWSER_TOOLS: _browserTools, startBrowserServer: _startBrowserServer, shutdownBrowserServer: _shutdownBrowserServer };
23
+ }
24
+ let _wordTools = null;
25
+ let _excelTools = null;
26
+ let _powerpointTools = null;
27
+ async function getOfficeModule() {
28
+ if (!_wordTools) {
29
+ const mod = await import('./office/index.js');
30
+ _wordTools = mod.WORD_TOOLS;
31
+ _excelTools = mod.EXCEL_TOOLS;
32
+ _powerpointTools = mod.POWERPOINT_TOOLS;
33
+ }
34
+ return { WORD_TOOLS: _wordTools, EXCEL_TOOLS: _excelTools, POWERPOINT_TOOLS: _powerpointTools };
35
+ }
14
36
  async function validateBrowserTools() {
15
37
  try {
38
+ const { startBrowserServer } = await getBrowserModule();
16
39
  await startBrowserServer();
17
40
  return { success: true };
18
41
  }
@@ -29,10 +52,19 @@ function getOptionalToolGroupsConfig() {
29
52
  id: 'browser',
30
53
  name: 'Browser Automation',
31
54
  description: 'Control Chrome/Edge browser for web testing (navigate, click, screenshot, etc.)',
32
- tools: BROWSER_TOOLS,
55
+ tools: [],
33
56
  enabled: false,
34
- onEnable: validateBrowserTools,
35
- onDisable: shutdownBrowserServer,
57
+ onEnable: async () => {
58
+ const { BROWSER_TOOLS } = await getBrowserModule();
59
+ const group = OPTIONAL_TOOL_GROUPS.find(g => g.id === 'browser');
60
+ if (group)
61
+ group.tools = BROWSER_TOOLS;
62
+ return validateBrowserTools();
63
+ },
64
+ onDisable: async () => {
65
+ const { shutdownBrowserServer } = await getBrowserModule();
66
+ await shutdownBrowserServer();
67
+ },
36
68
  },
37
69
  ];
38
70
  if (hasWindowsAccess()) {
@@ -40,20 +72,41 @@ function getOptionalToolGroupsConfig() {
40
72
  id: 'word',
41
73
  name: 'Microsoft Word',
42
74
  description: 'Control Word for document editing (write, read, save, export PDF, header/footer)',
43
- tools: WORD_TOOLS,
75
+ tools: [],
44
76
  enabled: false,
77
+ onEnable: async () => {
78
+ const { WORD_TOOLS } = await getOfficeModule();
79
+ const group = OPTIONAL_TOOL_GROUPS.find(g => g.id === 'word');
80
+ if (group)
81
+ group.tools = WORD_TOOLS;
82
+ return { success: true };
83
+ },
45
84
  }, {
46
85
  id: 'excel',
47
86
  name: 'Microsoft Excel',
48
87
  description: 'Control Excel for spreadsheet editing (cells, ranges, formulas, charts)',
49
- tools: EXCEL_TOOLS,
88
+ tools: [],
50
89
  enabled: false,
90
+ onEnable: async () => {
91
+ const { EXCEL_TOOLS } = await getOfficeModule();
92
+ const group = OPTIONAL_TOOL_GROUPS.find(g => g.id === 'excel');
93
+ if (group)
94
+ group.tools = EXCEL_TOOLS;
95
+ return { success: true };
96
+ },
51
97
  }, {
52
98
  id: 'powerpoint',
53
99
  name: 'Microsoft PowerPoint',
54
100
  description: 'Control PowerPoint for presentations (slides, shapes, transitions, PDF export)',
55
- tools: POWERPOINT_TOOLS,
101
+ tools: [],
56
102
  enabled: false,
103
+ onEnable: async () => {
104
+ const { POWERPOINT_TOOLS } = await getOfficeModule();
105
+ const group = OPTIONAL_TOOL_GROUPS.find(g => g.id === 'powerpoint');
106
+ if (group)
107
+ group.tools = POWERPOINT_TOOLS;
108
+ return { success: true };
109
+ },
57
110
  });
58
111
  }
59
112
  return groups;
@@ -138,9 +191,9 @@ class ToolRegistry {
138
191
  logger.error('Tool group not found', { groupId });
139
192
  return { success: false, error: `Tool group '${groupId}' not found` };
140
193
  }
141
- if (!skipValidation && group.onEnable) {
194
+ if (group.onEnable) {
142
195
  const result = await group.onEnable();
143
- if (!result.success) {
196
+ if (!skipValidation && !result.success) {
144
197
  return result;
145
198
  }
146
199
  }
@@ -17,6 +17,7 @@ interface PlanExecuteAppProps {
17
17
  model: string;
18
18
  endpoint: string;
19
19
  };
20
+ resumeLastSession?: boolean;
20
21
  }
21
22
  export declare const PlanExecuteApp: React.FC<PlanExecuteAppProps>;
22
23
  export default PlanExecuteApp;
@@ -38,6 +38,7 @@ import { readHookConfig } from '../../orquesta/hook-init.js';
38
38
  import { remotePhone } from '../../orquesta/remote-phone.js';
39
39
  import { logger } from '../../utils/logger.js';
40
40
  import { usageTracker } from '../../core/usage-tracker.js';
41
+ import { estimateCost, formatCostUsd } from '../../core/pricing.js';
41
42
  import { UpdateNotification } from '../UpdateNotification.js';
42
43
  import { checkForCliUpdate, runCliUpdate, setSkippedVersion } from '../../utils/update-checker.js';
43
44
  import { setToolExecutionCallback, setTellToUserCallback, setToolResponseCallback, setPlanCreatedCallback, setTodoStartCallback, setTodoCompleteCallback, setTodoFailCallback, setCompactCallback, setAssistantResponseCallback, setToolApprovalCallback, setReasoningCallback, } from '../../tools/llm/simple/file-tools.js';
@@ -88,7 +89,7 @@ function getStatusText({ phase, todos, currentToolName }) {
88
89
  }
89
90
  return `${progressPrefix}Processing`;
90
91
  }
91
- export const PlanExecuteApp = ({ llmClient: initialLlmClient, modelInfo }) => {
92
+ export const PlanExecuteApp = ({ llmClient: initialLlmClient, modelInfo, resumeLastSession }) => {
92
93
  const { exit } = useApp();
93
94
  const hookBinding = useMemo(() => {
94
95
  const cfg = readHookConfig(process.cwd());
@@ -103,6 +104,7 @@ export const PlanExecuteApp = ({ llmClient: initialLlmClient, modelInfo }) => {
103
104
  const [messages, setMessages] = useState([]);
104
105
  const { input, setInput, handleHistoryPrev, handleHistoryNext, addToHistory } = useInputHistory();
105
106
  const [isProcessing, setIsProcessing] = useState(false);
107
+ const [streamingText, setStreamingText] = useState('');
106
108
  const [updatePhase, setUpdatePhase] = useState('hidden');
107
109
  const [updateLatest, setUpdateLatest] = useState('');
108
110
  const [updateProgress, setUpdateProgress] = useState('');
@@ -168,6 +170,21 @@ export const PlanExecuteApp = ({ llmClient: initialLlmClient, modelInfo }) => {
168
170
  useEffect(() => {
169
171
  sessionManager.setLogEntries(logEntries);
170
172
  }, [logEntries]);
173
+ useEffect(() => {
174
+ if (!resumeLastSession)
175
+ return;
176
+ (async () => {
177
+ const sessions = await sessionManager.listSessions();
178
+ if (sessions.length === 0)
179
+ return;
180
+ const last = sessions[0];
181
+ const data = await sessionManager.loadSession(last.id);
182
+ if (data && data.messages.length > 0) {
183
+ setMessages(data.messages);
184
+ addLog({ type: 'session_restored', content: `↩ Resumed session (${data.messages.filter(m => m.role === 'user').length} messages)` });
185
+ }
186
+ })();
187
+ }, [resumeLastSession]);
171
188
  const fileBrowserState = useFileBrowserState(input, isProcessing);
172
189
  const commandBrowserState = useCommandBrowserState(input, isProcessing);
173
190
  const planExecutionState = usePlanExecution(pendingMessageCallbacks);
@@ -261,6 +278,7 @@ export const PlanExecuteApp = ({ llmClient: initialLlmClient, modelInfo }) => {
261
278
  }, [addLog]);
262
279
  useEffect(() => {
263
280
  setAssistantResponseCallback((content) => {
281
+ setStreamingText('');
264
282
  addLog({
265
283
  type: 'assistant_message',
266
284
  content,
@@ -271,6 +289,17 @@ export const PlanExecuteApp = ({ llmClient: initialLlmClient, modelInfo }) => {
271
289
  setAssistantResponseCallback(null);
272
290
  };
273
291
  }, [addLog]);
292
+ useEffect(() => {
293
+ if (llmClient) {
294
+ llmClient.onStreamingContent = (token) => {
295
+ setStreamingText(prev => prev + token);
296
+ };
297
+ }
298
+ return () => {
299
+ if (llmClient)
300
+ llmClient.onStreamingContent = null;
301
+ };
302
+ }, [llmClient]);
274
303
  useEffect(() => {
275
304
  setReasoningCallback((content, _isStreaming) => {
276
305
  addLog({
@@ -432,29 +461,30 @@ export const PlanExecuteApp = ({ llmClient: initialLlmClient, modelInfo }) => {
432
461
  logger.startTimer('app-init');
433
462
  try {
434
463
  setInitStep('health');
435
- logger.flow('Running health check');
464
+ logger.flow('Running health check + docs init in parallel');
436
465
  setHealthStatus('checking');
437
- if (configManager.hasEndpoints()) {
438
- const healthResults = await LLMClient.healthCheckAll();
439
- let hasHealthy = false;
440
- for (const [endpointId, modelResults] of healthResults) {
441
- logger.vars({ name: 'endpointId', value: endpointId }, { name: 'healthyModels', value: modelResults.filter(r => r.healthy).length });
442
- if (modelResults.some((r) => r.healthy)) {
443
- hasHealthy = true;
466
+ const healthPromise = (async () => {
467
+ if (configManager.hasEndpoints()) {
468
+ const healthResults = await LLMClient.healthCheckAll();
469
+ let hasHealthy = false;
470
+ for (const [endpointId, modelResults] of healthResults) {
471
+ logger.vars({ name: 'endpointId', value: endpointId }, { name: 'healthyModels', value: modelResults.filter(r => r.healthy).length });
472
+ if (modelResults.some((r) => r.healthy)) {
473
+ hasHealthy = true;
474
+ }
444
475
  }
476
+ logger.state('Health status', 'checking', hasHealthy ? 'healthy' : 'unhealthy');
477
+ setHealthStatus(hasHealthy ? 'healthy' : 'unhealthy');
478
+ await configManager.updateAllHealthStatus(healthResults);
445
479
  }
446
- logger.state('Health status', 'checking', hasHealthy ? 'healthy' : 'unhealthy');
447
- setHealthStatus(hasHealthy ? 'healthy' : 'unhealthy');
448
- await configManager.updateAllHealthStatus(healthResults);
449
- }
450
- else {
451
- setHealthStatus('unknown');
452
- }
453
- setInitStep('docs');
454
- logger.flow('Initializing docs directory');
455
- await initializeDocsDirectory().catch((err) => {
480
+ else {
481
+ setHealthStatus('unknown');
482
+ }
483
+ })();
484
+ const docsPromise = initializeDocsDirectory().catch((err) => {
456
485
  logger.warn('Docs directory initialization warning', { error: err });
457
486
  });
487
+ await Promise.all([healthPromise, docsPromise]);
458
488
  setInitStep('config');
459
489
  logger.flow('Checking configuration');
460
490
  if (!configManager.hasEndpoints()) {
@@ -938,6 +968,7 @@ export const PlanExecuteApp = ({ llmClient: initialLlmClient, modelInfo }) => {
938
968
  let updatedMessages = [...messages, userMsg];
939
969
  setMessages(updatedMessages);
940
970
  setIsProcessing(true);
971
+ setStreamingText('');
941
972
  setActivityStartTime(Date.now());
942
973
  setSubActivities([]);
943
974
  if (llmClient) {
@@ -1389,8 +1420,10 @@ export const PlanExecuteApp = ({ llmClient: initialLlmClient, modelInfo }) => {
1389
1420
  React.createElement(Static, { items: logEntries }, (entry) => renderLogEntry(entry)),
1390
1421
  pendingToolApproval && (React.createElement(Box, { marginY: 1 },
1391
1422
  React.createElement(ApprovalDialog, { toolName: pendingToolApproval.toolName, args: pendingToolApproval.args, reason: pendingToolApproval.reason, onResponse: handleApprovalResponse }))),
1392
- isProcessing && (planExecutionState.executionPhase === 'planning' || planExecutionState.todos.length === 0) && !pendingToolApproval && !isDocsSearching && (React.createElement(Box, { marginY: 1 },
1393
- React.createElement(ActivityIndicator, { activity: getCurrentActivityType(), startTime: activityStartTime, detail: activityDetail, subActivities: subActivities, modelName: currentModelInfo.model }))),
1423
+ isProcessing && (planExecutionState.executionPhase === 'planning' || planExecutionState.todos.length === 0) && !pendingToolApproval && !isDocsSearching && (React.createElement(Box, { marginY: 1, flexDirection: "column" },
1424
+ React.createElement(ActivityIndicator, { activity: getCurrentActivityType(), startTime: activityStartTime, detail: activityDetail, subActivities: subActivities, modelName: currentModelInfo.model }),
1425
+ streamingText && (React.createElement(Box, { marginTop: 1, paddingX: 1 },
1426
+ React.createElement(Text, { wrap: "wrap" }, streamingText))))),
1394
1427
  isDocsSearching && (React.createElement(DocsSearchProgress, { logs: docsSearchLogs, isSearching: isDocsSearching })),
1395
1428
  planExecutionState.todos.length > 0 && (React.createElement(Box, { marginTop: 2, marginBottom: 1 },
1396
1429
  React.createElement(TodoPanel, { todos: planExecutionState.todos, currentTodoId: planExecutionState.currentTodoId, isProcessing: isProcessing }))),
@@ -1458,7 +1491,11 @@ export const PlanExecuteApp = ({ llmClient: initialLlmClient, modelInfo }) => {
1458
1491
  ' ',
1459
1492
  "(esc to interrupt \u00B7 ",
1460
1493
  formatElapsedTime(sessionElapsed),
1461
- sessionTokens > 0 && ` · ↑ ${formatTokensCompact(sessionTokens)} tokens`,
1494
+ sessionTokens > 0 && (() => {
1495
+ const usage = usageTracker.getSessionUsage();
1496
+ const cost = estimateCost(currentModelInfo.model, usage.inputTokens, usage.outputTokens);
1497
+ return ` · ↑ ${formatTokensCompact(sessionTokens)} tokens${cost ? ` · ${formatCostUsd(cost)}` : ''}`;
1498
+ })(),
1462
1499
  ")"))),
1463
1500
  React.createElement(Box, null,
1464
1501
  (() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orquesta-cli",
3
- "version": "0.2.45",
3
+ "version": "0.2.46",
4
4
  "description": "Orquesta CLI - AI-powered coding assistant with team collaboration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -11,14 +11,16 @@
11
11
  "scripts": {
12
12
  "preinstall": "node scripts/check-node.cjs",
13
13
  "build": "tsc",
14
+ "test": "vitest run",
15
+ "test:watch": "vitest",
14
16
  "postbuild": "chmod +x dist/cli.js",
15
- "dev": "ts-node src/cli.ts",
17
+ "dev": "tsx watch src/cli.ts",
16
18
  "start": "node dist/cli.js",
17
19
  "watch": "tsc --watch",
18
20
  "lint": "eslint src/**/*.ts",
19
21
  "lint:fix": "eslint src/**/*.ts --fix",
20
22
  "format": "prettier --write \"src/**/*.ts\"",
21
- "test": "cd tests && python -m pytest test_eval.py -v",
23
+ "test:eval": "cd tests && python -m pytest test_eval.py -v",
22
24
  "test:quick": "cd tests && python -m pytest test_eval.py -v -m 'not slow'",
23
25
  "prepr": "npm run lint && npm run build",
24
26
  "bun:build": "node scripts/inject-version.js && npm run build && bun build dist/cli.js --compile --outfile bin/lcli && cp node_modules/yoga-wasm-web/dist/yoga.wasm bin/",
@@ -126,7 +128,9 @@
126
128
  "react-devtools-core": "^4.28.5",
127
129
  "react-dom": "^18.3.1",
128
130
  "ts-node": "^10.9.2",
131
+ "tsx": "^4.19.0",
129
132
  "typescript": "^5.3.3",
130
- "vite": "^7.3.1"
133
+ "vite": "^7.3.1",
134
+ "vitest": "^3.2.1"
131
135
  }
132
136
  }