wave-agent-sdk 1.1.4 → 1.2.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 (89) hide show
  1. package/dist/agent.d.ts +72 -24
  2. package/dist/agent.js +123 -31
  3. package/dist/builtin/plugins.js +17 -2
  4. package/dist/builtin/skills/settings.js +6 -8
  5. package/dist/core/plugin.d.ts +4 -0
  6. package/dist/core/plugin.js +7 -1
  7. package/dist/index.d.ts +2 -1
  8. package/dist/index.js +2 -1
  9. package/dist/managers/aiManager.d.ts +0 -24
  10. package/dist/managers/aiManager.js +23 -181
  11. package/dist/managers/bashModeManager.d.ts +33 -0
  12. package/dist/managers/bashModeManager.js +110 -0
  13. package/dist/managers/hookManager.d.ts +5 -0
  14. package/dist/managers/hookManager.js +7 -0
  15. package/dist/managers/liveConfigManager.js +3 -3
  16. package/dist/managers/mcpManager.d.ts +23 -0
  17. package/dist/managers/mcpManager.js +162 -14
  18. package/dist/managers/messageManager.d.ts +12 -13
  19. package/dist/managers/messageManager.js +63 -60
  20. package/dist/managers/permissionManager.d.ts +29 -0
  21. package/dist/managers/permissionManager.js +158 -70
  22. package/dist/managers/planManager.d.ts +9 -0
  23. package/dist/managers/planManager.js +19 -1
  24. package/dist/managers/skillManager.d.ts +31 -0
  25. package/dist/managers/skillManager.js +122 -12
  26. package/dist/managers/slashCommandManager.js +9 -28
  27. package/dist/managers/subagentManager.d.ts +7 -0
  28. package/dist/managers/subagentManager.js +61 -9
  29. package/dist/managers/workflowManager.js +6 -0
  30. package/dist/prompts/index.d.ts +0 -1
  31. package/dist/prompts/index.js +0 -4
  32. package/dist/services/MarketplaceService.js +36 -14
  33. package/dist/services/configurationService.d.ts +34 -2
  34. package/dist/services/configurationService.js +123 -16
  35. package/dist/services/initializationService.js +2 -2
  36. package/dist/services/jsonlHandler.d.ts +14 -0
  37. package/dist/services/jsonlHandler.js +44 -1
  38. package/dist/services/memory.d.ts +14 -0
  39. package/dist/services/memory.js +33 -0
  40. package/dist/services/officialMarketplaceMirror.d.ts +85 -0
  41. package/dist/services/officialMarketplaceMirror.js +289 -0
  42. package/dist/services/remoteSettingsService.js +4 -4
  43. package/dist/services/session.js +30 -13
  44. package/dist/services/worktreeHooks.js +6 -1
  45. package/dist/stdio/index.d.ts +10 -0
  46. package/dist/stdio/index.js +10 -0
  47. package/dist/stdio/notificationRouter.d.ts +38 -0
  48. package/dist/stdio/notificationRouter.js +96 -0
  49. package/dist/stdio/rpcClient.d.ts +18 -0
  50. package/dist/stdio/rpcClient.js +10 -0
  51. package/dist/stdio/stdioAgent.d.ts +222 -0
  52. package/dist/stdio/stdioAgent.js +341 -0
  53. package/dist/tools/bashTool.js +2 -0
  54. package/dist/tools/exitPlanMode.js +10 -2
  55. package/dist/types/agent.d.ts +8 -0
  56. package/dist/types/commands.d.ts +7 -0
  57. package/dist/types/configuration.d.ts +6 -1
  58. package/dist/types/hooks.d.ts +1 -0
  59. package/dist/types/hooks.js +19 -0
  60. package/dist/types/mcp.d.ts +3 -0
  61. package/dist/types/messaging.d.ts +1 -8
  62. package/dist/types/skills.d.ts +11 -0
  63. package/dist/utils/bashParser.d.ts +17 -0
  64. package/dist/utils/bashParser.js +72 -0
  65. package/dist/utils/bashStructure/bashLexer.d.ts +96 -0
  66. package/dist/utils/bashStructure/bashLexer.js +676 -0
  67. package/dist/utils/bashStructure/bashParser.d.ts +144 -0
  68. package/dist/utils/bashStructure/bashParser.js +606 -0
  69. package/dist/utils/bashStructure/bashSemantics.d.ts +70 -0
  70. package/dist/utils/bashStructure/bashSemantics.js +477 -0
  71. package/dist/utils/bashStructure/index.d.ts +26 -0
  72. package/dist/utils/bashStructure/index.js +27 -0
  73. package/dist/utils/bashStructure/types.d.ts +62 -0
  74. package/dist/utils/bashStructure/types.js +47 -0
  75. package/dist/utils/container.d.ts +6 -0
  76. package/dist/utils/container.js +9 -0
  77. package/dist/utils/containerSetup.d.ts +11 -1
  78. package/dist/utils/containerSetup.js +26 -11
  79. package/dist/utils/fileUtils.d.ts +11 -0
  80. package/dist/utils/fileUtils.js +37 -0
  81. package/dist/utils/messageOperations.d.ts +0 -18
  82. package/dist/utils/messageOperations.js +0 -62
  83. package/dist/utils/subagentParser.js +9 -2
  84. package/dist/utils/tokenCalculation.js +0 -8
  85. package/dist/utils/worktreeUtils.d.ts +2 -1
  86. package/dist/utils/worktreeUtils.js +64 -34
  87. package/package.json +6 -1
  88. package/dist/managers/bangManager.d.ts +0 -26
  89. 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
