wave-code 0.12.10 → 0.13.0

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":"AAoBA,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,EAMpC,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;IAkC3B,OAAO,CAAC,uBAAuB;YAmBjB,gBAAgB;IASxB,UAAU,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAwBzC,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;IAc5D,sBAAsB,CAC1B,MAAM,EAAE,6BAA6B,GACpC,OAAO,CAAC,8BAA8B,CAAC;IAqBpC,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;IAyDtD,MAAM,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IASvD,OAAO,CAAC,kBAAkB;YA2BZ,uBAAuB;IA8KrC,OAAO,CAAC,cAAc;IAwGtB,OAAO,CAAC,gBAAgB;IAmCxB,OAAO,CAAC,WAAW;IAmBnB,OAAO,CAAC,eAAe;CAmMxB"}
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/acp/agent.ts"],"names":[],"mappings":"AAqBA,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,EAMpC,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;IAkC3B,OAAO,CAAC,uBAAuB;YAmCjB,gBAAgB;IASxB,UAAU,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAwBzC,YAAY,IAAI,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;YAI5C,WAAW;IAkEnB,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;IAc5D,sBAAsB,CAC1B,MAAM,EAAE,6BAA6B,GACpC,OAAO,CAAC,8BAA8B,CAAC;IAuBpC,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;IAyDtD,MAAM,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IASvD,OAAO,CAAC,kBAAkB;YA2BZ,uBAAuB;IA8LrC,OAAO,CAAC,cAAc;IAwGtB,OAAO,CAAC,gBAAgB;IAmCxB,OAAO,CAAC,WAAW;IAmBnB,OAAO,CAAC,eAAe;CA+MxB"}
package/dist/acp/agent.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Agent as WaveAgent, listSessions as listWaveSessions, listAllSessions as listAllWaveSessions, deleteSession as deleteWaveSession, truncateContent, BASH_TOOL_NAME, EDIT_TOOL_NAME, WRITE_TOOL_NAME, EXIT_PLAN_MODE_TOOL_NAME, ASK_USER_QUESTION_TOOL_NAME, } from "wave-agent-sdk";
1
+ import { Agent as WaveAgent, listSessions as listWaveSessions, listAllSessions as listAllWaveSessions, deleteSession as deleteWaveSession, truncateContent, BASH_TOOL_NAME, EDIT_TOOL_NAME, WRITE_TOOL_NAME, EXIT_PLAN_MODE_TOOL_NAME, ENTER_PLAN_MODE_TOOL_NAME, ASK_USER_QUESTION_TOOL_NAME, } from "wave-agent-sdk";
2
2
  import { logger } from "../utils/logger.js";
3
3
  import { AGENT_METHODS, } from "@agentclientprotocol/sdk";
4
4
  export class WaveAcpAgent {
@@ -39,10 +39,13 @@ export class WaveAcpAgent {
39
39
  };
40
40
  }
41
41
  getSessionConfigOptions(agent) {
42
+ const configuredModels = agent.getConfiguredModels();
43
+ const currentModel = agent.getModelConfig().model;
42
44
  return [
43
45
  {
44
46
  id: "permission_mode",
45
47
  name: "Permission Mode",
48
+ description: "Controls how the agent requests permission",
46
49
  type: "select",
47
50
  category: "mode",
48
51
  currentValue: agent.getPermissionMode(),
@@ -54,6 +57,18 @@ export class WaveAcpAgent {
54
57
  { value: "dontAsk", name: "Don't Ask" },
55
58
  ],
56
59
  },
60
+ {
61
+ id: "model",
62
+ name: "Model",
63
+ description: "The AI model to use for this session",
64
+ type: "select",
65
+ category: "model",
66
+ currentValue: currentModel,
67
+ options: configuredModels.map((m) => ({
68
+ value: m,
69
+ name: m,
70
+ })),
71
+ },
57
72
  ];
58
73
  }
59
74
  async cleanupAllAgents() {
@@ -110,6 +125,7 @@ export class WaveAcpAgent {
110
125
  },
111
126
  onTasksChange: (tasks) => callbacks.onTasksChange?.(tasks),
112
127
  onPermissionModeChange: (mode) => callbacks.onPermissionModeChange?.(mode),
128
+ onModelChange: (model) => callbacks.onModelChange?.(model),
113
129
  },
114
130
  });
115
131
  agentRef.instance = agent;
@@ -211,6 +227,9 @@ export class WaveAcpAgent {
211
227
  if (configId === "permission_mode") {
212
228
  agent.setPermissionMode(value);
213
229
  }
230
+ else if (configId === "model" && typeof value === "string") {
231
+ agent.setModel(value);
232
+ }
214
233
  return {
215
234
  configOptions: this.getSessionConfigOptions(agent),
216
235
  };
@@ -366,6 +385,20 @@ export class WaveAcpAgent {
366
385
  },
367
386
  ];
368
387
  }
388
+ else if (context.toolName === ENTER_PLAN_MODE_TOOL_NAME) {
389
+ options = [
390
+ {
391
+ optionId: "allow_once",
392
+ name: "Yes, enter plan mode",
393
+ kind: "allow_once",
394
+ },
395
+ {
396
+ optionId: "reject_once",
397
+ name: "No, start implementing now",
398
+ kind: "reject_once",
399
+ },
400
+ ];
401
+ }
369
402
  else if (context.toolName === ASK_USER_QUESTION_TOOL_NAME) {
370
403
  options = [];
371
404
  }
