orquesta-cli 0.2.49 → 0.2.51
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/core/embeddings-context.d.ts +3 -0
- package/dist/core/embeddings-context.js +109 -0
- package/dist/core/llm/llm-client.js +10 -0
- package/dist/core/session/session-manager.js +2 -0
- package/dist/core/sub-agent.d.ts +14 -0
- package/dist/core/sub-agent.js +27 -0
- package/dist/orchestration/plan-executor.js +5 -2
- package/dist/orquesta/session-sync.d.ts +3 -0
- package/dist/orquesta/session-sync.js +31 -0
- package/dist/tools/llm/simple/sub-agent-tool.d.ts +3 -0
- package/dist/tools/llm/simple/sub-agent-tool.js +37 -0
- package/dist/tools/registry.js +2 -0
- package/dist/ui/components/PlanExecuteApp.js +36 -10
- package/package.json +1 -1
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { shouldIgnore } from './ignore-filter.js';
|
|
4
|
+
const SOURCE_EXTENSIONS = new Set([
|
|
5
|
+
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
|
|
6
|
+
'.py', '.rs', '.go', '.java', '.rb', '.vue', '.svelte',
|
|
7
|
+
]);
|
|
8
|
+
const SYMBOL_RE = /(?:function|class|interface|type|enum|const|let|var|export\s+(?:default\s+)?(?:function|class|const|let|async\s+function))\s+(\w+)/g;
|
|
9
|
+
let indexCache = null;
|
|
10
|
+
function scanDir(dir, entries, depth = 0) {
|
|
11
|
+
if (depth > 6)
|
|
12
|
+
return;
|
|
13
|
+
let items;
|
|
14
|
+
try {
|
|
15
|
+
items = fs.readdirSync(dir);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
for (const item of items) {
|
|
21
|
+
const full = path.join(dir, item);
|
|
22
|
+
const rel = path.relative(process.cwd(), full);
|
|
23
|
+
if (shouldIgnore(rel))
|
|
24
|
+
continue;
|
|
25
|
+
let stat;
|
|
26
|
+
try {
|
|
27
|
+
stat = fs.statSync(full);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (stat.isDirectory()) {
|
|
33
|
+
scanDir(full, entries, depth + 1);
|
|
34
|
+
}
|
|
35
|
+
else if (SOURCE_EXTENSIONS.has(path.extname(item))) {
|
|
36
|
+
try {
|
|
37
|
+
const content = fs.readFileSync(full, 'utf-8');
|
|
38
|
+
const lines = content.split('\n');
|
|
39
|
+
const symbols = [];
|
|
40
|
+
let m;
|
|
41
|
+
SYMBOL_RE.lastIndex = 0;
|
|
42
|
+
while ((m = SYMBOL_RE.exec(content)) !== null) {
|
|
43
|
+
if (m[1])
|
|
44
|
+
symbols.push(m[1]);
|
|
45
|
+
}
|
|
46
|
+
entries.set(rel, {
|
|
47
|
+
path: rel,
|
|
48
|
+
symbols,
|
|
49
|
+
preview: lines.slice(0, 5).join('\n'),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch { }
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function getIndex() {
|
|
57
|
+
if (!indexCache) {
|
|
58
|
+
indexCache = new Map();
|
|
59
|
+
scanDir(process.cwd(), indexCache);
|
|
60
|
+
}
|
|
61
|
+
return indexCache;
|
|
62
|
+
}
|
|
63
|
+
export function getRelevantContext(userMessage) {
|
|
64
|
+
const index = getIndex();
|
|
65
|
+
if (index.size === 0)
|
|
66
|
+
return '';
|
|
67
|
+
const words = userMessage
|
|
68
|
+
.toLowerCase()
|
|
69
|
+
.split(/[\s\W]+/)
|
|
70
|
+
.filter(w => w.length > 2);
|
|
71
|
+
if (words.length === 0)
|
|
72
|
+
return '';
|
|
73
|
+
const scored = [];
|
|
74
|
+
for (const entry of index.values()) {
|
|
75
|
+
let score = 0;
|
|
76
|
+
const pathLower = entry.path.toLowerCase();
|
|
77
|
+
const symbolsLower = entry.symbols.map(s => s.toLowerCase());
|
|
78
|
+
for (const word of words) {
|
|
79
|
+
if (pathLower.includes(word))
|
|
80
|
+
score += 2;
|
|
81
|
+
for (const sym of symbolsLower) {
|
|
82
|
+
if (sym.includes(word))
|
|
83
|
+
score += 3;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (score > 0)
|
|
87
|
+
scored.push({ entry, score });
|
|
88
|
+
}
|
|
89
|
+
if (scored.length === 0)
|
|
90
|
+
return '';
|
|
91
|
+
scored.sort((a, b) => b.score - a.score);
|
|
92
|
+
const top = scored.slice(0, 5);
|
|
93
|
+
const sections = top.map(({ entry }) => {
|
|
94
|
+
let preview;
|
|
95
|
+
try {
|
|
96
|
+
const content = fs.readFileSync(path.resolve(process.cwd(), entry.path), 'utf-8');
|
|
97
|
+
preview = content.split('\n').slice(0, 20).join('\n');
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
preview = entry.preview;
|
|
101
|
+
}
|
|
102
|
+
return `File: ${entry.path}\nSymbols: ${entry.symbols.join(', ')}\nPreview:\n${preview.split('\n').map(l => ' ' + l).join('\n')}`;
|
|
103
|
+
});
|
|
104
|
+
return `\n<relevant_files>\n${sections.join('\n\n')}\n</relevant_files>`;
|
|
105
|
+
}
|
|
106
|
+
export function resetEmbeddingsCache() {
|
|
107
|
+
indexCache = null;
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=embeddings-context.js.map
|
|
@@ -203,6 +203,16 @@ export class LLMClient {
|
|
|
203
203
|
const processedMessages = options.messages ?
|
|
204
204
|
this.preprocessMessages(options.messages, modelId) : [];
|
|
205
205
|
logger.vars({ name: 'modelId', value: modelId }, { name: 'originalMessages', value: options.messages?.length || 0 }, { name: 'processedMessages', value: processedMessages.length }, { name: 'temperature', value: options.temperature ?? 0 });
|
|
206
|
+
let systemCached = false;
|
|
207
|
+
for (const msg of processedMessages) {
|
|
208
|
+
if (!systemCached && msg.role === 'system') {
|
|
209
|
+
msg.cache_control = { type: 'ephemeral' };
|
|
210
|
+
systemCached = true;
|
|
211
|
+
}
|
|
212
|
+
else if (msg.role === 'user' && typeof msg.content === 'string' && msg.content.length > 2000) {
|
|
213
|
+
msg.cache_control = { type: 'ephemeral' };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
206
216
|
const isClaudeModel = /claude|sonnet|opus|haiku/i.test(modelId);
|
|
207
217
|
const requestBody = {
|
|
208
218
|
model: modelId,
|
|
@@ -4,6 +4,7 @@ import { configManager } from '../config/config-manager.js';
|
|
|
4
4
|
import { PROJECTS_DIR, cwdToProjectSegment } from '../../constants.js';
|
|
5
5
|
import { initializeJsonStreamLogger } from '../../utils/json-stream-logger.js';
|
|
6
6
|
import { logger } from '../../utils/logger.js';
|
|
7
|
+
import { scheduleSessionSync } from '../../orquesta/session-sync.js';
|
|
7
8
|
export class SessionManager {
|
|
8
9
|
currentSessionId = null;
|
|
9
10
|
currentSessionCreatedAt = null;
|
|
@@ -233,6 +234,7 @@ export class SessionManager {
|
|
|
233
234
|
const sessionsDir = this.getSessionsDir();
|
|
234
235
|
const filePath = path.join(sessionsDir, `${this.currentSessionId}.json`);
|
|
235
236
|
await fs.writeFile(filePath, JSON.stringify(sessionData, null, 2), 'utf-8');
|
|
237
|
+
scheduleSessionSync(this.currentSessionId, normalizedMessages);
|
|
236
238
|
}
|
|
237
239
|
finally {
|
|
238
240
|
this.isSaving = false;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface SubAgentResult {
|
|
2
|
+
success: boolean;
|
|
3
|
+
output: string;
|
|
4
|
+
error?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function spawnSubAgent(task: string, options?: {
|
|
7
|
+
cwd?: string;
|
|
8
|
+
timeout?: number;
|
|
9
|
+
}): Promise<SubAgentResult>;
|
|
10
|
+
export declare function spawnSubAgentsParallel(tasks: Array<{
|
|
11
|
+
task: string;
|
|
12
|
+
cwd?: string;
|
|
13
|
+
}>, maxConcurrent?: number): Promise<SubAgentResult[]>;
|
|
14
|
+
//# sourceMappingURL=sub-agent.d.ts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
export async function spawnSubAgent(task, options) {
|
|
3
|
+
const cwd = options?.cwd || process.cwd();
|
|
4
|
+
const timeout = options?.timeout || 120000;
|
|
5
|
+
try {
|
|
6
|
+
const output = execSync(`orquesta -p '${task.replace(/'/g, "'\\''")}' --dangerously-skip-permissions`, { cwd, timeout, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
7
|
+
return { success: true, output: output.trim() };
|
|
8
|
+
}
|
|
9
|
+
catch (err) {
|
|
10
|
+
const error = err;
|
|
11
|
+
return {
|
|
12
|
+
success: false,
|
|
13
|
+
output: error.stdout?.trim() || '',
|
|
14
|
+
error: error.stderr?.trim() || error.message || 'Unknown error',
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export async function spawnSubAgentsParallel(tasks, maxConcurrent = 3) {
|
|
19
|
+
const results = [];
|
|
20
|
+
for (let i = 0; i < tasks.length; i += maxConcurrent) {
|
|
21
|
+
const batch = tasks.slice(i, i + maxConcurrent);
|
|
22
|
+
const batchResults = await Promise.all(batch.map(t => spawnSubAgent(t.task, { cwd: t.cwd })));
|
|
23
|
+
results.push(...batchResults);
|
|
24
|
+
}
|
|
25
|
+
return results;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=sub-agent.js.map
|
|
@@ -16,6 +16,7 @@ import { GIT_COMMIT_RULES } from '../prompts/shared/git-rules.js';
|
|
|
16
16
|
import { logger } from '../utils/logger.js';
|
|
17
17
|
import { getStreamLogger } from '../utils/json-stream-logger.js';
|
|
18
18
|
import { detectGitRepo } from '../utils/git-utils.js';
|
|
19
|
+
import { getRelevantContext } from '../core/embeddings-context.js';
|
|
19
20
|
import { formatErrorMessage, buildTodoContext, findActiveTodo, getTodoStats } from './utils.js';
|
|
20
21
|
import { BaseError } from '../errors/base.js';
|
|
21
22
|
import { runParallelGraph, shouldUseParallelOrchestrator } from './parallel-orchestrator.js';
|
|
@@ -169,8 +170,9 @@ export class PlanExecutor {
|
|
|
169
170
|
const tools = toolRegistry.getLLMToolDefinitions();
|
|
170
171
|
const hasSystemMessage = currentMessages.some(m => m.role === 'system');
|
|
171
172
|
if (!hasSystemMessage) {
|
|
173
|
+
const relevantContext = getRelevantContext(userMessage);
|
|
172
174
|
currentMessages = [
|
|
173
|
-
{ role: 'system', content: this.getSystemPrompt() },
|
|
175
|
+
{ role: 'system', content: this.getSystemPrompt() + relevantContext },
|
|
174
176
|
...currentMessages
|
|
175
177
|
];
|
|
176
178
|
}
|
|
@@ -318,8 +320,9 @@ export class PlanExecutor {
|
|
|
318
320
|
const tools = toolRegistry.getLLMToolDefinitions();
|
|
319
321
|
const hasSystemMessage = currentMessages.some(m => m.role === 'system');
|
|
320
322
|
if (!hasSystemMessage) {
|
|
323
|
+
const relevantContext = getRelevantContext(userMessage);
|
|
321
324
|
currentMessages = [
|
|
322
|
-
{ role: 'system', content: this.getSystemPrompt() },
|
|
325
|
+
{ role: 'system', content: this.getSystemPrompt() + relevantContext },
|
|
323
326
|
...currentMessages
|
|
324
327
|
];
|
|
325
328
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { configManager } from '../core/config/config-manager.js';
|
|
2
|
+
const ORQUESTA_API = process.env['ORQUESTA_API_URL'] || 'https://getorquesta.com';
|
|
3
|
+
let syncTimeout = null;
|
|
4
|
+
export function scheduleSessionSync(sessionId, messages) {
|
|
5
|
+
if (syncTimeout)
|
|
6
|
+
clearTimeout(syncTimeout);
|
|
7
|
+
syncTimeout = setTimeout(() => doSync(sessionId, messages), 5000);
|
|
8
|
+
}
|
|
9
|
+
async function doSync(sessionId, messages) {
|
|
10
|
+
const config = configManager.getOrquestaConfig();
|
|
11
|
+
if (!config?.token)
|
|
12
|
+
return;
|
|
13
|
+
try {
|
|
14
|
+
await fetch(`${ORQUESTA_API}/api/sessions/sync`, {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: {
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
'Authorization': `Bearer ${config.token}`,
|
|
19
|
+
},
|
|
20
|
+
body: JSON.stringify({
|
|
21
|
+
sessionId,
|
|
22
|
+
messages: messages.map(m => ({ role: m.role, content: m.content?.slice(0, 10000) })),
|
|
23
|
+
cwd: process.cwd(),
|
|
24
|
+
model: configManager.getCurrentModel()?.id,
|
|
25
|
+
updatedAt: new Date().toISOString(),
|
|
26
|
+
}),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch { }
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=session-sync.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { spawnSubAgent } from '../../../core/sub-agent.js';
|
|
2
|
+
const SUB_AGENT_DEFINITION = {
|
|
3
|
+
type: 'function',
|
|
4
|
+
function: {
|
|
5
|
+
name: 'spawn_sub_agent',
|
|
6
|
+
description: 'Spawn a sub-agent to handle a specific task in parallel. Use for independent sub-tasks that can run concurrently.',
|
|
7
|
+
parameters: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
task: {
|
|
11
|
+
type: 'string',
|
|
12
|
+
description: 'The task description to pass to the sub-agent',
|
|
13
|
+
},
|
|
14
|
+
cwd: {
|
|
15
|
+
type: 'string',
|
|
16
|
+
description: 'Working directory for the sub-agent (optional, defaults to current directory)',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
required: ['task'],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
async function executeSpawnSubAgent(args) {
|
|
24
|
+
const task = args['task'];
|
|
25
|
+
const cwd = args['cwd'];
|
|
26
|
+
const result = await spawnSubAgent(task, { cwd });
|
|
27
|
+
if (result.success) {
|
|
28
|
+
return { success: true, result: result.output };
|
|
29
|
+
}
|
|
30
|
+
return { success: false, result: result.output, error: result.error };
|
|
31
|
+
}
|
|
32
|
+
export const SpawnSubAgentTool = {
|
|
33
|
+
definition: SUB_AGENT_DEFINITION,
|
|
34
|
+
execute: executeSpawnSubAgent,
|
|
35
|
+
categories: ['llm-simple'],
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=sub-agent-tool.js.map
|
package/dist/tools/registry.js
CHANGED
|
@@ -8,6 +8,7 @@ import { TODO_TOOLS } from './llm/simple/todo-tools.js';
|
|
|
8
8
|
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
|
+
import { SpawnSubAgentTool } from './llm/simple/sub-agent-tool.js';
|
|
11
12
|
import { getShellTools } from './llm/simple/index.js';
|
|
12
13
|
let _browserTools = null;
|
|
13
14
|
let _startBrowserServer = null;
|
|
@@ -316,6 +317,7 @@ export function initializeToolRegistry() {
|
|
|
316
317
|
toolRegistry.registerAll(TODO_TOOLS);
|
|
317
318
|
toolRegistry.register(FinalResponseTool);
|
|
318
319
|
toolRegistry.register(RemotePhoneTool);
|
|
320
|
+
toolRegistry.register(SpawnSubAgentTool);
|
|
319
321
|
toolRegistry.registerAll(PLANNING_TOOLS);
|
|
320
322
|
}
|
|
321
323
|
export async function initializeOptionalTools() {
|
|
@@ -290,14 +290,23 @@ export const PlanExecuteApp = ({ llmClient: initialLlmClient, modelInfo, resumeL
|
|
|
290
290
|
};
|
|
291
291
|
}, [addLog]);
|
|
292
292
|
useEffect(() => {
|
|
293
|
-
if (llmClient)
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
293
|
+
if (!llmClient)
|
|
294
|
+
return;
|
|
295
|
+
let buffer = '';
|
|
296
|
+
let rafId = null;
|
|
297
|
+
llmClient.onStreamingContent = (token) => {
|
|
298
|
+
buffer += token;
|
|
299
|
+
if (!rafId) {
|
|
300
|
+
rafId = setTimeout(() => {
|
|
301
|
+
setStreamingText(buffer);
|
|
302
|
+
rafId = null;
|
|
303
|
+
}, 80);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
298
306
|
return () => {
|
|
299
|
-
if (
|
|
300
|
-
|
|
307
|
+
if (rafId)
|
|
308
|
+
clearTimeout(rafId);
|
|
309
|
+
llmClient.onStreamingContent = null;
|
|
301
310
|
};
|
|
302
311
|
}, [llmClient]);
|
|
303
312
|
useEffect(() => {
|
|
@@ -1321,8 +1330,25 @@ export const PlanExecuteApp = ({ llmClient: initialLlmClient, modelInfo, resumeL
|
|
|
1321
1330
|
React.createElement(Text, { color: "yellow" }, "\uD83D\uDCAD "),
|
|
1322
1331
|
React.createElement(Text, { color: "white", dimColor: true }, truncatedReason)))));
|
|
1323
1332
|
}
|
|
1324
|
-
case 'tool_result':
|
|
1325
|
-
|
|
1333
|
+
case 'tool_result': {
|
|
1334
|
+
if (!entry.diff || entry.diff.length === 0)
|
|
1335
|
+
return null;
|
|
1336
|
+
let filePath = '';
|
|
1337
|
+
try {
|
|
1338
|
+
filePath = JSON.parse(entry.details || '{}').file || '';
|
|
1339
|
+
}
|
|
1340
|
+
catch { }
|
|
1341
|
+
const diffLines = entry.diff.length > 10 ? [...entry.diff.slice(0, 10), `... +${entry.diff.length - 10} more lines`] : entry.diff;
|
|
1342
|
+
const header = filePath ? `┌─ ${filePath} ─` : '┌─';
|
|
1343
|
+
return (React.createElement(Box, { key: entry.id, flexDirection: "column", marginLeft: 2 },
|
|
1344
|
+
React.createElement(Text, { color: "gray" },
|
|
1345
|
+
header,
|
|
1346
|
+
'─'.repeat(Math.max(0, 32 - header.length))),
|
|
1347
|
+
diffLines.map((line, i) => (React.createElement(Text, { key: i, color: line.startsWith('+') ? 'green' : line.startsWith('-') ? 'red' : 'gray' },
|
|
1348
|
+
'│ ',
|
|
1349
|
+
line))),
|
|
1350
|
+
React.createElement(Text, { color: "gray" }, '└' + '─'.repeat(32))));
|
|
1351
|
+
}
|
|
1326
1352
|
case 'shell_result': {
|
|
1327
1353
|
const SHELL_MAX_LINES = 8;
|
|
1328
1354
|
const allLines = (entry.content || '').split('\n');
|
|
@@ -1423,7 +1449,7 @@ export const PlanExecuteApp = ({ llmClient: initialLlmClient, modelInfo, resumeL
|
|
|
1423
1449
|
isProcessing && (planExecutionState.executionPhase === 'planning' || planExecutionState.todos.length === 0) && !pendingToolApproval && !isDocsSearching && (React.createElement(Box, { marginY: 1, flexDirection: "column" },
|
|
1424
1450
|
React.createElement(ActivityIndicator, { activity: getCurrentActivityType(), startTime: activityStartTime, detail: activityDetail, subActivities: subActivities, modelName: currentModelInfo.model }),
|
|
1425
1451
|
streamingText && (React.createElement(Box, { marginTop: 1, paddingX: 1 },
|
|
1426
|
-
React.createElement(Text, { wrap: "wrap" }, streamingText))))),
|
|
1452
|
+
React.createElement(Text, { wrap: "wrap" }, streamingText.split('\n').slice(-15).join('\n')))))),
|
|
1427
1453
|
isDocsSearching && (React.createElement(DocsSearchProgress, { logs: docsSearchLogs, isSearching: isDocsSearching })),
|
|
1428
1454
|
planExecutionState.todos.length > 0 && (React.createElement(Box, { marginTop: 2, marginBottom: 1 },
|
|
1429
1455
|
React.createElement(TodoPanel, { todos: planExecutionState.todos, currentTodoId: planExecutionState.currentTodoId, isProcessing: isProcessing }))),
|