wave-agent-sdk 0.14.4 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/builtin/skills/settings/SKILLS.md +31 -6
  2. package/dist/agent.d.ts +4 -5
  3. package/dist/agent.d.ts.map +1 -1
  4. package/dist/agent.js +10 -15
  5. package/dist/constants/toolLimits.d.ts +10 -0
  6. package/dist/constants/toolLimits.d.ts.map +1 -0
  7. package/dist/constants/toolLimits.js +9 -0
  8. package/dist/managers/aiManager.d.ts +0 -5
  9. package/dist/managers/aiManager.d.ts.map +1 -1
  10. package/dist/managers/aiManager.js +0 -22
  11. package/dist/managers/hookManager.d.ts +0 -4
  12. package/dist/managers/hookManager.d.ts.map +1 -1
  13. package/dist/managers/hookManager.js +0 -25
  14. package/dist/managers/messageManager.d.ts.map +1 -1
  15. package/dist/managers/messageManager.js +7 -6
  16. package/dist/managers/permissionManager.d.ts +1 -1
  17. package/dist/managers/permissionManager.d.ts.map +1 -1
  18. package/dist/managers/permissionManager.js +5 -5
  19. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  20. package/dist/managers/slashCommandManager.js +16 -4
  21. package/dist/managers/subagentManager.d.ts +6 -1
  22. package/dist/managers/subagentManager.d.ts.map +1 -1
  23. package/dist/managers/subagentManager.js +17 -18
  24. package/dist/prompts/index.d.ts +1 -3
  25. package/dist/prompts/index.d.ts.map +1 -1
  26. package/dist/prompts/index.js +3 -6
  27. package/dist/services/aiService.d.ts.map +1 -1
  28. package/dist/services/aiService.js +10 -8
  29. package/dist/services/hook.d.ts +0 -4
  30. package/dist/services/hook.d.ts.map +1 -1
  31. package/dist/services/hook.js +0 -10
  32. package/dist/services/jsonlHandler.d.ts +4 -4
  33. package/dist/services/jsonlHandler.d.ts.map +1 -1
  34. package/dist/services/jsonlHandler.js +4 -13
  35. package/dist/services/session.d.ts.map +1 -1
  36. package/dist/services/session.js +7 -13
  37. package/dist/tools/agentTool.d.ts.map +1 -1
  38. package/dist/tools/agentTool.js +16 -4
  39. package/dist/tools/bashTool.d.ts.map +1 -1
  40. package/dist/tools/bashTool.js +4 -50
  41. package/dist/tools/editTool.js +1 -1
  42. package/dist/tools/skillTool.d.ts.map +1 -1
  43. package/dist/tools/skillTool.js +16 -4
  44. package/dist/tools/types.d.ts +0 -3
  45. package/dist/tools/types.d.ts.map +1 -1
  46. package/dist/types/agent.d.ts +0 -1
  47. package/dist/types/agent.d.ts.map +1 -1
  48. package/dist/types/hooks.d.ts +1 -5
  49. package/dist/types/hooks.d.ts.map +1 -1
  50. package/dist/types/hooks.js +0 -1
  51. package/dist/types/messaging.d.ts +1 -0
  52. package/dist/types/messaging.d.ts.map +1 -1
  53. package/dist/types/session.d.ts +0 -4
  54. package/dist/types/session.d.ts.map +1 -1
  55. package/dist/utils/editUtils.d.ts +5 -2
  56. package/dist/utils/editUtils.d.ts.map +1 -1
  57. package/dist/utils/editUtils.js +3 -57
  58. package/dist/utils/markdownParser.d.ts +8 -1
  59. package/dist/utils/markdownParser.d.ts.map +1 -1
  60. package/dist/utils/markdownParser.js +64 -11
  61. package/dist/utils/messageOperations.d.ts.map +1 -1
  62. package/dist/utils/messageOperations.js +5 -0
  63. package/dist/utils/openaiClient.d.ts.map +1 -1
  64. package/dist/utils/openaiClient.js +0 -11
  65. package/package.json +1 -1
  66. package/src/agent.ts +12 -17
  67. package/src/constants/toolLimits.ts +12 -0
  68. package/src/managers/aiManager.ts +0 -38
  69. package/src/managers/hookManager.ts +0 -32
  70. package/src/managers/messageManager.ts +7 -8
  71. package/src/managers/permissionManager.ts +6 -6
  72. package/src/managers/slashCommandManager.ts +24 -5
  73. package/src/managers/subagentManager.ts +28 -23
  74. package/src/prompts/index.ts +3 -8
  75. package/src/services/aiService.ts +10 -12
  76. package/src/services/hook.ts +0 -15
  77. package/src/services/jsonlHandler.ts +12 -24
  78. package/src/services/session.ts +9 -15
  79. package/src/tools/agentTool.ts +24 -5
  80. package/src/tools/bashTool.ts +4 -56
  81. package/src/tools/editTool.ts +1 -1
  82. package/src/tools/skillTool.ts +24 -4
  83. package/src/tools/types.ts +0 -3
  84. package/src/types/agent.ts +0 -1
  85. package/src/types/hooks.ts +1 -7
  86. package/src/types/messaging.ts +1 -0
  87. package/src/types/session.ts +0 -8
  88. package/src/utils/editUtils.ts +3 -73
  89. package/src/utils/markdownParser.ts +85 -11
  90. package/src/utils/messageOperations.ts +5 -0
  91. package/src/utils/openaiClient.ts +0 -11
