wave-code 0.10.0 → 0.10.1
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/agent.d.ts +15 -1
- package/dist/acp/agent.d.ts.map +1 -1
- package/dist/acp/agent.js +430 -36
- package/dist/components/ChatInterface.js +1 -1
- package/dist/contexts/useChat.d.ts.map +1 -1
- package/dist/contexts/useChat.js +0 -3
- package/package.json +3 -3
- package/src/acp/agent.ts +534 -64
- package/src/components/ChatInterface.tsx +1 -1
- package/src/contexts/useChat.tsx +0 -3
package/dist/acp/agent.d.ts
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Agent as AcpAgent, type AgentSideConnection, type InitializeResponse, type NewSessionRequest, type NewSessionResponse, type LoadSessionRequest, type LoadSessionResponse, type ListSessionsRequest, type ListSessionsResponse, type PromptRequest, type PromptResponse, type CancelNotification, type AuthenticateResponse, type SetSessionModeRequest, type SetSessionConfigOptionRequest, type SetSessionConfigOptionResponse } from "@agentclientprotocol/sdk";
|
|
2
2
|
export declare class WaveAcpAgent implements AcpAgent {
|
|
3
3
|
private agents;
|
|
4
4
|
private connection;
|
|
5
5
|
constructor(connection: AgentSideConnection);
|
|
6
|
+
private getSessionModeState;
|
|
7
|
+
private getSessionConfigOptions;
|
|
8
|
+
private cleanupAllAgents;
|
|
6
9
|
initialize(): Promise<InitializeResponse>;
|
|
7
10
|
authenticate(): Promise<AuthenticateResponse | void>;
|
|
11
|
+
private createAgent;
|
|
8
12
|
newSession(params: NewSessionRequest): Promise<NewSessionResponse>;
|
|
9
13
|
loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse>;
|
|
14
|
+
listSessions(params: ListSessionsRequest): Promise<ListSessionsResponse>;
|
|
15
|
+
unstable_closeSession(params: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
16
|
+
extMethod(method: string, params: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
17
|
+
setSessionMode(params: SetSessionModeRequest): Promise<void>;
|
|
18
|
+
setSessionConfigOption(params: SetSessionConfigOptionRequest): Promise<SetSessionConfigOptionResponse>;
|
|
10
19
|
prompt(params: PromptRequest): Promise<PromptResponse>;
|
|
11
20
|
cancel(params: CancelNotification): Promise<void>;
|
|
21
|
+
private handlePermissionRequest;
|
|
22
|
+
private getToolContentAsync;
|
|
23
|
+
private getToolContent;
|
|
24
|
+
private getToolLocations;
|
|
25
|
+
private getToolKind;
|
|
12
26
|
private createCallbacks;
|
|
13
27
|
}
|
|
14
28
|
//# sourceMappingURL=agent.d.ts.map
|
package/dist/acp/agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/acp/agent.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/acp/agent.ts"],"names":[],"mappings":"AAaA,OAAO,EACL,KAAK,KAAK,IAAI,QAAQ,EACtB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EAUzB,KAAK,qBAAqB,EAC1B,KAAK,6BAA6B,EAClC,KAAK,8BAA8B,EAEpC,MAAM,0BAA0B,CAAC;AAElC,qBAAa,YAAa,YAAW,QAAQ;IAC3C,OAAO,CAAC,MAAM,CAAqC;IACnD,OAAO,CAAC,UAAU,CAAsB;gBAE5B,UAAU,EAAE,mBAAmB;IAI3C,OAAO,CAAC,mBAAmB;IA4B3B,OAAO,CAAC,uBAAuB;YAkBjB,gBAAgB;IASxB,UAAU,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAoBzC,YAAY,IAAI,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;YAI5C,WAAW;IA8CnB,UAAU,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IA8BlE,WAAW,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA4BrE,YAAY,CAChB,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,oBAAoB,CAAC;IAmB1B,qBAAqB,CACzB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAc7B,SAAS,CACb,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAO7B,cAAc,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAS5D,sBAAsB,CAC1B,MAAM,EAAE,6BAA6B,GACpC,OAAO,CAAC,8BAA8B,CAAC;IAgBpC,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;IAiDtD,MAAM,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;YASzC,uBAAuB;YAsGvB,mBAAmB;IA2EjC,OAAO,CAAC,cAAc;IA4BtB,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,WAAW;IAmBnB,OAAO,CAAC,eAAe;CAsIxB"}
|
package/dist/acp/agent.js
CHANGED
|
@@ -1,12 +1,67 @@
|
|
|
1
|
-
import { Agent as WaveAgent } from "wave-agent-sdk";
|
|
1
|
+
import { Agent as WaveAgent, listSessions as listWaveSessions, deleteSession as deleteWaveSession, } from "wave-agent-sdk";
|
|
2
|
+
import * as fs from "node:fs/promises";
|
|
3
|
+
import * as path from "node:path";
|
|
2
4
|
import { logger } from "../utils/logger.js";
|
|
5
|
+
import { AGENT_METHODS, } from "@agentclientprotocol/sdk";
|
|
3
6
|
export class WaveAcpAgent {
|
|
4
7
|
constructor(connection) {
|
|
5
8
|
this.agents = new Map();
|
|
6
9
|
this.connection = connection;
|
|
7
10
|
}
|
|
11
|
+
getSessionModeState(agent) {
|
|
12
|
+
return {
|
|
13
|
+
currentModeId: agent.getPermissionMode(),
|
|
14
|
+
availableModes: [
|
|
15
|
+
{
|
|
16
|
+
id: "default",
|
|
17
|
+
name: "Default",
|
|
18
|
+
description: "Ask for permission for restricted tools",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "acceptEdits",
|
|
22
|
+
name: "Accept Edits",
|
|
23
|
+
description: "Automatically accept file edits",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: "plan",
|
|
27
|
+
name: "Plan",
|
|
28
|
+
description: "Plan mode for complex tasks",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "bypassPermissions",
|
|
32
|
+
name: "Bypass Permissions",
|
|
33
|
+
description: "Automatically accept all tool calls",
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
getSessionConfigOptions(agent) {
|
|
39
|
+
return [
|
|
40
|
+
{
|
|
41
|
+
id: "permission_mode",
|
|
42
|
+
name: "Permission Mode",
|
|
43
|
+
type: "select",
|
|
44
|
+
category: "mode",
|
|
45
|
+
currentValue: agent.getPermissionMode(),
|
|
46
|
+
options: [
|
|
47
|
+
{ value: "default", name: "Default" },
|
|
48
|
+
{ value: "acceptEdits", name: "Accept Edits" },
|
|
49
|
+
{ value: "plan", name: "Plan" },
|
|
50
|
+
{ value: "bypassPermissions", name: "Bypass Permissions" },
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
async cleanupAllAgents() {
|
|
56
|
+
logger.info("Cleaning up all active agents due to connection closure");
|
|
57
|
+
const destroyPromises = Array.from(this.agents.values()).map((agent) => agent.destroy());
|
|
58
|
+
await Promise.all(destroyPromises);
|
|
59
|
+
this.agents.clear();
|
|
60
|
+
}
|
|
8
61
|
async initialize() {
|
|
9
62
|
logger.info("Initializing WaveAcpAgent");
|
|
63
|
+
// Setup cleanup on connection closure
|
|
64
|
+
this.connection.closed.then(() => this.cleanupAllAgents());
|
|
10
65
|
return {
|
|
11
66
|
protocolVersion: 1,
|
|
12
67
|
agentInfo: {
|
|
@@ -15,18 +70,28 @@ export class WaveAcpAgent {
|
|
|
15
70
|
},
|
|
16
71
|
agentCapabilities: {
|
|
17
72
|
loadSession: true,
|
|
73
|
+
sessionCapabilities: {
|
|
74
|
+
list: {},
|
|
75
|
+
close: {},
|
|
76
|
+
},
|
|
18
77
|
},
|
|
19
78
|
};
|
|
20
79
|
}
|
|
21
80
|
async authenticate() {
|
|
22
81
|
// No authentication required for now
|
|
23
82
|
}
|
|
24
|
-
async
|
|
25
|
-
const { cwd } = params;
|
|
26
|
-
logger.info(`Creating new session in ${cwd}`);
|
|
83
|
+
async createAgent(sessionId, cwd) {
|
|
27
84
|
const callbacks = {};
|
|
85
|
+
const agentRef = {};
|
|
28
86
|
const agent = await WaveAgent.create({
|
|
29
87
|
workdir: cwd,
|
|
88
|
+
restoreSessionId: sessionId,
|
|
89
|
+
canUseTool: (context) => {
|
|
90
|
+
if (!agentRef.instance) {
|
|
91
|
+
throw new Error("Agent instance not yet initialized");
|
|
92
|
+
}
|
|
93
|
+
return this.handlePermissionRequest(agentRef.instance.sessionId, context);
|
|
94
|
+
},
|
|
30
95
|
callbacks: {
|
|
31
96
|
onAssistantContentUpdated: (chunk) => callbacks.onAssistantContentUpdated?.(chunk, ""),
|
|
32
97
|
onAssistantReasoningUpdated: (chunk) => callbacks.onAssistantReasoningUpdated?.(chunk, ""),
|
|
@@ -34,46 +99,124 @@ export class WaveAcpAgent {
|
|
|
34
99
|
const cb = callbacks.onToolBlockUpdated;
|
|
35
100
|
cb?.(params);
|
|
36
101
|
},
|
|
37
|
-
onTasksChange: (tasks) =>
|
|
38
|
-
|
|
39
|
-
cb?.(tasks);
|
|
40
|
-
},
|
|
102
|
+
onTasksChange: (tasks) => callbacks.onTasksChange?.(tasks),
|
|
103
|
+
onPermissionModeChange: (mode) => callbacks.onPermissionModeChange?.(mode),
|
|
41
104
|
},
|
|
42
105
|
});
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
this.agents.set(
|
|
106
|
+
agentRef.instance = agent;
|
|
107
|
+
const actualSessionId = agent.sessionId;
|
|
108
|
+
this.agents.set(actualSessionId, agent);
|
|
46
109
|
// Update the callbacks object with the correct sessionId
|
|
47
|
-
Object.assign(callbacks, this.createCallbacks(
|
|
110
|
+
Object.assign(callbacks, this.createCallbacks(actualSessionId));
|
|
111
|
+
return agent;
|
|
112
|
+
}
|
|
113
|
+
async newSession(params) {
|
|
114
|
+
const { cwd } = params;
|
|
115
|
+
logger.info(`Creating new session in ${cwd}`);
|
|
116
|
+
const agent = await this.createAgent(undefined, cwd);
|
|
117
|
+
logger.info(`New session created with ID: ${agent.sessionId}`);
|
|
118
|
+
// Send initial available commands after agent creation
|
|
119
|
+
setImmediate(() => {
|
|
120
|
+
this.connection.sessionUpdate({
|
|
121
|
+
sessionId: agent.sessionId,
|
|
122
|
+
update: {
|
|
123
|
+
sessionUpdate: "available_commands_update",
|
|
124
|
+
availableCommands: agent.getSlashCommands().map((cmd) => ({
|
|
125
|
+
name: cmd.name,
|
|
126
|
+
description: cmd.description,
|
|
127
|
+
input: {
|
|
128
|
+
hint: "Enter arguments...",
|
|
129
|
+
},
|
|
130
|
+
})),
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
});
|
|
48
134
|
return {
|
|
49
|
-
sessionId: sessionId,
|
|
135
|
+
sessionId: agent.sessionId,
|
|
136
|
+
modes: this.getSessionModeState(agent),
|
|
137
|
+
configOptions: this.getSessionConfigOptions(agent),
|
|
50
138
|
};
|
|
51
139
|
}
|
|
52
140
|
async loadSession(params) {
|
|
53
|
-
const { sessionId } = params;
|
|
54
|
-
logger.info(`Loading session: ${sessionId}`);
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
141
|
+
const { sessionId, cwd } = params;
|
|
142
|
+
logger.info(`Loading session: ${sessionId} in ${cwd}`);
|
|
143
|
+
const agent = await this.createAgent(sessionId, cwd);
|
|
144
|
+
// Send initial available commands after agent creation
|
|
145
|
+
setImmediate(() => {
|
|
146
|
+
this.connection.sessionUpdate({
|
|
147
|
+
sessionId: agent.sessionId,
|
|
148
|
+
update: {
|
|
149
|
+
sessionUpdate: "available_commands_update",
|
|
150
|
+
availableCommands: agent.getSlashCommands().map((cmd) => ({
|
|
151
|
+
name: cmd.name,
|
|
152
|
+
description: cmd.description,
|
|
153
|
+
input: {
|
|
154
|
+
hint: "Enter arguments...",
|
|
155
|
+
},
|
|
156
|
+
})),
|
|
68
157
|
},
|
|
69
|
-
}
|
|
158
|
+
});
|
|
70
159
|
});
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
160
|
+
return {
|
|
161
|
+
modes: this.getSessionModeState(agent),
|
|
162
|
+
configOptions: this.getSessionConfigOptions(agent),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
async listSessions(params) {
|
|
166
|
+
const { cwd } = params;
|
|
167
|
+
logger.info(`listSessions called with params: ${JSON.stringify(params)}`);
|
|
168
|
+
if (!cwd) {
|
|
169
|
+
logger.warn("listSessions called without cwd, returning empty list");
|
|
170
|
+
return { sessions: [] };
|
|
171
|
+
}
|
|
172
|
+
logger.info(`Listing sessions for ${cwd}`);
|
|
173
|
+
const waveSessions = await listWaveSessions(cwd);
|
|
174
|
+
logger.info(`Found ${waveSessions.length} sessions for ${cwd}`);
|
|
175
|
+
const sessions = waveSessions.map((meta) => ({
|
|
176
|
+
sessionId: meta.id,
|
|
177
|
+
cwd: meta.workdir,
|
|
178
|
+
updatedAt: meta.lastActiveAt.toISOString(),
|
|
179
|
+
}));
|
|
180
|
+
return { sessions };
|
|
181
|
+
}
|
|
182
|
+
async unstable_closeSession(params) {
|
|
183
|
+
const sessionId = params.sessionId;
|
|
184
|
+
logger.info(`Stopping session ${sessionId}`);
|
|
185
|
+
const agent = this.agents.get(sessionId);
|
|
186
|
+
if (agent) {
|
|
187
|
+
const workdir = agent.workingDirectory;
|
|
188
|
+
await agent.destroy();
|
|
189
|
+
this.agents.delete(sessionId);
|
|
190
|
+
// Delete the session file so it doesn't show up in listSessions
|
|
191
|
+
await deleteWaveSession(sessionId, workdir);
|
|
192
|
+
}
|
|
75
193
|
return {};
|
|
76
194
|
}
|
|
195
|
+
async extMethod(method, params) {
|
|
196
|
+
if (method === AGENT_METHODS.session_close) {
|
|
197
|
+
return this.unstable_closeSession(params);
|
|
198
|
+
}
|
|
199
|
+
throw new Error(`Method ${method} not implemented`);
|
|
200
|
+
}
|
|
201
|
+
async setSessionMode(params) {
|
|
202
|
+
const { sessionId, modeId } = params;
|
|
203
|
+
const agent = this.agents.get(sessionId);
|
|
204
|
+
if (!agent)
|
|
205
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
206
|
+
agent.setPermissionMode(modeId);
|
|
207
|
+
}
|
|
208
|
+
async setSessionConfigOption(params) {
|
|
209
|
+
const { sessionId, configId, value } = params;
|
|
210
|
+
const agent = this.agents.get(sessionId);
|
|
211
|
+
if (!agent)
|
|
212
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
213
|
+
if (configId === "permission_mode") {
|
|
214
|
+
agent.setPermissionMode(value);
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
configOptions: this.getSessionConfigOptions(agent),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
77
220
|
async prompt(params) {
|
|
78
221
|
const { sessionId, prompt } = params;
|
|
79
222
|
logger.info(`Received prompt for session ${sessionId}`);
|
|
@@ -99,8 +242,6 @@ export class WaveAcpAgent {
|
|
|
99
242
|
try {
|
|
100
243
|
logger.info(`Sending message to agent: ${textContent.substring(0, 50)}...`);
|
|
101
244
|
await agent.sendMessage(textContent, images.length > 0 ? images : undefined);
|
|
102
|
-
// Force save session so it can be loaded later
|
|
103
|
-
await agent.messageManager.saveSession();
|
|
104
245
|
logger.info(`Message sent successfully for session ${sessionId}`);
|
|
105
246
|
return {
|
|
106
247
|
stopReason: "end_turn",
|
|
@@ -125,7 +266,217 @@ export class WaveAcpAgent {
|
|
|
125
266
|
agent.abortMessage();
|
|
126
267
|
}
|
|
127
268
|
}
|
|
269
|
+
async handlePermissionRequest(sessionId, context) {
|
|
270
|
+
logger.info(`Handling permission request for ${context.toolName} in session ${sessionId}`);
|
|
271
|
+
const agent = this.agents.get(sessionId);
|
|
272
|
+
const workdir = agent?.workingDirectory || process.cwd();
|
|
273
|
+
const toolCallId = context.toolCallId ||
|
|
274
|
+
"perm-" + Math.random().toString(36).substring(2, 9);
|
|
275
|
+
const options = [
|
|
276
|
+
{
|
|
277
|
+
optionId: "allow_once",
|
|
278
|
+
name: "Allow Once",
|
|
279
|
+
kind: "allow_once",
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
optionId: "allow_always",
|
|
283
|
+
name: "Allow Always",
|
|
284
|
+
kind: "allow_always",
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
optionId: "reject_once",
|
|
288
|
+
name: "Reject Once",
|
|
289
|
+
kind: "reject_once",
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
optionId: "reject_always",
|
|
293
|
+
name: "Reject Always",
|
|
294
|
+
kind: "reject_always",
|
|
295
|
+
},
|
|
296
|
+
];
|
|
297
|
+
const content = context.toolName
|
|
298
|
+
? await this.getToolContentAsync(context.toolName, context.toolInput, workdir)
|
|
299
|
+
: undefined;
|
|
300
|
+
const locations = context.toolName
|
|
301
|
+
? this.getToolLocations(context.toolName, context.toolInput)
|
|
302
|
+
: undefined;
|
|
303
|
+
const kind = context.toolName
|
|
304
|
+
? this.getToolKind(context.toolName)
|
|
305
|
+
: undefined;
|
|
306
|
+
try {
|
|
307
|
+
const response = await this.connection.requestPermission({
|
|
308
|
+
sessionId: sessionId,
|
|
309
|
+
toolCall: {
|
|
310
|
+
toolCallId,
|
|
311
|
+
title: `Permission for ${context.toolName}`,
|
|
312
|
+
status: "pending",
|
|
313
|
+
rawInput: context.toolInput,
|
|
314
|
+
content,
|
|
315
|
+
locations,
|
|
316
|
+
kind,
|
|
317
|
+
},
|
|
318
|
+
options,
|
|
319
|
+
});
|
|
320
|
+
if (response.outcome.outcome === "cancelled") {
|
|
321
|
+
return { behavior: "deny", message: "Cancelled by user" };
|
|
322
|
+
}
|
|
323
|
+
const selectedOptionId = response.outcome.optionId;
|
|
324
|
+
logger.info(`User selected permission option: ${selectedOptionId}`);
|
|
325
|
+
switch (selectedOptionId) {
|
|
326
|
+
case "allow_once":
|
|
327
|
+
return { behavior: "allow" };
|
|
328
|
+
case "allow_always":
|
|
329
|
+
return {
|
|
330
|
+
behavior: "allow",
|
|
331
|
+
newPermissionRule: `${context.toolName}(*)`,
|
|
332
|
+
};
|
|
333
|
+
case "reject_once":
|
|
334
|
+
return { behavior: "deny", message: "Rejected by user" };
|
|
335
|
+
case "reject_always":
|
|
336
|
+
return {
|
|
337
|
+
behavior: "deny",
|
|
338
|
+
message: "Rejected by user",
|
|
339
|
+
newPermissionRule: `!${context.toolName}(*)`,
|
|
340
|
+
};
|
|
341
|
+
default:
|
|
342
|
+
return { behavior: "deny", message: "Unknown option selected" };
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
logger.error("Error requesting permission via ACP:", error);
|
|
347
|
+
return {
|
|
348
|
+
behavior: "deny",
|
|
349
|
+
message: `Error requesting permission: ${error instanceof Error ? error.message : String(error)}`,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
async getToolContentAsync(name, parameters, workdir) {
|
|
354
|
+
if (!parameters)
|
|
355
|
+
return undefined;
|
|
356
|
+
if (name === "Write") {
|
|
357
|
+
let oldText = null;
|
|
358
|
+
try {
|
|
359
|
+
const filePath = parameters.file_path;
|
|
360
|
+
const fullPath = path.isAbsolute(filePath)
|
|
361
|
+
? filePath
|
|
362
|
+
: path.join(workdir, filePath);
|
|
363
|
+
oldText = await fs.readFile(fullPath, "utf-8");
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
// File might not exist, which is fine for Write
|
|
367
|
+
}
|
|
368
|
+
return [
|
|
369
|
+
{
|
|
370
|
+
type: "diff",
|
|
371
|
+
path: parameters.file_path,
|
|
372
|
+
oldText,
|
|
373
|
+
newText: parameters.content,
|
|
374
|
+
},
|
|
375
|
+
];
|
|
376
|
+
}
|
|
377
|
+
if (name === "Edit") {
|
|
378
|
+
let oldText = null;
|
|
379
|
+
let newText = null;
|
|
380
|
+
try {
|
|
381
|
+
const filePath = parameters.file_path;
|
|
382
|
+
const fullPath = path.isAbsolute(filePath)
|
|
383
|
+
? filePath
|
|
384
|
+
: path.join(workdir, filePath);
|
|
385
|
+
oldText = await fs.readFile(fullPath, "utf-8");
|
|
386
|
+
if (oldText) {
|
|
387
|
+
if (parameters.replace_all) {
|
|
388
|
+
newText = oldText
|
|
389
|
+
.split(parameters.old_string)
|
|
390
|
+
.join(parameters.new_string);
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
newText = oldText.replace(parameters.old_string, parameters.new_string);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
logger.error("Failed to read file for Edit diff");
|
|
399
|
+
}
|
|
400
|
+
if (oldText && newText) {
|
|
401
|
+
return [
|
|
402
|
+
{
|
|
403
|
+
type: "diff",
|
|
404
|
+
path: parameters.file_path,
|
|
405
|
+
oldText,
|
|
406
|
+
newText,
|
|
407
|
+
},
|
|
408
|
+
];
|
|
409
|
+
}
|
|
410
|
+
// Fallback to snippets if file reading fails
|
|
411
|
+
return [
|
|
412
|
+
{
|
|
413
|
+
type: "diff",
|
|
414
|
+
path: parameters.file_path,
|
|
415
|
+
oldText: parameters.old_string,
|
|
416
|
+
newText: parameters.new_string,
|
|
417
|
+
},
|
|
418
|
+
];
|
|
419
|
+
}
|
|
420
|
+
return this.getToolContent(name, parameters);
|
|
421
|
+
}
|
|
422
|
+
getToolContent(name, parameters) {
|
|
423
|
+
if (!parameters)
|
|
424
|
+
return undefined;
|
|
425
|
+
if (name === "Write") {
|
|
426
|
+
return [
|
|
427
|
+
{
|
|
428
|
+
type: "diff",
|
|
429
|
+
path: parameters.file_path,
|
|
430
|
+
oldText: null,
|
|
431
|
+
newText: parameters.content,
|
|
432
|
+
},
|
|
433
|
+
];
|
|
434
|
+
}
|
|
435
|
+
if (name === "Edit") {
|
|
436
|
+
return [
|
|
437
|
+
{
|
|
438
|
+
type: "diff",
|
|
439
|
+
path: parameters.file_path,
|
|
440
|
+
oldText: parameters.old_string,
|
|
441
|
+
newText: parameters.new_string,
|
|
442
|
+
},
|
|
443
|
+
];
|
|
444
|
+
}
|
|
445
|
+
return undefined;
|
|
446
|
+
}
|
|
447
|
+
getToolLocations(name, parameters) {
|
|
448
|
+
if (!parameters)
|
|
449
|
+
return undefined;
|
|
450
|
+
if (name === "Write" || name === "Edit" || name === "Read") {
|
|
451
|
+
return [
|
|
452
|
+
{
|
|
453
|
+
path: parameters.file_path,
|
|
454
|
+
line: parameters.offset,
|
|
455
|
+
},
|
|
456
|
+
];
|
|
457
|
+
}
|
|
458
|
+
return undefined;
|
|
459
|
+
}
|
|
460
|
+
getToolKind(name) {
|
|
461
|
+
switch (name) {
|
|
462
|
+
case "Read":
|
|
463
|
+
case "Glob":
|
|
464
|
+
case "Grep":
|
|
465
|
+
case "LSP":
|
|
466
|
+
return "read";
|
|
467
|
+
case "Write":
|
|
468
|
+
case "Edit":
|
|
469
|
+
return "edit";
|
|
470
|
+
case "Bash":
|
|
471
|
+
return "execute";
|
|
472
|
+
case "Agent":
|
|
473
|
+
return "other";
|
|
474
|
+
default:
|
|
475
|
+
return "other";
|
|
476
|
+
}
|
|
477
|
+
}
|
|
128
478
|
createCallbacks(sessionId) {
|
|
479
|
+
const getAgent = () => this.agents.get(sessionId);
|
|
129
480
|
return {
|
|
130
481
|
onAssistantContentUpdated: (chunk) => {
|
|
131
482
|
this.connection.sessionUpdate({
|
|
@@ -152,7 +503,23 @@ export class WaveAcpAgent {
|
|
|
152
503
|
});
|
|
153
504
|
},
|
|
154
505
|
onToolBlockUpdated: (params) => {
|
|
155
|
-
const { id, name, stage, success, error, result } = params;
|
|
506
|
+
const { id, name, stage, success, error, result, parameters } = params;
|
|
507
|
+
let parsedParameters = undefined;
|
|
508
|
+
if (parameters) {
|
|
509
|
+
try {
|
|
510
|
+
parsedParameters = JSON.parse(parameters);
|
|
511
|
+
}
|
|
512
|
+
catch {
|
|
513
|
+
// Ignore parse errors during streaming
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
const content = name && parsedParameters
|
|
517
|
+
? this.getToolContent(name, parsedParameters)
|
|
518
|
+
: undefined;
|
|
519
|
+
const locations = name && parsedParameters
|
|
520
|
+
? this.getToolLocations(name, parsedParameters)
|
|
521
|
+
: undefined;
|
|
522
|
+
const kind = name ? this.getToolKind(name) : undefined;
|
|
156
523
|
if (stage === "start") {
|
|
157
524
|
this.connection.sessionUpdate({
|
|
158
525
|
sessionId: sessionId,
|
|
@@ -161,6 +528,10 @@ export class WaveAcpAgent {
|
|
|
161
528
|
toolCallId: id,
|
|
162
529
|
title: name || "Tool Call",
|
|
163
530
|
status: "pending",
|
|
531
|
+
content,
|
|
532
|
+
locations,
|
|
533
|
+
kind,
|
|
534
|
+
rawInput: parsedParameters,
|
|
164
535
|
},
|
|
165
536
|
});
|
|
166
537
|
return;
|
|
@@ -184,6 +555,10 @@ export class WaveAcpAgent {
|
|
|
184
555
|
status,
|
|
185
556
|
title: name || "Tool Call",
|
|
186
557
|
rawOutput: result || error,
|
|
558
|
+
content,
|
|
559
|
+
locations,
|
|
560
|
+
kind,
|
|
561
|
+
rawInput: parsedParameters,
|
|
187
562
|
},
|
|
188
563
|
});
|
|
189
564
|
},
|
|
@@ -204,6 +579,25 @@ export class WaveAcpAgent {
|
|
|
204
579
|
},
|
|
205
580
|
});
|
|
206
581
|
},
|
|
582
|
+
onPermissionModeChange: (mode) => {
|
|
583
|
+
this.connection.sessionUpdate({
|
|
584
|
+
sessionId: sessionId,
|
|
585
|
+
update: {
|
|
586
|
+
sessionUpdate: "current_mode_update",
|
|
587
|
+
currentModeId: mode,
|
|
588
|
+
},
|
|
589
|
+
});
|
|
590
|
+
const agent = getAgent();
|
|
591
|
+
if (agent) {
|
|
592
|
+
this.connection.sessionUpdate({
|
|
593
|
+
sessionId: sessionId,
|
|
594
|
+
update: {
|
|
595
|
+
sessionUpdate: "config_option_update",
|
|
596
|
+
configOptions: this.getSessionConfigOptions(agent),
|
|
597
|
+
},
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
},
|
|
207
601
|
};
|
|
208
602
|
}
|
|
209
603
|
}
|
|
@@ -39,7 +39,7 @@ export const ChatInterface = () => {
|
|
|
39
39
|
}
|
|
40
40
|
const terminalHeight = stdout?.rows || 24;
|
|
41
41
|
const totalHeight = detailsHeight + selectorHeight + dynamicBlocksHeight;
|
|
42
|
-
if (totalHeight > terminalHeight) {
|
|
42
|
+
if (totalHeight > terminalHeight - 3) {
|
|
43
43
|
setIsConfirmationTooTall(true);
|
|
44
44
|
}
|
|
45
45
|
}, [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useChat.d.ts","sourceRoot":"","sources":["../../src/contexts/useChat.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"useChat.d.ts","sourceRoot":"","sources":["../../src/contexts/useChat.tsx"],"names":[],"mappings":"AAAA,OAAO,KAON,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EACV,OAAO,EACP,eAAe,EACf,cAAc,EACd,IAAI,EACJ,YAAY,EACZ,kBAAkB,EAClB,cAAc,EACf,MAAM,gBAAgB,CAAC;AAUxB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC;IAEvB,UAAU,EAAE,OAAO,CAAC;IACpB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,oBAAoB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACjD,cAAc,EAAE,KAAK,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KACpD,CAAC,CAAC;IAEH,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,CACX,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,KAC/C,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,IAAI,CAAC;IAE1B,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,gBAAgB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3D,mBAAmB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAE9D,eAAe,EAAE,cAAc,EAAE,CAAC;IAElC,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,uBAAuB,EAAE,CACvB,MAAM,EAAE,MAAM,KACX;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC/D,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;IAEhD,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,eAAe,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;IAEhD,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IAC5C,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE7C,cAAc,EAAE,cAAc,CAAC;IAC/B,iBAAiB,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAElD,qBAAqB,EAAE,OAAO,CAAC;IAC/B,cAAc,CAAC,EAAE;QACf,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAChC,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,oBAAoB,CAAC,EAAE,OAAO,CAAC;KAChC,CAAC;IACF,gBAAgB,EAAE,CAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,eAAe,CAAC,EAAE,MAAM,EACxB,oBAAoB,CAAC,EAAE,OAAO,KAC3B,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACjC,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,0BAA0B,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACnE,wBAAwB,EAAE,MAAM,IAAI,CAAC;IAErC,qBAAqB,EAAE,MAAM,IAAI,CAAC;IAElC,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,oBAAoB,EAAE,MAAM,OAAO,CAAC;QAClC,QAAQ,EAAE,OAAO,EAAE,CAAC;QACpB,UAAU,EAAE,MAAM,EAAE,CAAC;KACtB,CAAC,CAAC;IACH,qBAAqB,EAAE,MAAM,CAAC;IAC9B,wBAAwB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;IAEvE,gBAAgB,EAAE,MAAM,OAAO,gBAAgB,EAAE,aAAa,CAAC;IAC/D,cAAc,EAAE,MAAM,OAAO,gBAAgB,EAAE,WAAW,CAAC;IAC3D,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID,eAAO,MAAM,OAAO,uBAMnB,CAAC;AAEF,MAAM,WAAW,iBAAkB,SAAQ,YAAY;IACrD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAsiBpD,CAAC"}
|
package/dist/contexts/useChat.js
CHANGED
|
@@ -113,9 +113,6 @@ export const ChatProvider = ({ children, bypassPermissions, pluginDirs, tools, w
|
|
|
113
113
|
onPermissionModeChange: (mode) => {
|
|
114
114
|
setPermissionModeState(mode);
|
|
115
115
|
},
|
|
116
|
-
onSlashCommandsChange: (commands) => {
|
|
117
|
-
setSlashCommands([...commands]);
|
|
118
|
-
},
|
|
119
116
|
};
|
|
120
117
|
try {
|
|
121
118
|
// Create the permission callback inside the try block to access showConfirmation
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wave-code",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "CLI-based code assistant powered by AI, built with React and Ink",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"react": "^19.2.4",
|
|
40
40
|
"react-dom": "19.2.4",
|
|
41
41
|
"yargs": "^17.7.2",
|
|
42
|
-
"@agentclientprotocol/sdk": "0.
|
|
42
|
+
"@agentclientprotocol/sdk": "0.16.1",
|
|
43
43
|
"zod": "^3.23.8",
|
|
44
|
-
"wave-agent-sdk": "0.10.
|
|
44
|
+
"wave-agent-sdk": "0.10.1"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/react": "^19.1.8",
|
package/src/acp/agent.ts
CHANGED
|
@@ -1,20 +1,43 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Agent as WaveAgent,
|
|
3
|
+
AgentOptions,
|
|
4
|
+
PermissionDecision,
|
|
5
|
+
ToolPermissionContext,
|
|
6
|
+
AgentToolBlockUpdateParams,
|
|
7
|
+
Task,
|
|
8
|
+
listSessions as listWaveSessions,
|
|
9
|
+
deleteSession as deleteWaveSession,
|
|
10
|
+
} from "wave-agent-sdk";
|
|
11
|
+
import * as fs from "node:fs/promises";
|
|
12
|
+
import * as path from "node:path";
|
|
2
13
|
import { logger } from "../utils/logger.js";
|
|
3
|
-
import
|
|
4
|
-
Agent as AcpAgent,
|
|
5
|
-
AgentSideConnection,
|
|
6
|
-
InitializeResponse,
|
|
7
|
-
NewSessionRequest,
|
|
8
|
-
NewSessionResponse,
|
|
9
|
-
LoadSessionRequest,
|
|
10
|
-
LoadSessionResponse,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
import {
|
|
15
|
+
type Agent as AcpAgent,
|
|
16
|
+
type AgentSideConnection,
|
|
17
|
+
type InitializeResponse,
|
|
18
|
+
type NewSessionRequest,
|
|
19
|
+
type NewSessionResponse,
|
|
20
|
+
type LoadSessionRequest,
|
|
21
|
+
type LoadSessionResponse,
|
|
22
|
+
type ListSessionsRequest,
|
|
23
|
+
type ListSessionsResponse,
|
|
24
|
+
type PromptRequest,
|
|
25
|
+
type PromptResponse,
|
|
26
|
+
type CancelNotification,
|
|
27
|
+
type AuthenticateResponse,
|
|
28
|
+
type SessionId as AcpSessionId,
|
|
29
|
+
type ToolCallStatus,
|
|
30
|
+
type StopReason,
|
|
31
|
+
type PermissionOption,
|
|
32
|
+
type SessionInfo,
|
|
33
|
+
type ToolCallContent,
|
|
34
|
+
type ToolCallLocation,
|
|
35
|
+
type ToolKind,
|
|
36
|
+
type SessionConfigOption,
|
|
37
|
+
type SetSessionModeRequest,
|
|
38
|
+
type SetSessionConfigOptionRequest,
|
|
39
|
+
type SetSessionConfigOptionResponse,
|
|
40
|
+
AGENT_METHODS,
|
|
18
41
|
} from "@agentclientprotocol/sdk";
|
|
19
42
|
|
|
20
43
|
export class WaveAcpAgent implements AcpAgent {
|
|
@@ -25,8 +48,65 @@ export class WaveAcpAgent implements AcpAgent {
|
|
|
25
48
|
this.connection = connection;
|
|
26
49
|
}
|
|
27
50
|
|
|
51
|
+
private getSessionModeState(agent: WaveAgent) {
|
|
52
|
+
return {
|
|
53
|
+
currentModeId: agent.getPermissionMode(),
|
|
54
|
+
availableModes: [
|
|
55
|
+
{
|
|
56
|
+
id: "default",
|
|
57
|
+
name: "Default",
|
|
58
|
+
description: "Ask for permission for restricted tools",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "acceptEdits",
|
|
62
|
+
name: "Accept Edits",
|
|
63
|
+
description: "Automatically accept file edits",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: "plan",
|
|
67
|
+
name: "Plan",
|
|
68
|
+
description: "Plan mode for complex tasks",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: "bypassPermissions",
|
|
72
|
+
name: "Bypass Permissions",
|
|
73
|
+
description: "Automatically accept all tool calls",
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private getSessionConfigOptions(agent: WaveAgent): SessionConfigOption[] {
|
|
80
|
+
return [
|
|
81
|
+
{
|
|
82
|
+
id: "permission_mode",
|
|
83
|
+
name: "Permission Mode",
|
|
84
|
+
type: "select",
|
|
85
|
+
category: "mode",
|
|
86
|
+
currentValue: agent.getPermissionMode(),
|
|
87
|
+
options: [
|
|
88
|
+
{ value: "default", name: "Default" },
|
|
89
|
+
{ value: "acceptEdits", name: "Accept Edits" },
|
|
90
|
+
{ value: "plan", name: "Plan" },
|
|
91
|
+
{ value: "bypassPermissions", name: "Bypass Permissions" },
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private async cleanupAllAgents() {
|
|
98
|
+
logger.info("Cleaning up all active agents due to connection closure");
|
|
99
|
+
const destroyPromises = Array.from(this.agents.values()).map((agent) =>
|
|
100
|
+
agent.destroy(),
|
|
101
|
+
);
|
|
102
|
+
await Promise.all(destroyPromises);
|
|
103
|
+
this.agents.clear();
|
|
104
|
+
}
|
|
105
|
+
|
|
28
106
|
async initialize(): Promise<InitializeResponse> {
|
|
29
107
|
logger.info("Initializing WaveAcpAgent");
|
|
108
|
+
// Setup cleanup on connection closure
|
|
109
|
+
this.connection.closed.then(() => this.cleanupAllAgents());
|
|
30
110
|
return {
|
|
31
111
|
protocolVersion: 1,
|
|
32
112
|
agentInfo: {
|
|
@@ -35,6 +115,10 @@ export class WaveAcpAgent implements AcpAgent {
|
|
|
35
115
|
},
|
|
36
116
|
agentCapabilities: {
|
|
37
117
|
loadSession: true,
|
|
118
|
+
sessionCapabilities: {
|
|
119
|
+
list: {},
|
|
120
|
+
close: {},
|
|
121
|
+
},
|
|
38
122
|
},
|
|
39
123
|
};
|
|
40
124
|
}
|
|
@@ -43,12 +127,25 @@ export class WaveAcpAgent implements AcpAgent {
|
|
|
43
127
|
// No authentication required for now
|
|
44
128
|
}
|
|
45
129
|
|
|
46
|
-
async
|
|
47
|
-
|
|
48
|
-
|
|
130
|
+
private async createAgent(
|
|
131
|
+
sessionId: string | undefined,
|
|
132
|
+
cwd: string,
|
|
133
|
+
): Promise<WaveAgent> {
|
|
49
134
|
const callbacks: AgentOptions["callbacks"] = {};
|
|
135
|
+
const agentRef: { instance?: WaveAgent } = {};
|
|
136
|
+
|
|
50
137
|
const agent = await WaveAgent.create({
|
|
51
138
|
workdir: cwd,
|
|
139
|
+
restoreSessionId: sessionId,
|
|
140
|
+
canUseTool: (context) => {
|
|
141
|
+
if (!agentRef.instance) {
|
|
142
|
+
throw new Error("Agent instance not yet initialized");
|
|
143
|
+
}
|
|
144
|
+
return this.handlePermissionRequest(
|
|
145
|
+
agentRef.instance.sessionId,
|
|
146
|
+
context,
|
|
147
|
+
);
|
|
148
|
+
},
|
|
52
149
|
callbacks: {
|
|
53
150
|
onAssistantContentUpdated: (chunk: string) =>
|
|
54
151
|
callbacks.onAssistantContentUpdated?.(chunk, ""),
|
|
@@ -60,62 +157,154 @@ export class WaveAcpAgent implements AcpAgent {
|
|
|
60
157
|
| undefined;
|
|
61
158
|
cb?.(params);
|
|
62
159
|
},
|
|
63
|
-
onTasksChange: (tasks
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
| undefined;
|
|
67
|
-
cb?.(tasks);
|
|
68
|
-
},
|
|
160
|
+
onTasksChange: (tasks) => callbacks.onTasksChange?.(tasks as Task[]),
|
|
161
|
+
onPermissionModeChange: (mode) =>
|
|
162
|
+
callbacks.onPermissionModeChange?.(mode),
|
|
69
163
|
},
|
|
70
164
|
});
|
|
71
165
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
this.agents.set(
|
|
166
|
+
agentRef.instance = agent;
|
|
167
|
+
const actualSessionId = agent.sessionId;
|
|
168
|
+
this.agents.set(actualSessionId, agent);
|
|
75
169
|
|
|
76
170
|
// Update the callbacks object with the correct sessionId
|
|
77
|
-
Object.assign(callbacks, this.createCallbacks(
|
|
171
|
+
Object.assign(callbacks, this.createCallbacks(actualSessionId));
|
|
172
|
+
|
|
173
|
+
return agent;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async newSession(params: NewSessionRequest): Promise<NewSessionResponse> {
|
|
177
|
+
const { cwd } = params;
|
|
178
|
+
logger.info(`Creating new session in ${cwd}`);
|
|
179
|
+
const agent = await this.createAgent(undefined, cwd);
|
|
180
|
+
logger.info(`New session created with ID: ${agent.sessionId}`);
|
|
181
|
+
|
|
182
|
+
// Send initial available commands after agent creation
|
|
183
|
+
setImmediate(() => {
|
|
184
|
+
this.connection.sessionUpdate({
|
|
185
|
+
sessionId: agent.sessionId as AcpSessionId,
|
|
186
|
+
update: {
|
|
187
|
+
sessionUpdate: "available_commands_update",
|
|
188
|
+
availableCommands: agent.getSlashCommands().map((cmd) => ({
|
|
189
|
+
name: cmd.name,
|
|
190
|
+
description: cmd.description,
|
|
191
|
+
input: {
|
|
192
|
+
hint: "Enter arguments...",
|
|
193
|
+
},
|
|
194
|
+
})),
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
});
|
|
78
198
|
|
|
79
199
|
return {
|
|
80
|
-
sessionId: sessionId as AcpSessionId,
|
|
200
|
+
sessionId: agent.sessionId as AcpSessionId,
|
|
201
|
+
modes: this.getSessionModeState(agent),
|
|
202
|
+
configOptions: this.getSessionConfigOptions(agent),
|
|
81
203
|
};
|
|
82
204
|
}
|
|
83
205
|
|
|
84
206
|
async loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse> {
|
|
85
|
-
const { sessionId } = params;
|
|
86
|
-
logger.info(`Loading session: ${sessionId}`);
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const cb = callbacks.onTasksChange as
|
|
103
|
-
| ((tasks: unknown[]) => void)
|
|
104
|
-
| undefined;
|
|
105
|
-
cb?.(tasks);
|
|
207
|
+
const { sessionId, cwd } = params;
|
|
208
|
+
logger.info(`Loading session: ${sessionId} in ${cwd}`);
|
|
209
|
+
const agent = await this.createAgent(sessionId, cwd);
|
|
210
|
+
|
|
211
|
+
// Send initial available commands after agent creation
|
|
212
|
+
setImmediate(() => {
|
|
213
|
+
this.connection.sessionUpdate({
|
|
214
|
+
sessionId: agent.sessionId as AcpSessionId,
|
|
215
|
+
update: {
|
|
216
|
+
sessionUpdate: "available_commands_update",
|
|
217
|
+
availableCommands: agent.getSlashCommands().map((cmd) => ({
|
|
218
|
+
name: cmd.name,
|
|
219
|
+
description: cmd.description,
|
|
220
|
+
input: {
|
|
221
|
+
hint: "Enter arguments...",
|
|
222
|
+
},
|
|
223
|
+
})),
|
|
106
224
|
},
|
|
107
|
-
}
|
|
225
|
+
});
|
|
108
226
|
});
|
|
109
227
|
|
|
110
|
-
|
|
111
|
-
|
|
228
|
+
return {
|
|
229
|
+
modes: this.getSessionModeState(agent),
|
|
230
|
+
configOptions: this.getSessionConfigOptions(agent),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async listSessions(
|
|
235
|
+
params: ListSessionsRequest,
|
|
236
|
+
): Promise<ListSessionsResponse> {
|
|
237
|
+
const { cwd } = params;
|
|
238
|
+
logger.info(`listSessions called with params: ${JSON.stringify(params)}`);
|
|
239
|
+
if (!cwd) {
|
|
240
|
+
logger.warn("listSessions called without cwd, returning empty list");
|
|
241
|
+
return { sessions: [] };
|
|
242
|
+
}
|
|
112
243
|
|
|
113
|
-
|
|
114
|
-
|
|
244
|
+
logger.info(`Listing sessions for ${cwd}`);
|
|
245
|
+
const waveSessions = await listWaveSessions(cwd);
|
|
246
|
+
logger.info(`Found ${waveSessions.length} sessions for ${cwd}`);
|
|
247
|
+
const sessions: SessionInfo[] = waveSessions.map((meta) => ({
|
|
248
|
+
sessionId: meta.id as AcpSessionId,
|
|
249
|
+
cwd: meta.workdir,
|
|
250
|
+
updatedAt: meta.lastActiveAt.toISOString(),
|
|
251
|
+
}));
|
|
252
|
+
return { sessions };
|
|
253
|
+
}
|
|
115
254
|
|
|
255
|
+
async unstable_closeSession(
|
|
256
|
+
params: Record<string, unknown>,
|
|
257
|
+
): Promise<Record<string, unknown>> {
|
|
258
|
+
const sessionId = params.sessionId as string;
|
|
259
|
+
logger.info(`Stopping session ${sessionId}`);
|
|
260
|
+
const agent = this.agents.get(sessionId);
|
|
261
|
+
if (agent) {
|
|
262
|
+
const workdir = agent.workingDirectory;
|
|
263
|
+
await agent.destroy();
|
|
264
|
+
this.agents.delete(sessionId);
|
|
265
|
+
// Delete the session file so it doesn't show up in listSessions
|
|
266
|
+
await deleteWaveSession(sessionId, workdir);
|
|
267
|
+
}
|
|
116
268
|
return {};
|
|
117
269
|
}
|
|
118
270
|
|
|
271
|
+
async extMethod(
|
|
272
|
+
method: string,
|
|
273
|
+
params: Record<string, unknown>,
|
|
274
|
+
): Promise<Record<string, unknown>> {
|
|
275
|
+
if (method === AGENT_METHODS.session_close) {
|
|
276
|
+
return this.unstable_closeSession(params);
|
|
277
|
+
}
|
|
278
|
+
throw new Error(`Method ${method} not implemented`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async setSessionMode(params: SetSessionModeRequest): Promise<void> {
|
|
282
|
+
const { sessionId, modeId } = params;
|
|
283
|
+
const agent = this.agents.get(sessionId);
|
|
284
|
+
if (!agent) throw new Error(`Session ${sessionId} not found`);
|
|
285
|
+
agent.setPermissionMode(
|
|
286
|
+
modeId as "default" | "acceptEdits" | "plan" | "bypassPermissions",
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async setSessionConfigOption(
|
|
291
|
+
params: SetSessionConfigOptionRequest,
|
|
292
|
+
): Promise<SetSessionConfigOptionResponse> {
|
|
293
|
+
const { sessionId, configId, value } = params;
|
|
294
|
+
const agent = this.agents.get(sessionId);
|
|
295
|
+
if (!agent) throw new Error(`Session ${sessionId} not found`);
|
|
296
|
+
|
|
297
|
+
if (configId === "permission_mode") {
|
|
298
|
+
agent.setPermissionMode(
|
|
299
|
+
value as "default" | "acceptEdits" | "plan" | "bypassPermissions",
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
configOptions: this.getSessionConfigOptions(agent),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
119
308
|
async prompt(params: PromptRequest): Promise<PromptResponse> {
|
|
120
309
|
const { sessionId, prompt } = params;
|
|
121
310
|
logger.info(`Received prompt for session ${sessionId}`);
|
|
@@ -149,12 +338,6 @@ export class WaveAcpAgent implements AcpAgent {
|
|
|
149
338
|
textContent,
|
|
150
339
|
images.length > 0 ? images : undefined,
|
|
151
340
|
);
|
|
152
|
-
// Force save session so it can be loaded later
|
|
153
|
-
await (
|
|
154
|
-
agent as unknown as {
|
|
155
|
-
messageManager: { saveSession: () => Promise<void> };
|
|
156
|
-
}
|
|
157
|
-
).messageManager.saveSession();
|
|
158
341
|
logger.info(`Message sent successfully for session ${sessionId}`);
|
|
159
342
|
return {
|
|
160
343
|
stopReason: "end_turn" as StopReason,
|
|
@@ -180,7 +363,248 @@ export class WaveAcpAgent implements AcpAgent {
|
|
|
180
363
|
}
|
|
181
364
|
}
|
|
182
365
|
|
|
366
|
+
private async handlePermissionRequest(
|
|
367
|
+
sessionId: string,
|
|
368
|
+
context: ToolPermissionContext,
|
|
369
|
+
): Promise<PermissionDecision> {
|
|
370
|
+
logger.info(
|
|
371
|
+
`Handling permission request for ${context.toolName} in session ${sessionId}`,
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
const agent = this.agents.get(sessionId);
|
|
375
|
+
const workdir = agent?.workingDirectory || process.cwd();
|
|
376
|
+
|
|
377
|
+
const toolCallId =
|
|
378
|
+
context.toolCallId ||
|
|
379
|
+
"perm-" + Math.random().toString(36).substring(2, 9);
|
|
380
|
+
|
|
381
|
+
const options: PermissionOption[] = [
|
|
382
|
+
{
|
|
383
|
+
optionId: "allow_once",
|
|
384
|
+
name: "Allow Once",
|
|
385
|
+
kind: "allow_once",
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
optionId: "allow_always",
|
|
389
|
+
name: "Allow Always",
|
|
390
|
+
kind: "allow_always",
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
optionId: "reject_once",
|
|
394
|
+
name: "Reject Once",
|
|
395
|
+
kind: "reject_once",
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
optionId: "reject_always",
|
|
399
|
+
name: "Reject Always",
|
|
400
|
+
kind: "reject_always",
|
|
401
|
+
},
|
|
402
|
+
];
|
|
403
|
+
|
|
404
|
+
const content = context.toolName
|
|
405
|
+
? await this.getToolContentAsync(
|
|
406
|
+
context.toolName,
|
|
407
|
+
context.toolInput,
|
|
408
|
+
workdir,
|
|
409
|
+
)
|
|
410
|
+
: undefined;
|
|
411
|
+
const locations = context.toolName
|
|
412
|
+
? this.getToolLocations(context.toolName, context.toolInput)
|
|
413
|
+
: undefined;
|
|
414
|
+
const kind = context.toolName
|
|
415
|
+
? this.getToolKind(context.toolName)
|
|
416
|
+
: undefined;
|
|
417
|
+
|
|
418
|
+
try {
|
|
419
|
+
const response = await this.connection.requestPermission({
|
|
420
|
+
sessionId: sessionId as AcpSessionId,
|
|
421
|
+
toolCall: {
|
|
422
|
+
toolCallId,
|
|
423
|
+
title: `Permission for ${context.toolName}`,
|
|
424
|
+
status: "pending",
|
|
425
|
+
rawInput: context.toolInput,
|
|
426
|
+
content,
|
|
427
|
+
locations,
|
|
428
|
+
kind,
|
|
429
|
+
},
|
|
430
|
+
options,
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
if (response.outcome.outcome === "cancelled") {
|
|
434
|
+
return { behavior: "deny", message: "Cancelled by user" };
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const selectedOptionId = response.outcome.optionId;
|
|
438
|
+
logger.info(`User selected permission option: ${selectedOptionId}`);
|
|
439
|
+
|
|
440
|
+
switch (selectedOptionId) {
|
|
441
|
+
case "allow_once":
|
|
442
|
+
return { behavior: "allow" };
|
|
443
|
+
case "allow_always":
|
|
444
|
+
return {
|
|
445
|
+
behavior: "allow",
|
|
446
|
+
newPermissionRule: `${context.toolName}(*)`,
|
|
447
|
+
};
|
|
448
|
+
case "reject_once":
|
|
449
|
+
return { behavior: "deny", message: "Rejected by user" };
|
|
450
|
+
case "reject_always":
|
|
451
|
+
return {
|
|
452
|
+
behavior: "deny",
|
|
453
|
+
message: "Rejected by user",
|
|
454
|
+
newPermissionRule: `!${context.toolName}(*)`,
|
|
455
|
+
};
|
|
456
|
+
default:
|
|
457
|
+
return { behavior: "deny", message: "Unknown option selected" };
|
|
458
|
+
}
|
|
459
|
+
} catch (error) {
|
|
460
|
+
logger.error("Error requesting permission via ACP:", error);
|
|
461
|
+
return {
|
|
462
|
+
behavior: "deny",
|
|
463
|
+
message: `Error requesting permission: ${error instanceof Error ? error.message : String(error)}`,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
private async getToolContentAsync(
|
|
469
|
+
name: string,
|
|
470
|
+
parameters: Record<string, unknown> | undefined,
|
|
471
|
+
workdir: string,
|
|
472
|
+
): Promise<ToolCallContent[] | undefined> {
|
|
473
|
+
if (!parameters) return undefined;
|
|
474
|
+
if (name === "Write") {
|
|
475
|
+
let oldText: string | null = null;
|
|
476
|
+
try {
|
|
477
|
+
const filePath = parameters.file_path as string;
|
|
478
|
+
const fullPath = path.isAbsolute(filePath)
|
|
479
|
+
? filePath
|
|
480
|
+
: path.join(workdir, filePath);
|
|
481
|
+
oldText = await fs.readFile(fullPath, "utf-8");
|
|
482
|
+
} catch {
|
|
483
|
+
// File might not exist, which is fine for Write
|
|
484
|
+
}
|
|
485
|
+
return [
|
|
486
|
+
{
|
|
487
|
+
type: "diff",
|
|
488
|
+
path: parameters.file_path as string,
|
|
489
|
+
oldText,
|
|
490
|
+
newText: parameters.content as string,
|
|
491
|
+
},
|
|
492
|
+
];
|
|
493
|
+
}
|
|
494
|
+
if (name === "Edit") {
|
|
495
|
+
let oldText: string | null = null;
|
|
496
|
+
let newText: string | null = null;
|
|
497
|
+
try {
|
|
498
|
+
const filePath = parameters.file_path as string;
|
|
499
|
+
const fullPath = path.isAbsolute(filePath)
|
|
500
|
+
? filePath
|
|
501
|
+
: path.join(workdir, filePath);
|
|
502
|
+
oldText = await fs.readFile(fullPath, "utf-8");
|
|
503
|
+
if (oldText) {
|
|
504
|
+
if (parameters.replace_all) {
|
|
505
|
+
newText = oldText
|
|
506
|
+
.split(parameters.old_string as string)
|
|
507
|
+
.join(parameters.new_string as string);
|
|
508
|
+
} else {
|
|
509
|
+
newText = oldText.replace(
|
|
510
|
+
parameters.old_string as string,
|
|
511
|
+
parameters.new_string as string,
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
} catch {
|
|
516
|
+
logger.error("Failed to read file for Edit diff");
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (oldText && newText) {
|
|
520
|
+
return [
|
|
521
|
+
{
|
|
522
|
+
type: "diff",
|
|
523
|
+
path: parameters.file_path as string,
|
|
524
|
+
oldText,
|
|
525
|
+
newText,
|
|
526
|
+
},
|
|
527
|
+
];
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Fallback to snippets if file reading fails
|
|
531
|
+
return [
|
|
532
|
+
{
|
|
533
|
+
type: "diff",
|
|
534
|
+
path: parameters.file_path as string,
|
|
535
|
+
oldText: parameters.old_string as string,
|
|
536
|
+
newText: parameters.new_string as string,
|
|
537
|
+
},
|
|
538
|
+
];
|
|
539
|
+
}
|
|
540
|
+
return this.getToolContent(name, parameters);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
private getToolContent(
|
|
544
|
+
name: string,
|
|
545
|
+
parameters: Record<string, unknown> | undefined,
|
|
546
|
+
): ToolCallContent[] | undefined {
|
|
547
|
+
if (!parameters) return undefined;
|
|
548
|
+
if (name === "Write") {
|
|
549
|
+
return [
|
|
550
|
+
{
|
|
551
|
+
type: "diff",
|
|
552
|
+
path: parameters.file_path as string,
|
|
553
|
+
oldText: null,
|
|
554
|
+
newText: parameters.content as string,
|
|
555
|
+
},
|
|
556
|
+
];
|
|
557
|
+
}
|
|
558
|
+
if (name === "Edit") {
|
|
559
|
+
return [
|
|
560
|
+
{
|
|
561
|
+
type: "diff",
|
|
562
|
+
path: parameters.file_path as string,
|
|
563
|
+
oldText: parameters.old_string as string,
|
|
564
|
+
newText: parameters.new_string as string,
|
|
565
|
+
},
|
|
566
|
+
];
|
|
567
|
+
}
|
|
568
|
+
return undefined;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
private getToolLocations(
|
|
572
|
+
name: string,
|
|
573
|
+
parameters: Record<string, unknown> | undefined,
|
|
574
|
+
): ToolCallLocation[] | undefined {
|
|
575
|
+
if (!parameters) return undefined;
|
|
576
|
+
if (name === "Write" || name === "Edit" || name === "Read") {
|
|
577
|
+
return [
|
|
578
|
+
{
|
|
579
|
+
path: parameters.file_path as string,
|
|
580
|
+
line: parameters.offset as number,
|
|
581
|
+
},
|
|
582
|
+
];
|
|
583
|
+
}
|
|
584
|
+
return undefined;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
private getToolKind(name: string): ToolKind {
|
|
588
|
+
switch (name) {
|
|
589
|
+
case "Read":
|
|
590
|
+
case "Glob":
|
|
591
|
+
case "Grep":
|
|
592
|
+
case "LSP":
|
|
593
|
+
return "read";
|
|
594
|
+
case "Write":
|
|
595
|
+
case "Edit":
|
|
596
|
+
return "edit";
|
|
597
|
+
case "Bash":
|
|
598
|
+
return "execute";
|
|
599
|
+
case "Agent":
|
|
600
|
+
return "other";
|
|
601
|
+
default:
|
|
602
|
+
return "other";
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
183
606
|
private createCallbacks(sessionId: string): AgentOptions["callbacks"] {
|
|
607
|
+
const getAgent = () => this.agents.get(sessionId);
|
|
184
608
|
return {
|
|
185
609
|
onAssistantContentUpdated: (chunk: string) => {
|
|
186
610
|
this.connection.sessionUpdate({
|
|
@@ -206,8 +630,27 @@ export class WaveAcpAgent implements AcpAgent {
|
|
|
206
630
|
},
|
|
207
631
|
});
|
|
208
632
|
},
|
|
209
|
-
onToolBlockUpdated: (params) => {
|
|
210
|
-
const { id, name, stage, success, error, result } = params;
|
|
633
|
+
onToolBlockUpdated: (params: AgentToolBlockUpdateParams) => {
|
|
634
|
+
const { id, name, stage, success, error, result, parameters } = params;
|
|
635
|
+
|
|
636
|
+
let parsedParameters: Record<string, unknown> | undefined = undefined;
|
|
637
|
+
if (parameters) {
|
|
638
|
+
try {
|
|
639
|
+
parsedParameters = JSON.parse(parameters);
|
|
640
|
+
} catch {
|
|
641
|
+
// Ignore parse errors during streaming
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const content =
|
|
646
|
+
name && parsedParameters
|
|
647
|
+
? this.getToolContent(name, parsedParameters)
|
|
648
|
+
: undefined;
|
|
649
|
+
const locations =
|
|
650
|
+
name && parsedParameters
|
|
651
|
+
? this.getToolLocations(name, parsedParameters)
|
|
652
|
+
: undefined;
|
|
653
|
+
const kind = name ? this.getToolKind(name) : undefined;
|
|
211
654
|
|
|
212
655
|
if (stage === "start") {
|
|
213
656
|
this.connection.sessionUpdate({
|
|
@@ -217,6 +660,10 @@ export class WaveAcpAgent implements AcpAgent {
|
|
|
217
660
|
toolCallId: id,
|
|
218
661
|
title: name || "Tool Call",
|
|
219
662
|
status: "pending",
|
|
663
|
+
content,
|
|
664
|
+
locations,
|
|
665
|
+
kind,
|
|
666
|
+
rawInput: parsedParameters,
|
|
220
667
|
},
|
|
221
668
|
});
|
|
222
669
|
return;
|
|
@@ -244,6 +691,10 @@ export class WaveAcpAgent implements AcpAgent {
|
|
|
244
691
|
status,
|
|
245
692
|
title: name || "Tool Call",
|
|
246
693
|
rawOutput: result || error,
|
|
694
|
+
content,
|
|
695
|
+
locations,
|
|
696
|
+
kind,
|
|
697
|
+
rawInput: parsedParameters,
|
|
247
698
|
},
|
|
248
699
|
});
|
|
249
700
|
},
|
|
@@ -265,6 +716,25 @@ export class WaveAcpAgent implements AcpAgent {
|
|
|
265
716
|
},
|
|
266
717
|
});
|
|
267
718
|
},
|
|
719
|
+
onPermissionModeChange: (mode) => {
|
|
720
|
+
this.connection.sessionUpdate({
|
|
721
|
+
sessionId: sessionId as AcpSessionId,
|
|
722
|
+
update: {
|
|
723
|
+
sessionUpdate: "current_mode_update",
|
|
724
|
+
currentModeId: mode,
|
|
725
|
+
},
|
|
726
|
+
});
|
|
727
|
+
const agent = getAgent();
|
|
728
|
+
if (agent) {
|
|
729
|
+
this.connection.sessionUpdate({
|
|
730
|
+
sessionId: sessionId as AcpSessionId,
|
|
731
|
+
update: {
|
|
732
|
+
sessionUpdate: "config_option_update",
|
|
733
|
+
configOptions: this.getSessionConfigOptions(agent),
|
|
734
|
+
},
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
},
|
|
268
738
|
};
|
|
269
739
|
}
|
|
270
740
|
}
|
|
@@ -72,7 +72,7 @@ export const ChatInterface: React.FC = () => {
|
|
|
72
72
|
|
|
73
73
|
const terminalHeight = stdout?.rows || 24;
|
|
74
74
|
const totalHeight = detailsHeight + selectorHeight + dynamicBlocksHeight;
|
|
75
|
-
if (totalHeight > terminalHeight) {
|
|
75
|
+
if (totalHeight > terminalHeight - 3) {
|
|
76
76
|
setIsConfirmationTooTall(true);
|
|
77
77
|
}
|
|
78
78
|
}, [
|
package/src/contexts/useChat.tsx
CHANGED