wave-agent-sdk 1.0.1 → 1.0.3

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.
package/dist/agent.js CHANGED
@@ -503,6 +503,13 @@ export class Agent {
503
503
  await this.messageManager.saveSession();
504
504
  }
505
505
  async clearMessages() {
506
+ // Aligned with webview (ChatApp handleClearChat): /clear is ignored
507
+ // while the agent is running, instead of aborting the AI mid-turn
508
+ // (which used to inject a late "Request was aborted" error into the
509
+ // newly cleared session).
510
+ if (this.aiManager.isLoading) {
511
+ return;
512
+ }
506
513
  this.aiManager.abortAIMessage();
507
514
  // Capture old session info before clearing
508
515
  const oldSessionId = this.messageManager.getSessionId();
@@ -546,6 +553,11 @@ export class Agent {
546
553
  * @param customInstructions - Optional custom instructions for compaction
547
554
  */
548
555
  async compact(customInstructions) {
556
+ // Aligned with webview: /compact is ignored while the agent is running,
557
+ // instead of aborting the AI mid-turn before compacting.
558
+ if (this.aiManager.isLoading) {
559
+ return;
560
+ }
549
561
  this.aiManager.abortAIMessage();
550
562
  await this.aiManager.compactConversation({
551
563
  customInstructions,
@@ -1606,6 +1606,8 @@ ${question}`;
1606
1606
  id: toolId,
1607
1607
  shortResult,
1608
1608
  stage: "running",
1609
+ compactParams,
1610
+ name: toolName,
1609
1611
  });
1610
1612
  },
1611
1613
  onResultUpdate: (result) => {
@@ -1613,6 +1615,8 @@ ${question}`;
1613
1615
  id: toolId,
1614
1616
  result,
1615
1617
  stage: "running",
1618
+ compactParams,
1619
+ name: toolName,
1616
1620
  });
1617
1621
  },
1618
1622
  onCwdChange: async (newCwd) => {
@@ -310,16 +310,8 @@ export class PermissionManager {
310
310
  };
311
311
  }
312
312
  }
313
- // 1. If bypassPermissions mode, always allow
314
- // Exception: tools that require user interaction (e.g. AskUserQuestion)
315
- // must still prompt the user, matching Claude Code's requiresUserInteraction behavior.
316
- if (context.permissionMode === "bypassPermissions") {
317
- const requiresUserInteraction = context.toolName === ASK_USER_QUESTION_TOOL_NAME;
318
- if (!requiresUserInteraction) {
319
- return { behavior: "allow" };
320
- }
321
- }
322
- // 1.0 Check worktree safety for Write and Edit tools
313
+ // Check worktree safety for Write and Edit tools — unconditional safety
314
+ // check, applied regardless of permission mode (same as read-before-edit).
323
315
  // Support both CLI -w sessions (container-registered) and EnterWorktree mid-session
324
316
  // (per-agent WorktreeSession stored in this session's container)
325
317
  const worktreeSession = this.container.get("WorktreeSession");
@@ -371,7 +363,17 @@ export class PermissionManager {
371
363
  }
372
364
  }
373
365
  }
374
- // 1.1 If acceptEdits mode, allow Edit, Write, and mkdir in safe zone
366
+ // If bypassPermissions mode, always allow
367
+ // Exception: tools that require user interaction (e.g. AskUserQuestion)
368
+ // must still prompt the user, matching Claude Code's requiresUserInteraction behavior.
369
+ // Worktree safety check above runs unconditionally, so bypass never skips it.
370
+ if (context.permissionMode === "bypassPermissions") {
371
+ const requiresUserInteraction = context.toolName === ASK_USER_QUESTION_TOOL_NAME;
372
+ if (!requiresUserInteraction) {
373
+ return { behavior: "allow" };
374
+ }
375
+ }
376
+ // If acceptEdits mode, allow Edit, Write, and mkdir in safe zone
375
377
  if (context.permissionMode === "acceptEdits") {
376
378
  const autoAcceptedTools = [EDIT_TOOL_NAME, WRITE_TOOL_NAME];
377
379
  if (autoAcceptedTools.includes(context.toolName)) {
@@ -45,4 +45,3 @@ export declare function buildSystemPrompt(basePrompt: string | undefined, tools:
45
45
  content: string;
46
46
  };
47
47
  }): SystemPromptBlock[];
