wave-agent-sdk 0.10.4 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/builtin/skills/init/SKILL.md +26 -0
  2. package/builtin/skills/loop/SKILL.md +53 -0
  3. package/builtin/skills/settings/ENV.md +64 -0
  4. package/builtin/skills/settings/HOOKS.md +94 -0
  5. package/builtin/skills/settings/MCP.md +55 -0
  6. package/builtin/skills/settings/MEMORY_RULES.md +60 -0
  7. package/{dist/builtin-skills → builtin/skills}/settings/SKILL.md +23 -16
  8. package/builtin/skills/settings/SKILLS.md +63 -0
  9. package/builtin/skills/settings/SUBAGENTS.md +60 -0
  10. package/builtin/subagents/bash.md +18 -0
  11. package/builtin/subagents/explore.md +42 -0
  12. package/builtin/subagents/general-purpose.md +20 -0
  13. package/builtin/subagents/plan.md +55 -0
  14. package/dist/agent.d.ts +8 -6
  15. package/dist/agent.d.ts.map +1 -1
  16. package/dist/agent.js +12 -9
  17. package/dist/constants/tools.d.ts +3 -0
  18. package/dist/constants/tools.d.ts.map +1 -1
  19. package/dist/constants/tools.js +3 -0
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +1 -0
  23. package/dist/managers/aiManager.d.ts +0 -2
  24. package/dist/managers/aiManager.d.ts.map +1 -1
  25. package/dist/managers/aiManager.js +53 -14
  26. package/dist/managers/cronManager.d.ts +19 -0
  27. package/dist/managers/cronManager.d.ts.map +1 -0
  28. package/dist/managers/cronManager.js +124 -0
  29. package/dist/managers/hookManager.d.ts.map +1 -1
  30. package/dist/managers/hookManager.js +21 -13
  31. package/dist/managers/liveConfigManager.js +1 -1
  32. package/dist/managers/mcpManager.d.ts +1 -1
  33. package/dist/managers/mcpManager.d.ts.map +1 -1
  34. package/dist/managers/mcpManager.js +10 -2
  35. package/dist/managers/messageManager.d.ts +0 -1
  36. package/dist/managers/messageManager.d.ts.map +1 -1
  37. package/dist/managers/permissionManager.d.ts +27 -7
  38. package/dist/managers/permissionManager.d.ts.map +1 -1
  39. package/dist/managers/permissionManager.js +119 -14
  40. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  41. package/dist/managers/slashCommandManager.js +7 -12
  42. package/dist/managers/subagentManager.d.ts +3 -0
  43. package/dist/managers/subagentManager.d.ts.map +1 -1
  44. package/dist/managers/subagentManager.js +10 -17
  45. package/dist/managers/toolManager.d.ts +1 -1
  46. package/dist/managers/toolManager.d.ts.map +1 -1
  47. package/dist/managers/toolManager.js +28 -4
  48. package/dist/prompts/index.d.ts +0 -5
  49. package/dist/prompts/index.d.ts.map +1 -1
  50. package/dist/prompts/index.js +1 -136
  51. package/dist/services/configurationService.d.ts.map +1 -1
  52. package/dist/services/configurationService.js +8 -7
  53. package/dist/services/hook.d.ts.map +1 -1
  54. package/dist/services/hook.js +3 -10
  55. package/dist/services/initializationService.js +2 -2
  56. package/dist/services/jsonlHandler.d.ts.map +1 -1
  57. package/dist/services/jsonlHandler.js +3 -0
  58. package/dist/services/reversionService.d.ts +2 -2
  59. package/dist/services/reversionService.d.ts.map +1 -1
  60. package/dist/services/reversionService.js +3 -3
  61. package/dist/services/session.d.ts.map +1 -1
  62. package/dist/services/session.js +18 -11
  63. package/dist/tools/agentTool.js +1 -1
  64. package/dist/tools/bashTool.d.ts.map +1 -1
  65. package/dist/tools/bashTool.js +5 -5
  66. package/dist/tools/cronCreateTool.d.ts +3 -0
  67. package/dist/tools/cronCreateTool.d.ts.map +1 -0
  68. package/dist/tools/cronCreateTool.js +59 -0
  69. package/dist/tools/cronDeleteTool.d.ts +3 -0
  70. package/dist/tools/cronDeleteTool.d.ts.map +1 -0
  71. package/dist/tools/cronDeleteTool.js +38 -0
  72. package/dist/tools/cronListTool.d.ts +3 -0
  73. package/dist/tools/cronListTool.d.ts.map +1 -0
  74. package/dist/tools/cronListTool.js +30 -0
  75. package/dist/tools/skillTool.d.ts +0 -3
  76. package/dist/tools/skillTool.d.ts.map +1 -1
  77. package/dist/tools/skillTool.js +4 -3
  78. package/dist/tools/taskOutputTool.d.ts.map +1 -1
  79. package/dist/tools/taskOutputTool.js +15 -8
  80. package/dist/tools/types.d.ts +2 -0
  81. package/dist/tools/types.d.ts.map +1 -1
  82. package/dist/types/agent.d.ts +10 -0
  83. package/dist/types/agent.d.ts.map +1 -1
  84. package/dist/types/configuration.d.ts +1 -1
  85. package/dist/types/configuration.d.ts.map +1 -1
  86. package/dist/types/cron.d.ts +10 -0
  87. package/dist/types/cron.d.ts.map +1 -0
  88. package/dist/types/cron.js +1 -0
  89. package/dist/types/hooks.d.ts +1 -5
  90. package/dist/types/hooks.d.ts.map +1 -1
  91. package/dist/types/hooks.js +1 -1
  92. package/dist/types/index.d.ts +1 -0
  93. package/dist/types/index.d.ts.map +1 -1
  94. package/dist/types/index.js +1 -0
  95. package/dist/types/messaging.d.ts +1 -1
  96. package/dist/types/messaging.d.ts.map +1 -1
  97. package/dist/utils/configPaths.d.ts +4 -0
  98. package/dist/utils/configPaths.d.ts.map +1 -1
  99. package/dist/utils/configPaths.js +11 -9
  100. package/dist/utils/containerSetup.d.ts.map +1 -1
  101. package/dist/utils/containerSetup.js +40 -13
  102. package/dist/utils/fileSearch.d.ts.map +1 -1
  103. package/dist/utils/fileSearch.js +7 -1
  104. package/dist/utils/mcpUtils.d.ts +2 -2
  105. package/dist/utils/mcpUtils.d.ts.map +1 -1
  106. package/dist/utils/mcpUtils.js +1 -5
  107. package/dist/utils/subagentParser.d.ts.map +1 -1
  108. package/dist/utils/subagentParser.js +14 -4
  109. package/package.json +4 -2
  110. package/src/agent.ts +17 -12
  111. package/src/constants/tools.ts +3 -0
  112. package/src/index.ts +1 -0
  113. package/src/managers/aiManager.ts +72 -24
  114. package/src/managers/cronManager.ts +167 -0
  115. package/src/managers/hookManager.ts +27 -13
  116. package/src/managers/liveConfigManager.ts +2 -2
  117. package/src/managers/mcpManager.ts +23 -2
  118. package/src/managers/messageManager.ts +0 -6
  119. package/src/managers/permissionManager.ts +154 -18
  120. package/src/managers/slashCommandManager.ts +7 -14
  121. package/src/managers/subagentManager.ts +15 -19
  122. package/src/managers/toolManager.ts +37 -4
  123. package/src/prompts/index.ts +0 -144
  124. package/src/services/configurationService.ts +8 -7
  125. package/src/services/hook.ts +5 -11
  126. package/src/services/initializationService.ts +3 -3
  127. package/src/services/jsonlHandler.ts +4 -0
  128. package/src/services/reversionService.ts +9 -4
  129. package/src/services/session.ts +19 -12
  130. package/src/tools/agentTool.ts +1 -1
  131. package/src/tools/bashTool.ts +6 -5
  132. package/src/tools/cronCreateTool.ts +73 -0
  133. package/src/tools/cronDeleteTool.ts +47 -0
  134. package/src/tools/cronListTool.ts +38 -0
  135. package/src/tools/skillTool.ts +6 -4
  136. package/src/tools/taskOutputTool.ts +14 -8
  137. package/src/tools/types.ts +2 -0
  138. package/src/types/agent.ts +10 -0
  139. package/src/types/configuration.ts +1 -1
  140. package/src/types/cron.ts +9 -0
  141. package/src/types/hooks.ts +5 -9
  142. package/src/types/index.ts +1 -0
  143. package/src/types/messaging.ts +1 -1
  144. package/src/utils/configPaths.ts +12 -10
  145. package/src/utils/containerSetup.ts +50 -16
  146. package/src/utils/fileSearch.ts +7 -1
  147. package/src/utils/mcpUtils.ts +2 -5
  148. package/src/utils/subagentParser.ts +16 -6
  149. package/dist/builtin-skills/settings/HOOKS.md +0 -95
  150. package/dist/utils/builtinSubagents.d.ts +0 -7
  151. package/dist/utils/builtinSubagents.d.ts.map +0 -1
  152. package/dist/utils/builtinSubagents.js +0 -94
  153. package/src/builtin-skills/settings/HOOKS.md +0 -95
  154. package/src/builtin-skills/settings/SKILL.md +0 -86
  155. package/src/utils/builtinSubagents.ts +0 -122