3
  import type { Message, McpServerStatus, GatewayConfig, ModelConfig, Usage, PermissionMode, ForegroundTask, SkillMetadata } 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;
@@ -31,6 +32,8 @@ export declare class Agent {
31
32
  private taskManager;
32
33
  private foregroundTaskManager;
33
34
  private container;
35
+ /** Unregister module-level listeners registered during container setup; invoked at the end of destroy(). */
36
+ private teardown;
34
37
  private configurationService;
35
38
  private workdir;
36
39
  private systemPrompt?;
@@ -63,6 +66,10 @@ export declare class Agent {
63
66
  private constructor();
64
67
  get sessionId(): string;
65
68
  get messages(): Message[];
69
+ /** Full UI display message stream (includes pre-compaction history).
70
+ * Consumed by hosts for the full-list message view (getMessages RPC,
71
+ * CLI structural refresh); the API context stays on `messages`. */
72
+ get displayMessages(): Message[];
66
73
  get usages(): Usage[];
67
74
  get sessionFilePath(): string;
68
75
  get latestTotalTokens(): number;
@@ -85,6 +92,21 @@ export declare class Agent {
85
92
  get userMemory(): string;
86
93
  /** Get combined memory content (project + user + modular rules) */
87
94
  getCombinedMemory(): Promise<string>;
95
+ /**
96
+ * Read the user-level memory file (~/.wave/AGENTS.md). Unlike the cached
97
+ * `userMemory` getter this always reads fresh content (the settings UI edits
98
+ * the file while the agent may already hold a cache).
99
+ */
100
+ readUserMemoryContent(): Promise<string>;
101
+ /**
102
+ * Read the project-level memory file (<workdir>/AGENTS.md, CLAUDE.md
103
+ * fallback). Always reads fresh content for the settings UI.
104
+ */
105
+ readProjectMemoryContent(workdir: string): Promise<string>;
106
+ /** Persist the user-level memory file (~/.wave/AGENTS.md). */
107
+ writeUserMemoryContent(content: string): Promise<void>;
108
+ /** Persist the project-level memory file (<workdir>/AGENTS.md). */
109
+ writeProjectMemoryContent(workdir: string, content: string): Promise<void>;
88
110
  /** Get AI loading status */
89
111
  get isLoading(): boolean;
90
112
  /** Get message compaction status */
@@ -227,7 +249,7 @@ export declare class Agent {
227
249
  */
228
250
  restoreSession(sessionId: string): Promise<void>;
229
251
  abortAIMessage(): void;
230
- /** Execute bash command (bang command) */
252
+ /** Execute a bash-mode command (`!command`) */
231
253
  bang(command: string): Promise<void>;
232
254
  clearMessages(): Promise<void>;
233
255
  /**
@@ -272,28 +294,6 @@ export declare class Agent {
272
294
  * @returns Promise that resolves to the AI's answer
273
295
  */
274
296
  askBtw(question: string, abortSignal?: AbortSignal, onContent?: (content: string) => void, onReasoning?: (content: string) => void): Promise<string>;
275
- /**
276
- * Start a fork subagent in the background (the "/subtask" command path).
277
- *
278
- * The fork inherits the parent's full conversation context — same system
279
- * prompt, tools, model, and message prefix — so the prompt cache is reused,
280
- * then works independently in the background. On completion its final
281
- * response is delivered back to the main conversation as a task notification
282
- * carrying the `<result>` tag. Recursion is prevented inside the fork (the
283
- * Agent tool and Task tools are denied).
284
- *
285
- * @param prompt - The task description for the fork subagent
286
- * @param options - Background task label and optional loose turn bound
287
- * @param options.description - Display label for the background task
288
- * @param options.maxTurns - Loose turn bound (defaults to 200, aligned with
289
- * Claude Code's fork subagent)
290
- * @param abortSignal - Optional signal to abort the fork
291
- * @returns Promise that resolves to the background task ID
292
- */
293
- forkSubagent(prompt: string, options: {
294
- description: string;
295
- maxTurns?: number;
296
- }, abortSignal?: AbortSignal): Promise<string>;
297
297
  /**
298
298
  * Send a message to the AI agent with optional images
299
299
  *
@@ -349,6 +349,15 @@ export declare class Agent {
349
349
  connectMcpServer(serverName: string): Promise<boolean>;
350
350
  /** Disconnect MCP server */
351
351
  disconnectMcpServer(serverName: string): Promise<boolean>;
352
+ /**
353
+ * Remove an MCP server from a persisted config file (user ~/.wave/mcp.json
354
+ * or project <workdir>/.mcp.json), then disconnect and drop it from memory.
355
+ */
356
+ removeMcpServer(scope: "user" | "project", serverName: string): Promise<boolean>;
357
+ /** The user-level MCP config path (~/.wave/mcp.json) */
358
+ getUserMcpConfigPath(): string;
359
+ /** The project-level MCP config path (<workdir>/.mcp.json) */
360
+ getProjectMcpConfigPath(): string;
352
361
  /** Get all available slash commands */
353
362
  getSlashCommands(): SlashCommand[];
354
363
  /** Check if slash command exists */
@@ -393,6 +402,14 @@ export declare class Agent {
393
402
  * Get the current plan file path (for testing and UI)
394
403
  */
395
404
  getPlanFilePath(): string | undefined;
405
+ /**
406
+ * Resolve once the plan file path has been generated and set after entering
407
+ * plan mode. Resolves `undefined` when no generation is in flight.
408
+ * Used by the CLI /plan command and the stdio getPlanFile RPC to avoid
409
+ * querying the model before the path is known (the plan mode reminder needs
410
+ * the path to be set).
411
+ */
412
+ awaitPlanFilePath(): Promise<string | undefined>;
396
413
  /**
397
414
  * Get all currently allowed rules (user-defined and default)
398
415
  */
@@ -428,6 +445,37 @@ export declare class Agent {
428
445
  * @returns The list of skill metadata
429
446
  */
430
447
  getSkillMetadata(): SkillMetadata[];
448
+ /**
449
+ * Delete a user/personal or project skill by removing its directory.
450
+ * Builtin and plugin skills are read-only and cannot be deleted.
451
+ * @param name - The skill name as shown in skill metadata
452
+ * @returns true if the skill was deleted
453
+ */
454
+ deleteSkill(name: string): Promise<boolean>;
455
+ /**
456
+ * Delete a user-level or project-level subagent by removing its markdown
457
+ * file. Builtin and plugin agents are read-only and cannot be deleted.
458
+ * @param name - The subagent name as shown in configurations
459
+ * @returns true if the subagent was deleted
460
+ */
461
+ deleteSubagent(name: string): Promise<boolean>;
462
+ /**
463
+ * Get hook configurations at a specific scope (user or project settings.json;
464
+ * plugin hooks come from the hook manager's plugin-registered set).
465
+ */
466
+ getHooksByScope(scope: "user" | "project" | "plugin"): Promise<Partial<Record<HookEvent, HookEventConfig[]>>>;
467
+ /**
468
+ * Absolute path of the settings.json holding hooks at `scope` — user
469
+ * `~/.wave/settings.json` or project `<workdir>/.wave/settings.json`. Hosts
470
+ * pass it to the GUI so editors / file panels can open the file (they cannot
471
+ * expand `~`).
472
+ */
473
+ getHookConfigPath(scope: "user" | "project"): string;
474
+ /**
475
+ * Delete a hook at a specific scope, identified by hookName of the form
476
+ * `Event:Matcher`.
477
+ */
478
+ deleteHook(scope: "user" | "project", hookName: string): Promise<void>;
431
479
  /**
432
480
  * Get currently active subagent instances (status active/initializing).
433
481
  * @returns The list of active subagent instances
package/dist/agent.js CHANGED
@@ -51,11 +51,13 @@ export class Agent {
51
51
  * @param options - Configuration options for the Agent instance
52
52
  */
53
53
  constructor(options) {
54
- this.bangManager = null;
54
+ this.bashModeManager = null;
55
55
  this.dispatchPromise = null; // Track current dispatch for teardown
56
56
  this.isAborting = false; // Transient guard: prevents tryDispatch from firing during abortMessage (reset when the abort completes)
57
57
  this.isDestroyed = false; // Terminal guard: set in destroy(), never reset — no dispatch may ever start after destroy
58
58
  this.dispatchAborted = false; // Set on abort while a dispatch is running: suppress the .finally re-check so preserved notifications don't get dispatched after abort
59
+ /** Unregister module-level listeners registered during container setup; invoked at the end of destroy(). */
60
+ this.teardown = () => { };
59
61
  this.sessionStartTime = Date.now();
60
62
  const { logger, workdir, systemPrompt, stream = true } = options;
61
63
  // Set working directory early as we need it for loading configuration
@@ -68,7 +70,7 @@ export class Agent {
68
70
  this.stream = stream; // Save streaming mode flag
69
71
  // Store options for dynamic configuration resolution
70
72
  this.options = options;
71
- this.container = setupAgentContainer({
73
+ const { container, teardown } = setupAgentContainer({
72
74
  options,
73
75
  workdir: this.workdir,
74
76
  configurationService: this.configurationService,
@@ -92,6 +94,8 @@ export class Agent {
92
94
  addPermissionRule: (rule) => this.addPermissionRule(rule),
93
95
  addUsage: (usage) => this.messageManager.addUsage(usage),
94
96
  });
97
+ this.container = container;
98
+ this.teardown = teardown;
95
99
  // Retrieve managers from container
96
100
  this.foregroundTaskManager = this.container.get("ForegroundTaskManager");
97
101
  this.memoryRuleManager = this.container.get("MemoryRuleManager");
@@ -111,7 +115,7 @@ export class Agent {
111
115
  this.aiManager = this.container.get("AIManager");
112
116
  this.slashCommandManager = this.container.get("SlashCommandManager");
113
117
  this.pluginManager = this.container.get("PluginManager");
114
- this.bangManager = this.container.get("BangManager");
118
+ this.bashModeManager = this.container.get("BashModeManager");
115
119
  this.cronManager = this.container.get("CronManager");
116
120
  this.messageQueue = this.container.get("MessageQueue");
117
121
  this.asyncWorkRegistry = this.container.get("AsyncWorkRegistry");
@@ -131,7 +135,7 @@ export class Agent {
131
135
  this.tryDispatch();
132
136
  };
133
137
  // Wire up bang manager callback for command running changes
134
- this.bangManager.onCommandRunningChange = (running) => {
138
+ this.bashModeManager.onCommandRunningChange = (running) => {
135
139
  this.options.callbacks?.onCommandRunningChange?.(running);
136
140
  if (!running)
137
141
  this.tryDispatch();
@@ -148,6 +152,12 @@ export class Agent {
148
152
  get messages() {
149
153
  return this.messageManager.getMessages();
150
154
  }
155
+ /** Full UI display message stream (includes pre-compaction history).
156
+ * Consumed by hosts for the full-list message view (getMessages RPC,
157
+ * CLI structural refresh); the API context stays on `messages`. */
158
+ get displayMessages() {
159
+ return this.messageManager.getDisplayMessages();
160
+ }
151
161
  get usages() {
152
162
  return this.messageManager.getUsages();
153
163
  }
@@ -192,6 +202,33 @@ export class Agent {
192
202
  async getCombinedMemory() {
193
203
  return this.messageManager.getCombinedMemory();
194
204
  }
205
+ /**
206
+ * Read the user-level memory file (~/.wave/AGENTS.md). Unlike the cached
207
+ * `userMemory` getter this always reads fresh content (the settings UI edits
208
+ * the file while the agent may already hold a cache).
209
+ */
210
+ async readUserMemoryContent() {
211
+ const memoryService = this.container.get("MemoryService");
212
+ return (await memoryService?.getUserMemoryContent()) ?? "";
213
+ }
214
+ /**
215
+ * Read the project-level memory file (<workdir>/AGENTS.md, CLAUDE.md
216
+ * fallback). Always reads fresh content for the settings UI.
217
+ */
218
+ async readProjectMemoryContent(workdir) {
219
+ const memoryService = this.container.get("MemoryService");
220
+ return (await memoryService?.readMemoryFile(workdir)) ?? "";
221
+ }
222
+ /** Persist the user-level memory file (~/.wave/AGENTS.md). */
223
+ async writeUserMemoryContent(content) {
224
+ const memoryService = this.container.get("MemoryService");
225
+ await memoryService?.writeUserMemoryContent(content);
226
+ }
227
+ /** Persist the project-level memory file (<workdir>/AGENTS.md). */
228
+ async writeProjectMemoryContent(workdir, content) {
229
+ const memoryService = this.container.get("MemoryService");
230
+ await memoryService?.writeProjectMemoryContent(workdir, content);
231
+ }
195
232
  /** Get AI loading status */
196
233
  get isLoading() {
197
234
  return this.aiManager.isLoading;
@@ -202,7 +239,7 @@ export class Agent {
202
239
  }
203
240
  /** Get bash command execution status */
204
241
  get isCommandRunning() {
205
- return this.bangManager?.isCommandRunning ?? false;
242
+ return this.bashModeManager?.isCommandRunning ?? false;
206
243
  }
207
244
  /** Get queued user-facing messages (excludes background notifications) */
208
245
  get queuedMessages() {
@@ -313,7 +350,7 @@ export class Agent {
313
350
  this.messageQueue.transitionTo("running");
314
351
  this.options.callbacks?.onQueuedMessagesChange?.(this.queuedMessages);
315
352
  if (next.type === "bang") {
316
- await this.bangManager?.executeCommand(next.content);
353
+ await this.bashModeManager?.executeCommand(next.content);
317
354
  await this.messageManager.saveSession();
318
355
  }
319
356
  else if (next.type === "notification") {
@@ -512,7 +549,7 @@ export class Agent {
512
549
  abortAIMessage() {
513
550
  this.aiManager.abortAIMessage();
514
551
  }
515
- /** Execute bash command (bang command) */
552
+ /** Execute a bash-mode command (`!command`) */
516
553
  async bang(command) {
517
554
  this.assertNotDestroyed();
518
555
  // If the agent is busy, enqueue the bang command
@@ -521,7 +558,7 @@ export class Agent {
521
558
  this.options.callbacks?.onQueuedMessagesChange?.(this.queuedMessages);
522
559
  return;
523
560
  }
524
- await this.bangManager?.executeCommand(command);
561
+ await this.bashModeManager?.executeCommand(command);
525
562
  await this.messageManager.saveSession();
526
563
  }
527
564
  async clearMessages() {
@@ -616,7 +653,7 @@ export class Agent {
616
653
  }
617
654
  /** Interrupt bash command execution */
618
655
  abortBashCommand() {
619
- this.bangManager?.abortCommand();
656
+ this.bashModeManager?.abortCommand();
620
657
  }
621
658
  /** Interrupt slash command execution */
622
659
  abortSlashCommand() {
@@ -717,6 +754,14 @@ export class Agent {
717
754
  if (!drained) {
718
755
  this.logger?.error(`Async work did not drain: ${this.asyncWorkRegistry.size} live work item(s) remain after destroy`);
719
756
  }
757
+ // Unregister module-level listeners (remote settings hot-update, auth
758
+ // change) so the agent's object graph becomes collectable. Without this,
759
+ // the module-level callback arrays pin every created agent via the
760
+ // per-agent closure contexts, even after the host drops its references.
761
+ this.teardown();
762
+ // Break the DI container's internal references (services/factories) so no
763
+ // per-agent manager is retained through it after destroy.
764
+ this.container.clear();
720
765
  }
721
766
  /**
722
767
  * Trigger the rewind UI callback
@@ -741,28 +786,6 @@ export class Agent {
741
786
  const result = await this.aiManager.runBtwFork(question, abortSignal, onContent, onReasoning);
742
787
  return result.content ?? result.error ?? "No response received";
743
788
  }
744
- /**
745
- * Start a fork subagent in the background (the "/subtask" command path).
746
- *
747
- * The fork inherits the parent's full conversation context — same system
748
- * prompt, tools, model, and message prefix — so the prompt cache is reused,
749
- * then works independently in the background. On completion its final
750
- * response is delivered back to the main conversation as a task notification
751
- * carrying the `<result>` tag. Recursion is prevented inside the fork (the
752
- * Agent tool and Task tools are denied).
753
- *
754
- * @param prompt - The task description for the fork subagent
755
- * @param options - Background task label and optional loose turn bound
756
- * @param options.description - Display label for the background task
757
- * @param options.maxTurns - Loose turn bound (defaults to 200, aligned with
758
- * Claude Code's fork subagent)
759
- * @param abortSignal - Optional signal to abort the fork
760
- * @returns Promise that resolves to the background task ID
761
- */
762
- async forkSubagent(prompt, options, abortSignal) {
763
- this.assertNotDestroyed();
764
- return this.aiManager.runForkSubagent(prompt, options, abortSignal);
765
- }
766
789
  /**
767
790
  * Send a message to the AI agent with optional images
768
791
  *
@@ -864,6 +887,21 @@ export class Agent {
864
887
  async disconnectMcpServer(serverName) {
865
888
  return await this.mcpManager.disconnectServer(serverName);
866
889
  }
890
+ /**
891
+ * Remove an MCP server from a persisted config file (user ~/.wave/mcp.json
892
+ * or project <workdir>/.mcp.json), then disconnect and drop it from memory.
893
+ */
894
+ async removeMcpServer(scope, serverName) {
895
+ return await this.mcpManager.removeServerFromConfig(scope, serverName);
896
+ }
897
+ /** The user-level MCP config path (~/.wave/mcp.json) */
898
+ getUserMcpConfigPath() {
899
+ return this.mcpManager.getUserConfigPath();
900
+ }
901
+ /** The project-level MCP config path (<workdir>/.mcp.json) */
902
+ getProjectMcpConfigPath() {
903
+ return this.mcpManager.getProjectConfigPath();
904
+ }
867
905
  // ========== Slash Command Management Methods ==========
868
906
  /** Get all available slash commands */
869
907
  getSlashCommands() {
@@ -939,6 +977,16 @@ export class Agent {
939
977
  getPlanFilePath() {
940
978
  return this.permissionManager.getPlanFilePath();
941
979
  }
980
+ /**
981
+ * Resolve once the plan file path has been generated and set after entering
982
+ * plan mode. Resolves `undefined` when no generation is in flight.
983
+ * Used by the CLI /plan command and the stdio getPlanFile RPC to avoid
984
+ * querying the model before the path is known (the plan mode reminder needs
985
+ * the path to be set).
986
+ */
987
+ awaitPlanFilePath() {
988
+ return this.planManager.awaitPlanFilePath();
989
+ }
942
990
  /**
943
991
  * Get all currently allowed rules (user-defined and default)
944
992
  */
@@ -991,6 +1039,50 @@ export class Agent {
991
1039
  getSkillMetadata() {
992
1040
  return this.skillManager.getAvailableSkills();
993
1041
  }
1042
+ /**
1043
+ * Delete a user/personal or project skill by removing its directory.
1044
+ * Builtin and plugin skills are read-only and cannot be deleted.
1045
+ * @param name - The skill name as shown in skill metadata
1046
+ * @returns true if the skill was deleted
1047
+ */
1048
+ async deleteSkill(name) {
1049
+ return await this.skillManager.deleteSkill(name);
1050
+ }
1051
+ /**
1052
+ * Delete a user-level or project-level subagent by removing its markdown
1053
+ * file. Builtin and plugin agents are read-only and cannot be deleted.
1054
+ * @param name - The subagent name as shown in configurations
1055
+ * @returns true if the subagent was deleted
1056
+ */
1057
+ async deleteSubagent(name) {
1058
+ return await this.subagentManager.deleteSubagent(name);
1059
+ }
1060
+ /**
1061
+ * Get hook configurations at a specific scope (user or project settings.json;
1062
+ * plugin hooks come from the hook manager's plugin-registered set).
1063
+ */
1064
+ async getHooksByScope(scope) {
1065
+ if (scope === "plugin") {
1066
+ return this.hookManager.getPluginHooks();
1067
+ }
1068
+ return await this.configurationService.getHooksByScope(this.workdir, scope);
1069
+ }
1070
+ /**
1071
+ * Absolute path of the settings.json holding hooks at `scope` — user
1072
+ * `~/.wave/settings.json` or project `<workdir>/.wave/settings.json`. Hosts
1073
+ * pass it to the GUI so editors / file panels can open the file (they cannot
1074
+ * expand `~`).
1075
+ */
1076
+ getHookConfigPath(scope) {
1077
+ return this.configurationService.getHookConfigPath(this.workdir, scope);
1078
+ }
1079
+ /**
1080
+ * Delete a hook at a specific scope, identified by hookName of the form
1081
+ * `Event:Matcher`.
1082
+ */
1083
+ async deleteHook(scope, hookName) {
1084
+ await this.configurationService.deleteHook(this.workdir, scope, hookName);
1085
+ }
994
1086
  /**
995
1087
  * Get currently active subagent instances (status active/initializing).
996
1088
  * @returns The list of active subagent instances
@@ -41,6 +41,9 @@ const guidance = [
41
41
  "- 边界模糊时也先写 spec 草稿请用户确认,不要直接改代码。",
42
42
  "- 规格编写技能(specify)由 AI 自动触发:对话中涉及新需求或需求变更时主动创建或更新规格文件,不需要用户手动调用(不出现在斜杠命令列表中)。",
43
43
  \`- 新增或修改 spec 后运行校验:\${specCount}(自动检测 docs/specs/,否则 specs/,否则退出)。\`,
44
+ "- 规格确认与阶段衔接一次完成:通过 AskUserQuestion 单选让用户点击决策(选项只传「直接实现 / 制定技术方案」,并在问题文案中说明如需调整规格可选「其他」输入修改意见;「其他」由 AskUserQuestion 自动附加,勿手动添加;选「其他」=规格需调整,按反馈修改后再次询问)。",
45
+ "- 可选技术方案阶段:制定技术方案并批准后再编码;也可跳过——最短流程=规格+编码,最长=规格+技术方案+编码。",
46
+ "- 用 task 工具追踪进度:规格、技术方案、编码各阶段开始前用 TaskCreate 创建任务并标记进行中(TaskUpdate),完成/批准/确认后标记完成,让用户在任务列表中看到当前所处阶段。",
44
47
  ].join("\\n");
45
48
 
46
49
  // JSON form → parsed as hookSpecificOutput.additionalContext by the hook manager.
@@ -128,7 +131,7 @@ if (warnings.length) {
128
131
  `,
129
132
  "plugins/sdd/skills/specify/SKILL.md": `---
130
133
  name: specify
131
- description: 根据自然语言描述创建或更新功能规格说明,生成包含用户故事与验收场景的规格文件。
134
+ description: 根据自然语言描述创建或更新功能规格说明,并通过单选衔接可选技术方案与编码阶段。
132
135
  user-invocable: false
133
136
  ---
134
137
 
@@ -144,6 +147,8 @@ $ARGUMENTS
144
147
 
145
148
  根据对话中的功能描述,执行以下步骤:
146
149
 
150
+ 0. **创建进度任务**:用 TaskCreate 创建「编写功能规格」任务,并用 TaskUpdate 标记进行中。后续每个阶段(技术方案、编码)同样在开始前创建任务、结束后更新状态,让用户在任务列表中看到当前进度。
151
+
147
152
  1. **确定规格文件路径**:
148
153
  - **确定规格根目录**:优先复用项目中已有的规格目录——若 \`docs/specs/\` 存在则用之,否则若 \`specs/\` 存在则用之,否则默认 \`specs/\`(并在完成报告中说明所选目录,便于用户纠正)。
149
154
  - **选择分组**:若规格目录下已有分组子目录,沿用其既有分组约定;否则默认扁平结构(直接放在规格根目录下)。
@@ -165,7 +170,17 @@ $ARGUMENTS
165
170
  - 将每个标记作为问题展示,附带建议答案
166
171
  - 等待用户回复后更新规格文件
167
172
 
168
- 5. 报告完成,输出规格文件路径。
173
+ 5. **校验并确认规格**:
174
+ - 运行会话引导中给出的 spec-count 校验命令(自动检测 docs/specs/,否则 specs/,否则跳过)
175
+ - 输出规格文件路径,并通过 AskUserQuestion 单选请求决策(选项只传:直接实现 / 制定技术方案;AskUserQuestion 会自动附加「其他」选项,勿手动添加「其他」;在问题文案中说明如需调整规格可选「其他」并输入修改意见)
176
+ - 用户选「其他」并输入意见 → 视为规格需要调整:按用户反馈更新规格后重新校验,并再次单选
177
+ - 选「直接实现」→ 将「编写功能规格」任务标记完成,进入编码阶段
178
+ - 选「制定技术方案」→ 将「编写功能规格」任务标记完成;用 TaskCreate 创建「制定技术方案」任务并标记进行中;调用 EnterPlanMode 进入技术方案模式,制定技术方案(技术选型、架构设计、实现步骤)并写入计划文件;用 ExitPlanMode 请求批准——被拒绝则按反馈更新方案后重新请求,批准后标记任务完成
179
+
180
+ 6. **编码阶段**:
181
+ - 用 TaskCreate 创建「实现功能」任务并标记进行中
182
+ - 按已确认的规格实现;若批准了技术方案则遵循其架构
183
+ - 实现完成后将任务标记完成
169
184
 
170
185
  ## 指南
171
186
 
@@ -702,7 +702,7 @@ You can extend the Safe Zone by adding \`additionalDirectories\` to your \`permi
702
702
 
703
703
  ## Permission Modes
704
704
 
705
- The \`permissionMode\` setting determines how Wave handles requests to use restricted tools (e.g., \`Bash\`, \`Edit\`, \`Write\`, \`AskUserQuestion\`).
705
+ The \`defaultMode\` setting (under \`permissions\`, aligning with Claude Code's settings key) determines how Wave handles requests to use restricted tools (e.g., \`Bash\`, \`Edit\`, \`Write\`, \`AskUserQuestion\`).
706
706
 
707
707
  | Mode | Description |
708
708
  | :--- | :--- |
@@ -717,7 +717,7 @@ The \`permissionMode\` setting determines how Wave handles requests to use restr
717
717
  \`\`\`json
718
718
  {
719
719
  "permissions": {
720
- "permissionMode": "default",
720
+ "defaultMode": "default",
721
721
  "additionalDirectories": ["/home/user/my-exports"],
722
722
  "allow": ["ls -R", "git status"],
723
723
  "deny": ["rm -rf"]
@@ -735,7 +735,7 @@ You can pre-approve or explicitly forbid specific operations using \`allow\` and
735
735
  When a tool is called, Wave checks:
736
736
  1. If the operation matches a \`deny\` rule, it is rejected.
737
737
  2. If the operation matches an \`allow\` rule, it is permitted.
738
- 3. If no rules match, the behavior depends on the \`permissionMode\`.
738
+ 3. If no rules match, the behavior depends on the \`defaultMode\`.
739
739
 
740
740
  ### Rule Syntax
741
741
 
@@ -998,7 +998,7 @@ For detailed permission configuration and available permission modes, see [PERMI
998
998
  "permissions": {
999
999
  "allow": ["Bash", "Read"],
1000
1000
  "deny": ["Write"],
1001
- "permissionMode": "default",
1001
+ "defaultMode": "default",
1002
1002
  "additionalDirectories": ["/tmp/wave-exports"]
1003
1003
  }
1004
1004
  }
@@ -1104,10 +1104,6 @@ The \`SKILL.md\` file uses YAML frontmatter for configuration and Markdown for i
1104
1104
  ---
1105
1105
  name: my-skill
1106
1106
  description: A brief description of what the skill does.
1107
- context: fork
1108
- allowed-tools:
1109
- - Bash
1110
- - Read
1111
1107
  ---
1112
1108
 
1113
1109
  # My Skill Instructions
@@ -1117,6 +1113,8 @@ When this skill is invoked, follow these steps:
1117
1113
  2. Use the \`Bash\` tool to run \`npm test\`.
1118
1114
  \`\`\`
1119
1115
 
1116
+ By default, new skills only include \`name\` and \`description\`. Only add \`context: fork\` or \`allowed-tools\` when the user explicitly asks for them.
1117
+
1120
1118
  ### YAML Frontmatter Fields
1121
1119
 
1122
1120
  - \`name\`: (Required) Unique identifier (lowercase, numbers, hyphens).
@@ -56,6 +56,10 @@ export declare class PluginCore {
56
56
  removeMarketplace(name: string, scope?: Scope): Promise<void>;
57
57
  /**
58
58
  * Updates a specific marketplace or all marketplaces
59
+ *
60
+ * Pulls the latest marketplace source and reinstalls any plugins that are
61
+ * already installed from it, so a manual "update marketplace" also brings
62
+ * installed plugins up to date (mirrors Claude Code's refresh-and-bump).
59
63
  */
60
64
  updateMarketplace(name?: string): Promise<void>;
61
65
  /**
@@ -121,9 +121,15 @@ export class PluginCore {
121
121
  }
122
122
  /**
123
123
  * Updates a specific marketplace or all marketplaces
124
+ *
125
+ * Pulls the latest marketplace source and reinstalls any plugins that are
126
+ * already installed from it, so a manual "update marketplace" also brings
127
+ * installed plugins up to date (mirrors Claude Code's refresh-and-bump).
124
128
  */
125
129
  async updateMarketplace(name) {
126
- await this.marketplaceService.updateMarketplace(name);
130
+ await this.marketplaceService.updateMarketplace(name, {
131
+ updatePlugins: true,
132
+ });
127
133
  }
128
134
  /**
129
135
  * Lists all registered marketplaces
package/dist/index.d.ts CHANGED
@@ -25,7 +25,8 @@ export * from "./utils/nameGenerator.js";
25
25
  export * from "./utils/pathEncoder.js";
26
26
  export * from "./utils/worktreeSession.js";
27
27
  export * from "./utils/worktreeUtils.js";
28
- export { loadMergedWaveConfig, loadUserConfigEnv, } from "./services/configurationService.js";
28
+ export { loadMergedWaveConfig, loadUserConfigEnv, loadWaveConfigFromFile, } from "./services/configurationService.js";
29
+ export { getUserConfigPaths, getProjectConfigPaths, } from "./utils/configPaths.js";
29
30
  export * from "./types/index.js";
30
31
  export type { SubagentConfiguration } from "./utils/subagentParser.js";
31
32
  export type { SubagentInstance } from "./managers/subagentManager.js";
package/dist/index.js CHANGED
@@ -29,7 +29,8 @@ export * from "./utils/nameGenerator.js";
29
29
  export * from "./utils/pathEncoder.js";
30
30
  export * from "./utils/worktreeSession.js";
31
31
  export * from "./utils/worktreeUtils.js";
32
- export { loadMergedWaveConfig, loadUserConfigEnv, } from "./services/configurationService.js";
32
+ export { loadMergedWaveConfig, loadUserConfigEnv, loadWaveConfigFromFile, } from "./services/configurationService.js";
33
+ export { getUserConfigPaths, getProjectConfigPaths, } from "./utils/configPaths.js";
33
34
  export * from "./types/index.js";
34
35
  // Export tool building utilities
35
36
  export * from "./tools/buildTool.js";
@@ -189,30 +189,6 @@ export declare class AIManager {
189
189
  deniedToolMessage?: string;
190
190
  maxTurns?: number;
191
191
  }, abortSignal?: AbortSignal): Promise<ForkLoopResult>;
192
- /**
193
- * Fork subagent ("/subtask"): spawn a background agent that inherits the
194
- * parent's full conversation context — the same system prompt, tools, model,
195
- * and message prefix as the main loop — so the forked request prefix matches
196
- * exactly and the prompt cache is reused. The fork runs to completion in the
197
- * background via the shared BackgroundTaskManager (visible in /tasks, log at
198
- * `os.tmpdir()/wave-subagent-<taskId>.log`); its final response is enqueued
199
- * as a task notification carrying the `<result>` tag, so the main agent
200
- * picks it up on the next turn and can act on it.
201
- *
202
- * Recursion is prevented by denying the Agent tool and the Task management
203
- * tools (the fork cannot spawn further subagents or mutate the shared task
204
- * list), and by honoring the parent's denied rules for everything else.
205
- * Gate-approved tools execute locally in the stripped fork context (no
206
- * permission prompts), mirroring the other fork paths.
207
- *
208
- * Returns the background task ID (empty string when no BackgroundTaskManager
209
- * is registered, in which case the fork still runs but without task
210
- * bookkeeping or notification).
211
- */
212
- runForkSubagent(prompt: string, options: {
213
- description: string;
214
- maxTurns?: number;
215
- }, abortSignal?: AbortSignal): Promise<string>;
216
192
  /**
217
193
  * Parse a fork tool call's JSON arguments, recovering truncated JSON the
218
194
  * same way the main loop does. Unparseable arguments fall back to `{}` and