@@ -429,6 +462,9 @@ export class WaveAcpAgent {
429
462
  if (context.toolName === EXIT_PLAN_MODE_TOOL_NAME) {
430
463
  return { behavior: "allow", newPermissionMode: "default" };
431
464
  }
465
+ if (context.toolName === ENTER_PLAN_MODE_TOOL_NAME) {
466
+ return { behavior: "allow", newPermissionMode: "plan" };
467
+ }
432
468
  return { behavior: "allow" };
433
469
  case "reject_once":
434
470
  return { behavior: "deny", message: "Rejected by user" };
@@ -729,6 +765,18 @@ export class WaveAcpAgent {
729
765
  });
730
766
  }
731
767
  },
768
+ onModelChange: () => {
769
+ const agent = getAgent();
770
+ if (agent) {
771
+ this.connection.sessionUpdate({
772
+ sessionId: sessionId,
773
+ update: {
774
+ sessionUpdate: "config_option_update",
775
+ configOptions: this.getSessionConfigOptions(agent),
776
+ },
777
+ });
778
+ }
779
+ },
732
780
  };
733
781
  }
734
782
  }
@@ -2,10 +2,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from "ink";
3
3
  import { getLastLines } from "wave-agent-sdk";
4
4
  export const BangDisplay = ({ block, isExpanded = false, }) => {
5
- const { command, output, isRunning, exitCode } = block;
5
+ const { command, output, stage, exitCode } = block;
6
6
  const MAX_LINES = 3; // Set maximum display lines
7
7
  const getStatusColor = () => {
8
- if (isRunning)
8
+ if (stage === "running")
9
9
  return "yellow";
10
10
  if (exitCode === 0)
11
11
  return "green";
@@ -1 +1 @@
1
- {"version":3,"file":"ConfirmationDetails.d.ts","sourceRoot":"","sources":["../../src/components/ConfirmationDetails.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAsC1B,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CAoDlE,CAAC"}
1
+ {"version":3,"file":"ConfirmationDetails.d.ts","sourceRoot":"","sources":["../../src/components/ConfirmationDetails.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAyC1B,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CAqDlE,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Box, Text } from "ink";
3
- import { BASH_TOOL_NAME, EDIT_TOOL_NAME, WRITE_TOOL_NAME, EXIT_PLAN_MODE_TOOL_NAME, ASK_USER_QUESTION_TOOL_NAME, } from "wave-agent-sdk";
3
+ import { BASH_TOOL_NAME, EDIT_TOOL_NAME, WRITE_TOOL_NAME, EXIT_PLAN_MODE_TOOL_NAME, ENTER_PLAN_MODE_TOOL_NAME, ASK_USER_QUESTION_TOOL_NAME, } from "wave-agent-sdk";
4
4
  import { DiffDisplay } from "./DiffDisplay.js";
5
5
  import { PlanDisplay } from "./PlanDisplay.js";
6
6
  import { highlightToAnsi } from "../utils/highlightUtils.js";
@@ -18,6 +18,8 @@ const getActionDescription = (toolName, toolInput) => {
18
18
  return `Write to file: ${toolInput.file_path || "unknown file"}`;
19
19
  case EXIT_PLAN_MODE_TOOL_NAME:
20
20
  return "Review and approve the plan";
21
+ case ENTER_PLAN_MODE_TOOL_NAME:
22
+ return "Enter plan mode for complex task planning";
21
23
  case ASK_USER_QUESTION_TOOL_NAME:
22
24
  return "Answer questions to clarify intent";
23
25
  default:
@@ -30,6 +32,7 @@ export const ConfirmationDetails = ({ toolName, toolInput, planContent, isExpand
30
32
  const content = (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "yellow", borderBottom: false, borderLeft: false, borderRight: false, children: [_jsxs(Text, { color: "yellow", bold: true, children: ["Tool: ", toolName] }), _jsx(Text, { color: "yellow", children: getActionDescription(toolName, toolInput) }), _jsx(DiffDisplay, { toolName: toolName, parameters: JSON.stringify(toolInput), startLineNumber: startLineNumber }), toolName !== WRITE_TOOL_NAME &&
31
33
  toolName !== EDIT_TOOL_NAME &&
32
34
  toolName !== EXIT_PLAN_MODE_TOOL_NAME &&
35
+ toolName !== ENTER_PLAN_MODE_TOOL_NAME &&
33
36
  toolName !== ASK_USER_QUESTION_TOOL_NAME &&
34
37
  toolName !== BASH_TOOL_NAME &&
35
38
  !!toolInput && (_jsx(Box, { paddingLeft: 2, borderLeft: true, borderColor: "cyan", children: _jsx(Text, { children: highlightToAnsi(JSON.stringify(toolInput, null, 2), "json") }) })), toolName !== ASK_USER_QUESTION_TOOL_NAME &&
@@ -1 +1 @@
1
- {"version":3,"file":"ConfirmationSelector.d.ts","sourceRoot":"","sources":["../../src/components/ConfirmationSelector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAE3D,OAAO,KAAK,EAAE,kBAAkB,EAAwB,MAAM,gBAAgB,CAAC;AAgB/E,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACnD,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AASD,eAAO,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CAgnBpE,CAAC"}
1
+ {"version":3,"file":"ConfirmationSelector.d.ts","sourceRoot":"","sources":["../../src/components/ConfirmationSelector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAE3D,OAAO,KAAK,EAAE,kBAAkB,EAAwB,MAAM,gBAAgB,CAAC;AAiB/E,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACnD,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AASD,eAAO,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CA6nBpE,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, useState } from "react";
3
3
  import { Box, Text, useInput } from "ink";
4
- import { BASH_TOOL_NAME, EXIT_PLAN_MODE_TOOL_NAME, ASK_USER_QUESTION_TOOL_NAME, } from "wave-agent-sdk";
4
+ import { BASH_TOOL_NAME, EXIT_PLAN_MODE_TOOL_NAME, ENTER_PLAN_MODE_TOOL_NAME, ASK_USER_QUESTION_TOOL_NAME, } from "wave-agent-sdk";
5
5
  const getHeaderColor = (header) => {
6
6
  const colors = ["red", "green", "blue", "magenta", "cyan"];
7
7
  let hash = 0;
@@ -285,6 +285,9 @@ export const ConfirmationSelector = ({ toolName, toolInput, suggestedPrefix, hid
285
285
  if (toolName === EXIT_PLAN_MODE_TOOL_NAME) {
286
286
  onDecision({ behavior: "allow", newPermissionMode: "default" });
287
287
  }
288
+ else if (toolName === ENTER_PLAN_MODE_TOOL_NAME) {
289
+ onDecision({ behavior: "allow", newPermissionMode: "plan" });
290
+ }
288
291
  else {
289
292
  onDecision({ behavior: "allow" });
290
293
  }
@@ -302,6 +305,9 @@ export const ConfirmationSelector = ({ toolName, toolInput, suggestedPrefix, hid
302
305
  onDecision({ behavior: "allow", newPermissionRule: rule });
303
306
  }
304
307
  }
308
+ else if (toolName === ENTER_PLAN_MODE_TOOL_NAME) {
309
+ onDecision({ behavior: "allow", newPermissionMode: "plan" });
310
+ }
305
311
  else if (toolName.startsWith("mcp__")) {
306
312
  onDecision({ behavior: "allow", newPermissionRule: toolName });
307
313
  }
@@ -312,6 +318,12 @@ export const ConfirmationSelector = ({ toolName, toolInput, suggestedPrefix, hid
312
318
  else if (state.alternativeText.trim()) {
313
319
  onDecision({ behavior: "deny", message: state.alternativeText.trim() });
314
320
  }
321
+ else if (toolName === ENTER_PLAN_MODE_TOOL_NAME) {
322
+ onDecision({
323
+ behavior: "deny",
324
+ message: "User chose not to enter plan mode",
325
+ });
326
+ }
315
327
  return;
316
328
  }
317
329
  if (state.selectedOption === "alternative") {
@@ -421,6 +433,6 @@ export const ConfirmationSelector = ({ toolName, toolInput, suggestedPrefix, hid
421
433
  : "", option.label, option.description ? ` - ${option.description}` : "", isOther && isSelected && (_jsxs(Text, { children: [":", " ", questionState.otherText ? (_jsxs(_Fragment, { children: [questionState.otherText.slice(0, questionState.otherCursorPosition), _jsx(Text, { backgroundColor: "white", color: "black", children: questionState.otherText[questionState.otherCursorPosition] || " " }), questionState.otherText.slice(questionState.otherCursorPosition + 1)] })) : (_jsx(Text, { color: "gray", dimColor: true, children: "[Type your answer...]" }))] }))] }) }, index));
422
434
  }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Question ", questionState.currentQuestionIndex + 1, " of", " ", questions.length, " \u2022", currentQuestion.multiSelect ? " Space to toggle •" : "", " Use \u2191\u2193 or Tab to navigate \u2022 Enter to confirm"] }) })] })), toolName !== ASK_USER_QUESTION_TOOL_NAME && !isExpanded && (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, children: _jsx(Text, { children: "Do you want to proceed?" }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [toolName === EXIT_PLAN_MODE_TOOL_NAME && (_jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "clear" ? "black" : "white", backgroundColor: state.selectedOption === "clear" ? "yellow" : undefined, bold: state.selectedOption === "clear", children: [state.selectedOption === "clear" ? "> " : " ", "Yes, clear context and auto-accept edits"] }) }, "clear-option")), _jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "allow" ? "black" : "white", backgroundColor: state.selectedOption === "allow" ? "yellow" : undefined, bold: state.selectedOption === "allow", children: [state.selectedOption === "allow" ? "> " : " ", toolName === EXIT_PLAN_MODE_TOOL_NAME
423
435
  ? "Yes, manually approve edits"
424
- : "Yes, proceed"] }) }, "allow-option"), !hidePersistentOption && (_jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "auto" ? "black" : "white", backgroundColor: state.selectedOption === "auto" ? "yellow" : undefined, bold: state.selectedOption === "auto", children: [state.selectedOption === "auto" ? "> " : " ", getAutoOptionText()] }) }, "auto-option")), _jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "alternative" ? "black" : "white", backgroundColor: state.selectedOption === "alternative" ? "yellow" : undefined, bold: state.selectedOption === "alternative", children: [state.selectedOption === "alternative" ? "> " : " ", showPlaceholder ? (_jsx(Text, { color: "gray", dimColor: true, children: placeholderText })) : (_jsx(Text, { children: state.alternativeText ? (_jsxs(_Fragment, { children: [state.alternativeText.slice(0, state.alternativeCursorPosition), _jsx(Text, { backgroundColor: "white", color: "black", children: state.alternativeText[state.alternativeCursorPosition] || " " }), state.alternativeText.slice(state.alternativeCursorPosition + 1)] })) : ("Type here to tell Wave what to change") }))] }) }, "alternative-option")] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Use \u2191\u2193 or Tab to navigate \u2022 ESC to cancel" }) })] }))] }));
436
+ : "Yes, proceed"] }) }, "allow-option"), !hidePersistentOption && (_jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "auto" ? "black" : "white", backgroundColor: state.selectedOption === "auto" ? "yellow" : undefined, bold: state.selectedOption === "auto", children: [state.selectedOption === "auto" ? "> " : " ", getAutoOptionText()] }) }, "auto-option")), _jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "alternative" ? "black" : "white", backgroundColor: state.selectedOption === "alternative" ? "yellow" : undefined, bold: state.selectedOption === "alternative", children: [state.selectedOption === "alternative" ? "> " : " ", toolName === ENTER_PLAN_MODE_TOOL_NAME && showPlaceholder ? (_jsx(Text, { color: "gray", dimColor: true, children: "No, start implementing now" })) : showPlaceholder ? (_jsx(Text, { color: "gray", dimColor: true, children: placeholderText })) : (_jsx(Text, { children: state.alternativeText ? (_jsxs(_Fragment, { children: [state.alternativeText.slice(0, state.alternativeCursorPosition), _jsx(Text, { backgroundColor: "white", color: "black", children: state.alternativeText[state.alternativeCursorPosition] || " " }), state.alternativeText.slice(state.alternativeCursorPosition + 1)] })) : ("Type here to tell Wave what to change") }))] }) }, "alternative-option")] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Use \u2191\u2193 or Tab to navigate \u2022 ESC to cancel" }) })] }))] }));
425
437
  };
