agentstudio 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/.cc-sessions/ppt-editor/session_1756253549429_uau1hm6lh.json +665 -0
  2. package/.cc-sessions/ppt-editor/session_1756257240855_v0wa26mde.json +394 -0
  3. package/README.md +39 -41
  4. package/dist/bin/agentstudio.js +4 -27
  5. package/dist/bin/agentstudio.js.map +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +121 -52
  9. package/dist/index.js.map +1 -1
  10. package/dist/routes/agents.d.ts.map +1 -1
  11. package/dist/routes/agents.js +18 -19
  12. package/dist/routes/agents.js.map +1 -1
  13. package/dist/routes/commands.d.ts +4 -0
  14. package/dist/routes/commands.d.ts.map +1 -0
  15. package/dist/routes/commands.js +395 -0
  16. package/dist/routes/commands.js.map +1 -0
  17. package/dist/routes/projects.d.ts +4 -0
  18. package/dist/routes/projects.d.ts.map +1 -0
  19. package/dist/routes/projects.js +528 -0
  20. package/dist/routes/projects.js.map +1 -0
  21. package/dist/routes/sessions.d.ts +4 -0
  22. package/dist/routes/sessions.d.ts.map +1 -0
  23. package/dist/routes/sessions.js +733 -0
  24. package/dist/routes/sessions.js.map +1 -0
  25. package/dist/routes/settings.d.ts +4 -0
  26. package/dist/routes/settings.d.ts.map +1 -0
  27. package/dist/routes/settings.js +658 -0
  28. package/dist/routes/settings.js.map +1 -0
  29. package/dist/routes/subagents.d.ts +4 -0
  30. package/dist/routes/subagents.d.ts.map +1 -0
  31. package/dist/routes/subagents.js +326 -0
  32. package/dist/routes/subagents.js.map +1 -0
  33. package/docs/chat-clean-1.svg +1 -0
  34. package/docs/chat-clean.md +60 -0
  35. package/docs/chat-comprehensive-1.svg +1 -0
  36. package/docs/chat-comprehensive.md +166 -0
  37. package/docs/chat_api_sequence_diagram.md +58 -0
  38. package/docs/command-detection-logic.md +306 -0
  39. package/docs/command-detection-sequence.md +186 -0
  40. package/frontend/dist/assets/AgentsPage-Dqb_aqAA.js +1 -0
  41. package/frontend/dist/assets/ChatPage-BvQmXfcP.css +1 -0
  42. package/frontend/dist/assets/ChatPage-L8Paywyc.js +91 -0
  43. package/frontend/dist/assets/CommandForm-DLl7EIMS.js +7 -0
  44. package/frontend/dist/assets/CommandsPage-Bzavq0Ec.js +1 -0
  45. package/frontend/dist/assets/DashboardPage-B3o4AYFT.js +15 -0
  46. package/frontend/dist/assets/FileBrowser-DL3ayaqb.js +1 -0
  47. package/frontend/dist/assets/GeneralSettingsPage-CBN_de-V.js +1 -0
  48. package/frontend/dist/assets/LandingPage-Dl4ioKos.js +1 -0
  49. package/frontend/dist/assets/LoginPage-4QqRdiSi.js +12 -0
  50. package/frontend/dist/assets/McpPage-CY3tYiqj.js +39 -0
  51. package/frontend/dist/assets/MemorySettingsPage-DGxrok5K.js +1 -0
  52. package/frontend/dist/assets/ProjectSelector-hgmGYVFh.js +1 -0
  53. package/frontend/dist/assets/ProjectsPage-D399IM0c.js +14 -0
  54. package/frontend/dist/assets/SettingsLayout-CL_K-lzJ.js +1 -0
  55. package/frontend/dist/assets/SubagentForm-DXtTTIKg.js +7 -0
  56. package/frontend/dist/assets/SubagentsPage-Chbhj8p2.js +1 -0
  57. package/frontend/dist/assets/ToastTestPage-DT4wuN5C.js +1 -0
  58. package/frontend/dist/assets/UnifiedToolSelector-CsM9qBvs.js +1 -0
  59. package/frontend/dist/assets/VersionSettingsPage-74Q-LVgA.js +5 -0
  60. package/frontend/dist/assets/agents-ClAzIJTw.js +1 -0
  61. package/frontend/dist/assets/agents-DwCY2K8p.css +1 -0
  62. package/frontend/dist/assets/authFetch-BATQyPG5.js +1 -0
  63. package/frontend/dist/assets/data-structures-DLJedtzx.js +27 -0
  64. package/frontend/dist/assets/dateFormat-CXa8VnEC.js +1 -0
  65. package/frontend/dist/assets/index-B9YHa7XT.css +1 -0
  66. package/frontend/dist/assets/index-B_CTNvca.js +268 -0
  67. package/frontend/dist/assets/monaco-editor-C7Z4sOhS.js +19 -0
  68. package/frontend/dist/assets/syntax-highlighting-YWvMU4Hm.js +24 -0
  69. package/frontend/dist/assets/tabManager-DV8urRBM.js +30 -0
  70. package/frontend/dist/assets/table-D6q1rytw.js +1 -0
  71. package/frontend/dist/assets/tools-C4EPanYi.js +1 -0
  72. package/frontend/dist/assets/ui-components-Cw21Epuw.js +481 -0
  73. package/frontend/dist/assets/useAgents-DwnOE1_k.js +2 -0
  74. package/frontend/dist/assets/useClaudeVersions-CQdGnCqv.js +1 -0
  75. package/frontend/dist/assets/useCommands-CCVaurbt.js +1 -0
  76. package/frontend/dist/cc-studio.png +0 -0
  77. package/frontend/dist/index.html +68 -84
  78. package/frontend/dist/vite.svg +1 -0
  79. package/package.json +27 -27
  80. package/scripts/README.md +76 -0
  81. package/scripts/fix-project-names.js +113 -0
  82. package/scripts/migrate-projects.js +159 -0
  83. package/src/bin/agentstudio.ts +90 -121
  84. package/src/index.ts +139 -57
  85. package/src/routes/agents.ts +28 -29
  86. package/src/routes/{commands.ts.bak → commands.ts} +1 -1
  87. package/src/routes/{projects.ts.bak → projects.ts} +3 -3
  88. package/src/routes/{sessions.ts.bak → sessions.ts} +2 -2
  89. package/src/routes/{settings.ts.bak → settings.ts} +11 -11
  90. package/src/routes/{subagents.ts.bak → subagents.ts} +1 -1
  91. package/tsconfig.json +13 -12
  92. package/dist/types/claude-history.d.ts +0 -48
  93. package/dist/types/claude-history.d.ts.map +0 -1
  94. package/dist/types/claude-history.js +0 -2
  95. package/dist/types/claude-history.js.map +0 -1
  96. package/dist/types/claude-versions.d.ts +0 -31
  97. package/dist/types/claude-versions.d.ts.map +0 -1
  98. package/dist/types/claude-versions.js +0 -2
  99. package/dist/types/claude-versions.js.map +0 -1
  100. package/dist/types/commands.d.ts +0 -32
  101. package/dist/types/commands.d.ts.map +0 -1
  102. package/dist/types/commands.js +0 -2
  103. package/dist/types/commands.js.map +0 -1
  104. package/dist/types/index.d.ts +0 -81
  105. package/dist/types/index.d.ts.map +0 -1
  106. package/dist/types/index.js +0 -150
  107. package/dist/types/index.js.map +0 -1
  108. package/dist/types/subagents.d.ts +0 -88
  109. package/dist/types/subagents.d.ts.map +0 -1
  110. package/dist/types/subagents.js +0 -2
  111. package/dist/types/subagents.js.map +0 -1
  112. package/dist/utils/agentStorage.d.ts +0 -19
  113. package/dist/utils/agentStorage.d.ts.map +0 -1
  114. package/dist/utils/agentStorage.js +0 -110
  115. package/dist/utils/agentStorage.js.map +0 -1
  116. package/dist/utils/claudeVersionStorage.d.ts +0 -33
  117. package/dist/utils/claudeVersionStorage.d.ts.map +0 -1
  118. package/dist/utils/claudeVersionStorage.js +0 -168
  119. package/dist/utils/claudeVersionStorage.js.map +0 -1
  120. package/dist/utils/projectMetadataStorage.d.ts +0 -21
  121. package/dist/utils/projectMetadataStorage.d.ts.map +0 -1
  122. package/dist/utils/projectMetadataStorage.js +0 -68
  123. package/dist/utils/projectMetadataStorage.js.map +0 -1
  124. package/src/types/claude-history.ts +0 -50
  125. package/src/types/claude-versions.ts +0 -33
  126. package/src/types/commands.ts +0 -35
  127. package/src/types/index.ts +0 -248
  128. package/src/types/subagents.ts +0 -106
  129. package/src/utils/agentStorage.ts +0 -126
  130. package/src/utils/claudeVersionStorage.ts +0 -199
  131. package/src/utils/projectMetadataStorage.ts +0 -86
