apex-dev 3.0.2 → 3.1.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.
- package/dist/highlights-eq9cgrbb.scm +604 -0
- package/dist/highlights-ghv9g403.scm +205 -0
- package/dist/highlights-hk7bwhj4.scm +284 -0
- package/dist/highlights-r812a2qc.scm +150 -0
- package/dist/highlights-x6tmsnaa.scm +115 -0
- package/dist/index.js +62590 -0
- package/dist/injections-73j83es3.scm +27 -0
- package/dist/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
- package/dist/tree-sitter-markdown-411r6y9b.wasm +0 -0
- package/dist/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
- package/dist/tree-sitter-typescript-zxjzwt75.wasm +0 -0
- package/dist/tree-sitter-zig-e78zbjpm.wasm +0 -0
- package/package.json +10 -4
- package/.config/amp/settings.json +0 -3
- package/.config/opencode/oh-my-opencode.json +0 -58
- package/.config/opencode/opencode.json +0 -6
- package/.local/share/amp/device-id.json +0 -3
- package/.local/share/amp/history.jsonl +0 -78
- package/.local/share/amp/session.json +0 -6
- package/.local/share/amp/threads/T-019c93b8-fce7-7083-aab9-d5f1c88a9545.json +0 -2528
- package/.local/share/amp/threads/T-019c93c8-4b7a-71df-94ac-867d8236a288.json +0 -7
- package/.local/share/amp/threads/T-019c93cd-5a7d-728e-8289-02e0ef4ca2ff.json +0 -680
- package/.local/share/amp/threads/T-019c93e7-83ca-7633-9eed-12bdcc118163.json +0 -873
- package/.local/share/amp/threads/T-019c93ea-ccd3-765a-88c9-42d7b631e977.json +0 -620
- package/.local/share/amp/threads/T-019c93ee-5977-71af-9ab7-c4611004b703.json +0 -1000
- package/.local/share/amp/threads/T-019c93f0-8328-71ed-a250-6da169cebfe1.json +0 -829
- package/.local/share/amp/threads/T-019c93f5-7bdd-703b-b2cd-0a04da64441a.json +0 -459
- package/.local/share/amp/threads/T-019c93f8-2b2e-733b-8249-9876546d9b5b.json +0 -764
- package/.local/share/amp/threads/T-019c93fd-fade-7195-a3b7-358f180d40b8.json +0 -7
- package/.local/share/amp/threads/T-019c93fe-2e56-705e-827e-eb99bd02e257.json +0 -3593
- package/.local/share/amp/threads/T-019c9408-6e64-77e1-9519-b913e3b24a03.json +0 -1559
- package/.local/share/amp/threads/T-019c9409-feeb-736d-b92c-4f7a263a643c.json +0 -7
- package/.local/share/amp/threads/T-019c940b-8d11-755b-b9e1-f923d8a5e6ba.json +0 -7
- package/.local/share/amp/threads/T-019c943a-6c5e-76a5-bf4e-170f7ad452ce.json +0 -979
- package/.local/share/amp/threads/T-019c94b2-1c8f-76d8-96d0-82449a028849.json +0 -1584
- package/.local/share/amp/threads/T-019c94b6-68f0-726e-92dd-90c5411ca28c.json +0 -7
- package/.local/share/amp/threads/T-019c94bf-a589-72a3-b3c2-a81359d9e0a6.json +0 -7
- package/.local/share/amp/threads/T-019c94e1-1bd9-70ab-b6f2-abd5cab4f4ce.json +0 -1035
- package/.local/share/amp/threads/T-019c94fd-cc4a-714b-896a-74f94020f6eb.json +0 -1310
- package/.local/share/amp/threads/T-019c9501-8976-7138-aca6-245a01a8fe9b.json +0 -7
- package/.local/share/amp/threads/T-019c9504-4b51-763e-8a9f-5d4cdfcf0cfa.json +0 -496
- package/.local/share/amp/threads/T-019c9506-4e3b-74fd-8eda-cedbf3793598.json +0 -2679
- package/.local/share/amp/threads/T-019c9508-178c-718c-88d2-caf816d64f65.json +0 -965
- package/.local/share/amp/threads/T-019c9509-2812-71fd-8fd2-923e29ad34fa.json +0 -7
- package/.local/share/amp/threads/T-019c950e-69fe-77d6-9854-fc73b77a3148.json +0 -4570
- package/.local/share/amp/threads/T-019c9707-6e2b-741c-b4d4-117026a78449.json +0 -2899
- package/.local/share/amp/threads/T-019c971b-6bc0-77b8-8868-f8956d3e71a8.json +0 -7
- package/.local/share/amp/threads/T-019c971b-c87c-75f3-a61f-beb18a1cb25f.json +0 -474
- package/.local/share/amp/threads/T-019c971d-d371-70ac-9805-5c739908e73b.json +0 -802
- package/.local/share/amp/threads/T-019c9722-d73d-74f1-9d1d-8fafaad0ede7.json +0 -7
- package/.local/share/amp/threads/T-019c9761-858c-719b-911f-bc2e4c8cbdde.json +0 -188
- package/.local/share/amp/threads/T-019c9761-f5f3-7606-a900-ebe7f10d6e37.json +0 -121
- package/.local/share/amp/threads/T-019c9763-b1ae-729d-90aa-f59938ce912e.json +0 -799
- package/.local/share/amp/threads/T-019c9769-4a8a-77b8-beab-f48973276f9a.json +0 -1541
- package/.local/share/amp/threads/T-019c9772-edac-7075-b26e-0ada1f8697d2.json +0 -7
- package/.local/share/amp/threads/T-019c97e8-a9ab-71a1-a8f9-109c540c98bf.json +0 -111
- package/.local/share/amp/threads/T-019c97e9-2277-753c-8c5d-df745fa6cfff.json +0 -7
- package/.local/share/amp/threads/T-019c97e9-f28e-758d-9663-e37047a8ed95.json +0 -111
- package/.local/share/amp/threads/T-019c97ea-17c7-77b8-92b2-f641c069bcc9.json +0 -71
- package/.local/share/amp/threads/T-019c97ea-44c6-75b8-88bc-d88113194f6a.json +0 -1611
- package/.local/share/amp/threads/T-019c97ec-abae-7251-a5f6-693adf496a1c.json +0 -7
- package/.local/share/amp/threads/T-019c97f5-8e61-73ad-8c5d-2637abedcde6.json +0 -1341
- package/.local/share/amp/threads/T-019c989d-4f4e-7249-bde0-21d19455ccae.json +0 -163
- package/.local/share/amp/threads/T-019c989d-9024-73c4-bee8-e2ae45028a39.json +0 -124
- package/.local/share/amp/threads/T-019c989e-1394-74ad-8234-ac573fcdb4c7.json +0 -1260
- package/.local/share/amp/threads/T-019c989f-e3dd-772e-85ac-525d0fc88fda.json +0 -403
- package/.local/share/amp/threads/T-019c98a1-7b0c-778a-b311-2e1cff85d710.json +0 -3422
- package/.local/share/amp/threads/T-019c98c5-4b7f-7284-99e9-88aa8c18ba66.json +0 -1830
- package/.local/share/amp/threads/T-019c98d0-f27f-76ec-be10-6df96f22be99.json +0 -4061
- package/.local/share/amp/threads/T-019c98f9-d031-704d-a0c2-f2f395f68f2b.json +0 -509
- package/.local/share/amp/threads/T-019c9919-f9ee-766c-90be-af7a07f6a4c6.json +0 -2075
- package/.local/share/amp/threads/T-019c991c-b98b-7158-9083-cc52408beb13.json +0 -7
- package/.local/share/amp/threads/T-019c991d-66d6-72aa-a9a1-105f7df0ea06.json +0 -7
- package/.local/share/amp/threads/T-019c9c2e-71a4-77ff-bd7f-b053da7f9000.json +0 -1637
- package/.local/share/amp/threads/T-019c9c45-27ca-728b-ba77-835115dfa9b2.json +0 -3893
- package/.local/share/amp/threads/T-019c9c48-45dc-736a-9752-e4119fe698f9.json +0 -7
- package/.local/share/amp/threads/T-019c9c4d-266b-72d0-b56e-74a5777e6583.json +0 -7
- package/.local/share/amp/threads/T-019c9c52-ab89-758f-9178-bda99c39d10b.json +0 -7
- package/.local/share/amp/threads/T-019c9c56-5715-72e2-b8b4-87711a842dd1.json +0 -1799
- package/.local/share/amp/threads/T-019c9c5b-88b1-74cb-97e9-16b23e03daa2.json +0 -727
- package/.local/share/amp/threads/T-019c9c5c-3b3e-721c-ad2e-a2ef245dce3f.json +0 -738
- package/.local/share/amp/threads/T-019c9c5c-fd78-736f-9d29-a66d23839d40.json +0 -256
- package/.local/share/amp/threads/T-019c9c5d-db4a-74cd-ad2a-925fac87131d.json +0 -1859
- package/.local/share/kilo/kilo.db +0 -0
- package/.local/share/kilo/kilo.db-shm +0 -0
- package/.local/share/kilo/kilo.db-wal +0 -0
- package/.local/share/kilo/storage/migration +0 -1
- package/.local/share/kilo/storage/session_diff/ses_36bea4cb9ffe1b0j5HEL14KEaU.json +0 -1
- package/.local/share/kilo/storage/session_diff/ses_36beaa8f2ffeeZ3Y39SQ9UDWQQ.json +0 -1
- package/.local/share/kilo/telemetry-id +0 -1
- package/.local/share/opencode/auth.json +0 -6
- package/.local/share/opencode/opencode.db +0 -0
- package/.local/share/opencode/opencode.db-shm +0 -0
- package/.local/share/opencode/opencode.db-wal +0 -0
- package/.local/share/opencode/storage/agent-usage-reminder/ses_36870ea98ffe8S5ZOCE4F11yFh.json +0 -6
- package/.local/share/opencode/storage/agent-usage-reminder/ses_3687a3e9affewUnHBzvpiPR6df.json +0 -6
- package/.local/share/opencode/storage/agent-usage-reminder/ses_36886e68dffeKVgUWf6lzXdEEt.json +0 -6
- package/.local/share/opencode/storage/agent-usage-reminder/ses_36bee9f1effeJbiHHLWLR6O3WJ.json +0 -6
- package/.local/share/opencode/storage/agent-usage-reminder/ses_36c25e50affef2nhaXq9aSgKH3.json +0 -6
- package/.local/share/opencode/storage/agent-usage-reminder/ses_36c260708ffel4wG4yhdo0knDD.json +0 -6
- package/.local/share/opencode/storage/agent-usage-reminder/ses_36c261531ffeoVcvqXxry2bN9H.json +0 -6
- package/.local/share/opencode/storage/agent-usage-reminder/ses_36c291bddffePWRiaFLLJAC1y7.json +0 -6
- package/.local/share/opencode/storage/migration +0 -1
- package/.local/share/opencode/storage/session_diff/ses_36870ea98ffe8S5ZOCE4F11yFh.json +0 -1
- package/.local/share/opencode/storage/session_diff/ses_3687a3e9affewUnHBzvpiPR6df.json +0 -1
- package/.local/share/opencode/storage/session_diff/ses_36886e68dffeKVgUWf6lzXdEEt.json +0 -1
- package/.local/share/opencode/storage/session_diff/ses_36bee9f1effeJbiHHLWLR6O3WJ.json +0 -1
- package/.local/share/opencode/storage/session_diff/ses_36c25e50affef2nhaXq9aSgKH3.json +0 -1
- package/.local/share/opencode/storage/session_diff/ses_36c260708ffel4wG4yhdo0knDD.json +0 -1
- package/.local/share/opencode/storage/session_diff/ses_36c261531ffeoVcvqXxry2bN9H.json +0 -1
- package/.local/share/opencode/storage/session_diff/ses_36c291bddffePWRiaFLLJAC1y7.json +0 -1
- package/.local/share/opencode/storage/session_diff/ses_36c2af1c5ffegxEaOZOGcVykyy.json +0 -1
- package/.local/share/opencode/storage/session_diff/ses_36c2be235ffeOa6x8UCk1HW4kU.json +0 -1
- package/.local/share/opencode/tool-output/tool_c93da840c0016GrdyAkOnHGezU +0 -2330
- package/.local/share/opencode/tool-output/tool_c9411e784001cRoQqwVDb1a6lY +0 -1017
- package/.local/state/replit/log-query.db +0 -0
- package/.local/state/replit/log-query.db-shm +0 -0
- package/.local/state/replit/log-query.db-wal +0 -0
- package/.replit +0 -41
- package/.upm/store.json +0 -1
- package/AGENTS.md +0 -28
- package/bun.lock +0 -271
- package/generated-icon.png +0 -0
- package/hello.txt +0 -1
- package/index.jsx +0 -24
- package/src/agent.js +0 -504
- package/src/app.jsx +0 -96
- package/src/commands.js +0 -133
- package/src/components/AssistantMessage.jsx +0 -83
- package/src/components/ChatArea.jsx +0 -84
- package/src/components/DiffView.jsx +0 -26
- package/src/components/Divider.jsx +0 -8
- package/src/components/Header.jsx +0 -44
- package/src/components/HelpModal.jsx +0 -81
- package/src/components/InputBar.jsx +0 -32
- package/src/components/Spinner.jsx +0 -23
- package/src/components/StatusBar.jsx +0 -44
- package/src/components/SystemMessage.jsx +0 -31
- package/src/components/ThinkBlock.jsx +0 -29
- package/src/components/ToolCallItem.jsx +0 -43
- package/src/components/UserMessage.jsx +0 -11
- package/src/components/Welcome.jsx +0 -14
- package/src/config.js +0 -196
- package/src/hooks/useLayout.js +0 -15
- package/src/hooks/useStore.js +0 -6
- package/src/prompt.js +0 -101
- package/src/store.js +0 -99
- package/src/theme.js +0 -19
- package/src/thinking.js +0 -54
- package/src/toolExecutors.js +0 -853
- package/src/tools.js +0 -335
- package/src/utils.js +0 -32
- package/tsconfig.json +0 -10
package/src/toolExecutors.js
DELETED
|
@@ -1,853 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const https = require('https');
|
|
6
|
-
const { execSync } = require('child_process');
|
|
7
|
-
const {
|
|
8
|
-
PROJECT_ROOT,
|
|
9
|
-
TOOL_TIMEOUT,
|
|
10
|
-
REVIEWER_MODEL,
|
|
11
|
-
REVIEWER_SYSTEM_PROMPT,
|
|
12
|
-
FILE_PICKER_MODEL,
|
|
13
|
-
FILE_PICKER_SYSTEM_PROMPT,
|
|
14
|
-
THINKER_MODEL,
|
|
15
|
-
THINKER_SYSTEM_PROMPT,
|
|
16
|
-
COMMANDER_MODEL,
|
|
17
|
-
COMMANDER_SYSTEM_PROMPT,
|
|
18
|
-
CONTEXT_PRUNER_MODEL,
|
|
19
|
-
CONTEXT_PRUNER_SYSTEM_PROMPT,
|
|
20
|
-
SELECTOR_SYSTEM_PROMPT,
|
|
21
|
-
NVIDIA_MODEL,
|
|
22
|
-
nvidiaClient,
|
|
23
|
-
session,
|
|
24
|
-
truncateOutput,
|
|
25
|
-
resolvePath,
|
|
26
|
-
sleep,
|
|
27
|
-
getMode,
|
|
28
|
-
} = require('./config');
|
|
29
|
-
|
|
30
|
-
async function streamCompletion(params, onStream) {
|
|
31
|
-
for (let attempt = 0; attempt <= 2; attempt++) {
|
|
32
|
-
let content = '';
|
|
33
|
-
let reasoning = '';
|
|
34
|
-
try {
|
|
35
|
-
if (onStream) {
|
|
36
|
-
const stream = await nvidiaClient.chat.completions.create({ ...params, stream: true });
|
|
37
|
-
for await (const chunk of stream) {
|
|
38
|
-
const delta = chunk.choices?.[0]?.delta;
|
|
39
|
-
if (delta?.content) {
|
|
40
|
-
content += delta.content;
|
|
41
|
-
onStream(content || reasoning);
|
|
42
|
-
}
|
|
43
|
-
if (delta?.reasoning_content) {
|
|
44
|
-
reasoning += delta.reasoning_content;
|
|
45
|
-
onStream(content || reasoning);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return content || reasoning || '';
|
|
49
|
-
} else {
|
|
50
|
-
const response = await nvidiaClient.chat.completions.create(params);
|
|
51
|
-
return response.choices[0]?.message?.content
|
|
52
|
-
|| response.choices[0]?.message?.reasoning_content
|
|
53
|
-
|| '';
|
|
54
|
-
}
|
|
55
|
-
} catch (err) {
|
|
56
|
-
if (attempt < 2 && err.status >= 400 && err.status < 500) {
|
|
57
|
-
await sleep(1000 * Math.pow(2, attempt));
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
throw err;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async function executeTool(name, args, onStream) {
|
|
66
|
-
try {
|
|
67
|
-
switch (name) {
|
|
68
|
-
case 'Read': {
|
|
69
|
-
const filePath = resolvePath(args.path);
|
|
70
|
-
const stat = fs.statSync(filePath, { throwIfNoEntry: false });
|
|
71
|
-
if (!stat) return `Error: File not found: ${filePath}`;
|
|
72
|
-
if (stat.isDirectory()) return `Error: ${filePath} is a directory. Use ListDir instead.`;
|
|
73
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
74
|
-
const lines = content.split('\n');
|
|
75
|
-
const start = Math.max(0, (args.start_line || 1) - 1);
|
|
76
|
-
const end = args.end_line ? Math.min(lines.length, args.end_line) : Math.min(lines.length, start + 500);
|
|
77
|
-
const slice = lines.slice(start, end);
|
|
78
|
-
const numbered = slice.map((l, i) => `${start + i + 1}: ${l}`).join('\n');
|
|
79
|
-
session.filesRead.add(filePath);
|
|
80
|
-
if (end < lines.length) {
|
|
81
|
-
return truncateOutput(numbered) + `\n(showing lines ${start + 1}-${end} of ${lines.length})`;
|
|
82
|
-
}
|
|
83
|
-
return truncateOutput(numbered);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
case 'Write': {
|
|
87
|
-
const filePath = resolvePath(args.path);
|
|
88
|
-
const dir = path.dirname(filePath);
|
|
89
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
90
|
-
const existed = fs.existsSync(filePath);
|
|
91
|
-
const before = existed ? fs.readFileSync(filePath, 'utf-8') : null;
|
|
92
|
-
fs.writeFileSync(filePath, args.content, 'utf-8');
|
|
93
|
-
if (before !== null) {
|
|
94
|
-
session.editHistory.push({ path: filePath, before, after: args.content, timestamp: Date.now() });
|
|
95
|
-
}
|
|
96
|
-
session.filesModified.add(filePath);
|
|
97
|
-
const lines = args.content.split('\n').length;
|
|
98
|
-
return `${existed ? 'Overwritten' : 'Created'}: ${filePath} (${lines} lines)`;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
case 'Edit': {
|
|
102
|
-
const filePath = resolvePath(args.path);
|
|
103
|
-
if (!fs.existsSync(filePath)) return `Error: File not found: ${filePath}`;
|
|
104
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
105
|
-
const count = content.split(args.old_str).length - 1;
|
|
106
|
-
if (count === 0) return `Error: old_str not found in ${path.basename(filePath)}. Make sure it matches exactly (including whitespace and indentation).`;
|
|
107
|
-
if (count > 1) return `Error: old_str found ${count} times in ${path.basename(filePath)}. It must be unique. Add more surrounding context to make it unique.`;
|
|
108
|
-
const updated = content.replace(args.old_str, args.new_str);
|
|
109
|
-
fs.writeFileSync(filePath, updated, 'utf-8');
|
|
110
|
-
session.editHistory.push({ path: filePath, before: content, after: updated, timestamp: Date.now() });
|
|
111
|
-
session.filesModified.add(filePath);
|
|
112
|
-
// Generate a mini diff
|
|
113
|
-
const oldLines = args.old_str.split('\n');
|
|
114
|
-
const newLines = args.new_str.split('\n');
|
|
115
|
-
let diff = `Edited: ${filePath}\n`;
|
|
116
|
-
oldLines.forEach(l => diff += `- ${l}\n`);
|
|
117
|
-
newLines.forEach(l => diff += `+ ${l}\n`);
|
|
118
|
-
return diff;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
case 'Patch': {
|
|
122
|
-
const filePath = resolvePath(args.path);
|
|
123
|
-
if (!fs.existsSync(filePath)) return `Error: File not found: ${filePath}`;
|
|
124
|
-
let content = fs.readFileSync(filePath, 'utf-8');
|
|
125
|
-
const before = content;
|
|
126
|
-
const results = [];
|
|
127
|
-
for (let i = 0; i < args.edits.length; i++) {
|
|
128
|
-
const edit = args.edits[i];
|
|
129
|
-
if (!content.includes(edit.old_str)) {
|
|
130
|
-
results.push(`Edit ${i + 1}: FAILED - old_str not found`);
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
content = content.replace(edit.old_str, edit.new_str);
|
|
134
|
-
results.push(`Edit ${i + 1}: OK`);
|
|
135
|
-
}
|
|
136
|
-
fs.writeFileSync(filePath, content, 'utf-8');
|
|
137
|
-
session.editHistory.push({ path: filePath, before, after: content, timestamp: Date.now() });
|
|
138
|
-
session.filesModified.add(filePath);
|
|
139
|
-
return `Patched: ${filePath}\n${results.join('\n')}`;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
case 'Bash': {
|
|
143
|
-
const cwd = args.cwd ? resolvePath(args.cwd) : PROJECT_ROOT;
|
|
144
|
-
session.commandsRun.push(args.command);
|
|
145
|
-
try {
|
|
146
|
-
const output = execSync(args.command, {
|
|
147
|
-
encoding: 'utf-8',
|
|
148
|
-
timeout: TOOL_TIMEOUT,
|
|
149
|
-
cwd,
|
|
150
|
-
maxBuffer: 1024 * 1024 * 5,
|
|
151
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
152
|
-
});
|
|
153
|
-
return truncateOutput(output || '(no output)');
|
|
154
|
-
} catch (err) {
|
|
155
|
-
// Return both stdout and stderr on failure
|
|
156
|
-
const stdout = err.stdout || '';
|
|
157
|
-
const stderr = err.stderr || '';
|
|
158
|
-
const exitCode = err.status || 1;
|
|
159
|
-
return truncateOutput(`Exit code: ${exitCode}\n${stdout}\n${stderr}`.trim());
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
case 'Grep': {
|
|
164
|
-
const searchPath = resolvePath(args.path);
|
|
165
|
-
const flags = args.case_sensitive ? '' : '-i';
|
|
166
|
-
const include = args.include ? `--include='${args.include}'` : '';
|
|
167
|
-
try {
|
|
168
|
-
const cmd = `grep -rn ${flags} ${include} --color=never "${args.pattern.replace(/"/g, '\\"')}" "${searchPath}" 2>/dev/null | head -80`;
|
|
169
|
-
const output = execSync(cmd, { encoding: 'utf-8', timeout: 15000 });
|
|
170
|
-
return truncateOutput(output || 'No matches found.');
|
|
171
|
-
} catch {
|
|
172
|
-
return 'No matches found.';
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
case 'Glob': {
|
|
177
|
-
const cwd = args.cwd ? resolvePath(args.cwd) : PROJECT_ROOT;
|
|
178
|
-
try {
|
|
179
|
-
// Use find command to simulate glob
|
|
180
|
-
const pattern = args.pattern;
|
|
181
|
-
let cmd;
|
|
182
|
-
if (pattern.includes('**')) {
|
|
183
|
-
const namePattern = pattern.replace(/\*\*\//g, '').replace(/\*/g, '*');
|
|
184
|
-
cmd = `find "${cwd}" -name "${namePattern}" -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null | head -100`;
|
|
185
|
-
} else {
|
|
186
|
-
cmd = `find "${cwd}" -name "${pattern}" -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null | head -100`;
|
|
187
|
-
}
|
|
188
|
-
const output = execSync(cmd, { encoding: 'utf-8', timeout: 10000 });
|
|
189
|
-
if (!output.trim()) return 'No files found matching pattern.';
|
|
190
|
-
// Make paths relative
|
|
191
|
-
const files = output.trim().split('\n').map(f => path.relative(cwd, f)).sort();
|
|
192
|
-
return files.join('\n');
|
|
193
|
-
} catch {
|
|
194
|
-
return 'No files found matching pattern.';
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
case 'ListDir': {
|
|
199
|
-
const dirPath = resolvePath(args.path);
|
|
200
|
-
if (!fs.existsSync(dirPath)) return `Error: Directory not found: ${dirPath}`;
|
|
201
|
-
const stat = fs.statSync(dirPath);
|
|
202
|
-
if (!stat.isDirectory()) return `Error: ${dirPath} is not a directory.`;
|
|
203
|
-
|
|
204
|
-
function listRecursive(dir, depth, maxDepth) {
|
|
205
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
206
|
-
.filter(e => !e.name.startsWith('.') && e.name !== 'node_modules')
|
|
207
|
-
.sort((a, b) => {
|
|
208
|
-
if (a.isDirectory() && !b.isDirectory()) return -1;
|
|
209
|
-
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
210
|
-
return a.name.localeCompare(b.name);
|
|
211
|
-
});
|
|
212
|
-
const lines = [];
|
|
213
|
-
for (const entry of entries) {
|
|
214
|
-
const prefix = ' '.repeat(depth);
|
|
215
|
-
if (entry.isDirectory()) {
|
|
216
|
-
lines.push(`${prefix}${entry.name}/`);
|
|
217
|
-
if (depth < maxDepth) {
|
|
218
|
-
lines.push(...listRecursive(path.join(dir, entry.name), depth + 1, maxDepth));
|
|
219
|
-
}
|
|
220
|
-
} else {
|
|
221
|
-
const size = fs.statSync(path.join(dir, entry.name)).size;
|
|
222
|
-
const sizeStr = size < 1024 ? `${size}B` : size < 1024 * 1024 ? `${(size / 1024).toFixed(1)}K` : `${(size / (1024 * 1024)).toFixed(1)}M`;
|
|
223
|
-
lines.push(`${prefix}${entry.name} (${sizeStr})`);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
return lines;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const maxDepth = args.recursive ? 3 : 0;
|
|
230
|
-
const lines = listRecursive(dirPath, 0, maxDepth);
|
|
231
|
-
return truncateOutput(lines.join('\n') || '(empty directory)');
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
case 'UndoEdit': {
|
|
235
|
-
const filePath = resolvePath(args.path);
|
|
236
|
-
const lastEdit = [...session.editHistory].reverse().find(e => e.path === filePath);
|
|
237
|
-
if (!lastEdit) return `Error: No edit history for ${filePath}`;
|
|
238
|
-
fs.writeFileSync(filePath, lastEdit.before, 'utf-8');
|
|
239
|
-
session.editHistory = session.editHistory.filter(e => e !== lastEdit);
|
|
240
|
-
return `Undone last edit to ${filePath}`;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
case 'Task': {
|
|
244
|
-
const results = [];
|
|
245
|
-
for (const cmd of args.commands) {
|
|
246
|
-
try {
|
|
247
|
-
const output = execSync(cmd, {
|
|
248
|
-
encoding: 'utf-8',
|
|
249
|
-
timeout: TOOL_TIMEOUT,
|
|
250
|
-
cwd: PROJECT_ROOT,
|
|
251
|
-
maxBuffer: 1024 * 1024 * 5,
|
|
252
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
253
|
-
});
|
|
254
|
-
results.push(`✓ ${cmd}\n${output.trim()}`);
|
|
255
|
-
session.commandsRun.push(cmd);
|
|
256
|
-
} catch (err) {
|
|
257
|
-
results.push(`✗ ${cmd}\nExit code: ${err.status}\n${(err.stdout || '').trim()}\n${(err.stderr || '').trim()}`);
|
|
258
|
-
session.commandsRun.push(cmd);
|
|
259
|
-
break; // Stop on first failure
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
return truncateOutput(`Task: ${args.description}\n${'─'.repeat(40)}\n${results.join('\n\n')}`);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
case 'WebSearch': {
|
|
266
|
-
const apiKey = process.env.EXA_API_KEY;
|
|
267
|
-
if (!apiKey) return 'Error: EXA_API_KEY environment variable is not set. Get one at https://dashboard.exa.ai/api-keys';
|
|
268
|
-
|
|
269
|
-
const body = JSON.stringify({
|
|
270
|
-
query: args.query,
|
|
271
|
-
numResults: Math.min(args.num_results || 5, 10),
|
|
272
|
-
type: args.type || 'auto',
|
|
273
|
-
...(args.include_domains && { includeDomains: args.include_domains }),
|
|
274
|
-
...(args.category && { category: args.category }),
|
|
275
|
-
contents: { highlights: { maxCharacters: 300 }, text: { maxCharacters: 1000 } },
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
const result = await new Promise((resolve) => {
|
|
279
|
-
const req = https.request({
|
|
280
|
-
hostname: 'api.exa.ai',
|
|
281
|
-
path: '/search',
|
|
282
|
-
method: 'POST',
|
|
283
|
-
headers: {
|
|
284
|
-
'Content-Type': 'application/json',
|
|
285
|
-
'x-api-key': apiKey,
|
|
286
|
-
},
|
|
287
|
-
}, (res) => {
|
|
288
|
-
let data = '';
|
|
289
|
-
res.on('data', (chunk) => data += chunk);
|
|
290
|
-
res.on('end', () => {
|
|
291
|
-
if (res.statusCode !== 200) {
|
|
292
|
-
resolve(`Error: Exa API returned ${res.statusCode}: ${data}`);
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
try {
|
|
296
|
-
const json = JSON.parse(data);
|
|
297
|
-
if (!json.results || json.results.length === 0) {
|
|
298
|
-
resolve('No results found.');
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
const formatted = json.results.map((r, i) => {
|
|
302
|
-
let entry = `${i + 1}. **${r.title || 'Untitled'}**\n ${r.url}`;
|
|
303
|
-
if (r.publishedDate) entry += `\n Published: ${r.publishedDate.split('T')[0]}`;
|
|
304
|
-
if (r.author) entry += `\n Author: ${r.author}`;
|
|
305
|
-
if (r.text) entry += `\n ${r.text.trim().slice(0, 500)}`;
|
|
306
|
-
else if (r.highlights && r.highlights.length) entry += `\n ${r.highlights[0].trim().slice(0, 300)}`;
|
|
307
|
-
return entry;
|
|
308
|
-
}).join('\n\n');
|
|
309
|
-
resolve(truncateOutput(`Web Search Results (${json.results.length}):\n${'─'.repeat(40)}\n${formatted}`));
|
|
310
|
-
} catch (e) {
|
|
311
|
-
resolve(`Error: Failed to parse Exa response: ${e.message}`);
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
});
|
|
315
|
-
req.on('error', (e) => resolve(`Error: Exa request failed: ${e.message}`));
|
|
316
|
-
req.setTimeout(15000, () => { req.destroy(); resolve('Error: Exa search timed out.'); });
|
|
317
|
-
req.write(body);
|
|
318
|
-
req.end();
|
|
319
|
-
});
|
|
320
|
-
return result;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
case 'FilePickerMax': {
|
|
324
|
-
// Gather full directory tree
|
|
325
|
-
let tree = '';
|
|
326
|
-
try {
|
|
327
|
-
tree = execSync(
|
|
328
|
-
`find "${PROJECT_ROOT}" -not -path "*/node_modules/*" -not -path "*/.git/*" -not -path "*/.cache/*" -not -path "*/.local/*" -not -path "*/.upm/*" -not -path "*/.config/*" 2>/dev/null | head -500`,
|
|
329
|
-
{ encoding: 'utf-8', timeout: 15000 }
|
|
330
|
-
).trim();
|
|
331
|
-
// Make paths relative
|
|
332
|
-
tree = tree.split('\n').map(f => path.relative(PROJECT_ROOT, f) || '.').join('\n');
|
|
333
|
-
} catch {
|
|
334
|
-
tree = '(failed to scan directory tree)';
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// Gather previews of all source files (first 8 lines each)
|
|
338
|
-
const sourceExts = /\.(js|ts|jsx|tsx|py|rb|go|rs|java|c|cpp|h|hpp|css|scss|html|svelte|vue|json|yaml|yml|toml|md|sql|sh|bash|env|cfg|ini|xml)$/i;
|
|
339
|
-
const allFiles = tree.split('\n').filter(f => sourceExts.test(f));
|
|
340
|
-
const previews = [];
|
|
341
|
-
for (const relFile of allFiles.slice(0, 200)) {
|
|
342
|
-
const absFile = path.resolve(PROJECT_ROOT, relFile);
|
|
343
|
-
try {
|
|
344
|
-
const stat = fs.statSync(absFile, { throwIfNoEntry: false });
|
|
345
|
-
if (!stat || stat.isDirectory() || stat.size > 512 * 1024) continue;
|
|
346
|
-
const content = fs.readFileSync(absFile, 'utf-8');
|
|
347
|
-
const first8 = content.split('\n').slice(0, 8).join('\n');
|
|
348
|
-
previews.push(`--- ${relFile} ---\n${first8}`);
|
|
349
|
-
} catch { /* skip unreadable */ }
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
const pickerMessages = [
|
|
353
|
-
{ role: 'system', content: FILE_PICKER_SYSTEM_PROMPT },
|
|
354
|
-
{
|
|
355
|
-
role: 'user',
|
|
356
|
-
content: `# Prompt\n${args.prompt}\n\n# Directory Tree\n${tree}\n\n# File Previews (first 8 lines each)\n${previews.join('\n\n')}`,
|
|
357
|
-
},
|
|
358
|
-
];
|
|
359
|
-
|
|
360
|
-
try {
|
|
361
|
-
const header = `FilePickerMax Results\n${'─'.repeat(40)}\n`;
|
|
362
|
-
const streamCb = onStream ? (text) => onStream(truncateOutput(header + text)) : null;
|
|
363
|
-
const raw = await streamCompletion({
|
|
364
|
-
model: FILE_PICKER_MODEL,
|
|
365
|
-
messages: pickerMessages,
|
|
366
|
-
max_tokens: 4096,
|
|
367
|
-
temperature: 0.2,
|
|
368
|
-
}, streamCb) || '[]';
|
|
369
|
-
return truncateOutput(header + raw);
|
|
370
|
-
} catch (apiErr) {
|
|
371
|
-
return `Error: FilePickerMax failed — ${apiErr.message}`;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
case 'TodoList': {
|
|
376
|
-
const todoFile = path.join(PROJECT_ROOT, '.apex-todos.json');
|
|
377
|
-
const loadTodos = () => {
|
|
378
|
-
try { return JSON.parse(fs.readFileSync(todoFile, 'utf-8')); }
|
|
379
|
-
catch { return []; }
|
|
380
|
-
};
|
|
381
|
-
const saveTodos = (todos) => fs.writeFileSync(todoFile, JSON.stringify(todos, null, 2), 'utf-8');
|
|
382
|
-
const formatTodos = (todos) => {
|
|
383
|
-
if (todos.length === 0) return 'Todo list is empty.';
|
|
384
|
-
return todos.map((t, i) =>
|
|
385
|
-
`${i + 1}. [${t.done ? 'x' : ' '}] ${t.text}${t.done ? ' ✓' : ''}`
|
|
386
|
-
).join('\n');
|
|
387
|
-
};
|
|
388
|
-
|
|
389
|
-
const todos = loadTodos();
|
|
390
|
-
switch (args.action) {
|
|
391
|
-
case 'add': {
|
|
392
|
-
if (!args.text) return 'Error: "text" is required for add action.';
|
|
393
|
-
todos.push({ text: args.text, done: false, created: Date.now() });
|
|
394
|
-
saveTodos(todos);
|
|
395
|
-
return `Added item ${todos.length}: ${args.text}\n\n${formatTodos(todos)}`;
|
|
396
|
-
}
|
|
397
|
-
case 'list':
|
|
398
|
-
return formatTodos(todos);
|
|
399
|
-
case 'done': {
|
|
400
|
-
const idx = (args.index || 0) - 1;
|
|
401
|
-
if (idx < 0 || idx >= todos.length) return `Error: Invalid index. Use 1-${todos.length}.`;
|
|
402
|
-
todos[idx].done = true;
|
|
403
|
-
saveTodos(todos);
|
|
404
|
-
return `Completed: ${todos[idx].text}\n\n${formatTodos(todos)}`;
|
|
405
|
-
}
|
|
406
|
-
case 'remove': {
|
|
407
|
-
const idx = (args.index || 0) - 1;
|
|
408
|
-
if (idx < 0 || idx >= todos.length) return `Error: Invalid index. Use 1-${todos.length}.`;
|
|
409
|
-
const removed = todos.splice(idx, 1)[0];
|
|
410
|
-
saveTodos(todos);
|
|
411
|
-
return `Removed: ${removed.text}\n\n${formatTodos(todos)}`;
|
|
412
|
-
}
|
|
413
|
-
case 'clear': {
|
|
414
|
-
const before = todos.length;
|
|
415
|
-
const remaining = todos.filter(t => !t.done);
|
|
416
|
-
saveTodos(remaining);
|
|
417
|
-
return `Cleared ${before - remaining.length} completed item(s).\n\n${formatTodos(remaining)}`;
|
|
418
|
-
}
|
|
419
|
-
default:
|
|
420
|
-
return `Error: Unknown action "${args.action}". Use add, list, done, remove, or clear.`;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
case 'CodeReview': {
|
|
425
|
-
// Auto-collect all modified files from this session
|
|
426
|
-
const allFiles = new Set([...session.filesModified]);
|
|
427
|
-
// Add any extra files the agent explicitly passed
|
|
428
|
-
if (args.files && args.files.length) {
|
|
429
|
-
for (const f of args.files) allFiles.add(resolvePath(f));
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
if (allFiles.size === 0) {
|
|
433
|
-
return 'CodeReview skipped — no files were modified this session.';
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// Gather file contents for the reviewer's context
|
|
437
|
-
const fileContents = [];
|
|
438
|
-
for (const filePath of allFiles) {
|
|
439
|
-
if (!fs.existsSync(filePath)) {
|
|
440
|
-
fileContents.push(`--- ${filePath} ---\n[File not found]`);
|
|
441
|
-
continue;
|
|
442
|
-
}
|
|
443
|
-
const stat = fs.statSync(filePath);
|
|
444
|
-
if (stat.isDirectory()) continue;
|
|
445
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
446
|
-
fileContents.push(`--- ${path.relative(PROJECT_ROOT, filePath) || filePath} ---\n${content}`);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// Build git diff of modified files for richer context
|
|
450
|
-
let gitDiff = '';
|
|
451
|
-
try {
|
|
452
|
-
gitDiff = execSync('git diff 2>/dev/null', { encoding: 'utf-8', cwd: PROJECT_ROOT, timeout: 10000 }).trim();
|
|
453
|
-
} catch {}
|
|
454
|
-
|
|
455
|
-
const reviewMessages = [
|
|
456
|
-
{
|
|
457
|
-
role: 'system',
|
|
458
|
-
content: REVIEWER_SYSTEM_PROMPT,
|
|
459
|
-
},
|
|
460
|
-
{
|
|
461
|
-
role: 'user',
|
|
462
|
-
content: `# What was changed\n${args.prompt}\n\n# Modified files (${allFiles.size})\n\n${fileContents.join('\n\n')}${gitDiff ? `\n\n# Git diff\n\`\`\`diff\n${gitDiff}\n\`\`\`` : ''}`,
|
|
463
|
-
},
|
|
464
|
-
];
|
|
465
|
-
|
|
466
|
-
try {
|
|
467
|
-
const header = `Code Review (${REVIEWER_MODEL}) — ${allFiles.size} file(s)\n${'─'.repeat(40)}\n`;
|
|
468
|
-
const streamCb = onStream ? (text) => onStream(truncateOutput(header + text)) : null;
|
|
469
|
-
const reviewText = await streamCompletion({
|
|
470
|
-
model: REVIEWER_MODEL,
|
|
471
|
-
messages: reviewMessages,
|
|
472
|
-
max_tokens: 4096,
|
|
473
|
-
temperature: 0.3,
|
|
474
|
-
}, streamCb) || '(No response from reviewer)';
|
|
475
|
-
return truncateOutput(header + reviewText);
|
|
476
|
-
} catch (apiErr) {
|
|
477
|
-
return `Error: Code review failed — ${apiErr.message}`;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// ===== Thinker — Deep reasoning/planning =====
|
|
482
|
-
case 'Thinker': {
|
|
483
|
-
const historyContext = session.conversationHistory.slice(-10).map(m =>
|
|
484
|
-
`[${m.role}]: ${(m.content || '').slice(0, 500)}`
|
|
485
|
-
).join('\n');
|
|
486
|
-
|
|
487
|
-
const thinkerMessages = [
|
|
488
|
-
{ role: 'system', content: THINKER_SYSTEM_PROMPT },
|
|
489
|
-
{
|
|
490
|
-
role: 'user',
|
|
491
|
-
content: `# Recent conversation context\n${historyContext}\n\n# Task to reason about\n${args.prompt}`,
|
|
492
|
-
},
|
|
493
|
-
];
|
|
494
|
-
|
|
495
|
-
try {
|
|
496
|
-
const header = `Thinker (${THINKER_MODEL})\n${'─'.repeat(40)}\n`;
|
|
497
|
-
const streamCb = onStream ? (text) => onStream(truncateOutput(header + text)) : null;
|
|
498
|
-
const result = await streamCompletion({
|
|
499
|
-
model: THINKER_MODEL,
|
|
500
|
-
messages: thinkerMessages,
|
|
501
|
-
max_tokens: 4096,
|
|
502
|
-
temperature: 0.4,
|
|
503
|
-
}, streamCb) || '(No response from thinker)';
|
|
504
|
-
return truncateOutput(header + result);
|
|
505
|
-
} catch (apiErr) {
|
|
506
|
-
return `Error: Thinker failed — ${apiErr.message}`;
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// ===== ThinkerBestOfN — Multiple reasoning passes, select best =====
|
|
511
|
-
case 'ThinkerBestOfN': {
|
|
512
|
-
const mode = getMode();
|
|
513
|
-
if (mode !== 'max') {
|
|
514
|
-
return 'ThinkerBestOfN is only available in MAX mode. Use /mode max to enable it, or use Thinker instead.';
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
const n = Math.min(5, Math.max(2, args.n || 3));
|
|
518
|
-
const historyCtx = session.conversationHistory.slice(-10).map(m =>
|
|
519
|
-
`[${m.role}]: ${(m.content || '').slice(0, 500)}`
|
|
520
|
-
).join('\n');
|
|
521
|
-
|
|
522
|
-
const header = `Best-of-${n} Thinker (MAX mode)\n${'─'.repeat(40)}\n`;
|
|
523
|
-
if (onStream) onStream(header + `Spawning ${n} parallel thinking agents...`);
|
|
524
|
-
|
|
525
|
-
// Spawn N parallel thinking calls
|
|
526
|
-
const thinkPromises = [];
|
|
527
|
-
for (let i = 0; i < n; i++) {
|
|
528
|
-
const label = String.fromCharCode(65 + i); // A, B, C, ...
|
|
529
|
-
thinkPromises.push(
|
|
530
|
-
streamCompletion({
|
|
531
|
-
model: THINKER_MODEL,
|
|
532
|
-
messages: [
|
|
533
|
-
{ role: 'system', content: THINKER_SYSTEM_PROMPT + `\n\nYou are Thinker ${label}. Approach this from a unique angle. Be creative and thorough.` },
|
|
534
|
-
{
|
|
535
|
-
role: 'user',
|
|
536
|
-
content: `# Context\n${historyCtx}\n\n# Task\n${args.prompt}`,
|
|
537
|
-
},
|
|
538
|
-
],
|
|
539
|
-
max_tokens: 3072,
|
|
540
|
-
temperature: 0.7 + (i * 0.1), // Slightly different temperatures for diversity
|
|
541
|
-
}, null).then(result => ({ label, result }))
|
|
542
|
-
);
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
let thoughts;
|
|
546
|
-
try {
|
|
547
|
-
thoughts = await Promise.all(thinkPromises);
|
|
548
|
-
} catch (apiErr) {
|
|
549
|
-
return `Error: ThinkerBestOfN failed — ${apiErr.message}`;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
if (onStream) onStream(header + `All ${n} thinkers completed. Selecting best response...`);
|
|
553
|
-
|
|
554
|
-
// Format thoughts for selector
|
|
555
|
-
const thoughtsFormatted = thoughts.map(t =>
|
|
556
|
-
`## Thought ${t.label}\n${t.result || '(empty)'}`
|
|
557
|
-
).join('\n\n');
|
|
558
|
-
|
|
559
|
-
// Selector picks the best
|
|
560
|
-
try {
|
|
561
|
-
const selectorResult = await streamCompletion({
|
|
562
|
-
model: REVIEWER_MODEL,
|
|
563
|
-
messages: [
|
|
564
|
-
{
|
|
565
|
-
role: 'system',
|
|
566
|
-
content: `You are a thought selector. You will receive ${n} different reasoning responses to the same question. Pick the best one based on depth, correctness, clarity, and actionability. Output JSON only:\n{ "chosen": "A", "reason": "why this is best" }`,
|
|
567
|
-
},
|
|
568
|
-
{ role: 'user', content: `# Original question\n${args.prompt}\n\n${thoughtsFormatted}` },
|
|
569
|
-
],
|
|
570
|
-
max_tokens: 1024,
|
|
571
|
-
temperature: 0.1,
|
|
572
|
-
}, null);
|
|
573
|
-
|
|
574
|
-
let chosen = 'A';
|
|
575
|
-
let reason = '';
|
|
576
|
-
try {
|
|
577
|
-
const parsed = JSON.parse(selectorResult.replace(/```json?\n?/g, '').replace(/```/g, '').trim());
|
|
578
|
-
chosen = parsed.chosen || 'A';
|
|
579
|
-
reason = parsed.reason || '';
|
|
580
|
-
} catch { /* default to A */ }
|
|
581
|
-
|
|
582
|
-
const winningThought = thoughts.find(t => t.label === chosen) || thoughts[0];
|
|
583
|
-
const result = `${header}Selected: Thought ${chosen}${reason ? ` — ${reason}` : ''}\n\n${winningThought.result}`;
|
|
584
|
-
if (onStream) onStream(truncateOutput(result));
|
|
585
|
-
return truncateOutput(result);
|
|
586
|
-
} catch (apiErr) {
|
|
587
|
-
// Fallback to first thought
|
|
588
|
-
const result = `${header}Selector failed, using Thought A:\n\n${thoughts[0].result}`;
|
|
589
|
-
return truncateOutput(result);
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
// ===== EditorMultiPrompt — Parallel implementation strategies, select best =====
|
|
594
|
-
case 'EditorMultiPrompt': {
|
|
595
|
-
const mode = getMode();
|
|
596
|
-
if (mode !== 'max') {
|
|
597
|
-
return 'EditorMultiPrompt is only available in MAX mode. Use /mode max to enable it.';
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
const strategies = args.strategies || ['straightforward implementation', 'alternative approach'];
|
|
601
|
-
const filesCtx = (args.files || []).map(f =>
|
|
602
|
-
`--- ${f.path} ---\n${f.content}`
|
|
603
|
-
).join('\n\n');
|
|
604
|
-
|
|
605
|
-
const header = `Multi-Prompt Editor (${strategies.length} strategies)\n${'─'.repeat(40)}\n`;
|
|
606
|
-
if (onStream) onStream(header + `Spawning ${strategies.length} parallel editor agents...`);
|
|
607
|
-
|
|
608
|
-
// Spawn parallel editor agents, one per strategy
|
|
609
|
-
const editorPromises = strategies.map((strategy, i) => {
|
|
610
|
-
const label = String.fromCharCode(65 + i);
|
|
611
|
-
return streamCompletion({
|
|
612
|
-
model: NVIDIA_MODEL,
|
|
613
|
-
messages: [
|
|
614
|
-
{
|
|
615
|
-
role: 'system',
|
|
616
|
-
content: `You are Code Editor ${label}. You implement code changes using a specific strategy. Output your implementation as a series of file edits.\n\nFor each file change, output:\n--- EDIT: path/to/file ---\nOLD:\n\`\`\`\nexact old code\n\`\`\`\nNEW:\n\`\`\`\nnew replacement code\n\`\`\`\n\nFor new files, output:\n--- CREATE: path/to/file ---\n\`\`\`\nfull file content\n\`\`\`\n\nBe precise. Match existing code style.`,
|
|
617
|
-
},
|
|
618
|
-
{
|
|
619
|
-
role: 'user',
|
|
620
|
-
content: `# Task\n${args.prompt}\n\n# Strategy\n${strategy}\n\n# Current files\n${filesCtx}`,
|
|
621
|
-
},
|
|
622
|
-
],
|
|
623
|
-
max_tokens: 4096,
|
|
624
|
-
temperature: 0.3,
|
|
625
|
-
}, null).then(result => ({ label, strategy, result: result || '(empty)' }));
|
|
626
|
-
});
|
|
627
|
-
|
|
628
|
-
let implementations;
|
|
629
|
-
try {
|
|
630
|
-
implementations = await Promise.all(editorPromises);
|
|
631
|
-
} catch (apiErr) {
|
|
632
|
-
return `Error: EditorMultiPrompt failed — ${apiErr.message}`;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
if (onStream) onStream(header + `All editors completed. Selecting best implementation...`);
|
|
636
|
-
|
|
637
|
-
// Format implementations for selector
|
|
638
|
-
const implFormatted = implementations.map(impl =>
|
|
639
|
-
`## Implementation ${impl.label} — Strategy: "${impl.strategy}"\n${impl.result}`
|
|
640
|
-
).join('\n\n');
|
|
641
|
-
|
|
642
|
-
// Selector picks the best
|
|
643
|
-
try {
|
|
644
|
-
const selectorResult = await streamCompletion({
|
|
645
|
-
model: REVIEWER_MODEL,
|
|
646
|
-
messages: [
|
|
647
|
-
{ role: 'system', content: SELECTOR_SYSTEM_PROMPT },
|
|
648
|
-
{
|
|
649
|
-
role: 'user',
|
|
650
|
-
content: `# Original task\n${args.prompt}\n\n${implFormatted}`,
|
|
651
|
-
},
|
|
652
|
-
],
|
|
653
|
-
max_tokens: 1024,
|
|
654
|
-
temperature: 0.1,
|
|
655
|
-
}, null);
|
|
656
|
-
|
|
657
|
-
let chosen = 'A';
|
|
658
|
-
let reason = '';
|
|
659
|
-
let improvements = '';
|
|
660
|
-
try {
|
|
661
|
-
const parsed = JSON.parse(selectorResult.replace(/```json?\n?/g, '').replace(/```/g, '').trim());
|
|
662
|
-
chosen = parsed.chosen || 'A';
|
|
663
|
-
reason = parsed.reason || '';
|
|
664
|
-
improvements = parsed.improvements || '';
|
|
665
|
-
} catch { /* default to A */ }
|
|
666
|
-
|
|
667
|
-
const winning = implementations.find(impl => impl.label === chosen) || implementations[0];
|
|
668
|
-
let result = `${header}Selected: Implementation ${chosen} ("${winning.strategy}")`;
|
|
669
|
-
if (reason) result += `\nReason: ${reason}`;
|
|
670
|
-
if (improvements) result += `\nImprovements to consider: ${improvements}`;
|
|
671
|
-
result += `\n\n${winning.result}`;
|
|
672
|
-
if (onStream) onStream(truncateOutput(result));
|
|
673
|
-
return truncateOutput(result);
|
|
674
|
-
} catch (apiErr) {
|
|
675
|
-
const result = `${header}Selector failed, using Implementation A:\n\n${implementations[0].result}`;
|
|
676
|
-
return truncateOutput(result);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
// ===== CodeReviewMulti — Multiple review perspectives in parallel =====
|
|
681
|
-
case 'CodeReviewMulti': {
|
|
682
|
-
const mode = getMode();
|
|
683
|
-
if (mode !== 'max') {
|
|
684
|
-
return 'CodeReviewMulti is only available in MAX mode. Use /mode max to enable it, or use CodeReview for a single review.';
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
const perspectives = args.perspectives || [
|
|
688
|
-
'correctness, logic errors, and edge cases',
|
|
689
|
-
'security vulnerabilities and data safety',
|
|
690
|
-
'performance, efficiency, and resource usage',
|
|
691
|
-
];
|
|
692
|
-
|
|
693
|
-
// Gather modified files
|
|
694
|
-
const modFiles = new Set([...session.filesModified]);
|
|
695
|
-
if (modFiles.size === 0) return 'CodeReviewMulti skipped — no files were modified.';
|
|
696
|
-
|
|
697
|
-
const modFileContents = [];
|
|
698
|
-
for (const fp of modFiles) {
|
|
699
|
-
if (!fs.existsSync(fp)) continue;
|
|
700
|
-
const stat = fs.statSync(fp);
|
|
701
|
-
if (stat.isDirectory()) continue;
|
|
702
|
-
modFileContents.push(`--- ${path.relative(PROJECT_ROOT, fp)} ---\n${fs.readFileSync(fp, 'utf-8')}`);
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
let diffText = '';
|
|
706
|
-
try {
|
|
707
|
-
diffText = execSync('git diff 2>/dev/null', { encoding: 'utf-8', cwd: PROJECT_ROOT, timeout: 10000 }).trim();
|
|
708
|
-
} catch {}
|
|
709
|
-
|
|
710
|
-
const header = `Multi-Perspective Code Review (${perspectives.length} reviewers)\n${'─'.repeat(40)}\n`;
|
|
711
|
-
if (onStream) onStream(header + `Spawning ${perspectives.length} parallel reviewers...`);
|
|
712
|
-
|
|
713
|
-
const reviewPromises = perspectives.map((perspective, i) => {
|
|
714
|
-
const label = String.fromCharCode(65 + i);
|
|
715
|
-
return streamCompletion({
|
|
716
|
-
model: REVIEWER_MODEL,
|
|
717
|
-
messages: [
|
|
718
|
-
{
|
|
719
|
-
role: 'system',
|
|
720
|
-
content: REVIEWER_SYSTEM_PROMPT + `\n\nFocus specifically on: ${perspective}. You are Reviewer ${label}.`,
|
|
721
|
-
},
|
|
722
|
-
{
|
|
723
|
-
role: 'user',
|
|
724
|
-
content: `# Changes\n${args.prompt}\n\n# Files (${modFiles.size})\n${modFileContents.join('\n\n')}${diffText ? `\n\n# Git diff\n\`\`\`diff\n${diffText}\n\`\`\`` : ''}`,
|
|
725
|
-
},
|
|
726
|
-
],
|
|
727
|
-
max_tokens: 3072,
|
|
728
|
-
temperature: 0.3,
|
|
729
|
-
}, null).then(result => ({ label, perspective, result: result || '(no issues found)' }));
|
|
730
|
-
});
|
|
731
|
-
|
|
732
|
-
let reviews;
|
|
733
|
-
try {
|
|
734
|
-
reviews = await Promise.all(reviewPromises);
|
|
735
|
-
} catch (apiErr) {
|
|
736
|
-
return `Error: CodeReviewMulti failed — ${apiErr.message}`;
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
let result = header;
|
|
740
|
-
for (const review of reviews) {
|
|
741
|
-
result += `\n## Reviewer ${review.label} — ${review.perspective}\n${review.result}\n`;
|
|
742
|
-
}
|
|
743
|
-
if (onStream) onStream(truncateOutput(result));
|
|
744
|
-
return truncateOutput(result);
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
// ===== Commander — Terminal command specialist =====
|
|
748
|
-
case 'Commander': {
|
|
749
|
-
const header = `Commander (${COMMANDER_MODEL})\n${'─'.repeat(40)}\n`;
|
|
750
|
-
if (onStream) onStream(header + 'Planning commands...');
|
|
751
|
-
|
|
752
|
-
let commandPlan;
|
|
753
|
-
try {
|
|
754
|
-
commandPlan = await streamCompletion({
|
|
755
|
-
model: COMMANDER_MODEL,
|
|
756
|
-
messages: [
|
|
757
|
-
{ role: 'system', content: COMMANDER_SYSTEM_PROMPT },
|
|
758
|
-
{ role: 'user', content: args.prompt },
|
|
759
|
-
],
|
|
760
|
-
max_tokens: 2048,
|
|
761
|
-
temperature: 0.2,
|
|
762
|
-
}, null);
|
|
763
|
-
} catch (apiErr) {
|
|
764
|
-
return `Error: Commander failed — ${apiErr.message}`;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
// Parse command plan
|
|
768
|
-
let commands;
|
|
769
|
-
try {
|
|
770
|
-
commands = JSON.parse(commandPlan.replace(/```json?\n?/g, '').replace(/```/g, '').trim());
|
|
771
|
-
if (!Array.isArray(commands)) commands = [commands];
|
|
772
|
-
} catch {
|
|
773
|
-
return truncateOutput(`${header}Failed to parse command plan:\n${commandPlan}`);
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
// Execute commands in sequence
|
|
777
|
-
const results = [];
|
|
778
|
-
for (const cmd of commands) {
|
|
779
|
-
const command = typeof cmd === 'string' ? cmd : cmd.command;
|
|
780
|
-
const description = typeof cmd === 'string' ? '' : (cmd.description || '');
|
|
781
|
-
if (onStream) onStream(truncateOutput(`${header}Running: ${command}${description ? ` (${description})` : ''}...`));
|
|
782
|
-
try {
|
|
783
|
-
const output = execSync(command, {
|
|
784
|
-
encoding: 'utf-8',
|
|
785
|
-
timeout: TOOL_TIMEOUT,
|
|
786
|
-
cwd: PROJECT_ROOT,
|
|
787
|
-
maxBuffer: 1024 * 1024 * 5,
|
|
788
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
789
|
-
});
|
|
790
|
-
results.push(`✓ ${command}${description ? `\n (${description})` : ''}\n${(output || '').trim()}`);
|
|
791
|
-
session.commandsRun.push(command);
|
|
792
|
-
} catch (err) {
|
|
793
|
-
results.push(`✗ ${command}\nExit code: ${err.status}\n${(err.stdout || '').trim()}\n${(err.stderr || '').trim()}`);
|
|
794
|
-
session.commandsRun.push(command);
|
|
795
|
-
break;
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
const result = `${header}${results.join('\n\n')}`;
|
|
800
|
-
if (onStream) onStream(truncateOutput(result));
|
|
801
|
-
return truncateOutput(result);
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
// ===== ContextPruner — Conversation summarization =====
|
|
805
|
-
case 'ContextPruner': {
|
|
806
|
-
if (session.conversationHistory.length < 6) {
|
|
807
|
-
return 'Context pruning skipped — conversation is still short.';
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
const header = `Context Pruner\n${'─'.repeat(40)}\n`;
|
|
811
|
-
if (onStream) onStream(header + 'Summarizing conversation...');
|
|
812
|
-
|
|
813
|
-
const historyText = session.conversationHistory.map(m =>
|
|
814
|
-
`[${m.role}]: ${(m.content || '').slice(0, 1000)}`
|
|
815
|
-
).join('\n');
|
|
816
|
-
|
|
817
|
-
try {
|
|
818
|
-
const summary = await streamCompletion({
|
|
819
|
-
model: CONTEXT_PRUNER_MODEL,
|
|
820
|
-
messages: [
|
|
821
|
-
{ role: 'system', content: CONTEXT_PRUNER_SYSTEM_PROMPT },
|
|
822
|
-
{ role: 'user', content: `# Conversation to summarize (${session.conversationHistory.length} messages)\n\n${historyText}` },
|
|
823
|
-
],
|
|
824
|
-
max_tokens: 2048,
|
|
825
|
-
temperature: 0.2,
|
|
826
|
-
}, null);
|
|
827
|
-
|
|
828
|
-
// Replace conversation history with summary
|
|
829
|
-
const oldLen = session.conversationHistory.length;
|
|
830
|
-
session.conversationHistory = [
|
|
831
|
-
{
|
|
832
|
-
role: 'system',
|
|
833
|
-
content: `[Context Summary — ${oldLen} messages condensed]\n${summary}`,
|
|
834
|
-
},
|
|
835
|
-
];
|
|
836
|
-
|
|
837
|
-
const result = `${header}Condensed ${oldLen} messages into summary.\n\n${summary}`;
|
|
838
|
-
if (onStream) onStream(truncateOutput(result));
|
|
839
|
-
return truncateOutput(result);
|
|
840
|
-
} catch (apiErr) {
|
|
841
|
-
return `Error: Context pruning failed — ${apiErr.message}`;
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
default:
|
|
846
|
-
return `Unknown tool: ${name}`;
|
|
847
|
-
}
|
|
848
|
-
} catch (err) {
|
|
849
|
-
return `Error executing ${name}: ${err.message}`;
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
module.exports = { executeTool };
|