@@ -347,6 +347,7 @@ export class McpManager {
347
347
  async executeMcpTool(
348
348
  toolName: string,
349
349
  args: Record<string, unknown>,
350
+ context?: ToolContext,
350
351
  ): Promise<{
351
352
  success: boolean;
352
353
  content: string;
@@ -360,6 +361,23 @@ export class McpManager {
360
361
  );
361
362
  }
362
363
 
364
+ // Permission check
365
+ if (context?.permissionManager) {
366
+ const permissionContext = context.permissionManager.createContext(
367
+ toolName,
368
+ context.permissionMode || "default",
369
+ context.canUseToolCallback,
370
+ args,
371
+ context.toolCallId,
372
+ );
373
+
374
+ const decision =
375
+ await context.permissionManager.checkPermission(permissionContext);
376
+ if (decision.behavior === "deny") {
377
+ throw new Error(decision.message || "Permission denied");
378
+ }
379
+ }
380
+
363
381
  const parts = toolName.split("__");
364
382
  if (parts.length < 3) {
365
383
  throw new Error(
@@ -479,8 +497,11 @@ export class McpManager {
479
497
  const plugin = createMcpToolPlugin(
480
498
  tool,
481
499
  server.name,
482
- (name: string, args: Record<string, unknown>) =>
483
- this.executeMcpTool(name, args),
500
+ (
501
+ name: string,
502
+ args: Record<string, unknown>,
503
+ context?: ToolContext,
504
+ ) => this.executeMcpTool(name, args, context),
484
505
  );
485
506
  mcpTools.set(plugin.name, plugin);
486
507
  }
@@ -45,12 +45,6 @@ export interface MessageManagerCallbacks {
45
45
  onErrorBlockAdded?: (error: string) => void;
46
46
  onCompressBlockAdded?: (content: string) => void;
47
47
  onCompressionStateChange?: (isCompressing: boolean) => void;
48
- onMemoryBlockAdded?: (
49
- content: string,
50
- success: boolean,
51
- type: "project" | "user",
52
- storagePath: string,
53
- ) => void;
54
48
  // Bang callback
55
49
  onAddBangMessage?: (command: string) => void;
56
50
  onUpdateBangMessage?: (command: string, output: string) => void;
@@ -92,12 +92,16 @@ const DEFAULT_ALLOWED_RULES = [
92
92
  import { logger } from "../utils/globalLogger.js";
93
93
 
94
94
  export interface PermissionManagerOptions {
95
- /** Configured default permission mode from settings */
96
- configuredDefaultMode?: PermissionMode;
95
+ /** Configured permission mode from settings */
96
+ configuredPermissionMode?: PermissionMode;
97
97
  /** Allowed rules from settings */
98
98
  allowedRules?: string[];
99
99
  /** Denied rules from settings */
100
100
  deniedRules?: string[];
101
+ /** Instance-specific allowed rules (from AgentOptions) */
102
+ instanceAllowedRules?: string[];
103
+ /** Instance-specific denied rules (from AgentOptions) */
104
+ instanceDeniedRules?: string[];
101
105
  /** Additional directories considered part of the Safe Zone */
102
106
  additionalDirectories?: string[];
103
107
  /** The main working directory */
@@ -109,61 +113,70 @@ export interface PermissionManagerOptions {
109
113
  }
110
114
 
111
115
  export class PermissionManager {
112
- private configuredDefaultMode?: PermissionMode;
116
+ private configuredPermissionMode?: PermissionMode;
113
117
  private allowedRules: string[] = [];
114
118
  private deniedRules: string[] = [];
119
+ private instanceAllowedRules: string[] = [];
120
+ private instanceDeniedRules: string[] = [];
115
121
  private temporaryRules: string[] = [];
116
122
  private additionalDirectories: string[] = [];
117
123
  private systemAdditionalDirectories: string[] = [];
118
124
  private workdir?: string;
119
125
  private planFilePath?: string;
120
- private onConfiguredDefaultModeChange?: (mode: PermissionMode) => void;
126
+ private worktreeName?: string;
127
+ private mainRepoRoot?: string;
128
+ private onConfiguredPermissionModeChange?: (mode: PermissionMode) => void;
121
129
  private _logger?: Logger;
122
130
 
123
131
  constructor(
124
132
  private container: Container,
125
133
  options: PermissionManagerOptions = {},
126
134
  ) {
127
- this.configuredDefaultMode = options.configuredDefaultMode;
135
+ this.configuredPermissionMode = options.configuredPermissionMode;
128
136
  this.allowedRules = options.allowedRules || [];
129
137
  this.deniedRules = options.deniedRules || [];
138
+ this.instanceAllowedRules = options.instanceAllowedRules || [];
139
+ this.instanceDeniedRules = options.instanceDeniedRules || [];
130
140
  this.workdir = options.workdir;
131
141
  this.planFilePath = options.planFilePath;
132
142
  this._logger = options.logger;
133
143
  this.updateAdditionalDirectories(options.additionalDirectories || []);
144
+
145
+ this.worktreeName = this.container.get<string>("WorktreeName");
146
+ this.mainRepoRoot = this.container.get<string>("MainRepoRoot");
134
147
  }
135
148
 
136
149
  /**
137
150
  * Set a callback to be notified when the effective permission mode changes due to configuration updates
138
151
  */
139
- public setOnConfiguredDefaultModeChange(
152
+ public setOnConfiguredPermissionModeChange(
140
153
  callback: (mode: PermissionMode) => void,
141
154
  ): void {
142
- this.onConfiguredDefaultModeChange = callback;
155
+ this.onConfiguredPermissionModeChange = callback;
143
156
  }
144
157
 
145
158
  /**
146
159
  * Update the configured default mode (e.g., when configuration reloads)
147
160
  */
148
- updateConfiguredDefaultMode(defaultMode?: PermissionMode): void {
161
+ updateConfiguredPermissionMode(permissionMode?: PermissionMode): void {
149
162
  const oldEffectiveMode = this.getCurrentEffectiveMode();
150
163
 
151
- this.configuredDefaultMode = defaultMode;
164
+ this.configuredPermissionMode = permissionMode;
152
165
 
153
166
  const newEffectiveMode = this.getCurrentEffectiveMode();
154
167
  if (
155
168
  oldEffectiveMode !== newEffectiveMode &&
156
- this.onConfiguredDefaultModeChange
169
+ this.onConfiguredPermissionModeChange
157
170
  ) {
158
- this.onConfiguredDefaultModeChange(newEffectiveMode);
171
+ this.onConfiguredPermissionModeChange(newEffectiveMode);
159
172
  }
160
173
  }
161
174
 
162
175
  /**
163
176
  * Get the configured default mode
164
177
  */
165
- public getConfiguredDefaultMode(): PermissionMode | undefined {
166
- return this.configuredDefaultMode;
178
+ public getConfiguredPermissionMode(): PermissionMode | undefined {
179
+ return this.configuredPermissionMode;
167
180
  }
168
181
 
169
182
  /**
@@ -180,6 +193,20 @@ export class PermissionManager {
180
193
  return [...this.deniedRules];
181
194
  }
182
195
 
196
+ /**
197
+ * Get all instance-specific allowed rules
198
+ */
199
+ public getInstanceAllowedRules(): string[] {
200
+ return [...this.instanceAllowedRules];
201
+ }
202
+
203
+ /**
204
+ * Get all instance-specific denied rules
205
+ */
206
+ public getInstanceDeniedRules(): string[] {
207
+ return [...this.instanceDeniedRules];
208
+ }
209
+
183
210
  /**
184
211
  * Get all additional directories
185
212
  */
@@ -325,8 +352,8 @@ export class PermissionManager {
325
352
  }
326
353
 
327
354
  // Use configured default mode if available
328
- if (this.configuredDefaultMode !== undefined) {
329
- return this.configuredDefaultMode;
355
+ if (this.configuredPermissionMode !== undefined) {
356
+ return this.configuredPermissionMode;
330
357
  }
331
358
 
332
359
  // Fall back to system default
@@ -340,6 +367,54 @@ export class PermissionManager {
340
367
  async checkPermission(
341
368
  context: ToolPermissionContext,
342
369
  ): Promise<PermissionDecision> {
370
+ // 0. Check instance-specific denied rules first - Deny always takes precedence
371
+ for (const rule of this.instanceDeniedRules) {
372
+ if (this.matchesRule(context, rule)) {
373
+ logger?.warn("Permission denied by instance rule", {
374
+ toolName: context.toolName,
375
+ rule,
376
+ });
377
+ return {
378
+ behavior: "deny",
379
+ message: `Access to tool '${context.toolName}' is explicitly denied by instance rule: ${rule}`,
380
+ };
381
+ }
382
+ }
383
+
384
+ // 0. Check worktree safety for Write and Edit tools
385
+ if (
386
+ this.worktreeName &&
387
+ this.mainRepoRoot &&
388
+ this.workdir &&
389
+ (context.toolName === WRITE_TOOL_NAME ||
390
+ context.toolName === EDIT_TOOL_NAME)
391
+ ) {
392
+ const targetPath = context.toolInput?.file_path as string | undefined;
393
+ if (targetPath) {
394
+ const absoluteTargetPath = path.resolve(this.workdir, targetPath);
395
+ const isInsideMainRepo = isPathInside(
396
+ absoluteTargetPath,
397
+ this.mainRepoRoot,
398
+ );
399
+ const isInsideWorktree = isPathInside(absoluteTargetPath, this.workdir);
400
+
401
+ // If it's inside the main repo but NOT inside the current worktree
402
+ if (isInsideMainRepo && !isInsideWorktree) {
403
+ logger?.warn("Worktree safety violation", {
404
+ toolName: context.toolName,
405
+ targetPath,
406
+ worktreeName: this.worktreeName,
407
+ mainRepoRoot: this.mainRepoRoot,
408
+ workdir: this.workdir,
409
+ });
410
+ return {
411
+ behavior: "deny",
412
+ message: `Access denied: You are currently in a worktree session ("${this.worktreeName}"). Modifying files in the main repository (outside the worktree) is not allowed. Please only modify files within the worktree directory: ${this.workdir}`,
413
+ };
414
+ }
415
+ }
416
+ }
417
+
343
418
  // 0. Check denied rules first - Deny always takes precedence
344
419
  for (const rule of this.deniedRules) {
345
420
  if (this.matchesRule(context, rule)) {
@@ -359,7 +434,7 @@ export class PermissionManager {
359
434
  return { behavior: "allow" };
360
435
  }
361
436
 
362
- // 1.1 If acceptEdits mode, allow Edit, Write
437
+ // 1.1 If acceptEdits mode, allow Edit, Write, and mkdir in safe zone
363
438
  if (context.permissionMode === "acceptEdits") {
364
439
  const autoAcceptedTools = [EDIT_TOOL_NAME, WRITE_TOOL_NAME];
365
440
  if (autoAcceptedTools.includes(context.toolName)) {
@@ -387,6 +462,40 @@ export class PermissionManager {
387
462
  }
388
463
  }
389
464
  }
465
+
466
+ // Special case for mkdir in Bash tool
467
+ if (context.toolName === BASH_TOOL_NAME && context.toolInput?.command) {
468
+ const command = String(context.toolInput.command).trim();
469
+ if (command.startsWith("mkdir ")) {
470
+ const parts = splitBashCommand(command);
471
+ // Check if it's a simple mkdir command (first part is mkdir)
472
+ if (parts.length === 1) {
473
+ const processedPart = stripEnvVars(parts[0]);
474
+ if (processedPart.startsWith("mkdir ")) {
475
+ const args = processedPart.slice(6).trim();
476
+ const pathArgs =
477
+ (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter(
478
+ (arg) => !arg.startsWith("-"),
479
+ ) || [];
480
+
481
+ if (pathArgs.length > 0) {
482
+ const allPathsSafe = pathArgs.every((pathArg) => {
483
+ const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
484
+ const { isInside } = this.isInsideSafeZone(
485
+ cleanPath,
486
+ context.toolInput?.workdir as string | undefined,
487
+ );
488
+ return isInside;
489
+ });
490
+
491
+ if (allPathsSafe) {
492
+ return { behavior: "allow" };
493
+ }
494
+ }
495
+ }
496
+ }
497
+ }
498
+ }
390
499
  }
391
500
 
392
501
  // 1.2 Check if tool call is allowed by persistent or temporary rules
@@ -475,7 +584,27 @@ export class PermissionManager {
475
584
  * Determine if a tool requires permission checks based on its name
476
585
  */
477
586
  isRestrictedTool(toolName: string): boolean {
478
- return (RESTRICTED_TOOLS as readonly string[]).includes(toolName);
587
+ return (
588
+ (RESTRICTED_TOOLS as readonly string[]).includes(toolName) ||
589
+ toolName.startsWith("mcp__")
590
+ );
591
+ }
592
+
593
+ /**
594
+ * Check if a tool is completely denied by name in instance or global rules
595
+ */
596
+ public isToolDenied(toolName: string): boolean {
597
+ // Check instance-specific denied rules
598
+ if (this.instanceDeniedRules.includes(toolName)) {
599
+ return true;
600
+ }
601
+
602
+ // Check global denied rules
603
+ if (this.deniedRules.includes(toolName)) {
604
+ return true;
605
+ }
606
+
607
+ return false;
479
608
  }
480
609
 
481
610
  /**
@@ -497,6 +626,8 @@ export class PermissionManager {
497
626
  const processedPart = stripRedirections(stripEnvVars(parts[0]));
498
627
  suggestedPrefix = getSmartPrefix(processedPart) ?? undefined;
499
628
  }
629
+ } else if (toolName.startsWith("mcp__")) {
630
+ suggestedPrefix = toolName;
500
631
  }
501
632
 
502
633
  const context: ToolPermissionContext = {
@@ -742,7 +873,12 @@ export class PermissionManager {
742
873
  return rules.some((rule) => this.matchesRule(ctx, rule));
743
874
  };
744
875
 
745
- // Check temporary rules first
876
+ // Check instance-specific allowed rules first
877
+ if (isAllowedByRuleList(context, this.instanceAllowedRules)) {
878
+ return true;
879
+ }
880
+
881
+ // Check temporary rules
746
882
  if (isAllowedByRuleList(context, this.temporaryRules)) {
747
883
  return true;
748
884
  }
@@ -16,7 +16,6 @@ import {
16
16
  replaceBashCommandsWithOutput,
17
17
  executeBashCommands,
18
18
  } from "../utils/markdownParser.js";
19
- import { INIT_PROMPT } from "../prompts/index.js";
20
19
  import type { SkillManager } from "./skillManager.js";
21
20
  import type { SkillMetadata } from "../types/skills.js";
22
21
 
@@ -73,21 +72,15 @@ export class SlashCommandManager {
73
72
  }
74
73
 
75
74
  private initializeBuiltinCommands(): void {
76
- // Register built-in init command
75
+ // Register built-in clear command
77
76
  this.registerCommand({
78
- id: "init",
79
- name: "init",
80
- description:
81
- "Initialize repository for AI agents by generating AGENTS.md",
77
+ id: "clear",
78
+ name: "clear",
79
+ description: "Clear conversation history and reset session",
82
80
  handler: async () => {
83
- // Add custom command message to show the command being executed
84
- this.messageManager.addUserMessage({
85
- content: "/init",
86
- customCommandContent: INIT_PROMPT,
87
- });
88
-
89
- // Execute the AI conversation with the init prompt
90
- await this.aiManager.sendAIMessage();
81
+ this.aiManager.abortAIMessage();
82
+ this.messageManager.clearMessages();
83
+ await this.taskManager.syncWithSession();
91
84
  },
92
85
  });
93
86
  }
@@ -80,6 +80,7 @@ export interface SubagentManagerOptions {
80
80
  workdir: string;
81
81
  callbacks?: SubagentManagerCallbacks; // Use SubagentManagerCallbacks instead of parentCallbacks
82
82
  onUsageAdded?: (usage: Usage) => void;
83
+ stream: boolean;
83
84
  }
84
85
 
85
86
  export class SubagentManager {
@@ -90,12 +91,14 @@ export class SubagentManager {
90
91
  private callbacks?: SubagentManagerCallbacks; // Use SubagentManagerCallbacks instead of parentCallbacks
91
92
  private onUsageAdded?: (usage: Usage) => void;
92
93
  private container: Container;
94
+ private stream: boolean;
93
95
 
94
96
  constructor(container: Container, options: SubagentManagerOptions) {
95
97
  this.container = container;
96
98
  this.workdir = options.workdir;
97
99
  this.callbacks = options.callbacks; // Store SubagentManagerCallbacks
98
100
  this.onUsageAdded = options.onUsageAdded;
101
+ this.stream = options.stream;
99
102
  }
100
103
 
101
104
  private get configurationService(): ConfigurationService {
@@ -155,6 +158,7 @@ export class SubagentManager {
155
158
  subagent_type: string;
156
159
  allowedTools?: string[];
157
160
  model?: string;
161
+ stream?: boolean;
158
162
  },
159
163
  runInBackground?: boolean,
160
164
  onUpdate?: () => void,
@@ -178,10 +182,16 @@ export class SubagentManager {
178
182
  this.container.get<PermissionManager>("PermissionManager");
179
183
  const subagentPermissionManager = new PermissionManager(subagentContainer, {
180
184
  workdir: this.workdir,
181
- configuredDefaultMode:
182
- parentPermissionManager?.getConfiguredDefaultMode(),
185
+ configuredPermissionMode:
186
+ parentPermissionManager?.getConfiguredPermissionMode(),
183
187
  allowedRules: parentPermissionManager?.getAllowedRules(),
184
188
  deniedRules: parentPermissionManager?.getDeniedRules(),
189
+ instanceAllowedRules:
190
+ parentPermissionManager?.getInstanceAllowedRules?.(),
191
+ instanceDeniedRules: [
192
+ ...(parentPermissionManager?.getInstanceDeniedRules?.() || []),
193
+ AGENT_TOOL_NAME, // Always deny Agent tool in subagents to prevent recursion
194
+ ],
185
195
  additionalDirectories:
186
196
  parentPermissionManager?.getAdditionalDirectories(),
187
197
  planFilePath: parentPermissionManager?.getPlanFilePath(),
@@ -222,6 +232,7 @@ export class SubagentManager {
222
232
  systemPrompt: configuration.systemPrompt,
223
233
  subagentType: parameters.subagent_type, // Pass subagent type for hook context
224
234
  modelOverride: parameters.model || configuration.model, // Pass model override
235
+ stream: parameters.stream ?? this.stream, // Pass streaming mode flag
225
236
  callbacks: {
226
237
  onUsageAdded: this.onUsageAdded,
227
238
  },
@@ -410,24 +421,9 @@ export class SubagentManager {
410
421
  // Add the user's prompt as a message
411
422
  instance.messageManager.addUserMessage({ content: prompt });
412
423
 
413
- // Create enabled tools list - always exclude Agent tool to prevent subagent recursion
414
- // Use instance.configuration.tools if provided, otherwise fallback to all tools
415
- let enabledTools = instance.configuration.tools;
416
-
417
- // Always filter out the Agent tool to prevent subagents from creating sub-subagents
418
- if (enabledTools) {
419
- enabledTools = enabledTools.filter((tool) => tool !== AGENT_TOOL_NAME);
420
- } else {
421
- // If no tools specified, get all tools except Agent
422
- const allTools = instance.toolManager.list().map((tool) => tool.name);
423
- enabledTools = allTools.filter((tool) => tool !== AGENT_TOOL_NAME);
424
- }
425
-
426
- // Execute the AI request with tool restrictions
424
+ // Execute the AI request
427
425
  // The AIManager will handle abort signals through its own abort controllers
428
- const executeAI = instance.aiManager.sendAIMessage({
429
- tools: enabledTools,
430
- });
426
+ const executeAI = instance.aiManager.sendAIMessage();
431
427
 
432
428
  // If we have an abort signal, race against it using utilities to prevent listener accumulation
433
429
  if (abortSignal && !instance.backgroundTaskId) {
@@ -6,6 +6,9 @@ import { editTool } from "../tools/editTool.js";
6
6
  import { writeTool } from "../tools/writeTool.js";
7
7
  import { exitPlanModeTool } from "../tools/exitPlanMode.js";
8
8
  import { askUserQuestionTool } from "../tools/askUserQuestion.js";
9
+ import { cronCreateTool } from "../tools/cronCreateTool.js";
10
+ import { cronDeleteTool } from "../tools/cronDeleteTool.js";
11
+ import { cronListTool } from "../tools/cronListTool.js";
9
12
  // New tools
10
13
  import { globTool } from "../tools/globTool.js";
11
14
  import { grepTool } from "../tools/grepTool.js";
@@ -114,6 +117,9 @@ class ToolManager {
114
117
  taskGetTool,
115
118
  taskUpdateTool,
116
119
  taskListTool,
120
+ cronCreateTool,
121
+ cronDeleteTool,
122
+ cronListTool,
117
123
  ];
118
124
 
119
125
  for (const tool of builtInTools) {
@@ -124,9 +130,17 @@ class ToolManager {
124
130
  }
125
131
 
126
132
  /**
127
- * Check if a tool should be enabled based on tools configuration
133
+ * Check if a tool should be enabled based on tools configuration and permission rules
128
134
  */
129
135
  private shouldEnableTool(name: string): boolean {
136
+ const permissionManager =
137
+ this.container.get<PermissionManager>("PermissionManager");
138
+
139
+ // If tool is explicitly denied by name in permission rules, filter it out
140
+ if (permissionManager?.isToolDenied(name)) {
141
+ return false;
142
+ }
143
+
130
144
  if (!this.tools) {
131
145
  return true;
132
146
  }
@@ -194,6 +208,11 @@ class ToolManager {
194
208
  skillManager: this.container.has("SkillManager")
195
209
  ? this.container.get<SkillManager>("SkillManager")
196
210
  : undefined,
211
+ cronManager: this.container.has("CronManager")
212
+ ? this.container.get<import("./cronManager.js").CronManager>(
213
+ "CronManager",
214
+ )
215
+ : undefined,
197
216
  sessionId: context.sessionId,
198
217
  toolCallId: context.toolCallId,
199
218
  };
@@ -241,8 +260,14 @@ class ToolManager {
241
260
  }
242
261
 
243
262
  list(): ToolPlugin[] {
244
- const builtInTools = Array.from(this.toolsRegistry.values());
245
- const mcpTools = this.mcpManager.getMcpToolPlugins();
263
+ const permissionManager =
264
+ this.container.get<PermissionManager>("PermissionManager");
265
+ const builtInTools = Array.from(this.toolsRegistry.values()).filter(
266
+ (tool) => !permissionManager?.isToolDenied(tool.name),
267
+ );
268
+ const mcpTools = this.mcpManager
269
+ .getMcpToolPlugins()
270
+ .filter((tool) => !permissionManager?.isToolDenied(tool.name));
246
271
  return [...builtInTools, ...mcpTools];
247
272
  }
248
273
 
@@ -251,9 +276,15 @@ class ToolManager {
251
276
  availableSkills?: SkillMetadata[];
252
277
  workdir?: string;
253
278
  }): ChatCompletionFunctionTool[] {
279
+ const permissionManager =
280
+ this.container.get<PermissionManager>("PermissionManager");
254
281
  const effectivePermissionMode = this.getPermissionMode();
255
282
  const builtInToolsConfig = Array.from(this.toolsRegistry.values())
256
283
  .filter((tool) => {
284
+ // If tool is explicitly denied by name in permission rules, filter it out
285
+ if (permissionManager?.isToolDenied(tool.name)) {
286
+ return false;
287
+ }
257
288
  if (effectivePermissionMode === "bypassPermissions") {
258
289
  if (tool.name === "ExitPlanMode" || tool.name === "AskUserQuestion") {
259
290
  return false;
@@ -278,7 +309,9 @@ class ToolManager {
278
309
  }
279
310
  return config;
280
311
  });
281
- const mcpToolsConfig = this.mcpManager.getMcpToolsConfig();
312
+ const mcpToolsConfig = this.mcpManager
313
+ .getMcpToolsConfig()
314
+ .filter((tool) => !permissionManager?.isToolDenied(tool.function.name));
282
315
  return [...builtInToolsConfig, ...mcpToolsConfig];
283
316
  }
284
317