48
- export declare function enhanceSystemPromptWithEnvDetails(existingSystemPrompt: string, workdir: string, originalWorkdir?: string, worktreeSession?: WorktreeSession | null): string;
@@ -285,6 +285,43 @@ export function formatCompactSummary(summary) {
285
285
  return formattedSummary.trim();
286
286
  }
287
287
  export const WEB_CONTENT_SYSTEM_PROMPT = `You are a helpful assistant that extracts information from web content. The content is provided in Markdown format.`;
288
+ /**
289
+ * Notes block prepended to the subagent env section, aligned with Claude
290
+ * Code's enhanceSystemPromptWithEnvDetails().
291
+ */
292
+ const SUBAGENT_ENV_NOTES = `Notes:
293
+ - Agent threads always have their cwd reset between bash calls, as a result please only use absolute file paths.
294
+ - In your final response, share file paths (always absolute, never relative) that are relevant to the task. Include code snippets only when the exact text is load-bearing (e.g., a bug you found, a function signature the caller asked for) — do not recap code you merely read.
295
+ - For clear communication with the user the assistant MUST avoid using emojis.
296
+ - Do not use a colon before tool calls. Text like "Let me read the file:" followed by a read tool call should just be "Let me read the file." with a period.`;
297
+ /**
298
+ * Shell info line, aligned with Claude Code's getShellInfoLine(). On win32 an
299
+ * extra Unix-syntax hint is appended.
300
+ */
301
+ function getShellInfoLine() {
302
+ const shell = process.env.SHELL || "unknown";
303
+ const shellName = shell.includes("zsh")
304
+ ? "zsh"
305
+ : shell.includes("bash")
306
+ ? "bash"
307
+ : shell;
308
+ if (os.platform() === "win32") {
309
+ return `Shell: ${shellName} (use Unix shell syntax, not Windows — e.g., /dev/null not NUL, forward slashes in paths)`;
310
+ }
311
+ return `Shell: ${shellName}`;
312
+ }
313
+ /**
314
+ * OS Version value, aligned with Claude Code's getUnameSR(). os.type() and
315
+ * os.release() wrap uname(3) on POSIX, producing output byte-identical to
316
+ * `uname -sr`. Windows has no uname(3); os.type() returns "Windows_NT" there,
317
+ * but os.version() gives the friendlier "Windows 11 Pro", so use that instead.
318
+ */
319
+ function getUnameSR() {
320
+ if (os.platform() === "win32") {
321
+ return `${os.version()} ${os.release()}`;
322
+ }
323
+ return `${os.type()} ${os.release()}`;
324
+ }
288
325
  export function buildSystemPrompt(basePrompt, tools, options = {}) {
289
326
  // --- Static block (cacheable) ---
290
327
  let staticText = basePrompt || DEFAULT_SYSTEM_PROMPT;
@@ -305,27 +342,49 @@ export function buildSystemPrompt(basePrompt, tools, options = {}) {
305
342
  if (options.workdir) {
306
343
  const isGitRepo = isGitRepository(options.workdir);
307
344
  const platform = os.platform();
308
- const osVersion = `${os.type()} ${os.release()}`;
309
- const today = new Date().toISOString().split("T")[0];
310
- const shell = process.env.SHELL || "unknown";
311
- const shellName = shell.includes("zsh")
312
- ? "zsh"
313
- : shell.includes("bash")
314
- ? "bash"
315
- : shell;
345
+ const shellInfo = getShellInfoLine();
346
+ const osVersion = getUnameSR();
347
+ const primaryWorkdir = options.originalWorkdir ?? options.workdir;
316
348
  const worktreeSession = options.worktreeSession;
317
- dynamicText += `
349
+ if (options.isSubagent) {
350
+ // Subagent env section, aligned with Claude Code's computeEnvInfo() +
351
+ // enhanceSystemPromptWithEnvDetails() (without the model description and
352
+ // knowledge cutoff lines, which Wave does not use).
353
+ dynamicText += `
354
+
355
+ ${SUBAGENT_ENV_NOTES}
318
356
 
319
357
  Here is useful information about the environment you are running in:
320
358
  <env>
321
- Primary working directory: ${options.originalWorkdir ?? options.workdir}${worktreeSession ? `\nThis is a git worktree — an isolated copy of the repository. Run all commands from this directory. Do NOT \`cd\` to the original repository root at ${worktreeSession.originalCwd}.` : ""}
359
+ Working directory: ${primaryWorkdir}
322
360
  Is directory a git repo: ${isGitRepo}
323
361
  Platform: ${platform}
324
- Shell: ${shellName}
362
+ ${shellInfo}
325
363
  OS Version: ${osVersion}
326
- Today's date: ${today}
327
364
  </env>
328
365
  `;
366
+ }
367
+ else {
368
+ // Main agent env section, aligned with Claude Code's
369
+ // computeSimpleEnvInfo() (without the model description, knowledge
370
+ // cutoff, and marketing lines, which Wave does not use).
371
+ const envItems = [
372
+ `Primary working directory: ${primaryWorkdir}`,
373
+ worktreeSession
374
+ ? `This is a git worktree — an isolated copy of the repository. Run all commands from this directory. Do NOT \`cd\` to the original repository root.`
375
+ : null,
376
+ `Is a git repository: ${isGitRepo}`,
377
+ `Platform: ${platform}`,
378
+ shellInfo,
379
+ `OS Version: ${osVersion}`,
380
+ ].filter((item) => item !== null);
381
+ const envBlock = [
382
+ `# Environment`,
383
+ `You have been invoked in the following environment: `,
384
+ ...envItems.map((item) => ` - ${item}`),
385
+ ].join("\n");
386
+ dynamicText += `\n\n${envBlock}`;
387
+ }
329
388
  }