@@ -0,0 +1,733 @@
1
+ import express from 'express';
2
+ import * as path from 'path';
3
+ import * as fs from 'fs';
4
+ import * as os from 'os';
5
+ import { AgentStorage } from '@agentstudio/shared/utils/agentStorage';
6
+ import { sessionManager } from '../services/sessionManager.js';
7
+ const router = express.Router();
8
+ // Storage instances
9
+ const globalAgentStorage = new AgentStorage();
10
+ // Helper functions for reading Claude Code history from ~/.claude/projects
11
+ function convertProjectPathToClaudeFormat(projectPath) {
12
+ // Convert path like /Users/kongjie/claude-code-projects/ppt-editor-project-2025-08-27-00-12
13
+ // to: -Users-kongjie-claude-code-projects-ppt-editor-project-2025-08-27-00-12
14
+ return projectPath.replace(/\//g, '-');
15
+ }
16
+ // Function to get AgentStorage instance for specific project directory
17
+ const getAgentStorageForRequest = (req) => {
18
+ const projectPath = req.query.projectPath || req.body?.projectPath;
19
+ const workingDir = projectPath || process.cwd();
20
+ return new AgentStorage(workingDir);
21
+ };
22
+ // Process compact context messages - detect and convert the 4-message or 5-message pattern
23
+ function processCompactContextMessages(messages) {
24
+ const processedMessages = [];
25
+ let i = 0;
26
+ while (i < messages.length) {
27
+ const currentMsg = messages[i];
28
+ // Case 1a: New format (5-message pattern) - Check for compact_boundary message first
29
+ if (currentMsg.type === 'system' &&
30
+ currentMsg.subtype === 'compact_boundary' &&
31
+ currentMsg.parentUuid === null &&
32
+ i + 4 < messages.length) {
33
+ const summaryMsg = messages[i + 1];
34
+ const metaMsg = messages[i + 2];
35
+ const commandMsg = messages[i + 3];
36
+ const outputMsg = messages[i + 4];
37
+ // Verify this is the new 5-message compact pattern
38
+ if (summaryMsg.isCompactSummary &&
39
+ summaryMsg.parentUuid === currentMsg.uuid &&
40
+ metaMsg.isMeta === true &&
41
+ commandMsg.type === 'user' &&
42
+ commandMsg.message?.content &&
43
+ typeof commandMsg.message.content === 'string' &&
44
+ commandMsg.message.content.includes('<command-name>/compact</command-name>') &&
45
+ commandMsg.parentUuid === metaMsg.uuid &&
46
+ outputMsg.type === 'user' &&
47
+ outputMsg.message?.content &&
48
+ typeof outputMsg.message.content === 'string' &&
49
+ outputMsg.message.content.includes('<local-command-stdout>') &&
50
+ outputMsg.parentUuid === commandMsg.uuid) {
51
+ // Create synthetic user command message
52
+ const userCommandMessage = {
53
+ type: 'user',
54
+ uuid: `synthetic_cmd_${commandMsg.uuid}`,
55
+ timestamp: commandMsg.timestamp,
56
+ sessionId: commandMsg.sessionId,
57
+ parentUuid: currentMsg.parentUuid,
58
+ message: {
59
+ role: 'user',
60
+ content: '/compact'
61
+ },
62
+ isCompactCommand: true
63
+ };
64
+ // Create synthetic AI response with compressed content
65
+ const aiResponseMessage = {
66
+ type: 'assistant',
67
+ uuid: `synthetic_ai_${summaryMsg.uuid}`,
68
+ timestamp: summaryMsg.timestamp,
69
+ sessionId: summaryMsg.sessionId,
70
+ parentUuid: userCommandMessage.uuid,
71
+ message: {
72
+ role: 'assistant',
73
+ content: extractContentFromClaudeMessage(summaryMsg, messages) || '会话上下文已压缩'
74
+ },
75
+ isCompactSummary: true
76
+ };
77
+ processedMessages.push(userCommandMessage, aiResponseMessage);
78
+ i += 5; // Skip all 5 messages
79
+ continue;
80
+ }
81
+ }
82
+ // Case 1b: Old format (4-message pattern) - Check for isCompactSummary with parentUuid === null
83
+ if (currentMsg.isCompactSummary && currentMsg.parentUuid === null && i + 3 < messages.length) {
84
+ const metaMsg = messages[i + 1];
85
+ const commandMsg = messages[i + 2];
86
+ const outputMsg = messages[i + 3];
87
+ // Verify this is the old 4-message manual compact pattern
88
+ if (metaMsg.isMeta === true &&
89
+ commandMsg.type === 'user' &&
90
+ commandMsg.message?.content &&
91
+ typeof commandMsg.message.content === 'string' &&
92
+ commandMsg.message.content.includes('<command-name>/compact</command-name>') &&
93
+ commandMsg.parentUuid === metaMsg.uuid &&
94
+ outputMsg.type === 'user' &&
95
+ outputMsg.message?.content &&
96
+ typeof outputMsg.message.content === 'string' &&
97
+ outputMsg.message.content.includes('<local-command-stdout>') &&
98
+ outputMsg.parentUuid === commandMsg.uuid) {
99
+ // Create synthetic user command message
100
+ const userCommandMessage = {
101
+ type: 'user',
102
+ uuid: `synthetic_cmd_${commandMsg.uuid}`,
103
+ timestamp: commandMsg.timestamp,
104
+ sessionId: commandMsg.sessionId,
105
+ parentUuid: currentMsg.parentUuid,
106
+ message: {
107
+ role: 'user',
108
+ content: '/compact'
109
+ },
110
+ isCompactCommand: true
111
+ };
112
+ // Create synthetic AI response with compressed content
113
+ const aiResponseMessage = {
114
+ type: 'assistant',
115
+ uuid: `synthetic_ai_${currentMsg.uuid}`,
116
+ timestamp: currentMsg.timestamp,
117
+ sessionId: currentMsg.sessionId,
118
+ parentUuid: userCommandMessage.uuid,
119
+ message: {
120
+ role: 'assistant',
121
+ content: extractContentFromClaudeMessage(currentMsg, messages) || '会话上下文已压缩'
122
+ },
123
+ isCompactSummary: true
124
+ };
125
+ processedMessages.push(userCommandMessage, aiResponseMessage);
126
+ i += 4; // Skip all 4 messages
127
+ continue;
128
+ }
129
+ }
130
+ // Case 2: Auto compact - Single message with isCompactSummary
131
+ if (currentMsg.isCompactSummary && currentMsg.parentUuid === null &&
132
+ !(i + 1 < messages.length && messages[i + 1].isMeta === true)) {
133
+ // Create synthetic AI response for auto-compressed content
134
+ const aiResponseMessage = {
135
+ type: 'assistant',
136
+ uuid: `synthetic_auto_${currentMsg.uuid}`,
137
+ timestamp: currentMsg.timestamp,
138
+ sessionId: currentMsg.sessionId,
139
+ parentUuid: currentMsg.parentUuid,
140
+ message: {
141
+ role: 'assistant',
142
+ content: extractContentFromClaudeMessage(currentMsg, messages) || '会话上下文已自动压缩'
143
+ },
144
+ isCompactSummary: true
145
+ };
146
+ processedMessages.push(aiResponseMessage);
147
+ i++;
148
+ continue;
149
+ }
150
+ // Regular message - pass through
151
+ processedMessages.push(currentMsg);
152
+ i++;
153
+ }
154
+ return processedMessages;
155
+ }
156
+ function readClaudeHistorySessions(projectPath) {
157
+ try {
158
+ const claudeProjectPath = convertProjectPathToClaudeFormat(projectPath);
159
+ const historyDir = path.join(os.homedir(), '.claude', 'projects', claudeProjectPath);
160
+ if (!fs.existsSync(historyDir)) {
161
+ console.log('Claude history directory not found:', historyDir);
162
+ return [];
163
+ }
164
+ const jsonlFiles = fs.readdirSync(historyDir)
165
+ .filter(file => file.endsWith('.jsonl'))
166
+ .filter(file => !file.startsWith('.'));
167
+ const sessions = [];
168
+ for (const filename of jsonlFiles) {
169
+ const sessionId = filename.replace('.jsonl', '');
170
+ const filePath = path.join(historyDir, filename);
171
+ try {
172
+ const content = fs.readFileSync(filePath, 'utf-8');
173
+ const lines = content.trim().split('\n').filter(line => line.trim());
174
+ if (lines.length === 0)
175
+ continue;
176
+ const messages = lines.map(line => JSON.parse(line));
177
+ // Find summary message for session title
178
+ const summaryMessage = messages.find(msg => msg.type === 'summary');
179
+ const title = summaryMessage?.summary || `会话 ${sessionId.slice(0, 8)}`;
180
+ // Process compact context messages before filtering
181
+ const processedMessages = processCompactContextMessages(messages);
182
+ // Filter user and assistant messages, but exclude tool_result-only user messages, isMeta messages,
183
+ const conversationMessages = processedMessages.filter(msg => {
184
+ // Filter out isMeta messages (rule 1)
185
+ if (msg.isMeta === true) {
186
+ return false;
187
+ }
188
+ // Filter out /clear command messages and its related output messages
189
+ if (msg.type === 'user' && msg.message?.content && typeof msg.message.content === 'string') {
190
+ // Check for /clear command format
191
+ if (msg.message.content.includes('<command-name>/clear</command-name>')) {
192
+ return false;
193
+ }
194
+ // Check for /clear command's output (local-command-stdout that follows /clear command)
195
+ if (msg.message.content.includes('<local-command-stdout></local-command-stdout>') &&
196
+ msg.parentUuid) {
197
+ // Find the parent message to check if it was a /clear command
198
+ const parentMsg = messages.find(m => m.uuid === msg.parentUuid);
199
+ if (parentMsg && parentMsg.message?.content && typeof parentMsg.message.content === 'string' &&
200
+ parentMsg.message.content.includes('<command-name>/clear</command-name>')) {
201
+ return false;
202
+ }
203
+ }
204
+ }
205
+ if (msg.type === 'assistant')
206
+ return true;
207
+ if (msg.type === 'user') {
208
+ // Check if this user message contains only tool_result
209
+ if (msg.message?.content && Array.isArray(msg.message.content)) {
210
+ const hasNonToolResult = msg.message.content.some((block) => block.type !== 'tool_result');
211
+ return hasNonToolResult; // Only include if it has content other than tool_result
212
+ }
213
+ // Include user messages with string content or no content array
214
+ return typeof msg.message?.content === 'string' || !msg.message?.content;
215
+ }
216
+ return false;
217
+ });
218
+ if (conversationMessages.length === 0)
219
+ continue;
220
+ // Convert messages to our format and group consecutive assistant messages
221
+ const convertedMessages = [];
222
+ let i = 0;
223
+ while (i < conversationMessages.length) {
224
+ const msg = conversationMessages[i];
225
+ if (msg.type === 'user') {
226
+ // Check if this is a local-command-stdout message
227
+ const content = msg.message?.content;
228
+ if (typeof content === 'string' && content.includes('<local-command-stdout>')) {
229
+ // Extract content from local-command-stdout tag
230
+ const outputMatch = content.match(/<local-command-stdout>([^<]*)<\/local-command-stdout>/);
231
+ const displayOutput = outputMatch ? outputMatch[1] : '';
232
+ // Create AI response message
233
+ convertedMessages.push({
234
+ id: `msg_${convertedMessages.length}_${msg.uuid}`,
235
+ role: 'assistant',
236
+ content: displayOutput,
237
+ timestamp: new Date(msg.timestamp).getTime(),
238
+ messageParts: [{
239
+ id: `part_0_${msg.uuid}`,
240
+ type: 'text',
241
+ content: displayOutput,
242
+ order: 0
243
+ }]
244
+ });
245
+ }
246
+ else {
247
+ // Regular user message
248
+ convertedMessages.push({
249
+ id: `msg_${convertedMessages.length}_${msg.uuid}`,
250
+ role: msg.message?.role || msg.type,
251
+ content: extractContentFromClaudeMessage(msg, messages),
252
+ timestamp: new Date(msg.timestamp).getTime(),
253
+ messageParts: convertClaudeMessageToMessageParts(msg, messages)
254
+ });
255
+ }
256
+ i++;
257
+ }
258
+ else if (msg.type === 'assistant') {
259
+ // Find all consecutive assistant messages and combine them
260
+ const assistantMessages = [msg];
261
+ let j = i + 1;
262
+ // Collect all consecutive assistant messages
263
+ while (j < conversationMessages.length && conversationMessages[j].type === 'assistant') {
264
+ assistantMessages.push(conversationMessages[j]);
265
+ j++;
266
+ }
267
+ // Create combined assistant message
268
+ const combinedMessage = {
269
+ id: `msg_${convertedMessages.length}_${msg.uuid}`,
270
+ role: 'assistant',
271
+ content: '',
272
+ timestamp: new Date(msg.timestamp).getTime(),
273
+ messageParts: []
274
+ };
275
+ // Combine all assistant message parts
276
+ assistantMessages.forEach((assistantMsg) => {
277
+ const textContent = extractContentFromClaudeMessage(assistantMsg, messages);
278
+ const msgParts = convertClaudeMessageToMessageParts(assistantMsg, messages);
279
+ combinedMessage.content += textContent;
280
+ combinedMessage.messageParts.push(...msgParts.map(part => ({
281
+ ...part,
282
+ order: combinedMessage.messageParts.length + part.order
283
+ })));
284
+ });
285
+ convertedMessages.push(combinedMessage);
286
+ i = j; // Skip to next non-assistant message
287
+ }
288
+ else {
289
+ i++;
290
+ }
291
+ }
292
+ // Process tool results - find tool_result messages and associate them with tool_use
293
+ for (let i = 0; i < messages.length; i++) {
294
+ const msg = messages[i];
295
+ if (msg.type === 'user' && msg.message?.content && Array.isArray(msg.message.content)) {
296
+ for (const block of msg.message.content) {
297
+ if (block.type === 'tool_result' && block.tool_use_id) {
298
+ // Find the assistant message that contains the matching tool_use
299
+ // Look backwards through conversation messages (not all messages)
300
+ for (let j = convertedMessages.length - 1; j >= 0; j--) {
301
+ const assistantMsg = convertedMessages[j];
302
+ if (assistantMsg && assistantMsg.role === 'assistant') {
303
+ // Find the tool part with matching claudeId
304
+ const toolPart = assistantMsg.messageParts.find((part) => part.type === 'tool' &&
305
+ part.toolData &&
306
+ part.toolData.claudeId === block.tool_use_id);
307
+ if (toolPart && toolPart.toolData) {
308
+ toolPart.toolData.toolResult = typeof block.content === 'string'
309
+ ? block.content
310
+ : JSON.stringify(block.content);
311
+ // Check if the original message has toolUseResult (from Claude Code SDK)
312
+ if (msg.toolUseResult) {
313
+ toolPart.toolData.toolUseResult = msg.toolUseResult;
314
+ }
315
+ toolPart.toolData.isError = block.is_error || false;
316
+ break;
317
+ }
318
+ }
319
+ }
320
+ }
321
+ }
322
+ }
323
+ }
324
+ // Get timestamps
325
+ const timestamps = conversationMessages
326
+ .map(msg => new Date(msg.timestamp).getTime())
327
+ .filter(t => !isNaN(t));
328
+ const createdAt = timestamps.length > 0 ? Math.min(...timestamps) : Date.now();
329
+ const lastUpdated = timestamps.length > 0 ? Math.max(...timestamps) : Date.now();
330
+ sessions.push({
331
+ id: sessionId,
332
+ title,
333
+ createdAt: new Date(createdAt).toISOString(),
334
+ lastUpdated: new Date(lastUpdated).toISOString(),
335
+ messages: convertedMessages
336
+ });
337
+ }
338
+ catch (error) {
339
+ console.error(`Failed to parse Claude history file ${filename}:`, error);
340
+ continue;
341
+ }
342
+ }
343
+ return sessions.sort((a, b) => new Date(b.lastUpdated).getTime() - new Date(a.lastUpdated).getTime());
344
+ }
345
+ catch (error) {
346
+ console.error('Failed to read Claude history sessions:', error);
347
+ return [];
348
+ }
349
+ }
350
+ function extractContentFromClaudeMessage(msg, allMessages = []) {
351
+ if (!msg.message?.content)
352
+ return '';
353
+ // Handle both array and string content
354
+ if (typeof msg.message.content === 'string') {
355
+ const commandMatch = msg.message.content.match(/<command-name>(.+?)<\/command-name>/);
356
+ if (commandMatch) {
357
+ // Check for two different patterns:
358
+ // Pattern 1: Parent is meta message (3-message local-command pattern)
359
+ const parentMessage = msg.parentUuid ? allMessages.find(m => m.uuid === msg.parentUuid) : null;
360
+ const isMetaParent = parentMessage && parentMessage.isMeta === true;
361
+ // Pattern 2: Child is meta message (2-message user-custom-command pattern)
362
+ const childMessage = allMessages.find(m => m.parentUuid === msg.uuid);
363
+ const isMetaChild = childMessage && childMessage.isMeta === true;
364
+ if (isMetaParent) {
365
+ // 3-message pattern: return only command name
366
+ return commandMatch[1];
367
+ }
368
+ else if (isMetaChild) {
369
+ // 2-message pattern: return command name + args
370
+ const argsMatch = msg.message.content.match(/<command-args>([^<]*)<\/command-args>/);
371
+ const args = argsMatch ? argsMatch[1].trim() : '';
372
+ return args ? `${commandMatch[1]} ${args}` : commandMatch[1];
373
+ }
374
+ }
375
+ return msg.message.content;
376
+ }
377
+ if (Array.isArray(msg.message.content)) {
378
+ return msg.message.content
379
+ .filter((block) => block.type === 'text' || block.type === 'thinking')
380
+ .map((block) => block.text || block.thinking || '')
381
+ .join('');
382
+ }
383
+ return '';
384
+ }
385
+ function convertClaudeMessageToMessageParts(msg, allMessages = []) {
386
+ if (!msg.message?.content)
387
+ return [];
388
+ // Handle compact command messages
389
+ if (msg.isCompactCommand) {
390
+ return [{
391
+ id: `part_0_${msg.uuid}`,
392
+ type: 'command',
393
+ content: '/compact',
394
+ order: 0
395
+ }];
396
+ }
397
+ // Handle compact summary messages
398
+ if (msg.isCompactSummary) {
399
+ return [{
400
+ id: `part_0_${msg.uuid}`,
401
+ type: 'compactSummary',
402
+ content: msg.message.content,
403
+ order: 0
404
+ }];
405
+ }
406
+ // Handle string content
407
+ if (typeof msg.message.content === 'string') {
408
+ // Rule 2: Check for command message format and create command-specific part
409
+ const commandMatch = msg.message.content.match(/<command-name>(.+?)<\/command-name>/);
410
+ if (commandMatch) {
411
+ // Check for two different patterns:
412
+ // Pattern 1: Parent is meta message (3-message local-command pattern)
413
+ const parentMessage = msg.parentUuid ? allMessages.find(m => m.uuid === msg.parentUuid) : null;
414
+ const isMetaParent = parentMessage && parentMessage.isMeta === true;
415
+ // Pattern 2: Child is meta message (2-message user-custom-command pattern)
416
+ const childMessage = allMessages.find(m => m.parentUuid === msg.uuid);
417
+ const isMetaChild = childMessage && childMessage.isMeta === true;
418
+ if (isMetaParent) {
419
+ // 3-message pattern: show only command name
420
+ return [{
421
+ id: `part_0_${msg.uuid}`,
422
+ type: 'command',
423
+ content: commandMatch[1], // Only the command name
424
+ originalContent: msg.message.content, // Keep original for reference
425
+ order: 0
426
+ }];
427
+ }
428
+ else if (isMetaChild) {
429
+ // 2-message pattern: show command name + args
430
+ const argsMatch = msg.message.content.match(/<command-args>([^<]*)<\/command-args>/);
431
+ const args = argsMatch ? argsMatch[1].trim() : '';
432
+ const displayContent = args ? `${commandMatch[1]} ${args}` : commandMatch[1];
433
+ return [{
434
+ id: `part_0_${msg.uuid}`,
435
+ type: 'command',
436
+ content: displayContent, // Command name + args
437
+ originalContent: msg.message.content, // Keep original for reference
438
+ order: 0
439
+ }];
440
+ }
441
+ }
442
+ return [{
443
+ id: `part_0_${msg.uuid}`,
444
+ type: 'text',
445
+ content: msg.message.content,
446
+ order: 0
447
+ }];
448
+ }
449
+ // Handle array content
450
+ if (Array.isArray(msg.message.content)) {
451
+ return msg.message.content.map((block, index) => {
452
+ if (block.type === 'text') {
453
+ return {
454
+ id: `part_${index}_${msg.uuid}`,
455
+ type: 'text',
456
+ content: block.text,
457
+ order: index
458
+ };
459
+ }
460
+ else if (block.type === 'tool_use') {
461
+ return {
462
+ id: `part_${index}_${msg.uuid}`,
463
+ type: 'tool',
464
+ toolData: {
465
+ id: `tool_${index}_${msg.uuid}`,
466
+ claudeId: block.id,
467
+ toolName: block.name,
468
+ toolInput: block.input || {},
469
+ toolResult: '', // Will be filled by tool_result if available
470
+ isExecuting: false, // Historical data is not executing
471
+ isError: false
472
+ },
473
+ order: index
474
+ };
475
+ }
476
+ else if (block.type === 'tool_result') {
477
+ // Skip tool_result blocks as they will be merged with tool_use blocks
478
+ return null;
479
+ }
480
+ else if (block.type === 'image') {
481
+ // Handle image content blocks
482
+ return {
483
+ id: `part_${index}_${msg.uuid}`,
484
+ type: 'image',
485
+ imageData: {
486
+ id: `img_${index}_${msg.uuid}`,
487
+ data: block.source?.data || '',
488
+ mediaType: block.source?.media_type || 'image/jpeg',
489
+ filename: `image_${index}.jpg` // Default filename since Claude history may not store original filename
490
+ },
491
+ order: index
492
+ };
493
+ }
494
+ else if (block.type === 'thinking') {
495
+ // Handle thinking content blocks
496
+ return {
497
+ id: `part_${index}_${msg.uuid}`,
498
+ type: 'thinking',
499
+ content: block.thinking || '',
500
+ order: index
501
+ };
502
+ }
503
+ // Handle other content types
504
+ return {
505
+ id: `part_${index}_${msg.uuid}`,
506
+ type: 'unknown',
507
+ content: JSON.stringify(block),
508
+ order: index
509
+ };
510
+ }).filter((part) => part !== null);
511
+ }
512
+ return [];
513
+ }
514
+ // GET /api/sessions/_status - Get all sessions status (for monitoring)
515
+ router.get('/_status', (req, res) => {
516
+ try {
517
+ const sessionsInfo = sessionManager.getSessionsInfo();
518
+ res.json({
519
+ activeSessionCount: sessionManager.getActiveSessionCount(),
520
+ sessions: sessionsInfo
521
+ });
522
+ }
523
+ catch (error) {
524
+ console.error('Failed to get sessions status:', error);
525
+ res.status(500).json({ error: 'Failed to get sessions status' });
526
+ }
527
+ });
528
+ // GET /api/sessions/:agentId - Get agent sessions
529
+ router.get('/:agentId', (req, res) => {
530
+ try {
531
+ const { agentId } = req.params;
532
+ const { search } = req.query;
533
+ const projectPath = req.query.projectPath;
534
+ // Verify agent exists
535
+ const agent = globalAgentStorage.getAgent(agentId);
536
+ if (!agent) {
537
+ return res.status(404).json({ error: 'Agent not found' });
538
+ }
539
+ let sessions = [];
540
+ // If projectPath is provided, read from Claude Code history
541
+ if (projectPath) {
542
+ console.log('Reading Claude history sessions for project:', projectPath);
543
+ const claudeSessions = readClaudeHistorySessions(projectPath);
544
+ sessions = claudeSessions.map(session => ({
545
+ id: session.id,
546
+ agentId: agentId, // Associate with current agent
547
+ title: session.title,
548
+ createdAt: session.createdAt,
549
+ lastUpdated: session.lastUpdated,
550
+ messageCount: session.messages.length
551
+ }));
552
+ }
553
+ else {
554
+ // Use project-specific AgentStorage for sessions (existing behavior)
555
+ const agentStorage = getAgentStorageForRequest(req);
556
+ const agentSessions = agentStorage.getAgentSessions(agentId, search);
557
+ sessions = agentSessions.map(session => ({
558
+ id: session.id,
559
+ agentId: session.agentId,
560
+ title: session.title,
561
+ createdAt: session.createdAt,
562
+ lastUpdated: session.lastUpdated,
563
+ messageCount: session.messages.length
564
+ }));
565
+ }
566
+ // Apply search filter if provided
567
+ if (search && typeof search === 'string' && search.trim()) {
568
+ const searchTerm = search.trim().toLowerCase();
569
+ sessions = sessions.filter(session => session.title.toLowerCase().includes(searchTerm));
570
+ }
571
+ res.json({ sessions });
572
+ }
573
+ catch (error) {
574
+ console.error('Failed to get agent sessions:', error);
575
+ res.status(500).json({ error: 'Failed to retrieve agent sessions' });
576
+ }
577
+ });
578
+ // GET /api/sessions/:agentId/:sessionId/messages - Get session messages
579
+ router.get('/:agentId/:sessionId/messages', (req, res) => {
580
+ try {
581
+ const { agentId, sessionId } = req.params;
582
+ const projectPath = req.query.projectPath;
583
+ let session = null;
584
+ // If projectPath is provided, read from Claude Code history
585
+ if (projectPath) {
586
+ console.log('Reading Claude history messages for session:', sessionId, 'in project:', projectPath);
587
+ const claudeSessions = readClaudeHistorySessions(projectPath);
588
+ session = claudeSessions.find(s => s.id === sessionId);
589
+ if (session) {
590
+ console.log('📨 Found session with', session.messages?.length || 0, 'messages');
591
+ console.log('📨 First few messages:', session.messages?.slice(0, 3).map((msg) => ({
592
+ role: msg.role,
593
+ hasMessageParts: !!msg.messageParts,
594
+ messagePartsCount: msg.messageParts?.length || 0,
595
+ content: msg.content?.slice(0, 50) + '...'
596
+ })));
597
+ // Add agentId to match expected format
598
+ session = {
599
+ ...session,
600
+ agentId: agentId
601
+ };
602
+ }
603
+ }
604
+ else {
605
+ // Use project-specific AgentStorage for sessions (existing behavior)
606
+ const agentStorage = getAgentStorageForRequest(req);
607
+ session = agentStorage.getSession(agentId, sessionId);
608
+ }
609
+ if (!session) {
610
+ return res.status(404).json({ error: 'Session not found' });
611
+ }
612
+ res.json({
613
+ sessionId: session.id,
614
+ agentId: session.agentId,
615
+ title: session.title,
616
+ messages: session.messages
617
+ });
618
+ }
619
+ catch (error) {
620
+ console.error('Failed to get session messages:', error);
621
+ res.status(500).json({ error: 'Failed to retrieve session messages' });
622
+ }
623
+ });
624
+ // POST /api/sessions/:agentId - Create new session
625
+ router.post('/:agentId', (req, res) => {
626
+ try {
627
+ const { agentId } = req.params;
628
+ // Verify agent exists
629
+ const agent = globalAgentStorage.getAgent(agentId);
630
+ if (!agent) {
631
+ return res.status(404).json({ error: 'Agent not found' });
632
+ }
633
+ // Use project-specific AgentStorage for sessions
634
+ const agentStorage = getAgentStorageForRequest(req);
635
+ const session = agentStorage.createSession(agentId, req.body.title);
636
+ res.json({ sessionId: session.id, session });
637
+ }
638
+ catch (error) {
639
+ console.error('Failed to create agent session:', error);
640
+ res.status(500).json({ error: 'Failed to create agent session' });
641
+ }
642
+ });
643
+ // DELETE /api/sessions/:agentId/:sessionId - Delete session
644
+ router.delete('/:agentId/:sessionId', (req, res) => {
645
+ try {
646
+ const { agentId, sessionId } = req.params;
647
+ // Use project-specific AgentStorage for sessions
648
+ const agentStorage = getAgentStorageForRequest(req);
649
+ const deleted = agentStorage.deleteSession(agentId, sessionId);
650
+ res.json({ success: deleted });
651
+ }
652
+ catch (error) {
653
+ console.error('Failed to delete agent session:', error);
654
+ res.status(500).json({ error: 'Failed to delete agent session' });
655
+ }
656
+ });
657
+ // POST /api/sessions/:agentId/:sessionId/heartbeat - Update session heartbeat
658
+ router.post('/:agentId/:sessionId/heartbeat', (req, res) => {
659
+ try {
660
+ const { sessionId } = req.params;
661
+ // Update heartbeat in SessionManager
662
+ const success = sessionManager.updateHeartbeat(sessionId);
663
+ if (success) {
664
+ res.json({
665
+ success: true,
666
+ timestamp: Date.now(),
667
+ message: 'Heartbeat updated successfully'
668
+ });
669
+ }
670
+ else {
671
+ res.status(404).json({
672
+ success: false,
673
+ error: 'Session not found or not active'
674
+ });
675
+ }
676
+ }
677
+ catch (error) {
678
+ console.error('Failed to update session heartbeat:', error);
679
+ res.status(500).json({
680
+ success: false,
681
+ error: 'Failed to update heartbeat'
682
+ });
683
+ }
684
+ });
685
+ // DELETE /api/sessions/:agentId/:sessionId/cleanup - Manual cleanup session
686
+ router.delete('/:agentId/:sessionId/cleanup', async (req, res) => {
687
+ try {
688
+ const { sessionId } = req.params;
689
+ // Manual cleanup session in SessionManager
690
+ const success = await sessionManager.manualCleanupSession(sessionId);
691
+ if (success) {
692
+ res.json({
693
+ success: true,
694
+ message: 'Session cleaned up successfully'
695
+ });
696
+ }
697
+ else {
698
+ res.status(404).json({
699
+ success: false,
700
+ error: 'Session not found'
701
+ });
702
+ }
703
+ }
704
+ catch (error) {
705
+ console.error('Failed to cleanup session:', error);
706
+ res.status(500).json({
707
+ success: false,
708
+ error: 'Failed to cleanup session'
709
+ });
710
+ }
711
+ });
712
+ // GET /api/sessions/:agentId/:sessionId/check - Check if session exists in SessionManager
713
+ router.get('/:agentId/:sessionId/check', (req, res) => {
714
+ try {
715
+ const { sessionId } = req.params;
716
+ // Check if session exists in SessionManager
717
+ const exists = sessionManager.hasActiveSession(sessionId);
718
+ res.json({
719
+ exists,
720
+ sessionId,
721
+ message: exists ? 'Session is active in SessionManager' : 'Session not found in SessionManager'
722
+ });
723
+ }
724
+ catch (error) {
725
+ console.error('Failed to check session:', error);
726
+ res.status(500).json({
727
+ exists: false,
728
+ error: 'Failed to check session'
729
+ });
730
+ }
731
+ });
732
+ export default router;
733
+ //# sourceMappingURL=sessions.js.map