wave-agent-sdk 1.0.8 → 1.0.10

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 (74) hide show
  1. package/dist/builtin/index.d.ts +1 -0
  2. package/dist/builtin/index.js +20 -0
  3. package/dist/builtin/plugins.d.ts +1 -0
  4. package/dist/builtin/plugins.js +225 -0
  5. package/dist/builtin/skills/artifact.d.ts +1 -0
  6. package/dist/builtin/skills/artifact.js +18 -0
  7. package/dist/builtin/skills/code-review.d.ts +1 -0
  8. package/{builtin/skills/code-review/SKILL.md → dist/builtin/skills/code-review.js} +23 -19
  9. package/dist/builtin/skills/deep-research.d.ts +1 -0
  10. package/{builtin/skills/deep-research/SKILL.md → dist/builtin/skills/deep-research.js} +18 -14
  11. package/dist/builtin/skills/init.d.ts +1 -0
  12. package/{builtin/skills/init/SKILL.md → dist/builtin/skills/init.js} +6 -3
  13. package/dist/builtin/skills/loop.d.ts +1 -0
  14. package/dist/builtin/skills/loop.js +83 -0
  15. package/dist/builtin/skills/settings.d.ts +1 -0
  16. package/dist/builtin/skills/settings.js +1224 -0
  17. package/dist/builtin/skills/simplify.d.ts +1 -0
  18. package/{builtin/skills/simplify/SKILL.md → dist/builtin/skills/simplify.js} +7 -3
  19. package/dist/builtin/subagents.d.ts +1 -0
  20. package/dist/builtin/subagents.js +164 -0
  21. package/dist/managers/aiManager.js +18 -1
  22. package/dist/managers/backgroundTaskManager.js +52 -4
  23. package/dist/managers/mcpManager.js +1 -1
  24. package/dist/managers/messageManager.d.ts +6 -0
  25. package/dist/managers/messageManager.js +33 -0
  26. package/dist/managers/skillManager.d.ts +13 -0
  27. package/dist/managers/skillManager.js +30 -0
  28. package/dist/managers/subagentManager.d.ts +8 -0
  29. package/dist/managers/subagentManager.js +57 -2
  30. package/dist/managers/toolManager.d.ts +8 -0
  31. package/dist/managers/toolManager.js +11 -0
  32. package/dist/services/artifactAvailability.d.ts +3 -1
  33. package/dist/services/artifactAvailability.js +9 -1
  34. package/dist/services/remoteSettingsService.js +2 -0
  35. package/dist/tools/bashTool.js +13 -2
  36. package/dist/tools/editTool.js +8 -2
  37. package/dist/tools/readTool.js +6 -3
  38. package/dist/tools/types.d.ts +1 -0
  39. package/dist/tools/writeTool.js +1 -0
  40. package/dist/types/messaging.d.ts +1 -0
  41. package/dist/utils/builtinEmbed.d.ts +21 -0
  42. package/dist/utils/builtinEmbed.js +53 -0
  43. package/dist/utils/configPaths.d.ts +0 -1
  44. package/dist/utils/configPaths.js +5 -26
  45. package/dist/utils/containerSetup.js +8 -0
  46. package/dist/utils/convertMessagesForAPI.js +21 -2
  47. package/dist/utils/encoding.d.ts +28 -0
  48. package/dist/utils/encoding.js +99 -0
  49. package/dist/utils/messageOperations.d.ts +1 -0
  50. package/dist/utils/toolImagePersistence.d.ts +25 -0
  51. package/dist/utils/toolImagePersistence.js +56 -0
  52. package/package.json +1 -3
  53. package/builtin/plugins/sdd/.wave-plugin/plugin.json +0 -8
  54. package/builtin/plugins/sdd/hooks/hooks.json +0 -14
  55. package/builtin/plugins/sdd/scripts/session-start.js +0 -24
  56. package/builtin/plugins/sdd/scripts/spec-count.js +0 -77
  57. package/builtin/plugins/sdd/skills/specify/SKILL.md +0 -47
  58. package/builtin/plugins/sdd/skills/specify/templates/spec-template.md +0 -47
  59. package/builtin/skills/loop/SKILL.md +0 -79
  60. package/builtin/skills/settings/ENV.md +0 -78
  61. package/builtin/skills/settings/HOOKS.md +0 -227
  62. package/builtin/skills/settings/MCP.md +0 -137
  63. package/builtin/skills/settings/MEMORY.md +0 -76
  64. package/builtin/skills/settings/MODELS.md +0 -119
  65. package/builtin/skills/settings/PERMISSIONS.md +0 -88
  66. package/builtin/skills/settings/PLUGINS.md +0 -171
  67. package/builtin/skills/settings/SKILL.md +0 -125
  68. package/builtin/skills/settings/SKILLS.md +0 -107
  69. package/builtin/skills/settings/SUBAGENTS.md +0 -77
  70. package/builtin/subagents/bash.md +0 -19
  71. package/builtin/subagents/explore.md +0 -43
  72. package/builtin/subagents/general-purpose.md +0 -20
  73. package/builtin/subagents/plan.md +0 -56
  74. package/builtin/subagents/vision.md +0 -18
