wave-agent-sdk 0.0.6 → 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 (180) hide show
  1. package/dist/agent.d.ts +32 -20
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +209 -24
  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 +248 -132
  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 +13 -16
  16. package/dist/managers/hookManager.d.ts.map +1 -1
  17. package/dist/managers/hookManager.js +81 -44
  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 +41 -24
  22. package/dist/managers/messageManager.d.ts.map +1 -1
  23. package/dist/managers/messageManager.js +168 -49
  24. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  25. package/dist/managers/slashCommandManager.js +9 -3
  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 +190 -19
  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/todoWriteTool.d.ts.map +1 -1
  64. package/dist/tools/todoWriteTool.js +3 -10
  65. package/dist/tools/writeTool.d.ts.map +1 -1
  66. package/dist/tools/writeTool.js +5 -6
  67. package/dist/types/commands.d.ts +4 -0
  68. package/dist/types/commands.d.ts.map +1 -1
  69. package/dist/types/core.d.ts +35 -0
  70. package/dist/types/core.d.ts.map +1 -1
  71. package/dist/types/environment.d.ts +42 -0
  72. package/dist/types/environment.d.ts.map +1 -0
  73. package/dist/types/environment.js +21 -0
  74. package/dist/types/hooks.d.ts +8 -2
  75. package/dist/types/hooks.d.ts.map +1 -1
  76. package/dist/types/hooks.js +8 -2
  77. package/dist/types/index.d.ts +2 -0
  78. package/dist/types/index.d.ts.map +1 -1
  79. package/dist/types/index.js +2 -0
  80. package/dist/types/memoryStore.d.ts +82 -0
  81. package/dist/types/memoryStore.d.ts.map +1 -0
  82. package/dist/types/memoryStore.js +7 -0
  83. package/dist/types/messaging.d.ts +21 -9
  84. package/dist/types/messaging.d.ts.map +1 -1
  85. package/dist/types/messaging.js +5 -1
  86. package/dist/types/session.d.ts +20 -0
  87. package/dist/types/session.d.ts.map +1 -0
  88. package/dist/types/session.js +7 -0
  89. package/dist/utils/bashHistory.d.ts.map +1 -1
  90. package/dist/utils/bashHistory.js +27 -26
  91. package/dist/utils/cacheControlUtils.d.ts +121 -0
  92. package/dist/utils/cacheControlUtils.d.ts.map +1 -0
  93. package/dist/utils/cacheControlUtils.js +367 -0
  94. package/dist/utils/commandPathResolver.d.ts +52 -0
  95. package/dist/utils/commandPathResolver.d.ts.map +1 -0
  96. package/dist/utils/commandPathResolver.js +145 -0
  97. package/dist/utils/configPaths.d.ts +85 -0
  98. package/dist/utils/configPaths.d.ts.map +1 -0
  99. package/dist/utils/configPaths.js +121 -0
  100. package/dist/utils/configResolver.d.ts +37 -10
  101. package/dist/utils/configResolver.d.ts.map +1 -1
  102. package/dist/utils/configResolver.js +127 -23
  103. package/dist/utils/constants.d.ts +1 -1
  104. package/dist/utils/constants.js +1 -1
  105. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  106. package/dist/utils/convertMessagesForAPI.js +8 -13
  107. package/dist/utils/customCommands.d.ts.map +1 -1
  108. package/dist/utils/customCommands.js +66 -21
  109. package/dist/utils/fileUtils.d.ts +15 -0
  110. package/dist/utils/fileUtils.d.ts.map +1 -0
  111. package/dist/utils/fileUtils.js +61 -0
  112. package/dist/utils/globalLogger.d.ts +102 -0
  113. package/dist/utils/globalLogger.d.ts.map +1 -0
  114. package/dist/utils/globalLogger.js +136 -0
  115. package/dist/utils/hookMatcher.d.ts +1 -6
  116. package/dist/utils/hookMatcher.d.ts.map +1 -1
  117. package/dist/utils/mcpUtils.d.ts.map +1 -1
  118. package/dist/utils/mcpUtils.js +25 -3
  119. package/dist/utils/messageOperations.d.ts +27 -27
  120. package/dist/utils/messageOperations.d.ts.map +1 -1
  121. package/dist/utils/messageOperations.js +46 -36
  122. package/dist/utils/pathEncoder.d.ts +104 -0
  123. package/dist/utils/pathEncoder.d.ts.map +1 -0
  124. package/dist/utils/pathEncoder.js +272 -0
  125. package/dist/utils/subagentParser.d.ts.map +1 -1
  126. package/dist/utils/subagentParser.js +2 -1
  127. package/dist/utils/tokenCalculation.d.ts +26 -0
  128. package/dist/utils/tokenCalculation.d.ts.map +1 -0
  129. package/dist/utils/tokenCalculation.js +36 -0
  130. package/package.json +6 -3
  131. package/src/agent.ts +301 -37
  132. package/src/constants/events.ts +38 -0
  133. package/src/index.ts +2 -0
  134. package/src/managers/aiManager.ts +325 -173
  135. package/src/managers/backgroundBashManager.ts +7 -6
  136. package/src/managers/hookManager.ts +106 -84
  137. package/src/managers/liveConfigManager.ts +248 -0
  138. package/src/managers/messageManager.ts +237 -100
  139. package/src/managers/slashCommandManager.ts +9 -7
  140. package/src/managers/subagentManager.ts +284 -22
  141. package/src/services/aiService.ts +474 -83
  142. package/src/services/configurationWatcher.ts +622 -0
  143. package/src/services/fileWatcher.ts +301 -0
  144. package/src/services/hook.ts +538 -47
  145. package/src/services/jsonlHandler.ts +319 -0
  146. package/src/services/memory.ts +92 -12
  147. package/src/services/memoryStore.ts +279 -0
  148. package/src/services/session.ts +381 -157
  149. package/src/tools/bashTool.ts +5 -4
  150. package/src/tools/deleteFileTool.ts +2 -1
  151. package/src/tools/editTool.ts +3 -2
  152. package/src/tools/multiEditTool.ts +4 -3
  153. package/src/tools/readTool.ts +2 -1
  154. package/src/tools/todoWriteTool.ts +3 -11
  155. package/src/tools/writeTool.ts +7 -6
  156. package/src/types/commands.ts +6 -0
  157. package/src/types/core.ts +44 -0
  158. package/src/types/environment.ts +60 -0
  159. package/src/types/hooks.ts +21 -8
  160. package/src/types/index.ts +2 -0
  161. package/src/types/memoryStore.ts +94 -0
  162. package/src/types/messaging.ts +21 -10
  163. package/src/types/session.ts +25 -0
  164. package/src/utils/bashHistory.ts +27 -27
  165. package/src/utils/cacheControlUtils.ts +540 -0
  166. package/src/utils/commandPathResolver.ts +189 -0
  167. package/src/utils/configPaths.ts +163 -0
  168. package/src/utils/configResolver.ts +182 -22
  169. package/src/utils/constants.ts +1 -1
  170. package/src/utils/convertMessagesForAPI.ts +8 -14
  171. package/src/utils/customCommands.ts +90 -22
  172. package/src/utils/fileUtils.ts +65 -0
  173. package/src/utils/globalLogger.ts +145 -0
  174. package/src/utils/hookMatcher.ts +1 -12
  175. package/src/utils/mcpUtils.ts +34 -3
  176. package/src/utils/messageOperations.ts +77 -60
  177. package/src/utils/pathEncoder.ts +379 -0
  178. package/src/utils/subagentParser.ts +2 -1
  179. package/src/utils/tokenCalculation.ts +43 -0
  180. package/src/types/index.ts.backup +0 -357
