wave-agent-sdk 1.1.5 → 1.3.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.
Files changed (210) hide show
  1. package/dist/agent.d.ts +128 -28
  2. package/dist/agent.js +201 -49
  3. package/dist/builtin/index.js +2 -0
  4. package/dist/builtin/plugins.js +11 -20
  5. package/dist/builtin/skills/settings.js +7 -20
  6. package/dist/builtin/skills/wave-daemon.d.ts +1 -0
  7. package/dist/builtin/skills/wave-daemon.js +194 -0
  8. package/dist/constants/images.d.ts +26 -0
  9. package/dist/constants/images.js +26 -0
  10. package/dist/constants/index.d.ts +16 -0
  11. package/dist/constants/index.js +16 -0
  12. package/dist/constants/memory.d.ts +26 -0
  13. package/dist/constants/memory.js +34 -0
  14. package/dist/constants/messages.d.ts +11 -0
  15. package/dist/constants/messages.js +11 -0
  16. package/dist/constants/plugins.d.ts +8 -0
  17. package/dist/constants/plugins.js +8 -0
  18. package/dist/constants/tools.d.ts +1 -0
  19. package/dist/constants/tools.js +1 -0
  20. package/dist/core/plugin.d.ts +54 -10
  21. package/dist/core/plugin.js +137 -23
  22. package/dist/core/session.d.ts +1 -1
  23. package/dist/core/session.js +1 -1
  24. package/dist/exec/catalog.d.ts +140 -0
  25. package/dist/exec/catalog.js +470 -0
  26. package/dist/exec/catalogAnnouncement.d.ts +89 -0
  27. package/dist/exec/catalogAnnouncement.js +293 -0
  28. package/dist/exec/constants.d.ts +51 -0
  29. package/dist/exec/constants.js +51 -0
  30. package/dist/exec/execRuntime.d.ts +55 -0
  31. package/dist/exec/execRuntime.js +217 -0
  32. package/dist/exec/workerSource.d.ts +28 -0
  33. package/dist/exec/workerSource.js +299 -0
  34. package/dist/host/index.d.ts +23 -0
  35. package/dist/host/index.js +23 -0
  36. package/dist/index.d.ts +7 -1
  37. package/dist/index.js +8 -1
  38. package/dist/managers/MemoryRuleManager.d.ts +6 -0
  39. package/dist/managers/MemoryRuleManager.js +12 -0
  40. package/dist/managers/aiManager.d.ts +35 -25
  41. package/dist/managers/aiManager.js +204 -202
  42. package/dist/managers/backgroundTaskManager.js +14 -0
  43. package/dist/managers/bashModeManager.d.ts +33 -0
  44. package/dist/managers/bashModeManager.js +110 -0
  45. package/dist/managers/hookManager.d.ts +18 -0
  46. package/dist/managers/hookManager.js +37 -3
  47. package/dist/managers/liveConfigManager.d.ts +33 -0
  48. package/dist/managers/liveConfigManager.js +106 -11
  49. package/dist/managers/lspManager.d.ts +9 -0
  50. package/dist/managers/lspManager.js +47 -18
  51. package/dist/managers/mcpManager.d.ts +68 -10
  52. package/dist/managers/mcpManager.js +265 -15
  53. package/dist/managers/messageManager.d.ts +60 -18
  54. package/dist/managers/messageManager.js +170 -81
  55. package/dist/managers/permissionManager.d.ts +69 -0
  56. package/dist/managers/permissionManager.js +221 -78
  57. package/dist/managers/planManager.d.ts +9 -0
  58. package/dist/managers/planManager.js +19 -1
  59. package/dist/managers/pluginManager.d.ts +46 -2
  60. package/dist/managers/pluginManager.js +117 -11
  61. package/dist/managers/pluginScopeManager.d.ts +15 -2
  62. package/dist/managers/pluginScopeManager.js +20 -1
  63. package/dist/managers/skillManager.d.ts +50 -0
  64. package/dist/managers/skillManager.js +166 -12
  65. package/dist/managers/slashCommandManager.d.ts +10 -0
  66. package/dist/managers/slashCommandManager.js +44 -31
  67. package/dist/managers/subagentManager.d.ts +15 -0
  68. package/dist/managers/subagentManager.js +81 -9
  69. package/dist/managers/toolManager.d.ts +29 -3
  70. package/dist/managers/toolManager.js +87 -13
  71. package/dist/managers/workflowManager.js +6 -0
  72. package/dist/prompts/autoMemory.d.ts +9 -0
  73. package/dist/prompts/autoMemory.js +30 -31
  74. package/dist/prompts/autoMemoryExtraction.d.ts +4 -0
  75. package/dist/prompts/autoMemoryExtraction.js +8 -111
  76. package/dist/prompts/index.d.ts +0 -1
  77. package/dist/prompts/index.js +0 -4
  78. package/dist/prompts/memoryTypes.d.ts +63 -0
  79. package/dist/prompts/memoryTypes.js +191 -0
  80. package/dist/services/GitService.d.ts +7 -0
  81. package/dist/services/GitService.js +23 -0
  82. package/dist/services/MarketplaceService.d.ts +101 -17
  83. package/dist/services/MarketplaceService.js +323 -102
  84. package/dist/services/artifactContent.d.ts +84 -0
  85. package/dist/services/artifactContent.js +204 -0
  86. package/dist/services/artifactSession.d.ts +6 -0
  87. package/dist/services/artifactSession.js +17 -0
  88. package/dist/services/autoMemoryService.js +5 -13
  89. package/dist/services/configurationService.d.ts +92 -9
  90. package/dist/services/configurationService.js +246 -64
  91. package/dist/services/contentSummarizer.d.ts +15 -0
  92. package/dist/services/contentSummarizer.js +45 -0
  93. package/dist/services/execAvailability.d.ts +9 -0
  94. package/dist/services/execAvailability.js +32 -0
  95. package/dist/services/fileWatcher.js +61 -6
  96. package/dist/services/initializationService.js +21 -17
  97. package/dist/services/interactionService.d.ts +9 -1
  98. package/dist/services/interactionService.js +28 -8
  99. package/dist/services/jsonlHandler.d.ts +98 -0
  100. package/dist/services/jsonlHandler.js +250 -12
  101. package/dist/services/memory.d.ts +17 -1
  102. package/dist/services/memory.js +44 -7
  103. package/dist/services/officialMarketplaceMirror.d.ts +85 -0
  104. package/dist/services/officialMarketplaceMirror.js +290 -0
  105. package/dist/services/pluginLoader.d.ts +12 -4
  106. package/dist/services/pluginLoader.js +38 -7
  107. package/dist/services/remoteSettingsService.js +20 -6
  108. package/dist/services/session.d.ts +74 -0
  109. package/dist/services/session.js +174 -16
  110. package/dist/services/sessionEntries.d.ts +2 -0
  111. package/dist/services/sessionEntries.js +20 -0
  112. package/dist/services/worktreeHooks.js +6 -1
  113. package/dist/stdio/index.d.ts +12 -0
  114. package/dist/stdio/index.js +12 -0
  115. package/dist/stdio/notificationRouter.d.ts +38 -0
  116. package/dist/stdio/notificationRouter.js +97 -0
  117. package/dist/stdio/rpcClient.d.ts +18 -0
  118. package/dist/stdio/rpcClient.js +10 -0
  119. package/dist/stdio/stdioAgent.d.ts +229 -0
  120. package/dist/stdio/stdioAgent.js +360 -0
  121. package/dist/tools/artifactTool.js +406 -273
  122. package/dist/tools/bashTool.js +10 -6
  123. package/dist/tools/editTool.js +6 -3
  124. package/dist/tools/execTool.d.ts +2 -0
  125. package/dist/tools/execTool.js +165 -0
  126. package/dist/tools/exitPlanMode.js +10 -2
  127. package/dist/tools/grepTool.js +7 -1
  128. package/dist/tools/readTool.js +30 -2
  129. package/dist/tools/types.d.ts +34 -8
  130. package/dist/tools/webFetchTool.js +15 -166
  131. package/dist/tools/workflowTool.js +40 -8
  132. package/dist/tools/writeTool.js +6 -3
  133. package/dist/types/agent.d.ts +24 -1
  134. package/dist/types/commands.d.ts +7 -0
  135. package/dist/types/configuration.d.ts +45 -2
  136. package/dist/types/hooks.d.ts +1 -0
  137. package/dist/types/hooks.js +19 -0
  138. package/dist/types/marketplace.d.ts +40 -2
  139. package/dist/types/mcp.d.ts +42 -0
  140. package/dist/types/messaging.d.ts +1 -8
  141. package/dist/types/permissions.d.ts +22 -0
  142. package/dist/types/permissions.js +17 -0
  143. package/dist/types/plugins.d.ts +26 -2
  144. package/dist/types/skills.d.ts +26 -0
  145. package/dist/utils/bashParser.d.ts +17 -0
  146. package/dist/utils/bashParser.js +72 -0
  147. package/dist/utils/bashStructure/bashLexer.d.ts +96 -0
  148. package/dist/utils/bashStructure/bashLexer.js +676 -0
  149. package/dist/utils/bashStructure/bashParser.d.ts +144 -0
  150. package/dist/utils/bashStructure/bashParser.js +606 -0
  151. package/dist/utils/bashStructure/bashSemantics.d.ts +70 -0
  152. package/dist/utils/bashStructure/bashSemantics.js +477 -0
  153. package/dist/utils/bashStructure/index.d.ts +26 -0
  154. package/dist/utils/bashStructure/index.js +27 -0
  155. package/dist/utils/bashStructure/types.d.ts +62 -0
  156. package/dist/utils/bashStructure/types.js +47 -0
  157. package/dist/utils/constants.d.ts +10 -0
  158. package/dist/utils/constants.js +10 -0
  159. package/dist/utils/containerSetup.js +48 -6
  160. package/dist/utils/convertMessagesForAPI.d.ts +7 -1
  161. package/dist/utils/convertMessagesForAPI.js +64 -14
  162. package/dist/utils/fileChangeReminder.d.ts +20 -0
  163. package/dist/utils/fileChangeReminder.js +153 -0
  164. package/dist/utils/fileSearch.js +4 -3
  165. package/dist/utils/fileUtils.d.ts +44 -0
  166. package/dist/utils/fileUtils.js +118 -0
  167. package/dist/utils/frontmatterYaml.d.ts +33 -0
  168. package/dist/utils/frontmatterYaml.js +192 -0
  169. package/dist/utils/imageBudget.d.ts +85 -0
  170. package/dist/utils/imageBudget.js +109 -0
  171. package/dist/utils/imageDimensions.d.ts +83 -0
  172. package/dist/utils/imageDimensions.js +232 -0
  173. package/dist/utils/imageProcessor.d.ts +66 -0
  174. package/dist/utils/imageProcessor.js +84 -0
  175. package/dist/utils/imageRewrite.d.ts +29 -0
  176. package/dist/utils/imageRewrite.js +251 -0
  177. package/dist/utils/markdownParser.d.ts +5 -1
  178. package/dist/utils/markdownParser.js +9 -51
  179. package/dist/utils/mcpInstructions.d.ts +61 -0
  180. package/dist/utils/mcpInstructions.js +126 -0
  181. package/dist/utils/mcpUtils.d.ts +7 -0
  182. package/dist/utils/mcpUtils.js +11 -2
  183. package/dist/utils/memoryAge.d.ts +32 -0
  184. package/dist/utils/memoryAge.js +47 -0
  185. package/dist/utils/memoryEntrypoint.d.ts +20 -0
  186. package/dist/utils/memoryEntrypoint.js +49 -0
  187. package/dist/utils/memoryIndex.d.ts +30 -0
  188. package/dist/utils/memoryIndex.js +76 -0
  189. package/dist/utils/messageOperations.d.ts +6 -20
  190. package/dist/utils/messageOperations.js +40 -91
  191. package/dist/utils/nestedMemory.d.ts +22 -0
  192. package/dist/utils/nestedMemory.js +61 -0
  193. package/dist/utils/npmTarball.d.ts +19 -0
  194. package/dist/utils/npmTarball.js +92 -0
  195. package/dist/utils/pluginSource.d.ts +37 -0
  196. package/dist/utils/pluginSource.js +73 -0
  197. package/dist/utils/ripgrep.d.ts +18 -4
  198. package/dist/utils/ripgrep.js +56 -4
  199. package/dist/utils/runtimeDeps.d.ts +35 -0
  200. package/dist/utils/runtimeDeps.js +426 -0
  201. package/dist/utils/skillParser.js +22 -52
  202. package/dist/utils/subagentParser.js +48 -45
  203. package/dist/utils/tokenCalculation.js +0 -8
  204. package/dist/utils/userSettings.d.ts +90 -0
  205. package/dist/utils/userSettings.js +291 -0
  206. package/dist/utils/worktreeUtils.d.ts +2 -1
  207. package/dist/utils/worktreeUtils.js +64 -34
  208. package/package.json +12 -4
  209. package/dist/managers/bangManager.d.ts +0 -26
  210. package/dist/managers/bangManager.js +0 -78
