wave-agent-sdk 0.14.4 → 0.15.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 (91) hide show
  1. package/builtin/skills/settings/SKILLS.md +31 -6
  2. package/dist/agent.d.ts +4 -5
  3. package/dist/agent.d.ts.map +1 -1
  4. package/dist/agent.js +10 -15
  5. package/dist/constants/toolLimits.d.ts +10 -0
  6. package/dist/constants/toolLimits.d.ts.map +1 -0
  7. package/dist/constants/toolLimits.js +9 -0
  8. package/dist/managers/aiManager.d.ts +0 -5
  9. package/dist/managers/aiManager.d.ts.map +1 -1
  10. package/dist/managers/aiManager.js +0 -22
  11. package/dist/managers/hookManager.d.ts +0 -4
  12. package/dist/managers/hookManager.d.ts.map +1 -1
  13. package/dist/managers/hookManager.js +0 -25
  14. package/dist/managers/messageManager.d.ts.map +1 -1
  15. package/dist/managers/messageManager.js +7 -6
  16. package/dist/managers/permissionManager.d.ts +1 -1
  17. package/dist/managers/permissionManager.d.ts.map +1 -1
  18. package/dist/managers/permissionManager.js +5 -5
  19. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  20. package/dist/managers/slashCommandManager.js +16 -4
  21. package/dist/managers/subagentManager.d.ts +6 -1
  22. package/dist/managers/subagentManager.d.ts.map +1 -1
  23. package/dist/managers/subagentManager.js +17 -18
  24. package/dist/prompts/index.d.ts +1 -3
  25. package/dist/prompts/index.d.ts.map +1 -1
  26. package/dist/prompts/index.js +3 -6
  27. package/dist/services/aiService.d.ts.map +1 -1
  28. package/dist/services/aiService.js +10 -8
  29. package/dist/services/hook.d.ts +0 -4
  30. package/dist/services/hook.d.ts.map +1 -1
  31. package/dist/services/hook.js +0 -10
  32. package/dist/services/jsonlHandler.d.ts +4 -4
  33. package/dist/services/jsonlHandler.d.ts.map +1 -1
  34. package/dist/services/jsonlHandler.js +4 -13
  35. package/dist/services/session.d.ts.map +1 -1
  36. package/dist/services/session.js +7 -13
  37. package/dist/tools/agentTool.d.ts.map +1 -1
  38. package/dist/tools/agentTool.js +16 -4
  39. package/dist/tools/bashTool.d.ts.map +1 -1
  40. package/dist/tools/bashTool.js +4 -50
  41. package/dist/tools/editTool.js +1 -1
  42. package/dist/tools/skillTool.d.ts.map +1 -1
  43. package/dist/tools/skillTool.js +16 -4
  44. package/dist/tools/types.d.ts +0 -3
  45. package/dist/tools/types.d.ts.map +1 -1
  46. package/dist/types/agent.d.ts +0 -1
  47. package/dist/types/agent.d.ts.map +1 -1
  48. package/dist/types/hooks.d.ts +1 -5
  49. package/dist/types/hooks.d.ts.map +1 -1
  50. package/dist/types/hooks.js +0 -1
  51. package/dist/types/messaging.d.ts +1 -0
  52. package/dist/types/messaging.d.ts.map +1 -1
  53. package/dist/types/session.d.ts +0 -4
  54. package/dist/types/session.d.ts.map +1 -1
  55. package/dist/utils/editUtils.d.ts +5 -2
  56. package/dist/utils/editUtils.d.ts.map +1 -1
  57. package/dist/utils/editUtils.js +3 -57
  58. package/dist/utils/markdownParser.d.ts +8 -1
  59. package/dist/utils/markdownParser.d.ts.map +1 -1
  60. package/dist/utils/markdownParser.js +64 -11
  61. package/dist/utils/messageOperations.d.ts.map +1 -1
  62. package/dist/utils/messageOperations.js +5 -0
  63. package/dist/utils/openaiClient.d.ts.map +1 -1
  64. package/dist/utils/openaiClient.js +0 -11
  65. package/package.json +1 -1
  66. package/src/agent.ts +12 -17
  67. package/src/constants/toolLimits.ts +12 -0
  68. package/src/managers/aiManager.ts +0 -38
  69. package/src/managers/hookManager.ts +0 -32
  70. package/src/managers/messageManager.ts +7 -8
  71. package/src/managers/permissionManager.ts +6 -6
  72. package/src/managers/slashCommandManager.ts +24 -5
  73. package/src/managers/subagentManager.ts +28 -23
  74. package/src/prompts/index.ts +3 -8
  75. package/src/services/aiService.ts +10 -12
  76. package/src/services/hook.ts +0 -15
  77. package/src/services/jsonlHandler.ts +12 -24
  78. package/src/services/session.ts +9 -15
  79. package/src/tools/agentTool.ts +24 -5
  80. package/src/tools/bashTool.ts +4 -56
  81. package/src/tools/editTool.ts +1 -1
  82. package/src/tools/skillTool.ts +24 -4
  83. package/src/tools/types.ts +0 -3
  84. package/src/types/agent.ts +0 -1
  85. package/src/types/hooks.ts +1 -7
  86. package/src/types/messaging.ts +1 -0
  87. package/src/types/session.ts +0 -8
  88. package/src/utils/editUtils.ts +3 -73
  89. package/src/utils/markdownParser.ts +85 -11
  90. package/src/utils/messageOperations.ts +5 -0
  91. package/src/utils/openaiClient.ts +0 -11