@@ -0,0 +1 @@
1
+ export declare const simplifySkill: Record<string, string>;
@@ -1,4 +1,6 @@
1
- ---
1
+ import { AGENT_TOOL_NAME } from "../../constants/tools.js";
2
+ export const simplifySkill = {
3
+ "skills/simplify/SKILL.md": `---
2
4
  name: simplify
3
5
  description: Review the changed code for reuse, simplification, efficiency, and altitude cleanups, then apply the fixes. Quality only — it does not hunt for bugs; use /code-review for that.
4
6
  disable-model-invocation: true
@@ -10,11 +12,11 @@ Review all changed files for reuse, quality, and efficiency. Fix any issues foun
10
12
 
11
13
  ## Phase 1: Identify Changes
12
14
 
13
- Run `git diff` (or `git diff HEAD` if there are staged changes) to see what changed. If there are no git changes, review the most recently modified files that the user mentioned or that you edited earlier in this conversation.
15
+ Run \`git diff\` (or \`git diff HEAD\` if there are staged changes) to see what changed. If there are no git changes, review the most recently modified files that the user mentioned or that you edited earlier in this conversation.
14
16
 
15
17
  ## Phase 2: Launch Three Review Agents in Parallel
16
18
 
17
- Use the Agent tool to launch all three agents concurrently in a single message. Pass each agent the full diff so it has the complete context.
19
+ Use the ${AGENT_TOOL_NAME} tool to launch all three agents concurrently in a single message. Pass each agent the full diff so it has the complete context.
18
20
 
19
21
  ### Agent 1: Code Reuse Review
20
22
 
@@ -53,3 +55,5 @@ Review the same changes for efficiency:
53
55
  Wait for all three agents to complete. Aggregate their findings and fix each issue directly. If a finding is a false positive or not worth addressing, note it and move on — do not argue with the finding, just skip it.
54
56
 
55
57
  When done, briefly summarize what was fixed (or confirm the code was already clean).
58
+ `,
59
+ };
@@ -0,0 +1 @@
1
+ export declare const subagents: Record<string, string>;
@@ -0,0 +1,164 @@
1
+ import { BASH_TOOL_NAME, EDIT_TOOL_NAME, GLOB_TOOL_NAME, GREP_TOOL_NAME, LSP_TOOL_NAME, READ_TOOL_NAME, WRITE_TOOL_NAME, } from "../constants/tools.js";
2
+ export const subagents = {
3
+ "subagents/bash.md": `---
4
+ name: Bash
5
+ description: Command execution specialist for running bash commands. Use this for git operations, command execution, and other terminal tasks.
6
+ tools: [${BASH_TOOL_NAME}]
7
+ model: inherit
8
+ ---
9
+
10
+ You are a command execution specialist. Your role is to execute bash commands efficiently and safely.
11
+
12
+ Guidelines:
13
+ - Execute commands precisely as instructed
14
+ - For git operations, follow git safety protocols
15
+ - Report command output clearly and concisely
16
+ - If a command fails, explain the error and suggest solutions
17
+ - Use command chaining (&&) for dependent operations
18
+ - Quote paths with spaces properly
19
+ - For clear communication, avoid using emojis
20
+
21
+ Complete the requested operations efficiently.
22
+ `,
23
+ "subagents/explore.md": `---
24
+ name: Explore
25
+ description: 'Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions.'
26
+ tools: [${GLOB_TOOL_NAME}, ${GREP_TOOL_NAME}, ${READ_TOOL_NAME}, ${BASH_TOOL_NAME}, ${LSP_TOOL_NAME}]
27
+ model: fastModel
28
+ ---
29
+
30
+ You are a file search specialist. You excel at thoroughly navigating and exploring codebases.
31
+
32
+ === CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS ===
33
+ This is a READ-ONLY exploration task. You are STRICTLY PROHIBITED from:
34
+ - Creating new files (no ${WRITE_TOOL_NAME}, touch, or file creation of any kind)
35
+ - Modifying existing files (no ${EDIT_TOOL_NAME} operations)
36
+ - Moving or copying files (no mv or cp)
37
+ - Creating temporary files anywhere, including /tmp
38
+ - Using redirect operators (>, >>, |) or heredocs to write to files
39
+ - Running ANY commands that change system state
40
+
41
+ Your role is EXCLUSIVELY to search and analyze existing code. You do NOT have access to file editing tools - attempting to edit files will fail.
42
+
43
+ Your strengths:
44
+ - Rapidly finding files using glob patterns
45
+ - Searching code and text with powerful regex patterns
46
+ - Reading and analyzing file contents
47
+ - Using Language Server Protocol (LSP) for deep code intelligence (definitions, references, etc.)
48
+
49
+ Guidelines:
50
+ - Use ${GLOB_TOOL_NAME} for broad file pattern matching
51
+ - Use ${GREP_TOOL_NAME} for searching file contents with regex
52
+ - Use ${READ_TOOL_NAME} when you know the specific file path you need to read
53
+ - Use ${LSP_TOOL_NAME} for code intelligence features like finding definitions, references, implementations, and symbols. This is especially useful for understanding complex code relationships.
54
+ - Use ${BASH_TOOL_NAME} ONLY for read-only operations (ls, git status, git log, git diff, find, cat, head, tail)
55
+ - NEVER use ${BASH_TOOL_NAME} for: mkdir, touch, rm, cp, mv, git add, git commit, npm install, pip install, or any file creation/modification
56
+ - Adapt your search approach based on the thoroughness level specified by the caller
57
+ - Return file paths as absolute paths in your final response
58
+ - For clear communication, avoid using emojis
59
+ - Communicate your final report directly as a regular message - do NOT attempt to create files
60
+
61
+ NOTE: You are meant to be a fast agent that returns output as quickly as possible. In order to achieve this you must:
62
+ - Make efficient use of the tools that you have at your disposal: be smart about how you search for files and implementations
63
+ - Wherever possible you should try to spawn multiple parallel tool calls for grepping and reading files
64
+
65
+ Complete the user's search request efficiently and report your findings clearly.
66
+ `,
67
+ "subagents/general-purpose.md": `---
68
+ description: General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you.
69
+ ---
70
+
71
+ You are an agent. Given the user's message, you should use the tools available to complete the task. Do what has been asked; nothing more, nothing less. When you complete the task simply respond with a detailed writeup.
72
+
73
+ Your strengths:
74
+ - Searching for code, configurations, and patterns across large codebases
75
+ - Analyzing multiple files to understand system architecture
76
+ - Investigating complex questions that require exploring many files
77
+ - Performing multi-step research tasks
78
+
79
+ Guidelines:
80
+ - For file searches: Use ${GREP_TOOL_NAME} or ${GLOB_TOOL_NAME} when you need to search broadly. Use ${READ_TOOL_NAME} when you know the specific file path.
81
+ - For analysis: Start broad and narrow down. Use multiple search strategies if the first doesn't yield results.
82
+ - Be thorough: Check multiple locations, consider different naming conventions, look for related files.
83
+ - NEVER create files unless they're absolutely necessary for achieving your goal. ALWAYS prefer editing an existing file to creating a new one.
84
+ - NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested.
85
+ - In your final response always share relevant file names and code snippets. Any file paths you return in your response MUST be absolute. Do NOT use relative paths.
86
+ - For clear communication, avoid using emojis.
87
+ `,
88
+ "subagents/plan.md": `---
89
+ name: Plan
90
+ description: Software architect agent for designing implementation plans. Use this when you need to plan the implementation strategy for a task. Returns step-by-step plans, identifies critical files, and considers architectural trade-offs.
91
+ tools: [${GLOB_TOOL_NAME}, ${GREP_TOOL_NAME}, ${READ_TOOL_NAME}, ${BASH_TOOL_NAME}, ${LSP_TOOL_NAME}]
92
+ model: inherit
93
+ ---
94
+
95
+ You are a software architect and planning specialist. Your role is to explore the codebase and design implementation plans.
96
+
97
+ === CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS ===
98
+ This is a READ-ONLY planning task. You are STRICTLY PROHIBITED from:
99
+ - Creating new files (no ${WRITE_TOOL_NAME}, touch, or file creation of any kind)
100
+ - Modifying existing files (no ${EDIT_TOOL_NAME} operations)
101
+ - Moving or copying files (no mv or cp)
102
+ - Creating temporary files anywhere, including /tmp
103
+ - Using redirect operators (>, >>, |) or heredocs to write to files
104
+ - Running ANY commands that change system state
105
+
106
+ Your role is EXCLUSIVELY to explore the codebase and design implementation plans. You do NOT have access to file editing tools - attempting to edit files will fail.
107
+
108
+ You will be provided with a set of requirements and optionally a perspective on how to approach the design process.
109
+
110
+ ## Your Process
111
+
112
+ 1. **Understand Requirements**: Focus on the requirements provided and apply your assigned perspective throughout the design process.
113
+
114
+ 2. **Explore Thoroughly**:
115
+ - Read any files provided to you in the initial prompt
116
+ - Find existing patterns and conventions using ${GLOB_TOOL_NAME}, ${GREP_TOOL_NAME}, and ${READ_TOOL_NAME}
117
+ - Understand the current architecture
118
+ - Identify similar features as reference
119
+ - Trace through relevant code paths
120
+ - Use ${BASH_TOOL_NAME} ONLY for read-only operations (ls, git status, git log, git diff, find, cat, head, tail)
121
+ - NEVER use ${BASH_TOOL_NAME} for: mkdir, touch, rm, cp, mv, git add, git commit, npm install, pip install, or any file creation/modification
122
+
123
+ 3. **Design Solution**:
124
+ - Create implementation approach based on your assigned perspective
125
+ - Consider trade-offs and architectural decisions
126
+ - Follow existing patterns where appropriate
127
+
128
+ 4. **Detail the Plan**:
129
+ - Provide step-by-step implementation strategy
130
+ - Identify dependencies and sequencing
131
+ - Anticipate potential challenges
132
+
133
+ ## Required Output
134
+
135
+ End your response with:
136
+
137
+ ### Critical Files for Implementation
138
+ List 3-5 files most critical for implementing this plan:
139
+ - path/to/file1.ts - [Brief reason: e.g., "Core logic to modify"]
140
+ - path/to/file2.ts - [Brief reason: e.g., "Interfaces to implement"]
141
+ - path/to/file3.ts - [Brief reason: e.g., "Pattern to follow"]
142
+
143
+ REMEMBER: You can ONLY explore and plan. You CANNOT and MUST NOT write, edit, or modify any files. You do NOT have access to file editing tools.
144
+ `,
145
+ "subagents/vision.md": `---
146
+ name: vision
147
+ description: 'Image recognition specialist that runs on the model specified by the WAVE_VISION_MODEL environment variable. Use this when the current model does not support image recognition but the user has shared an image (identify the image by its "[Image source: <path>]" metadata). Pass the image file path(s) in the prompt; this agent reads the image with the ${READ_TOOL_NAME} tool and returns a detailed text description of its contents.'
148
+ tools: [${READ_TOOL_NAME}]
149
+ model: visionModel
150
+ ---
151
+
152
+ You are an image recognition specialist. You run on a vision-capable model and your job is to look at image files and return detailed text descriptions of their contents.
153
+
154
+ When given image file path(s):
155
+ - Use the ${READ_TOOL_NAME} tool on each image path to load it. The ${READ_TOOL_NAME} tool returns the image as base64 image data that you can see directly.
156
+ - Describe the image contents in detail: what is shown, any visible text (transcribe verbatim where relevant), layout, colors, objects, and anything else the caller asked about.
157
+ - If an image cannot be read (file missing, not an image, or too large), report the error clearly and state which path failed.
158
+ - Do not invent or guess content you cannot see — only describe what the image actually shows.
159
+ - Return your description directly as a text message. Do NOT create files.
160
+ - Avoid using emojis in your response.
161
+
162
+ Complete the image recognition task and report your findings clearly.
163
+ `,
164
+ };
@@ -1,6 +1,7 @@
1
1
  import * as aiService from "../services/aiService.js";
