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.
@@ -1,14 +1,28 @@
1
- import type { Agent as AcpAgent, AgentSideConnection, InitializeResponse, NewSessionRequest, NewSessionResponse, LoadSessionRequest, LoadSessionResponse, PromptRequest, PromptResponse, CancelNotification, AuthenticateResponse } from "@agentclientprotocol/sdk";
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
@@ -1 +1 @@
1
- {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/acp/agent.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,KAAK,IAAI,QAAQ,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,aAAa,EACb,cAAc,EACd,kBAAkB,EAClB,oBAAoB,EAIrB,MAAM,0BAA0B,CAAC;AAElC,qBAAa,YAAa,YAAW,QAAQ;IAC3C,OAAO,CAAC,MAAM,CAAqC;IACnD,OAAO,CAAC,UAAU,CAAsB;gBAE5B,UAAU,EAAE,mBAAmB;IAIrC,UAAU,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAczC,YAAY,IAAI,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAIpD,UAAU,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAsClE,WAAW,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAmCrE,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;IAuDtD,MAAM,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IASvD,OAAO,CAAC,eAAe;CAuFxB"}
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 newSession(params) {
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
- const cb = callbacks.onTasksChange;
39
- cb?.(tasks);
40
- },
103
+ onTasksChange: (tasks) => callbacks.onTasksChange?.(tasks),
104
+ onPermissionModeChange: (mode) => callbacks.onPermissionModeChange?.(mode),
41
105
  },
42
106
  });
43
- const sessionId = agent.sessionId;
44
- logger.info(`New session created: ${sessionId}`);
45
- this.agents.set(sessionId, agent);
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(sessionId));
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 callbacks = {};
56
- const agent = await WaveAgent.create({
57
- restoreSessionId: sessionId,
58
- callbacks: {
59
- onAssistantContentUpdated: (chunk) => callbacks.onAssistantContentUpdated?.(chunk, ""),
60
- onAssistantReasoningUpdated: (chunk) => callbacks.onAssistantReasoningUpdated?.(chunk, ""),
61
- onToolBlockUpdated: (params) => {
62
- const cb = callbacks.onToolBlockUpdated;
63
- cb?.(params);
64
- },
65
- onTasksChange: (tasks) => {
66
- const cb = callbacks.onTasksChange;
67
- cb?.(tasks);
68
- },
69
- },
70
- });
71
- this.agents.set(sessionId, agent);
72
- logger.info(`Session loaded: ${sessionId}`);
73
- // Update the callbacks object with the correct sessionId
74
- Object.assign(callbacks, this.createCallbacks(sessionId));
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: name || "Tool Call",
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: name || "Tool Call",
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;AAMvC,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,CA0VlD,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"}