renote-server 1.0.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.
@@ -0,0 +1,689 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getSessionFilePath = getSessionFilePath;
37
+ exports.watchSession = watchSession;
38
+ exports.unwatchSession = unwatchSession;
39
+ exports.listWorkspaces = listWorkspaces;
40
+ exports.listSessions = listSessions;
41
+ exports.getSessionMessages = getSessionMessages;
42
+ exports.getSessionMessagesPage = getSessionMessagesPage;
43
+ exports.listSubagents = listSubagents;
44
+ exports.getSubagentMessages = getSubagentMessages;
45
+ exports.listToolResults = listToolResults;
46
+ exports.getToolResultContent = getToolResultContent;
47
+ exports.getSessionFolderInfo = getSessionFolderInfo;
48
+ const fs = __importStar(require("fs"));
49
+ const path = __importStar(require("path"));
50
+ const os = __importStar(require("os"));
51
+ const readline = __importStar(require("readline"));
52
+ const chokidar_1 = require("chokidar");
53
+ const logger_1 = require("../utils/logger");
54
+ const PROJECTS_DIR = path.join(os.homedir(), '.claude', 'projects');
55
+ const sessionWatchers = new Map();
56
+ function getSessionFilePath(workspace, sessionId) {
57
+ return path.join(PROJECTS_DIR, workspace, `${sessionId}.jsonl`);
58
+ }
59
+ async function watchSession(clientId, workspace, sessionId, onUpdate) {
60
+ // 先停止之前的监听
61
+ unwatchSession(clientId);
62
+ const filePath = getSessionFilePath(workspace, sessionId);
63
+ try {
64
+ const stat = await fs.promises.stat(filePath);
65
+ const lastSize = stat.size;
66
+ const watcher = (0, chokidar_1.watch)(filePath, {
67
+ persistent: true,
68
+ usePolling: true,
69
+ interval: 2000, // Reduced from 500ms to save mobile battery
70
+ });
71
+ // Debounce change handler to prevent rapid-fire updates
72
+ let debounceTimer = null;
73
+ watcher.on('change', async () => {
74
+ if (debounceTimer)
75
+ clearTimeout(debounceTimer);
76
+ debounceTimer = setTimeout(async () => {
77
+ const watcherInfo = sessionWatchers.get(clientId);
78
+ if (!watcherInfo)
79
+ return;
80
+ try {
81
+ const newStat = await fs.promises.stat(filePath);
82
+ // Handle file truncation (e.g. log rotation)
83
+ if (newStat.size < watcherInfo.lastSize) {
84
+ watcherInfo.lastSize = 0;
85
+ }
86
+ if (newStat.size <= watcherInfo.lastSize)
87
+ return;
88
+ // 读取新增的内容
89
+ const newMessages = await readNewMessages(filePath, watcherInfo.lastSize);
90
+ watcherInfo.lastSize = newStat.size;
91
+ if (newMessages.length > 0) {
92
+ onUpdate(newMessages);
93
+ }
94
+ }
95
+ catch (error) {
96
+ logger_1.logger.error('Error reading session update:', error);
97
+ }
98
+ }, 200);
99
+ });
100
+ sessionWatchers.set(clientId, {
101
+ watcher,
102
+ filePath,
103
+ lastSize,
104
+ onUpdate,
105
+ });
106
+ logger_1.logger.info(`Started watching session: ${sessionId} for client: ${clientId}`);
107
+ }
108
+ catch (error) {
109
+ logger_1.logger.error('Error starting session watcher:', error);
110
+ }
111
+ }
112
+ function unwatchSession(clientId) {
113
+ const watcherInfo = sessionWatchers.get(clientId);
114
+ if (watcherInfo) {
115
+ watcherInfo.watcher.close();
116
+ sessionWatchers.delete(clientId);
117
+ logger_1.logger.info(`Stopped watching session for client: ${clientId}`);
118
+ }
119
+ }
120
+ async function readNewMessages(filePath, fromPosition) {
121
+ return new Promise((resolve, reject) => {
122
+ const messages = [];
123
+ const stream = fs.createReadStream(filePath, {
124
+ encoding: 'utf-8',
125
+ start: fromPosition,
126
+ });
127
+ const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
128
+ rl.on('line', (line) => {
129
+ if (!line.trim())
130
+ return;
131
+ try {
132
+ const entry = JSON.parse(line);
133
+ const parsed = parseEntry(entry);
134
+ if (parsed)
135
+ messages.push(...parsed);
136
+ }
137
+ catch {
138
+ // Skip malformed lines
139
+ }
140
+ });
141
+ rl.on('close', () => resolve(messages));
142
+ rl.on('error', reject);
143
+ });
144
+ }
145
+ function decodeWorkspacePath(dirName) {
146
+ // -Users-lincyaw-workspace-DevSpace → ~/workspace/DevSpace
147
+ const home = os.homedir(); // /Users/lincyaw
148
+ const homeParts = home.split(path.sep).filter(Boolean); // ['Users', 'lincyaw']
149
+ const parts = dirName.split('-').filter(Boolean);
150
+ // Check if the parts start with the home directory components
151
+ let matchLen = 0;
152
+ for (let i = 0; i < homeParts.length && i < parts.length; i++) {
153
+ if (parts[i] === homeParts[i]) {
154
+ matchLen++;
155
+ }
156
+ else {
157
+ break;
158
+ }
159
+ }
160
+ if (matchLen === homeParts.length) {
161
+ const rest = parts.slice(matchLen).join('/');
162
+ return rest ? `~/${rest}` : '~';
163
+ }
164
+ return '/' + parts.join('/');
165
+ }
166
+ async function listWorkspaces() {
167
+ try {
168
+ const entries = await fs.promises.readdir(PROJECTS_DIR, { withFileTypes: true });
169
+ const workspaces = [];
170
+ for (const entry of entries) {
171
+ if (!entry.isDirectory())
172
+ continue;
173
+ const workspaceDir = path.join(PROJECTS_DIR, entry.name);
174
+ const indexPath = path.join(workspaceDir, 'sessions-index.json');
175
+ let indexCount = 0;
176
+ let lastModified = 0;
177
+ // Read sessions-index.json if available
178
+ try {
179
+ const raw = await fs.promises.readFile(indexPath, 'utf-8');
180
+ const index = JSON.parse(raw);
181
+ const sessions = index.entries || [];
182
+ indexCount = sessions.length;
183
+ for (const s of sessions) {
184
+ const mtime = s.fileMtime || new Date(s.modified).getTime() || 0;
185
+ if (mtime > lastModified)
186
+ lastModified = mtime;
187
+ }
188
+ }
189
+ catch {
190
+ // No sessions-index.json or parse error, continue with file scan
191
+ }
192
+ // Also scan for .jsonl files to get accurate count
193
+ let jsonlCount = 0;
194
+ try {
195
+ const files = await fs.promises.readdir(workspaceDir);
196
+ jsonlCount = files.filter(f => f.endsWith('.jsonl')).length;
197
+ // Update lastModified from .jsonl files if needed
198
+ if (lastModified === 0 && jsonlCount > 0) {
199
+ for (const file of files) {
200
+ if (!file.endsWith('.jsonl'))
201
+ continue;
202
+ try {
203
+ const stat = await fs.promises.stat(path.join(workspaceDir, file));
204
+ const mtime = stat.mtimeMs;
205
+ if (mtime > lastModified)
206
+ lastModified = mtime;
207
+ }
208
+ catch {
209
+ // Skip files we can't stat
210
+ }
211
+ }
212
+ }
213
+ }
214
+ catch {
215
+ // Can't read directory, use index count
216
+ }
217
+ // Use the larger of the two counts to ensure accuracy
218
+ const sessionCount = Math.max(indexCount, jsonlCount);
219
+ // Only add workspace if it has sessions
220
+ if (sessionCount > 0) {
221
+ workspaces.push({
222
+ dirName: entry.name,
223
+ displayPath: decodeWorkspacePath(entry.name),
224
+ sessionCount,
225
+ lastModified,
226
+ });
227
+ }
228
+ }
229
+ workspaces.sort((a, b) => b.lastModified - a.lastModified);
230
+ return workspaces;
231
+ }
232
+ catch (error) {
233
+ logger_1.logger.error('Error listing workspaces:', error);
234
+ return [];
235
+ }
236
+ }
237
+ async function listSessions(workspace) {
238
+ const workspaceDir = path.join(PROJECTS_DIR, workspace);
239
+ const indexPath = path.join(workspaceDir, 'sessions-index.json');
240
+ // Build a map from sessionId to SessionInfo from the index
241
+ const sessionMap = new Map();
242
+ try {
243
+ const raw = await fs.promises.readFile(indexPath, 'utf-8');
244
+ const index = JSON.parse(raw);
245
+ const entries = index.entries || [];
246
+ for (const e of entries) {
247
+ sessionMap.set(e.sessionId, {
248
+ sessionId: e.sessionId,
249
+ firstPrompt: e.firstPrompt || '',
250
+ summary: e.summary || '',
251
+ messageCount: e.messageCount || 0,
252
+ created: e.created || '',
253
+ modified: e.modified || '',
254
+ });
255
+ }
256
+ }
257
+ catch {
258
+ // Index file doesn't exist or parse error, continue with file scan
259
+ }
260
+ // Scan for .jsonl files not in the index
261
+ try {
262
+ const files = await fs.promises.readdir(workspaceDir);
263
+ for (const file of files) {
264
+ if (!file.endsWith('.jsonl'))
265
+ continue;
266
+ const sessionId = file.replace('.jsonl', '');
267
+ if (sessionMap.has(sessionId))
268
+ continue;
269
+ // Extract basic info from file
270
+ const filePath = path.join(workspaceDir, file);
271
+ const stat = await fs.promises.stat(filePath);
272
+ const info = await extractSessionInfo(filePath, sessionId, stat);
273
+ if (info)
274
+ sessionMap.set(sessionId, info);
275
+ }
276
+ }
277
+ catch (error) {
278
+ logger_1.logger.error('Error scanning workspace directory:', error);
279
+ }
280
+ const sessions = Array.from(sessionMap.values());
281
+ sessions.sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime());
282
+ return sessions;
283
+ }
284
+ async function extractSessionInfo(filePath, sessionId, stat) {
285
+ try {
286
+ const stream = fs.createReadStream(filePath, { encoding: 'utf-8' });
287
+ const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
288
+ let firstPrompt = '';
289
+ let messageCount = 0;
290
+ for await (const line of rl) {
291
+ if (!line.trim())
292
+ continue;
293
+ try {
294
+ const entry = JSON.parse(line);
295
+ if (entry.type === 'user' && entry.message) {
296
+ messageCount++;
297
+ if (!firstPrompt && typeof entry.message.content === 'string') {
298
+ firstPrompt = entry.message.content.substring(0, 200);
299
+ }
300
+ }
301
+ else if (entry.type === 'assistant') {
302
+ messageCount++;
303
+ }
304
+ // Stop after reading enough to get firstPrompt and some count
305
+ if (firstPrompt && messageCount >= 10) {
306
+ rl.close();
307
+ break;
308
+ }
309
+ }
310
+ catch {
311
+ // Skip malformed lines
312
+ }
313
+ }
314
+ return {
315
+ sessionId,
316
+ firstPrompt: firstPrompt || 'No prompt',
317
+ summary: '',
318
+ messageCount,
319
+ created: stat.birthtime.toISOString(),
320
+ modified: stat.mtime.toISOString(),
321
+ };
322
+ }
323
+ catch {
324
+ return null;
325
+ }
326
+ }
327
+ async function getSessionMessages(workspace, sessionId) {
328
+ const filePath = path.join(PROJECTS_DIR, workspace, `${sessionId}.jsonl`);
329
+ const messages = [];
330
+ try {
331
+ const stream = fs.createReadStream(filePath, { encoding: 'utf-8' });
332
+ const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
333
+ for await (const line of rl) {
334
+ if (!line.trim())
335
+ continue;
336
+ try {
337
+ const entry = JSON.parse(line);
338
+ const parsed = parseEntry(entry);
339
+ if (parsed)
340
+ messages.push(...parsed);
341
+ }
342
+ catch {
343
+ // Skip malformed lines
344
+ }
345
+ }
346
+ }
347
+ catch (error) {
348
+ logger_1.logger.error('Error reading session messages:', error);
349
+ }
350
+ return messages;
351
+ }
352
+ /**
353
+ * Get a page of session messages, reading from the end of the file (newest first).
354
+ * This enables IM-style pagination where the latest messages are loaded first.
355
+ *
356
+ * @param workspace - The workspace directory name
357
+ * @param sessionId - The session ID
358
+ * @param limit - Maximum number of messages to return (default 50)
359
+ * @param beforeIndex - Only return messages with index < beforeIndex (for pagination)
360
+ * @returns A page of messages in chronological order (oldest first within the page)
361
+ */
362
+ async function getSessionMessagesPage(workspace, sessionId, limit = 50, beforeIndex) {
363
+ const filePath = path.join(PROJECTS_DIR, workspace, `${sessionId}.jsonl`);
364
+ try {
365
+ // Read all messages from file
366
+ const allMessages = [];
367
+ const stream = fs.createReadStream(filePath, { encoding: 'utf-8' });
368
+ const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
369
+ for await (const line of rl) {
370
+ if (!line.trim())
371
+ continue;
372
+ try {
373
+ const entry = JSON.parse(line);
374
+ const parsed = parseEntry(entry);
375
+ if (parsed)
376
+ allMessages.push(...parsed);
377
+ }
378
+ catch {
379
+ // Skip malformed lines
380
+ }
381
+ }
382
+ const totalCount = allMessages.length;
383
+ // Determine the slice range
384
+ // beforeIndex is the index in the full array (0-based)
385
+ // If not provided, start from the end
386
+ const endIndex = beforeIndex !== undefined ? beforeIndex : totalCount;
387
+ const startIndex = Math.max(0, endIndex - limit);
388
+ // Slice and return in chronological order
389
+ const pageMessages = allMessages.slice(startIndex, endIndex);
390
+ return {
391
+ messages: pageMessages,
392
+ hasMore: startIndex > 0,
393
+ oldestIndex: startIndex,
394
+ totalCount,
395
+ };
396
+ }
397
+ catch (error) {
398
+ logger_1.logger.error('Error reading session messages page:', error);
399
+ return {
400
+ messages: [],
401
+ hasMore: false,
402
+ oldestIndex: 0,
403
+ totalCount: 0,
404
+ };
405
+ }
406
+ }
407
+ function parseEntry(entry) {
408
+ const timestamp = entry.timestamp || '';
409
+ const uuid = entry.uuid || '';
410
+ if (entry.type === 'user' && entry.message) {
411
+ const content = entry.message.content;
412
+ // content is string → real user message
413
+ if (typeof content === 'string') {
414
+ return [{ uuid, type: 'user', content, timestamp }];
415
+ }
416
+ // content is array → may contain tool_result blocks or text blocks
417
+ if (Array.isArray(content)) {
418
+ const results = [];
419
+ for (const block of content) {
420
+ if (block.type === 'tool_result') {
421
+ const text = typeof block.content === 'string'
422
+ ? block.content
423
+ : Array.isArray(block.content)
424
+ ? block.content
425
+ .filter((c) => c.type === 'text')
426
+ .map((c) => c.text)
427
+ .join('\n')
428
+ : 'Tool completed';
429
+ results.push({
430
+ uuid: block.tool_use_id || uuid + '_result',
431
+ type: 'tool_result',
432
+ content: text.substring(0, 500),
433
+ timestamp,
434
+ });
435
+ }
436
+ else if (block.type === 'text' && block.text) {
437
+ results.push({ uuid: uuid + '_text', type: 'user', content: block.text, timestamp });
438
+ }
439
+ }
440
+ return results.length > 0 ? results : null;
441
+ }
442
+ return null;
443
+ }
444
+ if (entry.type === 'assistant' && entry.message) {
445
+ const contentBlocks = entry.message.content;
446
+ if (!Array.isArray(contentBlocks))
447
+ return null;
448
+ const results = [];
449
+ for (const block of contentBlocks) {
450
+ if (block.type === 'text' && block.text) {
451
+ results.push({ uuid: uuid + '_text', type: 'assistant', content: block.text, timestamp });
452
+ }
453
+ else if (block.type === 'tool_use') {
454
+ results.push({
455
+ uuid: block.id || uuid + '_tool',
456
+ type: 'tool_use',
457
+ content: `Tool: ${block.name}`,
458
+ timestamp,
459
+ toolName: block.name,
460
+ toolInput: block.input,
461
+ });
462
+ }
463
+ else if (block.type === 'tool_result') {
464
+ const text = typeof block.content === 'string'
465
+ ? block.content
466
+ : Array.isArray(block.content)
467
+ ? block.content
468
+ .filter((c) => c.type === 'text')
469
+ .map((c) => c.text)
470
+ .join('\n')
471
+ : 'Tool completed';
472
+ results.push({
473
+ uuid: block.tool_use_id || uuid + '_result',
474
+ type: 'tool_result',
475
+ content: text.substring(0, 500),
476
+ timestamp,
477
+ });
478
+ }
479
+ }
480
+ return results.length > 0 ? results : null;
481
+ }
482
+ if (entry.type === 'result' && entry.result) {
483
+ // Tool result entries at top level
484
+ const content = typeof entry.result === 'string'
485
+ ? entry.result.substring(0, 500)
486
+ : JSON.stringify(entry.result).substring(0, 500);
487
+ return [{ uuid, type: 'tool_result', content, timestamp }];
488
+ }
489
+ return null;
490
+ }
491
+ /**
492
+ * List all subagents for a session
493
+ */
494
+ async function listSubagents(workspace, sessionId) {
495
+ const subagentsDir = path.join(PROJECTS_DIR, workspace, sessionId, 'subagents');
496
+ const subagents = [];
497
+ try {
498
+ const files = await fs.promises.readdir(subagentsDir);
499
+ for (const file of files) {
500
+ if (!file.endsWith('.jsonl'))
501
+ continue;
502
+ const filePath = path.join(subagentsDir, file);
503
+ const stat = await fs.promises.stat(filePath);
504
+ // Extract info from first line
505
+ const info = await extractSubagentInfo(filePath, sessionId, stat);
506
+ if (info) {
507
+ subagents.push(info);
508
+ }
509
+ }
510
+ // Sort by modified time (newest first)
511
+ subagents.sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime());
512
+ return subagents;
513
+ }
514
+ catch (error) {
515
+ // Directory doesn't exist or other error
516
+ if (error.code !== 'ENOENT') {
517
+ logger_1.logger.error('Error listing subagents:', error);
518
+ }
519
+ return [];
520
+ }
521
+ }
522
+ async function extractSubagentInfo(filePath, parentSessionId, stat) {
523
+ try {
524
+ const stream = fs.createReadStream(filePath, { encoding: 'utf-8' });
525
+ const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
526
+ let agentId = '';
527
+ let slug = '';
528
+ let firstPrompt = '';
529
+ let messageCount = 0;
530
+ let created = '';
531
+ for await (const line of rl) {
532
+ if (!line.trim())
533
+ continue;
534
+ try {
535
+ const entry = JSON.parse(line);
536
+ messageCount++;
537
+ // Get agent info from first entry
538
+ if (!agentId && entry.agentId) {
539
+ agentId = entry.agentId;
540
+ slug = entry.slug || '';
541
+ created = entry.timestamp || '';
542
+ }
543
+ // Get first user prompt
544
+ if (!firstPrompt && entry.type === 'user' && entry.message) {
545
+ const content = entry.message.content;
546
+ if (typeof content === 'string') {
547
+ firstPrompt = content.substring(0, 200);
548
+ }
549
+ }
550
+ // Stop after reading enough
551
+ if (agentId && firstPrompt && messageCount >= 20) {
552
+ rl.close();
553
+ break;
554
+ }
555
+ }
556
+ catch {
557
+ // Skip malformed lines
558
+ }
559
+ }
560
+ if (!agentId)
561
+ return null;
562
+ return {
563
+ agentId,
564
+ slug,
565
+ filePath,
566
+ messageCount,
567
+ created: created || stat.birthtime.toISOString(),
568
+ modified: stat.mtime.toISOString(),
569
+ firstPrompt: firstPrompt || 'No prompt',
570
+ parentSessionId,
571
+ };
572
+ }
573
+ catch {
574
+ return null;
575
+ }
576
+ }
577
+ /**
578
+ * Get messages from a subagent
579
+ */
580
+ async function getSubagentMessages(workspace, sessionId, agentId) {
581
+ const subagentsDir = path.join(PROJECTS_DIR, workspace, sessionId, 'subagents');
582
+ const messages = [];
583
+ try {
584
+ const files = await fs.promises.readdir(subagentsDir);
585
+ // Find the file that matches this agentId
586
+ const agentFile = files.find(f => f.includes(agentId) && f.endsWith('.jsonl'));
587
+ if (!agentFile) {
588
+ logger_1.logger.warn(`Subagent file not found for agentId: ${agentId}`);
589
+ return [];
590
+ }
591
+ const filePath = path.join(subagentsDir, agentFile);
592
+ const stream = fs.createReadStream(filePath, { encoding: 'utf-8' });
593
+ const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
594
+ for await (const line of rl) {
595
+ if (!line.trim())
596
+ continue;
597
+ try {
598
+ const entry = JSON.parse(line);
599
+ const parsed = parseEntry(entry);
600
+ if (parsed)
601
+ messages.push(...parsed);
602
+ }
603
+ catch {
604
+ // Skip malformed lines
605
+ }
606
+ }
607
+ }
608
+ catch (error) {
609
+ logger_1.logger.error('Error reading subagent messages:', error);
610
+ }
611
+ return messages;
612
+ }
613
+ /**
614
+ * List tool result files for a session
615
+ */
616
+ async function listToolResults(workspace, sessionId) {
617
+ const toolResultsDir = path.join(PROJECTS_DIR, workspace, sessionId, 'tool-results');
618
+ const results = [];
619
+ try {
620
+ const files = await fs.promises.readdir(toolResultsDir);
621
+ for (const file of files) {
622
+ if (!file.endsWith('.txt'))
623
+ continue;
624
+ const filePath = path.join(toolResultsDir, file);
625
+ const stat = await fs.promises.stat(filePath);
626
+ // Extract tool_use_id from filename (e.g., toolu_018uDQVKJXngdcuQvtx35fRV.txt)
627
+ const toolUseId = file.replace('.txt', '');
628
+ results.push({
629
+ toolUseId,
630
+ filePath,
631
+ size: stat.size,
632
+ });
633
+ }
634
+ return results;
635
+ }
636
+ catch (error) {
637
+ if (error.code !== 'ENOENT') {
638
+ logger_1.logger.error('Error listing tool results:', error);
639
+ }
640
+ return [];
641
+ }
642
+ }
643
+ /**
644
+ * Read content of a tool result file
645
+ */
646
+ async function getToolResultContent(workspace, sessionId, toolUseId, maxSize = 50000) {
647
+ const filePath = path.join(PROJECTS_DIR, workspace, sessionId, 'tool-results', `${toolUseId}.txt`);
648
+ try {
649
+ const stat = await fs.promises.stat(filePath);
650
+ if (stat.size <= maxSize) {
651
+ return await fs.promises.readFile(filePath, 'utf-8');
652
+ }
653
+ // If file is too large, read only the beginning
654
+ const buffer = Buffer.alloc(maxSize);
655
+ const fd = await fs.promises.open(filePath, 'r');
656
+ await fd.read(buffer, 0, maxSize, 0);
657
+ await fd.close();
658
+ return buffer.toString('utf-8') + `\n\n... [truncated, total size: ${stat.size} bytes]`;
659
+ }
660
+ catch (error) {
661
+ logger_1.logger.error('Error reading tool result:', error);
662
+ return '';
663
+ }
664
+ }
665
+ /**
666
+ * Get session folder info (subagents count, tool-results count)
667
+ */
668
+ async function getSessionFolderInfo(workspace, sessionId) {
669
+ const sessionDir = path.join(PROJECTS_DIR, workspace, sessionId);
670
+ let subagentCount = 0;
671
+ let toolResultCount = 0;
672
+ try {
673
+ const subagentsDir = path.join(sessionDir, 'subagents');
674
+ const files = await fs.promises.readdir(subagentsDir);
675
+ subagentCount = files.filter(f => f.endsWith('.jsonl')).length;
676
+ }
677
+ catch {
678
+ // Directory doesn't exist
679
+ }
680
+ try {
681
+ const toolResultsDir = path.join(sessionDir, 'tool-results');
682
+ const files = await fs.promises.readdir(toolResultsDir);
683
+ toolResultCount = files.filter(f => f.endsWith('.txt')).length;
684
+ }
685
+ catch {
686
+ // Directory doesn't exist
687
+ }
688
+ return { subagentCount, toolResultCount };
689
+ }