wave-agent-sdk 0.7.1 → 0.8.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 (168) hide show
  1. package/dist/agent.d.ts +9 -79
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +85 -302
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +2 -1
  7. package/dist/managers/aiManager.d.ts.map +1 -1
  8. package/dist/managers/aiManager.js +20 -13
  9. package/dist/managers/backgroundTaskManager.d.ts +1 -1
  10. package/dist/managers/backgroundTaskManager.d.ts.map +1 -1
  11. package/dist/managers/backgroundTaskManager.js +1 -1
  12. package/dist/managers/{bashManager.d.ts → bangManager.d.ts} +4 -4
  13. package/dist/managers/{bashManager.d.ts.map → bangManager.d.ts.map} +1 -1
  14. package/dist/managers/{bashManager.js → bangManager.js} +5 -6
  15. package/dist/managers/hookManager.d.ts.map +1 -1
  16. package/dist/managers/hookManager.js +12 -3
  17. package/dist/managers/messageManager.d.ts +18 -6
  18. package/dist/managers/messageManager.d.ts.map +1 -1
  19. package/dist/managers/messageManager.js +42 -20
  20. package/dist/managers/permissionManager.d.ts +22 -1
  21. package/dist/managers/permissionManager.d.ts.map +1 -1
  22. package/dist/managers/permissionManager.js +106 -85
  23. package/dist/managers/planManager.d.ts +6 -0
  24. package/dist/managers/planManager.d.ts.map +1 -1
  25. package/dist/managers/planManager.js +21 -0
  26. package/dist/managers/skillManager.d.ts +7 -2
  27. package/dist/managers/skillManager.d.ts.map +1 -1
  28. package/dist/managers/skillManager.js +30 -10
  29. package/dist/managers/slashCommandManager.d.ts +7 -0
  30. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  31. package/dist/managers/slashCommandManager.js +57 -45
  32. package/dist/managers/subagentManager.d.ts +4 -0
  33. package/dist/managers/subagentManager.d.ts.map +1 -1
  34. package/dist/managers/subagentManager.js +47 -13
  35. package/dist/managers/toolManager.d.ts +7 -1
  36. package/dist/managers/toolManager.d.ts.map +1 -1
  37. package/dist/managers/toolManager.js +15 -2
  38. package/dist/prompts/index.d.ts +0 -4
  39. package/dist/prompts/index.d.ts.map +1 -1
  40. package/dist/prompts/index.js +0 -9
  41. package/dist/services/aiService.d.ts.map +1 -1
  42. package/dist/services/aiService.js +6 -6
  43. package/dist/services/configurationService.d.ts +2 -2
  44. package/dist/services/configurationService.d.ts.map +1 -1
  45. package/dist/services/configurationService.js +4 -4
  46. package/dist/services/hook.d.ts.map +1 -1
  47. package/dist/services/hook.js +6 -0
  48. package/dist/services/initializationService.d.ts +44 -0
  49. package/dist/services/initializationService.d.ts.map +1 -0
  50. package/dist/services/initializationService.js +170 -0
  51. package/dist/services/interactionService.d.ts +29 -0
  52. package/dist/services/interactionService.d.ts.map +1 -0
  53. package/dist/services/interactionService.js +97 -0
  54. package/dist/services/session.js +1 -1
  55. package/dist/services/taskManager.d.ts +5 -0
  56. package/dist/services/taskManager.d.ts.map +1 -1
  57. package/dist/services/taskManager.js +16 -2
  58. package/dist/tools/bashTool.d.ts.map +1 -1
  59. package/dist/tools/bashTool.js +7 -18
  60. package/dist/tools/editTool.js +1 -1
  61. package/dist/tools/exitPlanMode.js +1 -1
  62. package/dist/tools/lspTool.d.ts +2 -0
  63. package/dist/tools/lspTool.d.ts.map +1 -1
  64. package/dist/tools/lspTool.js +144 -52
  65. package/dist/tools/skillTool.d.ts.map +1 -1
  66. package/dist/tools/skillTool.js +97 -2
  67. package/dist/tools/taskManagementTools.d.ts.map +1 -1
  68. package/dist/tools/taskManagementTools.js +23 -2
  69. package/dist/tools/taskTool.d.ts.map +1 -1
  70. package/dist/tools/taskTool.js +9 -15
  71. package/dist/tools/types.d.ts +1 -2
  72. package/dist/tools/types.d.ts.map +1 -1
  73. package/dist/tools/writeTool.js +1 -1
  74. package/dist/types/agent.d.ts +64 -0
  75. package/dist/types/agent.d.ts.map +1 -0
  76. package/dist/types/agent.js +1 -0
  77. package/dist/types/commands.d.ts +0 -4
  78. package/dist/types/commands.d.ts.map +1 -1
  79. package/dist/types/config.d.ts +1 -1
  80. package/dist/types/config.d.ts.map +1 -1
  81. package/dist/types/hooks.d.ts +3 -1
  82. package/dist/types/hooks.d.ts.map +1 -1
  83. package/dist/types/hooks.js +1 -0
  84. package/dist/types/index.d.ts +1 -0
  85. package/dist/types/index.d.ts.map +1 -1
  86. package/dist/types/index.js +1 -0
  87. package/dist/types/messaging.d.ts +3 -3
  88. package/dist/types/messaging.d.ts.map +1 -1
  89. package/dist/types/skills.d.ts +13 -0
  90. package/dist/types/skills.d.ts.map +1 -1
  91. package/dist/utils/commandPathResolver.d.ts +3 -36
  92. package/dist/utils/commandPathResolver.d.ts.map +1 -1
  93. package/dist/utils/commandPathResolver.js +16 -93
  94. package/dist/utils/configValidator.d.ts +2 -2
  95. package/dist/utils/configValidator.d.ts.map +1 -1
  96. package/dist/utils/configValidator.js +4 -6
  97. package/dist/utils/containerSetup.d.ts +3 -4
  98. package/dist/utils/containerSetup.d.ts.map +1 -1
  99. package/dist/utils/containerSetup.js +14 -9
  100. package/dist/utils/customCommands.d.ts +2 -3
  101. package/dist/utils/customCommands.d.ts.map +1 -1
  102. package/dist/utils/customCommands.js +20 -60
  103. package/dist/utils/gitUtils.d.ts +25 -0
  104. package/dist/utils/gitUtils.d.ts.map +1 -1
  105. package/dist/utils/gitUtils.js +75 -0
  106. package/dist/utils/markdownParser.d.ts +4 -0
  107. package/dist/utils/markdownParser.d.ts.map +1 -1
  108. package/dist/utils/markdownParser.js +33 -0
  109. package/dist/utils/messageOperations.d.ts +16 -7
  110. package/dist/utils/messageOperations.d.ts.map +1 -1
  111. package/dist/utils/messageOperations.js +45 -20
  112. package/dist/utils/nameGenerator.d.ts +1 -1
  113. package/dist/utils/nameGenerator.d.ts.map +1 -1
  114. package/dist/utils/nameGenerator.js +10 -6
  115. package/dist/utils/skillParser.d.ts.map +1 -1
  116. package/dist/utils/skillParser.js +48 -0
  117. package/package.json +1 -1
  118. package/src/agent.ts +103 -458
  119. package/src/index.ts +2 -2
  120. package/src/managers/aiManager.ts +23 -17
  121. package/src/managers/backgroundTaskManager.ts +2 -2
  122. package/src/managers/{bashManager.ts → bangManager.ts} +11 -8
  123. package/src/managers/hookManager.ts +13 -3
  124. package/src/managers/messageManager.ts +55 -26
  125. package/src/managers/permissionManager.ts +121 -98
  126. package/src/managers/planManager.ts +26 -0
  127. package/src/managers/skillManager.ts +51 -14
  128. package/src/managers/slashCommandManager.ts +75 -55
  129. package/src/managers/subagentManager.ts +57 -13
  130. package/src/managers/toolManager.ts +22 -2
  131. package/src/prompts/index.ts +0 -15
  132. package/src/services/aiService.ts +9 -12
  133. package/src/services/configurationService.ts +4 -4
  134. package/src/services/hook.ts +7 -0
  135. package/src/services/initializationService.ts +291 -0
  136. package/src/services/interactionService.ts +171 -0
  137. package/src/services/session.ts +1 -1
  138. package/src/services/taskManager.ts +18 -2
  139. package/src/tools/bashTool.ts +8 -18
  140. package/src/tools/editTool.ts +1 -1
  141. package/src/tools/exitPlanMode.ts +1 -1
  142. package/src/tools/lsTool.ts +1 -1
  143. package/src/tools/lspTool.ts +184 -52
  144. package/src/tools/skillTool.ts +127 -2
  145. package/src/tools/taskManagementTools.ts +32 -2
  146. package/src/tools/taskTool.ts +13 -15
  147. package/src/tools/types.ts +1 -2
  148. package/src/tools/writeTool.ts +1 -1
  149. package/src/types/agent.ts +83 -0
  150. package/src/types/commands.ts +0 -6
  151. package/src/types/config.ts +1 -1
  152. package/src/types/hooks.ts +5 -1
  153. package/src/types/index.ts +1 -0
  154. package/src/types/messaging.ts +3 -3
  155. package/src/types/skills.ts +13 -0
  156. package/src/utils/commandPathResolver.ts +14 -117
  157. package/src/utils/configValidator.ts +5 -9
  158. package/src/utils/containerSetup.ts +17 -14
  159. package/src/utils/customCommands.ts +20 -83
  160. package/src/utils/gitUtils.ts +75 -0
  161. package/src/utils/markdownParser.ts +47 -0
  162. package/src/utils/messageOperations.ts +58 -28
  163. package/src/utils/nameGenerator.ts +10 -6
  164. package/src/utils/skillParser.ts +52 -0
  165. package/dist/managers/backgroundBashManager.d.ts +0 -27
  166. package/dist/managers/backgroundBashManager.d.ts.map +0 -1
  167. package/dist/managers/backgroundBashManager.js +0 -169
  168. package/src/managers/backgroundBashManager.ts +0 -206