426
438
  ConfirmationSelector.displayName = "ConfirmationSelector";
@@ -1 +1 @@
1
- {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../src/components/MessageList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAAE,OAAO,EAAgB,MAAM,gBAAgB,CAAC;AAG5D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,WAAW,uFAOnB,gBAAgB,6CAwGpB,CAAC"}
1
+ {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../src/components/MessageList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAAE,OAAO,EAAgB,MAAM,gBAAgB,CAAC;AAG5D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,WAAW,uFAOnB,gBAAgB,6CAuIpB,CAAC"}
@@ -9,11 +9,18 @@ export const MessageList = React.memo(({ messages, isExpanded = false, forceStat
9
9
  const maxMessages = 10;
10
10
  // Filter out meta messages
11
11
  const visibleMessages = messages.filter((m) => !m.isMeta);
12
- const isRunning = (b) => (b.type === "tool" && b.stage === "running") ||
13
- (b.type === "bang" && b.isRunning);
12
+ const isRunning = (b) => (b.type === "tool" &&
13
+ (b.stage === "running" ||
14
+ b.stage === "streaming" ||
15
+ b.stage === "start")) ||
16
+ (b.type === "bang" && b.stage === "running");
14
17
  // Flatten messages into blocks with metadata
18
+ // Filter out streaming text/reasoning blocks to avoid frequent height changes
15
19
  const allBlocks = visibleMessages.flatMap((message, messageIndex) => {
16
- return message.blocks.map((block, blockIndex) => ({
20
+ return message.blocks
21
+ .filter((block) => !((block.type === "text" || block.type === "reasoning") &&
22
+ block.stage === "streaming"))
23
+ .map((block, blockIndex) => ({
17
24
  block,
18
25
  message,
19
26
  messageIndex,
@@ -21,11 +28,27 @@ export const MessageList = React.memo(({ messages, isExpanded = false, forceStat
21
28
  key: `${message.id}-${blockIndex}`,
22
29
  }));
23
30
  });
31
+ // Find message indices that have any running/streaming block
32
+ const runningMessageIndices = new Set();
33
+ for (const item of allBlocks) {
34
+ if (isRunning(item.block)) {
35
+ runningMessageIndices.add(item.messageIndex);
36
+ }
37
+ }
24
38
  // Determine which blocks are static vs dynamic
25
- // Only mark individual running blocks as dynamic, not entire messages
39
+ // Blocks not in the last message are always static.
40
+ // For the last message: if any block is running/streaming, blocks in that message are dynamic,
41
+ // except text/reasoning blocks that have already completed (stage === "end")
42
+ const lastMessageIndex = visibleMessages.length - 1;
26
43
  const blocksWithStatus = allBlocks.map((item) => {
27
- const { block } = item;
28
- const isDynamic = !forceStatic && !isExpanded && isRunning(block);
44
+ const isInLastMessage = item.messageIndex === lastMessageIndex;
45
+ const isBlockCompleted = (item.block.type === "text" || item.block.type === "reasoning") &&
46
+ item.block.stage === "end";
47
+ const isDynamic = !forceStatic &&
48
+ !isExpanded &&
49
+ isInLastMessage &&
50
+ !isBlockCompleted &&
51
+ runningMessageIndices.has(item.messageIndex);
29
52
  return { ...item, isDynamic };
30
53
  });
31
54
  const staticBlocks = blocksWithStatus.filter((b) => !b.isDynamic);
@@ -1 +1 @@
1
- {"version":3,"file":"ToolDisplay.d.ts","sourceRoot":"","sources":["../../src/components/ToolDisplay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAIhD,UAAU,gBAAgB;IACxB,KAAK,EAAE,SAAS,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CA+HlD,CAAC"}
1
+ {"version":3,"file":"ToolDisplay.d.ts","sourceRoot":"","sources":["../../src/components/ToolDisplay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAIhD,UAAU,gBAAgB;IACxB,KAAK,EAAE,SAAS,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CA0IlD,CAAC"}
@@ -37,5 +37,11 @@ export const ToolDisplay = ({ block, isExpanded = false, }) => {
37
37
  return null;
38
38
  };
39
39
  const shortResult = getShortResult();
40
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsxs(Box, { flexShrink: 0, children: [_jsx(Text, { color: getStatusColor(), children: "\u25CF " }), _jsx(Text, { color: "white", children: toolName })] }), !isExpanded && compactParams && (_jsxs(Text, { color: "gray", children: [" ", compactParams] })), hasImages() && _jsxs(Text, { color: "blue", children: [" ", getImageIndicator()] })] }), !isExpanded && shortResult && !error && (_jsx(Box, { paddingLeft: 2, borderLeft: true, borderColor: "gray", flexDirection: "column", children: shortResult.split("\n").map((line, index) => (_jsx(Text, { color: "gray", wrap: "truncate-end", children: line }, index))) })), isExpanded && parameters && (_jsxs(Box, { paddingLeft: 2, borderLeft: true, borderColor: "gray", flexDirection: "column", children: [_jsx(Text, { color: "cyan", bold: true, children: "Parameters:" }), _jsx(Text, { color: "gray", children: parameters })] })), isExpanded && result && (_jsx(Box, { flexDirection: "column", children: _jsxs(Box, { paddingLeft: 2, borderLeft: true, borderColor: "green", flexDirection: "column", children: [_jsx(Text, { color: "cyan", bold: true, children: "Result:" }), _jsx(Text, { color: "white", children: result })] }) })), error && (_jsx(Box, { children: _jsxs(Text, { color: "red", children: ["Error: ", typeof error === "string" ? error : String(error)] }) })), !isExpanded && stage === "end" && success && (_jsx(DiffDisplay, { toolName: name, parameters: parameters, startLineNumber: block.startLineNumber }))] }));
40
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsxs(Box, { flexShrink: 0, children: [_jsx(Text, { color: getStatusColor(), children: "\u25CF " }), _jsx(Text, { color: "white", children: toolName })] }), !isExpanded && compactParams && (_jsxs(Text, { color: "gray", children: [" ", compactParams] })), !isExpanded &&
41
+ stage === "streaming" &&
42
+ parameters &&
43
+ !compactParams && (_jsx(Text, { color: "gray", wrap: "truncate-end", children: ` ${(() => {
44
+ const flat = parameters.replace(/\n/g, "\\n");
45
+ return flat.length > 30 ? `…${flat.slice(-30)}` : flat;
46
+ })()}` })), hasImages() && _jsxs(Text, { color: "blue", children: [" ", getImageIndicator()] })] }), !isExpanded && shortResult && !error && (_jsx(Box, { paddingLeft: 2, borderLeft: true, borderColor: "gray", flexDirection: "column", children: shortResult.split("\n").map((line, index) => (_jsx(Text, { color: "gray", wrap: "truncate-end", children: line }, index))) })), isExpanded && parameters && (_jsxs(Box, { paddingLeft: 2, borderLeft: true, borderColor: "gray", flexDirection: "column", children: [_jsx(Text, { color: "cyan", bold: true, children: "Parameters:" }), _jsx(Text, { color: "gray", children: parameters })] })), isExpanded && result && (_jsx(Box, { flexDirection: "column", children: _jsxs(Box, { paddingLeft: 2, borderLeft: true, borderColor: "green", flexDirection: "column", children: [_jsx(Text, { color: "cyan", bold: true, children: "Result:" }), _jsx(Text, { color: "white", children: result })] }) })), error && (_jsx(Box, { children: _jsxs(Text, { color: "red", children: ["Error: ", typeof error === "string" ? error : String(error)] }) })), !isExpanded && stage === "end" && success && (_jsx(DiffDisplay, { toolName: name, parameters: parameters, startLineNumber: block.startLineNumber }))] }));
41
47
  };