@@ -1,9 +1,12 @@
1
- import { addAssistantMessageToMessages, updateToolBlockInMessage, addErrorBlockToMessage, addUserMessageToMessages, updateUserMessageInMessages, addBangMessage, updateBangInMessage, completeBangInMessage, removeLastUserMessage, addToolBlockToMessageInMessages, addNotificationMessageToMessages, generateMessageId, sliceFromLastCompact, } from "../utils/messageOperations.js";
1
+ import { addAssistantMessageToMessages, updateToolBlockInMessage, addErrorBlockToMessage, addUserMessageToMessages, updateUserMessageInMessages, removeLastUserMessage, addToolBlockToMessageInMessages, addNotificationMessageToMessages, generateMessageId, sliceFromLastCompact, } from "../utils/messageOperations.js";
2
2
  import { getLastApiRounds } from "../utils/groupMessagesByApiRound.js";
3
- import { join, isAbsolute, relative } from "path";
4
- import { appendMessages, createSession, generateSessionId, SESSION_DIR, } from "../services/session.js";
3
+ import { join, isAbsolute, relative, resolve } from "path";
4
+ import { appendMessages, createSession, generateSessionId,
5
+ // Aliased: this class exposes a `reAppendSessionMetadata` method of its own.
6
+ reAppendSessionMetadata as reAppendSessionMetadataToFile, truncateSession, SESSION_DIR, } from "../services/session.js";
5
7
  import { pathEncoder } from "../utils/pathEncoder.js";
