codeep 1.2.74 → 1.2.76
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 +4 -2
- package/dist/acp/protocol.d.ts +54 -1
- package/dist/acp/server.js +109 -1
- package/dist/acp/session.d.ts +8 -0
- package/dist/acp/session.js +2 -0
- package/dist/acp/transport.d.ts +7 -0
- package/dist/acp/transport.js +23 -0
- package/dist/config/index.js +1 -1
- package/dist/renderer/components/Settings.js +1 -1
- package/dist/utils/agent.d.ts +7 -0
- package/dist/utils/agent.js +69 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
- **Z.AI (ZhipuAI)** — GLM-5, GLM-4.7, GLM-4.7 Flash (international & China endpoints)
|
|
26
26
|
- **MiniMax** — MiniMax M2.5 (international & China endpoints)
|
|
27
27
|
- **DeepSeek** — DeepSeek V3, DeepSeek R1 (reasoning)
|
|
28
|
-
- **Anthropic** — Claude Sonnet 4.6, Claude Opus 4.6, Claude Haiku 4.5
|
|
28
|
+
- **Anthropic** — Claude Sonnet 4.6, Claude Opus 4.6, Claude Sonnet 4.5, Claude Haiku 4.5
|
|
29
|
+
- **Google AI** — Gemini 3.1 Pro Preview, Gemini 3 Pro Preview, Gemini 3 Flash Preview, Gemini 2.5 Pro, Gemini 2.5 Flash, Gemini 2.5 Flash Lite
|
|
29
30
|
- Switch between providers with `/provider`
|
|
30
31
|
- Configure different API keys per provider
|
|
31
32
|
- Both OpenAI-compatible and Anthropic API protocols supported
|
|
@@ -668,6 +669,7 @@ With write access enabled:
|
|
|
668
669
|
| `MINIMAX_CN_API_KEY` | MiniMax China API key |
|
|
669
670
|
| `DEEPSEEK_API_KEY` | DeepSeek API key |
|
|
670
671
|
| `ANTHROPIC_API_KEY` | Anthropic Claude API key |
|
|
672
|
+
| `GOOGLE_API_KEY` | Google AI (Gemini) API key |
|
|
671
673
|
|
|
672
674
|
### Settings (`/settings`)
|
|
673
675
|
|
|
@@ -681,7 +683,7 @@ With write access enabled:
|
|
|
681
683
|
| Agent Mode | ON | `ON` = agent runs automatically (requires write permission via `/grant`), `Manual` = use /agent |
|
|
682
684
|
| Agent API Timeout | 180000ms | Timeout per agent API call (auto-adjusted for complexity) |
|
|
683
685
|
| Agent Max Duration | 20 min | Maximum time for agent to run (5-60 min) |
|
|
684
|
-
| Agent Max Iterations |
|
|
686
|
+
| Agent Max Iterations | 200 | Maximum agent iterations (10-500) |
|
|
685
687
|
| Agent Confirmation | Dangerous | `Never`, `Dangerous` (default), or `Always` |
|
|
686
688
|
| Agent Auto-Commit | Off | Automatically commit after agent completes |
|
|
687
689
|
| Agent Branch | Off | Create new branch for agent commits |
|
package/dist/acp/protocol.d.ts
CHANGED
|
@@ -34,6 +34,7 @@ export interface InitializeParams {
|
|
|
34
34
|
}
|
|
35
35
|
export interface AgentCapabilities {
|
|
36
36
|
loadSession?: boolean;
|
|
37
|
+
terminal?: boolean;
|
|
37
38
|
promptCapabilities?: {
|
|
38
39
|
image?: boolean;
|
|
39
40
|
audio?: boolean;
|
|
@@ -174,7 +175,17 @@ export interface SessionUpdateSessionInfo {
|
|
|
174
175
|
title: string;
|
|
175
176
|
updatedAt?: string;
|
|
176
177
|
}
|
|
177
|
-
export
|
|
178
|
+
export interface PlanEntry {
|
|
179
|
+
id: string;
|
|
180
|
+
content: string;
|
|
181
|
+
priority: 'high' | 'medium' | 'low';
|
|
182
|
+
status: 'pending' | 'in_progress' | 'completed';
|
|
183
|
+
}
|
|
184
|
+
export interface SessionUpdatePlan {
|
|
185
|
+
sessionUpdate: 'plan';
|
|
186
|
+
entries: PlanEntry[];
|
|
187
|
+
}
|
|
188
|
+
export type SessionUpdateInner = SessionUpdateAgentMessageChunk | SessionUpdateAgentThoughtChunk | SessionUpdateToolCall | SessionUpdateToolCallUpdate | SessionUpdatePlan | SessionUpdateAvailableCommands | SessionUpdateCurrentMode | SessionUpdateConfigOption | SessionUpdateSessionInfo;
|
|
178
189
|
export interface SessionUpdateParams {
|
|
179
190
|
sessionId: string;
|
|
180
191
|
update: SessionUpdateInner;
|
|
@@ -237,4 +248,46 @@ export interface FsWriteTextFileParams {
|
|
|
237
248
|
path: string;
|
|
238
249
|
content: string;
|
|
239
250
|
}
|
|
251
|
+
export interface TerminalCreateParams {
|
|
252
|
+
sessionId: string;
|
|
253
|
+
command: string;
|
|
254
|
+
args: string[];
|
|
255
|
+
cwd: string;
|
|
256
|
+
env?: Record<string, string>;
|
|
257
|
+
outputByteLimit?: number;
|
|
258
|
+
}
|
|
259
|
+
export interface TerminalCreateResult {
|
|
260
|
+
terminalId: string;
|
|
261
|
+
}
|
|
262
|
+
export interface TerminalWaitForExitParams {
|
|
263
|
+
sessionId: string;
|
|
264
|
+
terminalId: string;
|
|
265
|
+
timeoutMs?: number;
|
|
266
|
+
}
|
|
267
|
+
export type TerminalExitStatus = {
|
|
268
|
+
type: 'exited';
|
|
269
|
+
code: number;
|
|
270
|
+
} | {
|
|
271
|
+
type: 'killed';
|
|
272
|
+
signal?: string;
|
|
273
|
+
};
|
|
274
|
+
export interface TerminalWaitForExitResult {
|
|
275
|
+
exitStatus: TerminalExitStatus;
|
|
276
|
+
}
|
|
277
|
+
export interface TerminalOutputParams {
|
|
278
|
+
sessionId: string;
|
|
279
|
+
terminalId: string;
|
|
280
|
+
offset?: number;
|
|
281
|
+
}
|
|
282
|
+
export interface TerminalOutputResult {
|
|
283
|
+
output: string;
|
|
284
|
+
/** Absent while the process is still running; present once the process has exited. */
|
|
285
|
+
exitStatus?: TerminalExitStatus;
|
|
286
|
+
}
|
|
287
|
+
export interface TerminalReleaseParams {
|
|
288
|
+
sessionId: string;
|
|
289
|
+
terminalId: string;
|
|
290
|
+
}
|
|
291
|
+
export interface TerminalReleaseResult {
|
|
292
|
+
}
|
|
240
293
|
export type AcpMessage = JsonRpcRequest | JsonRpcResponse | JsonRpcNotification;
|
package/dist/acp/server.js
CHANGED
|
@@ -4,8 +4,10 @@ import { randomUUID } from 'crypto';
|
|
|
4
4
|
import { basename as pathBasename } from 'path';
|
|
5
5
|
import { StdioTransport } from './transport.js';
|
|
6
6
|
import { runAgentSession } from './session.js';
|
|
7
|
+
import { executeCommandAsync } from '../utils/shell.js';
|
|
7
8
|
import { initWorkspace, loadWorkspace, handleCommand } from './commands.js';
|
|
8
9
|
import { autoSaveSession, config, setProvider, listSessionsWithInfo, deleteSession as deleteSessionFile } from '../config/index.js';
|
|
10
|
+
import { ApiError } from '../api/index.js';
|
|
9
11
|
import { PROVIDERS } from '../config/providers.js';
|
|
10
12
|
import { getCurrentVersion } from '../utils/update.js';
|
|
11
13
|
// ─── Slash commands advertised to Zed ────────────────────────────────────────
|
|
@@ -148,6 +150,7 @@ export function startAcpServer() {
|
|
|
148
150
|
handleSessionDelete(req);
|
|
149
151
|
break;
|
|
150
152
|
default:
|
|
153
|
+
process.stderr.write(`[codeep-acp] Unknown method: ${req.method}\n`);
|
|
151
154
|
transport.error(req.id, -32601, `Method not found: ${req.method}`);
|
|
152
155
|
}
|
|
153
156
|
});
|
|
@@ -165,6 +168,7 @@ export function startAcpServer() {
|
|
|
165
168
|
protocolVersion: 1,
|
|
166
169
|
agentCapabilities: {
|
|
167
170
|
loadSession: true,
|
|
171
|
+
terminal: true,
|
|
168
172
|
sessionCapabilities: { list: {} },
|
|
169
173
|
},
|
|
170
174
|
agentInfo: {
|
|
@@ -381,6 +385,18 @@ export function startAcpServer() {
|
|
|
381
385
|
.join('\n');
|
|
382
386
|
const abortController = new AbortController();
|
|
383
387
|
session.abortController = abortController;
|
|
388
|
+
// Plan tracking: build a live plan from tool calls as the agent works
|
|
389
|
+
// ACP spec: send complete list on every update, client replaces current plan
|
|
390
|
+
const planEntries = new Map();
|
|
391
|
+
const sendPlan = () => {
|
|
392
|
+
transport.notify('session/update', {
|
|
393
|
+
sessionId: params.sessionId,
|
|
394
|
+
update: {
|
|
395
|
+
sessionUpdate: 'plan',
|
|
396
|
+
entries: [...planEntries.values()],
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
};
|
|
384
400
|
const agentResponseChunks = [];
|
|
385
401
|
const sendChunk = (text) => {
|
|
386
402
|
agentResponseChunks.push(text);
|
|
@@ -456,6 +472,16 @@ export function startAcpServer() {
|
|
|
456
472
|
: {}),
|
|
457
473
|
},
|
|
458
474
|
});
|
|
475
|
+
// Add to plan as in_progress — only meaningful actions (not reads)
|
|
476
|
+
if (kind === 'edit' || kind === 'execute' || kind === 'delete') {
|
|
477
|
+
planEntries.set(toolCallId, {
|
|
478
|
+
id: toolCallId,
|
|
479
|
+
content: title || toolName,
|
|
480
|
+
priority: kind === 'execute' ? 'high' : 'medium',
|
|
481
|
+
status: 'in_progress',
|
|
482
|
+
});
|
|
483
|
+
sendPlan();
|
|
484
|
+
}
|
|
459
485
|
}
|
|
460
486
|
else {
|
|
461
487
|
// tool_call_update: update status to completed/failed, with optional content
|
|
@@ -468,6 +494,70 @@ export function startAcpServer() {
|
|
|
468
494
|
...(rawOutput !== undefined ? { rawOutput } : {}),
|
|
469
495
|
},
|
|
470
496
|
});
|
|
497
|
+
// Mark plan entry as completed
|
|
498
|
+
const entry = planEntries.get(toolCallId);
|
|
499
|
+
if (entry) {
|
|
500
|
+
entry.status = 'completed';
|
|
501
|
+
sendPlan();
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
},
|
|
505
|
+
// Only request permission in Manual mode
|
|
506
|
+
onRequestPermission: session.currentModeId === 'manual'
|
|
507
|
+
? async (toolCall) => {
|
|
508
|
+
const permToolCallId = `perm_${randomUUID()}`;
|
|
509
|
+
const result = await transport.request('session/request_permission', {
|
|
510
|
+
sessionId: params.sessionId,
|
|
511
|
+
toolCall: {
|
|
512
|
+
toolCallId: permToolCallId,
|
|
513
|
+
toolName: toolCall.tool,
|
|
514
|
+
toolInput: toolCall.parameters,
|
|
515
|
+
status: 'pending',
|
|
516
|
+
content: [],
|
|
517
|
+
},
|
|
518
|
+
options: [
|
|
519
|
+
{ optionId: 'allow_once', name: 'Allow once', kind: 'allow_once' },
|
|
520
|
+
{ optionId: 'allow_always', name: 'Allow always', kind: 'allow_always' },
|
|
521
|
+
{ optionId: 'reject_once', name: 'Reject once', kind: 'reject_once' },
|
|
522
|
+
{ optionId: 'reject_always', name: 'Reject always', kind: 'reject_always' },
|
|
523
|
+
],
|
|
524
|
+
});
|
|
525
|
+
// Map ACP outcome back to PermissionOutcome
|
|
526
|
+
if (!result || result.outcome.type === 'cancelled')
|
|
527
|
+
return 'reject_once';
|
|
528
|
+
return result.outcome.optionId;
|
|
529
|
+
}
|
|
530
|
+
: undefined,
|
|
531
|
+
onExecuteCommand: async (command, args, cwd) => {
|
|
532
|
+
try {
|
|
533
|
+
const createResult = await transport.request('terminal/create', {
|
|
534
|
+
sessionId: params.sessionId,
|
|
535
|
+
command,
|
|
536
|
+
args,
|
|
537
|
+
cwd,
|
|
538
|
+
outputByteLimit: 1_000_000,
|
|
539
|
+
});
|
|
540
|
+
const { terminalId } = createResult;
|
|
541
|
+
const waitResult = await transport.request('terminal/waitForExit', {
|
|
542
|
+
sessionId: params.sessionId,
|
|
543
|
+
terminalId,
|
|
544
|
+
timeoutMs: 120_000,
|
|
545
|
+
});
|
|
546
|
+
const outputResult = await transport.request('terminal/output', {
|
|
547
|
+
sessionId: params.sessionId,
|
|
548
|
+
terminalId,
|
|
549
|
+
});
|
|
550
|
+
await transport.request('terminal/release', {
|
|
551
|
+
sessionId: params.sessionId,
|
|
552
|
+
terminalId,
|
|
553
|
+
});
|
|
554
|
+
const exitCode = waitResult.exitStatus.type === 'exited' ? waitResult.exitStatus.code : 1;
|
|
555
|
+
return { stdout: outputResult.output ?? '', stderr: '', exitCode };
|
|
556
|
+
}
|
|
557
|
+
catch (err) {
|
|
558
|
+
// Zed terminal unavailable — fall back to local execution
|
|
559
|
+
const r = await executeCommandAsync(command, args, { cwd, projectRoot: cwd, timeout: 120000 });
|
|
560
|
+
return { stdout: r.stdout ?? '', stderr: r.stderr ?? '', exitCode: r.exitCode ?? 0 };
|
|
471
561
|
}
|
|
472
562
|
},
|
|
473
563
|
}).then(() => {
|
|
@@ -487,6 +577,14 @@ export function startAcpServer() {
|
|
|
487
577
|
if (err.name === 'AbortError') {
|
|
488
578
|
transport.respond(msg.id, { stopReason: 'cancelled' });
|
|
489
579
|
}
|
|
580
|
+
else if (err.message?.includes('API key not configured') || err.message?.includes('API key') || (err instanceof ApiError && err.status === 401)) {
|
|
581
|
+
sendChunk(`❌ No API key configured. Use /login <provider> <key> or set the environment variable (e.g. ZAI_API_KEY, ANTHROPIC_API_KEY).`);
|
|
582
|
+
transport.respond(msg.id, { stopReason: 'end_turn' });
|
|
583
|
+
}
|
|
584
|
+
else if (err instanceof ApiError && err.status >= 500) {
|
|
585
|
+
sendChunk(`⚠️ API server error (${err.status}). Please try again.`);
|
|
586
|
+
transport.respond(msg.id, { stopReason: 'end_turn' });
|
|
587
|
+
}
|
|
490
588
|
else {
|
|
491
589
|
transport.error(msg.id, -32000, err.message);
|
|
492
590
|
}
|
|
@@ -496,7 +594,17 @@ export function startAcpServer() {
|
|
|
496
594
|
});
|
|
497
595
|
})
|
|
498
596
|
.catch((err) => {
|
|
499
|
-
|
|
597
|
+
if (err.message?.includes('API key not configured') || err.message?.includes('API key') || (err instanceof ApiError && err.status === 401)) {
|
|
598
|
+
sendChunk(`❌ No API key configured. Use /login <provider> <key> or set the environment variable (e.g. ZAI_API_KEY, ANTHROPIC_API_KEY).`);
|
|
599
|
+
transport.respond(msg.id, { stopReason: 'end_turn' });
|
|
600
|
+
}
|
|
601
|
+
else if (err instanceof ApiError && err.status >= 500) {
|
|
602
|
+
sendChunk(`⚠️ API server error (${err.status}). Please try again.`);
|
|
603
|
+
transport.respond(msg.id, { stopReason: 'end_turn' });
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
transport.error(msg.id, -32000, err.message);
|
|
607
|
+
}
|
|
500
608
|
if (session)
|
|
501
609
|
session.abortController = null;
|
|
502
610
|
});
|
package/dist/acp/session.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { PermissionOutcome } from '../utils/agent.js';
|
|
1
2
|
import { ProjectContext } from '../utils/project.js';
|
|
3
|
+
import { ToolCall } from '../utils/tools.js';
|
|
2
4
|
export interface AgentSessionOptions {
|
|
3
5
|
prompt: string;
|
|
4
6
|
workspaceRoot: string;
|
|
@@ -7,6 +9,12 @@ export interface AgentSessionOptions {
|
|
|
7
9
|
onChunk: (text: string) => void;
|
|
8
10
|
onThought?: (text: string) => void;
|
|
9
11
|
onToolCall?: (toolCallId: string, toolName: string, kind: string, title: string, status: 'pending' | 'running' | 'finished' | 'error', locations?: string[], rawOutput?: string) => void;
|
|
12
|
+
onRequestPermission?: (toolCall: ToolCall) => Promise<PermissionOutcome>;
|
|
13
|
+
onExecuteCommand?: (command: string, args: string[], cwd: string) => Promise<{
|
|
14
|
+
stdout: string;
|
|
15
|
+
stderr: string;
|
|
16
|
+
exitCode: number;
|
|
17
|
+
}>;
|
|
10
18
|
}
|
|
11
19
|
/**
|
|
12
20
|
* Build a ProjectContext from a workspace root directory.
|
package/dist/acp/session.js
CHANGED
|
@@ -140,6 +140,8 @@ export async function runAgentSession(opts) {
|
|
|
140
140
|
toolCallIdMap.delete(mapKey);
|
|
141
141
|
}
|
|
142
142
|
},
|
|
143
|
+
onRequestPermission: opts.onRequestPermission,
|
|
144
|
+
onExecuteCommand: opts.onExecuteCommand,
|
|
143
145
|
});
|
|
144
146
|
// result.finalResponse is already emitted via onChunk streaming above;
|
|
145
147
|
// only emit it here if nothing was streamed (e.g. non-streaming fallback path)
|
package/dist/acp/transport.d.ts
CHANGED
|
@@ -3,11 +3,18 @@ type MessageHandler = (msg: JsonRpcRequest | JsonRpcNotification) => void;
|
|
|
3
3
|
export declare class StdioTransport {
|
|
4
4
|
private buffer;
|
|
5
5
|
private handler;
|
|
6
|
+
private pendingRequests;
|
|
7
|
+
private requestIdCounter;
|
|
6
8
|
start(handler: MessageHandler): void;
|
|
7
9
|
private onData;
|
|
8
10
|
send(msg: JsonRpcResponse | JsonRpcNotification): void;
|
|
9
11
|
respond(id: number | string, result: unknown): void;
|
|
10
12
|
error(id: number | string, code: number, message: string): void;
|
|
11
13
|
notify(method: string, params: unknown): void;
|
|
14
|
+
/**
|
|
15
|
+
* Send a JSON-RPC request to the client and wait for the response.
|
|
16
|
+
* Used for agent-initiated requests like session/request_permission.
|
|
17
|
+
*/
|
|
18
|
+
request(method: string, params: unknown): Promise<unknown>;
|
|
12
19
|
}
|
|
13
20
|
export {};
|
package/dist/acp/transport.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
export class StdioTransport {
|
|
4
4
|
buffer = '';
|
|
5
5
|
handler = null;
|
|
6
|
+
pendingRequests = new Map();
|
|
7
|
+
requestIdCounter = 1000;
|
|
6
8
|
start(handler) {
|
|
7
9
|
this.handler = handler;
|
|
8
10
|
process.stdin.setEncoding('utf8');
|
|
@@ -19,6 +21,16 @@ export class StdioTransport {
|
|
|
19
21
|
continue;
|
|
20
22
|
try {
|
|
21
23
|
const msg = JSON.parse(trimmed);
|
|
24
|
+
// Check if this is a response to one of our outbound requests
|
|
25
|
+
if ('result' in msg || 'error' in msg) {
|
|
26
|
+
const response = msg;
|
|
27
|
+
const resolve = this.pendingRequests.get(response.id);
|
|
28
|
+
if (resolve) {
|
|
29
|
+
this.pendingRequests.delete(response.id);
|
|
30
|
+
resolve(response.result ?? null);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
22
34
|
this.handler?.(msg);
|
|
23
35
|
}
|
|
24
36
|
catch {
|
|
@@ -38,4 +50,15 @@ export class StdioTransport {
|
|
|
38
50
|
notify(method, params) {
|
|
39
51
|
this.send({ jsonrpc: '2.0', method, params });
|
|
40
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Send a JSON-RPC request to the client and wait for the response.
|
|
55
|
+
* Used for agent-initiated requests like session/request_permission.
|
|
56
|
+
*/
|
|
57
|
+
request(method, params) {
|
|
58
|
+
const id = ++this.requestIdCounter;
|
|
59
|
+
return new Promise((resolve) => {
|
|
60
|
+
this.pendingRequests.set(id, resolve);
|
|
61
|
+
process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n');
|
|
62
|
+
});
|
|
63
|
+
}
|
|
41
64
|
}
|
package/dist/config/index.js
CHANGED
|
@@ -148,7 +148,7 @@ function createConfig() {
|
|
|
148
148
|
agentAutoCommitBranch: false,
|
|
149
149
|
agentAutoVerify: 'off',
|
|
150
150
|
agentMaxFixAttempts: 3,
|
|
151
|
-
agentMaxIterations:
|
|
151
|
+
agentMaxIterations: 200,
|
|
152
152
|
agentMaxDuration: 20,
|
|
153
153
|
agentApiTimeout: 180000,
|
|
154
154
|
protocol: 'openai',
|
package/dist/utils/agent.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { ToolCall, ToolResult, ActionLog } from './tools';
|
|
|
12
12
|
import { undoLastAction, undoAllActions, getCurrentSession, getRecentSessions, formatSession, ActionSession } from './history';
|
|
13
13
|
import { VerifyResult } from './verify';
|
|
14
14
|
import { TaskPlan, SubTask } from './taskPlanner';
|
|
15
|
+
export type PermissionOutcome = 'allow_once' | 'allow_always' | 'reject_once' | 'reject_always';
|
|
15
16
|
export interface AgentOptions {
|
|
16
17
|
maxIterations: number;
|
|
17
18
|
maxDuration: number;
|
|
@@ -23,6 +24,12 @@ export interface AgentOptions {
|
|
|
23
24
|
onVerification?: (results: VerifyResult[]) => void;
|
|
24
25
|
onTaskPlan?: (plan: TaskPlan) => void;
|
|
25
26
|
onTaskUpdate?: (task: SubTask) => void;
|
|
27
|
+
onRequestPermission?: (toolCall: ToolCall) => Promise<PermissionOutcome>;
|
|
28
|
+
onExecuteCommand?: (command: string, args: string[], cwd: string) => Promise<{
|
|
29
|
+
stdout: string;
|
|
30
|
+
stderr: string;
|
|
31
|
+
exitCode: number;
|
|
32
|
+
}>;
|
|
26
33
|
abortSignal?: AbortSignal;
|
|
27
34
|
dryRun?: boolean;
|
|
28
35
|
autoVerify?: 'off' | 'build' | 'typecheck' | 'test' | 'all' | boolean;
|
package/dist/utils/agent.js
CHANGED
|
@@ -11,6 +11,7 @@ const debug = (...args) => {
|
|
|
11
11
|
};
|
|
12
12
|
// Import chat layer (prompt building + API calls)
|
|
13
13
|
import { agentChat, getAgentSystemPrompt, getFallbackSystemPrompt, loadProjectRules, formatChatHistoryForAgent, } from './agentChat.js';
|
|
14
|
+
import { ApiError } from '../api/index.js';
|
|
14
15
|
export { loadProjectRules, formatChatHistoryForAgent };
|
|
15
16
|
/**
|
|
16
17
|
* Calculate dynamic timeout based on task complexity
|
|
@@ -176,6 +177,10 @@ export async function runAgent(prompt, projectContext, options = {}) {
|
|
|
176
177
|
let consecutiveTimeouts = 0;
|
|
177
178
|
let incompleteWorkRetries = 0;
|
|
178
179
|
const maxIncompleteWorkRetries = 2;
|
|
180
|
+
// Track tools permanently allowed this session via allow_always
|
|
181
|
+
const alwaysAllowedTools = new Set();
|
|
182
|
+
// Tools that require permission when onRequestPermission is set
|
|
183
|
+
const dangerousTools = new Set(['delete_file', 'execute_command']);
|
|
179
184
|
const maxTimeoutRetries = 3;
|
|
180
185
|
const maxConsecutiveTimeouts = 9; // Allow more consecutive timeouts before giving up
|
|
181
186
|
const baseTimeout = config.get('agentApiTimeout');
|
|
@@ -283,6 +288,17 @@ export async function runAgent(prompt, projectContext, options = {}) {
|
|
|
283
288
|
await new Promise(resolve => setTimeout(resolve, 1000 * retryCount));
|
|
284
289
|
continue;
|
|
285
290
|
}
|
|
291
|
+
// Don't retry on 4xx client errors except 429 (rate limit)
|
|
292
|
+
if (err instanceof ApiError && err.status >= 400 && err.status < 500 && err.status !== 429) {
|
|
293
|
+
result = {
|
|
294
|
+
success: false,
|
|
295
|
+
iterations: iteration,
|
|
296
|
+
actions,
|
|
297
|
+
finalResponse: '',
|
|
298
|
+
error: err.message,
|
|
299
|
+
};
|
|
300
|
+
return result;
|
|
301
|
+
}
|
|
286
302
|
// All non-abort errors are retryable — retry with backoff
|
|
287
303
|
retryCount++;
|
|
288
304
|
const isRateLimit = err.message.includes('429');
|
|
@@ -376,9 +392,28 @@ export async function runAgent(prompt, projectContext, options = {}) {
|
|
|
376
392
|
const toolResults = [];
|
|
377
393
|
for (const toolCall of toolCalls) {
|
|
378
394
|
opts.onToolCall?.(toolCall);
|
|
395
|
+
// Permission check for dangerous tools (only when callback is provided, e.g. ACP/Zed)
|
|
396
|
+
if (opts.onRequestPermission && dangerousTools.has(toolCall.tool) && !alwaysAllowedTools.has(toolCall.tool)) {
|
|
397
|
+
const outcome = await opts.onRequestPermission(toolCall);
|
|
398
|
+
if (outcome === 'allow_always') {
|
|
399
|
+
alwaysAllowedTools.add(toolCall.tool);
|
|
400
|
+
}
|
|
401
|
+
else if (outcome === 'reject_once' || outcome === 'reject_always') {
|
|
402
|
+
const toolResult = {
|
|
403
|
+
success: false,
|
|
404
|
+
output: '',
|
|
405
|
+
error: `User rejected permission for ${toolCall.tool}`,
|
|
406
|
+
tool: toolCall.tool,
|
|
407
|
+
parameters: toolCall.parameters,
|
|
408
|
+
};
|
|
409
|
+
opts.onToolResult?.(toolResult, toolCall);
|
|
410
|
+
actions.push(createActionLog(toolCall, toolResult));
|
|
411
|
+
toolResults.push(`Tool ${toolCall.tool} was rejected by user.`);
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
379
415
|
let toolResult;
|
|
380
416
|
if (opts.dryRun) {
|
|
381
|
-
// In dry run mode, simulate success
|
|
382
417
|
toolResult = {
|
|
383
418
|
success: true,
|
|
384
419
|
output: `[DRY RUN] Would execute: ${toolCall.tool}`,
|
|
@@ -386,8 +421,40 @@ export async function runAgent(prompt, projectContext, options = {}) {
|
|
|
386
421
|
parameters: toolCall.parameters,
|
|
387
422
|
};
|
|
388
423
|
}
|
|
424
|
+
else if (opts.onExecuteCommand && toolCall.tool === 'execute_command') {
|
|
425
|
+
// Delegate to external terminal (e.g. Zed ACP terminal)
|
|
426
|
+
// Note: onExecuteCommand runs after the permission gate above
|
|
427
|
+
const command = toolCall.parameters.command;
|
|
428
|
+
const args = toolCall.parameters.args || [];
|
|
429
|
+
const cwd = projectContext.root || process.cwd();
|
|
430
|
+
if (!command) {
|
|
431
|
+
toolResult = {
|
|
432
|
+
success: false,
|
|
433
|
+
output: '',
|
|
434
|
+
error: 'execute_command called with missing command field',
|
|
435
|
+
tool: toolCall.tool,
|
|
436
|
+
parameters: toolCall.parameters,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
try {
|
|
441
|
+
const commandResult = await opts.onExecuteCommand(command, args, cwd);
|
|
442
|
+
toolResult = {
|
|
443
|
+
success: commandResult.exitCode === 0,
|
|
444
|
+
output: commandResult.stdout || '(no output)',
|
|
445
|
+
error: commandResult.exitCode !== 0 ? (commandResult.stderr || `exited with code ${commandResult.exitCode}`) : undefined,
|
|
446
|
+
tool: toolCall.tool,
|
|
447
|
+
parameters: toolCall.parameters,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
catch (err) {
|
|
451
|
+
debug('onExecuteCommand callback threw, falling back to local execution:', err);
|
|
452
|
+
// Fallback to local execution if callback throws
|
|
453
|
+
toolResult = await executeTool(toolCall, cwd);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
389
457
|
else {
|
|
390
|
-
// Actually execute the tool
|
|
391
458
|
toolResult = await executeTool(toolCall, projectContext.root || process.cwd());
|
|
392
459
|
}
|
|
393
460
|
opts.onToolResult?.(toolResult, toolCall);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeep",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.76",
|
|
4
4
|
"description": "AI-powered coding assistant built for the terminal. Multiple LLM providers, project-aware context, and a seamless development workflow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|