@@ -32,9 +32,33 @@ import {
32
32
  LS_TOOL_NAME,
33
33
  } from "../constants/tools.js";
34
34
  import { Container } from "../utils/container.js";
35
+ import { ConfigurationService } from "../services/configurationService.js";
35
36
 
36
37
  const SAFE_COMMANDS = ["cd", "ls", "pwd", "true", "false"];
37
38
 
39
+ const DEFAULT_ALLOWED_RULES = [
40
+ "Bash(git status*)",
41
+ "Bash(git diff*)",
42
+ "Bash(git log*)",
43
+ "Bash(git show*)",
44
+ "Bash(git branch*)",
45
+ "Bash(git tag*)",
46
+ "Bash(git remote*)",
47
+ "Bash(git ls-files*)",
48
+ "Bash(git rev-parse*)",
49
+ "Bash(git config --list*)",
50
+ "Bash(git config -l*)",
51
+ "Bash(git cat-file*)",
52
+ "Bash(git count-objects*)",
53
+ "Bash(echo*)",
54
+ "Bash(which*)",
55
+ "Bash(type*)",
56
+ "Bash(hostname*)",
57
+ "Bash(whoami*)",
58
+ "Bash(date*)",
59
+ "Bash(uptime*)",
60
+ ];
61
+
38
62
  import { logger } from "../utils/globalLogger.js";
