wave-code 0.10.0 → 0.10.2
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 +477 -40
- package/dist/components/ChatInterface.js +1 -1
- package/dist/components/DiffDisplay.d.ts.map +1 -1
- package/dist/components/DiffDisplay.js +23 -2
- package/dist/contexts/useChat.d.ts.map +1 -1
- package/dist/contexts/useChat.js +15 -4
- package/dist/utils/highlightUtils.d.ts.map +1 -1
- package/dist/utils/highlightUtils.js +8 -0
- package/dist/utils/toolParameterTransforms.d.ts +1 -5
- package/dist/utils/toolParameterTransforms.d.ts.map +1 -1
- package/dist/utils/toolParameterTransforms.js +0 -14
- package/dist/utils/worktree.js +2 -2
- package/package.json +3 -3
- package/src/acp/agent.ts +627 -68
- package/src/components/ChatInterface.tsx +1 -1
- package/src/components/DiffDisplay.tsx +27 -2
- package/src/contexts/useChat.tsx +20 -4
- package/src/utils/highlightUtils.ts +8 -0
- package/src/utils/toolParameterTransforms.ts +1 -23
- package/src/utils/worktree.ts +2 -2
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":"AAeA,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;IAiEnB,UAAU,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAalE,WAAW,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAWrE,YAAY,CAChB,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,oBAAoB,CAAC;IAuB1B,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;YAgHvB,mBAAmB;IA6EjC,OAAO,CAAC,cAAc;IAqCtB,OAAO,CAAC,gBAAgB;IAmCxB,OAAO,CAAC,WAAW;IAmBnB,OAAO,CAAC,eAAe;CAgMxB"}
|
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, listAllSessions as listAllWaveSessions, deleteSession as deleteWaveSession, truncateContent, } 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,29 @@ 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
|
+
stream: false,
|
|
90
|
+
canUseTool: (context) => {
|
|
91
|
+
if (!agentRef.instance) {
|
|
92
|
+
throw new Error("Agent instance not yet initialized");
|
|
93
|
+
}
|
|
94
|
+
return this.handlePermissionRequest(agentRef.instance.sessionId, context);
|
|
95
|
+
},
|
|
30
96
|
callbacks: {
|
|
31
97
|
onAssistantContentUpdated: (chunk) => callbacks.onAssistantContentUpdated?.(chunk, ""),
|
|
32
98
|
onAssistantReasoningUpdated: (chunk) => callbacks.onAssistantReasoningUpdated?.(chunk, ""),
|
|
@@ -34,46 +100,113 @@ export class WaveAcpAgent {
|
|
|
34
100
|
const cb = callbacks.onToolBlockUpdated;
|
|
35
101
|
cb?.(params);
|
|
36
102
|
},
|
|
37
|
-
onTasksChange: (tasks) =>
|
|
38
|
-
|
|
39
|
-
cb?.(tasks);
|
|
40
|
-
},
|
|
103
|
+
onTasksChange: (tasks) => callbacks.onTasksChange?.(tasks),
|
|
104
|
+
onPermissionModeChange: (mode) => callbacks.onPermissionModeChange?.(mode),
|
|
41
105
|
},
|
|
42
106
|
});
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
this.agents.set(
|
|
107
|
+
agentRef.instance = agent;
|
|
108
|
+
const actualSessionId = agent.sessionId;
|
|
109
|
+
this.agents.set(actualSessionId, agent);
|
|
46
110
|
// Update the callbacks object with the correct sessionId
|
|
47
|
-
Object.assign(callbacks, this.createCallbacks(
|
|
111
|
+
Object.assign(callbacks, this.createCallbacks(actualSessionId));
|
|
112
|
+
// Send initial available commands after agent creation
|
|
113
|
+
// Use setImmediate to ensure the client receives the session response before the update
|
|
114
|
+
setImmediate(() => {
|
|
115
|
+
this.connection.sessionUpdate({
|
|
116
|
+
sessionId: actualSessionId,
|
|
117
|
+
update: {
|
|
118
|
+
sessionUpdate: "available_commands_update",
|
|
119
|
+
availableCommands: agent.getSlashCommands().map((cmd) => ({
|
|
120
|
+
name: cmd.name,
|
|
121
|
+
description: cmd.description,
|
|
122
|
+
input: {
|
|
123
|
+
hint: "Enter arguments...",
|
|
124
|
+
},
|
|
125
|
+
})),
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
return agent;
|
|
130
|
+
}
|
|
131
|
+
async newSession(params) {
|
|
132
|
+
const { cwd } = params;
|
|
133
|
+
logger.info(`Creating new session in ${cwd}`);
|
|
134
|
+
const agent = await this.createAgent(undefined, cwd);
|
|
135
|
+
logger.info(`New session created with ID: ${agent.sessionId}`);
|
|
48
136
|
return {
|
|
49
|
-
sessionId: sessionId,
|
|
137
|
+
sessionId: agent.sessionId,
|
|
138
|
+
modes: this.getSessionModeState(agent),
|
|
139
|
+
configOptions: this.getSessionConfigOptions(agent),
|
|
50
140
|
};
|
|
51
141
|
}
|
|
52
142
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
143
|
+
const { sessionId, cwd } = params;
|
|
144
|
+
logger.info(`Loading session: ${sessionId} in ${cwd}`);
|
|
145
|
+
const agent = await this.createAgent(sessionId, cwd);
|
|
146
|
+
return {
|
|
147
|
+
modes: this.getSessionModeState(agent),
|
|
148
|
+
configOptions: this.getSessionConfigOptions(agent),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
async listSessions(params) {
|
|
152
|
+
const { cwd } = params;
|
|
153
|
+
logger.info(`listSessions called with params: ${JSON.stringify(params)}`);
|
|
154
|
+
let waveSessions;
|
|
155
|
+
if (!cwd) {
|
|
156
|
+
logger.info("listSessions called without cwd, listing all sessions");
|
|
157
|
+
waveSessions = await listAllWaveSessions();
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
logger.info(`Listing sessions for ${cwd}`);
|
|
161
|
+
waveSessions = await listWaveSessions(cwd);
|
|
162
|
+
}
|
|
163
|
+
logger.info(`Found ${waveSessions.length} sessions`);
|
|
164
|
+
const sessions = waveSessions.map((meta) => ({
|
|
165
|
+
sessionId: meta.id,
|
|
166
|
+
cwd: meta.workdir,
|
|
167
|
+
title: meta.firstMessage ? truncateContent(meta.firstMessage) : undefined,
|
|
168
|
+
updatedAt: meta.lastActiveAt.toISOString(),
|
|
169
|
+
}));
|
|
170
|
+
return { sessions };
|
|
171
|
+
}
|
|
172
|
+
async unstable_closeSession(params) {
|
|
173
|
+
const sessionId = params.sessionId;
|
|
174
|
+
logger.info(`Stopping session ${sessionId}`);
|
|
175
|
+
const agent = this.agents.get(sessionId);
|
|
176
|
+
if (agent) {
|
|
177
|
+
const workdir = agent.workingDirectory;
|
|
178
|
+
await agent.destroy();
|
|
179
|
+
this.agents.delete(sessionId);
|
|
180
|
+
// Delete the session file so it doesn't show up in listSessions
|
|
181
|
+
await deleteWaveSession(sessionId, workdir);
|
|
182
|
+
}
|
|
75
183
|
return {};
|
|
76
184
|
}
|
|
185
|
+
async extMethod(method, params) {
|
|
186
|
+
if (method === AGENT_METHODS.session_close) {
|
|
187
|
+
return this.unstable_closeSession(params);
|
|
188
|
+
}
|
|
189
|
+
throw new Error(`Method ${method} not implemented`);
|
|
190
|
+
}
|
|
191
|
+
async setSessionMode(params) {
|
|
192
|
+
const { sessionId, modeId } = params;
|
|
193
|
+
const agent = this.agents.get(sessionId);
|
|
194
|
+
if (!agent)
|
|
195
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
196
|
+
agent.setPermissionMode(modeId);
|
|
197
|
+
}
|
|
198
|
+
async setSessionConfigOption(params) {
|
|
199
|
+
const { sessionId, configId, value } = params;
|
|
200
|
+
const agent = this.agents.get(sessionId);
|
|
201
|
+
if (!agent)
|
|
202
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
203
|
+
if (configId === "permission_mode") {
|
|
204
|
+
agent.setPermissionMode(value);
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
configOptions: this.getSessionConfigOptions(agent),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
77
210
|
async prompt(params) {
|
|
78
211
|
const { sessionId, prompt } = params;
|
|
79
212
|
logger.info(`Received prompt for session ${sessionId}`);
|
|
@@ -99,8 +232,6 @@ export class WaveAcpAgent {
|
|
|
99
232
|
try {
|
|
100
233
|
logger.info(`Sending message to agent: ${textContent.substring(0, 50)}...`);
|
|
101
234
|
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
235
|
logger.info(`Message sent successfully for session ${sessionId}`);
|
|
105
236
|
return {
|
|
106
237
|
stopReason: "end_turn",
|
|
@@ -125,7 +256,243 @@ export class WaveAcpAgent {
|
|
|
125
256
|
agent.abortMessage();
|
|
126
257
|
}
|
|
127
258
|
}
|
|
259
|
+
async handlePermissionRequest(sessionId, context) {
|
|
260
|
+
logger.info(`Handling permission request for ${context.toolName} in session ${sessionId}`);
|
|
261
|
+
const agent = this.agents.get(sessionId);
|
|
262
|
+
const workdir = agent?.workingDirectory || process.cwd();
|
|
263
|
+
const toolCallId = context.toolCallId ||
|
|
264
|
+
"perm-" + Math.random().toString(36).substring(2, 9);
|
|
265
|
+
let effectiveName = context.toolName;
|
|
266
|
+
let effectiveCompactParams = undefined;
|
|
267
|
+
if (agent?.messages && context.toolCallId) {
|
|
268
|
+
const toolBlock = agent.messages
|
|
269
|
+
.flatMap((m) => m.blocks)
|
|
270
|
+
.find((b) => b.type === "tool" && b.id === context.toolCallId);
|
|
271
|
+
if (toolBlock) {
|
|
272
|
+
effectiveName = toolBlock.name || effectiveName;
|
|
273
|
+
effectiveCompactParams =
|
|
274
|
+
toolBlock.compactParams || effectiveCompactParams;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const displayTitle = effectiveName && effectiveCompactParams
|
|
278
|
+
? `${effectiveName}: ${effectiveCompactParams}`
|
|
279
|
+
: effectiveName || "Tool Call";
|
|
280
|
+
const options = [
|
|
281
|
+
{
|
|
282
|
+
optionId: "allow_once",
|
|
283
|
+
name: "Allow Once",
|
|
284
|
+
kind: "allow_once",
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
optionId: "allow_always",
|
|
288
|
+
name: "Allow Always",
|
|
289
|
+
kind: "allow_always",
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
optionId: "reject_once",
|
|
293
|
+
name: "Reject Once",
|
|
294
|
+
kind: "reject_once",
|
|
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: displayTitle,
|
|
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_always":
|
|
327
|
+
return {
|
|
328
|
+
behavior: "allow",
|
|
329
|
+
newPermissionRule: `${context.toolName}(*)`,
|
|
330
|
+
};
|
|
331
|
+
case "allow_once":
|
|
332
|
+
return { behavior: "allow" };
|
|
333
|
+
case "reject_once":
|
|
334
|
+
return { behavior: "deny", message: "Rejected by user" };
|
|
335
|
+
default:
|
|
336
|
+
return { behavior: "deny", message: "Unknown option selected" };
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
logger.error("Error requesting permission via ACP:", error);
|
|
341
|
+
return {
|
|
342
|
+
behavior: "deny",
|
|
343
|
+
message: `Error requesting permission: ${error instanceof Error ? error.message : String(error)}`,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
async getToolContentAsync(name, parameters, workdir) {
|
|
348
|
+
if (!parameters)
|
|
349
|
+
return undefined;
|
|
350
|
+
if (name === "Write") {
|
|
351
|
+
let oldText = null;
|
|
352
|
+
try {
|
|
353
|
+
const filePath = (parameters.file_path ||
|
|
354
|
+
parameters.filePath);
|
|
355
|
+
const fullPath = path.isAbsolute(filePath)
|
|
356
|
+
? filePath
|
|
357
|
+
: path.join(workdir, filePath);
|
|
358
|
+
oldText = await fs.readFile(fullPath, "utf-8");
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
// File might not exist, which is fine for Write
|
|
362
|
+
}
|
|
363
|
+
return [
|
|
364
|
+
{
|
|
365
|
+
type: "diff",
|
|
366
|
+
path: (parameters.file_path || parameters.filePath),
|
|
367
|
+
oldText,
|
|
368
|
+
newText: parameters.content,
|
|
369
|
+
},
|
|
370
|
+
];
|
|
371
|
+
}
|
|
372
|
+
if (name === "Edit") {
|
|
373
|
+
let oldText = null;
|
|
374
|
+
let newText = null;
|
|
375
|
+
try {
|
|
376
|
+
const filePath = (parameters.file_path ||
|
|
377
|
+
parameters.filePath);
|
|
378
|
+
const fullPath = path.isAbsolute(filePath)
|
|
379
|
+
? filePath
|
|
380
|
+
: path.join(workdir, filePath);
|
|
381
|
+
oldText = await fs.readFile(fullPath, "utf-8");
|
|
382
|
+
if (oldText) {
|
|
383
|
+
if (parameters.replace_all) {
|
|
384
|
+
newText = oldText
|
|
385
|
+
.split(parameters.old_string)
|
|
386
|
+
.join(parameters.new_string);
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
newText = oldText.replace(parameters.old_string, parameters.new_string);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
catch {
|
|
394
|
+
logger.error("Failed to read file for Edit diff");
|
|
395
|
+
}
|
|
396
|
+
if (oldText && newText) {
|
|
397
|
+
return [
|
|
398
|
+
{
|
|
399
|
+
type: "diff",
|
|
400
|
+
path: (parameters.file_path || parameters.filePath),
|
|
401
|
+
oldText,
|
|
402
|
+
newText,
|
|
403
|
+
},
|
|
404
|
+
];
|
|
405
|
+
}
|
|
406
|
+
// Fallback to snippets if file reading fails
|
|
407
|
+
return [
|
|
408
|
+
{
|
|
409
|
+
type: "diff",
|
|
410
|
+
path: (parameters.file_path || parameters.filePath),
|
|
411
|
+
oldText: parameters.old_string,
|
|
412
|
+
newText: parameters.new_string,
|
|
413
|
+
},
|
|
414
|
+
];
|
|
415
|
+
}
|
|
416
|
+
return this.getToolContent(name, parameters, undefined);
|
|
417
|
+
}
|
|
418
|
+
getToolContent(name, parameters, shortResult) {
|
|
419
|
+
const contents = [];
|
|
420
|
+
if (parameters) {
|
|
421
|
+
if (name === "Write") {
|
|
422
|
+
contents.push({
|
|
423
|
+
type: "diff",
|
|
424
|
+
path: (parameters.file_path || parameters.filePath),
|
|
425
|
+
oldText: null,
|
|
426
|
+
newText: parameters.content,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
else if (name === "Edit") {
|
|
430
|
+
contents.push({
|
|
431
|
+
type: "diff",
|
|
432
|
+
path: (parameters.file_path || parameters.filePath),
|
|
433
|
+
oldText: parameters.old_string,
|
|
434
|
+
newText: parameters.new_string,
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
if (shortResult) {
|
|
439
|
+
contents.push({
|
|
440
|
+
type: "content",
|
|
441
|
+
content: {
|
|
442
|
+
type: "text",
|
|
443
|
+
text: shortResult,
|
|
444
|
+
},
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
return contents.length > 0 ? contents : undefined;
|
|
448
|
+
}
|
|
449
|
+
getToolLocations(name, parameters, extraStartLineNumber) {
|
|
450
|
+
if (!parameters)
|
|
451
|
+
return undefined;
|
|
452
|
+
if (name === "Write" ||
|
|
453
|
+
name === "Edit" ||
|
|
454
|
+
name === "Read" ||
|
|
455
|
+
name === "LSP") {
|
|
456
|
+
const filePath = (parameters.file_path || parameters.filePath);
|
|
457
|
+
let line = extraStartLineNumber ??
|
|
458
|
+
parameters.startLineNumber ??
|
|
459
|
+
parameters.line ??
|
|
460
|
+
parameters.offset;
|
|
461
|
+
if (name === "Write" && line === undefined) {
|
|
462
|
+
line = 1;
|
|
463
|
+
}
|
|
464
|
+
if (filePath) {
|
|
465
|
+
return [
|
|
466
|
+
{
|
|
467
|
+
path: filePath,
|
|
468
|
+
line: line,
|
|
469
|
+
},
|
|
470
|
+
];
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return undefined;
|
|
474
|
+
}
|
|
475
|
+
getToolKind(name) {
|
|
476
|
+
switch (name) {
|
|
477
|
+
case "Read":
|
|
478
|
+
case "Glob":
|
|
479
|
+
case "Grep":
|
|
480
|
+
case "LSP":
|
|
481
|
+
return "read";
|
|
482
|
+
case "Write":
|
|
483
|
+
case "Edit":
|
|
484
|
+
return "edit";
|
|
485
|
+
case "Bash":
|
|
486
|
+
return "execute";
|
|
487
|
+
case "Agent":
|
|
488
|
+
return "other";
|
|
489
|
+
default:
|
|
490
|
+
return "other";
|
|
491
|
+
}
|
|
492
|
+
}
|
|
128
493
|
createCallbacks(sessionId) {
|
|
494
|
+
const getAgent = () => this.agents.get(sessionId);
|
|
495
|
+
const toolStates = new Map();
|
|
129
496
|
return {
|
|
130
497
|
onAssistantContentUpdated: (chunk) => {
|
|
131
498
|
this.connection.sessionUpdate({
|
|
@@ -152,15 +519,59 @@ export class WaveAcpAgent {
|
|
|
152
519
|
});
|
|
153
520
|
},
|
|
154
521
|
onToolBlockUpdated: (params) => {
|
|
155
|
-
const { id, name, stage, success, error, result } = params;
|
|
522
|
+
const { id, name, stage, success, error, result, parameters, compactParams, shortResult, startLineNumber, } = params;
|
|
523
|
+
let state = toolStates.get(id);
|
|
524
|
+
if (!state) {
|
|
525
|
+
state = {};
|
|
526
|
+
toolStates.set(id, state);
|
|
527
|
+
}
|
|
528
|
+
if (name)
|
|
529
|
+
state.name = name;
|
|
530
|
+
if (compactParams)
|
|
531
|
+
state.compactParams = compactParams;
|
|
532
|
+
if (shortResult)
|
|
533
|
+
state.shortResult = shortResult;
|
|
534
|
+
if (startLineNumber !== undefined)
|
|
535
|
+
state.startLineNumber = startLineNumber;
|
|
536
|
+
const effectiveName = state.name || name;
|
|
537
|
+
const effectiveCompactParams = state.compactParams || compactParams;
|
|
538
|
+
const effectiveShortResult = state.shortResult || shortResult;
|
|
539
|
+
const effectiveStartLineNumber = state.startLineNumber !== undefined
|
|
540
|
+
? state.startLineNumber
|
|
541
|
+
: startLineNumber;
|
|
542
|
+
const displayTitle = effectiveName && effectiveCompactParams
|
|
543
|
+
? `${effectiveName}: ${effectiveCompactParams}`
|
|
544
|
+
: effectiveName || "Tool Call";
|
|
545
|
+
let parsedParameters = undefined;
|
|
546
|
+
if (parameters) {
|
|
547
|
+
try {
|
|
548
|
+
parsedParameters = JSON.parse(parameters);
|
|
549
|
+
}
|
|
550
|
+
catch {
|
|
551
|
+
// Ignore parse errors during streaming
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
const content = effectiveName && (parsedParameters || effectiveShortResult)
|
|
555
|
+
? this.getToolContent(effectiveName, parsedParameters, effectiveShortResult)
|
|
556
|
+
: undefined;
|
|
557
|
+
const locations = effectiveName && parsedParameters
|
|
558
|
+
? this.getToolLocations(effectiveName, parsedParameters, effectiveStartLineNumber)
|
|
559
|
+
: undefined;
|
|
560
|
+
const kind = effectiveName
|
|
561
|
+
? this.getToolKind(effectiveName)
|
|
562
|
+
: undefined;
|
|
156
563
|
if (stage === "start") {
|
|
157
564
|
this.connection.sessionUpdate({
|
|
158
565
|
sessionId: sessionId,
|
|
159
566
|
update: {
|
|
160
567
|
sessionUpdate: "tool_call",
|
|
161
568
|
toolCallId: id,
|
|
162
|
-
title:
|
|
569
|
+
title: displayTitle,
|
|
163
570
|
status: "pending",
|
|
571
|
+
content,
|
|
572
|
+
locations,
|
|
573
|
+
kind,
|
|
574
|
+
rawInput: parsedParameters,
|
|
164
575
|
},
|
|
165
576
|
});
|
|
166
577
|
return;
|
|
@@ -182,10 +593,17 @@ export class WaveAcpAgent {
|
|
|
182
593
|
sessionUpdate: "tool_call_update",
|
|
183
594
|
toolCallId: id,
|
|
184
595
|
status,
|
|
185
|
-
title:
|
|
596
|
+
title: displayTitle,
|
|
186
597
|
rawOutput: result || error,
|
|
598
|
+
content,
|
|
599
|
+
locations,
|
|
600
|
+
kind,
|
|
601
|
+
rawInput: parsedParameters,
|
|
187
602
|
},
|
|
188
603
|
});
|
|
604
|
+
if (stage === "end") {
|
|
605
|
+
toolStates.delete(id);
|
|
606
|
+
}
|
|
189
607
|
},
|
|
190
608
|
onTasksChange: (tasks) => {
|
|
191
609
|
this.connection.sessionUpdate({
|
|
@@ -204,6 +622,25 @@ export class WaveAcpAgent {
|
|
|
204
622
|
},
|
|
205
623
|
});
|
|
206
624
|
},
|
|
625
|
+
onPermissionModeChange: (mode) => {
|
|
626
|
+
this.connection.sessionUpdate({
|
|
627
|
+
sessionId: sessionId,
|
|
628
|
+
update: {
|
|
629
|
+
sessionUpdate: "current_mode_update",
|
|
630
|
+
currentModeId: mode,
|
|
631
|
+
},
|
|
632
|
+
});
|
|
633
|
+
const agent = getAgent();
|
|
634
|
+
if (agent) {
|
|
635
|
+
this.connection.sessionUpdate({
|
|
636
|
+
sessionId: sessionId,
|
|
637
|
+
update: {
|
|
638
|
+
sessionUpdate: "config_option_update",
|
|
639
|
+
configOptions: this.getSessionConfigOptions(agent),
|
|
640
|
+
},
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
},
|
|
207
644
|
};
|
|
208
645
|
}
|
|
209
646
|
}
|
|
@@ -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":"DiffDisplay.d.ts","sourceRoot":"","sources":["../../src/components/DiffDisplay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"DiffDisplay.d.ts","sourceRoot":"","sources":["../../src/components/DiffDisplay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAYvC,UAAU,gBAAgB;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CA6WlD,CAAC"}
|