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
package/dist/agent.d.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import { type QueuedMessage } from "./managers/messageQueue.js";
2
2
  import { SlashCommand, CustomSlashCommand, AgentOptions } from "./types/index.js";
3
- import type { Message, McpServerStatus, GatewayConfig, ModelConfig, Usage, PermissionMode, ForegroundTask, SkillMetadata } from "./types/index.js";
3
+ import type { Message, McpServerStatus, GatewayConfig, ModelConfig, Usage, PermissionMode, ForegroundTask, SkillMetadata, PluginReloadResult } from "./types/index.js";
4
+ import type { HookEvent, HookEventConfig } from "./types/hooks.js";
4
5
  import type { WorktreeSession } from "./utils/worktreeSession.js";
5
6
  export declare class Agent {
6
7
  private messageManager;
7
8
  private aiManager;
8
- private bangManager;
9
+ private bashModeManager;
9
10
  private backgroundTaskManager;
10
11
  private logger?;
11
12
  private toolManager;
@@ -42,12 +43,39 @@ export declare class Agent {
42
43
  getGatewayConfig(): GatewayConfig;
43
44
  getModelConfig(): ModelConfig;
44
45
  getMaxInputTokens(): number;
45
- getLanguage(): string | undefined;
46
+ getLanguage(): string;
47
+ /**
48
+ * Re-read the merged configuration (user/project/local settings.json + remote
49
+ * managed settings) and apply it to this session **without rebuilding it**:
50
+ * hooks / permissions / env follow immediately, the settings.json-derived
51
+ * values the running turn reads are re-pinned at the next turn start.
52
+ *
53
+ * THE save path calls this explicitly after writing the user-level
54
+ * settings.json: that file is commonly *created* by that very write, so the
55
+ * file watcher — armed at session start on a path that did not exist yet —
56
+ * must not be the only trigger (docs/specs/core/agent-config.md
57
+ * 「设置实时重载」). Idempotent with the watcher (a concurrent reload is
58
+ * de-duplicated).
59
+ */
60
+ reloadConfiguration(): Promise<void>;
61
+ /**
62
+ * Re-read every plugin from disk and swap its contribution into this running
63
+ * session in place — the `/reload-plugins` command. Commands, skills,
64
+ * agents, hooks, MCP servers and LSP servers all follow; the session itself
65
+ * (sessionId, transcript, in-flight work) is untouched, so this is not a
66
+ * rebuild. Every live session in the process must call this to see the new
67
+ * state; sessions created afterwards pick it up from disk on their own.
68
+ *
69
+ * Busts the prompt cache for the tool-schema prefix (the skill and agent
70
+ * lists are inlined) — deliberately accepted, see
71
+ * docs/specs/ecosystem/plugin.md「插件变更的就地重载」.
72
+ */
73
+ reloadPlugins(): Promise<PluginReloadResult>;
46
74
  /**
47
75
  * Set the active model for the agent session
48
76
  * @param model - The ID of the model to use
49
77
  */
50
- setModel(model: string): void;
78
+ setModel(model: string): Promise<void>;
51
79
  /**
52
80
  * Get all configured models from settings.json and defaults
53
81
  * @returns Array of model IDs
@@ -65,6 +93,10 @@ export declare class Agent {
65
93
  private constructor();
66
94
  get sessionId(): string;
67
95
  get messages(): Message[];
96
+ /** Full UI display message stream (includes pre-compaction history).
97
+ * Consumed by hosts for the full-list message view (getMessages RPC,
98
+ * CLI structural refresh); the API context stays on `messages`. */
99
+ get displayMessages(): Message[];
68
100
  get usages(): Usage[];
69
101
  get sessionFilePath(): string;
70
102
  get latestTotalTokens(): number;
@@ -87,6 +119,21 @@ export declare class Agent {
87
119
  get userMemory(): string;
88
120
  /** Get combined memory content (project + user + modular rules) */
89
121
  getCombinedMemory(): Promise<string>;
122
+ /**
123
+ * Read the user-level memory file (~/.wave/AGENTS.md). Unlike the cached
124
+ * `userMemory` getter this always reads fresh content (the settings UI edits
125
+ * the file while the agent may already hold a cache).
126
+ */
127
+ readUserMemoryContent(): Promise<string>;
128
+ /**
129
+ * Read the project-level memory file (<workdir>/AGENTS.md, CLAUDE.md
130
+ * fallback). Always reads fresh content for the settings UI.
131
+ */
132
+ readProjectMemoryContent(workdir: string): Promise<string>;
133
+ /** Persist the user-level memory file (~/.wave/AGENTS.md). */
134
+ writeUserMemoryContent(content: string): Promise<void>;
135
+ /** Persist the project-level memory file (<workdir>/AGENTS.md). */
136
+ writeProjectMemoryContent(workdir: string, content: string): Promise<void>;
90
137
  /** Get AI loading status */
91
138
  get isLoading(): boolean;
92
139
  /** Get message compaction status */
@@ -226,10 +273,37 @@ export declare class Agent {
226
273
  /**
227
274
  * Restore a session by ID, switching to the target session without destroying the Agent instance
228
275
  * @param sessionId - The ID of the session to restore
276
+ * @param options.workdir - The directory the target session lives in. Pass it
277
+ * when the target was recorded under a different project directory (e.g.
278
+ * another worktree of the same repo); the session transcript, project rules
279
+ * and memory are then re-pointed at it.
229
280
  */
230
- restoreSession(sessionId: string): Promise<void>;
281
+ restoreSession(sessionId: string, options?: {
282
+ workdir?: string;
283
+ }): Promise<void>;
284
+ /**
285
+ * Set this session's user-visible title.
286
+ *
287
+ * The title is appended to the session's own JSONL file as a `custom-title`
288
+ * entry (see `setSessionCustomTitle`), so every host that reads session files
289
+ * — this process, a later run, the IDE plugins, the desktop app — shows the
290
+ * same label. A blank title is a no-op.
291
+ */
292
+ renameSession(title: string): Promise<void>;
293
+ /** Shared wiring for the InteractionService entry points. */
294
+ private interactionContext;
295
+ /**
296
+ * Re-point every directory-derived piece of session state at `workdir`
297
+ * (used when restoring a session recorded in another directory).
298
+ *
299
+ * The memory cache needs no explicit clearing: moving to a session always
300
+ * changes the session id, and the resulting `onSessionIdChange` already
301
+ * clears it. Project rules do — they are discovered once at startup and
302
+ * resolved relative to the workdir.
303
+ */
304
+ private switchSessionWorkdir;
231
305
  abortAIMessage(): void;
232
- /** Execute bash command (bang command) */
306
+ /** Execute a bash-mode command (`!command`) */
233
307
  bang(command: string): Promise<void>;
234
308
  clearMessages(): Promise<void>;
235
309
  /**
@@ -274,28 +348,6 @@ export declare class Agent {
274
348
  * @returns Promise that resolves to the AI's answer
275
349
  */
276
350
  askBtw(question: string, abortSignal?: AbortSignal, onContent?: (content: string) => void, onReasoning?: (content: string) => void): Promise<string>;
277
- /**
278
- * Start a fork subagent in the background (the "/subtask" command path).
279
- *
280
- * The fork inherits the parent's full conversation context — same system
281
- * prompt, tools, model, and message prefix — so the prompt cache is reused,
282
- * then works independently in the background. On completion its final
283
- * response is delivered back to the main conversation as a task notification
284
- * carrying the `<result>` tag. Recursion is prevented inside the fork (the
285
- * Agent tool and Task tools are denied).
286
- *
287
- * @param prompt - The task description for the fork subagent
288
- * @param options - Background task label and optional loose turn bound
289
- * @param options.description - Display label for the background task
290
- * @param options.maxTurns - Loose turn bound (defaults to 200, aligned with
291
- * Claude Code's fork subagent)
292
- * @param abortSignal - Optional signal to abort the fork
293
- * @returns Promise that resolves to the background task ID
294
- */
295
- forkSubagent(prompt: string, options: {
296
- description: string;
297
- maxTurns?: number;
298
- }, abortSignal?: AbortSignal): Promise<string>;
299
351
  /**
300
352
  * Send a message to the AI agent with optional images
301
353
  *
@@ -351,6 +403,15 @@ export declare class Agent {
351
403
  connectMcpServer(serverName: string): Promise<boolean>;
352
404
  /** Disconnect MCP server */
353
405
  disconnectMcpServer(serverName: string): Promise<boolean>;
406
+ /**
407
+ * Remove an MCP server from a persisted config file (user ~/.wave/mcp.json
408
+ * or project <workdir>/.mcp.json), then disconnect and drop it from memory.
409
+ */
410
+ removeMcpServer(scope: "user" | "project", serverName: string): Promise<boolean>;
411
+ /** The user-level MCP config path (~/.wave/mcp.json) */
412
+ getUserMcpConfigPath(): string;
413
+ /** The project-level MCP config path (<workdir>/.mcp.json) */
414
+ getProjectMcpConfigPath(): string;
354
415
  /** Get all available slash commands */
355
416
  getSlashCommands(): SlashCommand[];
356
417
  /** Check if slash command exists */
@@ -395,6 +456,14 @@ export declare class Agent {
395
456
  * Get the current plan file path (for testing and UI)
396
457
  */
397
458
  getPlanFilePath(): string | undefined;
459
+ /**
460
+ * Resolve once the plan file path has been generated and set after entering
461
+ * plan mode. Resolves `undefined` when no generation is in flight.
462
+ * Used by the CLI /plan command and the stdio getPlanFile RPC to avoid
463
+ * querying the model before the path is known (the plan mode reminder needs
464
+ * the path to be set).
465
+ */
466
+ awaitPlanFilePath(): Promise<string | undefined>;
398
467
  /**
399
468
  * Get all currently allowed rules (user-defined and default)
400
469
  */
@@ -430,6 +499,37 @@ export declare class Agent {
430
499
  * @returns The list of skill metadata
431
500
  */
432
501
  getSkillMetadata(): SkillMetadata[];
502
+ /**
503
+ * Delete a user/personal or project skill by removing its directory.
504
+ * Builtin and plugin skills are read-only and cannot be deleted.
505
+ * @param name - The skill name as shown in skill metadata
506
+ * @returns true if the skill was deleted
507
+ */
508
+ deleteSkill(name: string): Promise<boolean>;
509
+ /**
510
+ * Delete a user-level or project-level subagent by removing its markdown
511
+ * file. Builtin and plugin agents are read-only and cannot be deleted.
512
+ * @param name - The subagent name as shown in configurations
513
+ * @returns true if the subagent was deleted
514
+ */
515
+ deleteSubagent(name: string): Promise<boolean>;
516
+ /**
517
+ * Get hook configurations at a specific scope (user or project settings.json;
518
+ * plugin hooks come from the hook manager's plugin-registered set).
519
+ */
520
+ getHooksByScope(scope: "user" | "project" | "plugin"): Promise<Partial<Record<HookEvent, HookEventConfig[]>>>;
521
+ /**
522
+ * Absolute path of the settings.json holding hooks at `scope` — user
523
+ * `~/.wave/settings.json` or project `<workdir>/.wave/settings.json`. Hosts
524
+ * pass it to the GUI so editors / file panels can open the file (they cannot
525
+ * expand `~`).
526
+ */
527
+ getHookConfigPath(scope: "user" | "project"): string;
528
+ /**
529
+ * Delete a hook at a specific scope, identified by hookName of the form
530
+ * `Event:Matcher`.
531
+ */
532
+ deleteHook(scope: "user" | "project", hookName: string): Promise<void>;
433
533
  /**
434
534
  * Get currently active subagent instances (status active/initializing).
435
535
  * @returns The list of active subagent instances
package/dist/agent.js CHANGED
@@ -3,8 +3,9 @@ import { configValidator } from "./utils/configValidator.js";
3
3
  import path from "node:path";
4
4
  import { parseTaskNotificationXml } from "./utils/notificationXml.js";
5
5
  import { InitializationService } from "./services/initializationService.js";
6
- import { InteractionService } from "./services/interactionService.js";
6
+ import { InteractionService, } from "./services/interactionService.js";
7
7
  import { ConfigurationService } from "./services/configurationService.js";
8
+ import { setSessionCustomTitle } from "./services/session.js";
8
9
  import { setupAgentContainer } from "./utils/containerSetup.js";
9
10
  import { initializeTelemetry, shutdownTelemetry, } from "./telemetry/instrumentation.js";
10
11
  import { logOTelEvent } from "./telemetry/events.js";
@@ -26,12 +27,47 @@ export class Agent {
26
27
  getLanguage() {
27
28
  return this.configurationService.resolveLanguage();
28
29
  }
30
+ /**
31
+ * Re-read the merged configuration (user/project/local settings.json + remote
32
+ * managed settings) and apply it to this session **without rebuilding it**:
33
+ * hooks / permissions / env follow immediately, the settings.json-derived
34
+ * values the running turn reads are re-pinned at the next turn start.
35
+ *
36
+ * THE save path calls this explicitly after writing the user-level
37
+ * settings.json: that file is commonly *created* by that very write, so the
38
+ * file watcher — armed at session start on a path that did not exist yet —
39
+ * must not be the only trigger (docs/specs/core/agent-config.md
40
+ * 「设置实时重载」). Idempotent with the watcher (a concurrent reload is
41
+ * de-duplicated).
42
+ */
43
+ async reloadConfiguration() {
44
+ await this.liveConfigManager.reload();
45
+ }
46
+ /**
47
+ * Re-read every plugin from disk and swap its contribution into this running
48
+ * session in place — the `/reload-plugins` command. Commands, skills,
49
+ * agents, hooks, MCP servers and LSP servers all follow; the session itself
50
+ * (sessionId, transcript, in-flight work) is untouched, so this is not a
51
+ * rebuild. Every live session in the process must call this to see the new
52
+ * state; sessions created afterwards pick it up from disk on their own.
53
+ *
54
+ * Busts the prompt cache for the tool-schema prefix (the skill and agent
55
+ * lists are inlined) — deliberately accepted, see
56
+ * docs/specs/ecosystem/plugin.md「插件变更的就地重载」.
57
+ */
58
+ async reloadPlugins() {
59
+ const result = await this.pluginManager.reloadAllPlugins();
60
+ // Plugin skills arrive after SkillManager.initialize(), so their slash
61
+ // commands have to be re-derived explicitly (same as at startup).
62
+ this.slashCommandManager.registerSkillCommands(this.skillManager.getAvailableSkills());
63
+ return result;
64
+ }
29
65
  /**
30
66
  * Set the active model for the agent session
31
67
  * @param model - The ID of the model to use
32
68
  */
33
- setModel(model) {
34
- this.configurationService.setModel(model);
69
+ async setModel(model) {
70
+ await this.configurationService.setModel(model);
35
71
  this.options.callbacks?.onModelChange?.(model);
36
72
  }
37
73
  /**
@@ -51,7 +87,7 @@ export class Agent {
51
87
  * @param options - Configuration options for the Agent instance
52
88
  */
53
89
  constructor(options) {
54
- this.bangManager = null;
90
+ this.bashModeManager = null;
55
91
  this.dispatchPromise = null; // Track current dispatch for teardown
56
92
  this.isAborting = false; // Transient guard: prevents tryDispatch from firing during abortMessage (reset when the abort completes)
57
93
  this.isDestroyed = false; // Terminal guard: set in destroy(), never reset — no dispatch may ever start after destroy
@@ -115,7 +151,7 @@ export class Agent {
115
151
  this.aiManager = this.container.get("AIManager");
116
152
  this.slashCommandManager = this.container.get("SlashCommandManager");
117
153
  this.pluginManager = this.container.get("PluginManager");
118
- this.bangManager = this.container.get("BangManager");
154
+ this.bashModeManager = this.container.get("BashModeManager");
119
155
  this.cronManager = this.container.get("CronManager");
120
156
  this.messageQueue = this.container.get("MessageQueue");
121
157
  this.asyncWorkRegistry = this.container.get("AsyncWorkRegistry");
@@ -135,7 +171,7 @@ export class Agent {
135
171
  this.tryDispatch();
136
172
  };
137
173
  // Wire up bang manager callback for command running changes
138
- this.bangManager.onCommandRunningChange = (running) => {
174
+ this.bashModeManager.onCommandRunningChange = (running) => {
139
175
  this.options.callbacks?.onCommandRunningChange?.(running);
140
176
  if (!running)
141
177
  this.tryDispatch();
@@ -152,6 +188,12 @@ export class Agent {
152
188
  get messages() {
153
189
  return this.messageManager.getMessages();
154
190
  }
191
+ /** Full UI display message stream (includes pre-compaction history).
192
+ * Consumed by hosts for the full-list message view (getMessages RPC,
193
+ * CLI structural refresh); the API context stays on `messages`. */
194
+ get displayMessages() {
195
+ return this.messageManager.getDisplayMessages();
196
+ }
155
197
  get usages() {
156
198
  return this.messageManager.getUsages();
157
199
  }
@@ -196,6 +238,33 @@ export class Agent {
196
238
  async getCombinedMemory() {
197
239
  return this.messageManager.getCombinedMemory();
198
240
  }
241
+ /**
242
+ * Read the user-level memory file (~/.wave/AGENTS.md). Unlike the cached
243
+ * `userMemory` getter this always reads fresh content (the settings UI edits
244
+ * the file while the agent may already hold a cache).
245
+ */
246
+ async readUserMemoryContent() {
247
+ const memoryService = this.container.get("MemoryService");
248
+ return (await memoryService?.getUserMemoryContent()) ?? "";
249
+ }
250
+ /**
251
+ * Read the project-level memory file (<workdir>/AGENTS.md, CLAUDE.md
252
+ * fallback). Always reads fresh content for the settings UI.
253
+ */
254
+ async readProjectMemoryContent(workdir) {
255
+ const memoryService = this.container.get("MemoryService");
256
+ return (await memoryService?.readMemoryFile(workdir)) ?? "";
257
+ }
258
+ /** Persist the user-level memory file (~/.wave/AGENTS.md). */
259
+ async writeUserMemoryContent(content) {
260
+ const memoryService = this.container.get("MemoryService");
261
+ await memoryService?.writeUserMemoryContent(content);
262
+ }
263
+ /** Persist the project-level memory file (<workdir>/AGENTS.md). */
264
+ async writeProjectMemoryContent(workdir, content) {
265
+ const memoryService = this.container.get("MemoryService");
266
+ await memoryService?.writeProjectMemoryContent(workdir, content);
267
+ }
199
268
  /** Get AI loading status */
200
269
  get isLoading() {
201
270
  return this.aiManager.isLoading;
@@ -206,7 +275,7 @@ export class Agent {
206
275
  }
207
276
  /** Get bash command execution status */
208
277
  get isCommandRunning() {
209
- return this.bangManager?.isCommandRunning ?? false;
278
+ return this.bashModeManager?.isCommandRunning ?? false;
210
279
  }
211
280
  /** Get queued user-facing messages (excludes background notifications) */
212
281
  get queuedMessages() {
@@ -317,7 +386,7 @@ export class Agent {
317
386
  this.messageQueue.transitionTo("running");
318
387
  this.options.callbacks?.onQueuedMessagesChange?.(this.queuedMessages);
319
388
  if (next.type === "bang") {
320
- await this.bangManager?.executeCommand(next.content);
389
+ await this.bashModeManager?.executeCommand(next.content);
321
390
  await this.messageManager.saveSession();
322
391
  }
323
392
  else if (next.type === "notification") {
@@ -470,6 +539,11 @@ export class Agent {
470
539
  taskManager: this.taskManager,
471
540
  resolveAndValidateConfig: () => this.resolveAndValidateConfig(),
472
541
  }, options);
542
+ // Freeze the session-level bypass authorization now that settings.json (and
543
+ // thus `permissions.defaultMode`) has been loaded. Later mode switches and
544
+ // config reloads must not change it — it records what the user authorized
545
+ // when the session started.
546
+ this.permissionManager.captureBypassAuthorization();
473
547
  // Initialize OpenTelemetry (awaited to ensure ALS context is ready before
474
548
  // startInteractionSpan is called, preventing trace context fragmentation)
475
549
  const telemetryConfig = this.configurationService.resolveTelemetryConfig();
@@ -497,9 +571,33 @@ export class Agent {
497
571
  /**
498
572
  * Restore a session by ID, switching to the target session without destroying the Agent instance
499
573
  * @param sessionId - The ID of the session to restore
574
+ * @param options.workdir - The directory the target session lives in. Pass it
575
+ * when the target was recorded under a different project directory (e.g.
576
+ * another worktree of the same repo); the session transcript, project rules
577
+ * and memory are then re-pointed at it.
578
+ */
579
+ async restoreSession(sessionId, options) {
580
+ await InteractionService.restoreSession(this.interactionContext(), sessionId, options);
581
+ }
582
+ /**
583
+ * Set this session's user-visible title.
584
+ *
585
+ * The title is appended to the session's own JSONL file as a `custom-title`
586
+ * entry (see `setSessionCustomTitle`), so every host that reads session files
587
+ * — this process, a later run, the IDE plugins, the desktop app — shows the
588
+ * same label. A blank title is a no-op.
500
589
  */
501
- async restoreSession(sessionId) {
502
- await InteractionService.restoreSession({
590
+ async renameSession(title) {
591
+ const applied = await setSessionCustomTitle(this.sessionId, this.workdir, title);
592
+ // Keep the in-memory value in step: it is what gets written back at the
593
+ // next flush point if the entry has since slid out of the tail window.
594
+ if (applied) {
595
+ this.messageManager.setCustomTitle(applied);
596
+ }
597
+ }
598
+ /** Shared wiring for the InteractionService entry points. */
599
+ interactionContext() {
600
+ return {
503
601
  messageManager: this.messageManager,
504
602
  slashCommandManager: this.slashCommandManager,
505
603
  hookManager: this.hookManager,
@@ -511,12 +609,27 @@ export class Agent {
511
609
  taskManager: this.taskManager,
512
610
  options: this.options,
513
611
  abortMessage: () => this.abortMessage(),
514
- }, sessionId);
612
+ switchWorkdir: (workdir) => this.switchSessionWorkdir(workdir),
613
+ };
614
+ }
615
+ /**
616
+ * Re-point every directory-derived piece of session state at `workdir`
617
+ * (used when restoring a session recorded in another directory).
618
+ *
619
+ * The memory cache needs no explicit clearing: moving to a session always
620
+ * changes the session id, and the resulting `onSessionIdChange` already
621
+ * clears it. Project rules do — they are discovered once at startup and
622
+ * resolved relative to the workdir.
623
+ */
624
+ async switchSessionWorkdir(workdir) {
625
+ this.setWorkdir(workdir);
626
+ this.messageManager.setWorkdir(workdir);
627
+ await this.memoryRuleManager.setWorkdir(workdir);
515
628
  }
516
629
  abortAIMessage() {
517
630
  this.aiManager.abortAIMessage();
518
631
  }
519
- /** Execute bash command (bang command) */
632
+ /** Execute a bash-mode command (`!command`) */
520
633
  async bang(command) {
521
634
  this.assertNotDestroyed();
522
635
  // If the agent is busy, enqueue the bang command
@@ -525,7 +638,7 @@ export class Agent {
525
638
  this.options.callbacks?.onQueuedMessagesChange?.(this.queuedMessages);
526
639
  return;
527
640
  }
528
- await this.bangManager?.executeCommand(command);
641
+ await this.bashModeManager?.executeCommand(command);
529
642
  await this.messageManager.saveSession();
530
643
  }
531
644
  async clearMessages() {
@@ -620,7 +733,7 @@ export class Agent {
620
733
  }
621
734
  /** Interrupt bash command execution */
622
735
  abortBashCommand() {
623
- this.bangManager?.abortCommand();
736
+ this.bashModeManager?.abortCommand();
624
737
  }
625
738
  /** Interrupt slash command execution */
626
739
  abortSlashCommand() {
@@ -678,6 +791,10 @@ export class Agent {
678
791
  this.logger?.warn(`SessionEnd hooks failed: ${error.message}`);
679
792
  }
680
793
  await this.messageManager.saveSession();
794
+ // Flush point: the transcript is about to be left behind for other
795
+ // processes to list, so re-append the metadata entries at EOF. Must come
796
+ // after saveSession (which returns early when nothing is unsaved).
797
+ await this.messageManager.reAppendSessionMetadata();
681
798
  this.abortAIMessage(); // This will abort tools including Agent tool (subagents)
682
799
  this.abortBashCommand();
683
800
  this.abortSlashCommand();
@@ -753,28 +870,6 @@ export class Agent {
753
870
  const result = await this.aiManager.runBtwFork(question, abortSignal, onContent, onReasoning);
754
871
  return result.content ?? result.error ?? "No response received";
755
872
  }
756
- /**
757
- * Start a fork subagent in the background (the "/subtask" command path).
758
- *
759
- * The fork inherits the parent's full conversation context — same system
760
- * prompt, tools, model, and message prefix — so the prompt cache is reused,
761
- * then works independently in the background. On completion its final
762
- * response is delivered back to the main conversation as a task notification
763
- * carrying the `<result>` tag. Recursion is prevented inside the fork (the
764
- * Agent tool and Task tools are denied).
765
- *
766
- * @param prompt - The task description for the fork subagent
767
- * @param options - Background task label and optional loose turn bound
768
- * @param options.description - Display label for the background task
769
- * @param options.maxTurns - Loose turn bound (defaults to 200, aligned with
770
- * Claude Code's fork subagent)
771
- * @param abortSignal - Optional signal to abort the fork
772
- * @returns Promise that resolves to the background task ID
773
- */
774
- async forkSubagent(prompt, options, abortSignal) {
775
- this.assertNotDestroyed();
776
- return this.aiManager.runForkSubagent(prompt, options, abortSignal);
777
- }
778
873
  /**
779
874
  * Send a message to the AI agent with optional images
780
875
  *
@@ -821,19 +916,7 @@ export class Agent {
821
916
  }
822
917
  // Immediate slash command: fall through to InteractionService
823
918
  }
824
- await InteractionService.sendMessage({
825
- messageManager: this.messageManager,
826
- slashCommandManager: this.slashCommandManager,
827
- hookManager: this.hookManager,
828
- workdir: this.workdir,
829
- configurationService: this.configurationService,
830
- logger: this.logger,
831
- aiManager: this.aiManager,
832
- subagentManager: this.subagentManager,
833
- taskManager: this.taskManager,
834
- options: this.options,
835
- abortMessage: () => this.abortMessage(),
836
- }, content, images);
919
+ await InteractionService.sendMessage(this.interactionContext(), content, images);
837
920
  }
838
921
  // ========== Additional Directories ==========
839
922
  /**
@@ -876,6 +959,21 @@ export class Agent {
876
959
  async disconnectMcpServer(serverName) {
877
960
  return await this.mcpManager.disconnectServer(serverName);
878
961
  }
962
+ /**
963
+ * Remove an MCP server from a persisted config file (user ~/.wave/mcp.json
964
+ * or project <workdir>/.mcp.json), then disconnect and drop it from memory.
965
+ */
966
+ async removeMcpServer(scope, serverName) {
967
+ return await this.mcpManager.removeServerFromConfig(scope, serverName);
968
+ }
969
+ /** The user-level MCP config path (~/.wave/mcp.json) */
970
+ getUserMcpConfigPath() {
971
+ return this.mcpManager.getUserConfigPath();
972
+ }
973
+ /** The project-level MCP config path (<workdir>/.mcp.json) */
974
+ getProjectMcpConfigPath() {
975
+ return this.mcpManager.getProjectConfigPath();
976
+ }
879
977
  // ========== Slash Command Management Methods ==========
880
978
  /** Get all available slash commands */
881
979
  getSlashCommands() {
@@ -951,6 +1049,16 @@ export class Agent {
951
1049
  getPlanFilePath() {
952
1050
  return this.permissionManager.getPlanFilePath();
953
1051
  }
1052
+ /**
1053
+ * Resolve once the plan file path has been generated and set after entering
1054
+ * plan mode. Resolves `undefined` when no generation is in flight.
1055
+ * Used by the CLI /plan command and the stdio getPlanFile RPC to avoid
1056
+ * querying the model before the path is known (the plan mode reminder needs
1057
+ * the path to be set).
1058
+ */
1059
+ awaitPlanFilePath() {
1060
+ return this.planManager.awaitPlanFilePath();
1061
+ }
954
1062
  /**
955
1063
  * Get all currently allowed rules (user-defined and default)
956
1064
  */
@@ -1003,6 +1111,50 @@ export class Agent {
1003
1111
  getSkillMetadata() {
1004
1112
  return this.skillManager.getAvailableSkills();
1005
1113
  }
1114
+ /**
1115
+ * Delete a user/personal or project skill by removing its directory.
1116
+ * Builtin and plugin skills are read-only and cannot be deleted.
1117
+ * @param name - The skill name as shown in skill metadata
1118
+ * @returns true if the skill was deleted
1119
+ */
1120
+ async deleteSkill(name) {
1121
+ return await this.skillManager.deleteSkill(name);
1122
+ }
1123
+ /**
1124
+ * Delete a user-level or project-level subagent by removing its markdown
1125
+ * file. Builtin and plugin agents are read-only and cannot be deleted.
1126
+ * @param name - The subagent name as shown in configurations
1127
+ * @returns true if the subagent was deleted
1128
+ */
1129
+ async deleteSubagent(name) {
1130
+ return await this.subagentManager.deleteSubagent(name);
1131
+ }
1132
+ /**
1133
+ * Get hook configurations at a specific scope (user or project settings.json;
1134
+ * plugin hooks come from the hook manager's plugin-registered set).
1135
+ */
1136
+ async getHooksByScope(scope) {
1137
+ if (scope === "plugin") {
1138
+ return this.hookManager.getPluginHooks();
1139
+ }
1140
+ return await this.configurationService.getHooksByScope(this.workdir, scope);
1141
+ }
1142
+ /**
1143
+ * Absolute path of the settings.json holding hooks at `scope` — user
1144
+ * `~/.wave/settings.json` or project `<workdir>/.wave/settings.json`. Hosts
1145
+ * pass it to the GUI so editors / file panels can open the file (they cannot
1146
+ * expand `~`).
1147
+ */
1148
+ getHookConfigPath(scope) {
1149
+ return this.configurationService.getHookConfigPath(this.workdir, scope);
1150
+ }
1151
+ /**
1152
+ * Delete a hook at a specific scope, identified by hookName of the form
1153
+ * `Event:Matcher`.
1154
+ */
1155
+ async deleteHook(scope, hookName) {
1156
+ await this.configurationService.deleteHook(this.workdir, scope, hookName);
1157
+ }
1006
1158
  /**
1007
1159
  * Get currently active subagent instances (status active/initializing).
1008
1160
  * @returns The list of active subagent instances
@@ -5,6 +5,7 @@ import { initSkill } from "./skills/init.js";
5
5
  import { loopSkill } from "./skills/loop.js";
6
6
  import { simplifySkill } from "./skills/simplify.js";
7
7
  import { settingsSkills } from "./skills/settings.js";
8
+ import { waveDaemonSkill } from "./skills/wave-daemon.js";
8
9
  import { subagents } from "./subagents.js";
9
10
  import { sddPlugin } from "./plugins.js";
10
11
  export const BUILTIN_CONTENT = {
@@ -15,6 +16,7 @@ export const BUILTIN_CONTENT = {
15
16
  ...loopSkill,
16
17
  ...simplifySkill,
17
18
  ...settingsSkills,
19
+ ...waveDaemonSkill,
18
20
  ...subagents,
19
21
  ...sddPlugin,
20
22
  };