@@ -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":"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"}
@@ -29,17 +29,22 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
29
29
  setMessages([...agentRef.current.messages]);
30
30
  }
31
31
  }, 300, { leading: true, trailing: true }), []);
32
+ const throttledSetTokens = useMemo(() => throttle(((tokens) => {
33
+ setlatestTotalTokens(tokens);
34
+ }), 300, { leading: true, trailing: true }), []);
32
35
  useEffect(() => {
33
36
  isExpandedRef.current = isExpanded;
34
37
  if (isExpanded) {
35
38
  throttledSetMessages.cancel();
39
+ throttledSetTokens.cancel();
36
40
  }
37
- }, [isExpanded, throttledSetMessages]);
41
+ }, [isExpanded, throttledSetMessages, throttledSetTokens]);
38
42
  useEffect(() => {
39
43
  return () => {
40
44
  throttledSetMessages.cancel();
45
+ throttledSetTokens.cancel();
41
46
  };
42
- }, [throttledSetMessages]);
47
+ }, [throttledSetMessages, throttledSetTokens]);
43
48
  const [isLoading, setIsLoading] = useState(false);
44
49
  const [latestTotalTokens, setlatestTotalTokens] = useState(0);
45
50
  const [sessionId, setSessionId] = useState("");
@@ -122,7 +127,7 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
122
127
  setSessionId(sessionId);
