wave-agent-sdk 0.2.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/dist/agent.d.ts +66 -20
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +156 -83
  4. package/dist/constants/prompts.d.ts +7 -2
  5. package/dist/constants/prompts.d.ts.map +1 -1
  6. package/dist/constants/prompts.js +41 -5
  7. package/dist/constants/tools.d.ts +2 -2
  8. package/dist/constants/tools.js +2 -2
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +1 -1
  12. package/dist/managers/MemoryRuleManager.d.ts.map +1 -1
  13. package/dist/managers/MemoryRuleManager.js +16 -2
  14. package/dist/managers/aiManager.d.ts +14 -4
  15. package/dist/managers/aiManager.d.ts.map +1 -1
  16. package/dist/managers/aiManager.js +61 -9
  17. package/dist/managers/backgroundBashManager.d.ts.map +1 -1
  18. package/dist/managers/backgroundBashManager.js +1 -0
  19. package/dist/managers/backgroundTaskManager.d.ts +35 -0
  20. package/dist/managers/backgroundTaskManager.d.ts.map +1 -0
  21. package/dist/managers/backgroundTaskManager.js +249 -0
  22. package/dist/managers/bashManager.d.ts.map +1 -1
  23. package/dist/managers/bashManager.js +0 -3
  24. package/dist/managers/foregroundTaskManager.d.ts +9 -0
  25. package/dist/managers/foregroundTaskManager.d.ts.map +1 -0
  26. package/dist/managers/foregroundTaskManager.js +20 -0
  27. package/dist/managers/liveConfigManager.d.ts +1 -1
  28. package/dist/managers/liveConfigManager.d.ts.map +1 -1
  29. package/dist/managers/lspManager.d.ts.map +1 -1
  30. package/dist/managers/lspManager.js +3 -1
  31. package/dist/managers/messageManager.d.ts +34 -4
  32. package/dist/managers/messageManager.d.ts.map +1 -1
  33. package/dist/managers/messageManager.js +104 -13
  34. package/dist/managers/permissionManager.d.ts.map +1 -1
  35. package/dist/managers/permissionManager.js +11 -13
  36. package/dist/managers/pluginManager.d.ts.map +1 -1
  37. package/dist/managers/pluginManager.js +3 -2
  38. package/dist/managers/pluginScopeManager.d.ts +13 -2
  39. package/dist/managers/pluginScopeManager.d.ts.map +1 -1
  40. package/dist/managers/pluginScopeManager.js +38 -0
  41. package/dist/managers/reversionManager.d.ts +39 -0
  42. package/dist/managers/reversionManager.d.ts.map +1 -0
  43. package/dist/managers/reversionManager.js +118 -0
  44. package/dist/managers/slashCommandManager.d.ts +4 -1
  45. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  46. package/dist/managers/slashCommandManager.js +16 -6
  47. package/dist/managers/subagentManager.d.ts +13 -2
  48. package/dist/managers/subagentManager.d.ts.map +1 -1
  49. package/dist/managers/subagentManager.js +144 -35
  50. package/dist/managers/toolManager.d.ts +11 -1
  51. package/dist/managers/toolManager.d.ts.map +1 -1
  52. package/dist/managers/toolManager.js +11 -3
  53. package/dist/services/GitService.d.ts.map +1 -1
  54. package/dist/services/GitService.js +6 -2
  55. package/dist/services/MarketplaceService.d.ts +14 -1
  56. package/dist/services/MarketplaceService.d.ts.map +1 -1
  57. package/dist/services/MarketplaceService.js +72 -4
  58. package/dist/services/MemoryRuleService.d.ts +1 -1
  59. package/dist/services/MemoryRuleService.d.ts.map +1 -1
  60. package/dist/services/MemoryRuleService.js +13 -2
  61. package/dist/services/aiService.js +1 -1
  62. package/dist/services/configurationService.d.ts +18 -2
  63. package/dist/services/configurationService.d.ts.map +1 -1
  64. package/dist/services/configurationService.js +62 -0
  65. package/dist/services/fileWatcher.d.ts +0 -5
  66. package/dist/services/fileWatcher.d.ts.map +1 -1
  67. package/dist/services/fileWatcher.js +0 -11
  68. package/dist/services/memory.js +1 -1
  69. package/dist/services/pluginLoader.d.ts.map +1 -1
  70. package/dist/services/pluginLoader.js +6 -1
  71. package/dist/services/reversionService.d.ts +24 -0
  72. package/dist/services/reversionService.d.ts.map +1 -0
  73. package/dist/services/reversionService.js +76 -0
  74. package/dist/services/session.d.ts +7 -0
  75. package/dist/services/session.d.ts.map +1 -1
  76. package/dist/services/session.js +126 -3
  77. package/dist/tools/bashTool.d.ts +0 -8
  78. package/dist/tools/bashTool.d.ts.map +1 -1
  79. package/dist/tools/bashTool.js +52 -174
  80. package/dist/tools/deleteFileTool.d.ts.map +1 -1
  81. package/dist/tools/deleteFileTool.js +9 -0
  82. package/dist/tools/editTool.d.ts.map +1 -1
  83. package/dist/tools/editTool.js +15 -4
  84. package/dist/tools/multiEditTool.d.ts.map +1 -1
  85. package/dist/tools/multiEditTool.js +16 -5
  86. package/dist/tools/taskOutputTool.d.ts +3 -0
  87. package/dist/tools/taskOutputTool.d.ts.map +1 -0
  88. package/dist/tools/taskOutputTool.js +149 -0
  89. package/dist/tools/taskStopTool.d.ts +3 -0
  90. package/dist/tools/taskStopTool.d.ts.map +1 -0
  91. package/dist/tools/taskStopTool.js +65 -0
  92. package/dist/tools/taskTool.d.ts.map +1 -1
  93. package/dist/tools/taskTool.js +105 -63
  94. package/dist/tools/types.d.ts +7 -0
  95. package/dist/tools/types.d.ts.map +1 -1
  96. package/dist/tools/writeTool.d.ts.map +1 -1
  97. package/dist/tools/writeTool.js +9 -0
  98. package/dist/types/commands.d.ts +1 -0
  99. package/dist/types/commands.d.ts.map +1 -1
  100. package/dist/types/configuration.d.ts +3 -0
  101. package/dist/types/configuration.d.ts.map +1 -1
  102. package/dist/types/environment.d.ts +2 -1
  103. package/dist/types/environment.d.ts.map +1 -1
  104. package/dist/types/environment.js +0 -6
  105. package/dist/types/history.d.ts +5 -0
  106. package/dist/types/history.d.ts.map +1 -0
  107. package/dist/types/history.js +1 -0
  108. package/dist/types/index.d.ts +1 -0
  109. package/dist/types/index.d.ts.map +1 -1
  110. package/dist/types/index.js +1 -0
  111. package/dist/types/marketplace.d.ts +4 -0
  112. package/dist/types/marketplace.d.ts.map +1 -1
  113. package/dist/types/messaging.d.ts +7 -1
  114. package/dist/types/messaging.d.ts.map +1 -1
  115. package/dist/types/processes.d.ts +24 -4
  116. package/dist/types/processes.d.ts.map +1 -1
  117. package/dist/types/reversion.d.ts +29 -0
  118. package/dist/types/reversion.d.ts.map +1 -0
  119. package/dist/types/reversion.js +1 -0
  120. package/dist/utils/builtinSubagents.d.ts.map +1 -1
  121. package/dist/utils/builtinSubagents.js +16 -0
  122. package/dist/utils/constants.d.ts +2 -2
  123. package/dist/utils/constants.d.ts.map +1 -1
  124. package/dist/utils/constants.js +2 -2
  125. package/dist/utils/editUtils.d.ts +4 -9
  126. package/dist/utils/editUtils.d.ts.map +1 -1
  127. package/dist/utils/editUtils.js +54 -55
  128. package/dist/utils/messageOperations.d.ts +3 -1
  129. package/dist/utils/messageOperations.d.ts.map +1 -1
  130. package/dist/utils/messageOperations.js +8 -1
  131. package/dist/utils/openaiClient.d.ts.map +1 -1
  132. package/dist/utils/openaiClient.js +56 -26
  133. package/dist/utils/promptHistory.d.ts +20 -0
  134. package/dist/utils/promptHistory.d.ts.map +1 -0
  135. package/dist/utils/promptHistory.js +117 -0
  136. package/package.json +5 -3
  137. package/src/agent.ts +193 -109
  138. package/src/constants/prompts.ts +45 -5
  139. package/src/constants/tools.ts +2 -2
  140. package/src/index.ts +1 -1
  141. package/src/managers/MemoryRuleManager.ts +18 -2
  142. package/src/managers/aiManager.ts +87 -18
  143. package/src/managers/backgroundBashManager.ts +1 -0
  144. package/src/managers/backgroundTaskManager.ts +306 -0
  145. package/src/managers/bashManager.ts +0 -4
  146. package/src/managers/foregroundTaskManager.ts +26 -0
  147. package/src/managers/liveConfigManager.ts +2 -1
  148. package/src/managers/lspManager.ts +3 -1
  149. package/src/managers/messageManager.ts +136 -18
  150. package/src/managers/permissionManager.ts +11 -13
  151. package/src/managers/pluginManager.ts +4 -3
  152. package/src/managers/pluginScopeManager.ts +57 -8
  153. package/src/managers/reversionManager.ts +152 -0
  154. package/src/managers/slashCommandManager.ts +30 -7
  155. package/src/managers/subagentManager.ts +176 -31
  156. package/src/managers/toolManager.ts +23 -4
  157. package/src/services/GitService.ts +6 -2
  158. package/src/services/MarketplaceService.ts +100 -4
  159. package/src/services/MemoryRuleService.ts +18 -6
  160. package/src/services/aiService.ts +1 -1
  161. package/src/services/configurationService.ts +79 -1
  162. package/src/services/fileWatcher.ts +0 -13
  163. package/src/services/memory.ts +1 -1
  164. package/src/services/pluginLoader.ts +7 -1
  165. package/src/services/reversionService.ts +94 -0
  166. package/src/services/session.ts +161 -3
  167. package/src/tools/bashTool.ts +73 -200
  168. package/src/tools/deleteFileTool.ts +15 -0
  169. package/src/tools/editTool.ts +20 -10
  170. package/src/tools/multiEditTool.ts +21 -11
  171. package/src/tools/taskOutputTool.ts +174 -0
  172. package/src/tools/taskStopTool.ts +72 -0
  173. package/src/tools/taskTool.ts +130 -74
  174. package/src/tools/types.ts +7 -0
  175. package/src/tools/writeTool.ts +14 -0
  176. package/src/types/commands.ts +3 -0
  177. package/src/types/configuration.ts +4 -0
  178. package/src/types/environment.ts +3 -1
  179. package/src/types/history.ts +4 -0
  180. package/src/types/index.ts +1 -0
  181. package/src/types/marketplace.ts +5 -0
  182. package/src/types/messaging.ts +9 -1
  183. package/src/types/processes.ts +33 -4
  184. package/src/types/reversion.ts +29 -0
  185. package/src/utils/builtinSubagents.ts +18 -0
  186. package/src/utils/constants.ts +2 -2
  187. package/src/utils/editUtils.ts +66 -58
  188. package/src/utils/messageOperations.ts +10 -0
  189. package/src/utils/openaiClient.ts +69 -35
  190. package/src/utils/promptHistory.ts +133 -0
  191. package/dist/utils/bashHistory.d.ts +0 -50
  192. package/dist/utils/bashHistory.d.ts.map +0 -1
  193. package/dist/utils/bashHistory.js +0 -256
  194. package/src/utils/bashHistory.ts +0 -320
