codeep 1.2.35 → 1.2.37
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/acp/commands.d.ts +9 -0
- package/dist/acp/commands.js +34 -0
- package/dist/acp/protocol.d.ts +154 -12
- package/dist/acp/protocol.js +1 -1
- package/dist/acp/server.js +165 -54
- package/dist/acp/session.js +29 -0
- package/dist/acp/transport.d.ts +1 -1
- package/dist/api/index.js +1 -1
- package/dist/config/index.js +16 -8
- package/dist/config/providers.js +1 -1
- package/dist/renderer/main.js +5 -5
- package/dist/utils/agent.js +8 -2
- package/package.json +1 -1
package/dist/acp/commands.d.ts
CHANGED
|
@@ -31,6 +31,15 @@ export declare function initWorkspace(workspaceRoot: string): {
|
|
|
31
31
|
history: Message[];
|
|
32
32
|
welcomeText: string;
|
|
33
33
|
};
|
|
34
|
+
/**
|
|
35
|
+
* Restore a previously saved ACP session by its Zed sessionId.
|
|
36
|
+
* Falls back to initWorkspace if the session cannot be found on disk.
|
|
37
|
+
*/
|
|
38
|
+
export declare function loadWorkspace(workspaceRoot: string, acpSessionId: string): {
|
|
39
|
+
codeepSessionId: string;
|
|
40
|
+
history: Message[];
|
|
41
|
+
welcomeText: string;
|
|
42
|
+
};
|
|
34
43
|
/**
|
|
35
44
|
* Try to handle a slash command. Async because skills and diff/review
|
|
36
45
|
* need to call the AI API or run shell commands.
|
package/dist/acp/commands.js
CHANGED
|
@@ -74,6 +74,40 @@ export function initWorkspace(workspaceRoot) {
|
|
|
74
74
|
}
|
|
75
75
|
return { codeepSessionId, history, welcomeText: lines.join('\n') };
|
|
76
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Restore a previously saved ACP session by its Zed sessionId.
|
|
79
|
+
* Falls back to initWorkspace if the session cannot be found on disk.
|
|
80
|
+
*/
|
|
81
|
+
export function loadWorkspace(workspaceRoot, acpSessionId) {
|
|
82
|
+
// Ensure workspace is set up
|
|
83
|
+
const codeepDir = join(workspaceRoot, '.codeep');
|
|
84
|
+
if (!existsSync(codeepDir)) {
|
|
85
|
+
mkdirSync(codeepDir, { recursive: true });
|
|
86
|
+
}
|
|
87
|
+
if (!isManuallyInitializedProject(workspaceRoot)) {
|
|
88
|
+
initializeAsProject(workspaceRoot);
|
|
89
|
+
}
|
|
90
|
+
if (!hasReadPermission(workspaceRoot)) {
|
|
91
|
+
setProjectPermission(workspaceRoot, true, true);
|
|
92
|
+
}
|
|
93
|
+
// Try to load the session that was saved under this ACP session ID
|
|
94
|
+
const loaded = loadSession(acpSessionId, workspaceRoot);
|
|
95
|
+
if (loaded) {
|
|
96
|
+
const history = loaded;
|
|
97
|
+
const provider = getCurrentProvider();
|
|
98
|
+
const model = config.get('model');
|
|
99
|
+
const lines = [
|
|
100
|
+
`**Codeep** • ${provider.name} • \`${model}\``,
|
|
101
|
+
'',
|
|
102
|
+
`**Session restored:** ${acpSessionId} (${history.length} messages)`,
|
|
103
|
+
'',
|
|
104
|
+
...formatSessionPreviewLines(history),
|
|
105
|
+
];
|
|
106
|
+
return { codeepSessionId: acpSessionId, history, welcomeText: lines.join('\n') };
|
|
107
|
+
}
|
|
108
|
+
// Session not found — fall back to initWorkspace behaviour
|
|
109
|
+
return initWorkspace(workspaceRoot);
|
|
110
|
+
}
|
|
77
111
|
// ─── Command dispatch ─────────────────────────────────────────────────────────
|
|
78
112
|
/**
|
|
79
113
|
* Try to handle a slash command. Async because skills and diff/review
|
package/dist/acp/protocol.d.ts
CHANGED
|
@@ -19,15 +19,23 @@ export interface JsonRpcNotification {
|
|
|
19
19
|
params?: unknown;
|
|
20
20
|
}
|
|
21
21
|
export interface InitializeParams {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
protocolVersion?: number;
|
|
23
|
+
clientCapabilities?: {
|
|
24
|
+
fs?: {
|
|
25
|
+
readTextFile?: boolean;
|
|
26
|
+
writeTextFile?: boolean;
|
|
27
|
+
};
|
|
28
|
+
terminal?: boolean;
|
|
29
|
+
};
|
|
30
|
+
clientInfo?: {
|
|
25
31
|
name: string;
|
|
26
|
-
|
|
32
|
+
version: string;
|
|
33
|
+
};
|
|
27
34
|
}
|
|
28
35
|
export interface InitializeResult {
|
|
29
36
|
protocolVersion: number;
|
|
30
37
|
agentCapabilities: {
|
|
38
|
+
loadSession?: boolean;
|
|
31
39
|
streaming?: boolean;
|
|
32
40
|
fileEditing?: boolean;
|
|
33
41
|
};
|
|
@@ -37,21 +45,155 @@ export interface InitializeResult {
|
|
|
37
45
|
};
|
|
38
46
|
authMethods: unknown[];
|
|
39
47
|
}
|
|
40
|
-
export interface
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
export interface McpServer {
|
|
49
|
+
name: string;
|
|
50
|
+
command: string;
|
|
51
|
+
args: string[];
|
|
52
|
+
env?: Record<string, string>;
|
|
53
|
+
}
|
|
54
|
+
export interface SessionMode {
|
|
55
|
+
id: string;
|
|
56
|
+
name: string;
|
|
57
|
+
description?: string | null;
|
|
58
|
+
}
|
|
59
|
+
export interface SessionModeState {
|
|
60
|
+
availableModes: SessionMode[];
|
|
61
|
+
currentModeId: string;
|
|
62
|
+
}
|
|
63
|
+
export interface SessionConfigOption {
|
|
64
|
+
id: string;
|
|
65
|
+
name: string;
|
|
66
|
+
description?: string | null;
|
|
67
|
+
category?: 'mode' | 'model' | 'thought_level' | null;
|
|
68
|
+
type: 'select';
|
|
69
|
+
options?: {
|
|
70
|
+
id: string;
|
|
43
71
|
name: string;
|
|
44
|
-
command: string;
|
|
45
|
-
args: string[];
|
|
46
|
-
env?: Record<string, string>;
|
|
47
72
|
}[];
|
|
73
|
+
currentValue?: string;
|
|
74
|
+
}
|
|
75
|
+
export interface SessionNewParams {
|
|
76
|
+
cwd: string;
|
|
77
|
+
mcpServers?: McpServer[];
|
|
78
|
+
}
|
|
79
|
+
export interface SessionNewResult {
|
|
80
|
+
sessionId: string;
|
|
81
|
+
modes?: SessionModeState | null;
|
|
82
|
+
configOptions?: SessionConfigOption[] | null;
|
|
83
|
+
}
|
|
84
|
+
export interface SessionLoadParams {
|
|
85
|
+
sessionId: string;
|
|
86
|
+
cwd: string;
|
|
87
|
+
mcpServers?: McpServer[];
|
|
88
|
+
}
|
|
89
|
+
export interface SessionLoadResult {
|
|
90
|
+
modes?: SessionModeState | null;
|
|
91
|
+
configOptions?: SessionConfigOption[] | null;
|
|
48
92
|
}
|
|
49
93
|
export interface ContentBlock {
|
|
50
|
-
type: 'text';
|
|
51
|
-
text
|
|
94
|
+
type: 'text' | 'image' | 'audio' | 'resource_link' | 'resource';
|
|
95
|
+
text?: string;
|
|
96
|
+
data?: string;
|
|
97
|
+
mimeType?: string;
|
|
98
|
+
uri?: string;
|
|
99
|
+
name?: string;
|
|
52
100
|
}
|
|
53
101
|
export interface SessionPromptParams {
|
|
54
102
|
sessionId: string;
|
|
55
103
|
prompt: ContentBlock[];
|
|
56
104
|
}
|
|
105
|
+
export interface SessionPromptResult {
|
|
106
|
+
stopReason: 'end_turn' | 'cancelled';
|
|
107
|
+
}
|
|
108
|
+
export interface SessionCancelParams {
|
|
109
|
+
sessionId: string;
|
|
110
|
+
}
|
|
111
|
+
export interface SetSessionModeParams {
|
|
112
|
+
sessionId: string;
|
|
113
|
+
modeId: string;
|
|
114
|
+
}
|
|
115
|
+
export interface SetSessionConfigOptionParams {
|
|
116
|
+
sessionId: string;
|
|
117
|
+
configId: string;
|
|
118
|
+
value: unknown;
|
|
119
|
+
}
|
|
120
|
+
export type ToolCallState = 'running' | 'finished' | 'error';
|
|
121
|
+
export interface SessionUpdateContentChunk {
|
|
122
|
+
type: 'content_chunk';
|
|
123
|
+
sessionId: string;
|
|
124
|
+
content: ContentBlock;
|
|
125
|
+
}
|
|
126
|
+
export interface SessionUpdateToolCall {
|
|
127
|
+
type: 'tool_call';
|
|
128
|
+
sessionId: string;
|
|
129
|
+
toolCallId: string;
|
|
130
|
+
toolName: string;
|
|
131
|
+
toolInput: unknown;
|
|
132
|
+
state: ToolCallState;
|
|
133
|
+
content?: {
|
|
134
|
+
type: 'text';
|
|
135
|
+
text: string;
|
|
136
|
+
}[];
|
|
137
|
+
}
|
|
138
|
+
export interface SessionUpdateThoughtChunk {
|
|
139
|
+
type: 'agent_thought_chunk';
|
|
140
|
+
sessionId: string;
|
|
141
|
+
content: ContentBlock;
|
|
142
|
+
}
|
|
143
|
+
export interface SessionUpdateAvailableCommands {
|
|
144
|
+
type: 'available_commands_update';
|
|
145
|
+
sessionId: string;
|
|
146
|
+
availableCommands: {
|
|
147
|
+
name: string;
|
|
148
|
+
description: string;
|
|
149
|
+
input?: {
|
|
150
|
+
hint: string;
|
|
151
|
+
};
|
|
152
|
+
}[];
|
|
153
|
+
}
|
|
154
|
+
export interface SessionUpdateCurrentMode {
|
|
155
|
+
type: 'current_mode_update';
|
|
156
|
+
sessionId: string;
|
|
157
|
+
currentModeId: string;
|
|
158
|
+
}
|
|
159
|
+
export type SessionUpdateParams = SessionUpdateContentChunk | SessionUpdateToolCall | SessionUpdateThoughtChunk | SessionUpdateAvailableCommands | SessionUpdateCurrentMode;
|
|
160
|
+
export type PermissionOptionKind = 'allow_once' | 'allow_always' | 'reject_once' | 'reject_always';
|
|
161
|
+
export interface PermissionOption {
|
|
162
|
+
optionId: string;
|
|
163
|
+
name: string;
|
|
164
|
+
kind: PermissionOptionKind;
|
|
165
|
+
}
|
|
166
|
+
export interface RequestPermissionParams {
|
|
167
|
+
sessionId: string;
|
|
168
|
+
toolCall: {
|
|
169
|
+
toolCallId: string;
|
|
170
|
+
toolName: string;
|
|
171
|
+
toolInput: unknown;
|
|
172
|
+
state: ToolCallState;
|
|
173
|
+
content: unknown[];
|
|
174
|
+
};
|
|
175
|
+
options: PermissionOption[];
|
|
176
|
+
}
|
|
177
|
+
export interface RequestPermissionResult {
|
|
178
|
+
outcome: {
|
|
179
|
+
type: 'cancelled';
|
|
180
|
+
} | {
|
|
181
|
+
type: 'selected';
|
|
182
|
+
optionId: string;
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
export interface FsReadTextFileParams {
|
|
186
|
+
sessionId: string;
|
|
187
|
+
path: string;
|
|
188
|
+
line?: number;
|
|
189
|
+
limit?: number;
|
|
190
|
+
}
|
|
191
|
+
export interface FsReadTextFileResult {
|
|
192
|
+
content: string;
|
|
193
|
+
}
|
|
194
|
+
export interface FsWriteTextFileParams {
|
|
195
|
+
sessionId: string;
|
|
196
|
+
path: string;
|
|
197
|
+
content: string;
|
|
198
|
+
}
|
|
57
199
|
export type AcpMessage = JsonRpcRequest | JsonRpcResponse | JsonRpcNotification;
|
package/dist/acp/protocol.js
CHANGED
package/dist/acp/server.js
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
import { randomUUID } from 'crypto';
|
|
4
4
|
import { StdioTransport } from './transport.js';
|
|
5
5
|
import { runAgentSession } from './session.js';
|
|
6
|
-
import { initWorkspace, handleCommand } from './commands.js';
|
|
7
|
-
import { autoSaveSession } from '../config/index.js';
|
|
8
|
-
|
|
6
|
+
import { initWorkspace, loadWorkspace, handleCommand } from './commands.js';
|
|
7
|
+
import { autoSaveSession, config } from '../config/index.js';
|
|
8
|
+
import { getCurrentVersion } from '../utils/update.js';
|
|
9
|
+
// ─── Slash commands advertised to Zed ────────────────────────────────────────
|
|
9
10
|
const AVAILABLE_COMMANDS = [
|
|
10
11
|
// Configuration
|
|
11
12
|
{ name: 'help', description: 'Show available commands' },
|
|
@@ -48,51 +49,92 @@ const AVAILABLE_COMMANDS = [
|
|
|
48
49
|
{ name: 'build', description: 'Build the project' },
|
|
49
50
|
{ name: 'deploy', description: 'Deploy the project' },
|
|
50
51
|
];
|
|
52
|
+
// ─── Mode definitions ─────────────────────────────────────────────────────────
|
|
53
|
+
const AGENT_MODES = {
|
|
54
|
+
currentModeId: 'auto',
|
|
55
|
+
availableModes: [
|
|
56
|
+
{ id: 'auto', name: 'Auto', description: 'Agent runs automatically without confirmation' },
|
|
57
|
+
{ id: 'manual', name: 'Manual', description: 'Confirm dangerous operations before running' },
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
// ─── Config options ───────────────────────────────────────────────────────────
|
|
61
|
+
function buildConfigOptions() {
|
|
62
|
+
return [
|
|
63
|
+
{
|
|
64
|
+
id: 'model',
|
|
65
|
+
name: 'Model',
|
|
66
|
+
description: 'AI model to use',
|
|
67
|
+
category: 'model',
|
|
68
|
+
type: 'select',
|
|
69
|
+
currentValue: config.get('model'),
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
// ─── Server ───────────────────────────────────────────────────────────────────
|
|
51
74
|
export function startAcpServer() {
|
|
52
75
|
const transport = new StdioTransport();
|
|
53
76
|
// ACP sessionId → full AcpSession (includes history + codeep session tracking)
|
|
54
77
|
const sessions = new Map();
|
|
55
78
|
transport.start((msg) => {
|
|
56
|
-
|
|
79
|
+
// Notifications have no id — handle separately
|
|
80
|
+
if (!('id' in msg)) {
|
|
81
|
+
handleNotification(msg);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const req = msg;
|
|
85
|
+
switch (req.method) {
|
|
57
86
|
case 'initialize':
|
|
58
|
-
handleInitialize(
|
|
59
|
-
break;
|
|
60
|
-
case 'initialized':
|
|
61
|
-
// no-op acknowledgment
|
|
87
|
+
handleInitialize(req);
|
|
62
88
|
break;
|
|
89
|
+
case 'initialized': /* no-op acknowledgment */ break;
|
|
63
90
|
case 'session/new':
|
|
64
|
-
handleSessionNew(
|
|
91
|
+
handleSessionNew(req);
|
|
92
|
+
break;
|
|
93
|
+
case 'session/load':
|
|
94
|
+
handleSessionLoad(req);
|
|
65
95
|
break;
|
|
66
96
|
case 'session/prompt':
|
|
67
|
-
handleSessionPrompt(
|
|
97
|
+
handleSessionPrompt(req);
|
|
98
|
+
break;
|
|
99
|
+
case 'session/set_mode':
|
|
100
|
+
handleSetMode(req);
|
|
68
101
|
break;
|
|
69
|
-
case 'session/
|
|
70
|
-
|
|
102
|
+
case 'session/set_config_option':
|
|
103
|
+
handleSetConfigOption(req);
|
|
71
104
|
break;
|
|
72
105
|
default:
|
|
73
|
-
transport.error(
|
|
106
|
+
transport.error(req.id, -32601, `Method not found: ${req.method}`);
|
|
74
107
|
}
|
|
75
108
|
});
|
|
109
|
+
// ── Notification handler (no id, no response) ──────────────────────────────
|
|
110
|
+
function handleNotification(msg) {
|
|
111
|
+
if (msg.method === 'session/cancel') {
|
|
112
|
+
const { sessionId } = (msg.params ?? {});
|
|
113
|
+
sessions.get(sessionId)?.abortController?.abort();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// ── initialize ──────────────────────────────────────────────────────────────
|
|
76
117
|
function handleInitialize(msg) {
|
|
77
118
|
const _params = msg.params;
|
|
78
119
|
const result = {
|
|
79
120
|
protocolVersion: 1,
|
|
80
121
|
agentCapabilities: {
|
|
122
|
+
loadSession: true,
|
|
81
123
|
streaming: true,
|
|
82
124
|
fileEditing: true,
|
|
83
125
|
},
|
|
84
126
|
agentInfo: {
|
|
85
127
|
name: 'codeep',
|
|
86
|
-
version:
|
|
128
|
+
version: getCurrentVersion(),
|
|
87
129
|
},
|
|
88
130
|
authMethods: [],
|
|
89
131
|
};
|
|
90
132
|
transport.respond(msg.id, result);
|
|
91
133
|
}
|
|
134
|
+
// ── session/new ─────────────────────────────────────────────────────────────
|
|
92
135
|
function handleSessionNew(msg) {
|
|
93
136
|
const params = msg.params;
|
|
94
137
|
const acpSessionId = randomUUID();
|
|
95
|
-
// Initialise workspace: create .codeep folder, load/create codeep session
|
|
96
138
|
const { codeepSessionId, history, welcomeText } = initWorkspace(params.cwd);
|
|
97
139
|
sessions.set(acpSessionId, {
|
|
98
140
|
sessionId: acpSessionId,
|
|
@@ -101,25 +143,103 @@ export function startAcpServer() {
|
|
|
101
143
|
codeepSessionId,
|
|
102
144
|
addedFiles: new Map(),
|
|
103
145
|
abortController: null,
|
|
146
|
+
currentModeId: 'auto',
|
|
104
147
|
});
|
|
105
|
-
|
|
106
|
-
|
|
148
|
+
const result = {
|
|
149
|
+
sessionId: acpSessionId,
|
|
150
|
+
modes: AGENT_MODES,
|
|
151
|
+
configOptions: buildConfigOptions(),
|
|
152
|
+
};
|
|
153
|
+
transport.respond(msg.id, result);
|
|
154
|
+
// Advertise slash commands
|
|
107
155
|
transport.notify('session/update', {
|
|
156
|
+
type: 'available_commands_update',
|
|
108
157
|
sessionId: acpSessionId,
|
|
109
|
-
|
|
110
|
-
sessionUpdate: 'available_commands_update',
|
|
111
|
-
availableCommands: AVAILABLE_COMMANDS,
|
|
112
|
-
},
|
|
158
|
+
availableCommands: AVAILABLE_COMMANDS,
|
|
113
159
|
});
|
|
114
|
-
//
|
|
160
|
+
// Send welcome message
|
|
115
161
|
transport.notify('session/update', {
|
|
162
|
+
type: 'content_chunk',
|
|
116
163
|
sessionId: acpSessionId,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
164
|
+
content: { type: 'text', text: welcomeText },
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
// ── session/load ────────────────────────────────────────────────────────────
|
|
168
|
+
function handleSessionLoad(msg) {
|
|
169
|
+
const params = msg.params;
|
|
170
|
+
// Try to restore existing Codeep session or fall back to fresh workspace
|
|
171
|
+
const existing = sessions.get(params.sessionId);
|
|
172
|
+
if (existing) {
|
|
173
|
+
// Session already in memory — update cwd if changed
|
|
174
|
+
existing.workspaceRoot = params.cwd;
|
|
175
|
+
const result = {
|
|
176
|
+
modes: AGENT_MODES,
|
|
177
|
+
configOptions: buildConfigOptions(),
|
|
178
|
+
};
|
|
179
|
+
transport.respond(msg.id, result);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
// Session not in memory — try to load from disk
|
|
183
|
+
const { codeepSessionId, history, welcomeText } = loadWorkspace(params.cwd, params.sessionId);
|
|
184
|
+
sessions.set(params.sessionId, {
|
|
185
|
+
sessionId: params.sessionId,
|
|
186
|
+
workspaceRoot: params.cwd,
|
|
187
|
+
history,
|
|
188
|
+
codeepSessionId,
|
|
189
|
+
addedFiles: new Map(),
|
|
190
|
+
abortController: null,
|
|
191
|
+
currentModeId: 'auto',
|
|
121
192
|
});
|
|
193
|
+
const result = {
|
|
194
|
+
modes: AGENT_MODES,
|
|
195
|
+
configOptions: buildConfigOptions(),
|
|
196
|
+
};
|
|
197
|
+
transport.respond(msg.id, result);
|
|
198
|
+
// Send restored session welcome
|
|
199
|
+
transport.notify('session/update', {
|
|
200
|
+
type: 'content_chunk',
|
|
201
|
+
sessionId: params.sessionId,
|
|
202
|
+
content: { type: 'text', text: welcomeText },
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
// ── session/set_mode ────────────────────────────────────────────────────────
|
|
206
|
+
function handleSetMode(msg) {
|
|
207
|
+
const { sessionId, modeId } = msg.params;
|
|
208
|
+
const session = sessions.get(sessionId);
|
|
209
|
+
if (!session) {
|
|
210
|
+
transport.error(msg.id, -32602, `Unknown sessionId: ${sessionId}`);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const validMode = AGENT_MODES.availableModes.find(m => m.id === modeId);
|
|
214
|
+
if (!validMode) {
|
|
215
|
+
transport.error(msg.id, -32602, `Unknown modeId: ${modeId}`);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
session.currentModeId = modeId;
|
|
219
|
+
// Map ACP mode to Codeep agentConfirmation setting
|
|
220
|
+
config.set('agentConfirmation', modeId === 'manual' ? 'dangerous' : 'never');
|
|
221
|
+
transport.respond(msg.id, {});
|
|
222
|
+
// Notify Zed of the mode change
|
|
223
|
+
transport.notify('session/update', {
|
|
224
|
+
type: 'current_mode_update',
|
|
225
|
+
sessionId,
|
|
226
|
+
currentModeId: modeId,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
// ── session/set_config_option ───────────────────────────────────────────────
|
|
230
|
+
function handleSetConfigOption(msg) {
|
|
231
|
+
const { sessionId, configId, value } = msg.params;
|
|
232
|
+
const session = sessions.get(sessionId);
|
|
233
|
+
if (!session) {
|
|
234
|
+
transport.error(msg.id, -32602, `Unknown sessionId: ${sessionId}`);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (configId === 'model' && typeof value === 'string') {
|
|
238
|
+
config.set('model', value);
|
|
239
|
+
}
|
|
240
|
+
transport.respond(msg.id, {});
|
|
122
241
|
}
|
|
242
|
+
// ── session/prompt ──────────────────────────────────────────────────────────
|
|
123
243
|
function handleSessionPrompt(msg) {
|
|
124
244
|
const params = msg.params;
|
|
125
245
|
const session = sessions.get(params.sessionId);
|
|
@@ -130,32 +250,29 @@ export function startAcpServer() {
|
|
|
130
250
|
// Extract text from ContentBlock[]
|
|
131
251
|
const prompt = params.prompt
|
|
132
252
|
.filter((b) => b.type === 'text')
|
|
133
|
-
.map((b) => b.text)
|
|
253
|
+
.map((b) => b.text ?? '')
|
|
134
254
|
.join('\n');
|
|
135
255
|
const abortController = new AbortController();
|
|
136
256
|
session.abortController = abortController;
|
|
257
|
+
const agentResponseChunks = [];
|
|
137
258
|
const sendChunk = (text) => {
|
|
259
|
+
agentResponseChunks.push(text);
|
|
138
260
|
transport.notify('session/update', {
|
|
261
|
+
type: 'content_chunk',
|
|
139
262
|
sessionId: params.sessionId,
|
|
140
|
-
|
|
141
|
-
sessionUpdate: 'agent_message_chunk',
|
|
142
|
-
content: { type: 'text', text },
|
|
143
|
-
},
|
|
263
|
+
content: { type: 'text', text },
|
|
144
264
|
});
|
|
145
265
|
};
|
|
146
|
-
// Try slash commands first
|
|
266
|
+
// Try slash commands first
|
|
147
267
|
handleCommand(prompt, session, sendChunk, abortController.signal)
|
|
148
268
|
.then((cmd) => {
|
|
149
269
|
if (cmd.handled) {
|
|
150
|
-
// For streaming commands (skills, diff), chunks were already sent via onChunk.
|
|
151
|
-
// For simple commands, send the response now.
|
|
152
270
|
if (cmd.response)
|
|
153
271
|
sendChunk(cmd.response);
|
|
154
272
|
transport.respond(msg.id, { stopReason: 'end_turn' });
|
|
155
273
|
return;
|
|
156
274
|
}
|
|
157
275
|
// Not a command — run agent loop
|
|
158
|
-
// Prepend any added-files context to the prompt
|
|
159
276
|
let enrichedPrompt = prompt;
|
|
160
277
|
if (session.addedFiles.size > 0) {
|
|
161
278
|
const parts = ['[Attached files]'];
|
|
@@ -172,28 +289,23 @@ export function startAcpServer() {
|
|
|
172
289
|
onChunk: sendChunk,
|
|
173
290
|
onThought: (text) => {
|
|
174
291
|
transport.notify('session/update', {
|
|
292
|
+
type: 'agent_thought_chunk',
|
|
175
293
|
sessionId: params.sessionId,
|
|
176
|
-
|
|
177
|
-
sessionUpdate: 'agent_thought_chunk',
|
|
178
|
-
content: { type: 'text', text },
|
|
179
|
-
},
|
|
294
|
+
content: { type: 'text', text },
|
|
180
295
|
});
|
|
181
296
|
},
|
|
182
|
-
onToolCall: (toolCallId,
|
|
297
|
+
onToolCall: (toolCallId, toolName, _kind, _title, status, _locations) => {
|
|
183
298
|
transport.notify('session/update', {
|
|
299
|
+
type: 'tool_call',
|
|
184
300
|
sessionId: params.sessionId,
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
status,
|
|
191
|
-
...(locations?.length ? { locations: locations.map(uri => ({ uri })) } : {}),
|
|
192
|
-
},
|
|
301
|
+
toolCallId,
|
|
302
|
+
toolName,
|
|
303
|
+
toolInput: {},
|
|
304
|
+
state: status === 'running' ? 'running' : status === 'finished' ? 'finished' : 'error',
|
|
305
|
+
content: [],
|
|
193
306
|
});
|
|
194
307
|
},
|
|
195
308
|
onFileEdit: (uri, newText) => {
|
|
196
|
-
// ACP structured file/edit notification — lets the editor apply changes
|
|
197
309
|
transport.notify('file/edit', {
|
|
198
310
|
uri,
|
|
199
311
|
textChanges: newText
|
|
@@ -203,6 +315,10 @@ export function startAcpServer() {
|
|
|
203
315
|
},
|
|
204
316
|
}).then(() => {
|
|
205
317
|
session.history.push({ role: 'user', content: prompt });
|
|
318
|
+
const agentResponse = agentResponseChunks.join('');
|
|
319
|
+
if (agentResponse) {
|
|
320
|
+
session.history.push({ role: 'assistant', content: agentResponse });
|
|
321
|
+
}
|
|
206
322
|
autoSaveSession(session.history, session.workspaceRoot);
|
|
207
323
|
transport.respond(msg.id, { stopReason: 'end_turn' });
|
|
208
324
|
}).catch((err) => {
|
|
@@ -223,11 +339,6 @@ export function startAcpServer() {
|
|
|
223
339
|
session.abortController = null;
|
|
224
340
|
});
|
|
225
341
|
}
|
|
226
|
-
function handleSessionCancel(msg) {
|
|
227
|
-
const { sessionId } = msg.params;
|
|
228
|
-
sessions.get(sessionId)?.abortController?.abort();
|
|
229
|
-
transport.respond(msg.id, {});
|
|
230
|
-
}
|
|
231
342
|
// Keep process alive until stdin closes (Zed terminates us)
|
|
232
343
|
return new Promise((resolve) => {
|
|
233
344
|
process.stdin.on('end', resolve);
|
package/dist/acp/session.js
CHANGED
|
@@ -44,6 +44,8 @@ function toolCallMeta(toolName, params) {
|
|
|
44
44
|
export async function runAgentSession(opts) {
|
|
45
45
|
const projectContext = buildProjectContext(opts.workspaceRoot);
|
|
46
46
|
let toolCallCounter = 0;
|
|
47
|
+
// Maps tool call key → ACP toolCallId so onToolResult can emit finished/error status
|
|
48
|
+
const toolCallIdMap = new Map();
|
|
47
49
|
const result = await runAgent(opts.prompt, projectContext, {
|
|
48
50
|
abortSignal: opts.abortSignal,
|
|
49
51
|
onIteration: (_iteration, _message) => {
|
|
@@ -68,6 +70,9 @@ export async function runAgentSession(opts) {
|
|
|
68
70
|
: join(opts.workspaceRoot, filePath);
|
|
69
71
|
locations.push(pathToFileURL(absPath).href);
|
|
70
72
|
}
|
|
73
|
+
// Track this tool call so onToolResult can emit finished/error
|
|
74
|
+
const mapKey = toolCall.id ?? `${name}_${toolCallCounter}`;
|
|
75
|
+
toolCallIdMap.set(mapKey, { toolCallId, kind, locations: locations.length ? locations : undefined });
|
|
71
76
|
// Emit tool_call notification (running state)
|
|
72
77
|
opts.onToolCall?.(toolCallId, name, kind, title, 'running', locations.length ? locations : undefined);
|
|
73
78
|
// For file edits, also send structured file/edit notification
|
|
@@ -81,6 +86,30 @@ export async function runAgentSession(opts) {
|
|
|
81
86
|
}
|
|
82
87
|
}
|
|
83
88
|
},
|
|
89
|
+
onToolResult: (toolResult, toolCall) => {
|
|
90
|
+
// Find the tracked entry: prefer exact id match, then first FIFO entry for same tool name
|
|
91
|
+
let mapKey;
|
|
92
|
+
if (toolCall.id && toolCallIdMap.has(toolCall.id)) {
|
|
93
|
+
mapKey = toolCall.id;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// FIFO: find oldest pending entry for this tool name
|
|
97
|
+
for (const [k, v] of toolCallIdMap) {
|
|
98
|
+
if (k.startsWith(`${toolCall.tool}_`) && v.toolCallId) {
|
|
99
|
+
mapKey = k;
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (mapKey !== undefined) {
|
|
105
|
+
const tracked = toolCallIdMap.get(mapKey);
|
|
106
|
+
if (tracked && opts.onToolCall) {
|
|
107
|
+
const status = toolResult.success ? 'finished' : 'error';
|
|
108
|
+
opts.onToolCall(tracked.toolCallId, toolCall.tool, tracked.kind, '', status, tracked.locations);
|
|
109
|
+
}
|
|
110
|
+
toolCallIdMap.delete(mapKey);
|
|
111
|
+
}
|
|
112
|
+
},
|
|
84
113
|
});
|
|
85
114
|
// Emit the final response text if present
|
|
86
115
|
if (result.finalResponse) {
|
package/dist/acp/transport.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { JsonRpcRequest, JsonRpcResponse, JsonRpcNotification } from './protocol.js';
|
|
2
|
-
type MessageHandler = (msg: JsonRpcRequest) => void;
|
|
2
|
+
type MessageHandler = (msg: JsonRpcRequest | JsonRpcNotification) => void;
|
|
3
3
|
export declare class StdioTransport {
|
|
4
4
|
private buffer;
|
|
5
5
|
private handler;
|
package/dist/api/index.js
CHANGED
package/dist/config/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Conf from 'conf';
|
|
2
2
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, unlinkSync, statSync } from 'fs';
|
|
3
3
|
import { join, dirname } from 'path';
|
|
4
|
+
import { randomUUID } from 'crypto';
|
|
4
5
|
import { PROVIDERS, getProvider } from './providers.js';
|
|
5
6
|
import { logSession } from '../utils/logger.js';
|
|
6
7
|
// We'll initialize GLOBAL_SESSIONS_DIR after config is created (to use config.path)
|
|
@@ -389,8 +390,17 @@ export function isConfigured(providerId) {
|
|
|
389
390
|
}
|
|
390
391
|
// Get current provider info
|
|
391
392
|
export function getCurrentProvider() {
|
|
392
|
-
|
|
393
|
-
|
|
393
|
+
let providerId = config.get('provider');
|
|
394
|
+
let provider = getProvider(providerId);
|
|
395
|
+
// If stored provider no longer exists in registry, fall back to first available
|
|
396
|
+
if (!provider) {
|
|
397
|
+
const fallback = Object.keys(PROVIDERS)[0];
|
|
398
|
+
if (fallback) {
|
|
399
|
+
providerId = fallback;
|
|
400
|
+
provider = getProvider(fallback);
|
|
401
|
+
config.set('provider', fallback);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
394
404
|
return {
|
|
395
405
|
id: providerId,
|
|
396
406
|
name: provider?.name || providerId,
|
|
@@ -406,7 +416,7 @@ export function setProvider(providerId) {
|
|
|
406
416
|
config.set('protocol', provider.defaultProtocol);
|
|
407
417
|
// Load API key for the new provider into cache
|
|
408
418
|
// This is async but we fire-and-forget since the key will be loaded before next API call
|
|
409
|
-
loadApiKey(providerId);
|
|
419
|
+
loadApiKey(providerId).catch(() => { });
|
|
410
420
|
return true;
|
|
411
421
|
}
|
|
412
422
|
// Get models for current provider
|
|
@@ -425,10 +435,8 @@ export function getModelsForCurrentProvider() {
|
|
|
425
435
|
export { PROVIDERS } from './providers.js';
|
|
426
436
|
// Generate unique session ID
|
|
427
437
|
function generateSessionId() {
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
const time = now.toTimeString().split(' ')[0].replace(/:/g, '-');
|
|
431
|
-
return `session-${date}-${time}`;
|
|
438
|
+
const date = new Date().toISOString().split('T')[0];
|
|
439
|
+
return `session-${date}-${randomUUID().slice(0, 8)}`;
|
|
432
440
|
}
|
|
433
441
|
// Get or create current session ID
|
|
434
442
|
export function getCurrentSessionId() {
|
|
@@ -636,7 +644,7 @@ export function getProjectPermission(projectPath) {
|
|
|
636
644
|
if (configPath && existsSync(configPath)) {
|
|
637
645
|
try {
|
|
638
646
|
const data = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
639
|
-
if (data.permission)
|
|
647
|
+
if (data.permission && typeof data.permission === 'object')
|
|
640
648
|
return data.permission;
|
|
641
649
|
}
|
|
642
650
|
catch {
|
package/dist/config/providers.js
CHANGED
|
@@ -207,7 +207,7 @@ export function supportsNativeTools(providerId, protocol) {
|
|
|
207
207
|
const provider = PROVIDERS[providerId];
|
|
208
208
|
if (!provider)
|
|
209
209
|
return false;
|
|
210
|
-
return provider.protocols[protocol]?.supportsNativeTools ??
|
|
210
|
+
return provider.protocols[protocol]?.supportsNativeTools ?? false; // Default to false (safer)
|
|
211
211
|
}
|
|
212
212
|
/**
|
|
213
213
|
* Returns the effective max output tokens for a provider, capped by the provider's limit.
|
package/dist/renderer/main.js
CHANGED
|
@@ -129,17 +129,17 @@ async function handleSubmit(message) {
|
|
|
129
129
|
executeAgentTask(enhancedTask, dryRun, ctx);
|
|
130
130
|
return;
|
|
131
131
|
}
|
|
132
|
+
const rateCheck = checkApiRateLimit();
|
|
133
|
+
if (!rateCheck.allowed) {
|
|
134
|
+
app.notify(rateCheck.message || 'Rate limit exceeded', 5000);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
132
137
|
// Auto agent mode
|
|
133
138
|
const agentMode = config.get('agentMode') || 'off';
|
|
134
139
|
if (agentMode === 'on' && projectContext && hasWriteAccess && !isAgentRunningFlag) {
|
|
135
140
|
runAgentTask(message, false, ctx, () => pendingInteractiveContext, (v) => { pendingInteractiveContext = v; });
|
|
136
141
|
return;
|
|
137
142
|
}
|
|
138
|
-
const rateCheck = checkApiRateLimit();
|
|
139
|
-
if (!rateCheck.allowed) {
|
|
140
|
-
app.notify(rateCheck.message || 'Rate limit exceeded', 5000);
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
143
|
try {
|
|
144
144
|
app.startStreaming();
|
|
145
145
|
const history = app.getChatHistory();
|
package/dist/utils/agent.js
CHANGED
|
@@ -124,6 +124,8 @@ export async function runAgent(prompt, projectContext, options = {}) {
|
|
|
124
124
|
let finalResponse = '';
|
|
125
125
|
let result;
|
|
126
126
|
let consecutiveTimeouts = 0;
|
|
127
|
+
let incompleteWorkRetries = 0;
|
|
128
|
+
const maxIncompleteWorkRetries = 2;
|
|
127
129
|
const maxTimeoutRetries = 3;
|
|
128
130
|
const maxConsecutiveTimeouts = 9; // Allow more consecutive timeouts before giving up
|
|
129
131
|
const baseTimeout = config.get('agentApiTimeout');
|
|
@@ -159,7 +161,7 @@ export async function runAgent(prompt, projectContext, options = {}) {
|
|
|
159
161
|
const dynamicTimeout = calculateDynamicTimeout(prompt, iteration, baseTimeout);
|
|
160
162
|
debug(`Using timeout: ${dynamicTimeout}ms (base: ${baseTimeout}ms)`);
|
|
161
163
|
// Get AI response with retry logic for timeouts
|
|
162
|
-
let chatResponse;
|
|
164
|
+
let chatResponse = null;
|
|
163
165
|
let retryCount = 0;
|
|
164
166
|
while (true) {
|
|
165
167
|
try {
|
|
@@ -248,9 +250,11 @@ export async function runAgent(prompt, projectContext, options = {}) {
|
|
|
248
250
|
const wantsToContinue = continueIndicators.some(indicator => lowerResponse.includes(indicator));
|
|
249
251
|
// Also check if there were tool call parsing failures in this iteration
|
|
250
252
|
// by looking for incomplete actions (e.g., write_file without content)
|
|
251
|
-
const hasIncompleteWork =
|
|
253
|
+
const hasIncompleteWork = wantsToContinue && finalResponse.length < 500
|
|
254
|
+
&& incompleteWorkRetries < maxIncompleteWorkRetries;
|
|
252
255
|
if (hasIncompleteWork) {
|
|
253
256
|
debug('Model wants to continue, prompting for next action');
|
|
257
|
+
incompleteWorkRetries++;
|
|
254
258
|
messages.push({ role: 'assistant', content });
|
|
255
259
|
messages.push({
|
|
256
260
|
role: 'user',
|
|
@@ -258,6 +262,8 @@ export async function runAgent(prompt, projectContext, options = {}) {
|
|
|
258
262
|
});
|
|
259
263
|
continue;
|
|
260
264
|
}
|
|
265
|
+
// Reset counter once model produces real output or we give up
|
|
266
|
+
incompleteWorkRetries = 0;
|
|
261
267
|
// Model is done
|
|
262
268
|
debug(`Agent finished at iteration ${iteration}`);
|
|
263
269
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeep",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.37",
|
|
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",
|