@@ -131,7 +131,7 @@ export class PermissionManager {
131
131
  private planFilePath?: string;
132
132
  private worktreeName?: string;
133
133
  private mainRepoRoot?: string;
134
- private originalWorkdir?: string;
134
+ private workdir?: string;
135
135
  private onConfiguredPermissionModeChange?: (mode: PermissionMode) => void;
136
136
  private _logger?: Logger;
137
137
 
@@ -153,7 +153,7 @@ export class PermissionManager {
153
153
 
154
154
  this.worktreeName = this.container.get<string>("WorktreeName");
155
155
  this.mainRepoRoot = this.container.get<string>("MainRepoRoot");
156
- this.originalWorkdir = this.container.get<string>("Workdir");
156
+ this.workdir = this.container.get<string>("Workdir");
157
157
  }
158
158
 
159
159
  /**
@@ -277,7 +277,7 @@ export class PermissionManager {
277
277
  * Update the additional directories (e.g., when configuration reloads)
278
278
  */
279
279
  updateAdditionalDirectories(directories: string[]): void {
280
- const workdir = this.originalWorkdir;
280
+ const workdir = this.workdir;
281
281
  this.additionalDirectories = directories.map((dir) => {
282
282
  if (workdir && !path.isAbsolute(dir)) {
283
283
  return path.resolve(workdir, dir);
@@ -290,7 +290,7 @@ export class PermissionManager {
290
290
  * Add a system-level additional directory that is persistent across configuration reloads
291
291
  */
292
292
  public addSystemAdditionalDirectory(directory: string): void {
293
- const workdir = this.originalWorkdir;
293
+ const workdir = this.workdir;
294
294
  const resolvedPath =
295
295
  workdir && !path.isAbsolute(directory)
296
296
  ? path.resolve(workdir, directory)
@@ -329,7 +329,7 @@ export class PermissionManager {
329
329
  targetPath: string,
330
330
  workdir?: string,
331
331
  ): { isInside: boolean; resolvedPath: string } {
332
- const effectiveWorkdir = this.originalWorkdir || workdir;
332
+ const effectiveWorkdir = this.workdir || workdir;
333
333
 
334
334
  // Resolve the target path relative to effectiveWorkdir if it's not absolute
335
335
  const absolutePath =
@@ -1068,7 +1068,7 @@ export class PermissionManager {
1068
1068
  * @param rule - The rule to add (e.g., "Bash(ls)")
1069
1069
  */
1070
1070
  public async addPermissionRule(rule: string): Promise<void> {
1071
- const workdir = this.originalWorkdir;
1071
+ const workdir = this.workdir;
1072
1072
  if (!workdir) {
1073
1073
  throw new Error("Working directory not set in PermissionManager");
1074
1074
  }
@@ -224,20 +224,39 @@ export class SlashCommandManager {
224
224
  const messages = subagent.messages;
225
225
  const tokens =
226
226
  subagent.messageManager.getLatestTotalTokens();
227
- const lastTools = subagent.lastTools;
227
+ const usedTools = subagent.usedTools;
228
228
 
229
229
  const toolCount = countToolBlocks(messages);
230
230
  const summary = formatToolTokenSummary(toolCount, tokens);
231
231
 
232
+ const getDisplayParam = (t: {
233
+ name: string;
234
+ parameters: string;
235
+ compactParams?: string;
236
+ stage?: string;
237
+ }) => {
238
+ if (
239
+ (t.stage === "end" || t.stage === "running") &&
240
+ t.compactParams
241
+ ) {
242
+ return t.compactParams;
243
+ }
244
+ const flat = t.parameters.replace(/\n/g, "\\n");
245
+ return flat.length > 30 ? `…${flat.slice(-30)}` : flat;
246
+ };
247
+
232
248
  let shortResult = "";
233
249
  if (toolCount > 2) {
234
250
  shortResult += "... ";
235
251
  }
236
- if (lastTools.length > 0) {
237
- shortResult += `${lastTools.join(", ")} `;
238
- }
239
-
240
252
  shortResult += summary;
253
+ if (usedTools.length > 0) {
254
+ shortResult +=
255
+ "\n" +
256
+ usedTools
257
+ .map((t) => `${t.name} ${getDisplayParam(t)}`)
258
+ .join("\n");
259
+ }
241
260
 
242
261
  this.messageManager.updateToolBlock({
243
262
  id: toolBlockId,
@@ -68,7 +68,12 @@ export interface SubagentInstance {
68
68
  toolManager: ToolManager;
69
69
  status: "initializing" | "active" | "completed" | "error" | "aborted";
70
70
  messages: Message[];
71
- lastTools: string[]; // Track last two tool names
71
+ usedTools: {
72
+ name: string;
73
+ parameters: string;
74
+ compactParams?: string;
75
+ stage?: string;
76
+ }[]; // Track tools with display info
72
77
  subagentType: string; // Store the subagent type for hook context
73
78
  description: string; // Store the AI-generated description
74
79
  allowedTools?: string[]; // Optional permission rules (e.g. git:*)
@@ -388,7 +393,7 @@ export class SubagentManager {
388
393
  toolManager,
389
394
  status: "initializing",
390
395
  messages: [],
391
- lastTools: [], // Initialize lastTools
396
+ usedTools: [], // Initialize usedTools
392
397
  subagentType: parameters.subagent_type, // Store the subagent type
393
398
  description: parameters.description, // Store the AI-generated description
394
399
  allowedTools: parameters.allowedTools, // Store optional permission rules
@@ -790,27 +795,16 @@ export class SubagentManager {
790
795
  },
791
796
 
792
797
  onToolBlockUpdated: (params: AgentToolBlockUpdateParams) => {
793
- // Track last two tool names when a tool starts running
794
- if (params.stage === "running" && params.name) {
795
- const instance = this.instances.get(subagentId);
796
- if (instance) {
797
- // Add to lastTools if it's different from the last one or we want to show duplicates
798
- // Based on "ToolA, ToolB" requirement, we'll just keep the last two
799
- instance.lastTools.push(params.name);
800
- if (instance.lastTools.length > 2) {
801
- instance.lastTools.shift();
802
- }
803
- instance.onUpdate?.();
804
-
805
- // Log tool execution to file
806
- if (instance.logStream) {
807
- const displayParams =
808
- params.compactParams ||
809
- (params.parameters || "{}").substring(0, 100);
810
- instance.logStream.write(
811
- `[${new Date().toISOString()}] ${params.name}${displayParams ? ` ${displayParams}` : ""}\n`,
812
- );
813
- }
798
+ const instance = this.instances.get(subagentId);
799
+ if (instance) {
800
+ // Log tool execution to file only when finalized
801
+ if (instance.logStream && params.stage === "end") {
802
+ const displayParams =
803
+ params.compactParams ||
804
+ (params.parameters || "").substring(0, 100);
805
+ instance.logStream.write(
806
+ `[${new Date().toISOString()}] ${params.name}${displayParams ? ` ${displayParams}` : ""}\n`,
807
+ );
814
808
  }
815
809
  }
816
810
 
@@ -825,6 +819,17 @@ export class SubagentManager {
825
819
  const instance = this.instances.get(subagentId);
826
820
  if (instance) {
827
821
  instance.messages = messages;
822
+ // Compute usedTools from messages (last 2 tool blocks)
823
+ const toolBlocks = messages.flatMap(
824
+ (m) => m.blocks?.filter((b) => b.type === "tool") ?? [],
825
+ );
826
+ const last2 = toolBlocks.slice(-2);
827
+ instance.usedTools = last2.map((tb) => ({
828
+ name: tb.name ?? "",
829
+ parameters: tb.parameters ?? "",
830
+ compactParams: tb.compactParams,
831
+ stage: tb.stage,
832
+ }));
828
833
  // Trigger the onUpdate callback if provided
829
834
  instance.onUpdate?.();
830
835
  // Forward subagent message changes to parent via callbacks
@@ -19,8 +19,6 @@ import {
19
19
  GREP_TOOL_NAME,
20
20
  } from "../constants/tools.js";
21
21
 
22
- export const MAX_PARALLEL_TOOL_CALLS = 3;
23
-
24
22
  export const BASE_SYSTEM_PROMPT = `You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.`;
25
23
 
26
24
  export const DOING_TASKS_PROMPT = `# Doing tasks
@@ -60,7 +58,6 @@ export const TOOL_POLICY = `# Using your tools
60
58
  - To search the content of files, use ${GREP_TOOL_NAME} instead of grep or rg
61
59
  - Reserve using the ${BASH_TOOL_NAME} exclusively for system commands and terminal operations that require shell execution. If you are unsure and there is a relevant dedicated tool, default to using the dedicated tool and only fallback on using the ${BASH_TOOL_NAME} tool for these if it is absolutely necessary.
62
60
  - You can call multiple tools in a single response. If you intend to call multiple tools and there are no dependencies between them, make all independent tool calls in parallel. Maximize use of parallel tool calls where possible to increase efficiency.
63
- - **Limit**: You MUST NOT call more than ${MAX_PARALLEL_TOOL_CALLS} tools in parallel in a single response.
64
61
  - However, if some tool calls depend on previous calls to inform dependent values, do NOT call these tools in parallel and instead call them sequentially. For instance, if one operation must complete before another starts, run these operations sequentially instead. Never use placeholders or guess missing parameters in tool calls.
65
62
  - If the user specifies that they want you to run tools "in parallel", you MUST send a single message with multiple tool use content blocks.`;
66
63
 
@@ -238,7 +235,6 @@ export function buildSystemPrompt(
238
235
  tools: ToolPlugin[],
239
236
  options: {
240
237
  workdir?: string;
241
- originalWorkdir?: string;
242
238
  memory?: string;
243
239
  language?: string;
244
240
  isSubagent?: boolean;
@@ -276,9 +272,8 @@ export function buildSystemPrompt(
276
272
  prompt += `\n\n${buildPlanModePrompt(options.planMode.planFilePath, options.planMode.planExists, options.isSubagent)}`;
277
273
  }
278
274
 
279
- const workdirForPrompt = options.originalWorkdir || options.workdir;
280
- if (workdirForPrompt) {
281
- const isGitRepo = isGitRepository(workdirForPrompt);
275
+ if (options.workdir) {
276
+ const isGitRepo = isGitRepository(options.workdir);
282
277
  const platform = os.platform();
283
278
  const osVersion = `${os.type()} ${os.release()}`;
284
279
  const today = new Date().toISOString().split("T")[0];
@@ -293,7 +288,7 @@ export function buildSystemPrompt(
293
288
 
294
289
  Here is useful information about the environment you are running in:
295
290
  <env>
296
- Working directory: ${workdirForPrompt}
291
+ Working directory: ${options.workdir}
297
292
  Is directory a git repo: ${isGitRepo}
298
293
  Platform: ${platform}
299
294
  Shell: ${shellName}
@@ -377,10 +377,7 @@ export async function callAgent(
377
377
  result.content = finalContent;
378
378
  }
379
379
 
380
- if (
381
- typeof finalReasoningContent === "string" &&
382
- finalReasoningContent.length > 0
383
- ) {
380
+ if (typeof finalReasoningContent === "string") {
384
381
  result.reasoning_content = finalReasoningContent;
385
382
  }
386
383
 
@@ -544,6 +541,7 @@ async function processStreamingResponse(
544
541
  ): Promise<CallAgentResult> {
545
542
  let accumulatedContent = "";
546
543
  let accumulatedReasoningContent = "";
544
+ let hasReasoningContent = false;
547
545
  const toolCalls: {
548
546
  id: string;
549
547
  type: "function";
@@ -618,13 +616,13 @@ async function processStreamingResponse(
618
616
  }
619
617
  }
620
618
 
621
- if (
622
- typeof reasoning_content === "string" &&
623
- reasoning_content.length > 0
624
- ) {
625
- accumulatedReasoningContent += reasoning_content;
626
- if (onReasoningUpdate) {
627
- onReasoningUpdate(accumulatedReasoningContent);
619
+ if (typeof reasoning_content === "string") {
620
+ hasReasoningContent = true;
621
+ if (reasoning_content.length > 0) {
622
+ accumulatedReasoningContent += reasoning_content;
623
+ if (onReasoningUpdate) {
624
+ onReasoningUpdate(accumulatedReasoningContent);
625
+ }
628
626
  }
629
627
  }
630
628
 
@@ -716,7 +714,7 @@ async function processStreamingResponse(
716
714
  result.content = accumulatedContent.trim();
717
715
  }
718
716
 
719
- if (accumulatedReasoningContent) {
717
+ if (hasReasoningContent) {
720
718
  result.reasoning_content = accumulatedReasoningContent.trim();
721
719
  }
722
720
 
@@ -277,21 +277,6 @@ export async function executeCommands(
277
277
  return results;
278
278
  }
279
279
 
280
- /**
281
- * Execute a CwdChanged hook
282
- */
283
- export async function executeCwdChangedHooks(
284
- oldCwd: string,
285
- newCwd: string,
286
- context: ExtendedHookExecutionContext,
287
- ): Promise<HookExecutionResult[]> {
288
- // CwdChanged hooks are executed through HookManager.executeCwdChangedHooks()
289
- void context;
290
- void oldCwd;
291
- void newCwd;
292
- return [];
293
- }
294
-
295
280
  /**
296
281
  * Validate command safety (basic checks)
297
282
  */
@@ -8,7 +8,7 @@ import { dirname } from "path";
8
8
  import { getLastLine } from "../utils/fileUtils.js";
9
9
 
10
10
  import type { Message } from "../types/index.js";
11
- import type { SessionMessage, SessionFilename } from "../types/session.js";
11
+ import type { SessionFilename } from "../types/session.js";
12
12
 
13
13
  /**
14
14
  * JSONL write options
@@ -56,13 +56,7 @@ export class JsonlHandler {
56
56
  return;
57
57
  }
58
58
 
59
- // Convert to SessionMessage format with timestamps
60
- const sessionMessages: SessionMessage[] = messages.map((message) => ({
61
- ...message,
62
- timestamp: new Date().toISOString(),
63
- }));
64
-
65
- return this.append(filePath, sessionMessages);
59
+ return this.append(filePath, messages);
66
60
  }
67
61
 
68
62
  /**
@@ -70,7 +64,7 @@ export class JsonlHandler {
70
64
  */
71
65
  async append(
72
66
  filePath: string,
73
- messages: SessionMessage[],
67
+ messages: Message[],
74
68
  options?: JsonlWriteOptions,
75
69
  ): Promise<void> {
76
70
  if (messages.length === 0) {
@@ -85,16 +79,10 @@ export class JsonlHandler {
85
79
  // Ensure directory exists
86
80
  await this.ensureDirectory(dirname(filePath));
87
81
 
88
- // Convert messages to JSONL lines (always compact JSON)
82
+ // Convert messages to JSONL lines (compact JSON, timestamp first)
89
83
  const lines = messages.map((message) => {
90
- const { timestamp: existingTimestamp, ...messageWithoutTimestamp } =
91
- message;
92
- const messageWithTimestamp = {
93
- timestamp: existingTimestamp || new Date().toISOString(),
94
- ...messageWithoutTimestamp,
95
- };
96
-
97
- return JSON.stringify(messageWithTimestamp);
84
+ const { timestamp, ...rest } = message;
85
+ return JSON.stringify({ timestamp, ...rest });
98
86
  });
99
87
 
100
88
  const content = lines.join("\n") + "\n";
@@ -116,7 +104,7 @@ export class JsonlHandler {
116
104
  /**
117
105
  * Read all messages from JSONL file (simplified - no metadata handling)
118
106
  */
119
- async read(filePath: string): Promise<SessionMessage[]> {
107
+ async read(filePath: string): Promise<Message[]> {
120
108
  try {
121
109
  const content = await readFile(filePath, "utf8");
122
110
  const lines = content
@@ -128,14 +116,14 @@ export class JsonlHandler {
128
116
  return [];
129
117
  }
130
118
 
131
- const allMessages: SessionMessage[] = [];
119
+ const allMessages: Message[] = [];
132
120
 
133
121
  // Parse all messages (no metadata line to skip)
134
122
  for (let i = 0; i < lines.length; i++) {
135
123
  const line = lines[i];
136
124
 
137
125
  try {
138
- const message = JSON.parse(line) as SessionMessage;
126
+ const message = JSON.parse(line) as Message;
139
127
  if (message.timestamp) allMessages.push(message);
140
128
  } catch (error) {
141
129
  // Throw error for invalid JSON lines with line number
@@ -155,7 +143,7 @@ export class JsonlHandler {
155
143
  /**
156
144
  * Get the last message from JSONL file using efficient file reading (simplified)
157
145
  */
158
- async getLastMessage(filePath: string): Promise<SessionMessage | null> {
146
+ async getLastMessage(filePath: string): Promise<Message | null> {
159
147
  try {
160
148
  // First check if file exists
161
149
  try {
@@ -176,7 +164,7 @@ export class JsonlHandler {
176
164
 
177
165
  try {
178
166
  const parsed = JSON.parse(lastLine);
179
- return parsed as SessionMessage;
167
+ return parsed as Message;
180
168
  } catch (error) {
181
169
  throw new Error(`Invalid JSON in last line of "${filePath}": ${error}`);
182
170
  }
@@ -190,7 +178,7 @@ export class JsonlHandler {
190
178
  /**
191
179
  * Validate messages before writing
192
180
  */
193
- private validateMessages(messages: SessionMessage[]): void {
181
+ private validateMessages(messages: Message[]): void {
194
182
  for (let i = 0; i < messages.length; i++) {
195
183
  const message = messages[i];
196
184
 
@@ -20,7 +20,6 @@ import { join } from "path";
20
20
  import { homedir } from "os";
21
21
  import { randomUUID } from "crypto";
22
22
  import type { Message } from "../types/index.js";
23
- import type { SessionMessage } from "../types/session.js";
24
23
  import { PathEncoder } from "../utils/pathEncoder.js";
25
24
  import { JsonlHandler } from "../services/jsonlHandler.js";
26
25
  import { extractLatestTotalTokens } from "../utils/tokenCalculation.js";
@@ -228,19 +227,14 @@ export async function appendMessages(
228
227
  );
229
228
  }
230
229
 
231
- const messagesWithTimestamp: SessionMessage[] = newMessages.map((msg) => ({
232
- timestamp: new Date().toISOString(),
233
- ...msg,
234
- }));
235
-
236
- await jsonlHandler.append(filePath, messagesWithTimestamp, {
230
+ await jsonlHandler.append(filePath, newMessages, {
237
231
  atomic: false,
238
232
  });
239
233
 
240
234
  // Update index
241
235
  const encoder = new PathEncoder();
242
236
  const projectDir = await encoder.getProjectDirectory(workdir, SESSION_DIR);
243
- const lastMessage = messagesWithTimestamp[messagesWithTimestamp.length - 1];
237
+ const lastMessage = newMessages[newMessages.length - 1];
244
238
 
245
239
  // Get first message content if it's a new session or we don't have it
246
240
  let firstMessage: string | undefined;
@@ -333,12 +327,7 @@ export async function loadSessionFromJsonl(
333
327
  id: sessionId,
334
328
  rootSessionId: rootSessionId || sessionId,
335
329
  parentSessionId,
336
- messages: messages.map((msg) => {
337
- // Remove timestamp property for backward compatibility
338
- const { timestamp: _ignored, ...messageWithoutTimestamp } = msg;
339
- void _ignored; // Use the variable to avoid eslint error
340
- return messageWithoutTimestamp;
341
- }),
330
+ messages,
342
331
  metadata: {
343
332
  workdir,
344
333
  lastActiveAt: lastMessage
@@ -953,7 +942,12 @@ export async function handleSessionRestoration(
953
942
  // Use only JSONL format - no legacy support
954
943
  sessionToRestore = await loadSessionFromJsonl(restoreSessionId, workdir);
955
944
  if (!sessionToRestore) {
956
- throw new Error(`Session not found: ${restoreSessionId}`);
945
+ // Session doesn't exist on disk (e.g. new project with no messages saved yet).
946
+ // Gracefully fall back to starting fresh instead of throwing.
947
+ logger?.warn(
948
+ `Session ${restoreSessionId} not found on disk, starting fresh session`,
949
+ );
950
+ return;
957
951
  }
958
952
  } else if (continueLastSession) {
959
953
  // Use only JSONL format - no legacy support
@@ -159,20 +159,39 @@ When using the Agent tool, you must specify a subagent_type parameter to select
159
159
 
160
160
  const messages = instance.messageManager.getMessages();
161
161
  const tokens = instance.messageManager.getLatestTotalTokens();
162
- const lastTools = instance.lastTools;
162
+ const usedTools = instance.usedTools;
163
163
 
164
164
  const toolCount = countToolBlocks(messages);
165
165
  const summary = formatToolTokenSummary(toolCount, tokens);
166
166
 
167
+ const getDisplayParam = (t: {
168
+ name: string;
169
+ parameters: string;
170
+ compactParams?: string;
171
+ stage?: string;
172
+ }) => {
173
+ if (
174
+ (t.stage === "end" || t.stage === "running") &&
175
+ t.compactParams
176
+ ) {
177
+ return t.compactParams;
178
+ }
179
+ const flat = t.parameters.replace(/\n/g, "\\n");
180
+ return flat.length > 30 ? `…${flat.slice(-30)}` : flat;
181
+ };
182
+
167
183
  let shortResult = "";
168
184
  if (toolCount > 2) {
169
185
  shortResult += "... ";
170
186
  }
171
- if (lastTools.length > 0) {
172
- shortResult += `${lastTools.join(", ")} `;
173
- }
174
-
175
187
  shortResult += summary;
188
+ if (usedTools.length > 0) {
189
+ shortResult +=
190
+ "\n" +
191
+ usedTools
192
+ .map((t) => `${t.name} ${getDisplayParam(t)}`)
193
+ .join("\n");
194
+ }
176
195
 
177
196
  context.onShortResultUpdate?.(shortResult);
178
197
  },
@@ -80,7 +80,7 @@ export const bashTool: ToolPlugin = {
80
80
  },
81
81
  },
82
82
  prompt: () => `
83
- Executes a given bash command in a persistent shell session with optional timeout, ensuring proper handling and security measures.
83
+ Executes a given bash command with optional timeout, ensuring proper handling and security measures. Each invocation runs in a fresh shell process starting from the project root.
84
84
 
85
85
  IMPORTANT: This tool is for terminal operations like git, npm, docker, etc. DO NOT use it for file operations (reading, writing, editing, searching, finding files) - use the specialized tools for this instead.
86
86
 
@@ -139,10 +139,7 @@ Use the gh command via the Bash tool for GitHub-related tasks including working
139
139
  - Do not retry failing commands in a sleep loop — diagnose the root cause.
140
140
  - If waiting for a background task you started with \`run_in_background\`, you will be notified when it completes — do not poll.
141
141
  - If you must poll an external process, use a check command (e.g. \`gh run view\`) rather than sleeping first.
142
- - If you must sleep, keep the duration short (1-5 seconds) to avoid blocking the user.
143
-
144
- # CWD management
145
- Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of \`cd\`. You may use \`cd\` if the User explicitly requests it. When you use \`cd\`, the shell working directory will be reset to the original working directory after the command completes.`,
142
+ - If you must sleep, keep the duration short (1-5 seconds) to avoid blocking the user.`,
146
143
  execute: async (
147
144
  args: Record<string, unknown>,
148
145
  context: ToolContext,
@@ -240,14 +237,7 @@ Try to maintain your current working directory throughout the session by using a
240
237
 
241
238
  // Foreground execution (original behavior)
242
239
  return new Promise((resolve) => {
243
- // Create a temporary file to store the CWD
244
- const tempCwdFile = path.join(
245
- os.tmpdir(),
246
- `wave_cwd_${Date.now()}_${Math.random().toString(36).substring(2, 11)}.tmp`,
247
- );
248
- const wrappedCommand = `${command} && pwd -P >| ${tempCwdFile}`;
249
-
250
- const child: ChildProcess = spawn(wrappedCommand, {
240
+ const child: ChildProcess = spawn(command, {
251
241
  shell: true,
252
242
  stdio: "pipe",
253
243
  cwd: context.workdir,
@@ -431,55 +421,13 @@ Try to maintain your current working directory throughout the session by using a
431
421
  clearTimeout(timeoutHandle);
432
422
  }
433
423
 
434
- // Read the new CWD from the temporary file
435
- let newCwd: string | undefined;
436
- try {
437
- if (fs.existsSync(tempCwdFile)) {
438
- newCwd = fs.readFileSync(tempCwdFile, "utf8").trim();
439
- // Validate the path exists before calling the callback
440
- fs.accessSync(newCwd, fs.constants.F_OK);
441
- }
442
- } catch (fileError) {
443
- logger.warn(
444
- `Could not read or validate new CWD from temp file ${tempCwdFile}:`,
445
- fileError,
446
- );
447
- newCwd = undefined;
448
- } finally {
449
- // Ensure temp file is cleaned up even if reading fails
450
- try {
451
- if (fs.existsSync(tempCwdFile)) {
452
- fs.unlinkSync(tempCwdFile);
453
- }
454
- } catch (fileError) {
455
- logger.error("Failed to clean up temp CWD file:", fileError);
456
- }
457
- }
458
-
459
- // If CWD changed, call the onCwdChange callback and add notification
460
- let cwdChangedNotification = "";
461
- if (newCwd && newCwd !== context.workdir && context.onCwdChange) {
462
- const isInSafeZone =
463
- context.permissionManager?.isPathInSafeZone?.(newCwd) ?? true;
464
-
465
- if (isInSafeZone) {
466
- context.onCwdChange(newCwd);
467
- } else if (context.originalWorkdir) {
468
- context.onCwdChange(context.originalWorkdir);
469
- cwdChangedNotification = `Shell cwd was reset to ${context.originalWorkdir}\n`;
470
- } else {
471
- context.onCwdChange(newCwd);
472
- }
473
- }
474
-
475
424
  const exitCode = code ?? 0;
476
425
  const combinedOutput =
477
426
  outputBuffer + (errorBuffer ? "\n" + errorBuffer : "");
478
427
 
479
428
  // Handle large output by truncation and persistence if needed
480
429
  const finalOutput =
481
- cwdChangedNotification +
482
- (combinedOutput || `Command executed with exit code: ${exitCode}`);
430
+ combinedOutput || `Command executed with exit code: ${exitCode}`;
483
431
  const content = processOutput(finalOutput);
484
432
 
485
433
  const lines = combinedOutput.trim().split("\n");
@@ -136,7 +136,7 @@ Usage:
136
136
  return {
137
137
  success: false,
138
138
  content: "",
139
- error: analyzeEditMismatch(originalContent, oldString),
139
+ error: analyzeEditMismatch(),
140
140
  };
141
141
  }
142
142
 
@@ -148,19 +148,39 @@ export const skillTool: ToolPlugin = {
148
148
  // Update shortResult
149
149
  const messages = instance.messageManager.getMessages();
150
150
  const tokens = instance.messageManager.getLatestTotalTokens();
151
- const lastTools = instance.lastTools;
151
+ const usedTools = instance.usedTools;
152
152
 
153
153
  const toolCount = countToolBlocks(messages);
154
154
  const summary = formatToolTokenSummary(toolCount, tokens);
155
155
 
156
+ const getDisplayParam = (t: {
157
+ name: string;
158
+ parameters: string;
159
+ compactParams?: string;
160
+ stage?: string;
161
+ }) => {
162
+ if (
163
+ (t.stage === "end" || t.stage === "running") &&
164
+ t.compactParams
165
+ ) {
166
+ return t.compactParams;
167
+ }
168
+ const flat = t.parameters.replace(/\n/g, "\\n");
169
+ return flat.length > 30 ? `…${flat.slice(-30)}` : flat;
170
+ };
171
+
156
172
  let shortResult = "";
157
173
  if (toolCount > 2) {
158
174
  shortResult += "... ";
159
175
  }
160
- if (lastTools.length > 0) {
161
- shortResult += `${lastTools.join(", ")} `;
162
- }
163
176
  shortResult += summary;
177
+ if (usedTools.length > 0) {
178
+ shortResult +=
179
+ "\n" +
180
+ usedTools
181
+ .map((t) => `${t.name} ${getDisplayParam(t)}`)
182
+ .join("\n");
183
+ }
164
184
 
165
185
  context.onShortResultUpdate?.(shortResult);
166
186
  },
@@ -58,7 +58,6 @@ export interface ToolContext {
58
58
  abortSignal?: AbortSignal;
59
59
  backgroundTaskManager?: import("../managers/backgroundTaskManager.js").BackgroundTaskManager;
60
60
  workdir: string;
61
- originalWorkdir?: string;
62
61
  /** Permission mode for this tool execution */
63
62
  permissionMode?: PermissionMode;
64
63
  /** Custom permission callback */
@@ -104,6 +103,4 @@ export interface ToolContext {
104
103
  };
105
104
  /** State of files read in the current session for deduplication */
106
105
  readFileState?: Map<string, { mtime: number; hash: string }>;
107
- /** Callback to notify when the current working directory changes */
108
- onCwdChange?: (newCwd: string) => void;
109
106
  }