@@ -3,80 +3,88 @@
3
3
  */
4
4
 
5
5
  /**
6
- * Find a match in content that is identical to searchString except for a consistent indentation offset.
7
- *
8
- * Priority:
9
- * 1. If exact matches exist, returns searchString (letting the tool handle uniqueness/replaceAll).
10
- * 2. If no exact match, but exactly one unique indentation-insensitive match exists, returns that match.
11
- * 3. Otherwise returns null.
6
+ * Escape regular expression special characters
7
+ */
8
+ export function escapeRegExp(string: string): string {
9
+ return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10
+ }
11
+
12
+ /**
13
+ * Analyze why an edit failed by finding the best partial match and highlighting mismatches.
12
14
  */
13
- export function findIndentationInsensitiveMatch(
15
+ export function analyzeEditMismatch(
14
16
  content: string,
15
17
  searchString: string,
16
- ): string | null {
17
- // 1. If exact match exists, return it
18
- if (content.includes(searchString)) {
19
- return searchString;
20
- }
21
-
18
+ ): string {
19
+ const contentLines = content.split("\n");
22
20
  const searchLines = searchString.split("\n");
23
- if (searchLines.length === 0) return null;
24
21
 
25
- const contentLines = content.split("\n");
26
- let foundMatch: string | null = null;
22
+ if (searchLines.length === 0 || contentLines.length === 0) {
23
+ return "old_string not found in file (empty search or content)";
24
+ }
27
25
 
28
- for (let i = 0; i <= contentLines.length - searchLines.length; i++) {
29
- let offset: number | null = null;
30
- let isMatch = true;
26
+ let bestMatchIndex = -1;
27
+ let bestMatchScore = -1;
31
28
 
29
+ // Sliding window to find the best partial match
30
+ for (let i = 0; i <= contentLines.length - searchLines.length; i++) {
31
+ let currentScore = 0;
32
32
  for (let j = 0; j < searchLines.length; j++) {
33
- const sLine = searchLines[j];
34
- const cLine = contentLines[i + j];
35
-
36
- const sTrimmed = sLine.trimStart();
37
- const cTrimmed = cLine.trimStart();
38
-
39
- // If trimmed content doesn't match, it's not a match
40
- if (sTrimmed !== cTrimmed) {
41
- isMatch = false;
42
- break;
33
+ if (contentLines[i + j] === searchLines[j]) {
34
+ currentScore++;
43
35
  }
36
+ }
44
37
 
45
- // For non-empty lines, check for consistent indentation offset
46
- if (sTrimmed !== "") {
47
- const sIndent = sLine.length - sTrimmed.length;
48
- const cIndent = cLine.length - cTrimmed.length;
49
- const currentOffset = cIndent - sIndent;
38
+ // Heuristic: prioritize matches where first or last lines match
39
+ if (contentLines[i] === searchLines[0]) currentScore += 0.5;
40
+ if (
41
+ contentLines[i + searchLines.length - 1] ===
42
+ searchLines[searchLines.length - 1]
43
+ )
44
+ currentScore += 0.5;
50
45
 
51
- if (offset === null) {
52
- offset = currentOffset;
53
- } else if (offset !== currentOffset) {
54
- isMatch = false;
55
- break;
56
- }
46
+ // Also consider trimmed matches to catch indentation issues
47
+ for (let j = 0; j < searchLines.length; j++) {
48
+ if (
49
+ contentLines[i + j].trim() === searchLines[j].trim() &&
50
+ contentLines[i + j] !== searchLines[j]
51
+ ) {
52
+ currentScore += 0.1;
57
53
  }
58
54
  }
59
55
 
60
- if (isMatch) {
61
- const matchCandidate = contentLines
62
- .slice(i, i + searchLines.length)
63
- .join("\n");
64
- if (foundMatch !== null && foundMatch !== matchCandidate) {
65
- // Multiple different smart matches found
66
- return null;
67
- }
68
- foundMatch = matchCandidate;
56
+ if (currentScore > bestMatchScore) {
57
+ bestMatchScore = currentScore;
58
+ bestMatchIndex = i;
69
59
  }
70
60
  }
71
61
 
72
- // If we found exactly one unique smart match (or multiple instances of the same smart match)
73
- // return it. The tool will then check for uniqueness if replaceAll is false.
74
- return foundMatch;
75
- }
62
+ // If no decent match found (score <= 0), return generic message
63
+ if (bestMatchScore <= 0) {
64
+ return "old_string not found in file (no similar block found)";
65
+ }
76
66
 
77
- /**
78
- * Escape regular expression special characters
79
- */
80
- export function escapeRegExp(string: string): string {
81
- return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
67
+ // Generate detailed report
68
+ const reportLines: string[] = [
69
+ `old_string not found in file. Best partial match found at line ${bestMatchIndex + 1}:`,
70
+ ];
71
+
72
+ for (let j = 0; j < searchLines.length; j++) {
73
+ const lineNum = bestMatchIndex + j + 1;
74
+ const actualLine = contentLines[bestMatchIndex + j];
75
+ const expectedLine = searchLines[j];
76
+
77
+ if (actualLine === expectedLine) {
78
+ reportLines.push(`${lineNum.toString().padStart(4)} | ${actualLine}`);
79
+ } else {
80
+ reportLines.push(
81
+ `${lineNum.toString().padStart(4)} | - ${expectedLine} (expected)`,
82
+ );
83
+ reportLines.push(
84
+ `${lineNum.toString().padStart(4)} | + ${actualLine} (actual)`,
85
+ );
86
+ }
87
+ }
88
+
89
+ return reportLines.join("\n");
82
90
  }
@@ -169,6 +169,7 @@ export const addUserMessageToMessages = ({
169
169
  }
170
170
 
171
171
  const userMessage: Message = {
172
+ id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
172
173
  role: "user",
173
174
  blocks,
174
175
  };
@@ -205,6 +206,7 @@ export const addAssistantMessageToMessages = (
205
206
  }
206
207
 
207
208
  const initialAssistantMessage: Message = {
209
+ id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
208
210
  role: "assistant",
209
211
  blocks,
210
212
  usage, // Include usage data if provided
@@ -502,6 +504,7 @@ export interface AddSubagentBlockParams {
502
504
  status: "active" | "completed" | "error" | "aborted";
503
505
  sessionId: string;
504
506
  configuration: SubagentConfiguration;
507
+ runInBackground?: boolean;
505
508
  }
506
509
 
507
510
  export interface UpdateSubagentBlockParams {
@@ -518,6 +521,7 @@ export const addSubagentBlockToMessage = ({
518
521
  status,
519
522
  sessionId,
520
523
  configuration,
524
+ runInBackground,
521
525
  }: AddSubagentBlockParams): Message[] => {
522
526
  const newMessages = [...messages];
523
527
 
@@ -527,6 +531,7 @@ export const addSubagentBlockToMessage = ({
527
531
  if (!lastAssistantMessage || lastAssistantMessage.role !== "assistant") {
528
532
  // Create new assistant message if the last message is not from assistant
529
533
  lastAssistantMessage = {
534
+ id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
530
535
  role: "assistant",
531
536
  blocks: [],
532
537
  };
@@ -541,6 +546,7 @@ export const addSubagentBlockToMessage = ({
541
546
  status,
542
547
  sessionId,
543
548
  configuration,
549
+ runInBackground,
544
550
  });
545
551
 
546
552
  return newMessages;
@@ -552,6 +558,7 @@ export const updateSubagentBlockInMessage = (
552
558
  updates: Partial<{
553
559
  status: "active" | "completed" | "error" | "aborted";
554
560
  sessionId: string;
561
+ runInBackground: boolean;
555
562
  }>,
556
563
  ): Message[] => {
557
564
  const newMessages = [...messages];
@@ -568,6 +575,9 @@ export const updateSubagentBlockInMessage = (
568
575
  if (updates.sessionId !== undefined) {
569
576
  block.sessionId = updates.sessionId;
570
577
  }
578
+ if (updates.runInBackground !== undefined) {
579
+ block.runInBackground = updates.runInBackground;
580
+ }
571
581
  return newMessages;
572
582
  }
573
583
  }
@@ -51,7 +51,9 @@ export class OpenAIClient {
51
51
  >
52
52
  >;
53
53
  // Prevent unhandled rejection if only withResponse() is used
54
- promise.catch(() => {});
54
+ promise.catch((e) => {
55
+ logger.error("Unhandled OpenAI promise rejection:", e);
56
+ });
55
57
  return promise;
56
58
  },
57
59
  },
@@ -83,24 +85,61 @@ export class OpenAIClient {
83
85
  };
84
86
 
85
87
  const fetchFn = (customFetch as typeof fetch) || fetch;
86
- const response = await fetchFn(url, {
87
- method: "POST",
88
- headers,
89
- body: JSON.stringify(params),
90
- signal: options?.signal,
91
- ...(fetchOptions as RequestInit),
92
- });
93
-
94
- if (!response.ok) {
88
+ let lastError: (Error & { status?: number; body?: unknown }) | undefined;
89
+ const maxRetries = 3;
90
+ const initialDelay = 1000;
91
+
92
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
93
+ if (attempt > 0) {
94
+ const delay = initialDelay * Math.pow(2, attempt - 1);
95
+ await new Promise((resolve) => setTimeout(resolve, delay));
96
+ }
97
+
98
+ const response = await fetchFn(url, {
99
+ method: "POST",
100
+ headers,
101
+ body: JSON.stringify(params),
102
+ signal: options?.signal,
103
+ ...(fetchOptions as RequestInit),
104
+ });
105
+
106
+ if (response.ok) {
107
+ if (params.stream) {
108
+ return {
109
+ data: this.streamChatCompletion(response),
110
+ response,
111
+ } as unknown as APIResponse<
112
+ P extends ChatCompletionCreateParamsStreaming
113
+ ? AsyncIterable<ChatCompletionChunk>
114
+ : ChatCompletion
115
+ >;
116
+ } else {
117
+ const data = await response.json();
118
+ return {
119
+ data,
120
+ response,
121
+ } as unknown as APIResponse<
122
+ P extends ChatCompletionCreateParamsStreaming
123
+ ? AsyncIterable<ChatCompletionChunk>
124
+ : ChatCompletion
125
+ >;
126
+ }
127
+ }
128
+
95
129
  let errorBody: unknown;
96
130
  try {
97
131
  const text = await response.text();
98
132
  try {
99
133
  errorBody = JSON.parse(text);
100
- } catch {
134
+ } catch (e) {
135
+ logger.error("Failed to parse error response body as JSON", {
136
+ error: e,
137
+ text,
138
+ });
101
139
  errorBody = text;
102
140
  }
103
- } catch {
141
+ } catch (e) {
142
+ logger.error("Failed to read error response text", { error: e });
104
143
  errorBody = {};
105
144
  }
106
145
 
@@ -118,6 +157,21 @@ export class OpenAIClient {
118
157
  ) as Error & { status?: number; body?: unknown };
119
158
  error.status = response.status;
120
159
  error.body = errorBody;
160
+
161
+ if (response.status === 429 && attempt < maxRetries) {
162
+ const responseHeaders: Record<string, string> = {};
163
+ response.headers.forEach((value, key) => {
164
+ responseHeaders[key] = value;
165
+ });
166
+ logger.warn("OpenAI API 429 Too Many Requests, retrying...", {
167
+ attempt: attempt + 1,
168
+ status: response.status,
169
+ responseHeaders,
170
+ });
171
+ lastError = error;
172
+ continue;
173
+ }
174
+
121
175
  const responseHeaders: Record<string, string> = {};
122
176
  response.headers.forEach((value, key) => {
123
177
  responseHeaders[key] = value;
@@ -131,27 +185,7 @@ export class OpenAIClient {
131
185
  });
132
186
  throw error;
133
187
  }
134
-
135
- if (params.stream) {
136
- return {
137
- data: this.streamChatCompletion(response),
138
- response,
139
- } as unknown as APIResponse<
140
- P extends ChatCompletionCreateParamsStreaming
141
- ? AsyncIterable<ChatCompletionChunk>
142
- : ChatCompletion
143
- >;
144
- } else {
145
- const data = await response.json();
146
- return {
147
- data,
148
- response,
149
- } as unknown as APIResponse<
150
- P extends ChatCompletionCreateParamsStreaming
151
- ? AsyncIterable<ChatCompletionChunk>
152
- : ChatCompletion
153
- >;
154
- }
188
+ throw lastError;
155
189
  }
156
190
 
157
191
  private async *streamChatCompletion(
@@ -182,8 +216,8 @@ export class OpenAIClient {
182
216
  try {
183
217
  const json = JSON.parse(data);
184
218
  yield json as ChatCompletionChunk;
185
- } catch {
186
- // Ignore parse errors for non-JSON lines if any
219
+ } catch (e) {
220
+ logger.error("Failed to parse stream chunk", { error: e, data });
187
221
  }
188
222
  }
189
223
  }
@@ -0,0 +1,133 @@
1
+ import fs from "fs";
2
+ import { PROMPT_HISTORY_FILE, DATA_DIRECTORY } from "./constants.js";
3
+ import { logger } from "./globalLogger.js";
4
+ import { PromptEntry } from "../types/history.js";
5
+
6
+ /**
7
+ * Ensure data directory exists
8
+ */
9
+ const ensureDataDirectory = (): void => {
10
+ try {
11
+ if (!fs.existsSync(DATA_DIRECTORY)) {
12
+ fs.mkdirSync(DATA_DIRECTORY, { recursive: true });
13
+ }
14
+ } catch (error) {
15
+ logger.debug("Failed to create data directory:", error);
16
+ }
17
+ };
18
+
19
+ const MAX_HISTORY_ENTRIES = 1000;
20
+
21
+ export class PromptHistoryManager {
22
+ /**
23
+ * Add a new prompt to history
24
+ */
25
+ static async addEntry(prompt: string): Promise<void> {
26
+ try {
27
+ if (!prompt.trim()) return;
28
+
29
+ ensureDataDirectory();
30
+ const entry: PromptEntry = {
31
+ prompt,
32
+ timestamp: Date.now(),
33
+ };
34
+
35
+ const line = JSON.stringify(entry) + "\n";
36
+ await fs.promises.appendFile(PROMPT_HISTORY_FILE, line, "utf-8");
37
+
38
+ // Periodically trim history file to prevent it from growing too large
39
+ // We do this randomly (1 in 50 chance) to avoid performance hit on every entry
40
+ if (Math.random() < 0.02) {
41
+ await this.trimHistory();
42
+ }
43
+ } catch (error) {
44
+ logger.debug("Failed to add prompt to history:", error);
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Trim history file to MAX_HISTORY_ENTRIES
50
+ */
51
+ private static async trimHistory(): Promise<void> {
52
+ try {
53
+ if (!fs.existsSync(PROMPT_HISTORY_FILE)) return;
54
+
55
+ const data = await fs.promises.readFile(PROMPT_HISTORY_FILE, "utf-8");
56
+ const lines = data.split("\n").filter((line) => line.trim());
57
+
58
+ if (lines.length > MAX_HISTORY_ENTRIES * 1.2) {
59
+ const trimmedLines = lines.slice(-MAX_HISTORY_ENTRIES);
60
+ await fs.promises.writeFile(
61
+ PROMPT_HISTORY_FILE,
62
+ trimmedLines.join("\n") + "\n",
63
+ "utf-8",
64
+ );
65
+ }
66
+ } catch (error) {
67
+ logger.debug("Failed to trim prompt history:", error);
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Get all history entries
73
+ */
74
+ static async getHistory(): Promise<PromptEntry[]> {
75
+ try {
76
+ if (!fs.existsSync(PROMPT_HISTORY_FILE)) {
77
+ return [];
78
+ }
79
+
80
+ const data = await fs.promises.readFile(PROMPT_HISTORY_FILE, "utf-8");
81
+ const lines = data.split("\n").filter((line) => line.trim());
82
+
83
+ const entries: PromptEntry[] = lines
84
+ .map((line) => {
85
+ try {
86
+ return JSON.parse(line) as PromptEntry;
87
+ } catch {
88
+ logger.debug("Failed to parse history line:", line);
89
+ return null;
90
+ }
91
+ })
92
+ .filter((entry): entry is PromptEntry => entry !== null);
93
+
94
+ // Deduplicate by prompt, keeping the most recent one
95
+ const uniqueEntries: PromptEntry[] = [];
96
+ const seenPrompts = new Set<string>();
97
+
98
+ // Process from newest to oldest
99
+ for (let i = entries.length - 1; i >= 0; i--) {
100
+ const entry = entries[i];
101
+ if (!seenPrompts.has(entry.prompt)) {
102
+ uniqueEntries.push(entry);
103
+ seenPrompts.add(entry.prompt);
104
+ }
105
+ }
106
+
107
+ return uniqueEntries;
108
+ } catch (error) {
109
+ logger.debug("Failed to load prompt history:", error);
110
+ return [];
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Search history by query
116
+ */
117
+ static async searchHistory(query: string): Promise<PromptEntry[]> {
118
+ try {
119
+ const history = await this.getHistory();
120
+ if (!query.trim()) {
121
+ return history;
122
+ }
123
+
124
+ const normalizedQuery = query.toLowerCase();
125
+ return history.filter((entry) =>
126
+ entry.prompt.toLowerCase().includes(normalizedQuery),
127
+ );
128
+ } catch (error) {
129
+ logger.debug("Failed to search prompt history:", error);
130
+ return [];
131
+ }
132
+ }
133
+ }
@@ -1,50 +0,0 @@
1
- /**
2
- * Bash command history management module
3
- * Used for persistent storage and searching of bash commands executed by users
4
- */
5
- export interface BashHistoryEntry {
6
- command: string;
7
- timestamp: number;
8
- workdir: string;
9
- }
10
- export interface BashHistory {
11
- commands: BashHistoryEntry[];
12
- version: number;
13
- }
14
- /**
15
- * Load bash history
16
- */
17
- export declare const loadBashHistory: () => BashHistory;
18
- /**
19
- * Save bash history
20
- */
21
- export declare const saveBashHistory: (history: BashHistory) => void;
22
- /**
23
- * Add command to bash history
24
- */
25
- export declare const addBashCommandToHistory: (command: string, workdir: string) => void;
26
- /**
27
- * Search bash history by keywords
28
- */
29
- export declare const searchBashHistory: (query: string, limit?: number) => BashHistoryEntry[];
30
- /**
31
- * Get recently used bash commands
32
- */
33
- export declare const getRecentBashCommands: (limit?: number) => BashHistoryEntry[];
34
- /**
35
- * Delete a specific command from bash history
36
- */
37
- export declare const deleteBashCommandFromHistory: (command: string, workdir?: string) => void;
38
- /**
39
- * Clear bash history
40
- */
41
- export declare const clearBashHistory: () => void;
42
- /**
43
- * Get bash command statistics
44
- */
45
- export declare const getBashCommandStats: () => {
46
- totalCommands: number;
47
- uniqueCommands: number;
48
- workdirs: string[];
49
- };
50
- //# sourceMappingURL=bashHistory.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bashHistory.d.ts","sourceRoot":"","sources":["../../src/utils/bashHistory.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;CACjB;AAkBD;;GAEG;AACH,eAAO,MAAM,eAAe,QAAO,WA+BlC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,SAAS,WAAW,KAAG,IAoBtD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uBAAuB,GAClC,SAAS,MAAM,EACf,SAAS,MAAM,KACd,IA4BF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAC5B,OAAO,MAAM,EACb,QAAO,MAAW,KACjB,gBAAgB,EAiElB,CAAC;AAwBF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAChC,QAAO,MAAW,KACjB,gBAAgB,EAWlB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,4BAA4B,GACvC,SAAS,MAAM,EACf,UAAU,MAAM,KACf,IAmBF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB,QAAO,IAWnC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,QAAO;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,EAAE,CAAC;CAwBpB,CAAC"}