123
128
  },
124
129
  onLatestTotalTokensChange: (tokens) => {
125
- setlatestTotalTokens(tokens);
130
+ throttledSetTokens(tokens);
126
131
  },
127
132
  onCompressionStateChange: (isCompressingState) => {
128
133
  setIsCompressing(isCompressingState);
@@ -167,7 +172,7 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
167
172
  permissionMode: initialPermissionMode ||
168
173
  (bypassPermissions ? "bypassPermissions" : undefined),
169
174
  canUseTool: permissionCallback,
170
- stream: false, // 关闭流式模式
175
+ stream: true,
171
176
  plugins: pluginDirs?.map((path) => ({ type: "local", path })),
172
177
  tools,
173
178
  allowedTools,
@@ -215,6 +220,7 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
215
220
  model,
216
221
  initialPermissionMode,
217
222
  throttledSetMessages,
223
+ throttledSetTokens,
218
224
  ]);
219
225
  // Cleanup on unmount
220
226
  useEffect(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-code",
3
- "version": "0.12.10",
3
+ "version": "0.13.0",
4
4
  "description": "CLI-based code assistant powered by AI, built with React and Ink",
5
5
  "repository": {
6
6
  "type": "git",
@@ -42,7 +42,7 @@
42
42
  "semver": "^7.7.4",
43
43
  "yargs": "^17.7.2",
44
44
  "zod": "^3.23.8",
45
- "wave-agent-sdk": "0.12.10"
45
+ "wave-agent-sdk": "0.13.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/react": "^19.1.8",
package/src/acp/agent.ts CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  EDIT_TOOL_NAME,
14
14
  WRITE_TOOL_NAME,
15
15
  EXIT_PLAN_MODE_TOOL_NAME,
16
+ ENTER_PLAN_MODE_TOOL_NAME,
16
17
  ASK_USER_QUESTION_TOOL_NAME,
17
18
  AskUserQuestion,
18
19
  AskUserQuestionOption,
@@ -94,10 +95,14 @@ export class WaveAcpAgent implements AcpAgent {
94
95
  }
95
96
 
96
97
  private getSessionConfigOptions(agent: WaveAgent): SessionConfigOption[] {
98
+ const configuredModels = agent.getConfiguredModels();
99
+ const currentModel = agent.getModelConfig().model;
100
+
97
101
  return [
98
102
  {
99
103
  id: "permission_mode",
100
104
  name: "Permission Mode",
105
+ description: "Controls how the agent requests permission",
101
106
  type: "select",
102
107
  category: "mode",
103
108
  currentValue: agent.getPermissionMode(),
@@ -109,6 +114,18 @@ export class WaveAcpAgent implements AcpAgent {
109
114
  { value: "dontAsk", name: "Don't Ask" },
110
115
  ],
111
116
  },
117
+ {
118
+ id: "model",
119
+ name: "Model",
120
+ description: "The AI model to use for this session",
121
+ type: "select",
122
+ category: "model",
123
+ currentValue: currentModel,
124
+ options: configuredModels.map((m) => ({
125
+ value: m,
126
+ name: m,
127
+ })),
128
+ },
112
129
  ];
113
130
  }
114
131
 
@@ -183,6 +200,7 @@ export class WaveAcpAgent implements AcpAgent {
183
200
  onTasksChange: (tasks) => callbacks.onTasksChange?.(tasks as Task[]),
184
201
  onPermissionModeChange: (mode) =>
185
202
  callbacks.onPermissionModeChange?.(mode),
203
+ onModelChange: (model) => callbacks.onModelChange?.(model),
186
204
  },
187
205
  });
