wave-agent-sdk 0.17.5 → 0.17.7

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 (67) hide show
  1. package/dist/agent.d.ts +18 -0
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +114 -1
  4. package/dist/managers/MemoryRuleManager.d.ts.map +1 -1
  5. package/dist/managers/MemoryRuleManager.js +30 -13
  6. package/dist/managers/aiManager.d.ts +1 -0
  7. package/dist/managers/aiManager.d.ts.map +1 -1
  8. package/dist/managers/aiManager.js +20 -62
  9. package/dist/managers/hookManager.d.ts.map +1 -1
  10. package/dist/managers/hookManager.js +3 -1
  11. package/dist/managers/lspManager.d.ts.map +1 -1
  12. package/dist/managers/lspManager.js +12 -4
  13. package/dist/managers/mcpManager.d.ts.map +1 -1
  14. package/dist/managers/mcpManager.js +13 -6
  15. package/dist/managers/messageManager.d.ts.map +1 -1
  16. package/dist/managers/messageManager.js +0 -4
  17. package/dist/managers/permissionManager.d.ts.map +1 -1
  18. package/dist/managers/permissionManager.js +7 -2
  19. package/dist/managers/planManager.d.ts +3 -0
  20. package/dist/managers/planManager.d.ts.map +1 -1
  21. package/dist/managers/planManager.js +9 -0
  22. package/dist/managers/skillManager.d.ts +3 -0
  23. package/dist/managers/skillManager.d.ts.map +1 -1
  24. package/dist/managers/skillManager.js +69 -54
  25. package/dist/managers/slashCommandManager.d.ts +0 -6
  26. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  27. package/dist/managers/slashCommandManager.js +0 -170
  28. package/dist/managers/toolManager.d.ts.map +1 -1
  29. package/dist/managers/toolManager.js +2 -5
  30. package/dist/prompts/planModeReminders.d.ts +0 -1
  31. package/dist/prompts/planModeReminders.d.ts.map +1 -1
  32. package/dist/prompts/planModeReminders.js +3 -12
  33. package/dist/services/MarketplaceService.d.ts.map +1 -1
  34. package/dist/services/MarketplaceService.js +12 -4
  35. package/dist/services/memory.d.ts.map +1 -1
  36. package/dist/services/memory.js +39 -5
  37. package/dist/services/pluginLoader.d.ts.map +1 -1
  38. package/dist/services/pluginLoader.js +30 -7
  39. package/dist/types/skills.d.ts +1 -0
  40. package/dist/types/skills.d.ts.map +1 -1
  41. package/dist/utils/customCommands.d.ts.map +1 -1
  42. package/dist/utils/customCommands.js +11 -9
  43. package/dist/utils/skillParser.d.ts.map +1 -1
  44. package/dist/utils/skillParser.js +3 -1
  45. package/dist/utils/subagentParser.d.ts.map +1 -1
  46. package/dist/utils/subagentParser.js +18 -7
  47. package/package.json +1 -1
  48. package/src/agent.ts +146 -1
  49. package/src/managers/MemoryRuleManager.ts +29 -14
  50. package/src/managers/aiManager.ts +28 -78
  51. package/src/managers/hookManager.ts +6 -1
  52. package/src/managers/lspManager.ts +23 -5
  53. package/src/managers/mcpManager.ts +24 -7
  54. package/src/managers/messageManager.ts +0 -5
  55. package/src/managers/permissionManager.ts +8 -1
  56. package/src/managers/planManager.ts +11 -0
  57. package/src/managers/skillManager.ts +90 -57
  58. package/src/managers/slashCommandManager.ts +0 -215
  59. package/src/managers/toolManager.ts +2 -9
  60. package/src/prompts/planModeReminders.ts +3 -15
  61. package/src/services/MarketplaceService.ts +22 -4
  62. package/src/services/memory.ts +43 -6
  63. package/src/services/pluginLoader.ts +35 -7
  64. package/src/types/skills.ts +1 -0
  65. package/src/utils/customCommands.ts +17 -12
  66. package/src/utils/skillParser.ts +3 -1
  67. package/src/utils/subagentParser.ts +22 -8
