wave-agent-sdk 0.0.7 → 0.0.8

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 (172) hide show
  1. package/dist/agent.d.ts +32 -20
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +202 -20
  4. package/dist/constants/events.d.ts +28 -0
  5. package/dist/constants/events.d.ts.map +1 -0
  6. package/dist/constants/events.js +27 -0
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +2 -0
  10. package/dist/managers/aiManager.d.ts +34 -1
  11. package/dist/managers/aiManager.d.ts.map +1 -1
  12. package/dist/managers/aiManager.js +243 -128
  13. package/dist/managers/backgroundBashManager.d.ts.map +1 -1
  14. package/dist/managers/backgroundBashManager.js +7 -6
  15. package/dist/managers/hookManager.d.ts +9 -4
  16. package/dist/managers/hookManager.d.ts.map +1 -1
  17. package/dist/managers/hookManager.js +62 -30
  18. package/dist/managers/liveConfigManager.d.ts +58 -0
  19. package/dist/managers/liveConfigManager.d.ts.map +1 -0
  20. package/dist/managers/liveConfigManager.js +160 -0
  21. package/dist/managers/messageManager.d.ts +38 -13
  22. package/dist/managers/messageManager.d.ts.map +1 -1
  23. package/dist/managers/messageManager.js +163 -30
  24. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  25. package/dist/managers/slashCommandManager.js +4 -1
  26. package/dist/managers/subagentManager.d.ts +51 -0
  27. package/dist/managers/subagentManager.d.ts.map +1 -1
  28. package/dist/managers/subagentManager.js +189 -18
  29. package/dist/services/aiService.d.ts +13 -5
  30. package/dist/services/aiService.d.ts.map +1 -1
  31. package/dist/services/aiService.js +350 -74
  32. package/dist/services/configurationWatcher.d.ts +120 -0
  33. package/dist/services/configurationWatcher.d.ts.map +1 -0
  34. package/dist/services/configurationWatcher.js +439 -0
  35. package/dist/services/fileWatcher.d.ts +69 -0
  36. package/dist/services/fileWatcher.d.ts.map +1 -0
  37. package/dist/services/fileWatcher.js +213 -0
  38. package/dist/services/hook.d.ts +91 -9
  39. package/dist/services/hook.d.ts.map +1 -1
  40. package/dist/services/hook.js +393 -43
  41. package/dist/services/jsonlHandler.d.ts +62 -0
  42. package/dist/services/jsonlHandler.d.ts.map +1 -0
  43. package/dist/services/jsonlHandler.js +257 -0
  44. package/dist/services/memory.d.ts +9 -0
  45. package/dist/services/memory.d.ts.map +1 -1
  46. package/dist/services/memory.js +81 -12
  47. package/dist/services/memoryStore.d.ts +81 -0
  48. package/dist/services/memoryStore.d.ts.map +1 -0
  49. package/dist/services/memoryStore.js +200 -0
  50. package/dist/services/session.d.ts +64 -49
  51. package/dist/services/session.d.ts.map +1 -1
  52. package/dist/services/session.js +310 -132
  53. package/dist/tools/bashTool.d.ts.map +1 -1
  54. package/dist/tools/bashTool.js +5 -4
  55. package/dist/tools/deleteFileTool.d.ts.map +1 -1
  56. package/dist/tools/deleteFileTool.js +2 -1
  57. package/dist/tools/editTool.d.ts.map +1 -1
  58. package/dist/tools/editTool.js +3 -2
  59. package/dist/tools/multiEditTool.d.ts.map +1 -1
  60. package/dist/tools/multiEditTool.js +4 -3
  61. package/dist/tools/readTool.d.ts.map +1 -1
  62. package/dist/tools/readTool.js +2 -1
  63. package/dist/tools/writeTool.d.ts.map +1 -1
  64. package/dist/tools/writeTool.js +5 -6
  65. package/dist/types/commands.d.ts +4 -0
  66. package/dist/types/commands.d.ts.map +1 -1
  67. package/dist/types/core.d.ts +35 -0
  68. package/dist/types/core.d.ts.map +1 -1
  69. package/dist/types/environment.d.ts +42 -0
  70. package/dist/types/environment.d.ts.map +1 -0
  71. package/dist/types/environment.js +21 -0
  72. package/dist/types/hooks.d.ts +8 -2
  73. package/dist/types/hooks.d.ts.map +1 -1
  74. package/dist/types/hooks.js +8 -2
  75. package/dist/types/index.d.ts +2 -0
  76. package/dist/types/index.d.ts.map +1 -1
  77. package/dist/types/index.js +2 -0
  78. package/dist/types/memoryStore.d.ts +82 -0
  79. package/dist/types/memoryStore.d.ts.map +1 -0
  80. package/dist/types/memoryStore.js +7 -0
  81. package/dist/types/messaging.d.ts +14 -2
  82. package/dist/types/messaging.d.ts.map +1 -1
  83. package/dist/types/session.d.ts +20 -0
  84. package/dist/types/session.d.ts.map +1 -0
  85. package/dist/types/session.js +7 -0
  86. package/dist/utils/bashHistory.d.ts.map +1 -1
  87. package/dist/utils/bashHistory.js +27 -26
  88. package/dist/utils/cacheControlUtils.d.ts +121 -0
  89. package/dist/utils/cacheControlUtils.d.ts.map +1 -0
  90. package/dist/utils/cacheControlUtils.js +367 -0
  91. package/dist/utils/commandPathResolver.d.ts +52 -0
  92. package/dist/utils/commandPathResolver.d.ts.map +1 -0
  93. package/dist/utils/commandPathResolver.js +145 -0
  94. package/dist/utils/configPaths.d.ts +85 -0
  95. package/dist/utils/configPaths.d.ts.map +1 -0
  96. package/dist/utils/configPaths.js +121 -0
  97. package/dist/utils/configResolver.d.ts +37 -10
  98. package/dist/utils/configResolver.d.ts.map +1 -1
  99. package/dist/utils/configResolver.js +127 -23
  100. package/dist/utils/constants.d.ts +1 -1
  101. package/dist/utils/constants.js +1 -1
  102. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  103. package/dist/utils/convertMessagesForAPI.js +7 -5
  104. package/dist/utils/customCommands.d.ts.map +1 -1
  105. package/dist/utils/customCommands.js +66 -21
  106. package/dist/utils/fileUtils.d.ts +15 -0
  107. package/dist/utils/fileUtils.d.ts.map +1 -0
  108. package/dist/utils/fileUtils.js +61 -0
  109. package/dist/utils/globalLogger.d.ts +102 -0
  110. package/dist/utils/globalLogger.d.ts.map +1 -0
  111. package/dist/utils/globalLogger.js +136 -0
  112. package/dist/utils/mcpUtils.d.ts.map +1 -1
  113. package/dist/utils/mcpUtils.js +25 -3
  114. package/dist/utils/messageOperations.d.ts +20 -8
  115. package/dist/utils/messageOperations.d.ts.map +1 -1
  116. package/dist/utils/messageOperations.js +25 -16
  117. package/dist/utils/pathEncoder.d.ts +104 -0
  118. package/dist/utils/pathEncoder.d.ts.map +1 -0
  119. package/dist/utils/pathEncoder.js +272 -0
  120. package/dist/utils/subagentParser.d.ts.map +1 -1
  121. package/dist/utils/subagentParser.js +2 -1
  122. package/dist/utils/tokenCalculation.d.ts +26 -0
  123. package/dist/utils/tokenCalculation.d.ts.map +1 -0
  124. package/dist/utils/tokenCalculation.js +36 -0
  125. package/package.json +6 -3
  126. package/src/agent.ts +298 -34
  127. package/src/constants/events.ts +38 -0
  128. package/src/index.ts +2 -0
  129. package/src/managers/aiManager.ts +323 -170
  130. package/src/managers/backgroundBashManager.ts +7 -6
  131. package/src/managers/hookManager.ts +83 -40
  132. package/src/managers/liveConfigManager.ts +248 -0
  133. package/src/managers/messageManager.ts +230 -63
  134. package/src/managers/slashCommandManager.ts +4 -1
  135. package/src/managers/subagentManager.ts +283 -21
  136. package/src/services/aiService.ts +474 -83
  137. package/src/services/configurationWatcher.ts +622 -0
  138. package/src/services/fileWatcher.ts +301 -0
  139. package/src/services/hook.ts +538 -47
  140. package/src/services/jsonlHandler.ts +319 -0
  141. package/src/services/memory.ts +92 -12
  142. package/src/services/memoryStore.ts +279 -0
  143. package/src/services/session.ts +381 -157
  144. package/src/tools/bashTool.ts +5 -4
  145. package/src/tools/deleteFileTool.ts +2 -1
  146. package/src/tools/editTool.ts +3 -2
  147. package/src/tools/multiEditTool.ts +4 -3
  148. package/src/tools/readTool.ts +2 -1
  149. package/src/tools/writeTool.ts +7 -6
  150. package/src/types/commands.ts +6 -0
  151. package/src/types/core.ts +44 -0
  152. package/src/types/environment.ts +60 -0
  153. package/src/types/hooks.ts +21 -8
  154. package/src/types/index.ts +2 -0
  155. package/src/types/memoryStore.ts +94 -0
  156. package/src/types/messaging.ts +14 -2
  157. package/src/types/session.ts +25 -0
  158. package/src/utils/bashHistory.ts +27 -27
  159. package/src/utils/cacheControlUtils.ts +540 -0
  160. package/src/utils/commandPathResolver.ts +189 -0
  161. package/src/utils/configPaths.ts +163 -0
  162. package/src/utils/configResolver.ts +182 -22
  163. package/src/utils/constants.ts +1 -1
  164. package/src/utils/convertMessagesForAPI.ts +7 -5
  165. package/src/utils/customCommands.ts +90 -22
  166. package/src/utils/fileUtils.ts +65 -0
  167. package/src/utils/globalLogger.ts +145 -0
  168. package/src/utils/mcpUtils.ts +34 -3
  169. package/src/utils/messageOperations.ts +42 -20
  170. package/src/utils/pathEncoder.ts +379 -0
  171. package/src/utils/subagentParser.ts +2 -1
  172. package/src/utils/tokenCalculation.ts +43 -0