188
206
 
@@ -319,6 +337,8 @@ export class WaveAcpAgent implements AcpAgent {
319
337
  | "bypassPermissions"
320
338
  | "dontAsk",
321
339
  );
340
+ } else if (configId === "model" && typeof value === "string") {
341
+ agent.setModel(value);
322
342
  }
323
343
 
324
344
  return {
@@ -502,6 +522,19 @@ export class WaveAcpAgent implements AcpAgent {
502
522
  kind: "allow_always",
503
523
  },
504
524
  ];
525
+ } else if (context.toolName === ENTER_PLAN_MODE_TOOL_NAME) {
526
+ options = [
527
+ {
528
+ optionId: "allow_once",
529
+ name: "Yes, enter plan mode",
530
+ kind: "allow_once",
531
+ },
532
+ {
533
+ optionId: "reject_once",
534
+ name: "No, start implementing now",
535
+ kind: "reject_once",
536
+ },
537
+ ];
505
538
  } else if (context.toolName === ASK_USER_QUESTION_TOOL_NAME) {
506
539
  options = [];
507
540
  }
@@ -578,6 +611,9 @@ export class WaveAcpAgent implements AcpAgent {
578
611
  if (context.toolName === EXIT_PLAN_MODE_TOOL_NAME) {
579
612
  return { behavior: "allow", newPermissionMode: "default" };
580
613
  }
614
+ if (context.toolName === ENTER_PLAN_MODE_TOOL_NAME) {
615
+ return { behavior: "allow", newPermissionMode: "plan" };
616
+ }
581
617
  return { behavior: "allow" };
582
618
  case "reject_once":
583
619
  return { behavior: "deny", message: "Rejected by user" };
@@ -944,6 +980,18 @@ export class WaveAcpAgent implements AcpAgent {
944
980
  });
945
981
  }
946
982
  },
983
+ onModelChange: () => {
984
+ const agent = getAgent();
985
+ if (agent) {
986
+ this.connection.sessionUpdate({
987
+ sessionId: sessionId as AcpSessionId,
988
+ update: {
989
+ sessionUpdate: "config_option_update",
990
+ configOptions: this.getSessionConfigOptions(agent),
991
+ },
992
+ });
993
+ }
994
+ },
947
995
  };
948
996
  }
949
997
  }