2
2
  import { convertMessagesForAPI } from "../utils/convertMessagesForAPI.js";
3
3
  import { supportsVision } from "../utils/modelCapabilities.js";
4
+ import { persistToolImages } from "../utils/toolImagePersistence.js";
4
5
  import { parseTaskNotificationXml, taskNotificationToXml, } from "../utils/notificationXml.js";
5
6
  import { calculateComprehensiveTotalTokens } from "../utils/tokenCalculation.js";
6
7
  import { estimateTokens } from "../utils/tokenEstimate.js";
@@ -452,6 +453,10 @@ export class AIManager {
452
453
  const enhancedSummary = await this.buildPostCompactContext(formattedSummary);
453
454
  // 8. Execute message reconstruction
454
455
  await this.messageManager.compactMessagesAndUpdateSession(enhancedSummary, compactUsage);
456
+ // Clear readFileState after compaction (aligned with Claude Code's
457
+ // compact flow): a file_unchanged stub would otherwise reference Read
458
+ // results that were just folded away. Clearing forces a real re-read.
459
+ this.readFileState.clear();
455
460
  // Re-add plan mode reminder as persistent meta message after compaction
456
461
  const postCompactMode = this.permissionManager?.getCurrentEffectiveMode(this.getModelConfig().permissionMode);
457
462
  if (postCompactMode === "plan") {
@@ -1904,6 +1909,18 @@ ${question}`;
1904
1909
  };
1905
1910
  // Execute tool
1906
1911
  const toolResult = await this.toolManager.execute(functionToolCall.function?.name || "", toolArgs, context);
1912
+ // MCP tools can return images (e.g. Figma screenshots) as in-memory
1913
+ // base64. When this agent's model cannot see them, persist each image to
1914
+ // a temp file so the non-vision path in convertMessagesForAPI can attach
1915
+ // `[Image source: <path>]` metadata and the main model can delegate
1916
+ // recognition to the vision subagent. Vision-capable models keep the
1917
+ // inline base64 — no disk writes.
1918
+ let toolImages = toolResult.images;
1919
+ if (toolImages &&
1920
+ toolImages.length > 0 &&
1921
+ !supportsVision(this.getModelConfig().capabilities)) {
1922
+ toolImages = persistToolImages(toolImages);
1923
+ }
1907
1924
  // Build result content, adding truncation warning if JSON was recovered
1908
1925
  let toolResultContent = toolResult.content ||
1909
1926
  (toolResult.error ? `Error: ${toolResult.error}` : "");
@@ -1926,7 +1943,7 @@ ${question}`;
1926
1943
  backgroundedByUser: toolResult.backgroundedByUser,
1927
1944
  assistantAutoBackgrounded: toolResult.assistantAutoBackgrounded,
1928
1945
  startLineNumber: toolResult.startLineNumber,
1929
- images: toolResult.images,
1946
+ images: toolImages,
1930
1947
  timestamp: Date.now(),
1931
1948
  });
