wave-code 0.10.1 → 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 +1 @@
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"}
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,4 +1,4 @@
1
- import { Agent as WaveAgent, listSessions as listWaveSessions, deleteSession as deleteWaveSession, } from "wave-agent-sdk";
1
+ import { Agent as WaveAgent, listSessions as listWaveSessions, listAllSessions as listAllWaveSessions, deleteSession as deleteWaveSession, truncateContent, } from "wave-agent-sdk";
2
2
  import * as fs from "node:fs/promises";
3
3
  import * as path from "node:path";
4
4
  import { logger } from "../utils/logger.js";
@@ -86,6 +86,7 @@ export class WaveAcpAgent {
86
86
  const agent = await WaveAgent.create({
87
87
  workdir: cwd,
88
88
  restoreSessionId: sessionId,
89
+ stream: false,
89
90
  canUseTool: (context) => {
90
91
  if (!agentRef.instance) {
91
92
  throw new Error("Agent instance not yet initialized");
@@ -108,17 +109,11 @@ export class WaveAcpAgent {
108
109
  this.agents.set(actualSessionId, agent);
109
110
  // Update the callbacks object with the correct sessionId
110
111
  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
112
  // Send initial available commands after agent creation
113
+ // Use setImmediate to ensure the client receives the session response before the update
119
114
  setImmediate(() => {
120
115
  this.connection.sessionUpdate({
121
- sessionId: agent.sessionId,
116
+ sessionId: actualSessionId,
122
117
  update: {
123
118
  sessionUpdate: "available_commands_update",
124
119
  availableCommands: agent.getSlashCommands().map((cmd) => ({
@@ -131,6 +126,13 @@ export class WaveAcpAgent {
131
126
  },
132
127
  });
133
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}`);
134
136
  return {
135
137
  sessionId: agent.sessionId,
136
138
  modes: this.getSessionModeState(agent),
@@ -141,22 +143,6 @@ export class WaveAcpAgent {
141
143
  const { sessionId, cwd } = params;
142
144
  logger.info(`Loading session: ${sessionId} in ${cwd}`);
143
145
  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
- })),
157
- },
158
- });
159
- });
160
146
  return {
161
147
  modes: this.getSessionModeState(agent),
162
148
  configOptions: this.getSessionConfigOptions(agent),
@@ -165,16 +151,20 @@ export class WaveAcpAgent {
165
151
  async listSessions(params) {
166
152
  const { cwd } = params;
167
153
  logger.info(`listSessions called with params: ${JSON.stringify(params)}`);
154
+ let waveSessions;
168
155
  if (!cwd) {
169
- logger.warn("listSessions called without cwd, returning empty list");
170
- return { sessions: [] };
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);
171
162
  }
172
- logger.info(`Listing sessions for ${cwd}`);
173
- const waveSessions = await listWaveSessions(cwd);
174
- logger.info(`Found ${waveSessions.length} sessions for ${cwd}`);
163
+ logger.info(`Found ${waveSessions.length} sessions`);
175
164
  const sessions = waveSessions.map((meta) => ({
176
165
  sessionId: meta.id,
177
166
  cwd: meta.workdir,
167
+ title: meta.firstMessage ? truncateContent(meta.firstMessage) : undefined,
178
168
  updatedAt: meta.lastActiveAt.toISOString(),
179
169
  }));
180
170
  return { sessions };
@@ -272,6 +262,21 @@ export class WaveAcpAgent {
272
262
  const workdir = agent?.workingDirectory || process.cwd();
273
263
  const toolCallId = context.toolCallId ||
274
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";
275
280
  const options = [
276
281
  {
277
282
  optionId: "allow_once",
@@ -288,11 +293,6 @@ export class WaveAcpAgent {
288
293
  name: "Reject Once",
289
294
  kind: "reject_once",
290
295
  },
291
- {
292
- optionId: "reject_always",
293
- name: "Reject Always",
294
- kind: "reject_always",
295
- },
296
296
  ];
297
297
  const content = context.toolName
298
298
  ? await this.getToolContentAsync(context.toolName, context.toolInput, workdir)
@@ -308,7 +308,7 @@ export class WaveAcpAgent {
308
308
  sessionId: sessionId,
309
309
  toolCall: {
310
310
  toolCallId,
311
- title: `Permission for ${context.toolName}`,
311
+ title: displayTitle,
312
312
  status: "pending",
313
313
  rawInput: context.toolInput,
314
314
  content,
@@ -323,21 +323,15 @@ export class WaveAcpAgent {
323
323
  const selectedOptionId = response.outcome.optionId;
324
324
  logger.info(`User selected permission option: ${selectedOptionId}`);
325
325
  switch (selectedOptionId) {
326
- case "allow_once":
327
- return { behavior: "allow" };
328
326
  case "allow_always":
329
327
  return {
330
328
  behavior: "allow",
331
329
  newPermissionRule: `${context.toolName}(*)`,
332
330
  };
331
+ case "allow_once":
332
+ return { behavior: "allow" };
333
333
  case "reject_once":
334
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
335
  default:
342
336
  return { behavior: "deny", message: "Unknown option selected" };
343
337
  }
@@ -356,7 +350,8 @@ export class WaveAcpAgent {
356
350
  if (name === "Write") {
357
351
  let oldText = null;
358
352
  try {
359
- const filePath = parameters.file_path;
353
+ const filePath = (parameters.file_path ||
354
+ parameters.filePath);
360
355
  const fullPath = path.isAbsolute(filePath)
361
356
  ? filePath
362
357
  : path.join(workdir, filePath);
@@ -368,7 +363,7 @@ export class WaveAcpAgent {
368
363
  return [
369
364
  {
370
365
  type: "diff",
371
- path: parameters.file_path,
366
+ path: (parameters.file_path || parameters.filePath),
372
367
  oldText,
373
368
  newText: parameters.content,
374
369
  },
@@ -378,7 +373,8 @@ export class WaveAcpAgent {
378
373
  let oldText = null;
379
374
  let newText = null;
380
375
  try {
381
- const filePath = parameters.file_path;
376
+ const filePath = (parameters.file_path ||
377
+ parameters.filePath);
382
378
  const fullPath = path.isAbsolute(filePath)
383
379
  ? filePath
384
380
  : path.join(workdir, filePath);
@@ -401,7 +397,7 @@ export class WaveAcpAgent {
401
397
  return [
402
398
  {
403
399
  type: "diff",
404
- path: parameters.file_path,
400
+ path: (parameters.file_path || parameters.filePath),
405
401
  oldText,
406
402
  newText,
407
403
  },
@@ -411,49 +407,68 @@ export class WaveAcpAgent {
411
407
  return [
412
408
  {
413
409
  type: "diff",
414
- path: parameters.file_path,
410
+ path: (parameters.file_path || parameters.filePath),
415
411
  oldText: parameters.old_string,
416
412
  newText: parameters.new_string,
417
413
  },
418
414
  ];
419
415
  }
420
- return this.getToolContent(name, parameters);
416
+ return this.getToolContent(name, parameters, undefined);
421
417
  }
422
- getToolContent(name, parameters) {
423
- if (!parameters)
424
- return undefined;
425
- if (name === "Write") {
426
- return [
427
- {
418
+ getToolContent(name, parameters, shortResult) {
419
+ const contents = [];
420
+ if (parameters) {
421
+ if (name === "Write") {
422
+ contents.push({
428
423
  type: "diff",
429
- path: parameters.file_path,
424
+ path: (parameters.file_path || parameters.filePath),
430
425
  oldText: null,
431
426
  newText: parameters.content,
432
- },
433
- ];
434
- }
435
- if (name === "Edit") {
436
- return [
437
- {
427
+ });
428
+ }
429
+ else if (name === "Edit") {
430
+ contents.push({
438
431
  type: "diff",
439
- path: parameters.file_path,
432
+ path: (parameters.file_path || parameters.filePath),
440
433
  oldText: parameters.old_string,
441
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,
442
444
  },
443
- ];
445
+ });
444
446
  }
445
- return undefined;
447
+ return contents.length > 0 ? contents : undefined;
446
448
  }
447
- getToolLocations(name, parameters) {
449
+ getToolLocations(name, parameters, extraStartLineNumber) {
448
450
  if (!parameters)
449
451
  return undefined;
450
- if (name === "Write" || name === "Edit" || name === "Read") {
451
- return [
452
- {
453
- path: parameters.file_path,
454
- line: parameters.offset,
455
- },
456
- ];
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
+ }
457
472
  }
458
473
  return undefined;
459
474
  }
@@ -477,6 +492,7 @@ export class WaveAcpAgent {
477
492
  }
478
493
  createCallbacks(sessionId) {
479
494
  const getAgent = () => this.agents.get(sessionId);
495
+ const toolStates = new Map();
480
496
  return {
481
497
  onAssistantContentUpdated: (chunk) => {
482
498
  this.connection.sessionUpdate({
@@ -503,7 +519,29 @@ export class WaveAcpAgent {
503
519
  });
504
520
  },
505
521
  onToolBlockUpdated: (params) => {
506
- const { id, name, stage, success, error, result, parameters } = 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";
507
545
  let parsedParameters = undefined;
508
546
  if (parameters) {
509
547
  try {
@@ -513,20 +551,22 @@ export class WaveAcpAgent {
513
551
  // Ignore parse errors during streaming
514
552
  }
515
553
  }
516
- const content = name && parsedParameters
517
- ? this.getToolContent(name, parsedParameters)
554
+ const content = effectiveName && (parsedParameters || effectiveShortResult)
555
+ ? this.getToolContent(effectiveName, parsedParameters, effectiveShortResult)
518
556
  : undefined;
519
- const locations = name && parsedParameters
520
- ? this.getToolLocations(name, parsedParameters)
557
+ const locations = effectiveName && parsedParameters
558
+ ? this.getToolLocations(effectiveName, parsedParameters, effectiveStartLineNumber)
559
+ : undefined;
560
+ const kind = effectiveName
561
+ ? this.getToolKind(effectiveName)
521
562
  : undefined;
522
- const kind = name ? this.getToolKind(name) : undefined;
523
563
  if (stage === "start") {
524
564
  this.connection.sessionUpdate({
525
565
  sessionId: sessionId,
526
566
  update: {
527
567
  sessionUpdate: "tool_call",
528
568
  toolCallId: id,
529
- title: name || "Tool Call",
569
+ title: displayTitle,
530
570
  status: "pending",
531
571
  content,
532
572
  locations,
@@ -553,7 +593,7 @@ export class WaveAcpAgent {
553
593
  sessionUpdate: "tool_call_update",
554
594
  toolCallId: id,
555
595
  status,
556
- title: name || "Tool Call",
596
+ title: displayTitle,
557
597
  rawOutput: result || error,
558
598
  content,
559
599
  locations,
@@ -561,6 +601,9 @@ export class WaveAcpAgent {
561
601
  rawInput: parsedParameters,
562
602
  },
563
603
  });
604
+ if (stage === "end") {
605
+ toolStates.delete(id);
606
+ }
564
607
  },
565
608
  onTasksChange: (tasks) => {
566
609
  this.connection.sessionUpdate({
@@ -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"}
@@ -1,9 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useMemo } from "react";
3
3
  import { Box, Text } from "ink";
4
- import { WRITE_TOOL_NAME, EDIT_TOOL_NAME } from "wave-agent-sdk";
4
+ import { WRITE_TOOL_NAME, EDIT_TOOL_NAME, } from "wave-agent-sdk";
5
5
  import { transformToolBlockToChanges } from "../utils/toolParameterTransforms.js";
6
6
  import { diffLines, diffWords } from "diff";
7
+ import path from "path";
8
+ import { Markdown } from "./Markdown.js";
7
9
  export const DiffDisplay = ({ toolName, parameters, startLineNumber, }) => {
8
10
  const showDiff = toolName && [WRITE_TOOL_NAME, EDIT_TOOL_NAME].includes(toolName);
9
11
  // Diff detection and transformation using typed parameters
@@ -53,6 +55,23 @@ export const DiffDisplay = ({ toolName, parameters, startLineNumber, }) => {
53
55
  };
54
56
  }
55
57
  };
58
+ // Render highlighted code for Write tool
59
+ const renderWriteContent = () => {
60
+ if (!parameters)
61
+ return null;
62
+ try {
63
+ const parsed = JSON.parse(parameters);
64
+ const content = parsed.content || "";
65
+ const filePath = parsed.file_path || "";
66
+ const ext = path.extname(filePath).slice(1);
67
+ const markdown = `\`\`\`${ext}\n${content}\n\`\`\``;
68
+ return _jsx(Markdown, { children: markdown });
69
+ }
70
+ catch (error) {
71
+ console.warn("Error rendering write content:", error);
72
+ return null;
73
+ }
74
+ };
56
75
  // Render expanded diff display
57
76
  const renderExpandedDiff = () => {
58
77
  try {
@@ -197,5 +216,7 @@ export const DiffDisplay = ({ toolName, parameters, startLineNumber, }) => {
197
216
  if (!showDiff) {
198
217
  return null;
199
218
  }
200
- return (_jsx(Box, { flexDirection: "column", children: _jsx(Box, { paddingLeft: 2, borderLeft: true, borderColor: "cyan", flexDirection: "column", children: renderExpandedDiff() }) }));
219
+ return (_jsx(Box, { flexDirection: "column", children: _jsx(Box, { paddingLeft: 2, borderLeft: true, borderColor: "cyan", flexDirection: "column", children: toolName === WRITE_TOOL_NAME
220
+ ? renderWriteContent()
221
+ : renderExpandedDiff() }) }));
201
222
  };
@@ -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,CAyjBpD,CAAC"}
@@ -55,6 +55,16 @@ export const ChatProvider = ({ children, bypassPermissions, pluginDirs, tools, w
55
55
  // Confirmation too tall state
56
56
  const [wasLastDetailsTooTall, setWasLastDetailsTooTall] = useState(0);
57
57
  const agentRef = useRef(null);
58
+ const taskUpdateTimerRef = useRef(null);
59
+ const debouncedSetTasks = useCallback((newTasks) => {
60
+ if (taskUpdateTimerRef.current) {
61
+ clearTimeout(taskUpdateTimerRef.current);
62
+ }
63
+ taskUpdateTimerRef.current = setTimeout(() => {
64
+ setTasks([...newTasks]);
65
+ taskUpdateTimerRef.current = null;
66
+ }, 100);
67
+ }, [setTasks]);
58
68
  // Permission confirmation methods with queue support
59
69
  const showConfirmation = useCallback(async (toolName, toolInput, suggestedPrefix, hidePersistentOption) => {
60
70
  return new Promise((resolve, reject) => {
@@ -95,7 +105,7 @@ export const ChatProvider = ({ children, bypassPermissions, pluginDirs, tools, w
95
105
  setBackgroundTasks([...tasks]);
96
106
  },
97
107
  onTasksChange: (tasks) => {
98
- setTasks([...tasks]);
108
+ debouncedSetTasks(tasks);
99
109
  },
100
110
  onSubagentMessagesChange: (subagentId, messages) => {
101
111
  logger.debug("onSubagentMessagesChange", subagentId, messages.length);
@@ -177,10 +187,14 @@ export const ChatProvider = ({ children, bypassPermissions, pluginDirs, tools, w
177
187
  workdir,
178
188
  worktreeSession,
179
189
  model,
190
+ debouncedSetTasks,
180
191
  ]);
181
192
  // Cleanup on unmount
182
193
  useEffect(() => {
183
194
  return () => {
195
+ if (taskUpdateTimerRef.current) {
196
+ clearTimeout(taskUpdateTimerRef.current);
197
+ }
184
198
  if (agentRef.current) {
185
199
  try {
186
200
  // Display usage summary before cleanup
@@ -1 +1 @@
1
- {"version":3,"file":"highlightUtils.d.ts","sourceRoot":"","sources":["../../src/utils/highlightUtils.ts"],"names":[],"mappings":"AA6DA,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAsCvE"}
1
+ {"version":3,"file":"highlightUtils.d.ts","sourceRoot":"","sources":["../../src/utils/highlightUtils.ts"],"names":[],"mappings":"AAqEA,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAsCvE"}
@@ -45,6 +45,14 @@ function nodeToAnsi(node) {
45
45
  const classes = node.getAttribute("class")?.split(/\s+/) || [];
46
46
  for (const className of classes) {
47
47
  if (theme[className]) {
48
+ // If content has newlines, split it and apply style to each line
49
+ // to ensure ANSI codes are correctly applied when splitting the final string by lines.
50
+ if (content.includes("\n")) {
51
+ return content
52
+ .split("\n")
53
+ .map((line) => theme[className](line))
54
+ .join("\n");
55
+ }
48
56
  return theme[className](content);
49
57
  }
50
58
  }
@@ -2,11 +2,7 @@
2
2
  * Tool parameter transformation utilities for UI rendering
3
3
  * Forces type judgment based on tool name using type assertions
4
4
  */
5
- import { type Change, type WriteToolParameters, type EditToolParameters } from "wave-agent-sdk";
6
- /**
7
- * Transform Write tool parameters to changes
8
- */
9
- export declare function transformWriteParameters(parameters: WriteToolParameters): Change[];
5
+ import { type Change, type EditToolParameters } from "wave-agent-sdk";
10
6
  /**
11
7
  * Transform Edit tool parameters to changes
12
8
  */
@@ -1 +1 @@
1
- {"version":3,"file":"toolParameterTransforms.d.ts","sourceRoot":"","sources":["../../src/utils/toolParameterTransforms.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,KAAK,MAAM,EACX,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACxB,MAAM,gBAAgB,CAAC;AAmBxB;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,mBAAmB,GAC9B,MAAM,EAAE,CAOV;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,kBAAkB,GAC7B,MAAM,EAAE,CAOV;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,eAAe,CAAC,EAAE,MAAM,GACvB,MAAM,EAAE,CA8BV"}
1
+ {"version":3,"file":"toolParameterTransforms.d.ts","sourceRoot":"","sources":["../../src/utils/toolParameterTransforms.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAmBtE;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,kBAAkB,GAC7B,MAAM,EAAE,CAOV;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,eAAe,CAAC,EAAE,MAAM,GACvB,MAAM,EAAE,CA0BV"}
@@ -18,17 +18,6 @@ function parseToolParameters(parameters) {
18
18
  return {};
19
19
  }
20
20
  }
21
- /**
22
- * Transform Write tool parameters to changes
23
- */
24
- export function transformWriteParameters(parameters) {
25
- return [
26
- {
27
- oldContent: "", // No previous content for write operations
28
- newContent: parameters.content,
29
- },
30
- ];
31
- }
32
21
  /**
33
22
  * Transform Edit tool parameters to changes
34
23
  */
@@ -52,9 +41,6 @@ export function transformToolBlockToChanges(toolName, parameters, startLineNumbe
52
41
  const parsedParams = parseToolParameters(parameters);
53
42
  let changes = [];
54
43
  switch (toolName) {
55
- case "Write":
56
- changes = transformWriteParameters(parsedParams);
57
- break;
58
44
  case "Edit":
59
45
  changes = transformEditParameters(parsedParams);
60
46
  break;
@@ -1,7 +1,7 @@
1
1
  import { execSync } from "node:child_process";
2
2
  import * as path from "node:path";
3
3
  import * as fs from "node:fs";
4
- import { getGitRepoRoot, getDefaultRemoteBranch } from "wave-agent-sdk";
4
+ import { getDefaultRemoteBranch, getGitMainRepoRoot } from "wave-agent-sdk";
5
5
  export const WORKTREE_DIR = ".wave/worktrees";
6
6
  /**
7
7
  * Create a new git worktree
@@ -10,7 +10,7 @@ export const WORKTREE_DIR = ".wave/worktrees";
10
10
  * @returns Worktree session details
11
11
  */
12
12
  export function createWorktree(name, cwd) {
13
- const repoRoot = getGitRepoRoot(cwd);
13
+ const repoRoot = getGitMainRepoRoot(cwd);
14
14
  const worktreePath = path.join(repoRoot, WORKTREE_DIR, name);
15
15
  const branchName = `worktree-${name}`;
16
16
  const baseBranch = getDefaultRemoteBranch(cwd);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-code",
3
- "version": "0.10.1",
3
+ "version": "0.10.2",
4
4
  "description": "CLI-based code assistant powered by AI, built with React and Ink",
5
5
  "repository": {
6
6
  "type": "git",
@@ -41,7 +41,7 @@
41
41
  "yargs": "^17.7.2",
42
42
  "@agentclientprotocol/sdk": "0.16.1",
43
43
  "zod": "^3.23.8",
44
- "wave-agent-sdk": "0.10.1"
44
+ "wave-agent-sdk": "0.10.2"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/react": "^19.1.8",
package/src/acp/agent.ts CHANGED
@@ -6,7 +6,9 @@ import {
6
6
  AgentToolBlockUpdateParams,
7
7
  Task,
8
8
  listSessions as listWaveSessions,
9
+ listAllSessions as listAllWaveSessions,
9
10
  deleteSession as deleteWaveSession,
11
+ truncateContent,
10
12
  } from "wave-agent-sdk";
11
13
  import * as fs from "node:fs/promises";
12
14
  import * as path from "node:path";
@@ -137,6 +139,7 @@ export class WaveAcpAgent implements AcpAgent {
137
139
  const agent = await WaveAgent.create({
138
140
  workdir: cwd,
139
141
  restoreSessionId: sessionId,
142
+ stream: false,
140
143
  canUseTool: (context) => {
141
144
  if (!agentRef.instance) {
142
145
  throw new Error("Agent instance not yet initialized");
@@ -170,19 +173,11 @@ export class WaveAcpAgent implements AcpAgent {
170
173
  // Update the callbacks object with the correct sessionId
171
174
  Object.assign(callbacks, this.createCallbacks(actualSessionId));
172
175
 
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
176
  // Send initial available commands after agent creation
177
+ // Use setImmediate to ensure the client receives the session response before the update
183
178
  setImmediate(() => {
184
179
  this.connection.sessionUpdate({
185
- sessionId: agent.sessionId as AcpSessionId,
180
+ sessionId: actualSessionId as AcpSessionId,
186
181
  update: {
187
182
  sessionUpdate: "available_commands_update",
188
183
  availableCommands: agent.getSlashCommands().map((cmd) => ({
@@ -196,6 +191,15 @@ export class WaveAcpAgent implements AcpAgent {
196
191
  });
197
192
  });
198
193
 
194
+ return agent;
195
+ }
196
+
197
+ async newSession(params: NewSessionRequest): Promise<NewSessionResponse> {
198
+ const { cwd } = params;
199
+ logger.info(`Creating new session in ${cwd}`);
200
+ const agent = await this.createAgent(undefined, cwd);
201
+ logger.info(`New session created with ID: ${agent.sessionId}`);
202
+
199
203
  return {
200
204
  sessionId: agent.sessionId as AcpSessionId,
201
205
  modes: this.getSessionModeState(agent),
@@ -208,23 +212,6 @@ export class WaveAcpAgent implements AcpAgent {
208
212
  logger.info(`Loading session: ${sessionId} in ${cwd}`);
209
213
  const agent = await this.createAgent(sessionId, cwd);
210
214
 
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
- })),
224
- },
225
- });
226
- });
227
-
228
215
  return {
229
216
  modes: this.getSessionModeState(agent),
230
217
  configOptions: this.getSessionConfigOptions(agent),
@@ -236,17 +223,21 @@ export class WaveAcpAgent implements AcpAgent {
236
223
  ): Promise<ListSessionsResponse> {
237
224
  const { cwd } = params;
238
225
  logger.info(`listSessions called with params: ${JSON.stringify(params)}`);
226
+
227
+ let waveSessions;
239
228
  if (!cwd) {
240
- logger.warn("listSessions called without cwd, returning empty list");
241
- return { sessions: [] };
229
+ logger.info("listSessions called without cwd, listing all sessions");
230
+ waveSessions = await listAllWaveSessions();
231
+ } else {
232
+ logger.info(`Listing sessions for ${cwd}`);
233
+ waveSessions = await listWaveSessions(cwd);
242
234
  }
243
235
 
244
- logger.info(`Listing sessions for ${cwd}`);
245
- const waveSessions = await listWaveSessions(cwd);
246
- logger.info(`Found ${waveSessions.length} sessions for ${cwd}`);
236
+ logger.info(`Found ${waveSessions.length} sessions`);
247
237
  const sessions: SessionInfo[] = waveSessions.map((meta) => ({
248
238
  sessionId: meta.id as AcpSessionId,
249
239
  cwd: meta.workdir,
240
+ title: meta.firstMessage ? truncateContent(meta.firstMessage) : undefined,
250
241
  updatedAt: meta.lastActiveAt.toISOString(),
251
242
  }));
252
243
  return { sessions };
@@ -378,6 +369,27 @@ export class WaveAcpAgent implements AcpAgent {
378
369
  context.toolCallId ||
379
370
  "perm-" + Math.random().toString(36).substring(2, 9);
380
371
 
372
+ let effectiveName = context.toolName;
373
+ let effectiveCompactParams: string | undefined = undefined;
374
+
375
+ if (agent?.messages && context.toolCallId) {
376
+ const toolBlock = agent.messages
377
+ .flatMap((m) => m.blocks)
378
+ .find((b) => b.type === "tool" && b.id === context.toolCallId) as
379
+ | import("wave-agent-sdk").ToolBlock
380
+ | undefined;
381
+ if (toolBlock) {
382
+ effectiveName = toolBlock.name || effectiveName;
383
+ effectiveCompactParams =
384
+ toolBlock.compactParams || effectiveCompactParams;
385
+ }
386
+ }
387
+
388
+ const displayTitle =
389
+ effectiveName && effectiveCompactParams
390
+ ? `${effectiveName}: ${effectiveCompactParams}`
391
+ : effectiveName || "Tool Call";
392
+
381
393
  const options: PermissionOption[] = [
382
394
  {
383
395
  optionId: "allow_once",
@@ -394,11 +406,6 @@ export class WaveAcpAgent implements AcpAgent {
394
406
  name: "Reject Once",
395
407
  kind: "reject_once",
396
408
  },
397
- {
398
- optionId: "reject_always",
399
- name: "Reject Always",
400
- kind: "reject_always",
401
- },
402
409
  ];
403
410
 
404
411
  const content = context.toolName
@@ -420,7 +427,7 @@ export class WaveAcpAgent implements AcpAgent {
420
427
  sessionId: sessionId as AcpSessionId,
421
428
  toolCall: {
422
429
  toolCallId,
423
- title: `Permission for ${context.toolName}`,
430
+ title: displayTitle,
424
431
  status: "pending",
425
432
  rawInput: context.toolInput,
426
433
  content,
@@ -438,21 +445,15 @@ export class WaveAcpAgent implements AcpAgent {
438
445
  logger.info(`User selected permission option: ${selectedOptionId}`);
439
446
 
440
447
  switch (selectedOptionId) {
441
- case "allow_once":
442
- return { behavior: "allow" };
443
448
  case "allow_always":
444
449
  return {
445
450
  behavior: "allow",
446
451
  newPermissionRule: `${context.toolName}(*)`,
447
452
  };
453
+ case "allow_once":
454
+ return { behavior: "allow" };
448
455
  case "reject_once":
449
456
  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
457
  default:
457
458
  return { behavior: "deny", message: "Unknown option selected" };
458
459
  }
@@ -474,7 +475,8 @@ export class WaveAcpAgent implements AcpAgent {
474
475
  if (name === "Write") {
475
476
  let oldText: string | null = null;
476
477
  try {
477
- const filePath = parameters.file_path as string;
478
+ const filePath = (parameters.file_path ||
479
+ parameters.filePath) as string;
478
480
  const fullPath = path.isAbsolute(filePath)
479
481
  ? filePath
480
482
  : path.join(workdir, filePath);
@@ -485,7 +487,7 @@ export class WaveAcpAgent implements AcpAgent {
485
487
  return [
486
488
  {
487
489
  type: "diff",
488
- path: parameters.file_path as string,
490
+ path: (parameters.file_path || parameters.filePath) as string,
489
491
  oldText,
490
492
  newText: parameters.content as string,
491
493
  },
@@ -495,7 +497,8 @@ export class WaveAcpAgent implements AcpAgent {
495
497
  let oldText: string | null = null;
496
498
  let newText: string | null = null;
497
499
  try {
498
- const filePath = parameters.file_path as string;
500
+ const filePath = (parameters.file_path ||
501
+ parameters.filePath) as string;
499
502
  const fullPath = path.isAbsolute(filePath)
500
503
  ? filePath
501
504
  : path.join(workdir, filePath);
@@ -520,7 +523,7 @@ export class WaveAcpAgent implements AcpAgent {
520
523
  return [
521
524
  {
522
525
  type: "diff",
523
- path: parameters.file_path as string,
526
+ path: (parameters.file_path || parameters.filePath) as string,
524
527
  oldText,
525
528
  newText,
526
529
  },
@@ -531,55 +534,83 @@ export class WaveAcpAgent implements AcpAgent {
531
534
  return [
532
535
  {
533
536
  type: "diff",
534
- path: parameters.file_path as string,
537
+ path: (parameters.file_path || parameters.filePath) as string,
535
538
  oldText: parameters.old_string as string,
536
539
  newText: parameters.new_string as string,
537
540
  },
538
541
  ];
539
542
  }
540
- return this.getToolContent(name, parameters);
543
+ return this.getToolContent(name, parameters, undefined);
541
544
  }
542
545
 
543
546
  private getToolContent(
544
547
  name: string,
545
548
  parameters: Record<string, unknown> | undefined,
549
+ shortResult: string | undefined,
546
550
  ): ToolCallContent[] | undefined {
547
- if (!parameters) return undefined;
548
- if (name === "Write") {
549
- return [
550
- {
551
+ const contents: ToolCallContent[] = [];
552
+ if (parameters) {
553
+ if (name === "Write") {
554
+ contents.push({
551
555
  type: "diff",
552
- path: parameters.file_path as string,
556
+ path: (parameters.file_path || parameters.filePath) as string,
553
557
  oldText: null,
554
558
  newText: parameters.content as string,
555
- },
556
- ];
557
- }
558
- if (name === "Edit") {
559
- return [
560
- {
559
+ });
560
+ } else if (name === "Edit") {
561
+ contents.push({
561
562
  type: "diff",
562
- path: parameters.file_path as string,
563
+ path: (parameters.file_path || parameters.filePath) as string,
563
564
  oldText: parameters.old_string as string,
564
565
  newText: parameters.new_string as string,
566
+ });
567
+ }
568
+ }
569
+
570
+ if (shortResult) {
571
+ contents.push({
572
+ type: "content",
573
+ content: {
574
+ type: "text",
575
+ text: shortResult,
565
576
  },
566
- ];
577
+ });
567
578
  }
568
- return undefined;
579
+
580
+ return contents.length > 0 ? contents : undefined;
569
581
  }
570
582
 
571
583
  private getToolLocations(
572
584
  name: string,
573
585
  parameters: Record<string, unknown> | undefined,
586
+ extraStartLineNumber?: number,
574
587
  ): ToolCallLocation[] | undefined {
575
588
  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
- ];
589
+ if (
590
+ name === "Write" ||
591
+ name === "Edit" ||
592
+ name === "Read" ||
593
+ name === "LSP"
594
+ ) {
595
+ const filePath = (parameters.file_path || parameters.filePath) as string;
596
+ let line =
597
+ extraStartLineNumber ??
598
+ (parameters.startLineNumber as number) ??
599
+ (parameters.line as number) ??
600
+ (parameters.offset as number);
601
+
602
+ if (name === "Write" && line === undefined) {
603
+ line = 1;
604
+ }
605
+
606
+ if (filePath) {
607
+ return [
608
+ {
609
+ path: filePath,
610
+ line: line,
611
+ },
612
+ ];
613
+ }
583
614
  }
584
615
  return undefined;
585
616
  }
@@ -605,6 +636,15 @@ export class WaveAcpAgent implements AcpAgent {
605
636
 
606
637
  private createCallbacks(sessionId: string): AgentOptions["callbacks"] {
607
638
  const getAgent = () => this.agents.get(sessionId);
639
+ const toolStates = new Map<
640
+ string,
641
+ {
642
+ name?: string;
643
+ compactParams?: string;
644
+ shortResult?: string;
645
+ startLineNumber?: number;
646
+ }
647
+ >();
608
648
  return {
609
649
  onAssistantContentUpdated: (chunk: string) => {
610
650
  this.connection.sessionUpdate({
@@ -631,7 +671,42 @@ export class WaveAcpAgent implements AcpAgent {
631
671
  });
632
672
  },
633
673
  onToolBlockUpdated: (params: AgentToolBlockUpdateParams) => {
634
- const { id, name, stage, success, error, result, parameters } = params;
674
+ const {
675
+ id,
676
+ name,
677
+ stage,
678
+ success,
679
+ error,
680
+ result,
681
+ parameters,
682
+ compactParams,
683
+ shortResult,
684
+ startLineNumber,
685
+ } = params;
686
+
687
+ let state = toolStates.get(id);
688
+ if (!state) {
689
+ state = {};
690
+ toolStates.set(id, state);
691
+ }
692
+ if (name) state.name = name;
693
+ if (compactParams) state.compactParams = compactParams;
694
+ if (shortResult) state.shortResult = shortResult;
695
+ if (startLineNumber !== undefined)
696
+ state.startLineNumber = startLineNumber;
697
+
698
+ const effectiveName = state.name || name;
699
+ const effectiveCompactParams = state.compactParams || compactParams;
700
+ const effectiveShortResult = state.shortResult || shortResult;
701
+ const effectiveStartLineNumber =
702
+ state.startLineNumber !== undefined
703
+ ? state.startLineNumber
704
+ : startLineNumber;
705
+
706
+ const displayTitle =
707
+ effectiveName && effectiveCompactParams
708
+ ? `${effectiveName}: ${effectiveCompactParams}`
709
+ : effectiveName || "Tool Call";
635
710
 
636
711
  let parsedParameters: Record<string, unknown> | undefined = undefined;
637
712
  if (parameters) {
@@ -643,14 +718,24 @@ export class WaveAcpAgent implements AcpAgent {
643
718
  }
644
719
 
645
720
  const content =
646
- name && parsedParameters
647
- ? this.getToolContent(name, parsedParameters)
721
+ effectiveName && (parsedParameters || effectiveShortResult)
722
+ ? this.getToolContent(
723
+ effectiveName,
724
+ parsedParameters,
725
+ effectiveShortResult,
726
+ )
648
727
  : undefined;
649
728
  const locations =
650
- name && parsedParameters
651
- ? this.getToolLocations(name, parsedParameters)
729
+ effectiveName && parsedParameters
730
+ ? this.getToolLocations(
731
+ effectiveName,
732
+ parsedParameters,
733
+ effectiveStartLineNumber,
734
+ )
652
735
  : undefined;
653
- const kind = name ? this.getToolKind(name) : undefined;
736
+ const kind = effectiveName
737
+ ? this.getToolKind(effectiveName)
738
+ : undefined;
654
739
 
655
740
  if (stage === "start") {
656
741
  this.connection.sessionUpdate({
@@ -658,7 +743,7 @@ export class WaveAcpAgent implements AcpAgent {
658
743
  update: {
659
744
  sessionUpdate: "tool_call",
660
745
  toolCallId: id,
661
- title: name || "Tool Call",
746
+ title: displayTitle,
662
747
  status: "pending",
663
748
  content,
664
749
  locations,
@@ -689,7 +774,7 @@ export class WaveAcpAgent implements AcpAgent {
689
774
  sessionUpdate: "tool_call_update",
690
775
  toolCallId: id,
691
776
  status,
692
- title: name || "Tool Call",
777
+ title: displayTitle,
693
778
  rawOutput: result || error,
694
779
  content,
695
780
  locations,
@@ -697,6 +782,10 @@ export class WaveAcpAgent implements AcpAgent {
697
782
  rawInput: parsedParameters,
698
783
  },
699
784
  });
785
+
786
+ if (stage === "end") {
787
+ toolStates.delete(id);
788
+ }
700
789
  },
701
790
  onTasksChange: (tasks) => {
702
791
  this.connection.sessionUpdate({
@@ -1,8 +1,14 @@
1
1
  import React, { useMemo } from "react";
2
2
  import { Box, Text } from "ink";
3
- import { WRITE_TOOL_NAME, EDIT_TOOL_NAME } from "wave-agent-sdk";
3
+ import {
4
+ WRITE_TOOL_NAME,
5
+ EDIT_TOOL_NAME,
6
+ type WriteToolParameters,
7
+ } from "wave-agent-sdk";
4
8
  import { transformToolBlockToChanges } from "../utils/toolParameterTransforms.js";
5
9
  import { diffLines, diffWords } from "diff";
10
+ import path from "path";
11
+ import { Markdown } from "./Markdown.js";
6
12
 
7
13
  interface DiffDisplayProps {
8
14
  toolName?: string;
@@ -97,6 +103,23 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
97
103
  }
98
104
  };
99
105
 
106
+ // Render highlighted code for Write tool
107
+ const renderWriteContent = () => {
108
+ if (!parameters) return null;
109
+ try {
110
+ const parsed = JSON.parse(parameters) as WriteToolParameters;
111
+ const content = parsed.content || "";
112
+ const filePath = parsed.file_path || "";
113
+ const ext = path.extname(filePath).slice(1);
114
+
115
+ const markdown = `\`\`\`${ext}\n${content}\n\`\`\``;
116
+ return <Markdown>{markdown}</Markdown>;
117
+ } catch (error) {
118
+ console.warn("Error rendering write content:", error);
119
+ return null;
120
+ }
121
+ };
122
+
100
123
  // Render expanded diff display
101
124
  const renderExpandedDiff = () => {
102
125
  try {
@@ -352,7 +375,9 @@ export const DiffDisplay: React.FC<DiffDisplayProps> = ({
352
375
  return (
353
376
  <Box flexDirection="column">
354
377
  <Box paddingLeft={2} borderLeft borderColor="cyan" flexDirection="column">
355
- {renderExpandedDiff()}
378
+ {toolName === WRITE_TOOL_NAME
379
+ ? renderWriteContent()
380
+ : renderExpandedDiff()}
356
381
  </Box>
357
382
  </Box>
358
383
  );
@@ -223,6 +223,20 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
223
223
  const [wasLastDetailsTooTall, setWasLastDetailsTooTall] = useState(0);
224
224
 
225
225
  const agentRef = useRef<Agent | null>(null);
226
+ const taskUpdateTimerRef = useRef<NodeJS.Timeout | null>(null);
227
+
228
+ const debouncedSetTasks = useCallback(
229
+ (newTasks: Task[]) => {
230
+ if (taskUpdateTimerRef.current) {
231
+ clearTimeout(taskUpdateTimerRef.current);
232
+ }
233
+ taskUpdateTimerRef.current = setTimeout(() => {
234
+ setTasks([...newTasks]);
235
+ taskUpdateTimerRef.current = null;
236
+ }, 100);
237
+ },
238
+ [setTasks],
239
+ );
226
240
 
227
241
  // Permission confirmation methods with queue support
228
242
  const showConfirmation = useCallback(
@@ -274,7 +288,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
274
288
  setBackgroundTasks([...tasks]);
275
289
  },
276
290
  onTasksChange: (tasks) => {
277
- setTasks([...tasks]);
291
+ debouncedSetTasks(tasks);
278
292
  },
279
293
  onSubagentMessagesChange: (subagentId: string, messages: Message[]) => {
280
294
  logger.debug("onSubagentMessagesChange", subagentId, messages.length);
@@ -371,11 +385,16 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
371
385
  workdir,
372
386
  worktreeSession,
373
387
  model,
388
+ debouncedSetTasks,
374
389
  ]);
375
390
 
376
391
  // Cleanup on unmount
377
392
  useEffect(() => {
378
393
  return () => {
394
+ if (taskUpdateTimerRef.current) {
395
+ clearTimeout(taskUpdateTimerRef.current);
396
+ }
397
+
379
398
  if (agentRef.current) {
380
399
  try {
381
400
  // Display usage summary before cleanup
@@ -49,6 +49,14 @@ function nodeToAnsi(node: Node): string {
49
49
 
50
50
  for (const className of classes) {
51
51
  if (theme[className]) {
52
+ // If content has newlines, split it and apply style to each line
53
+ // to ensure ANSI codes are correctly applied when splitting the final string by lines.
54
+ if (content.includes("\n")) {
55
+ return content
56
+ .split("\n")
57
+ .map((line) => theme[className](line))
58
+ .join("\n");
59
+ }
52
60
  return theme[className](content);
53
61
  }
54
62
  }
@@ -3,11 +3,7 @@
3
3
  * Forces type judgment based on tool name using type assertions
4
4
  */
5
5
 
6
- import {
7
- type Change,
8
- type WriteToolParameters,
9
- type EditToolParameters,
10
- } from "wave-agent-sdk";
6
+ import { type Change, type EditToolParameters } from "wave-agent-sdk";
11
7
  import { logger } from "./logger.js";
12
8
 
13
9
  /**
@@ -26,20 +22,6 @@ function parseToolParameters(parameters: string): unknown {
26
22
  }
27
23
  }
28
24
 
29
- /**
30
- * Transform Write tool parameters to changes
31
- */
32
- export function transformWriteParameters(
33
- parameters: WriteToolParameters,
34
- ): Change[] {
35
- return [
36
- {
37
- oldContent: "", // No previous content for write operations
38
- newContent: parameters.content,
39
- },
40
- ];
41
- }
42
-
43
25
  /**
44
26
  * Transform Edit tool parameters to changes
45
27
  */
@@ -72,10 +54,6 @@ export function transformToolBlockToChanges(
72
54
 
73
55
  let changes: Change[] = [];
74
56
  switch (toolName) {
75
- case "Write":
76
- changes = transformWriteParameters(parsedParams as WriteToolParameters);
77
- break;
78
-
79
57
  case "Edit":
80
58
  changes = transformEditParameters(parsedParams as EditToolParameters);
81
59
  break;
@@ -1,7 +1,7 @@
1
1
  import { execSync } from "node:child_process";
2
2
  import * as path from "node:path";
3
3
  import * as fs from "node:fs";
4
- import { getGitRepoRoot, getDefaultRemoteBranch } from "wave-agent-sdk";
4
+ import { getDefaultRemoteBranch, getGitMainRepoRoot } from "wave-agent-sdk";
5
5
 
6
6
  export interface WorktreeSession {
7
7
  name: string;
@@ -22,7 +22,7 @@ export const WORKTREE_DIR = ".wave/worktrees";
22
22
  * @returns Worktree session details
23
23
  */
24
24
  export function createWorktree(name: string, cwd: string): WorktreeSession {
25
- const repoRoot = getGitRepoRoot(cwd);
25
+ const repoRoot = getGitMainRepoRoot(cwd);
26
26
  const worktreePath = path.join(repoRoot, WORKTREE_DIR, name);
27
27
  const branchName = `worktree-${name}`;
28
28
  const baseBranch = getDefaultRemoteBranch(cwd);