@@ -12,11 +12,11 @@ export const BangDisplay: React.FC<BangDisplayProps> = ({
12
12
  block,
13
13
  isExpanded = false,
14
14
  }) => {
15
- const { command, output, isRunning, exitCode } = block;
15
+ const { command, output, stage, exitCode } = block;
16
16
  const MAX_LINES = 3; // Set maximum display lines
17
17
 
18
18
  const getStatusColor = () => {
19
- if (isRunning) return "yellow";
19
+ if (stage === "running") return "yellow";
20
20
  if (exitCode === 0) return "green";
21
21
  if (exitCode !== null && exitCode !== 0) return "red";
22
22
  return "gray"; // Unknown state
@@ -5,6 +5,7 @@ import {
5
5
  EDIT_TOOL_NAME,
6
6
  WRITE_TOOL_NAME,
7
7
  EXIT_PLAN_MODE_TOOL_NAME,
8
+ ENTER_PLAN_MODE_TOOL_NAME,
8
9
  ASK_USER_QUESTION_TOOL_NAME,
9
10
  } from "wave-agent-sdk";
10
11
  import { DiffDisplay } from "./DiffDisplay.js";
@@ -29,6 +30,8 @@ const getActionDescription = (
29
30
  return `Write to file: ${toolInput.file_path || "unknown file"}`;
30
31
  case EXIT_PLAN_MODE_TOOL_NAME:
31
32
  return "Review and approve the plan";
33
+ case ENTER_PLAN_MODE_TOOL_NAME:
34
+ return "Enter plan mode for complex task planning";
32
35
  case ASK_USER_QUESTION_TOOL_NAME:
33
36
  return "Answer questions to clarify intent";
34
37
  default:
@@ -76,6 +79,7 @@ export const ConfirmationDetails: React.FC<ConfirmationDetailsProps> = ({
76
79
  {toolName !== WRITE_TOOL_NAME &&
77
80
  toolName !== EDIT_TOOL_NAME &&
78
81
  toolName !== EXIT_PLAN_MODE_TOOL_NAME &&
82
+ toolName !== ENTER_PLAN_MODE_TOOL_NAME &&
79
83
  toolName !== ASK_USER_QUESTION_TOOL_NAME &&
80
84
  toolName !== BASH_TOOL_NAME &&
81
85
  !!toolInput && (
@@ -4,6 +4,7 @@ import type { PermissionDecision, AskUserQuestionInput } from "wave-agent-sdk";
4
4
  import {
5
5
  BASH_TOOL_NAME,
6
6
  EXIT_PLAN_MODE_TOOL_NAME,
7
+ ENTER_PLAN_MODE_TOOL_NAME,
7
8
  ASK_USER_QUESTION_TOOL_NAME,
8
9
  } from "wave-agent-sdk";
9
10
 
@@ -353,6 +354,8 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
353
354
  } else if (state.selectedOption === "allow") {
354
355
  if (toolName === EXIT_PLAN_MODE_TOOL_NAME) {
355
356
  onDecision({ behavior: "allow", newPermissionMode: "default" });
357
+ } else if (toolName === ENTER_PLAN_MODE_TOOL_NAME) {
358
+ onDecision({ behavior: "allow", newPermissionMode: "plan" });
356
359
  } else {
357
360
  onDecision({ behavior: "allow" });
358
361
  }
@@ -367,6 +370,8 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
367
370
  : `Bash(${toolInput?.command})`;
368
371
  onDecision({ behavior: "allow", newPermissionRule: rule });
369
372
  }
373
+ } else if (toolName === ENTER_PLAN_MODE_TOOL_NAME) {
374
+ onDecision({ behavior: "allow", newPermissionMode: "plan" });
370
375
  } else if (toolName.startsWith("mcp__")) {
371
376
  onDecision({ behavior: "allow", newPermissionRule: toolName });
372
377
  } else {
@@ -374,6 +379,11 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
374
379
  }
375
380
  } else if (state.alternativeText.trim()) {
376
381
  onDecision({ behavior: "deny", message: state.alternativeText.trim() });
382
+ } else if (toolName === ENTER_PLAN_MODE_TOOL_NAME) {
383
+ onDecision({
384
+ behavior: "deny",
385
+ message: "User chose not to enter plan mode",
386
+ });
377
387
  }
378
388
  return;
379
389
  }
@@ -622,7 +632,11 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
622
632
  bold={state.selectedOption === "alternative"}
623
633
  >
624
634
  {state.selectedOption === "alternative" ? "> " : " "}
625
- {showPlaceholder ? (
635
+ {toolName === ENTER_PLAN_MODE_TOOL_NAME && showPlaceholder ? (
636
+ <Text color="gray" dimColor>
637
+ No, start implementing now
638
+ </Text>
639
+ ) : showPlaceholder ? (
626
640
  <Text color="gray" dimColor>
627
641
  {placeholderText}
628
642
  </Text>
@@ -38,25 +38,56 @@ export const MessageList = React.memo(
38
38
  const visibleMessages = messages.filter((m) => !m.isMeta);
39
39
 
40
40
  const isRunning = (b: MessageBlock) =>
41
- (b.type === "tool" && b.stage === "running") ||
42
- (b.type === "bang" && b.isRunning);
41
+ (b.type === "tool" &&
42
+ (b.stage === "running" ||
43
+ b.stage === "streaming" ||
44
+ b.stage === "start")) ||
45
+ (b.type === "bang" && b.stage === "running");
43
46
 
44
47
  // Flatten messages into blocks with metadata
48
+ // Filter out streaming text/reasoning blocks to avoid frequent height changes
45
49
  const allBlocks = visibleMessages.flatMap((message, messageIndex) => {
46
- return message.blocks.map((block, blockIndex) => ({
47
- block,
48
- message,
49
- messageIndex,
50
- // Unique key for each block to help Static component
51
- key: `${message.id}-${blockIndex}`,
52
- }));
50
+ return message.blocks
51
+ .filter(
52
+ (block) =>
53
+ !(
54
+ (block.type === "text" || block.type === "reasoning") &&
55
+ block.stage === "streaming"
56
+ ),
57
+ )
58
+ .map((block, blockIndex) => ({
59
+ block,
60
+ message,
61
+ messageIndex,
62
+ // Unique key for each block to help Static component
63
+ key: `${message.id}-${blockIndex}`,
64
+ }));
53
65
  });
54
66
 
67
+ // Find message indices that have any running/streaming block
68
+ const runningMessageIndices = new Set<number>();
69
+ for (const item of allBlocks) {
70
+ if (isRunning(item.block)) {
71
+ runningMessageIndices.add(item.messageIndex);
72
+ }
73
+ }
74
+
55
75
  // Determine which blocks are static vs dynamic
56
- // Only mark individual running blocks as dynamic, not entire messages
76
+ // Blocks not in the last message are always static.
77
+ // For the last message: if any block is running/streaming, blocks in that message are dynamic,
78
+ // except text/reasoning blocks that have already completed (stage === "end")
79
+ const lastMessageIndex = visibleMessages.length - 1;
57
80
  const blocksWithStatus = allBlocks.map((item) => {
58
- const { block } = item;
59
- const isDynamic = !forceStatic && !isExpanded && isRunning(block);
81
+ const isInLastMessage = item.messageIndex === lastMessageIndex;
82
+ const isBlockCompleted =
83
+ (item.block.type === "text" || item.block.type === "reasoning") &&
84
+ item.block.stage === "end";
85
+ const isDynamic =
86
+ !forceStatic &&
87
+ !isExpanded &&
88
+ isInLastMessage &&
89
+ !isBlockCompleted &&
90
+ runningMessageIndices.has(item.messageIndex);
60
91
  return { ...item, isDynamic };
61
92
  });
62
93
 
@@ -61,10 +61,21 @@ export const ToolDisplay: React.FC<ToolDisplayProps> = ({
61
61
  <Text color={getStatusColor()}>● </Text>
62
62
  <Text color="white">{toolName}</Text>
63
63
  </Box>
64
- {/* Display compactParams in collapsed state */}
64
+ {/* Display compactParams or streaming parameters in collapsed state */}
65
65
  {!isExpanded && compactParams && (
66
66
  <Text color="gray"> {compactParams}</Text>
67
67
  )}
68
+ {!isExpanded &&
69
+ stage === "streaming" &&
70
+ parameters &&
71
+ !compactParams && (
72
+ <Text color="gray" wrap="truncate-end">
73
+ {` ${(() => {
74
+ const flat = parameters.replace(/\n/g, "\\n");
75
+ return flat.length > 30 ? `…${flat.slice(-30)}` : flat;
76
+ })()}`}
77
+ </Text>
78
+ )}
68
79
  {/* Display image indicator */}
69
80
  {hasImages() && <Text color="blue"> {getImageIndicator()}</Text>}
70
81
  </Box>
@@ -182,18 +182,32 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
182
182
  [],
183
183
  );
184
184
 
185
+ const throttledSetTokens = useMemo(
186
+ () =>
187
+ throttle(
188
+ ((tokens: number) => {
189
+ setlatestTotalTokens(tokens);
190
+ }) as (...args: unknown[]) => void,
191
+ 300,
192
+ { leading: true, trailing: true },
193
+ ),
194
+ [],
195
+ );
196
+
185
197
  useEffect(() => {
186
198
  isExpandedRef.current = isExpanded;
187
199
  if (isExpanded) {
188
200
  throttledSetMessages.cancel();
201
+ throttledSetTokens.cancel();
189
202
  }
190
- }, [isExpanded, throttledSetMessages]);
203
+ }, [isExpanded, throttledSetMessages, throttledSetTokens]);
191
204
 
192
205
  useEffect(() => {
193
206
  return () => {
194
207
  throttledSetMessages.cancel();
208
+ throttledSetTokens.cancel();
195
209
  };
196
- }, [throttledSetMessages]);
210
+ }, [throttledSetMessages, throttledSetTokens]);
197
211
 
198
212
  const [isLoading, setIsLoading] = useState(false);
199
213
  const [latestTotalTokens, setlatestTotalTokens] = useState(0);
@@ -341,7 +355,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
341
355
  setSessionId(sessionId);
342
356
  },
343
357
  onLatestTotalTokensChange: (tokens) => {
344
- setlatestTotalTokens(tokens);
358
+ throttledSetTokens(tokens);
345
359
  },
346
360
  onCompressionStateChange: (isCompressingState) => {
347
361
  setIsCompressing(isCompressingState);
@@ -396,7 +410,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
396
410
  initialPermissionMode ||
397
411
  (bypassPermissions ? "bypassPermissions" : undefined),
398
412
  canUseTool: permissionCallback,
399
- stream: false, // 关闭流式模式
413
+ stream: true,
400
414
  plugins: pluginDirs?.map((path) => ({ type: "local", path })),
401
415
  tools,
402
416
  allowedTools,
@@ -448,6 +462,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
448
462
  model,
449
463
  initialPermissionMode,
450
464
  throttledSetMessages,
465
+ throttledSetTokens,
451
466
  ]);
452
467
 
453
468
  // Cleanup on unmount