6
8
  import { estimateTokens } from "../utils/tokenEstimate.js";
9
+ import { findNestedMemoryFiles, } from "../utils/nestedMemory.js";
7
10
  import { READ_TOOL_NAME } from "../constants/tools.js";
8
11
  // Cap for recentFileReads: content is only consumed by getRecentFileReads
9
12
  // (top 5 for post-compact restoration), so bound both the entry count and the
@@ -16,10 +19,19 @@ import { logger } from "../utils/globalLogger.js";
16
19
  export class MessageManager {
17
20
  constructor(container, options) {
18
21
  this.container = container;
22
+ /** Full display message stream for the UI. Mirrors `messages` (context) on
23
+ * every normal change; the two diverge only around compaction, where
24
+ * `messages` is replaced by [compact, ...last API rounds] for the API
25
+ * context while `displayMessages` keeps the full history and appends the
26
+ * compact block (aligned with Claude Code desktop/VS Code: compaction
27
+ * folds the API context but leaves the UI transcript intact). */
28
+ this.displayMessages = [];
19
29
  this.pendingFileReadTriggers = new Set(); // File paths read via Read tool, awaiting rule matching
20
30
  this.loadedRuleIds = new Set(); // IDs of conditional rules already injected as meta messages
31
+ this.pendingNestedMemoryTriggers = new Set(); // Absolute paths read since the last nested-memory scan
32
+ this.loadedNestedMemoryPaths = new Set(); // Nested memory files already injected (session-scoped, never evicted)
21
33
  this.recentFileReads = new Map(); // Track file read contents
22
- this.invokedSkills = new Map(); // Track invoked skill names
34
+ this.invokedSkills = new Map(); // Track invoked skills
23
35
  this._usages = [];
24
36
  this.sessionId = generateSessionId();
25
37
  this.messages = [];
@@ -55,12 +67,30 @@ export class MessageManager {
55
67
  getMessages() {
56
68
  return [...this.messages];
57
69
  }
70
+ /** Full UI display message stream (includes pre-compaction history). */
71
+ getDisplayMessages() {
72
+ return [...this.displayMessages];
73
+ }
58
74
  getUsages() {
59
75
  return [...this._usages];
60
76
  }
61
77
  getLatestTotalTokens() {
62
78
  return this.latestTotalTokens;
63
79
  }
80
+ /**
81
+ * Re-point this manager at another working directory (in-session session
82
+ * switch). The encoded workdir and the transcript path are both derived from
83
+ * it, so they must move together with it: otherwise the next append would
84
+ * create a second copy of the same session file under the old project dir.
85
+ */
86
+ setWorkdir(workdir) {
87
+ if (this.workdir === workdir) {
88
+ return;
89
+ }
90
+ this.workdir = workdir;
91
+ this.encodedWorkdir = pathEncoder.encodeSync(workdir);
92
+ this.transcriptPath = this.computeTranscriptPath();
93
+ }
64
94
  getWorkdir() {
65
95
  return this.workdir;
66
96
  }
@@ -163,6 +193,31 @@ export class MessageManager {
163
193
  : filePath;
164
194
  this.pendingFileReadTriggers.add(normalizedPath);
165
195
  }
196
+ /**
197
+ * Record an absolute path whose read may pull in a nested memory file
198
+ * (`AGENTS.md` in one of its ancestor directories). Separate from
199
+ * `triggerFileRead`: rule matching needs workdir-relative paths, the nested
200
+ * walk needs absolute ones.
201
+ */
202
+ triggerNestedMemory(filePath) {
203
+ this.pendingNestedMemoryTriggers.add(isAbsolute(filePath) ? filePath : resolve(this.workdir, filePath));
204
+ }
205
+ /**
206
+ * Resolve nested memory files for the paths read since the last call,
207
+ * outermost first. Clears the pending set; dedup is session-scoped via
208
+ * `loadedNestedMemoryPaths`.
209
+ */
210
+ async collectNestedMemoryFiles() {
211
+ if (this.pendingNestedMemoryTriggers.size === 0)
212
+ return [];
213
+ const filePaths = Array.from(this.pendingNestedMemoryTriggers);
214
+ this.pendingNestedMemoryTriggers.clear();
215
+ const memories = [];
216
+ for (const filePath of filePaths) {
217
+ memories.push(...(await findNestedMemoryFiles(filePath, this.workdir, this.loadedNestedMemoryPaths)));
218
+ }
219
+ return memories;
220
+ }
166
221
  /**
167
222
  * Check if a file has been read (for read-before-edit enforcement).
168
223
  * Uses recentFileReads Map populated from Read tool execution.
@@ -174,9 +229,35 @@ export class MessageManager {
174
229
  return (this.recentFileReads.has(normalizedPath) ||
175
230
  this.recentFileReads.has(filePath));
176
231
  }
177
- setMessages(messages) {
232
+ setMessages(messages, opts) {
178
233
  const oldLength = this.messages.length;
179
234
  this.messages = [...messages];
235
+ // The UI display stream follows the context by default. Compaction /
236
+ // session restore / rewind pass keepDisplay and manage displayMessages
237
+ // explicitly (the display stream must keep the full pre-compaction
238
+ // history while the context folds at the last compact boundary).
239
+ if (!opts?.keepDisplay) {
240
+ if (messages.length === 0) {
241
+ // Clear path — nothing to merge.
242
+ this.displayMessages = [...messages];
243
+ }
244
+ else {
245
+ // Routine changes (new messages, tool block updates) still touch the
246
+ // display stream, but after compaction the display holds the full
247
+ // pre-compaction history while the context is folded. Merge by id so
248
+ // new messages append to the display stream and updated messages
249
+ // replace their display copy without dropping that history.
250
+ const byId = new Map(messages.map((m) => [m.id, m]));
251
+ const merged = this.displayMessages.map((m) => byId.get(m.id) ?? m);
252
+ const known = new Set(this.displayMessages.map((m) => m.id));
253
+ for (const m of messages) {
254
+ if (!known.has(m.id)) {
255
+ merged.push(m);
256
+ }
257
+ }
258
+ this.displayMessages = merged;
259
+ }
260
+ }
180
261
  // Incrementally extract metadata from new messages
181
262
  const newMessages = messages.slice(oldLength);
182
263
  for (const message of newMessages) {
@@ -231,6 +312,27 @@ export class MessageManager {
231
312
  this.callbacks.onLatestTotalTokensChange?.(latestTotalTokens);
232
313
  }
233
314
  }
315
+ getCustomTitle() {
316
+ return this.customTitle;
317
+ }
318
+ setCustomTitle(customTitle) {
319
+ this.customTitle = customTitle;
320
+ }
321
+ /**
322
+ * Re-append the session's metadata entries at EOF, so they stay inside the
323
+ * tail window the session list reads. Called at the sparse points where the
324
+ * transcript is about to be read back by another process (exit, compaction,
325
+ * resume, rewind); doing it per save would cost a tail read plus a line on
326
+ * every message.
327
+ */
328
+ async reAppendSessionMetadata() {
329
+ try {
330
+ await reAppendSessionMetadataToFile(this.sessionId, this.workdir, this.customTitle, this.sessionType);
331
+ }
332
+ catch (error) {
333
+ logger?.error("Failed to re-append session metadata:", error);
334
+ }
335
+ }
234
336
  /**
235
337
  * Clear messages
236
338
  */
@@ -240,6 +342,9 @@ export class MessageManager {
240
342
  this.setSessionId(newSessionId);
241
343
  this.setlatestTotalTokens(0);
242
344
  this.savedMessageCount = 0; // Reset saved message count
345
+ // The title belongs to the session that was just abandoned: the new
346
+ // session id starts untitled until the user renames it.
347
+ this.customTitle = undefined;
243
348
  }
244
349
  /**
245
350
  * Trigger the rewind UI callback
@@ -250,13 +355,23 @@ export class MessageManager {
250
355
  // Initialize state from session data
251
356
  initializeFromSession(sessionData) {
252
357
  this.setSessionId(sessionData.id);
253
- this.setMessages([...sessionData.messages]);
358
+ // The UI displays the full transcript (pre-compaction history included);
359
+ // the API context folds at the last compact boundary so the model only
360
+ // sees messages from the latest summary forward.
361
+ this.setMessages(sliceFromLastCompact(sessionData.messages), {
362
+ keepDisplay: true,
363
+ });
364
+ this.displayMessages = [...sessionData.messages];
254
365
  this.setlatestTotalTokens(sessionData.metadata.latestTotalTokens);
366
+ // Recovered by the whole-file scan on load: from here on it is the
367
+ // in-memory fallback that survives the title entry sliding out of the
368
+ // tail window.
369
+ this.customTitle = sessionData.metadata.customTitle;
255
370
  // Rebuild loadedRuleIds from persisted meta messages (session restore)
256
371
  this.rebuildLoadedRuleIds();
257
372
  // Set saved message count to the number of loaded messages since they're already saved
258
373
  // This must be done after setSessionId which resets it to 0
259
- this.savedMessageCount = sessionData.messages.length;
374
+ this.savedMessageCount = this.messages.length;
260
375
  }
261
376
  // Encapsulated message operation functions
262
377
  addUserMessage(params) {
@@ -398,17 +513,32 @@ export class MessageManager {
398
513
  };
399
514
  // Build new message array: keep the compacted message and last messages
400
515
  const newMessages = [compactMessage, ...lastThreeMessages];
401
- // APPEND to the same session file (append-only, like Claude Code)
516
+ // APPEND only the compact block to the session file (append-only, like
517
+ // Claude Code). The preserved last API rounds were already written before
518
+ // compaction (saveSession ran at the start), so re-appending them would
519
+ // duplicate those lines in the transcript — the loader relies on each
520
+ // message id appearing exactly once (dedup-skipped, aligned with CC).
402
521
  const { appendMessages } = await import("../services/session.js");
403
- await appendMessages(this.sessionId, newMessages, this.workdir, this.sessionType);
404
- // Update in-memory state
405
- this.setMessages(newMessages);
522
+ await appendMessages(this.sessionId, [compactMessage], this.workdir, this.sessionType);
523
+ // Compaction leaves the transcript readable by the next process that
524
+ // lists sessions, so this is a flush point for the metadata entries.
525
+ await this.reAppendSessionMetadata();
526
+ // Update in-memory state: the API context folds to [compact, ...last
527
+ // rounds], while the UI display stream keeps the full history and appends
528
+ // the compact block at the end.
529
+ this.setMessages(newMessages, { keepDisplay: true });
530
+ this.displayMessages = [...this.displayMessages, compactMessage];
406
531
  this.savedMessageCount = newMessages.length;
407
532
  // Trim recent file read cache so contents compacted away are released
408
533
  this.trimRecentFileReads();
409
534
  // Clear and rebuild loaded rule IDs from remaining meta messages
410
535
  this.clearLoadedRuleIds();
411
536
  this.rebuildLoadedRuleIds();
537
+ // Nested memory has no marker to rebuild from, so the dedup set is dropped
538
+ // outright: compaction may have summarized the injected text away, and
539
+ // re-injecting it on the next read of that subtree beats never showing it
540
+ // again in this session.
541
+ this.loadedNestedMemoryPaths.clear();
412
542
  // Trigger compaction callback
413
543
  this.callbacks.onCompactBlockAdded?.(compactedContent);
414
544
  }
@@ -425,54 +555,6 @@ export class MessageManager {
425
555
  this.callbacks.onFileHistoryBlockAdded?.(snapshots);
426
556
  }
427
557
  }
428
- // Bang related message operations
429
- addBangMessage(command) {
430
- const updatedMessages = addBangMessage({
431
- messages: this.messages,
432
- command,
433
- });
434
- this.setMessages(updatedMessages);
435
- // The bang message is appended as the last message
436
- const messageId = this.messages[this.messages.length - 1]?.id ?? "";
437
- this.callbacks.onAddBangMessage?.(command, messageId);
438
- }
439
- updateBangMessage(command, output) {
440
- const updatedMessages = updateBangInMessage({
441
- messages: this.messages,
442
- command,
443
- output,
444
- });
445
- this.setMessages(updatedMessages);
446
- const messageId = this.findBangMessageId(command) ?? "";
447
- this.callbacks.onUpdateBangMessage?.(command, output, messageId);
448
- }
449
- completeBangMessage(command, exitCode, output) {
450
- const updatedMessages = completeBangInMessage({
451
- messages: this.messages,
452
- command,
453
- exitCode,
454
- output,
455
- });
456
- this.setMessages(updatedMessages);
457
- const messageId = this.findBangMessageId(command) ?? "";
458
- this.callbacks.onCompleteBangMessage?.(command, exitCode, messageId, output?.trim());
459
- }
460
- /**
461
- * Find the message ID of the most recent message containing a bang block
462
- * for the given command. Bang callbacks do not carry a block ID, so the
463
- * message is located by matching the command against bang blocks.
464
- */
465
- findBangMessageId(command) {
466
- for (let i = this.messages.length - 1; i >= 0; i--) {
467
- const message = this.messages[i];
468
- if (message.role !== "user")
469
- continue;
470
- const hasBangBlock = message.blocks?.some((block) => block.type === "bang" && block.command === command);
471
- if (hasBangBlock)
472
- return message.id;
473
- }
474
- return undefined;
475
- }
476
558
  addNotificationMessage(params) {
477
559
  const newMessages = addNotificationMessageToMessages({
478
560
  messages: this.messages,
@@ -735,12 +817,21 @@ export class MessageManager {
735
817
  if (reversionManager && messageIdsToRemove.length > 0) {
736
818
  await reversionManager.revertTo(messageIdsToRemove, messages);
737
819
  }
738
- // Rewrite file with truncated messages (full history kept on disk)
739
- await this.rewriteSessionFile(newMessages);
740
- // Fold the in-memory messages at the last compact boundary so the agent
741
- // only sees messages from the latest compact summary forward — matching
742
- // the compact and resume behaviors (which also fold memory).
743
- this.setMessages(sliceFromLastCompact(newMessages));
820
+ // Keep the first `newMessages.length` messages on disk. Note the count is
821
+ // the message index, not a file operation: the rewrite walks the file's own
822
+ // lines and carries reserved entries (metadata header, custom-title) over.
823
+ await this.rewriteSessionFile(newMessages.length);
824
+ // The rewrite drops messages but keeps the reserved entries where they sit,
825
+ // which is not enough for the title: the session list reads only the file's
826
+ // tail window, and a title written early can end up outside it (the
827
+ // rewrite keeps the *first* messages, so the survivors after it are exactly
828
+ // what pushes it away). Re-append it at EOF like the other flush points do.
829
+ await this.reAppendSessionMetadata();
830
+ // The UI keeps the full truncated thread; the agent context folds at the
831
+ // last compact boundary so the agent only sees messages from the latest
832
+ // compact summary forward — matching the compact and resume behaviors.
833
+ this.setMessages(sliceFromLastCompact(newMessages), { keepDisplay: true });
834
+ this.displayMessages = newMessages;
744
835
  // savedMessageCount tracks in-memory progress, so it must be the folded
745
836
  // length. Using the full disk count here would make saveSession's
746
837
  // slice(savedMessageCount) empty after a rewind past a compact boundary
@@ -748,18 +839,13 @@ export class MessageManager {
748
839
  this.savedMessageCount = this.messages.length;
749
840
  }
750
841
  /**
751
- * Rewrite the session file with the current messages.
842
+ * Drop everything past the first `keepMessageCount` messages from the
843
+ * session file. The rewrite is a filter over the file's own lines, so the
844
+ * metadata header, the user-set title and any other reserved entry survive.
752
845
  */
753
- async rewriteSessionFile(messages) {
846
+ async rewriteSessionFile(keepMessageCount) {
754
847
  try {
755
- const { writeFile } = await import("fs/promises");
756
- const content = messages
757
- .map((m) => {
758
- const { timestamp, ...rest } = m;
759
- return JSON.stringify({ timestamp, ...rest });
760
- })
761
- .join("\n") + (messages.length > 0 ? "\n" : "");
762
- await writeFile(this.transcriptPath, content, "utf8");
848
+ await truncateSession(this.sessionId, keepMessageCount, this.workdir, this.sessionType);
763
849
  }
764
850
  catch (error) {
765
851
  logger?.error("Failed to rewrite session file:", error);
@@ -856,6 +942,10 @@ export class MessageManager {
856
942
  this.invokedSkills.set(skillName, {
857
943
  skillName,
858
944
  timestamp: Date.now(),
945
+ // 模型当时实际看到的那份正文(已替换 ${WAVE_SKILL_DIR} 等占位符、
946
+ // 首行带 `Base directory for this skill:`)——压缩后回灌直接复用它,
947
+ // 不能重新读文件原文(spec core/message-compact 场景 4)。
948
+ content: block.result,
859
949
  });
860
950
  }
861
951
  }
@@ -866,15 +956,14 @@ export class MessageManager {
866
956
  }
867
957
  }
868
958
  /**
869
- * Get recently invoked skill names, sorted by timestamp (newest first).
959
+ * Get recently invoked skills, sorted by timestamp (newest first).
870
960
  * @param maxSkills - Maximum number of skills to return
871
- * @returns Array of skill names sorted by recency
961
+ * @returns Invoked skill records sorted by recency
872
962
  */
873
- getInvokedSkillNames(maxSkills = 10) {
874
- const sorted = Array.from(this.invokedSkills.entries())
875
- .sort(([, a], [, b]) => b.timestamp - a.timestamp)
963
+ getInvokedSkills(maxSkills = 10) {
964
+ return Array.from(this.invokedSkills.values())
965
+ .sort((a, b) => b.timestamp - a.timestamp)
876
966
  .slice(0, maxSkills);
877
- return sorted.map(([, { skillName }]) => skillName);
878
967
  }
879
968
  /**
880
969
  * Clear all invoked skills (e.g., after compaction).
@@ -8,9 +8,22 @@
8
8
  import type { PermissionDecision, ToolPermissionContext, PermissionCallback, PermissionMode } from "../types/permissions.js";
9
9
  import type { Logger } from "../types/index.js";
10
10
  import { Container } from "../utils/container.js";
11
+ /**
12
+ * Resolve an additional-directory config entry to an absolute path.
13
+ * Supports `~` / `~/` prefixes (expanded against the user home directory,
14
+ * aligned with Claude Code's expandPath) alongside absolute paths and paths
15
+ * relative to the working directory.
16
+ */
17
+ export declare function resolveAdditionalDirectory(dir: string, workdir: string | undefined): string;
11
18
  export interface PermissionManagerOptions {
12
19
  /** Configured permission mode from settings */
13
20
  configuredPermissionMode?: PermissionMode;
21
+ /**
22
+ * Session-level bypass authorization (see `captureBypassAuthorization`).
23
+ * Passed explicitly by subagents so they inherit the parent session's value
24
+ * instead of re-deriving it from their own (mid-session) mode.
25
+ */
26
+ bypassAuthorization?: boolean;
14
27
  /** Allowed rules from settings */
15
28
  allowedRules?: string[];
16
29
  /** Denied rules from settings */
@@ -33,6 +46,15 @@ export interface PermissionManagerOptions {
33
46
  export declare class PermissionManager {
34
47
  private container;
35
48
  private configuredPermissionMode?;
49
+ /**
50
+ * Session-level bypass authorization: the permission mode in effect when the
51
+ * session was created was `bypassPermissions`. Frozen at session start, so a
52
+ * later mode switch (or config reload) can never grant it. Its only effect is
53
+ * that plan mode skips permission checks instead of re-asking — mirroring
54
+ * Claude Code's `isBypassPermissionsModeAvailable`. `undefined` = not
55
+ * captured yet (subagents get an explicit value instead).
56
+ */
57
+ private bypassAuthorization?;
36
58
  private allowedRules;
37
59
  private deniedRules;
38
60
  private instanceAllowedRules;
@@ -66,6 +88,17 @@ export declare class PermissionManager {
66
88
  * Get the configured default mode
67
89
  */
68
90
  getConfiguredPermissionMode(): PermissionMode | undefined;
91
+ /**
92
+ * Freeze the session-level bypass authorization from the permission mode in
93
+ * effect when the session was created (CLI mode > configured default mode >
94
+ * `default`). No-op once captured, so switching to `bypassPermissions`
95
+ * mid-session does not grant it.
96
+ */
97
+ captureBypassAuthorization(): void;
98
+ /**
99
+ * Whether this session keeps bypass semantics while in plan mode
100
+ */
101
+ getBypassAuthorization(): boolean;
69
102
  /**
70
103
  * Get all currently allowed rules (user-defined)
71
104
  */
@@ -87,6 +120,13 @@ export declare class PermissionManager {
87
120
  * Unlike addPermissionRule, this does NOT persist to settings.local.json.
88
121
  */
89
122
  addInstanceAllowedRule(rule: string): void;
123
+ /**
124
+ * Remove an instance-level allowed rule added via addInstanceAllowedRule.
125
+ * Used when a builtin plugin that contributed a helper-script grant is
126
+ * unloaded, so the grant does not outlive the plugin.
127
+ * @returns true if the rule was present
128
+ */
129
+ removeInstanceAllowedRule(rule: string): boolean;
90
130
  /**
91
131
  * Get all additional directories
92
132
  */
@@ -136,6 +176,13 @@ export declare class PermissionManager {
136
176
  * Add a system-level additional directory that is persistent across configuration reloads
137
177
  */
138
178
  addSystemAdditionalDirectory(directory: string): void;
179
+ /**
180
+ * Remove a system-level additional directory (idempotent). Used when a
181
+ * settings.json change revokes a privilege that was granted at construction
182
+ * time — e.g. turning auto-memory off removes its directory from the safe
183
+ * zone (core/agent-config.md scenario 6).
184
+ */
185
+ removeSystemAdditionalDirectory(directory: string): void;
139
186
  /**
140
187
  * Set the current plan file path
141
188
  */
@@ -192,10 +239,32 @@ export declare class PermissionManager {
192
239
  * command substitution, process substitution, or sed -i are auto-allowed.
193
240
  */
194
241
  private isAutoAllowedPart;
242
+ /**
243
+ * Check whether a bash command part takes path arguments (cd or a
244
+ * path-taking read-only command) that escape the Safe Zone. Used to
245
+ * disqualify auto-allow via DEFAULT_ALLOWED_RULES and to hide the
246
+ * "don't ask again" persistence option for out-of-bounds access.
247
+ */
248
+ private isOutOfSafeZonePart;
195
249
  /**
196
250
  * Check if a tool call is allowed by persistent or temporary rules
197
251
  */
198
252
  private isAllowedByRule;
253
+ /**
254
+ * Classify one expanded leaf command. Every leaf of a compound command or
255
+ * command substitution is judged with the exact rules used for a plain
256
+ * command, with two structure-aware additions:
257
+ *
258
+ * - a leaf the parser flagged `unsafe` (a bare reference to a variable whose
259
+ * value is not statically known, or a command substitution inside the
260
+ * leaf's own argument words) is never auto-allowed and never matches the
261
+ * built-in default rules — only an explicit allow rule may cover it;
262
+ * - `read` (e.g. the condition of `while read …`) only reads stdin, so it
263
+ * is treated as safe when it carries no redirection of its own.
264
+ */
265
+ private isBashLeafAllowed;
266
+ /** A context whose Bash command is a single part/leaf text. */
267
+ private ruleContext;
199
268
  /**
200
269
  * Expand a bash command into individual permission rules, filtering out safe commands.
201
270
  * Used when saving permissions to the allow list.