@@ -1,14 +1,17 @@
1
+ import { MessageSource } from "../types/index.js";
1
2
  import { readFileSync } from "fs";
2
3
  import { extname } from "path";
4
+ import { logger } from "./globalLogger.js";
3
5
  /**
4
6
  * Extract text content from user messages in the messages array
7
+ * Excludes messages with source HOOK to prevent hook-generated content from entering user history
5
8
  */
6
9
  export const extractUserInputHistory = (messages) => {
7
10
  return messages
8
11
  .filter((message) => message.role === "user")
9
12
  .map((message) => {
10
- // Extract all text block content and merge
11
- const textBlocks = message.blocks.filter((block) => block.type === "text");
13
+ // Extract text block content, excluding HOOK-sourced blocks
14
+ const textBlocks = message.blocks.filter((block) => block.type === "text" && block.source !== MessageSource.HOOK);
12
15
  return textBlocks
13
16
  .map((block) => block.content)
14
17
  .join(" ")
@@ -50,8 +53,8 @@ export const convertImageToBase64 = (imagePath) => {
50
53
  const base64String = imageBuffer.toString("base64");
51
54
  return `data:${mimeType};base64,${base64String}`;
52
55
  }
53
- catch {
54
- // logger.error(`Failed to convert image to base64: ${imagePath}`, error);
56
+ catch (error) {
57
+ logger.error(`Failed to convert image to base64: ${imagePath}`, error);
55
58
  // Return an error placeholder or throw error
56
59
  return `data:image/png;base64,`; // Empty base64, avoid program crash
57
60
  }
@@ -82,7 +85,7 @@ export const addUserMessageToMessages = ({ messages, content, images, customComm
82
85
  return [...messages, userMessage];
83
86
  };
84
87
  // Add assistant message (support one-time addition of answer and tool calls)
85
- export const addAssistantMessageToMessages = (messages, content, toolCalls, usage) => {
88
+ export const addAssistantMessageToMessages = (messages, content, toolCalls, usage, metadata) => {
86
89
  const blocks = [];
87
90
  // If there's answer content, add text block
88
91
  if (content) {
@@ -97,7 +100,7 @@ export const addAssistantMessageToMessages = (messages, content, toolCalls, usag
97
100
  result: "",
98
101
  id: toolCall.id || "",
99
102
  name: toolCall.function?.name || "",
100
- isRunning: false,
103
+ stage: "start",
101
104
  });
102
105
  });
103
106
  }
@@ -105,6 +108,7 @@ export const addAssistantMessageToMessages = (messages, content, toolCalls, usag
105
108
  role: "assistant",
106
109
  blocks,
107
110
  usage, // Include usage data if provided
111
+ ...(metadata ? { metadata: { ...metadata } } : {}),
108
112
  };
109
113
  return [...messages, initialAssistantMessage];
110
114
  };
@@ -126,7 +130,7 @@ export const addDiffBlockToMessage = ({ messages, path, diffResult, }) => {
126
130
  return newMessages;
127
131
  };
128
132
  // Update Tool Block of the last assistant message
129
- export const updateToolBlockInMessage = ({ messages, id, parameters, result, success, error, isRunning, name, shortResult, images, compactParams, }) => {
133
+ export const updateToolBlockInMessage = ({ messages, id, parameters, result, success, error, stage, name, shortResult, images, compactParams, parametersChunk, }) => {
130
134
  const newMessages = [...messages];
131
135
  // Find the last assistant message
132
136
  for (let i = newMessages.length - 1; i >= 0; i--) {
@@ -145,26 +149,30 @@ export const updateToolBlockInMessage = ({ messages, id, parameters, result, suc
145
149
  toolBlock.success = success;
146
150
  if (error !== undefined)
147
151
  toolBlock.error = error;
148
- if (isRunning !== undefined)
149
- toolBlock.isRunning = isRunning;
152
+ if (stage !== undefined)
153
+ toolBlock.stage = stage;
150
154
  if (compactParams !== undefined)
151
155
  toolBlock.compactParams = compactParams;
156
+ if (parametersChunk !== undefined)
157
+ toolBlock.parametersChunk = parametersChunk;
152
158
  }
153
159
  }
154
- else if (result !== undefined) {
160
+ else {
155
161
  // If existing block not found, create new one
162
+ // This handles cases where we're streaming tool parameters before execution
156
163
  newMessages[i].blocks.push({
157
164
  type: "tool",
158
165
  parameters: parameters,
159
- result: result,
166
+ result: result || "",
160
167
  shortResult: shortResult,
161
168
  images: images, // Add image data
162
169
  id: id,
163
170
  name: name || "unknown",
164
171
  success: success,
165
172
  error: error,
166
- isRunning: isRunning ?? false,
173
+ stage: stage ?? "start",
167
174
  compactParams: compactParams,
175
+ parametersChunk: parametersChunk,
168
176
  });
169
177
  }
170
178
  break;
@@ -334,7 +342,7 @@ export const completeCommandInMessage = ({ messages, command, exitCode, }) => {
334
342
  }
335
343
  return newMessages;
336
344
  };
337
- export const addSubagentBlockToMessage = ({ messages, subagentId, subagentName, status, subagentMessages = [], }) => {
345
+ export const addSubagentBlockToMessage = ({ messages, subagentId, subagentName, status, sessionId, configuration, }) => {
338
346
  const newMessages = [...messages];
339
347
  // Find the last assistant message or create one
340
348
  let lastAssistantMessage = newMessages[newMessages.length - 1];
@@ -352,7 +360,8 @@ export const addSubagentBlockToMessage = ({ messages, subagentId, subagentName,
352
360
  subagentId,
353
361
  subagentName,
354
362
  status,
355
- messages: subagentMessages,
363
+ sessionId,
364
+ configuration,
356
365
  });
357
366
  return newMessages;
358
367
  };
@@ -367,8 +376,8 @@ export const updateSubagentBlockInMessage = (messages, subagentId, updates) => {
367
376
  if (updates.status !== undefined) {
368
377
  block.status = updates.status;
369
378
  }
370
- if (updates.messages !== undefined) {
371
- block.messages = updates.messages;
379
+ if (updates.sessionId !== undefined) {
380
+ block.sessionId = updates.sessionId;
372
381
  }
373
382
  return newMessages;
374
383
  }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Path encoding utility for converting working directory paths to filesystem-safe names
3
+ * Handles cross-platform directory name encoding for project-based session organization
4
+ */
5
+ /**
6
+ * Project directory information
7
+ */
8
+ export interface ProjectDirectory {
9
+ readonly originalPath: string;
10
+ readonly encodedName: string;
11
+ readonly encodedPath: string;
12
+ readonly pathHash?: string;
13
+ readonly isSymbolicLink: boolean;
14
+ }
15
+ /**
16
+ * Path encoding configuration options
17
+ */
18
+ export interface PathEncodingOptions {
19
+ maxLength?: number;
20
+ pathSeparatorReplacement?: string;
21
+ spaceReplacement?: string;
22
+ invalidCharReplacement?: string;
23
+ preserveCase?: boolean;
24
+ hashLength?: number;
25
+ }
26
+ /**
27
+ * Platform-specific filesystem constraints
28
+ */
29
+ export interface FilesystemConstraints {
30
+ readonly maxDirectoryNameLength: number;
31
+ readonly maxPathLength: number;
32
+ readonly invalidCharacters: string[];
33
+ readonly reservedNames: string[];
34
+ readonly caseSensitive: boolean;
35
+ }
36
+ /**
37
+ * Path validation result
38
+ */
39
+ export interface PathValidationResult {
40
+ readonly isValid: boolean;
41
+ readonly errors: string[];
42
+ readonly warnings: string[];
43
+ readonly suggestedFix?: string;
44
+ }
45
+ /**
46
+ * PathEncoder class for converting working directory paths to filesystem-safe names
47
+ */
48
+ export declare class PathEncoder {
49
+ private readonly options;
50
+ private readonly constraints;
51
+ constructor(options?: PathEncodingOptions);
52
+ /**
53
+ * Encode a working directory path to a filesystem-safe directory name
54
+ */
55
+ encode(originalPath: string): Promise<string>;
56
+ /**
57
+ * Synchronously encode a path to a filesystem-safe directory name
58
+ * Note: Does not resolve symbolic links - use encode() for full path resolution
59
+ */
60
+ encodeSync(pathToEncode: string): string;
61
+ /**
62
+ * Decode an encoded directory name back to original path (limited functionality)
63
+ * Note: This is best-effort as encoding is lossy
64
+ */
65
+ decode(encodedName: string): Promise<string | null>;
66
+ /**
67
+ * Synchronously decode an encoded directory name back to original path (limited functionality)
68
+ * Note: This is best-effort as encoding is lossy
69
+ */
70
+ decodeSync(encodedName: string): string | null;
71
+ /**
72
+ * Resolve symbolic links and normalize path before encoding
73
+ */
74
+ resolvePath(path: string): Promise<string>;
75
+ /**
76
+ * Create project directory entity from original path
77
+ */
78
+ createProjectDirectory(originalPath: string, baseSessionDir: string): Promise<ProjectDirectory>;
79
+ /**
80
+ * Validate that an encoded name is filesystem-safe
81
+ */
82
+ validateEncodedName(encodedName: string): boolean;
83
+ /**
84
+ * Handle encoding collisions by generating unique names
85
+ */
86
+ resolveCollision(baseName: string, existingNames: Set<string>): string;
87
+ /**
88
+ * Get platform-specific filesystem constraints
89
+ */
90
+ private getFilesystemConstraints;
91
+ /**
92
+ * Generate hash for collision resolution
93
+ */
94
+ private generateHash;
95
+ /**
96
+ * Expand tilde (~) to home directory
97
+ */
98
+ private expandTilde;
99
+ }
100
+ /**
101
+ * Default PathEncoder instance
102
+ */
103
+ export declare const pathEncoder: PathEncoder;
104
+ //# sourceMappingURL=pathEncoder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pathEncoder.d.ts","sourceRoot":"","sources":["../../src/utils/pathEncoder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,aAAa,EAAE,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgC;IACxD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAwB;gBAExC,OAAO,GAAE,mBAAwB;IAY7C;;OAEG;IACG,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAMnD;;;OAGG;IACH,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM;IAyCxC;;;OAGG;IACG,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAIzD;;;OAGG;IACH,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAgC9C;;OAEG;IACG,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAiBhD;;OAEG;IACG,sBAAsB,CAC1B,YAAY,EAAE,MAAM,EACpB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,gBAAgB,CAAC;IA0C5B;;OAEG;IACH,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IA+BjD;;OAEG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM;IAqBtE;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAsDhC;;OAEG;IACH,OAAO,CAAC,YAAY;IAOpB;;OAEG;IACH,OAAO,CAAC,WAAW;CAMpB;AAED;;GAEG;AACH,eAAO,MAAM,WAAW,aAAoB,CAAC"}
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Path encoding utility for converting working directory paths to filesystem-safe names
3
+ * Handles cross-platform directory name encoding for project-based session organization
4
+ */
5
+ import { resolve, join } from "path";
6
+ import { createHash } from "crypto";
7
+ import { realpath, mkdir } from "fs/promises";
8
+ import { homedir, platform } from "os";
9
+ /**
10
+ * PathEncoder class for converting working directory paths to filesystem-safe names
11
+ */
12
+ export class PathEncoder {
13
+ constructor(options = {}) {
14
+ this.options = {
15
+ maxLength: options.maxLength ?? 200,
16
+ pathSeparatorReplacement: options.pathSeparatorReplacement ?? "-",
17
+ spaceReplacement: options.spaceReplacement ?? "_",
18
+ invalidCharReplacement: options.invalidCharReplacement ?? "_",
19
+ preserveCase: options.preserveCase ?? false,
20
+ hashLength: options.hashLength ?? 8,
21
+ };
22
+ this.constraints = this.getFilesystemConstraints();
23
+ }
24
+ /**
25
+ * Encode a working directory path to a filesystem-safe directory name
26
+ */
27
+ async encode(originalPath) {
28
+ // Resolve symbolic links and normalize path
29
+ const resolvedPath = await this.resolvePath(originalPath);
30
+ return this.encodeSync(resolvedPath);
31
+ }
32
+ /**
33
+ * Synchronously encode a path to a filesystem-safe directory name
34
+ * Note: Does not resolve symbolic links - use encode() for full path resolution
35
+ */
36
+ encodeSync(pathToEncode) {
37
+ // Convert to safe directory name
38
+ let encoded = pathToEncode;
39
+ // Remove leading slash to avoid empty directory names
40
+ if (encoded.startsWith("/")) {
41
+ encoded = encoded.substring(1);
42
+ }
43
+ // Replace path separators with hyphens
44
+ encoded = encoded.replace(/[/\\]/g, this.options.pathSeparatorReplacement);
45
+ // Replace spaces with underscores
46
+ encoded = encoded.replace(/\s+/g, this.options.spaceReplacement);
47
+ // Replace invalid characters with underscores
48
+ const escapedChars = this.constraints.invalidCharacters
49
+ .map((c) => `\\${c}`)
50
+ .join("");
51
+ const invalidChars = new RegExp(`[${escapedChars}]`, "g");
52
+ encoded = encoded.replace(invalidChars, this.options.invalidCharReplacement);
53
+ // Convert to lowercase unless preserveCase is true
54
+ if (!this.options.preserveCase) {
55
+ encoded = encoded.toLowerCase();
56
+ }
57
+ // Handle length limit with hash
58
+ if (encoded.length > this.options.maxLength) {
59
+ const hash = this.generateHash(pathToEncode, this.options.hashLength);
60
+ const maxBaseLength = this.options.maxLength - this.options.hashLength - 1; // -1 for separator
61
+ encoded = `${encoded.substring(0, maxBaseLength)}-${hash}`;
62
+ }
63
+ return encoded;
64
+ }
65
+ /**
66
+ * Decode an encoded directory name back to original path (limited functionality)
67
+ * Note: This is best-effort as encoding is lossy
68
+ */
69
+ async decode(encodedName) {
70
+ return this.decodeSync(encodedName);
71
+ }
72
+ /**
73
+ * Synchronously decode an encoded directory name back to original path (limited functionality)
74
+ * Note: This is best-effort as encoding is lossy
75
+ */
76
+ decodeSync(encodedName) {
77
+ // This is a simplified version - full reversal is not always possible
78
+ // due to lossy encoding (case changes, character replacements, hashing)
79
+ // Check if this has a hash suffix
80
+ const hashPattern = new RegExp(`-[a-f0-9]{${this.options.hashLength}}$`);
81
+ if (hashPattern.test(encodedName)) {
82
+ // Cannot reliably decode hashed paths
83
+ return null;
84
+ }
85
+ // Attempt basic reversal
86
+ let decoded = encodedName;
87
+ // Reverse path separator replacement
88
+ decoded = decoded.replace(new RegExp(this.options.pathSeparatorReplacement, "g"), "/");
89
+ // Reverse space replacement
90
+ decoded = decoded.replace(new RegExp(this.options.spaceReplacement, "g"), " ");
91
+ // Add leading slash
92
+ decoded = `/${decoded}`;
93
+ return decoded;
94
+ }
95
+ /**
96
+ * Resolve symbolic links and normalize path before encoding
97
+ */
98
+ async resolvePath(path) {
99
+ try {
100
+ // Expand tilde to home directory
101
+ const expandedPath = this.expandTilde(path);
102
+ // Resolve to absolute path
103
+ const absolutePath = resolve(expandedPath);
104
+ // Resolve symbolic links
105
+ const resolvedPath = await realpath(absolutePath);
106
+ return resolvedPath;
107
+ }
108
+ catch (error) {
109
+ throw new Error(`Failed to resolve path "${path}": ${error}`);
110
+ }
111
+ }
112
+ /**
113
+ * Create project directory entity from original path
114
+ */
115
+ async createProjectDirectory(originalPath, baseSessionDir) {
116
+ // Resolve the original path and check for symbolic links
117
+ const expandedPath = this.expandTilde(originalPath);
118
+ const absolutePath = resolve(expandedPath);
119
+ let resolvedPath;
120
+ let isSymbolicLink = false;
121
+ try {
122
+ resolvedPath = await realpath(absolutePath);
123
+ isSymbolicLink = resolvedPath !== absolutePath;
124
+ }
125
+ catch {
126
+ // If realpath fails, use the absolute path
127
+ resolvedPath = absolutePath;
128
+ }
129
+ // Encode the resolved path
130
+ const encodedName = await this.encode(resolvedPath);
131
+ const encodedPath = join(baseSessionDir, encodedName);
132
+ // Generate hash if encoding resulted in truncation
133
+ let pathHash;
134
+ if (resolvedPath.length > this.options.maxLength) {
135
+ pathHash = this.generateHash(resolvedPath, this.options.hashLength);
136
+ }
137
+ // Ensure the encoded directory exists
138
+ try {
139
+ await mkdir(encodedPath, { recursive: true });
140
+ }
141
+ catch {
142
+ // Ignore errors if directory already exists
143
+ }
144
+ return {
145
+ originalPath: resolvedPath,
146
+ encodedName,
147
+ encodedPath,
148
+ pathHash,
149
+ isSymbolicLink,
150
+ };
151
+ }
152
+ /**
153
+ * Validate that an encoded name is filesystem-safe
154
+ */
155
+ validateEncodedName(encodedName) {
156
+ // Check length
157
+ if (encodedName.length > this.constraints.maxDirectoryNameLength) {
158
+ return false;
159
+ }
160
+ // Check for invalid characters
161
+ for (const char of this.constraints.invalidCharacters) {
162
+ if (encodedName.includes(char)) {
163
+ return false;
164
+ }
165
+ }
166
+ // Check for reserved names
167
+ const lowerName = encodedName.toLowerCase();
168
+ if (this.constraints.reservedNames.some((reserved) => reserved.toLowerCase() === lowerName)) {
169
+ return false;
170
+ }
171
+ // Check for empty or dots-only names
172
+ if (!encodedName.trim() || /^\.+$/.test(encodedName)) {
173
+ return false;
174
+ }
175
+ return true;
176
+ }
177
+ /**
178
+ * Handle encoding collisions by generating unique names
179
+ */
180
+ resolveCollision(baseName, existingNames) {
181
+ if (!existingNames.has(baseName)) {
182
+ return baseName;
183
+ }
184
+ // Try numbered suffixes first
185
+ for (let i = 1; i <= 999; i++) {
186
+ const candidate = `${baseName}-${i}`;
187
+ if (!existingNames.has(candidate)) {
188
+ return candidate;
189
+ }
190
+ }
191
+ // If all numbered suffixes are taken, use hash
192
+ const hash = this.generateHash(baseName + Date.now(), this.options.hashLength);
193
+ return `${baseName}-${hash}`;
194
+ }
195
+ /**
196
+ * Get platform-specific filesystem constraints
197
+ */
198
+ getFilesystemConstraints() {
199
+ const currentPlatform = platform();
200
+ switch (currentPlatform) {
201
+ case "win32":
202
+ return {
203
+ maxDirectoryNameLength: 255,
204
+ maxPathLength: 260,
205
+ invalidCharacters: ["<", ">", ":", '"', "|", "?", "*"],
206
+ reservedNames: [
207
+ "CON",
208
+ "PRN",
209
+ "AUX",
210
+ "NUL",
211
+ "COM1",
212
+ "COM2",
213
+ "COM3",
214
+ "COM4",
215
+ "COM5",
216
+ "COM6",
217
+ "COM7",
218
+ "COM8",
219
+ "COM9",
220
+ "LPT1",
221
+ "LPT2",
222
+ "LPT3",
223
+ "LPT4",
224
+ "LPT5",
225
+ "LPT6",
226
+ "LPT7",
227
+ "LPT8",
228
+ "LPT9",
229
+ ],
230
+ caseSensitive: false,
231
+ };
232
+ case "darwin":
233
+ return {
234
+ maxDirectoryNameLength: 255,
235
+ maxPathLength: 1024,
236
+ invalidCharacters: [":"],
237
+ reservedNames: [],
238
+ caseSensitive: false, // HFS+ is case-insensitive by default
239
+ };
240
+ default: // Linux and other Unix-like systems
241
+ return {
242
+ maxDirectoryNameLength: 255,
243
+ maxPathLength: 4096,
244
+ invalidCharacters: ["\0"],
245
+ reservedNames: [],
246
+ caseSensitive: true,
247
+ };
248
+ }
249
+ }
250
+ /**
251
+ * Generate hash for collision resolution
252
+ */
253
+ generateHash(input, length) {
254
+ return createHash("sha256")
255
+ .update(input)
256
+ .digest("hex")
257
+ .substring(0, length);
258
+ }
259
+ /**
260
+ * Expand tilde (~) to home directory
261
+ */
262
+ expandTilde(path) {
263
+ if (path.startsWith("~/") || path === "~") {
264
+ return path.replace(/^~/, homedir());
265
+ }
266
+ return path;
267
+ }
268
+ }
269
+ /**
270
+ * Default PathEncoder instance
271
+ */
272
+ export const pathEncoder = new PathEncoder();
@@ -1 +1 @@
1
- {"version":3,"file":"subagentParser.d.ts","sourceRoot":"","sources":["../../src/utils/subagentParser.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;CAClB;AA+KD;;GAEG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAmBlC;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAGvC"}
1
+ {"version":3,"file":"subagentParser.d.ts","sourceRoot":"","sources":["../../src/utils/subagentParser.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;CAClB;AA+KD;;GAEG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAmBlC;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAGvC"}
@@ -1,5 +1,6 @@
1
1
  import { readFileSync, readdirSync, statSync } from "fs";
2
2
  import { join, extname } from "path";
3
+ import { logger } from "./globalLogger.js";
3
4
  /**
4
5
  * Parse YAML frontmatter from markdown file content
5
6
  */
@@ -120,7 +121,7 @@ function scanSubagentDirectory(dirPath, scope) {
120
121
  }
121
122
  catch (parseError) {
122
123
  // Log error but continue with other files
123
- console.warn(`Warning: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
124
+ logger.warn(`Warning: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
124
125
  }
125
126
  }
126
127
  }
@@ -0,0 +1,26 @@
1
+ import type { Usage } from "../types/index.js";
2
+ /**
3
+ * Calculate comprehensive total tokens including cache-related tokens
4
+ *
5
+ * This function computes the true total token cost by including:
6
+ * - Base total_tokens (prompt + completion)
7
+ * - Cache read tokens (cost savings indicator)
8
+ * - Cache creation tokens (cache investment)
9
+ *
10
+ * For accurate cost tracking with Claude models that support cache control.
11
+ *
12
+ * @param usage - Usage statistics from AI operation
13
+ * @returns Comprehensive total including all cache-related tokens
14
+ */
15
+ export declare function calculateComprehensiveTotalTokens(usage: Usage): number;
16
+ /**
17
+ * Extract the latest total tokens from the last message with usage data
18
+ * Uses comprehensive calculation that includes cache tokens for accurate tracking
19
+ *
20
+ * @param messages - Array of messages to search
21
+ * @returns Comprehensive total tokens from the most recent usage data, or 0 if none found
22
+ */
23
+ export declare function extractLatestTotalTokens(messages: Array<{
24
+ usage?: Usage;
25
+ }>): number;
26
+ //# sourceMappingURL=tokenCalculation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenCalculation.d.ts","sourceRoot":"","sources":["../../src/utils/tokenCalculation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE/C;;;;;;;;;;;;GAYG;AACH,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAMtE;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,KAAK,CAAC;IAAE,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC,GACjC,MAAM,CAUR"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Calculate comprehensive total tokens including cache-related tokens
3
+ *
4
+ * This function computes the true total token cost by including:
5
+ * - Base total_tokens (prompt + completion)
6
+ * - Cache read tokens (cost savings indicator)
7
+ * - Cache creation tokens (cache investment)
8
+ *
9
+ * For accurate cost tracking with Claude models that support cache control.
10
+ *
11
+ * @param usage - Usage statistics from AI operation
12
+ * @returns Comprehensive total including all cache-related tokens
13
+ */
14
+ export function calculateComprehensiveTotalTokens(usage) {
15
+ const baseTokens = usage.total_tokens;
16
+ const cacheReadTokens = usage.cache_read_input_tokens || 0;
17
+ const cacheCreateTokens = usage.cache_creation_input_tokens || 0;
18
+ return baseTokens + cacheReadTokens + cacheCreateTokens;
19
+ }
20
+ /**
21
+ * Extract the latest total tokens from the last message with usage data
22
+ * Uses comprehensive calculation that includes cache tokens for accurate tracking
23
+ *
24
+ * @param messages - Array of messages to search
25
+ * @returns Comprehensive total tokens from the most recent usage data, or 0 if none found
26
+ */
27
+ export function extractLatestTotalTokens(messages) {
28
+ // Find the last message with usage data (iterate backwards for efficiency)
29
+ for (let i = messages.length - 1; i >= 0; i--) {
30
+ const message = messages[i];
31
+ if (message.usage) {
32
+ return calculateComprehensiveTotalTokens(message.usage);
33
+ }
34
+ }
35
+ return 0; // No usage data found
36
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-agent-sdk",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "SDK for building AI-powered development tools and agents",
5
5
  "keywords": [
6
6
  "ai",
@@ -21,12 +21,15 @@
21
21
  "dependencies": {
22
22
  "@modelcontextprotocol/sdk": "^1.18.2",
23
23
  "@vscode/ripgrep": "^1.15.14",
24
+ "chokidar": "^5.0.0",
24
25
  "diff": "^8.0.2",
25
26
  "glob": "^11.0.3",
26
27
  "minimatch": "^10.0.3",
27
- "openai": "^5.12.2"
28
+ "openai": "^5.12.2",
29
+ "uuid": "^13.0.0"
28
30
  },
29
31
  "devDependencies": {
32
+ "@types/uuid": "^11.0.0",
30
33
  "rimraf": "^6.0.1",
31
34
  "tsc-alias": "^1.8.16",
32
35
  "tsx": "^4.20.4",
@@ -39,7 +42,7 @@
39
42
  "scripts": {
40
43
  "build": "rimraf dist && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
41
44
  "type-check": "tsc --noEmit --incremental",
42
- "dev": "tsc -p tsconfig.build.json --watch & tsc-alias -p tsconfig.build.json --watch",
45
+ "watch": "tsc -p tsconfig.build.json --watch & tsc-alias -p tsconfig.build.json --watch",
43
46
  "test": "vitest run",
44
47
  "lint": "eslint --cache",
45
48
  "format": "prettier --write ."