@@ -92,22 +92,40 @@ export class LspManager implements ILspManager {
92
92
  try {
93
93
  const env = { ...process.env, ...config.env };
94
94
 
95
- // For plugin servers, substitute ${WAVE_PLUGIN_ROOT} in command/args/env
95
+ // For plugin servers, substitute ${WAVE_PLUGIN_ROOT} and ${CLAUDE_PLUGIN_ROOT}
96
96
  let command = config.command;
97
97
  let args = config.args || [];
98
98
  if (config.pluginRoot) {
99
99
  env.WAVE_PLUGIN_ROOT = config.pluginRoot;
100
+ env.CLAUDE_PLUGIN_ROOT = config.pluginRoot;
100
101
  command = command.replace(/\$\{WAVE_PLUGIN_ROOT\}/g, config.pluginRoot);
101
- args = args.map((arg) =>
102
- arg.replace(/\$\{WAVE_PLUGIN_ROOT\}/g, config.pluginRoot!),
102
+ command = command.replace(
103
+ /\$\{CLAUDE_PLUGIN_ROOT\}/g,
104
+ config.pluginRoot,
103
105
  );
104
- // Also expand WAVE_PLUGIN_ROOT in user-provided env values
106
+ args = args.map((arg) => {
107
+ let result = arg.replace(
108
+ /\$\{WAVE_PLUGIN_ROOT\}/g,
109
+ config.pluginRoot!,
110
+ );
111
+ result = result.replace(
112
+ /\$\{CLAUDE_PLUGIN_ROOT\}/g,
113
+ config.pluginRoot!,
114
+ );
115
+ return result;
116
+ });
117
+ // Also expand plugin root in user-provided env values
105
118
  if (config.env) {
106
119
  for (const [key, value] of Object.entries(config.env)) {
107
- env[key] = value.replace(
120
+ let expanded = value.replace(
108
121
  /\$\{WAVE_PLUGIN_ROOT\}/g,
109
122
  config.pluginRoot!,
110
123
  );
124
+ expanded = expanded.replace(
125
+ /\$\{CLAUDE_PLUGIN_ROOT\}/g,
126
+ config.pluginRoot!,
127
+ );
128
+ env[key] = expanded;
111
129
  }
112
130
  }
113
131
  }
@@ -40,7 +40,7 @@ export interface McpManagerOptions {
40
40
  * Expand environment variables in a string value.
41
41
  * Supports ${VAR} and ${VAR:-default} patterns.
42
42
  */
43
- const WAVE_TEMPLATE_VARS = ["WAVE_PLUGIN_ROOT"];
43
+ const WAVE_TEMPLATE_VARS = ["WAVE_PLUGIN_ROOT", "CLAUDE_PLUGIN_ROOT"];
44
44
 
45
45
  export function expandEnvVars(value: string): string {
46
46
  return value.replace(/\$\{([^}]+)\}/g, (_match, expr: string) => {
@@ -440,25 +440,42 @@ export class McpManager {
440
440
  ...(server.config.env || {}),
441
441
  };
442
442
 
443
- // For plugin servers, substitute ${WAVE_PLUGIN_ROOT} in command/args/env
444
- // (same pattern as Claude Code's substitutePluginVariables)
443
+ // For plugin servers, substitute ${WAVE_PLUGIN_ROOT} and ${CLAUDE_PLUGIN_ROOT}
445
444
  let command = server.config.command;
446
445
  let args = server.config.args || [];
447
446
  if (server.config.pluginRoot) {
448
447
  env.WAVE_PLUGIN_ROOT = server.config.pluginRoot;
448
+ env.CLAUDE_PLUGIN_ROOT = server.config.pluginRoot;
449
449
  command = command.replace(
450
450
  /\$\{WAVE_PLUGIN_ROOT\}/g,
451
451
  server.config.pluginRoot,
452
452
  );
453
- args = args.map((arg) =>
454
- arg.replace(/\$\{WAVE_PLUGIN_ROOT\}/g, server.config.pluginRoot!),
453
+ command = command.replace(
454
+ /\$\{CLAUDE_PLUGIN_ROOT\}/g,
455
+ server.config.pluginRoot,
455
456
  );
456
- // Also expand WAVE_PLUGIN_ROOT in user-provided env values
457
+ args = args.map((arg) => {
458
+ let result = arg.replace(
459
+ /\$\{WAVE_PLUGIN_ROOT\}/g,
460
+ server.config.pluginRoot!,
461
+ );
462
+ result = result.replace(
463
+ /\$\{CLAUDE_PLUGIN_ROOT\}/g,
464
+ server.config.pluginRoot!,
465
+ );
466
+ return result;
467
+ });
468
+ // Also expand plugin root in user-provided env values
457
469
  for (const [key, value] of Object.entries(server.config.env || {})) {
458
- env[key] = value.replace(
470
+ let expanded = value.replace(
459
471
  /\$\{WAVE_PLUGIN_ROOT\}/g,
460
472
  server.config.pluginRoot!,
461
473
  );
474
+ expanded = expanded.replace(
475
+ /\$\{CLAUDE_PLUGIN_ROOT\}/g,
476
+ server.config.pluginRoot!,
477
+ );
478
+ env[key] = expanded;
462
479
  }
463
480
  }
464
481
  transport = new StdioClientTransport({
@@ -548,11 +548,6 @@ export class MessageManager {
548
548
  this.setSessionId(generateSessionId());
549
549
  this.parentSessionId = oldSessionId;
550
550
 
551
- // Trigger task list update if this is the main session to ensure continuity
552
- if (this.sessionType === "main") {
553
- this.callbacks.onSessionIdChange?.(this.sessionId);
554
- }
555
-
556
551
  // Set new message list
557
552
  this.setMessages(newMessages);
558
553
 
@@ -31,6 +31,7 @@ import {
31
31
  EDIT_TOOL_NAME,
32
32
  WRITE_TOOL_NAME,
33
33
  READ_TOOL_NAME,
34
+ ASK_USER_QUESTION_TOOL_NAME,
34
35
  } from "../constants/tools.js";
35
36
  import { Container } from "../utils/container.js";
36
37
  import { ConfigurationService } from "../services/configurationService.js";
@@ -440,8 +441,14 @@ export class PermissionManager {
440
441
  }
441
442
 
442
443
  // 1. If bypassPermissions mode, always allow
444
+ // Exception: tools that require user interaction (e.g. AskUserQuestion)
445
+ // must still prompt the user, matching Claude Code's requiresUserInteraction behavior.
443
446
  if (context.permissionMode === "bypassPermissions") {
444
- return { behavior: "allow" };
447
+ const requiresUserInteraction =
448
+ context.toolName === ASK_USER_QUESTION_TOOL_NAME;
449
+ if (!requiresUserInteraction) {
450
+ return { behavior: "allow" };
451
+ }
445
452
  }
446
453
 
447
454
  // 1.0 Check worktree safety for Write and Edit tools
@@ -16,6 +16,7 @@ import { logger } from "../utils/globalLogger.js";
16
16
  export class PlanManager {
17
17
  private planDir: string;
18
18
  private currentPlanFilePath: string | null = null;
19
+ private planEntryReminderPending: boolean = false;
19
20
 
20
21
  constructor(private container: Container) {
21
22
  this.planDir = path.join(os.homedir(), ".wave", "plans");
@@ -80,6 +81,7 @@ export class PlanManager {
80
81
  // Entering plan mode: clear any pending exit attachment
81
82
  // (prevents sending both plan_mode and plan_mode_exit on rapid toggle)
82
83
  permissionManager?.setNeedsPlanModeExitAttachment(false);
84
+ this.planEntryReminderPending = true;
83
85
 
84
86
  this.getOrGeneratePlanFilePath(messageManager?.getRootSessionId())
85
87
  .then(({ path }) => {
@@ -94,8 +96,17 @@ export class PlanManager {
94
96
  permissionManager?.setHasExitedPlanMode(true);
95
97
  permissionManager?.setNeedsPlanModeExitAttachment(true);
96
98
  permissionManager?.setPlanFilePath(undefined);
99
+ this.planEntryReminderPending = false;
97
100
  } else {
98
101
  permissionManager?.setPlanFilePath(undefined);
99
102
  }
100
103
  }
104
+
105
+ public isPlanEntryReminderPending(): boolean {
106
+ return this.planEntryReminderPending;
107
+ }
108
+
109
+ public consumePlanEntryReminder(): void {
110
+ this.planEntryReminderPending = false;
111
+ }
101
112
  }
@@ -29,6 +29,7 @@ import { logger } from "../utils/globalLogger.js";
29
29
  */
30
30
  export class SkillManager extends EventEmitter {
31
31
  private personalSkillsPath: string;
32
+ private personalClaudeSkillsPath: string;
32
33
  private scanTimeout: number;
33
34
  private workdir: string;
34
35
 
@@ -47,6 +48,8 @@ export class SkillManager extends EventEmitter {
47
48
  super();
48
49
  this.personalSkillsPath =
49
50
  options.personalSkillsPath || join(homedir(), ".wave", "skills");
51
+ this.personalClaudeSkillsPath =
52
+ options.personalClaudeSkillsPath || join(homedir(), ".claude", "skills");
50
53
  this.scanTimeout = options.scanTimeout || 5000;
51
54
  this.workdir = options.workdir || process.cwd();
52
55
  this.watchEnabled = options.watch ?? false;
@@ -127,7 +130,9 @@ export class SkillManager extends EventEmitter {
127
130
 
128
131
  const pathsToWatch = [
129
132
  this.personalSkillsPath,
133
+ this.personalClaudeSkillsPath,
130
134
  join(this.workdir, ".wave", "skills"),
135
+ join(this.workdir, ".claude", "skills"),
131
136
  ];
132
137
 
133
138
  logger?.debug(`Setting up skill watcher for: ${pathsToWatch.join(", ")}`);
@@ -220,6 +225,11 @@ export class SkillManager extends EventEmitter {
220
225
  "builtin",
221
226
  );
222
227
 
228
+ const personalClaudeCollection = await this.discoverSkillCollection(
229
+ this.personalClaudeSkillsPath,
230
+ "personal",
231
+ );
232
+
223
233
  const personalCollection = await this.discoverSkillCollection(
224
234
  this.personalSkillsPath,
225
235
  "personal",
@@ -232,10 +242,14 @@ export class SkillManager extends EventEmitter {
232
242
 
233
243
  return {
234
244
  builtinSkills: builtinCollection.skills,
235
- personalSkills: personalCollection.skills,
245
+ personalSkills: new Map([
246
+ ...personalClaudeCollection.skills,
247
+ ...personalCollection.skills, // .wave overrides .claude
248
+ ]),
236
249
  projectSkills: projectCollection.skills,
237
250
  errors: [
238
251
  ...builtinCollection.errors,
252
+ ...personalClaudeCollection.errors,
239
253
  ...personalCollection.errors,
240
254
  ...projectCollection.errors,
241
255
  ],
@@ -256,77 +270,88 @@ export class SkillManager extends EventEmitter {
256
270
  errors: [],
257
271
  };
258
272
 
259
- let skillsPath: string;
260
- if (type === "personal") {
261
- skillsPath = basePath;
262
- } else if (type === "builtin") {
263
- skillsPath = basePath;
273
+ if (type === "project") {
274
+ // Scan .claude/skills first, then .wave/skills (wave overrides)
275
+ const claudePath = join(basePath, ".claude", "skills");
276
+ const wavePath = join(basePath, ".wave", "skills");
277
+ await this.scanSkillPath(claudePath, collection);
278
+ await this.scanSkillPath(wavePath, collection);
264
279
  } else {
265
- skillsPath = join(basePath, ".wave", "skills");
280
+ await this.scanSkillPath(basePath, collection);
266
281
  }
267
282
 
283
+ return collection;
284
+ }
285
+
286
+ private async scanSkillPath(
287
+ skillsPath: string,
288
+ collection: SkillCollection,
289
+ ): Promise<void> {
268
290
  try {
269
291
  const skillDirs = await this.findSkillDirectories(skillsPath);
270
292
  logger?.debug(
271
293
  `Found ${skillDirs.length} potential skill directories in ${skillsPath}`,
272
294
  );
295
+ await this.processSkillDirs(skillDirs, collection);
296
+ } catch (error) {
297
+ logger?.debug(
298
+ `Could not scan ${skillsPath}: ${error instanceof Error ? error.message : String(error)}`,
299
+ );
300
+ }
301
+ }
273
302
 
274
- for (const skillDir of skillDirs) {
275
- try {
276
- const skillFilePath = join(skillDir, "SKILL.md");
303
+ private async processSkillDirs(
304
+ skillDirs: string[],
305
+ collection: SkillCollection,
306
+ ): Promise<void> {
307
+ for (const skillDir of skillDirs) {
308
+ try {
309
+ const skillFilePath = join(skillDir, "SKILL.md");
277
310
 
278
- // Check if SKILL.md exists
279
- try {
280
- await stat(skillFilePath);
281
- } catch {
282
- continue; // Skip directories without SKILL.md
283
- }
284
-
285
- const parsed = parseSkillFile(skillFilePath, {
286
- basePath: skillDir,
287
- validateMetadata: true,
288
- });
311
+ // Check if SKILL.md exists
312
+ try {
313
+ await stat(skillFilePath);
314
+ } catch {
315
+ continue; // Skip directories without SKILL.md
316
+ }
289
317
 
290
- if (parsed.isValid) {
291
- // Override the skill type with the collection type
292
- const skillMetadata: SkillMetadata = {
293
- ...parsed.skillMetadata,
294
- type,
295
- };
296
-
297
- // Create full skill object with content
298
- const skill: Skill = {
299
- ...skillMetadata,
300
- content: parsed.content,
301
- frontmatter: parsed.frontmatter,
302
- isValid: parsed.isValid,
303
- errors: parsed.validationErrors,
304
- };
305
-
306
- collection.skills.set(skillMetadata.name, skillMetadata);
307
- // Store the full skill content in the manager's skillContent map
308
- this.skillContent.set(skillMetadata.name, skill);
309
- } else {
310
- collection.errors.push({
311
- skillPath: skillDir,
312
- message: parsed.validationErrors.join("; "),
313
- });
314
- }
315
- } catch (error) {
318
+ const parsed = parseSkillFile(skillFilePath, {
319
+ basePath: skillDir,
320
+ validateMetadata: true,
321
+ });
322
+
323
+ if (parsed.isValid) {
324
+ // Override the skill type with the collection type
325
+ const skillMetadata: SkillMetadata = {
326
+ ...parsed.skillMetadata,
327
+ type: collection.type,
328
+ };
329
+
330
+ // Create full skill object with content
331
+ const skill: Skill = {
332
+ ...skillMetadata,
333
+ content: parsed.content,
334
+ frontmatter: parsed.frontmatter,
335
+ isValid: parsed.isValid,
336
+ errors: parsed.validationErrors,
337
+ };
338
+
339
+ collection.skills.set(skillMetadata.name, skillMetadata);
340
+ // Store the full skill content in the manager's skillContent map
341
+ this.skillContent.set(skillMetadata.name, skill);
342
+ } else {
316
343
  collection.errors.push({
317
344
  skillPath: skillDir,
318
- message: `Failed to process skill: ${error instanceof Error ? error.message : String(error)}`,
345
+ message: parsed.validationErrors.join("; "),
319
346
  });
320
347
  }
348
+ } catch (error) {
349
+ collection.errors.push({
350
+ skillPath: skillDir,
351
+ message: `Failed to process skill: ${error instanceof Error ? error.message : String(error)}`,
352
+ });
321
353
  }
322
- } catch (error) {
323
- logger?.debug(
324
- `Could not scan ${skillsPath}: ${error instanceof Error ? error.message : String(error)}`,
325
- );
326
- // Not an error - the directory might not exist yet
327
354
  }
328
-
329
- return collection;
330
355
  }
331
356
 
332
357
  /**
@@ -450,15 +475,23 @@ export class SkillManager extends EventEmitter {
450
475
  // 1. Substitute parameters ($1, $ARGUMENTS, etc.)
451
476
  mainContent = substituteCommandParameters(mainContent, argsString);
452
477
 
453
- // 2. Substitute ${WAVE_SKILL_DIR} with the skill's directory path
478
+ // 2. Substitute ${WAVE_SKILL_DIR} and ${CLAUDE_SKILL_DIR}
454
479
  mainContent = mainContent.replace(/\$\{WAVE_SKILL_DIR\}/g, skill.skillPath);
480
+ mainContent = mainContent.replace(
481
+ /\$\{CLAUDE_SKILL_DIR\}/g,
482
+ skill.skillPath,
483
+ );
455
484
 
456
- // 3. Substitute ${WAVE_PLUGIN_ROOT} with the skill's plugin root path
485
+ // 3. Substitute ${WAVE_PLUGIN_ROOT} and ${CLAUDE_PLUGIN_ROOT}
457
486
  if (skill.pluginRoot) {
458
487
  mainContent = mainContent.replace(
459
488
  /\$\{WAVE_PLUGIN_ROOT\}/g,
460
489
  skill.pluginRoot,
461
490
  );
491
+ mainContent = mainContent.replace(
492
+ /\$\{CLAUDE_PLUGIN_ROOT\}/g,
493
+ skill.pluginRoot,
494
+ );
462
495
  }
463
496
 
464
497
  return skillPath + mainContent;
@@ -1,7 +1,5 @@
1
1
  import type { MessageManager } from "./messageManager.js";
2
2
  import type { AIManager } from "./aiManager.js";
3
- import type { BackgroundTaskManager } from "./backgroundTaskManager.js";
4
- import type { TaskManager } from "../services/taskManager.js";
5
3
  import type { SlashCommand, CustomSlashCommand } from "../types/index.js";
6
4
  import { loadCustomSlashCommands } from "../utils/customCommands.js";
7
5
 
@@ -23,10 +21,6 @@ import {
23
21
  import type { SkillManager } from "./skillManager.js";
24
22
  import type { SkillMetadata } from "../types/skills.js";
25
23
  import type { SubagentManager } from "./subagentManager.js";
26
- import type { MemoryService } from "../services/memory.js";
27
- import type { HookManager } from "./hookManager.js";
28
- import type { GoalManager } from "./goalManager.js";
29
-
30
24
  import { logger } from "../utils/globalLogger.js";
31
25
 
32
26
  export interface SlashCommandManagerOptions {
@@ -48,7 +42,6 @@ export class SlashCommandManager {
48
42
  }
49
43
 
50
44
  public initialize(): void {
51
- this.initializeBuiltinCommands();
52
45
  this.loadCustomCommands();
53
46
 
54
47
  // Listen for skill refreshes and update skill commands
@@ -68,14 +61,6 @@ export class SlashCommandManager {
68
61
  return this.container.get<AIManager>("AIManager")!;
69
62
  }
70
63
 
71
- private get backgroundTaskManager(): BackgroundTaskManager {
72
- return this.container.get<BackgroundTaskManager>("BackgroundTaskManager")!;
73
- }
74
-
75
- private get taskManager(): TaskManager {
76
- return this.container.get<TaskManager>("TaskManager")!;
77
- }
78
-
79
64
  private get skillManager(): SkillManager {
80
65
  return this.container.get<SkillManager>("SkillManager")!;
81
66
  }
@@ -84,206 +69,6 @@ export class SlashCommandManager {
84
69
  return this.container.get<SubagentManager>("SubagentManager")!;
85
70
  }
86
71
 
87
- private get memoryService(): MemoryService {
88
- return this.container.get<MemoryService>("MemoryService")!;
89
- }
90
-
91
- private get hookManager(): HookManager | undefined {
92
- return this.container.get<HookManager>("HookManager");
93
- }
94
-
95
- private get goalManager(): GoalManager | undefined {
96
- return this.container.get<GoalManager>("GoalManager");
97
- }
98
-
99
- private initializeBuiltinCommands(): void {
100
- // Register built-in clear command
101
- this.registerCommand({
102
- id: "clear",
103
- name: "clear",
104
- description: "Clear conversation history and reset session",
105
- immediate: true,
106
- handler: async () => {
107
- this.aiManager.abortAIMessage();
108
-
109
- // Clear any active goal
110
- this.goalManager?.clearGoal();
111
-
112
- // Capture old session info before clearing
113
- const oldSessionId = this.messageManager.getSessionId();
114
- const transcriptPath = this.messageManager.getTranscriptPath();
115
-
116
- // Run SessionEnd hooks (cleanup before clear)
117
- if (this.hookManager) {
118
- try {
119
- await this.hookManager.executeSessionEndHooks(
120
- "clear",
121
- oldSessionId,
122
- transcriptPath,
123
- );
124
- } catch (error) {
125
- logger?.warn(
126
- `SessionEnd hooks on clear failed: ${(error as Error).message}`,
127
- );
128
- }
129
- }
130
-
131
- // Clear messages and generate new session
132
- this.messageManager.clearMessages();
133
- this.memoryService.clearCache();
134
- await this.taskManager.syncWithSession();
135
-
136
- // Run SessionStart hooks (restore context for new session)
137
- if (this.hookManager) {
138
- try {
139
- const newSessionId = this.messageManager.getSessionId();
140
- const sessionStartResult =
141
- await this.hookManager.executeSessionStartHooks(
142
- "clear",
143
- newSessionId,
144
- this.messageManager.getTranscriptPath(),
145
- );
146
-
147
- // Inject additionalContext as a meta user message
148
- if (sessionStartResult.additionalContext) {
149
- this.messageManager.addUserMessage({
150
- content: `<system-reminder>\nSessionStart hook additional context: ${sessionStartResult.additionalContext}\n</system-reminder>`,
151
- isMeta: true,
152
- });
153
- }
154
-
155
- // Inject initialUserMessage as a meta user message
156
- if (sessionStartResult.initialUserMessage) {
157
- this.messageManager.addUserMessage({
158
- content: sessionStartResult.initialUserMessage,
159
- isMeta: true,
160
- });
161
- }
162
- } catch (error) {
163
- logger?.warn(
164
- `SessionStart hooks on clear failed: ${(error as Error).message}`,
165
- );
166
- }
167
- }
168
- },
169
- });
170
-
171
- // Register built-in compact command
172
- this.registerCommand({
173
- id: "compact",
174
- name: "compact",
175
- description: "Compact conversation history to reduce context usage",
176
- immediate: true,
177
- handler: async (args?: string, signal?: AbortSignal) => {
178
- this.aiManager.abortAIMessage();
179
-
180
- const customInstructions = args?.trim() || undefined;
181
-
182
- await this.aiManager.compactConversation({
183
- customInstructions,
184
- abortSignal: signal,
185
- });
186
- },
187
- });
188
-
189
- // Register built-in goal command
190
- this.registerCommand({
191
- id: "goal",
192
- name: "goal",
193
- description: "Set, check, or clear an autonomous goal for the session",
194
- immediate: (args?: string) => {
195
- const trimmed = args?.trim() ?? "";
196
- return (
197
- !trimmed ||
198
- ["clear", "stop", "off", "reset", "none", "cancel"].includes(trimmed)
199
- );
200
- },
201
- handler: async (args?: string) => {
202
- const goalManager = this.goalManager;
203
- if (!goalManager) {
204
- this.messageManager.addUserMessage({
205
- content: "Goal manager is not available",
206
- isMeta: true,
207
- });
208
- return;
209
- }
210
-
211
- const trimmed = args?.trim() ?? "";
212
-
213
- // Clear aliases
214
- if (
215
- ["clear", "stop", "off", "reset", "none", "cancel"].includes(trimmed)
216
- ) {
217
- if (goalManager.isGoalActive()) {
218
- goalManager.clearGoal();
219
- this.messageManager.addUserMessage({
220
- content: "<system-reminder>Goal cleared.</system-reminder>",
221
- isMeta: true,
222
- });
223
- } else {
224
- this.messageManager.addUserMessage({
225
- content:
226
- "<system-reminder>No active goal to clear.</system-reminder>",
227
- isMeta: true,
228
- });
229
- }
230
- return;
231
- }
232
-
233
- // Show status
234
- if (!trimmed) {
235
- if (goalManager.isGoalActive()) {
236
- this.messageManager.addUserMessage({
237
- content: `<system-reminder>${goalManager.getStatusString()}</system-reminder>`,
238
- isMeta: true,
239
- });
240
- } else {
241
- this.messageManager.addUserMessage({
242
- content:
243
- "<system-reminder>No active goal. Use /goal <condition> to set one.</system-reminder>",
244
- isMeta: true,
245
- });
246
- }
247
- return;
248
- }
249
-
250
- // Check plan mode
251
- const permissionMode = this.container.has("PermissionMode")
252
- ? this.container.get<
253
- import("../types/permissions.js").PermissionMode
254
- >("PermissionMode")
255
- : undefined;
256
- if (permissionMode === "plan") {
257
- this.messageManager.addUserMessage({
258
- content:
259
- "<system-reminder>Cannot set a goal in plan mode. Exit plan mode first.</system-reminder>",
260
- isMeta: true,
261
- });
262
- return;
263
- }
264
-
265
- // Set goal
266
- try {
267
- goalManager.setGoal(trimmed);
268
- this.messageManager.addUserMessage({
269
- content: `<system-reminder>Goal set: ${trimmed}. The agent will work autonomously until this goal is achieved.</system-reminder>`,
270
- isMeta: true,
271
- });
272
- // Add the goal as a user directive to start working
273
- this.messageManager.addUserMessage({
274
- content: trimmed,
275
- });
276
- this.aiManager.sendAIMessage();
277
- } catch (error) {
278
- this.messageManager.addUserMessage({
279
- content: `<system-reminder>Failed to set goal: ${(error as Error).message}</system-reminder>`,
280
- isMeta: true,
281
- });
282
- }
283
- },
284
- });
285
- }
286
-
287
72
  /**
288
73
  * Load custom commands from filesystem
289
74
  */
@@ -364,11 +364,7 @@ class ToolManager {
364
364
  return false;
365
365
  }
366
366
  if (effectivePermissionMode === "bypassPermissions") {
367
- if (
368
- tool.name === "ExitPlanMode" ||
369
- tool.name === "AskUserQuestion" ||
370
- tool.name === "EnterPlanMode"
371
- ) {
367
+ if (tool.name === "ExitPlanMode") {
372
368
  return false;
373
369
  }
374
370
  }
@@ -376,10 +372,7 @@ class ToolManager {
376
372
  return effectivePermissionMode === "plan";
377
373
  }
378
374
  if (tool.name === "EnterPlanMode") {
379
- return (
380
- effectivePermissionMode !== "plan" &&
381
- effectivePermissionMode !== "bypassPermissions"
382
- );
375
+ return effectivePermissionMode !== "plan";
383
376
  }
384
377
  return true;
385
378
  })