codeep 1.2.16 → 1.2.18
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/README.md +20 -7
- package/dist/api/index.d.ts +7 -0
- package/dist/api/index.js +21 -17
- package/dist/renderer/App.d.ts +1 -5
- package/dist/renderer/App.js +106 -486
- package/dist/renderer/Input.js +8 -1
- package/dist/renderer/agentExecution.d.ts +36 -0
- package/dist/renderer/agentExecution.js +394 -0
- package/dist/renderer/commands.d.ts +16 -0
- package/dist/renderer/commands.js +838 -0
- package/dist/renderer/handlers.d.ts +87 -0
- package/dist/renderer/handlers.js +260 -0
- package/dist/renderer/highlight.d.ts +18 -0
- package/dist/renderer/highlight.js +130 -0
- package/dist/renderer/main.d.ts +4 -2
- package/dist/renderer/main.js +103 -1550
- package/dist/utils/agent.d.ts +5 -15
- package/dist/utils/agent.js +9 -693
- package/dist/utils/agentChat.d.ts +46 -0
- package/dist/utils/agentChat.js +343 -0
- package/dist/utils/agentStream.d.ts +23 -0
- package/dist/utils/agentStream.js +216 -0
- package/dist/utils/keychain.js +3 -2
- package/dist/utils/learning.js +9 -3
- package/dist/utils/mcpIntegration.d.ts +61 -0
- package/dist/utils/mcpIntegration.js +154 -0
- package/dist/utils/project.js +8 -3
- package/dist/utils/skills.js +21 -11
- package/dist/utils/smartContext.d.ts +4 -0
- package/dist/utils/smartContext.js +51 -14
- package/dist/utils/toolExecution.d.ts +27 -0
- package/dist/utils/toolExecution.js +525 -0
- package/dist/utils/toolParsing.d.ts +18 -0
- package/dist/utils/toolParsing.js +302 -0
- package/dist/utils/tools.d.ts +27 -24
- package/dist/utils/tools.js +30 -1169
- package/package.json +3 -1
- package/dist/config/config.test.d.ts +0 -1
- package/dist/config/config.test.js +0 -157
- package/dist/config/providers.test.d.ts +0 -1
- package/dist/config/providers.test.js +0 -187
- package/dist/hooks/index.d.ts +0 -4
- package/dist/hooks/index.js +0 -4
- package/dist/hooks/useAgent.d.ts +0 -29
- package/dist/hooks/useAgent.js +0 -148
- package/dist/utils/agent.test.d.ts +0 -1
- package/dist/utils/agent.test.js +0 -315
- package/dist/utils/git.test.d.ts +0 -1
- package/dist/utils/git.test.js +0 -193
- package/dist/utils/gitignore.test.d.ts +0 -1
- package/dist/utils/gitignore.test.js +0 -167
- package/dist/utils/project.test.d.ts +0 -1
- package/dist/utils/project.test.js +0 -212
- package/dist/utils/ratelimit.test.d.ts +0 -1
- package/dist/utils/ratelimit.test.js +0 -131
- package/dist/utils/retry.test.d.ts +0 -1
- package/dist/utils/retry.test.js +0 -163
- package/dist/utils/smartContext.test.d.ts +0 -1
- package/dist/utils/smartContext.test.js +0 -382
- package/dist/utils/tools.test.d.ts +0 -1
- package/dist/utils/tools.test.js +0 -676
- package/dist/utils/validation.test.d.ts +0 -1
- package/dist/utils/validation.test.js +0 -164
package/dist/renderer/Input.js
CHANGED
|
@@ -448,8 +448,15 @@ export class LineEditor {
|
|
|
448
448
|
}
|
|
449
449
|
break;
|
|
450
450
|
default:
|
|
451
|
+
// Alt+b / Alt+f — word jump (macOS Option+Left/Right)
|
|
452
|
+
if (event.alt && event.key === 'b') {
|
|
453
|
+
this.wordLeft();
|
|
454
|
+
}
|
|
455
|
+
else if (event.alt && event.key === 'f') {
|
|
456
|
+
this.wordRight();
|
|
457
|
+
}
|
|
451
458
|
// Regular character (single char, not control)
|
|
452
|
-
if (event.key.length === 1 && !event.ctrl && !event.alt) {
|
|
459
|
+
else if (event.key.length === 1 && !event.ctrl && !event.alt) {
|
|
453
460
|
this.value = this.value.slice(0, this.cursorPos) + event.key + this.value.slice(this.cursorPos);
|
|
454
461
|
this.cursorPos++;
|
|
455
462
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent task execution, skill dispatch, and command chaining.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from main.ts to keep the entry point lean. All functions
|
|
5
|
+
* receive an AppExecutionContext so they remain decoupled from the
|
|
6
|
+
* global variables in main.ts.
|
|
7
|
+
*/
|
|
8
|
+
import { App } from './App';
|
|
9
|
+
import { ProjectContext } from '../utils/project';
|
|
10
|
+
export interface AppExecutionContext {
|
|
11
|
+
app: App;
|
|
12
|
+
projectPath: string;
|
|
13
|
+
projectContext: ProjectContext | null;
|
|
14
|
+
hasWriteAccess: boolean;
|
|
15
|
+
addedFiles: Map<string, {
|
|
16
|
+
relativePath: string;
|
|
17
|
+
content: string;
|
|
18
|
+
}>;
|
|
19
|
+
isAgentRunning: () => boolean;
|
|
20
|
+
setAgentRunning: (v: boolean) => void;
|
|
21
|
+
abortController: AbortController | null;
|
|
22
|
+
setAbortController: (ctrl: AbortController | null) => void;
|
|
23
|
+
formatAddedFilesContext: () => string;
|
|
24
|
+
handleCommand: (command: string, args: string[]) => Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export declare function isDangerousTool(toolName: string, parameters: Record<string, unknown>): boolean;
|
|
27
|
+
export declare function requestToolConfirmation(app: App, tool: string, parameters: Record<string, unknown>, onConfirm: () => void, onCancel: () => void): void;
|
|
28
|
+
export interface PendingInteractiveContext {
|
|
29
|
+
originalTask: string;
|
|
30
|
+
context: import('../utils/interactive').InteractiveContext;
|
|
31
|
+
dryRun: boolean;
|
|
32
|
+
}
|
|
33
|
+
export declare function runAgentTask(task: string, dryRun: boolean, ctx: AppExecutionContext, getPendingInteractive: () => PendingInteractiveContext | null, setPendingInteractive: (v: PendingInteractiveContext | null) => void): Promise<void>;
|
|
34
|
+
export declare function executeAgentTask(task: string, dryRun: boolean, ctx: AppExecutionContext): Promise<void>;
|
|
35
|
+
export declare function runSkill(nameOrShortcut: string, args: string[], ctx: AppExecutionContext): Promise<boolean>;
|
|
36
|
+
export declare function runCommandChain(commands: string[], index: number, ctx: AppExecutionContext): void;
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent task execution, skill dispatch, and command chaining.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from main.ts to keep the entry point lean. All functions
|
|
5
|
+
* receive an AppExecutionContext so they remain decoupled from the
|
|
6
|
+
* global variables in main.ts.
|
|
7
|
+
*/
|
|
8
|
+
import { chat } from '../api/index.js';
|
|
9
|
+
import { runAgent } from '../utils/agent.js';
|
|
10
|
+
import { config, autoSaveSession } from '../config/index.js';
|
|
11
|
+
import { getGitStatus } from '../utils/git.js';
|
|
12
|
+
// ─── Dangerous tool detection ────────────────────────────────────────────────
|
|
13
|
+
const DANGEROUS_TOOLS = ['write', 'edit', 'delete', 'command', 'execute', 'shell', 'rm', 'mv'];
|
|
14
|
+
export function isDangerousTool(toolName, parameters) {
|
|
15
|
+
const lowerName = toolName.toLowerCase();
|
|
16
|
+
if (DANGEROUS_TOOLS.some(d => lowerName.includes(d)))
|
|
17
|
+
return true;
|
|
18
|
+
const command = parameters.command || '';
|
|
19
|
+
const dangerousCommands = ['rm ', 'rm -', 'rmdir', 'del ', 'delete', 'drop ', 'truncate'];
|
|
20
|
+
return dangerousCommands.some(c => command.toLowerCase().includes(c));
|
|
21
|
+
}
|
|
22
|
+
export function requestToolConfirmation(app, tool, parameters, onConfirm, onCancel) {
|
|
23
|
+
const target = parameters.path ||
|
|
24
|
+
parameters.command ||
|
|
25
|
+
parameters.pattern ||
|
|
26
|
+
'unknown';
|
|
27
|
+
const shortTarget = target.length > 50 ? '...' + target.slice(-47) : target;
|
|
28
|
+
app.showConfirm({
|
|
29
|
+
title: '⚠️ Confirm Action',
|
|
30
|
+
message: [
|
|
31
|
+
`The agent wants to execute:`,
|
|
32
|
+
'',
|
|
33
|
+
` ${tool}`,
|
|
34
|
+
` ${shortTarget}`,
|
|
35
|
+
'',
|
|
36
|
+
'Allow this action?',
|
|
37
|
+
],
|
|
38
|
+
confirmLabel: 'Allow',
|
|
39
|
+
cancelLabel: 'Deny',
|
|
40
|
+
onConfirm,
|
|
41
|
+
onCancel,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// ─── Agent task execution ─────────────────────────────────────────────────────
|
|
45
|
+
export async function runAgentTask(task, dryRun, ctx, getPendingInteractive, setPendingInteractive) {
|
|
46
|
+
const { app, projectContext } = ctx;
|
|
47
|
+
if (!projectContext) {
|
|
48
|
+
app.notify('Agent requires project context');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (!ctx.hasWriteAccess && !dryRun) {
|
|
52
|
+
app.notify('Agent requires write access. Use /grant first.');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (ctx.isAgentRunning()) {
|
|
56
|
+
app.notify('Agent already running. Use /stop to cancel.');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const interactiveMode = config.get('agentInteractive') !== false;
|
|
60
|
+
if (interactiveMode) {
|
|
61
|
+
const { analyzeForClarification, formatQuestions } = await import('../utils/interactive.js');
|
|
62
|
+
const interactiveContext = analyzeForClarification(task);
|
|
63
|
+
if (interactiveContext.needsClarification) {
|
|
64
|
+
setPendingInteractive({ originalTask: task, context: interactiveContext, dryRun });
|
|
65
|
+
app.addMessage({ role: 'assistant', content: formatQuestions(interactiveContext) });
|
|
66
|
+
app.notify('Answer questions or type "proceed" to continue');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const confirmationMode = config.get('agentConfirmation') || 'dangerous';
|
|
71
|
+
if (confirmationMode === 'never' || dryRun) {
|
|
72
|
+
executeAgentTask(task, dryRun, ctx);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (confirmationMode === 'always') {
|
|
76
|
+
const shortTask = task.length > 60 ? task.slice(0, 57) + '...' : task;
|
|
77
|
+
app.showConfirm({
|
|
78
|
+
title: '⚠️ Confirm Agent Task',
|
|
79
|
+
message: [
|
|
80
|
+
'The agent will execute the following task:',
|
|
81
|
+
'',
|
|
82
|
+
` "${shortTask}"`,
|
|
83
|
+
'',
|
|
84
|
+
'This may modify files in your project.',
|
|
85
|
+
'Do you want to proceed?',
|
|
86
|
+
],
|
|
87
|
+
confirmLabel: 'Run Agent',
|
|
88
|
+
cancelLabel: 'Cancel',
|
|
89
|
+
onConfirm: () => executeAgentTask(task, dryRun, ctx),
|
|
90
|
+
onCancel: () => app.notify('Agent task cancelled'),
|
|
91
|
+
});
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
// 'dangerous' mode — confirm only for risky keywords
|
|
95
|
+
const dangerousKeywords = ['delete', 'remove', 'drop', 'reset', 'force', 'overwrite', 'replace all', 'rm ', 'clear'];
|
|
96
|
+
if (dangerousKeywords.some(k => task.toLowerCase().includes(k))) {
|
|
97
|
+
const shortTask = task.length > 60 ? task.slice(0, 57) + '...' : task;
|
|
98
|
+
app.showConfirm({
|
|
99
|
+
title: '⚠️ Potentially Dangerous Task',
|
|
100
|
+
message: [
|
|
101
|
+
'This task contains potentially dangerous operations:',
|
|
102
|
+
'',
|
|
103
|
+
` "${shortTask}"`,
|
|
104
|
+
'',
|
|
105
|
+
'Files may be deleted or overwritten.',
|
|
106
|
+
'Do you want to proceed?',
|
|
107
|
+
],
|
|
108
|
+
confirmLabel: 'Proceed',
|
|
109
|
+
cancelLabel: 'Cancel',
|
|
110
|
+
onConfirm: () => executeAgentTask(task, dryRun, ctx),
|
|
111
|
+
onCancel: () => app.notify('Agent task cancelled'),
|
|
112
|
+
});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
executeAgentTask(task, dryRun, ctx);
|
|
116
|
+
}
|
|
117
|
+
export async function executeAgentTask(task, dryRun, ctx) {
|
|
118
|
+
const { app, projectContext } = ctx;
|
|
119
|
+
if (!projectContext) {
|
|
120
|
+
app.notify('Agent requires project context');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
// Guard against concurrent execution — set flag immediately before any await
|
|
124
|
+
if (ctx.isAgentRunning()) {
|
|
125
|
+
app.notify('Agent already running. Use /stop to cancel.');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
ctx.setAgentRunning(true);
|
|
129
|
+
const abortController = new AbortController();
|
|
130
|
+
ctx.setAbortController(abortController);
|
|
131
|
+
const prefix = dryRun ? '[DRY RUN] ' : '[AGENT] ';
|
|
132
|
+
app.addMessage({ role: 'user', content: prefix + task });
|
|
133
|
+
app.setAgentRunning(true);
|
|
134
|
+
const context = projectContext;
|
|
135
|
+
try {
|
|
136
|
+
const fileContext = ctx.formatAddedFilesContext();
|
|
137
|
+
const enrichedTask = fileContext ? fileContext + task : task;
|
|
138
|
+
const result = await runAgent(enrichedTask, context, {
|
|
139
|
+
dryRun,
|
|
140
|
+
chatHistory: app.getChatHistory(),
|
|
141
|
+
onIteration: (iteration) => {
|
|
142
|
+
app.updateAgentProgress(iteration);
|
|
143
|
+
},
|
|
144
|
+
onToolCall: (tool) => {
|
|
145
|
+
const toolName = tool.tool.toLowerCase();
|
|
146
|
+
const target = tool.parameters.path ||
|
|
147
|
+
tool.parameters.command ||
|
|
148
|
+
tool.parameters.pattern || '';
|
|
149
|
+
const actionType = toolName.includes('write') ? 'write' :
|
|
150
|
+
toolName.includes('edit') ? 'edit' :
|
|
151
|
+
toolName.includes('read') ? 'read' :
|
|
152
|
+
toolName.includes('delete') ? 'delete' :
|
|
153
|
+
toolName.includes('list') ? 'list' :
|
|
154
|
+
toolName.includes('search') || toolName.includes('grep') ? 'search' :
|
|
155
|
+
toolName.includes('mkdir') ? 'mkdir' :
|
|
156
|
+
toolName.includes('fetch') ? 'fetch' : 'command';
|
|
157
|
+
const shortTarget = target.length > 50 ? '...' + target.slice(-47) : target;
|
|
158
|
+
app.setAgentThinking(`${actionType}: ${shortTarget}`);
|
|
159
|
+
if (actionType === 'write' && tool.parameters.content) {
|
|
160
|
+
const filePath = tool.parameters.path;
|
|
161
|
+
try {
|
|
162
|
+
const { createFileDiff, formatDiffForDisplay } = require('../utils/diffPreview');
|
|
163
|
+
const diff = createFileDiff(filePath, tool.parameters.content, context.root);
|
|
164
|
+
const diffText = formatDiffForDisplay(diff);
|
|
165
|
+
const additions = diff.hunks.reduce((sum, h) => sum + h.lines.filter((l) => l.type === 'add').length, 0);
|
|
166
|
+
const deletions = diff.hunks.reduce((sum, h) => sum + h.lines.filter((l) => l.type === 'remove').length, 0);
|
|
167
|
+
app.addMessage({
|
|
168
|
+
role: 'system',
|
|
169
|
+
content: `**${diff.type === 'create' ? 'Create' : 'Write'}** \`${filePath}\` (+${additions} -${deletions})\n\n\`\`\`diff\n${diffText}\n\`\`\``,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
const ext = filePath.split('.').pop() || '';
|
|
174
|
+
app.addMessage({
|
|
175
|
+
role: 'system',
|
|
176
|
+
content: `**Write** \`${filePath}\`\n\n\`\`\`${ext}\n${tool.parameters.content}\n\`\`\``,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
else if (actionType === 'edit' && tool.parameters.new_text) {
|
|
181
|
+
const filePath = tool.parameters.path;
|
|
182
|
+
try {
|
|
183
|
+
const { createEditDiff, formatDiffForDisplay } = require('../utils/diffPreview');
|
|
184
|
+
const diff = createEditDiff(filePath, tool.parameters.old_text, tool.parameters.new_text, context.root);
|
|
185
|
+
if (diff) {
|
|
186
|
+
const additions = diff.hunks.reduce((sum, h) => sum + h.lines.filter((l) => l.type === 'add').length, 0);
|
|
187
|
+
const deletions = diff.hunks.reduce((sum, h) => sum + h.lines.filter((l) => l.type === 'remove').length, 0);
|
|
188
|
+
app.addMessage({
|
|
189
|
+
role: 'system',
|
|
190
|
+
content: `**Edit** \`${filePath}\` (+${additions} -${deletions})\n\n\`\`\`diff\n${formatDiffForDisplay(diff)}\n\`\`\``,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
const ext = filePath.split('.').pop() || '';
|
|
195
|
+
app.addMessage({
|
|
196
|
+
role: 'system',
|
|
197
|
+
content: `**Edit** \`${filePath}\`\n\n\`\`\`${ext}\n${tool.parameters.new_text}\n\`\`\``,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
const ext = filePath.split('.').pop() || '';
|
|
203
|
+
app.addMessage({
|
|
204
|
+
role: 'system',
|
|
205
|
+
content: `**Edit** \`${filePath}\`\n\n\`\`\`${ext}\n${tool.parameters.new_text}\n\`\`\``,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else if (actionType === 'delete') {
|
|
210
|
+
const filePath = tool.parameters.path;
|
|
211
|
+
app.addMessage({ role: 'system', content: `**Delete** \`${filePath}\`` });
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
onToolResult: (result, toolCall) => {
|
|
215
|
+
const toolName = toolCall.tool.toLowerCase();
|
|
216
|
+
const target = toolCall.parameters.path || toolCall.parameters.command || '';
|
|
217
|
+
const actionType = toolName.includes('write') ? 'write' :
|
|
218
|
+
toolName.includes('edit') ? 'edit' :
|
|
219
|
+
toolName.includes('read') ? 'read' :
|
|
220
|
+
toolName.includes('delete') ? 'delete' :
|
|
221
|
+
toolName.includes('list') ? 'list' :
|
|
222
|
+
toolName.includes('search') || toolName.includes('grep') ? 'search' :
|
|
223
|
+
toolName.includes('mkdir') ? 'mkdir' :
|
|
224
|
+
toolName.includes('fetch') ? 'fetch' : 'command';
|
|
225
|
+
app.updateAgentProgress(0, {
|
|
226
|
+
type: actionType,
|
|
227
|
+
target,
|
|
228
|
+
result: result.success ? 'success' : 'error',
|
|
229
|
+
});
|
|
230
|
+
},
|
|
231
|
+
onThinking: (text) => {
|
|
232
|
+
if (text)
|
|
233
|
+
app.setAgentThinking(text);
|
|
234
|
+
},
|
|
235
|
+
abortSignal: abortController.signal,
|
|
236
|
+
});
|
|
237
|
+
if (result.success) {
|
|
238
|
+
const summary = result.finalResponse || `Completed ${result.actions.length} actions in ${result.iterations} steps.`;
|
|
239
|
+
app.addMessage({ role: 'assistant', content: summary });
|
|
240
|
+
app.notify(`Agent completed: ${result.actions.length} actions`);
|
|
241
|
+
// Auto-commit if enabled and there were file changes
|
|
242
|
+
if (!dryRun && config.get('agentAutoCommit') && result.actions.length > 0) {
|
|
243
|
+
try {
|
|
244
|
+
const { autoCommitAgentChanges, createBranchAndCommit } = await import('../utils/git.js');
|
|
245
|
+
const useBranch = config.get('agentAutoCommitBranch');
|
|
246
|
+
if (useBranch) {
|
|
247
|
+
const commitResult = createBranchAndCommit(task, result.actions, context.root);
|
|
248
|
+
if (commitResult.success) {
|
|
249
|
+
app.addMessage({ role: 'system', content: `Auto-committed on branch \`${commitResult.branch}\` (${commitResult.hash?.slice(0, 7)})` });
|
|
250
|
+
}
|
|
251
|
+
else if (commitResult.error !== 'No changes detected by git') {
|
|
252
|
+
app.addMessage({ role: 'system', content: `Auto-commit failed: ${commitResult.error}` });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
const commitResult = autoCommitAgentChanges(task, result.actions, context.root);
|
|
257
|
+
if (commitResult.success) {
|
|
258
|
+
app.addMessage({ role: 'system', content: `Auto-committed: ${commitResult.hash?.slice(0, 7)}` });
|
|
259
|
+
}
|
|
260
|
+
else if (commitResult.error !== 'No changes detected by git') {
|
|
261
|
+
app.addMessage({ role: 'system', content: `Auto-commit failed: ${commitResult.error}` });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
// auto-commit is best-effort
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
else if (result.aborted) {
|
|
271
|
+
app.addMessage({ role: 'assistant', content: 'Agent stopped by user.' });
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
app.addMessage({ role: 'assistant', content: `Agent failed: ${result.error || 'Unknown error'}` });
|
|
275
|
+
}
|
|
276
|
+
autoSaveSession(app.getMessages(), ctx.projectPath);
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
const err = error;
|
|
280
|
+
app.addMessage({ role: 'assistant', content: `Agent error: ${err.message}` });
|
|
281
|
+
app.notify(`Agent error: ${err.message}`, 5000);
|
|
282
|
+
}
|
|
283
|
+
finally {
|
|
284
|
+
ctx.setAgentRunning(false);
|
|
285
|
+
ctx.setAbortController(null);
|
|
286
|
+
app.setAgentRunning(false);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// ─── Skill execution ──────────────────────────────────────────────────────────
|
|
290
|
+
export async function runSkill(nameOrShortcut, args, ctx) {
|
|
291
|
+
const { findSkill, parseSkillArgs, executeSkill, trackSkillUsage } = await import('../utils/skills.js');
|
|
292
|
+
const skill = findSkill(nameOrShortcut);
|
|
293
|
+
if (!skill)
|
|
294
|
+
return false;
|
|
295
|
+
if (skill.requiresGit) {
|
|
296
|
+
if (!ctx.projectPath || !getGitStatus(ctx.projectPath).isRepo) {
|
|
297
|
+
ctx.app.notify('This skill requires a git repository');
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (skill.requiresWriteAccess && !ctx.hasWriteAccess) {
|
|
302
|
+
ctx.app.notify('This skill requires write access. Use /grant first.');
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
const params = parseSkillArgs(args.join(' '), skill);
|
|
306
|
+
ctx.app.addMessage({ role: 'user', content: `/${skill.name}${args.length ? ' ' + args.join(' ') : ''}` });
|
|
307
|
+
trackSkillUsage(skill.name);
|
|
308
|
+
const { spawnSync } = await import('child_process');
|
|
309
|
+
try {
|
|
310
|
+
const result = await executeSkill(skill, params, {
|
|
311
|
+
onCommand: async (cmd) => {
|
|
312
|
+
const proc = spawnSync(cmd, {
|
|
313
|
+
cwd: ctx.projectPath || process.cwd(),
|
|
314
|
+
encoding: 'utf-8',
|
|
315
|
+
timeout: 60000,
|
|
316
|
+
shell: true,
|
|
317
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
318
|
+
});
|
|
319
|
+
const stdout = (proc.stdout || '').trim();
|
|
320
|
+
const stderr = (proc.stderr || '').trim();
|
|
321
|
+
const output = stdout || stderr || '';
|
|
322
|
+
if (proc.status === 0) {
|
|
323
|
+
if (output)
|
|
324
|
+
ctx.app.addMessage({ role: 'system', content: `\`${cmd}\`\n\`\`\`\n${output}\n\`\`\`` });
|
|
325
|
+
return output;
|
|
326
|
+
}
|
|
327
|
+
if (output)
|
|
328
|
+
ctx.app.addMessage({ role: 'system', content: `\`${cmd}\` failed:\n\`\`\`\n${output}\n\`\`\`` });
|
|
329
|
+
throw new Error(output || `Command exited with code ${proc.status}`);
|
|
330
|
+
},
|
|
331
|
+
onPrompt: async (prompt) => {
|
|
332
|
+
try {
|
|
333
|
+
ctx.app.addMessage({ role: 'user', content: prompt });
|
|
334
|
+
ctx.app.startStreaming();
|
|
335
|
+
const history = ctx.app.getChatHistory();
|
|
336
|
+
const response = await chat(prompt, history, (chunk) => {
|
|
337
|
+
ctx.app.addStreamChunk(chunk);
|
|
338
|
+
}, undefined, ctx.projectContext, undefined);
|
|
339
|
+
ctx.app.endStreaming();
|
|
340
|
+
const msgs = ctx.app.getMessages();
|
|
341
|
+
const last = msgs[msgs.length - 1];
|
|
342
|
+
return (last?.role === 'assistant' ? last.content : response || '').trim();
|
|
343
|
+
}
|
|
344
|
+
catch (err) {
|
|
345
|
+
ctx.app.endStreaming();
|
|
346
|
+
throw err;
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
onAgent: (task) => {
|
|
350
|
+
return new Promise((resolve, reject) => {
|
|
351
|
+
if (!ctx.projectContext) {
|
|
352
|
+
reject(new Error('Agent requires project context'));
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
executeAgentTask(task, false, ctx).then(() => resolve('Agent completed')).catch(reject);
|
|
356
|
+
});
|
|
357
|
+
},
|
|
358
|
+
onConfirm: (message) => {
|
|
359
|
+
return new Promise((resolve) => {
|
|
360
|
+
ctx.app.showConfirm({
|
|
361
|
+
title: 'Confirm',
|
|
362
|
+
message: [message],
|
|
363
|
+
confirmLabel: 'Yes',
|
|
364
|
+
cancelLabel: 'No',
|
|
365
|
+
onConfirm: () => resolve(true),
|
|
366
|
+
onCancel: () => resolve(false),
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
},
|
|
370
|
+
onNotify: (message) => {
|
|
371
|
+
ctx.app.notify(message);
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
if (!result.success && result.output !== 'Cancelled by user') {
|
|
375
|
+
ctx.app.notify(`Skill failed: ${result.output}`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
catch (err) {
|
|
379
|
+
ctx.app.notify(`Skill error: ${err.message}`);
|
|
380
|
+
trackSkillUsage(skill.name, false);
|
|
381
|
+
}
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
// ─── Command chaining ─────────────────────────────────────────────────────────
|
|
385
|
+
export function runCommandChain(commands, index, ctx) {
|
|
386
|
+
if (index >= commands.length) {
|
|
387
|
+
ctx.app.notify(`Completed ${commands.length} commands`);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
const cmd = commands[index].toLowerCase();
|
|
391
|
+
ctx.app.notify(`Running /${cmd}... (${index + 1}/${commands.length})`);
|
|
392
|
+
ctx.handleCommand(cmd, []);
|
|
393
|
+
setTimeout(() => runCommandChain(commands, index + 1, ctx), 500);
|
|
394
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command dispatch for all /command handlers.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from main.ts. Receives an AppCommandContext so it remains
|
|
5
|
+
* decoupled from global state. Import-heavy commands use dynamic imports
|
|
6
|
+
* to keep startup time low.
|
|
7
|
+
*/
|
|
8
|
+
import { getProjectContext } from '../utils/project';
|
|
9
|
+
import { AppExecutionContext } from './agentExecution';
|
|
10
|
+
export interface AppCommandContext extends AppExecutionContext {
|
|
11
|
+
sessionId: string;
|
|
12
|
+
setSessionId: (id: string) => void;
|
|
13
|
+
setProjectContext: (ctx: ReturnType<typeof getProjectContext>) => void;
|
|
14
|
+
setHasWriteAccess: (v: boolean) => void;
|
|
15
|
+
}
|
|
16
|
+
export declare function handleCommand(command: string, args: string[], ctx: AppCommandContext): Promise<void>;
|