@@ -98,6 +98,5 @@ export interface AgentCallbacks
98
98
  onConfiguredModelsChange?: (models: string[]) => void;
99
99
  onLoadingChange?: (loading: boolean) => void;
100
100
  onCommandRunningChange?: (running: boolean) => void;
101
- onWorkdirChange?: (newCwd: string) => void;
102
101
  onQueuedMessagesChange?: (messages: QueuedMessage[]) => void;
103
102
  }
@@ -21,7 +21,6 @@ export type HookEvent =
21
21
  | "SubagentStop"
22
22
  | "PermissionRequest"
23
23
  | "WorktreeCreate"
24
- | "CwdChanged"
25
24
  | "SessionStart"
26
25
  | "SessionEnd";
27
26
 
@@ -110,7 +109,6 @@ export function isValidHookEvent(event: string): event is HookEvent {
110
109
  "SubagentStop",
111
110
  "PermissionRequest",
112
111
  "WorktreeCreate",
113
- "CwdChanged",
114
112
  "SessionStart",
115
113
  "SessionEnd",
116
114
  ].includes(event);
@@ -169,7 +167,7 @@ export interface HookJsonInput {
169
167
  session_id: string; // Format: "wave_session_{uuid}_{shortId}"
170
168
  transcript_path: string; // Format: "~/.wave/sessions/session_{shortId}.json"
171
169
  cwd: string; // Absolute path to current working directory
172
- hook_event_name: HookEvent; // "PreToolUse" | "PostToolUse" | "UserPromptSubmit" | "Stop" | "SubagentStop" | "PermissionRequest" | "WorktreeCreate" | "CwdChanged" | "SessionStart"
170
+ hook_event_name: HookEvent; // "PreToolUse" | "PostToolUse" | "UserPromptSubmit" | "Stop" | "SubagentStop" | "PermissionRequest" | "WorktreeCreate" | "SessionStart"
173
171
 
174
172
  // Optional fields based on event type
175
173
  tool_name?: string; // Present for PreToolUse, PostToolUse, PermissionRequest
@@ -178,8 +176,6 @@ export interface HookJsonInput {
178
176
  user_prompt?: string; // Present for UserPromptSubmit only
179
177
  subagent_type?: string; // Present when hook is executed by a subagent
180
178
  name?: string; // Present for WorktreeCreate events
181
- old_cwd?: string; // Present for CwdChanged events
182
- new_cwd?: string; // Present for CwdChanged events
183
179
  source?: SessionStartSource; // Present for SessionStart events
184
180
  agent_type?: string; // Present for SessionStart events
185
181
  end_source?: SessionEndSource; // Present for SessionEnd events
@@ -196,8 +192,6 @@ export interface ExtendedHookExecutionContext extends HookExecutionContext {
196
192
  userPrompt?: string; // User prompt text (UserPromptSubmit only)
197
193
  subagentType?: string; // Subagent type when hook is executed by a subagent
198
194
  worktreeName?: string; // Worktree name (WorktreeCreate only)
199
- oldCwd?: string; // Previous working directory (CwdChanged only)
200
- newCwd?: string; // New working directory (CwdChanged only)
201
195
  source?: SessionStartSource; // Session start source (SessionStart only)
202
196
  agentType?: string; // Agent type identifier (SessionStart only)
203
197
  endSource?: SessionEndSource; // Session end source (SessionEnd only)
@@ -14,6 +14,7 @@ export interface Message {
14
14
  id: string; // Unique identifier for the message
15
15
  role: "user" | "assistant";
16
16
  blocks: MessageBlock[];
17
+ timestamp: string; // ISO 8601 timestamp, assigned at creation
17
18
  usage?: Usage; // Usage data for this message's AI operation (assistant messages only)
18
19
  additionalFields?: Record<string, unknown>; // Additional metadata from AI responses
19
20
  isMeta?: boolean; // Whether the message is a meta message (hidden from UI)
@@ -5,14 +5,6 @@
5
5
  * SIMPLIFIED: Removed unused interfaces to focus on core functionality
6
6
  */
7
7
 
8
- import type { Message } from "./messaging.js";
9
-
10
- // Enhanced message interface for JSONL storage (extends existing Message)
11
- export interface SessionMessage extends Message {
12
- timestamp: string; // ISO 8601 - added for JSONL format
13
- // Inherits: role: "user" | "assistant", blocks: MessageBlock[], usage?, additionalFields?
14
- }
15
-
16
8
  // Session filename structure for simple filename-based metadata
17
9
  export interface SessionFilename {
18
10
  sessionId: string;
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * Utility functions for file editing tools
3
3
  */
4
- import { formatLineNumberPrefix } from "./stringUtils.js";
5
4
 
6
5
  /**
7
6
  * Escape regular expression special characters
@@ -11,77 +10,8 @@ export function escapeRegExp(string: string): string {
11
10
  }
12
11
 
13
12
  /**
14
- * Analyze why an edit failed by finding the best partial match and highlighting mismatches.
13
+ * Returns a generic error message when old_string is not found.
15
14
  */
16
- export function analyzeEditMismatch(
17
- content: string,
18
- searchString: string,
19
- ): string {
20
- const contentLines = content.split("\n");
21
- const searchLines = searchString.split("\n");
22
-
23
- if (searchLines.length === 0 || contentLines.length === 0) {
24
- return "old_string not found in file (empty search or content)";
25
- }
26
-
27
- let bestMatchIndex = -1;
28
- let bestMatchScore = -1;
29
-
30
- // Sliding window to find the best partial match
31
- for (let i = 0; i <= contentLines.length - searchLines.length; i++) {
32
- let currentScore = 0;
33
- for (let j = 0; j < searchLines.length; j++) {
34
- if (contentLines[i + j] === searchLines[j]) {
35
- currentScore++;
36
- }
37
- }
38
-
39
- // Heuristic: prioritize matches where first or last lines match
40
- if (contentLines[i] === searchLines[0]) currentScore += 0.5;
41
- if (
42
- contentLines[i + searchLines.length - 1] ===
43
- searchLines[searchLines.length - 1]
44
- )
45
- currentScore += 0.5;
46
-
47
- // Also consider trimmed matches to catch indentation issues
48
- for (let j = 0; j < searchLines.length; j++) {
49
- if (
50
- contentLines[i + j].trim() === searchLines[j].trim() &&
51
- contentLines[i + j] !== searchLines[j]
52
- ) {
53
- currentScore += 0.1;
54
- }
55
- }
56
-
57
- if (currentScore > bestMatchScore) {
58
- bestMatchScore = currentScore;
59
- bestMatchIndex = i;
60
- }
61
- }
62
-
63
- // If no decent match found (score <= 0), return generic message
64
- if (bestMatchScore <= 0) {
65
- return "old_string not found in file (no similar block found)";
66
- }
67
-
68
- // Generate detailed report
69
- const reportLines: string[] = [
70
- `old_string not found in file. Best partial match found at line ${bestMatchIndex + 1}:`,
71
- ];
72
-
73
- for (let j = 0; j < searchLines.length; j++) {
74
- const lineNum = bestMatchIndex + j + 1;
75
- const actualLine = contentLines[bestMatchIndex + j];
76
- const expectedLine = searchLines[j];
77
-
78
- if (actualLine === expectedLine) {
79
- reportLines.push(`${formatLineNumberPrefix(lineNum)}${actualLine}`);
80
- } else {
81
- reportLines.push(`${formatLineNumberPrefix(lineNum)}- ${expectedLine}`);
82
- reportLines.push(`${formatLineNumberPrefix(lineNum)}+ ${actualLine}`);
83
- }
84
- }
85
-
86
- return reportLines.join("\n");
15
+ export function analyzeEditMismatch(): string {
16
+ return "old_string not found in file";
87
17
  }
@@ -1,7 +1,13 @@
1
- import { readFileSync } from "fs";
1
+ import { readFileSync, writeFileSync, mkdirSync } from "fs";
2
2
  import { exec } from "child_process";
3
3
  import { promisify } from "util";
4
+ import { join } from "path";
5
+ import { tmpdir } from "os";
4
6
  import type { CustomSlashCommandConfig } from "../types/index.js";
7
+ import {
8
+ SKILL_BASH_MAX_OUTPUT_CHARS,
9
+ PREVIEW_SIZE_BYTES,
10
+ } from "../constants/toolLimits.js";
5
11
 
6
12
  const execAsync = promisify(exec);
7
13
 
@@ -138,17 +144,52 @@ export interface BashCommandResult {
138
144
  exitCode: number;
139
145
  }
140
146
 
147
+ /**
148
+ * Block syntax pattern: ```! command ```
149
+ */
150
+ const BLOCK_BASH_REGEX = /```!\s*\n?([\s\S]*?)\n?```/g;
151
+
152
+ /**
153
+ * Inline syntax pattern: !`command`
154
+ */
155
+ const INLINE_BASH_REGEX = /!`([^`]+)`/g;
156
+
141
157
  export function parseBashCommands(content: string): {
142
158
  commands: string[];
143
159
  processedContent: string;
144
160
  } {
145
- const bashCommandRegex = /!`([^`]+)`/g;
161
+ // Performance gate: skip expensive regex if no bash pattern exists
162
+ // Covers the common case where 93% of skills have no bash substitution
163
+ if (!content.includes("!`") && !content.includes("```!")) {
164
+ return { commands: [], processedContent: content };
165
+ }
166
+
146
167
  const commands: string[] = [];
147
- let match;
148
168
 
149
- // Extract all bash commands
150
- while ((match = bashCommandRegex.exec(content)) !== null) {
151
- commands.push(match[1]);
169
+ // Extract block commands
170
+ let blockMatch;
171
+ const blockRegex = new RegExp(
172
+ BLOCK_BASH_REGEX.source,
173
+ BLOCK_BASH_REGEX.flags,
174
+ );
175
+ while ((blockMatch = blockRegex.exec(content)) !== null) {
176
+ const cmd = blockMatch[1].trim();
177
+ if (cmd) {
178
+ commands.push(cmd);
179
+ }
180
+ }
181
+
182
+ // Extract inline commands
183
+ let inlineMatch;
184
+ const inlineRegex = new RegExp(
185
+ INLINE_BASH_REGEX.source,
186
+ INLINE_BASH_REGEX.flags,
187
+ );
188
+ while ((inlineMatch = inlineRegex.exec(content)) !== null) {
189
+ const cmd = inlineMatch[1].trim();
190
+ if (cmd) {
191
+ commands.push(cmd);
192
+ }
152
193
  }
153
194
 
154
195
  // For now, return the content as-is. The actual command execution
@@ -160,22 +201,55 @@ export function parseBashCommands(content: string): {
160
201
  }
161
202
 
162
203
  /**
163
- * Replace bash command placeholders with their outputs
204
+ * Truncate output if it exceeds the size limit.
205
+ * Writes to a temp file and returns a preview + file path if truncated.
206
+ */
207
+ export function truncateOutput(output: string): string {
208
+ if (output.length <= SKILL_BASH_MAX_OUTPUT_CHARS) {
209
+ return output;
210
+ }
211
+
212
+ const preview = output.slice(0, PREVIEW_SIZE_BYTES);
213
+ const tempDir = join(tmpdir(), "wave-skill-bash");
214
+ mkdirSync(tempDir, { recursive: true });
215
+
216
+ const tempFile = join(
217
+ tempDir,
218
+ `output-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.txt`,
219
+ );
220
+ writeFileSync(tempFile, output, "utf-8");
221
+
222
+ return `${preview}\n\n[Output truncated (${output.length} chars). Full output saved to: ${tempFile}]`;
223
+ }
224
+
225
+ /**
226
+ * Replace bash command placeholders with their outputs.
227
+ * Uses function replacer to avoid $$, $&, $' corruption in shell output.
228
+ * Handles both inline (!`cmd`) and block (```! cmd ```) syntax.
164
229
  */
165
230
  export function replaceBashCommandsWithOutput(
166
231
  content: string,
167
232
  results: BashCommandResult[],
168
233
  ): string {
169
- const bashCommandRegex = /!`([^`]+)`/g;
170
234
  let processedContent = content;
171
235
  let commandIndex = 0;
172
236
 
173
- processedContent = processedContent.replace(bashCommandRegex, (match) => {
237
+ // Replace block syntax first: ```! command ```
238
+ processedContent = processedContent.replace(BLOCK_BASH_REGEX, () => {
239
+ if (commandIndex < results.length) {
240
+ const result = results[commandIndex++];
241
+ return truncateOutput(result.output);
242
+ }
243
+ return "";
244
+ });
245
+
246
+ // Replace inline syntax: !`command`
247
+ processedContent = processedContent.replace(INLINE_BASH_REGEX, () => {
174
248
  if (commandIndex < results.length) {
175
249
  const result = results[commandIndex++];
176
- return result.output;
250
+ return truncateOutput(result.output);
177
251
  }
178
- return match;
252
+ return "";
179
253
  });
180
254
 
181
255
  return processedContent;
@@ -160,6 +160,7 @@ export const addUserMessageToMessages = ({
160
160
  id: id || generateMessageId(),
161
161
  role: "user",
162
162
  blocks,
163
+ timestamp: new Date().toISOString(),
163
164
  ...(isMeta !== undefined && { isMeta }),
164
165
  };
165
166
  return [...messages, userMessage];
@@ -231,6 +232,7 @@ export const addAssistantMessageToMessages = (
231
232
  id: generateMessageId(),
232
233
  role: "assistant",
233
234
  blocks,
235
+ timestamp: new Date().toISOString(),
234
236
  usage, // Include usage data if provided
235
237
  ...(additionalFields ? { additionalFields: { ...additionalFields } } : {}),
236
238
  };
@@ -407,6 +409,7 @@ export const addErrorBlockToMessage = ({
407
409
  content: error,
408
410
  },
409
411
  ],
412
+ timestamp: new Date().toISOString(),
410
413
  });
411
414
  }
412
415
 
@@ -430,6 +433,7 @@ export const addBangMessage = ({
430
433
  exitCode: null,
431
434
  },
432
435
  ],
436
+ timestamp: new Date().toISOString(),
433
437
  };
434
438
 
435
439
  return [...messages, outputMessage];
@@ -620,6 +624,7 @@ export const addNotificationMessageToMessages = ({
620
624
  id: generateMessageId(),
621
625
  role: "user",
622
626
  blocks: [block],
627
+ timestamp: new Date().toISOString(),
623
628
  };
624
629
 
625
630
  return [...messages, notificationMessage];
@@ -177,28 +177,17 @@ export class OpenAIClient {
177
177
  error.body = errorBody;
178
178
 
179
179
  if (response.status === 429 && attempt < maxRetries) {
180
- const responseHeaders: Record<string, string> = {};
181
- response.headers.forEach((value, key) => {
182
- responseHeaders[key] = value;
183
- });
184
180
  logger.warn("OpenAI API 429 Too Many Requests, retrying...", {
185
181
  attempt: attempt + 1,
186
182
  status: response.status,
187
- responseHeaders,
188
183
  });
189
184
  lastError = error;
190
185
  continue;
191
186
  }
192
187
 
193
- const responseHeaders: Record<string, string> = {};
194
- response.headers.forEach((value, key) => {
195
- responseHeaders[key] = value;
196
- });
197
188
  logger.error("OpenAI API Error:", {
198
189
  status: response.status,
199
190
  statusText: response.statusText,
200
- requestHeaders: headers,
201
- responseHeaders,
202
191
  errorBody,
203
192
  });
204
193
  throw error;