1932
1949
  // Execute PostToolUse hooks after successful tool completion
@@ -3,6 +3,7 @@ import * as os from "os";
3
3
  import * as fs from "fs";
4
4
  import * as path from "path";
5
5
  import { stripAnsiColors } from "../utils/stringUtils.js";
6
+ import { WindowsStreamDecoder } from "../utils/encoding.js";
6
7
  import { logger } from "../utils/globalLogger.js";
7
8
  import { resolveShellPath } from "../utils/shellResolver.js";
8
9
  export class BackgroundTaskManager {
@@ -116,8 +117,12 @@ export class BackgroundTaskManager {
116
117
  }
117
118
  }, timeout);
118
119
  }
120
+ // On Windows, native tools (taskkill, powershell, ...) write GBK (cp936)
121
+ // instead of UTF-8; decode their byte streams accordingly (issue #1753).
122
+ const stdoutDecoder = process.platform === "win32" ? new WindowsStreamDecoder() : null;
123
+ const stderrDecoder = process.platform === "win32" ? new WindowsStreamDecoder() : null;
119
124
  const onStdout = (data) => {
120
- const stripped = stripAnsiColors(data.toString());
125
+ const stripped = stripAnsiColors(stdoutDecoder ? stdoutDecoder.push(data) : data.toString());
121
126
  shell.stdout += stripped;
122
127
  if (logStream.writable) {
123
128
  logStream.write(stripped);
@@ -125,7 +130,7 @@ export class BackgroundTaskManager {
125
130
  this.notifyTasksChange();
126
131
  };
127
132
  const onStderr = (data) => {
128
- const stripped = stripAnsiColors(data.toString());
133
+ const stripped = stripAnsiColors(stderrDecoder ? stderrDecoder.push(data) : data.toString());
129
134
  shell.stderr += stripped;
130
135
  if (logStream.writable) {
131
136
  logStream.write(stripped);
@@ -136,6 +141,26 @@ export class BackgroundTaskManager {
136
141
  if (timeoutHandle) {
137
142
  clearTimeout(timeoutHandle);
138
143
  }
144
+ // Decode any bytes still held at stream end (e.g. a trailing UTF-8
145
+ // character split across the last chunk).
146
+ if (stdoutDecoder) {
147
+ const rest = stdoutDecoder.flush();
148
+ if (rest) {
149
+ shell.stdout += rest;
150
+ if (logStream.writable) {
151
+ logStream.write(rest);
152
+ }
153
+ }
154
+ }
155
+ if (stderrDecoder) {
156
+ const rest = stderrDecoder.flush();
157
+ if (rest) {
158
+ shell.stderr += rest;
159
+ if (logStream.writable) {
160
+ logStream.write(rest);
161
+ }
162
+ }
163
+ }
139
164
  if (logStream.writable) {
140
165
  logStream.end();
141
166
  }
@@ -256,8 +281,12 @@ export class BackgroundTaskManager {
256
281
  };
257
282
  this.tasks.set(id, shell);
258
283
  this.notifyTasksChange();
284
+ // On Windows, native tools write GBK (cp936) instead of UTF-8; decode
285
+ // their byte streams accordingly (issue #1753).
286
+ const stdoutDecoder = process.platform === "win32" ? new WindowsStreamDecoder() : null;
287
+ const stderrDecoder = process.platform === "win32" ? new WindowsStreamDecoder() : null;
259
288
  child.stdout?.on("data", (data) => {
260
- const stripped = stripAnsiColors(data.toString());
289
+ const stripped = stripAnsiColors(stdoutDecoder ? stdoutDecoder.push(data) : data.toString());
261
290
  shell.stdout += stripped;
262
291
  if (logStream.writable) {
263
292
  logStream.write(stripped);
@@ -265,7 +294,7 @@ export class BackgroundTaskManager {
265
294
  this.notifyTasksChange();
266
295
  });
267
296
  child.stderr?.on("data", (data) => {
268
- const stripped = stripAnsiColors(data.toString());
297
+ const stripped = stripAnsiColors(stderrDecoder ? stderrDecoder.push(data) : data.toString());
269
298
  shell.stderr += stripped;
270
299
  if (logStream.writable) {
271
300
  logStream.write(stripped);
@@ -273,6 +302,25 @@ export class BackgroundTaskManager {
273
302
  this.notifyTasksChange();
274
303
  });
275
304
  child.on("exit", (code) => {
305
+ // Decode any bytes still held at stream end
306
+ if (stdoutDecoder) {
307
+ const rest = stdoutDecoder.flush();
308
+ if (rest) {
309
+ shell.stdout += rest;
310
+ if (logStream.writable) {
311
+ logStream.write(rest);
312
+ }
313
+ }
314
+ }
315
+ if (stderrDecoder) {
316
+ const rest = stderrDecoder.flush();
317
+ if (rest) {
318
+ shell.stderr += rest;
319
+ if (logStream.writable) {
320
+ logStream.write(rest);
321
+ }
322
+ }
323
+ }
276
324
  if (logStream.writable) {
277
325
  logStream.end();
278
326
  }
@@ -656,7 +656,7 @@ export class McpManager {
656
656
  else if (c.type === "image" && c.data) {
657
657
  images.push({
658
658
  data: c.data,
659
- mediaType: "image/png", // Default to PNG
659
+ mediaType: c.mimeType || "image/png", // MCP standard mimeType, PNG fallback
660
660
  });
661
661
  }
662
662
  else if (c.type === "resource") {
@@ -222,6 +222,12 @@ export declare class MessageManager {
222
222
  * Extract file read contents from tool result blocks in a message.
223
223
  */
224
224
  private extractFileReadsFromMessage;
225
+ /**
226
+ * Bound recentFileReads growth: evict oldest entries beyond the cache limit
227
+ * and drop content from all but the most recent entries (paths are kept for
228
+ * hasFileBeenRead). Prevents unbounded memory growth in long sessions.
229
+ */
230
+ private trimRecentFileReads;
225
231
  /**
226
232
  * Get recent file read contents, sorted by timestamp (newest first).
227
233
  * @param maxFiles - Maximum number of files to return
@@ -5,6 +5,13 @@ import { appendMessages, createSession, generateSessionId, SESSION_DIR, } from "
5
5
  import { pathEncoder } from "../utils/pathEncoder.js";
6
6
  import { estimateTokens } from "../utils/tokenEstimate.js";
7
7
  import { READ_TOOL_NAME } from "../constants/tools.js";
8
+ // Cap for recentFileReads: content is only consumed by getRecentFileReads
9
+ // (top 5 for post-compact restoration), so bound both the entry count and the
10
+ // number of entries that keep full content (aligned with Claude Code's
11
+ // FileStateCache 100-entry limit). Paths beyond the content cap are kept so
12
+ // hasFileBeenRead still works for read-before-edit enforcement.
13
+ const RECENT_FILE_READS_CACHE_LIMIT = 100;
14
+ const RECENT_FILE_READS_CONTENT_LIMIT = 10;
8
15
  import { logger } from "../utils/globalLogger.js";
9
16
  export class MessageManager {
10
17
  constructor(container, options) {
@@ -397,6 +404,8 @@ export class MessageManager {
397
404
  // Update in-memory state
398
405
  this.setMessages(newMessages);
399
406
  this.savedMessageCount = newMessages.length;
407
+ // Trim recent file read cache so contents compacted away are released
408
+ this.trimRecentFileReads();
400
409
  // Clear and rebuild loaded rule IDs from remaining meta messages
401
410
  this.clearLoadedRuleIds();
402
411
  this.rebuildLoadedRuleIds();
@@ -779,10 +788,34 @@ export class MessageManager {
779
788
  content: block.result,
780
789
  timestamp: Date.now(),
781
790
  });
791
+ this.trimRecentFileReads();
782
792
  }
783
793
  }
784
794
  }
785
795
  }
796
+ /**
797
+ * Bound recentFileReads growth: evict oldest entries beyond the cache limit
798
+ * and drop content from all but the most recent entries (paths are kept for
799
+ * hasFileBeenRead). Prevents unbounded memory growth in long sessions.
800
+ */
801
+ trimRecentFileReads() {
802
+ if (this.recentFileReads.size > RECENT_FILE_READS_CACHE_LIMIT) {
803
+ const sortedAsc = Array.from(this.recentFileReads.entries()).sort(([, a], [, b]) => a.timestamp - b.timestamp);
804
+ const excess = this.recentFileReads.size - RECENT_FILE_READS_CACHE_LIMIT;
805
+ for (const [path] of sortedAsc.slice(0, excess)) {
806
+ this.recentFileReads.delete(path);
807
+ }
808
+ }
809
+ if (this.recentFileReads.size > RECENT_FILE_READS_CONTENT_LIMIT) {
810
+ const sortedDesc = Array.from(this.recentFileReads.entries()).sort(([, a], [, b]) => b.timestamp - a.timestamp);
811
+ for (const [path, entry] of sortedDesc.slice(RECENT_FILE_READS_CONTENT_LIMIT)) {
812
+ this.recentFileReads.set(path, {
813
+ content: "",
814
+ timestamp: entry.timestamp,
815
+ });
816
+ }
817
+ }
818
+ }
786
819
  /**
787
820
  * Get recent file read contents, sorted by timestamp (newest first).
788
821
  * @param maxFiles - Maximum number of files to return
@@ -66,6 +66,19 @@ export declare class SkillManager extends EventEmitter {
66
66
  * Find all directories that could contain skills
67
67
  */
68
68
  private findSkillDirectories;
69
+ /**
70
+ * Whether a discovered skill is a feature-gated builtin skill whose gate
71
+ * is currently off. Mirrors ToolManager's gate for the Artifact tool so
72
+ * the /artifact skill command stays in sync with enableArtifact.
73
+ */
74
+ private isSkillGatedOff;
75
+ /**
76
+ * Re-evaluate feature-gated builtin skills after a live configuration
77
+ * reload (same hook as ToolManager.reloadFeatureGatedTools). Re-scans
78
+ * skill directories and emits "refreshed" so slash-command registration
79
+ * follows the gate.
80
+ */
81
+ reloadFeatureGatedSkills(): Promise<void>;
69
82
  /**
70
83
  * Execute a skill by name
71
84
  */
@@ -7,6 +7,7 @@ import { parseSkillFile, formatSkillError } from "../utils/skillParser.js";
7
7
  import { substituteCommandParameters } from "../utils/commandArgumentParser.js";
8
8
  import { parseBashCommands, replaceBashCommandsWithOutput, executeBashCommands, } from "../utils/markdownParser.js";
9
9
  import { getBuiltinSkillsDir } from "../utils/configPaths.js";
10
+ import { isArtifactEnabled } from "../services/artifactAvailability.js";
10
11
  import { logger } from "../utils/globalLogger.js";
11
12
  /**
12
13
  * Manages skill discovery and loading
@@ -247,6 +248,12 @@ export class SkillManager extends EventEmitter {
247
248
  ...parsed.skillMetadata,
248
249
  type: collection.type,
249
250
  };
251
+ // Feature-gated builtin skills (e.g. artifact behind
252
+ // enableArtifact) are skipped while the gate is off, mirroring
253
+ // ToolManager's registration gate for the Artifact tool.
254
+ if (this.isSkillGatedOff(skillMetadata)) {
255
+ continue;
256
+ }
250
257
  // Create full skill object with content
251
258
  const skill = {
252
259
  ...skillMetadata,
@@ -304,6 +311,29 @@ export class SkillManager extends EventEmitter {
304
311
  }
305
312
  return directories;
306
313
  }
314
+ /**
315
+ * Whether a discovered skill is a feature-gated builtin skill whose gate
316
+ * is currently off. Mirrors ToolManager's gate for the Artifact tool so
317
+ * the /artifact skill command stays in sync with enableArtifact.
318
+ */
319
+ isSkillGatedOff(metadata) {
320
+ if (metadata.type !== "builtin") {
321
+ return false;
322
+ }
323
+ if (metadata.name === "artifact") {
324
+ return !isArtifactEnabled(this.workdir);
325
+ }
326
+ return false;
327
+ }
328
+ /**
329
+ * Re-evaluate feature-gated builtin skills after a live configuration
330
+ * reload (same hook as ToolManager.reloadFeatureGatedTools). Re-scans
331
+ * skill directories and emits "refreshed" so slash-command registration
332
+ * follows the gate.
333
+ */
334
+ async reloadFeatureGatedSkills() {
335
+ await this.refreshSkills();
336
+ }
307
337
  /**
308
338
  * Execute a skill by name
309
339
  */
@@ -150,6 +150,14 @@ export declare class SubagentManager {
150
150
  * Clean up completed, errored, or aborted instances
151
151
  */
152
152
  cleanupInstance(subagentId: string): void;
153
+ /**
154
+ * Release a subagent instance so its graph (message history, file-read
155
+ * caches, log stream) can be garbage-collected. Besides removing the
156
+ * instance from the map, this drops the background task's onStop closure,
157
+ * which captures the instance and would otherwise keep the whole graph
158
+ * alive for the rest of the session.
159
+ */
160
+ private releaseInstance;
153
161
  /**
154
162
  * Get all active instances
155
163
  */
@@ -171,6 +171,16 @@ export class SubagentManager {
171
171
  // Without this, concurrent background subagents steal sibling completion
172
172
  // notifications from the parent queue, causing the main agent to exit early.
173
173
  subagentContainer.register("MessageQueue", new MessageQueue());
174
+ // Deliberately shadow the parent ReversionManager with `undefined` so the
175
+ // subagent resolves no reversion manager instead of falling back to the
176
+ // parent's. Subagent Write/Edit must not be recorded into the main agent's
177
+ // file_history buffer — this mirrors Claude Code's explicit isolation for
178
+ // forked agents (`updateFileHistoryState: () => {}` in forkedAgent.ts).
179
+ // Without this, the subagent drains the SHARED parent buffer into its own
180
+ // transient messages (discarded on cleanup), leaving orphaned snapshot
181
+ // files on disk and racing the main agent's own drain. All ReversionManager
182
+ // consumers are truthiness-guarded, so resolving to `undefined` is safe.
183
+ subagentContainer.register("ReversionManager", undefined);
174
184
  // Register a modified AgentOptions without onLoadingChange to prevent subagent loading
175
185
  // from affecting the parent agent's loading state
176
186
  const parentOptions = this.container.get("AgentOptions");
@@ -342,13 +352,29 @@ export class SubagentManager {
342
352
  task.runtime = task.endTime - startTime;
343
353
  }
344
354
  }
355
+ finally {
356
+ // Free the instance once the background task finishes: without
357
+ // this the instance (message history, caches) would linger in the
358
+ // map for the whole session, and the task's onStop closure would
359
+ // keep it alive even after that.
360
+ this.releaseInstance(instance.subagentId);
361
+ }
345
362
  })();
346
363
  return taskId;
347
364
  }
348
- return await this.internalExecute(instance, prompt, abortSignal);
365
+ const result = await this.internalExecute(instance, prompt, abortSignal);
366
+ // Free the instance once the subagent finishes. Callers that still hold
367
+ // the reference can read its messageManager, but the map entry and the
368
+ // background task's onStop closure are released so the message history
369
+ // and caches can be garbage-collected.
370
+ this.releaseInstance(instance.subagentId);
371
+ return result;
349
372
  }
350
373
  catch (error) {
351
374
  this.updateInstanceStatus(instance.subagentId, "error");
375
+ // Release on failure too so errored instances don't linger for the
376
+ // session (the abort listener already releases on abort).
377
+ this.releaseInstance(instance.subagentId);
352
378
  throw error;
353
379
  }
354
380
  }
@@ -543,8 +569,37 @@ export class SubagentManager {
543
569
  (instance.status === "completed" ||
544
570
  instance.status === "error" ||
545
571
  instance.status === "aborted")) {
546
- this.instances.delete(subagentId);
572
+ this.releaseInstance(subagentId);
573
+ }
574
+ }
575
+ /**
576
+ * Release a subagent instance so its graph (message history, file-read
577
+ * caches, log stream) can be garbage-collected. Besides removing the
578
+ * instance from the map, this drops the background task's onStop closure,
579
+ * which captures the instance and would otherwise keep the whole graph
580
+ * alive for the rest of the session.
581
+ */
582
+ releaseInstance(subagentId) {
583
+ const instance = this.instances.get(subagentId);
584
+ if (!instance) {
585
+ return;
586
+ }
587
+ if (instance.backgroundTaskId) {
588
+ const backgroundTaskManager = this.container.has("BackgroundTaskManager")
589
+ ? this.container.get("BackgroundTaskManager")
590
+ : undefined;
591
+ const task = backgroundTaskManager?.getTask(instance.backgroundTaskId);
592
+ if (task) {
593
+ task.onStop = undefined;
594
+ }
595
+ }
596
+ if (instance.logStream && !instance.logStream.writableEnded) {
597
+ // Only force-close a stream that hasn't been ended yet (the completion
598
+ // and error paths already call end(); destroy would drop buffered data).
599
+ instance.logStream.destroy();
547
600
  }
601
+ instance.logStream = undefined;
602
+ this.instances.delete(subagentId);
548
603
  }
549
604
  /**
550
605
  * Get all active instances
@@ -53,6 +53,14 @@ declare class ToolManager {
53
53
  * ```
54
54
  */
55
55
  initializeBuiltInTools(): void;
56
+ /**
57
+ * Re-evaluate feature-gated built-in tools after a live configuration
58
+ * reload. Currently gates the Artifact tool on settings.json
59
+ * `enableArtifact`. Safe to call multiple times: gated tools are removed
60
+ * from the registry first, then initializeBuiltInTools() re-registers them
61
+ * only if still enabled (so toggling the flag off actually unregisters).
62
+ */
63
+ reloadFeatureGatedTools(): void;
56
64
  /**
57
65
  * Check if a tool should be enabled based on tools configuration and permission rules
58
66
  */
@@ -114,6 +114,17 @@ class ToolManager {
114
114
  }
115
115
  }
116
116
  }
117
+ /**
118
+ * Re-evaluate feature-gated built-in tools after a live configuration
119
+ * reload. Currently gates the Artifact tool on settings.json
120
+ * `enableArtifact`. Safe to call multiple times: gated tools are removed
121
+ * from the registry first, then initializeBuiltInTools() re-registers them
122
+ * only if still enabled (so toggling the flag off actually unregisters).
123
+ */
124
+ reloadFeatureGatedTools() {
125
+ this.toolsRegistry.delete(artifactTool.name);
126
+ this.initializeBuiltInTools();
127
+ }
117
128
  /**
118
129
  * Check if a tool should be enabled based on tools configuration and permission rules
119
130
  */
@@ -2,6 +2,8 @@
2
2
  export declare const ARTIFACT_DEFAULT_ENABLED = false;
3
3
  /**
4
4
  * Whether the Artifact tool should be registered / usable for the given workdir.
5
- * Explicit `enableArtifact` in merged settings wins over the code default.
5
+ * Resolution order: remote managed settings (`enableArtifact` from
6
+ * `GET /api/wave/settings`, admin override) → explicit `enableArtifact` in local
7
+ * merged settings → code default.
6
8
  */
7
9
  export declare function isArtifactEnabled(workdir?: string): boolean;