330
389
  if (options.autoMemory) {
331
390
  dynamicText += `\n\n${buildAutoMemoryPrompt(options.autoMemory.directory)}`;
@@ -338,34 +397,3 @@ Today's date: ${today}
338
397
  }
339
398
  return blocks;
340
399
  }
341
- export function enhanceSystemPromptWithEnvDetails(existingSystemPrompt, workdir, originalWorkdir, worktreeSession) {
342
- const isGitRepo = isGitRepository(workdir);
343
- const platform = os.platform();
344
- const osVersion = `${os.type()} ${os.release()}`;
345
- const today = new Date().toISOString().split("T")[0];
346
- const shell = process.env.SHELL || "unknown";
347
- const shellName = shell.includes("zsh")
348
- ? "zsh"
349
- : shell.includes("bash")
350
- ? "bash"
351
- : shell;
352
- const notes = `Notes:
353
- - Agent threads always have their cwd reset between bash calls, as a result please only use absolute file paths.${worktreeSession ? `\n- You are in a git worktree at ${worktreeSession.worktreePath} (branch: ${worktreeSession.worktreeBranch}). Absolute paths from prior context may refer to the original repo at ${worktreeSession.originalCwd}; translate them to your worktree. Do NOT edit files outside this worktree.` : ""}
354
- - In your final response, share file paths (always absolute, never relative) that are relevant to the task. Include code snippets only when the exact text is load-bearing (e.g., a bug you found, a function signature the caller asked for) — do not recap code you merely read.
355
- - For clear communication with the user the assistant MUST avoid using emojis.
356
- - Do not use a colon before tool calls. Text like "Let me read the file:" followed by a read tool call should just be "Let me read the file." with a period.`;
357
- return `${existingSystemPrompt}
358
-
359
- ${notes}
360
-
361
- Here is useful information about the environment you are running in:
362
- <env>
363
- Primary working directory: ${originalWorkdir ?? workdir}${worktreeSession ? `\nThis is a git worktree — an isolated copy of the repository. Run all commands from this directory. Do NOT \`cd\` to the original repository root at ${worktreeSession.originalCwd}.` : ""}
364
- Is directory a git repo: ${isGitRepo}
365
- Platform: ${platform}
366
- Shell: ${shellName}
367
- OS Version: ${osVersion}
368
- Today's date: ${today}
369
- </env>
370
- `;
371
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-agent-sdk",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "SDK for building AI-powered development tools and agents",
5
5
  "keywords": [
6
6
  "ai",
package/src/agent.ts CHANGED
@@ -681,6 +681,14 @@ export class Agent {
681
681
  }
682
682
 
683
683
  public async clearMessages(): Promise<void> {
684
+ // Aligned with webview (ChatApp handleClearChat): /clear is ignored
685
+ // while the agent is running, instead of aborting the AI mid-turn
686
+ // (which used to inject a late "Request was aborted" error into the
687
+ // newly cleared session).
688
+ if (this.aiManager.isLoading) {
689
+ return;
690
+ }
691
+
684
692
  this.aiManager.abortAIMessage();
685
693
 
686
694
  // Capture old session info before clearing
@@ -743,6 +751,12 @@ export class Agent {
743
751
  * @param customInstructions - Optional custom instructions for compaction
744
752
  */
745
753
  public async compact(customInstructions?: string): Promise<void> {
754
+ // Aligned with webview: /compact is ignored while the agent is running,
755
+ // instead of aborting the AI mid-turn before compacting.
756
+ if (this.aiManager.isLoading) {
757
+ return;
758
+ }
759
+
746
760
  this.aiManager.abortAIMessage();
747
761
 
748
762
  await this.aiManager.compactConversation({
@@ -2195,6 +2195,8 @@ ${question}`;
2195
2195
  id: toolId,