39
63
 
40
64
  export interface PermissionManagerOptions {
@@ -93,10 +117,6 @@ export class PermissionManager {
93
117
  updateConfiguredDefaultMode(defaultMode?: PermissionMode): void {
94
118
  const oldEffectiveMode = this.getCurrentEffectiveMode();
95
119
 
96
- logger?.debug("Updating configured default permission mode", {
97
- previous: this.configuredDefaultMode,
98
- new: defaultMode,
99
- });
100
120
  this.configuredDefaultMode = defaultMode;
101
121
 
102
122
  const newEffectiveMode = this.getCurrentEffectiveMode();
@@ -104,31 +124,49 @@ export class PermissionManager {
104
124
  oldEffectiveMode !== newEffectiveMode &&
105
125
  this.onConfiguredDefaultModeChange
106
126
  ) {
107
- logger?.debug(
108
- "Effective permission mode changed due to configuration update",
109
- {
110
- oldMode: oldEffectiveMode,
111
- newMode: newEffectiveMode,
112
- },
113
- );
114
127
  this.onConfiguredDefaultModeChange(newEffectiveMode);
115
128
  }
116
129
  }
117
130
 
118
131
  /**
119
- * Get all currently allowed rules
132
+ * Get the configured default mode
133
+ */
134
+ public getConfiguredDefaultMode(): PermissionMode | undefined {
135
+ return this.configuredDefaultMode;
136
+ }
137
+
138
+ /**
139
+ * Get all currently allowed rules (user-defined)
120
140
  */
121
141
  public getAllowedRules(): string[] {
122
142
  return [...this.allowedRules];
123
143
  }
124
144
 
145
+ /**
146
+ * Get all currently denied rules
147
+ */
148
+ public getDeniedRules(): string[] {
149
+ return [...this.deniedRules];
150
+ }
151
+
152
+ /**
153
+ * Get all additional directories
154
+ */
155
+ public getAdditionalDirectories(): string[] {
156
+ return [...this.additionalDirectories];
157
+ }
158
+
159
+ /**
160
+ * Get all default allowed rules
161
+ */
162
+ public getDefaultAllowedRules(): string[] {
163
+ return [...DEFAULT_ALLOWED_RULES];
164
+ }
165
+
125
166
  /**
126
167
  * Update the allowed rules (e.g., when configuration reloads)
127
168
  */
128
169
  updateAllowedRules(rules: string[]): void {
129
- logger?.debug("Updating allowed permission rules", {
130
- count: rules.length,
131
- });
132
170
  this.allowedRules = rules;
133
171
  }
134
172
 
@@ -136,9 +174,6 @@ export class PermissionManager {
136
174
  * Update the denied rules (e.g., when configuration reloads)
137
175
  */
138
176
  updateDeniedRules(rules: string[]): void {
139
- logger?.debug("Updating denied permission rules", {
140
- count: rules.length,
141
- });
142
177
  this.deniedRules = rules;
143
178
  }
144
179
 
@@ -146,10 +181,6 @@ export class PermissionManager {
146
181
  * Add temporary rules for the current session
147
182
  */
148
183
  public addTemporaryRules(rules: string[]): void {
149
- logger?.debug("Adding temporary permission rules", {
150
- count: rules.length,
151
- rules,
152
- });
153
184
  this.temporaryRules.push(...rules);
154
185
  }
155
186
 
@@ -157,7 +188,6 @@ export class PermissionManager {
157
188
  * Clear all temporary rules
158
189
  */
159
190
  public clearTemporaryRules(): void {
160
- logger?.debug("Clearing temporary permission rules");
161
191
  this.temporaryRules = [];
162
192
  }
163
193
 
@@ -165,9 +195,6 @@ export class PermissionManager {
165
195
  * Update the additional directories (e.g., when configuration reloads)
166
196
  */
167
197
  updateAdditionalDirectories(directories: string[]): void {
168
- logger?.debug("Updating additional directories", {
169
- count: directories.length,
170
- });
171
198
  this.additionalDirectories = directories.map((dir) => {
172
199
  if (this.workdir && !path.isAbsolute(dir)) {
173
200
  return path.resolve(this.workdir, dir);
@@ -180,9 +207,6 @@ export class PermissionManager {
180
207
  * Update the working directory
181
208
  */
182
209
  updateWorkdir(workdir: string): void {
183
- logger?.debug("Updating working directory", {
184
- workdir,
185
- });
186
210
  this.workdir = workdir;
187
211
  }
188
212
 
@@ -190,7 +214,6 @@ export class PermissionManager {
190
214
  * Set the current plan file path
191
215
  */
192
216
  public setPlanFilePath(path: string | undefined): void {
193
- logger?.debug("Setting plan file path", { path });
194
217
  this.planFilePath = path;
195
218
  }
196
219
 
@@ -228,12 +251,6 @@ export class PermissionManager {
228
251
  }
229
252
  }
230
253
 
231
- logger?.debug("Path is outside Safe Zone", {
232
- absolutePath,
233
- workdir: effectiveWorkdir,
234
- additionalDirectories: this.additionalDirectories,
235
- });
236
-
237
254
  return { isInside: false, resolvedPath: absolutePath };
238
255
  }
239
256
 
@@ -252,23 +269,15 @@ export class PermissionManager {
252
269
  ): PermissionMode {
253
270
  // CLI override takes highest precedence
254
271
  if (cliPermissionMode !== undefined) {
255
- logger?.debug("Using CLI permission mode override", {
256
- cliMode: cliPermissionMode,
257
- configuredDefault: this.configuredDefaultMode,
258
- });
259
272
  return cliPermissionMode;
260
273
  }
261
274
 
262
275
  // Use configured default mode if available
263
276
  if (this.configuredDefaultMode !== undefined) {
264
- logger?.debug("Using configured default permission mode", {
265
- configuredDefault: this.configuredDefaultMode,
266
- });
267
277
  return this.configuredDefaultMode;
268
278
  }
269
279
 
270
280
  // Fall back to system default
271
- logger?.debug("Using system default permission mode");
272
281
  return "default";
273
282
  }
274
283
 
@@ -279,12 +288,6 @@ export class PermissionManager {
279
288
  async checkPermission(
280
289
  context: ToolPermissionContext,
281
290
  ): Promise<PermissionDecision> {
282
- logger?.debug("Checking permission for tool", {
283
- toolName: context.toolName,
284
- permissionMode: context.permissionMode,
285
- hasCallback: !!context.canUseToolCallback,
286
- });
287
-
288
291
  // 0. Check denied rules first - Deny always takes precedence
289
292
  for (const rule of this.deniedRules) {
290
293
  if (this.matchesRule(context, rule)) {
@@ -301,9 +304,6 @@ export class PermissionManager {
301
304
 
302
305
  // 1. If bypassPermissions mode, always allow
303
306
  if (context.permissionMode === "bypassPermissions") {
304
- logger?.debug("Permission bypassed for tool", {
305
- toolName: context.toolName,
306
- });
307
307
  return { behavior: "allow" };
308
308
  }
309
309
 
@@ -331,12 +331,6 @@ export class PermissionManager {
331
331
  );
332
332
  // Fall through to normal permission check flow to trigger confirmation prompt
333
333
  } else {
334
- logger?.debug(
335
- "Permission automatically accepted for tool in acceptEdits mode",
336
- {
337
- toolName: context.toolName,
338
- },
339
- );
340
334
  return { behavior: "allow" };
341
335
  }
342
336
  }
@@ -345,9 +339,6 @@ export class PermissionManager {
345
339
 
346
340
  // 1.2 Check if tool call is allowed by persistent or temporary rules
347
341
  if (this.isAllowedByRule(context)) {
348
- logger?.debug("Permission allowed by persistent rule", {
349
- toolName: context.toolName,
350
- });
351
342
  return { behavior: "allow" };
352
343
  }
353
344
 
@@ -362,10 +353,6 @@ export class PermissionManager {
362
353
  const absolutePlanPath = path.resolve(this.planFilePath);
363
354
 
364
355
  if (absoluteTargetPath === absolutePlanPath) {
365
- logger?.debug("Allowing write to plan file in plan mode", {
366
- toolName: context.toolName,
367
- targetPath,
368
- });
369
356
  return { behavior: "allow" };
370
357
  }
371
358
  }
@@ -379,23 +366,19 @@ export class PermissionManager {
379
366
 
380
367
  // 2. If not a restricted tool, always allow
381
368
  if (!this.isRestrictedTool(context.toolName)) {
382
- logger?.debug("Tool is not restricted, allowing", {
383
- toolName: context.toolName,
384
- });
385
369
  return { behavior: "allow" };
386
370
  }
387
371
 
388
372
  // 3. If custom callback provided, call it and return result
389
373
  if (context.canUseToolCallback) {
390
374
  try {
391
- logger?.debug("Calling custom permission callback for tool", {
392
- toolName: context.toolName,
393
- });
394
375
  const decision = await context.canUseToolCallback(context);
395
- logger?.debug("Custom callback returned decision", {
396
- toolName: context.toolName,
397
- decision,
398
- });
376
+ if (decision.behavior !== "allow") {
377
+ logger?.debug("Custom callback returned decision", {
378
+ toolName: context.toolName,
379
+ decision,
380
+ });
381
+ }
399
382
  return decision;
400
383
  } catch (error) {
401
384
  const errorMessage =
@@ -429,14 +412,7 @@ export class PermissionManager {
429
412
  * Determine if a tool requires permission checks based on its name
430
413
  */
431
414
  isRestrictedTool(toolName: string): boolean {
432
- const isRestricted = (RESTRICTED_TOOLS as readonly string[]).includes(
433
- toolName,
434
- );
435
- logger?.debug("Checking if tool is restricted", {
436
- toolName,
437
- isRestricted,
438
- });
439
- return isRestricted;
415
+ return (RESTRICTED_TOOLS as readonly string[]).includes(toolName);
440
416
  }
441
417
 
442
418
  /**
@@ -521,14 +497,6 @@ export class PermissionManager {
521
497
  }
522
498
  }
523
499
 
524
- logger?.debug("Created permission context", {
525
- toolName,
526
- permissionMode,
527
- hasCallback: !!callback,
528
- hasToolInput: !!toolInput,
529
- suggestedPrefix,
530
- });
531
-
532
500
  return context;
533
501
  }
534
502
 
@@ -562,8 +530,9 @@ export class PermissionManager {
562
530
  const regexPattern = pattern
563
531
  .replace(/[.+^${}()|[\]\\?]/g, "\\$&") // Escape regex special chars including ?
564
532
  .replace(/\*/g, ".*"); // Replace * with .*
565
- const regex = new RegExp(`^${regexPattern}$`);
566
- return regex.test(processedPart);
533
+ const regex = new RegExp(`^${regexPattern}$`, "s");
534
+ const matched = regex.test(processedPart);
535
+ return matched;
567
536
  }
568
537
 
569
538
  // Handle path-based rules (e.g., "Read(**/*.env)")
@@ -649,9 +618,9 @@ export class PermissionManager {
649
618
  ...ctx,
650
619
  toolInput: { ...ctx.toolInput, command: processedPart },
651
620
  };
652
- const allowedByRule = rules.some((rule) =>
653
- this.matchesRule(partContext, rule),
654
- );
621
+ const allowedByRule = rules.some((rule) => {
622
+ return this.matchesRule(partContext, rule);
623
+ });
655
624
 
656
625
  if (allowedByRule) return true;
657
626
 
@@ -669,7 +638,12 @@ export class PermissionManager {
669
638
  }
670
639
 
671
640
  // Check persistent allowed rules
672
- return isAllowedByRuleList(context, this.allowedRules);
641
+ if (isAllowedByRuleList(context, this.allowedRules)) {
642
+ return true;
643
+ }
644
+
645
+ // Check default allowed rules
646
+ return isAllowedByRuleList(context, DEFAULT_ALLOWED_RULES);
673
647
  }
674
648
 
675
649
  /**
@@ -761,4 +735,53 @@ export class PermissionManager {
761
735
 
762
736
  return rules;
763
737
  }
738
+
739
+ /**
740
+ * Add a persistent permission rule
741
+ * @param rule - The rule to add (e.g., "Bash(ls)")
742
+ */
743
+ public async addPermissionRule(rule: string): Promise<void> {
744
+ if (!this.workdir) {
745
+ throw new Error("Working directory not set in PermissionManager");
746
+ }
747
+
748
+ // 1. Expand rule if it's a Bash command
749
+ let rulesToAdd = [rule];
750
+ const bashMatch = rule.match(/^Bash\((.*)\)$/);
751
+ if (bashMatch) {
752
+ const command = bashMatch[1];
753
+ rulesToAdd = this.expandBashRule(command, this.workdir);
754
+ }
755
+
756
+ const configurationService = this.container.get<ConfigurationService>(
757
+ "ConfigurationService",
758
+ );
759
+
760
+ for (const ruleToAdd of rulesToAdd) {
761
+ // 2. Update PermissionManager state
762
+ const currentRules = this.getAllowedRules();
763
+ const defaultRules = this.getDefaultAllowedRules();
764
+ if (
765
+ !currentRules.includes(ruleToAdd) &&
766
+ !defaultRules.includes(ruleToAdd)
767
+ ) {
768
+ this.updateAllowedRules([...currentRules, ruleToAdd]);
769
+
770
+ // 3. Persist to settings.local.json
771
+ try {
772
+ if (configurationService) {
773
+ await configurationService.addAllowedRule(this.workdir, ruleToAdd);
774
+ this._logger?.debug("Persistent permission rule added", {
775
+ rule: ruleToAdd,
776
+ });
777
+ }
778
+ } catch (error) {
779
+ this._logger?.error("Failed to persist permission rule", {
780
+ rule: ruleToAdd,
781
+ error: error instanceof Error ? error.message : String(error),
782
+ });
783
+ }
784
+ }
785
+ }
786
+ }
764
787
  }
@@ -4,6 +4,9 @@ import os from "node:os";
4
4
  import { generateRandomName } from "../utils/nameGenerator.js";
5
5
 
6
6
  import { Container } from "../utils/container.js";
7
+ import { MessageManager } from "./messageManager.js";
8
+ import { PermissionManager } from "./permissionManager.js";
9
+ import type { PermissionMode } from "../types/permissions.js";
7
10
 
8
11
  /**
9
12
  * Manages plan files for plan mode
@@ -61,4 +64,27 @@ export class PlanManager {
61
64
  public getPlanDir(): string {
62
65
  return this.planDir;
63
66
  }
67
+
68
+ /**
69
+ * Handle plan mode transition, generating or clearing plan file path
70
+ * @param mode - The current effective permission mode
71
+ */
72
+ public handlePlanModeTransition(mode: PermissionMode): void {
73
+ const permissionManager =
74
+ this.container.get<PermissionManager>("PermissionManager");
75
+ const messageManager = this.container.get<MessageManager>("MessageManager");
76
+
77
+ if (mode === "plan") {
78
+ this.getOrGeneratePlanFilePath(messageManager?.getRootSessionId())
79
+ .then(({ path }) => {
80
+ logger?.debug("Plan file path generated", { path });
81
+ permissionManager?.setPlanFilePath(path);
82
+ })
83
+ .catch((error) => {
84
+ logger?.error("Failed to generate plan file path", error);
85
+ });
86
+ } else {
87
+ permissionManager?.setPlanFilePath(undefined);
88
+ }
89
+ }
64
90
  }
@@ -11,6 +11,12 @@ import type {
11
11
  SkillInvocationContext,
12
12
  } from "../types/index.js";
13
13
  import { parseSkillFile, formatSkillError } from "../utils/skillParser.js";
14
+ import { substituteCommandParameters } from "../utils/commandArgumentParser.js";
15
+ import {
16
+ parseBashCommands,
17
+ replaceBashCommandsWithOutput,
18
+ executeBashCommands,
19
+ } from "../utils/markdownParser.js";
14
20
 
15
21
  import { Container } from "../utils/container.js";
16
22
  import { logger } from "../utils/globalLogger.js";
@@ -94,6 +100,17 @@ export class SkillManager {
94
100
  return Array.from(this.skillMetadata.values());
95
101
  }
96
102
 
103
+ /**
104
+ * Get metadata for a specific skill by name
105
+ */
106
+ getSkillMetadata(name: string): SkillMetadata | undefined {
107
+ if (!this.initialized) {
108
+ throw new Error("SkillManager not initialized. Call initialize() first.");
109
+ }
110
+
111
+ return this.skillMetadata.get(name);
112
+ }
113
+
97
114
  /**
98
115
  * Load a specific skill by name
99
116
  * Returns the skill content that was loaded during initialization
@@ -176,17 +193,14 @@ export class SkillManager {
176
193
 
177
194
  if (parsed.isValid) {
178
195
  // Override the skill type with the collection type
179
- const skillMetadata = {
196
+ const skillMetadata: SkillMetadata = {
180
197
  ...parsed.skillMetadata,
181
198
  type,
182
199
  };
183
200
 
184
201
  // Create full skill object with content
185
202
  const skill: Skill = {
186
- name: parsed.skillMetadata.name,
187
- description: parsed.skillMetadata.description,
188
- type: type, // Use the collection type
189
- skillPath: parsed.skillMetadata.skillPath,
203
+ ...skillMetadata,
190
204
  content: parsed.content,
191
205
  frontmatter: parsed.frontmatter,
192
206
  isValid: parsed.isValid,
@@ -243,12 +257,14 @@ export class SkillManager {
243
257
  /**
244
258
  * Execute a skill by name
245
259
  */
246
- async executeSkill(
247
- args: SkillToolArgs,
248
- ): Promise<{ content: string; context?: SkillInvocationContext }> {
249
- const { skill_name } = args;
260
+ async executeSkill(args: SkillToolArgs): Promise<{
261
+ content: string;
262
+ context?: SkillInvocationContext;
263
+ allowedTools?: string[];
264
+ }> {
265
+ const { skill_name, args: skillArgs } = args;
250
266
 
251
- logger?.debug(`Invoking skill: ${skill_name}`);
267
+ logger?.debug(`Invoking skill: ${skill_name} with args: ${skillArgs}`);
252
268
 
253
269
  try {
254
270
  // Load the skill
@@ -267,12 +283,19 @@ export class SkillManager {
267
283
  };
268
284
  }
269
285
 
286
+ // Process skill content with arguments and bash commands
287
+ const processedContent = await this.processSkillContent(
288
+ skill,
289
+ skillArgs || "",
290
+ );
291
+
270
292
  // Return skill content with context
271
293
  return {
272
- content: this.formatSkillContent(skill),
294
+ content: processedContent,
273
295
  context: {
274
296
  skillName: skill_name,
275
297
  },
298
+ allowedTools: skill.allowedTools,
276
299
  };
277
300
  } catch (error) {
278
301
  logger?.error(`Failed to execute skill '${skill_name}':`, error);
@@ -283,16 +306,29 @@ export class SkillManager {
283
306
  }
284
307
 
285
308
  /**
286
- * Format skill content for output
309
+ * Process skill content with arguments and bash commands
287
310
  */
288
- private formatSkillContent(skill: Skill): string {
311
+ private async processSkillContent(
312
+ skill: Skill,
313
+ argsString: string,
314
+ ): Promise<string> {
289
315
  const header = `🧠 **${skill.name}** (${skill.type} skill)\n\n`;
290
316
  const description = `*${skill.description}*\n\n`;
291
317
  const skillPath = `📁 Skill location: \`${skill.skillPath}\`\n\n`;
292
318
 
293
319
  // Extract content after frontmatter
294
320
  const contentMatch = skill.content.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/);
295
- const mainContent = contentMatch ? contentMatch[1].trim() : skill.content;
321
+ let mainContent = contentMatch ? contentMatch[1].trim() : skill.content;
322
+
323
+ // 1. Substitute parameters ($1, $ARGUMENTS, etc.)
324
+ mainContent = substituteCommandParameters(mainContent, argsString);
325
+
326
+ // 2. Parse and execute bash commands (!`command`)
327
+ const { commands } = parseBashCommands(mainContent);
328
+ if (commands.length > 0) {
329
+ const results = await executeBashCommands(commands, this.workdir);
330
+ mainContent = replaceBashCommandsWithOutput(mainContent, results);
331
+ }
296
332
 
297
333
  return header + description + skillPath + mainContent;
298
334
  }
@@ -324,6 +360,7 @@ export class SkillManager {
324
360
  description: skill.description,
325
361
  type: skill.type,
326
362
  skillPath: skill.skillPath,
363
+ allowedTools: skill.allowedTools,
327
364
  });
328
365
  this.skillContent.set(skill.name, skill);
329
366
  }