@@ -1,4 +1,3 @@
1
- import { randomUUID } from "crypto";
2
1
  import {
3
2
  addAssistantMessageToMessages,
4
3
  updateToolBlockInMessage,
@@ -13,20 +12,26 @@ import {
13
12
  addSubagentBlockToMessage,
14
13
  updateSubagentBlockInMessage,
15
14
  removeLastUserMessage,
15
+ UserMessageParams,
16
16
  type AddSubagentBlockParams,
17
17
  type UpdateSubagentBlockParams,
18
18
  type AgentToolBlockUpdateParams,
19
19
  } from "../utils/messageOperations.js";
20
+ import type { SubagentConfiguration } from "../utils/subagentParser.js";
20
21
  import type { Logger, Message, Usage } from "../types/index.js";
22
+ import { join } from "path";
21
23
  import {
22
- cleanupExpiredSessions,
23
- getLatestSession,
24
- loadSession,
25
- saveSession,
24
+ cleanupExpiredSessionsFromJsonl,
25
+ getLatestSessionFromJsonl,
26
+ loadSessionFromJsonl,
27
+ appendMessages,
28
+ createSession,
29
+ generateSessionId,
26
30
  SessionData,
27
- getSessionFilePath,
31
+ SESSION_DIR,
28
32
  } from "../services/session.js";
29
33
  import { ChatCompletionMessageFunctionToolCall } from "openai/resources.js";
34
+ import { pathEncoder } from "../utils/pathEncoder.js";
30
35
 
31
36
  export interface MessageManagerCallbacks {
32
37
  onMessagesChange?: (messages: Message[]) => void;
@@ -35,14 +40,11 @@ export interface MessageManagerCallbacks {
35
40
  onUserInputHistoryChange?: (history: string[]) => void;
36
41
  onUsagesChange?: (usages: Usage[]) => void;
37
42
  // Incremental callback
38
- onUserMessageAdded?: (
39
- content: string,
40
- images?: Array<{ path: string; mimeType: string }>,
41
- ) => void;
42
- onAssistantMessageAdded?: (
43
- content?: string,
44
- toolCalls?: ChatCompletionMessageFunctionToolCall[],
45
- ) => void;
43
+ onUserMessageAdded?: (params: UserMessageParams) => void;
44
+ // MODIFIED: Remove arguments for separation of concerns
45
+ onAssistantMessageAdded?: () => void;
46
+ // NEW: Streaming content callback - FR-001: receives chunk and accumulated content
47
+ onAssistantContentUpdated?: (chunk: string, accumulated: string) => void;
46
48
  onToolBlockUpdated?: (params: AgentToolBlockUpdateParams) => void;
47
49
  onDiffBlockAdded?: (filePath: string, diffResult: string) => void;
48
50
  onErrorBlockAdded?: (error: string) => void;
@@ -54,12 +56,6 @@ export interface MessageManagerCallbacks {
54
56
  type: "project" | "user",
55
57
  storagePath: string,
56
58
  ) => void;
57
- // Custom command callback
58
- onCustomCommandAdded?: (
59
- commandName: string,
60
- content: string,
61
- originalInput?: string,
62
- ) => void;
63
59
  // Bash command callback
64
60
  onAddCommandOutputMessage?: (command: string) => void;
65
61
  onUpdateCommandOutputMessage?: (command: string, output: string) => void;
@@ -75,7 +71,6 @@ export interface MessageManagerCallbacks {
75
71
  ) => void;
76
72
  onSubAgentBlockUpdated?: (
77
73
  subagentId: string,
78
- messages: Message[],
79
74
  status: "active" | "completed" | "error" | "aborted",
80
75
  ) => void;
81
76
  }
@@ -84,13 +79,9 @@ export interface MessageManagerOptions {
84
79
  callbacks: MessageManagerCallbacks;
85
80
  workdir: string;
86
81
  logger?: Logger;
87
-
88
- // New: Optional session directory override
89
- /**
90
- * Custom session directory path
91
- * @default join(homedir(), ".wave", "sessions")
92
- */
93
- sessionDir?: string;
82
+ sessionType?: "main" | "subagent";
83
+ parentSessionId?: string;
84
+ subagentType?: string;
94
85
  }
95
86
 
96
87
  export class MessageManager {
@@ -101,20 +92,32 @@ export class MessageManager {
101
92
  private userInputHistory: string[];
102
93
  private sessionStartTime: string;
103
94
  private workdir: string;
95
+ private encodedWorkdir: string; // Cached encoded workdir
104
96
  private logger?: Logger; // Add optional logger property
105
97
  private callbacks: MessageManagerCallbacks;
106
- private sessionDir?: string; // Add session directory property
98
+ private transcriptPath: string; // Cached transcript path
99
+ private savedMessageCount: number; // Track how many messages have been saved to prevent duplication
100
+ private sessionType: "main" | "subagent";
101
+ private parentSessionId?: string;
102
+ private subagentType?: string;
107
103
 
108
104
  constructor(options: MessageManagerOptions) {
109
- this.sessionId = randomUUID();
105
+ this.sessionId = generateSessionId();
110
106
  this.messages = [];
111
107
  this.latestTotalTokens = 0;
112
108
  this.userInputHistory = [];
113
109
  this.sessionStartTime = new Date().toISOString();
114
110
  this.workdir = options.workdir;
111
+ this.encodedWorkdir = pathEncoder.encodeSync(this.workdir); // Cache encoded workdir
115
112
  this.callbacks = options.callbacks;
116
113
  this.logger = options.logger;
117
- this.sessionDir = options.sessionDir;
114
+ this.savedMessageCount = 0; // Initialize saved message count tracker
115
+ this.sessionType = options.sessionType || "main";
116
+ this.parentSessionId = options.parentSessionId;
117
+ this.subagentType = options.subagentType;
118
+
119
+ // Compute and cache the transcript path
120
+ this.transcriptPath = this.computeTranscriptPath();
118
121
  }
119
122
 
120
123
  // Getter methods
@@ -134,18 +137,63 @@ export class MessageManager {
134
137
  return [...this.userInputHistory];
135
138
  }
136
139
 
140
+ public getSessionStartTime(): string {
141
+ return this.sessionStartTime;
142
+ }
143
+
144
+ public getWorkdir(): string {
145
+ return this.workdir;
146
+ }
147
+
148
+ public getSessionDir(): string {
149
+ return SESSION_DIR;
150
+ }
151
+
137
152
  public getTranscriptPath(): string {
138
- return getSessionFilePath(this.sessionId, this.sessionDir);
153
+ return this.transcriptPath;
154
+ }
155
+
156
+ /**
157
+ * Compute the transcript path using cached encoded workdir
158
+ * Called during construction and when sessionId changes
159
+ */
160
+ private computeTranscriptPath(): string {
161
+ const baseDir = join(SESSION_DIR, this.encodedWorkdir);
162
+
163
+ // All sessions now go in the same directory
164
+ // Session type is determined by metadata, not file path
165
+ return join(baseDir, `${this.sessionId}.jsonl`);
139
166
  }
140
167
 
141
168
  // Setter methods, will trigger callbacks
142
169
  public setSessionId(sessionId: string): void {
143
170
  if (this.sessionId !== sessionId) {
144
171
  this.sessionId = sessionId;
172
+ // Reset saved message count for new session
173
+ this.savedMessageCount = 0;
174
+ // Recompute transcript path since session ID changed
175
+ this.transcriptPath = this.computeTranscriptPath();
145
176
  this.callbacks.onSessionIdChange?.(sessionId);
146
177
  }
147
178
  }
148
179
 
180
+ /**
181
+ * Create session if needed (async helper)
182
+ */
183
+ private async createSessionIfNeeded(): Promise<void> {
184
+ try {
185
+ await createSession(
186
+ this.sessionId,
187
+ this.workdir,
188
+ this.sessionType,
189
+ this.parentSessionId,
190
+ this.subagentType,
191
+ );
192
+ } catch (error) {
193
+ this.logger?.error("Failed to create session:", error);
194
+ }
195
+ }
196
+
149
197
  public setMessages(messages: Message[]): void {
150
198
  this.messages = [...messages];
151
199
  this.callbacks.onMessagesChange?.([...messages]);
@@ -156,14 +204,29 @@ export class MessageManager {
156
204
  */
157
205
  public async saveSession(): Promise<void> {
158
206
  try {
159
- await saveSession(
207
+ // Only save messages that haven't been saved yet
208
+ const unsavedMessages = this.messages.slice(this.savedMessageCount);
209
+
210
+ if (unsavedMessages.length === 0) {
211
+ // No new messages to save
212
+ return;
213
+ }
214
+
215
+ // Create session if needed (only when we have messages to save)
216
+ if (this.savedMessageCount === 0) {
217
+ // This is the first time saving messages, so create the session
218
+ await this.createSessionIfNeeded();
219
+ }
220
+
221
+ // Use JSONL format for new sessions
222
+ await appendMessages(
160
223
  this.sessionId,
161
- this.messages,
224
+ unsavedMessages, // Only append new messages
162
225
  this.workdir,
163
- this.latestTotalTokens,
164
- this.sessionStartTime,
165
- this.sessionDir,
166
226
  );
227
+
228
+ // Update the saved message count
229
+ this.savedMessageCount = this.messages.length;
167
230
  } catch (error) {
168
231
  this.logger?.error("Failed to save session:", error);
169
232
  }
@@ -177,11 +240,9 @@ export class MessageManager {
177
240
  continueLastSession?: boolean,
178
241
  ): Promise<void> {
179
242
  // Clean up expired sessions first
180
- try {
181
- await cleanupExpiredSessions(this.workdir, this.sessionDir);
182
- } catch (error) {
243
+ cleanupExpiredSessionsFromJsonl(this.workdir).catch((error) => {
183
244
  this.logger?.warn("Failed to cleanup expired sessions:", error);
184
- }
245
+ });
185
246
 
186
247
  if (!restoreSessionId && !continueLastSession) {
187
248
  return;
@@ -191,16 +252,18 @@ export class MessageManager {
191
252
  let sessionToRestore: SessionData | null = null;
192
253
 
193
254
  if (restoreSessionId) {
194
- sessionToRestore = await loadSession(restoreSessionId, this.sessionDir);
255
+ // Use only JSONL format - no legacy support
256
+ sessionToRestore = await loadSessionFromJsonl(
257
+ restoreSessionId,
258
+ this.workdir,
259
+ );
195
260
  if (!sessionToRestore) {
196
261
  console.error(`Session not found: ${restoreSessionId}`);
197
262
  process.exit(1);
198
263
  }
199
264
  } else if (continueLastSession) {
200
- sessionToRestore = await getLatestSession(
201
- this.workdir,
202
- this.sessionDir,
203
- );
265
+ // Use only JSONL format - no legacy support
266
+ sessionToRestore = await getLatestSessionFromJsonl(this.workdir);
204
267
  if (!sessionToRestore) {
205
268
  console.error(
206
269
  `No previous session found for workdir: ${this.workdir}`,
@@ -213,11 +276,7 @@ export class MessageManager {
213
276
  console.log(`Restoring session: ${sessionToRestore.id}`);
214
277
 
215
278
  // Initialize from session data
216
- this.initializeFromSession(
217
- sessionToRestore.id,
218
- sessionToRestore.messages,
219
- sessionToRestore.metadata.latestTotalTokens,
220
- );
279
+ this.initializeFromSession(sessionToRestore);
221
280
  }
222
281
  } catch (error) {
223
282
  console.error("Failed to restore session:", error);
@@ -243,23 +302,27 @@ export class MessageManager {
243
302
  public clearMessages(): void {
244
303
  this.setMessages([]);
245
304
  this.setUserInputHistory([]);
246
- this.setSessionId(randomUUID());
305
+ this.setSessionId(generateSessionId());
247
306
  this.setlatestTotalTokens(0);
248
307
  this.sessionStartTime = new Date().toISOString();
308
+ this.savedMessageCount = 0; // Reset saved message count
249
309
  }
250
310
 
251
311
  // Initialize state from session data
252
- public initializeFromSession(
253
- sessionId: string,
254
- messages: Message[],
255
- latestTotalTokens: number,
256
- ): void {
257
- this.setSessionId(sessionId);
258
- this.setMessages([...messages]);
259
- this.setlatestTotalTokens(latestTotalTokens);
312
+ public initializeFromSession(sessionData: SessionData): void {
313
+ this.setSessionId(sessionData.id);
314
+ this.setMessages([...sessionData.messages]);
315
+ this.setlatestTotalTokens(sessionData.metadata.latestTotalTokens);
316
+
317
+ // Restore the original session start time
318
+ this.sessionStartTime = sessionData.metadata.startedAt;
260
319
 
261
320
  // Extract user input history from session messages
262
- this.setUserInputHistory(extractUserInputHistory(messages));
321
+ this.setUserInputHistory(extractUserInputHistory(sessionData.messages));
322
+
323
+ // Set saved message count to the number of loaded messages since they're already saved
324
+ // This must be done after setSessionId which resets it to 0
325
+ this.savedMessageCount = sessionData.messages.length;
263
326
  }
264
327
 
265
328
  // Add to input history
@@ -281,68 +344,92 @@ export class MessageManager {
281
344
  }
282
345
 
283
346
  // Encapsulated message operation functions
284
- public addUserMessage(
285
- content: string,
286
- images?: Array<{ path: string; mimeType: string }>,
287
- ): void {
347
+ public addUserMessage(params: UserMessageParams): void {
288
348
  const newMessages = addUserMessageToMessages({
289
349
  messages: this.messages,
290
- content,
291
- images,
350
+ ...params,
292
351
  });
293
352
  this.setMessages(newMessages);
294
- this.callbacks.onUserMessageAdded?.(content, images);
295
- }
353
+ this.callbacks.onUserMessageAdded?.(params);
296
354
 
297
- public addCustomCommandMessage(
298
- commandName: string,
299
- content: string,
300
- originalInput?: string,
301
- ): void {
302
- const newMessages = addUserMessageToMessages({
303
- messages: this.messages,
304
- content: "", // Empty content, as we will use CustomCommandBlock
305
- customCommandBlock: {
306
- type: "custom_command",
307
- commandName,
308
- content,
309
- originalInput,
310
- },
311
- });
312
- this.setMessages(newMessages);
313
- this.callbacks.onCustomCommandAdded?.(commandName, content, originalInput);
355
+ // Note: Subagent-specific callbacks are now handled by SubagentManager
314
356
  }
315
357
 
316
358
  public addAssistantMessage(
317
359
  content?: string,
318
360
  toolCalls?: ChatCompletionMessageFunctionToolCall[],
319
361
  usage?: Usage,
362
+ metadata?: Record<string, unknown>,
320
363
  ): void {
364
+ const metadataRecord = metadata
365
+ ? Object.fromEntries(
366
+ Object.entries(metadata).filter(([, value]) => value !== undefined),
367
+ )
368
+ : undefined;
369
+
321
370
  const newMessages = addAssistantMessageToMessages(
322
371
  this.messages,
323
372
  content,
324
373
  toolCalls,
325
374
  usage,
375
+ metadataRecord,
326
376
  );
327
377
  this.setMessages(newMessages);
328
- this.callbacks.onAssistantMessageAdded?.(content, toolCalls);
378
+ this.callbacks.onAssistantMessageAdded?.();
379
+
380
+ // Note: Subagent-specific callbacks are now handled by SubagentManager
381
+ }
382
+
383
+ public mergeAssistantMetadata(metadata: Record<string, unknown>): void {
384
+ if (!metadata || Object.keys(metadata).length === 0) {
385
+ return;
386
+ }
387
+
388
+ const newMessages = [...this.messages];
389
+ for (let i = newMessages.length - 1; i >= 0; i--) {
390
+ const message = newMessages[i];
391
+ if (message.role === "assistant") {
392
+ const mergedMetadata = {
393
+ ...(message.metadata || {}),
394
+ } as Record<string, unknown>;
395
+
396
+ for (const [key, value] of Object.entries(metadata)) {
397
+ if (value === undefined) {
398
+ continue;
399
+ }
400
+ mergedMetadata[key] = value;
401
+ }
402
+
403
+ if (Object.keys(mergedMetadata).length === 0) {
404
+ return;
405
+ }
406
+
407
+ message.metadata = mergedMetadata;
408
+ this.setMessages(newMessages);
409
+ return;
410
+ }
411
+ }
329
412
  }
330
413
 
331
414
  public updateToolBlock(params: AgentToolBlockUpdateParams): void {
332
415
  const newMessages = updateToolBlockInMessage({
333
416
  messages: this.messages,
334
- id: params.toolId,
335
- parameters: params.args || "",
417
+ id: params.id,
418
+ parameters: params.parameters,
336
419
  result: params.result,
337
420
  success: params.success,
338
421
  error: params.error,
339
- isRunning: params.isRunning,
422
+ stage: params.stage,
340
423
  name: params.name,
341
424
  shortResult: params.shortResult,
425
+ images: params.images,
342
426
  compactParams: params.compactParams,
427
+ parametersChunk: params.parametersChunk,
343
428
  });
344
429
  this.setMessages(newMessages);
345
430
  this.callbacks.onToolBlockUpdated?.(params);
431
+
432
+ // Note: Subagent-specific callbacks are now handled by SubagentManager
346
433
  }
347
434
 
348
435
  public addDiffBlock(
@@ -373,6 +460,7 @@ export class MessageManager {
373
460
  public compressMessagesAndUpdateSession(
374
461
  insertIndex: number,
375
462
  compressedContent: string,
463
+ usage?: Usage,
376
464
  ): void {
377
465
  const currentMessages = this.messages;
378
466
 
@@ -383,8 +471,10 @@ export class MessageManager {
383
471
  {
384
472
  type: "compress",
385
473
  content: compressedContent,
474
+ sessionId: this.sessionId,
386
475
  },
387
476
  ],
477
+ ...(usage && { usage }),
388
478
  };
389
479
 
390
480
  // Convert negative index to positive index
@@ -398,7 +488,7 @@ export class MessageManager {
398
488
  ];
399
489
 
400
490
  // Update sessionId
401
- this.setSessionId(randomUUID());
491
+ this.setSessionId(generateSessionId());
402
492
 
403
493
  // Set new message list
404
494
  this.setMessages(newMessages);
@@ -458,8 +548,9 @@ export class MessageManager {
458
548
  public addSubagentBlock(
459
549
  subagentId: string,
460
550
  subagentName: string,
551
+ sessionId: string,
552
+ configuration: SubagentConfiguration,
461
553
  status: "active" | "completed" | "error" = "active",
462
- subagentMessages: Message[] = [],
463
554
  parameters: {
464
555
  description: string;
465
556
  prompt: string;
@@ -470,8 +561,9 @@ export class MessageManager {
470
561
  messages: this.messages,
471
562
  subagentId,
472
563
  subagentName,
564
+ sessionId,
473
565
  status,
474
- subagentMessages,
566
+ configuration,
475
567
  };
476
568
  const updatedMessages = addSubagentBlockToMessage(params);
477
569
  this.setMessages(updatedMessages);
@@ -482,7 +574,7 @@ export class MessageManager {
482
574
  subagentId: string,
483
575
  updates: Partial<{
484
576
  status: "active" | "completed" | "error" | "aborted";
485
- messages: Message[];
577
+ sessionId: string;
486
578
  }>,
487
579
  ): void {
488
580
  const updatedMessages = updateSubagentBlockInMessage(
@@ -495,13 +587,8 @@ export class MessageManager {
495
587
  messages: this.messages,
496
588
  subagentId,
497
589
  status: updates.status || "active",
498
- subagentMessages: updates.messages || [],
499
590
  };
500
- this.callbacks.onSubAgentBlockUpdated?.(
501
- params.subagentId,
502
- params.messages,
503
- params.status,
504
- );
591
+ this.callbacks.onSubAgentBlockUpdated?.(params.subagentId, params.status);
505
592
  }
506
593
 
507
594
  /**
@@ -517,6 +604,56 @@ export class MessageManager {
517
604
  this.callbacks.onUsagesChange?.(usages);
518
605
  }
519
606
 
607
+ /**
608
+ * Update the current assistant message content during streaming
609
+ * This method updates the last assistant message's content without creating a new message
610
+ * FR-001: Tracks and provides both chunk (new content) and accumulated (total content)
611
+ */
612
+ public updateCurrentMessageContent(newAccumulatedContent: string): void {
613
+ if (this.messages.length === 0) return;
614
+
615
+ const lastMessage = this.messages[this.messages.length - 1];
616
+ if (lastMessage.role !== "assistant") return;
617
+
618
+ // Get the current content to calculate the chunk
619
+ const textBlockIndex = lastMessage.blocks.findIndex(
620
+ (block) => block.type === "text",
621
+ );
622
+ const currentContent =
623
+ textBlockIndex >= 0
624
+ ? (
625
+ lastMessage.blocks[textBlockIndex] as {
626
+ type: "text";
627
+ content: string;
628
+ }
629
+ ).content || ""
630
+ : "";
631
+
632
+ // Calculate the chunk (new content since last update)
633
+ const chunk = newAccumulatedContent.slice(currentContent.length);
634
+
635
+ if (textBlockIndex >= 0) {
636
+ // Update existing text block
637
+ lastMessage.blocks[textBlockIndex] = {
638
+ type: "text",
639
+ content: newAccumulatedContent,
640
+ };
641
+ } else {
642
+ // Add new text block if none exists
643
+ lastMessage.blocks.unshift({
644
+ type: "text",
645
+ content: newAccumulatedContent,
646
+ });
647
+ }
648
+
649
+ // FR-001: Trigger callbacks with chunk and accumulated content
650
+ this.callbacks.onAssistantContentUpdated?.(chunk, newAccumulatedContent);
651
+
652
+ // Note: Subagent-specific callbacks are now handled by SubagentManager
653
+
654
+ this.callbacks.onMessagesChange?.([...this.messages]); // Still need to notify of changes
655
+ }
656
+
520
657
  /**
521
658
  * Remove the last user message from the conversation
522
659
  * Used for hook error handling when the user prompt needs to be erased
@@ -52,9 +52,12 @@ export class SlashCommandManager {
52
52
  this.registerCommand({
53
53
  id: "clear",
54
54
  name: "clear",
55
- description: "Clear the chat session",
55
+ description: "Clear the chat session and terminal",
56
56
  handler: () => {
57
+ // Clear chat messages
57
58
  this.messageManager.clearMessages();
59
+ // Clear terminal screen
60
+ process.stdout.write("\x1Bc");
58
61
  },
59
62
  });
60
63
  }
@@ -257,15 +260,14 @@ export class SlashCommandManager {
257
260
  ? replaceBashCommandsWithOutput(processedContent, bashResults)
258
261
  : processedContent;
259
262
 
260
- // Add custom command block to show the command being executed
263
+ // Add custom command message to show the command being executed
261
264
  const originalInput = args
262
265
  ? `/${commandName} ${args}`
263
266
  : `/${commandName}`;
264
- this.messageManager.addCustomCommandMessage(
265
- commandName,
266
- finalContent,
267
- originalInput,
268
- );
267
+ this.messageManager.addUserMessage({
268
+ content: originalInput,
269
+ customCommandContent: finalContent,
270
+ });
269
271
 
270
272
  // Execute the AI conversation with custom configuration
271
273
  await this.aiManager.sendAIMessage({