2196
2196
  shortResult,
2197
2197
  stage: "running",
2198
+ compactParams,
2199
+ name: toolName,
2198
2200
  });
2199
2201
  },
2200
2202
  onResultUpdate: (result: string) => {
@@ -2202,6 +2204,8 @@ ${question}`;
2202
2204
  id: toolId,
2203
2205
  result,
2204
2206
  stage: "running",
2207
+ compactParams,
2208
+ name: toolName,
2205
2209
  });
2206
2210
  },
2207
2211
  onCwdChange: async (newCwd: string) => {
@@ -427,18 +427,8 @@ export class PermissionManager {
427
427
  }
428
428
  }
429
429
 
430
- // 1. If bypassPermissions mode, always allow
431
- // Exception: tools that require user interaction (e.g. AskUserQuestion)
432
- // must still prompt the user, matching Claude Code's requiresUserInteraction behavior.
433
- if (context.permissionMode === "bypassPermissions") {
434
- const requiresUserInteraction =
435
- context.toolName === ASK_USER_QUESTION_TOOL_NAME;
436
- if (!requiresUserInteraction) {
437
- return { behavior: "allow" };
438
- }
439
- }
440
-
441
- // 1.0 Check worktree safety for Write and Edit tools
430
+ // Check worktree safety for Write and Edit tools — unconditional safety
431
+ // check, applied regardless of permission mode (same as read-before-edit).
442
432
  // Support both CLI -w sessions (container-registered) and EnterWorktree mid-session
443
433
  // (per-agent WorktreeSession stored in this session's container)
444
434
  const worktreeSession = this.container.get<WorktreeSession | null>(
@@ -503,7 +493,19 @@ export class PermissionManager {
503
493
  }
504
494
  }
505
495
 
506
- // 1.1 If acceptEdits mode, allow Edit, Write, and mkdir in safe zone
496
+ // If bypassPermissions mode, always allow
497
+ // Exception: tools that require user interaction (e.g. AskUserQuestion)
498
+ // must still prompt the user, matching Claude Code's requiresUserInteraction behavior.
499
+ // Worktree safety check above runs unconditionally, so bypass never skips it.
500
+ if (context.permissionMode === "bypassPermissions") {
501
+ const requiresUserInteraction =
502
+ context.toolName === ASK_USER_QUESTION_TOOL_NAME;
503
+ if (!requiresUserInteraction) {
504
+ return { behavior: "allow" };
505
+ }
506
+ }
507
+
508
+ // If acceptEdits mode, allow Edit, Write, and mkdir in safe zone
507
509
  if (context.permissionMode === "acceptEdits") {
508
510
  const autoAcceptedTools = [EDIT_TOOL_NAME, WRITE_TOOL_NAME];
509
511
  if (autoAcceptedTools.includes(context.toolName)) {
@@ -347,6 +347,46 @@ export function formatCompactSummary(summary: string): string {
347
347
 
348
348
  export const WEB_CONTENT_SYSTEM_PROMPT = `You are a helpful assistant that extracts information from web content. The content is provided in Markdown format.`;
349
349
 
350
+ /**
351
+ * Notes block prepended to the subagent env section, aligned with Claude
352
+ * Code's enhanceSystemPromptWithEnvDetails().
353
+ */
354
+ const SUBAGENT_ENV_NOTES = `Notes:
355
+ - Agent threads always have their cwd reset between bash calls, as a result please only use absolute file paths.
356
+ - In your final response, share file paths (always absolute, never relative) that are relevant to the task. Include code snippets only when the exact text is load-bearing (e.g., a bug you found, a function signature the caller asked for) — do not recap code you merely read.
357
+ - For clear communication with the user the assistant MUST avoid using emojis.
358
+ - Do not use a colon before tool calls. Text like "Let me read the file:" followed by a read tool call should just be "Let me read the file." with a period.`;
359
+
360
+ /**
361
+ * Shell info line, aligned with Claude Code's getShellInfoLine(). On win32 an
362
+ * extra Unix-syntax hint is appended.
363
+ */
364
+ function getShellInfoLine(): string {
365
+ const shell = process.env.SHELL || "unknown";
366
+ const shellName = shell.includes("zsh")
367
+ ? "zsh"
368
+ : shell.includes("bash")
369
+ ? "bash"
370
+ : shell;
371
+ if (os.platform() === "win32") {
372
+ return `Shell: ${shellName} (use Unix shell syntax, not Windows — e.g., /dev/null not NUL, forward slashes in paths)`;
373
+ }
374
+ return `Shell: ${shellName}`;
375
+ }
376
+
377
+ /**
378
+ * OS Version value, aligned with Claude Code's getUnameSR(). os.type() and
379
+ * os.release() wrap uname(3) on POSIX, producing output byte-identical to
380
+ * `uname -sr`. Windows has no uname(3); os.type() returns "Windows_NT" there,
381
+ * but os.version() gives the friendlier "Windows 11 Pro", so use that instead.
382
+ */
383
+ function getUnameSR(): string {
384
+ if (os.platform() === "win32") {
385
+ return `${os.version()} ${os.release()}`;
386
+ }
387
+ return `${os.type()} ${os.release()}`;
388
+ }
389
+
350
390
  export function buildSystemPrompt(
351
391
  basePrompt: string | undefined,
352
392
  tools: ToolPlugin[],
@@ -387,29 +427,51 @@ export function buildSystemPrompt(
387
427
  if (options.workdir) {
388
428
  const isGitRepo = isGitRepository(options.workdir);
389
429
  const platform = os.platform();
390
- const osVersion = `${os.type()} ${os.release()}`;
391
- const today = new Date().toISOString().split("T")[0];
392
- const shell = process.env.SHELL || "unknown";
393
- const shellName = shell.includes("zsh")
394
- ? "zsh"
395
- : shell.includes("bash")
396
- ? "bash"
397
- : shell;
398
-
430
+ const shellInfo = getShellInfoLine();
431
+ const osVersion = getUnameSR();
432
+ const primaryWorkdir = options.originalWorkdir ?? options.workdir;
399
433
  const worktreeSession = options.worktreeSession;
400
434
 
401
- dynamicText += `
435
+ if (options.isSubagent) {
436
+ // Subagent env section, aligned with Claude Code's computeEnvInfo() +
437
+ // enhanceSystemPromptWithEnvDetails() (without the model description and
438
+ // knowledge cutoff lines, which Wave does not use).
439
+ dynamicText += `
440
+
441
+ ${SUBAGENT_ENV_NOTES}
402
442
 
403
443
  Here is useful information about the environment you are running in:
404
444
  <env>
405
- Primary working directory: ${options.originalWorkdir ?? options.workdir}${worktreeSession ? `\nThis is a git worktree — an isolated copy of the repository. Run all commands from this directory. Do NOT \`cd\` to the original repository root at ${worktreeSession.originalCwd}.` : ""}
445
+ Working directory: ${primaryWorkdir}
406
446
  Is directory a git repo: ${isGitRepo}
407
447
  Platform: ${platform}
408
- Shell: ${shellName}
448
+ ${shellInfo}
409
449
  OS Version: ${osVersion}
410
- Today's date: ${today}
411
450
  </env>
412
451
  `;
452
+ } else {
453
+ // Main agent env section, aligned with Claude Code's
454
+ // computeSimpleEnvInfo() (without the model description, knowledge
455
+ // cutoff, and marketing lines, which Wave does not use).
456
+ const envItems = [
457
+ `Primary working directory: ${primaryWorkdir}`,
458
+ worktreeSession
459
+ ? `This is a git worktree — an isolated copy of the repository. Run all commands from this directory. Do NOT \`cd\` to the original repository root.`
460
+ : null,
461
+ `Is a git repository: ${isGitRepo}`,
462
+ `Platform: ${platform}`,
463
+ shellInfo,
464
+ `OS Version: ${osVersion}`,
465
+ ].filter((item): item is string => item !== null);
466
+
467
+ const envBlock = [
468
+ `# Environment`,
469
+ `You have been invoked in the following environment: `,
470
+ ...envItems.map((item) => ` - ${item}`),
471
+ ].join("\n");
472
+
473
+ dynamicText += `\n\n${envBlock}`;
474
+ }
413
475
  }
414
476
 
415
477
  if (options.autoMemory) {
@@ -425,42 +487,3 @@ Today's date: ${today}
425
487
 
426
488
  return blocks;
427
489
  }
428
-
429
- export function enhanceSystemPromptWithEnvDetails(
430
- existingSystemPrompt: string,
431
- workdir: string,
432
- originalWorkdir?: string,
433
- worktreeSession?: WorktreeSession | null,
434
- ): string {
435
- const isGitRepo = isGitRepository(workdir);
436
- const platform = os.platform();
437
- const osVersion = `${os.type()} ${os.release()}`;
438
- const today = new Date().toISOString().split("T")[0];
439
- const shell = process.env.SHELL || "unknown";
440
- const shellName = shell.includes("zsh")
441
- ? "zsh"
442
- : shell.includes("bash")
443
- ? "bash"
444
- : shell;
445
-
446
- const notes = `Notes:
447
- - Agent threads always have their cwd reset between bash calls, as a result please only use absolute file paths.${worktreeSession ? `\n- You are in a git worktree at ${worktreeSession.worktreePath} (branch: ${worktreeSession.worktreeBranch}). Absolute paths from prior context may refer to the original repo at ${worktreeSession.originalCwd}; translate them to your worktree. Do NOT edit files outside this worktree.` : ""}
448
- - In your final response, share file paths (always absolute, never relative) that are relevant to the task. Include code snippets only when the exact text is load-bearing (e.g., a bug you found, a function signature the caller asked for) — do not recap code you merely read.
449
- - For clear communication with the user the assistant MUST avoid using emojis.
450
- - Do not use a colon before tool calls. Text like "Let me read the file:" followed by a read tool call should just be "Let me read the file." with a period.`;
451
-
452
- return `${existingSystemPrompt}
453
-
454
- ${notes}
455
-
456
- Here is useful information about the environment you are running in:
457
- <env>
458
- Primary working directory: ${originalWorkdir ?? workdir}${worktreeSession ? `\nThis is a git worktree — an isolated copy of the repository. Run all commands from this directory. Do NOT \`cd\` to the original repository root at ${worktreeSession.originalCwd}.` : ""}
459
- Is directory a git repo: ${isGitRepo}
460
- Platform: ${platform}
461
- Shell: ${shellName}
462
- OS Version: ${osVersion}
463
- Today's date: ${today}
464
- </env>
465
- `;
466
- }