tabminal 3.0.13 → 3.0.15

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.
@@ -16,6 +16,11 @@ const DEFAULT_TERMINAL_OUTPUT_LIMIT = 256 * 1024;
16
16
  const DEFAULT_AVAILABILITY_OVERRIDE_TTL_MS = 30 * 1000;
17
17
  const DEFAULT_PROBE_CACHE_TTL_MS = 15 * 1000;
18
18
  const DEFAULT_TRANSCRIPT_PERSIST_DELAY_MS = 250;
19
+ const ALL_SESSION_AGENT_IDS = new Set([
20
+ 'claude',
21
+ 'codex',
22
+ 'copilot'
23
+ ]);
19
24
  const TEXT_ATTACHMENT_EXTENSIONS = new Set([
20
25
  'txt', 'md', 'markdown', 'json', 'jsonl', 'yaml', 'yml', 'toml',
21
26
  'ini', 'env', 'xml', 'html', 'htm', 'css', 'scss', 'less', 'csv',
@@ -104,7 +109,8 @@ function buildAgentConfigSummary(agentId, config = {}) {
104
109
 
105
110
  function normalizeAgentSessionCapabilities(
106
111
  agentCapabilities = null,
107
- connection = null
112
+ connection = null,
113
+ definitionId = ''
108
114
  ) {
109
115
  const sessionCapabilities = (
110
116
  agentCapabilities?.sessionCapabilities
@@ -121,6 +127,11 @@ function normalizeAgentSessionCapabilities(
121
127
  sessionCapabilities?.list
122
128
  && typeof connection?.listSessions === 'function'
123
129
  ),
130
+ listAll: !!(
131
+ sessionCapabilities?.list
132
+ && typeof connection?.listSessions === 'function'
133
+ && ALL_SESSION_AGENT_IDS.has(String(definitionId || '').trim())
134
+ ),
124
135
  resume: !!(
125
136
  sessionCapabilities?.resume
126
137
  && typeof connection?.unstable_resumeSession === 'function'
@@ -1514,17 +1525,23 @@ class AcpRuntime extends EventEmitter {
1514
1525
  #getSessionCapabilities() {
1515
1526
  const capabilities = normalizeAgentSessionCapabilities(
1516
1527
  this.agentCapabilities,
1517
- this.connection
1528
+ this.connection,
1529
+ this.definition?.id
1518
1530
  );
1519
1531
  if (
1520
1532
  this.definition?.id === 'gemini'
1521
1533
  && this.#supportsGeminiCliSessionListing()
1522
1534
  ) {
1523
1535
  capabilities.list = true;
1536
+ capabilities.listAll = false;
1524
1537
  }
1525
1538
  return capabilities;
1526
1539
  }
1527
1540
 
1541
+ getSessionCapabilities() {
1542
+ return { ...this.#getSessionCapabilities() };
1543
+ }
1544
+
1528
1545
  #supportsGeminiCliSessionListing() {
1529
1546
  if (this.definition?.id !== 'gemini') return false;
1530
1547
  const command = this.definition.command;
@@ -1577,6 +1594,32 @@ class AcpRuntime extends EventEmitter {
1577
1594
  }));
1578
1595
  }
1579
1596
 
1597
+ #supportsAllSessionListing() {
1598
+ return !!(
1599
+ this.#getSessionCapabilities().listAll
1600
+ && typeof this.connection?.listSessions === 'function'
1601
+ );
1602
+ }
1603
+
1604
+ async #listSessionsViaConnection(options = {}) {
1605
+ const response = await this.connection.listSessions({
1606
+ cwd: options.all ? null : (options.cwd || this.cwd),
1607
+ cursor: options.cursor || null
1608
+ });
1609
+ return {
1610
+ sessions: Array.isArray(response?.sessions)
1611
+ ? response.sessions
1612
+ .map(normalizeListedSessionInfo)
1613
+ .filter(
1614
+ (session) => session.sessionId && session.cwd
1615
+ )
1616
+ : [],
1617
+ nextCursor: typeof response?.nextCursor === 'string'
1618
+ ? response.nextCursor
1619
+ : ''
1620
+ };
1621
+ }
1622
+
1580
1623
  #resolveAvailableModes(availableModes, existingModes = []) {
1581
1624
  if (Array.isArray(availableModes) && availableModes.length > 0) {
1582
1625
  this.cachedAvailableModes = availableModes;
@@ -2023,27 +2066,24 @@ class AcpRuntime extends EventEmitter {
2023
2066
  this.clearIdleShutdown();
2024
2067
 
2025
2068
  const sessionCapabilities = this.#getSessionCapabilities();
2069
+ const wantsAll = !!options.all;
2026
2070
  if (
2027
2071
  sessionCapabilities.list
2028
2072
  && typeof this.connection?.listSessions === 'function'
2029
2073
  ) {
2074
+ if (wantsAll && this.#supportsAllSessionListing()) {
2075
+ return await this.#listSessionsViaConnection({
2076
+ cwd: options.cwd || this.cwd,
2077
+ cursor: options.cursor || '',
2078
+ all: true
2079
+ });
2080
+ }
2030
2081
  try {
2031
- const response = await this.connection.listSessions({
2082
+ return await this.#listSessionsViaConnection({
2032
2083
  cwd: options.cwd || this.cwd,
2033
- cursor: options.cursor || null
2084
+ cursor: options.cursor || '',
2085
+ all: false
2034
2086
  });
2035
- return {
2036
- sessions: Array.isArray(response?.sessions)
2037
- ? response.sessions
2038
- .map(normalizeListedSessionInfo)
2039
- .filter(
2040
- (session) => session.sessionId && session.cwd
2041
- )
2042
- : [],
2043
- nextCursor: typeof response?.nextCursor === 'string'
2044
- ? response.nextCursor
2045
- : ''
2046
- };
2047
2087
  } catch (error) {
2048
2088
  const message = String(error?.message || '');
2049
2089
  const canFallbackToCli = this.#supportsGeminiCliSessionListing();
@@ -3391,7 +3431,8 @@ export class AcpManager {
3391
3431
 
3392
3432
  const sessionCapabilities = normalizeAgentSessionCapabilities(
3393
3433
  runtime?.agentCapabilities,
3394
- runtime?.connection
3434
+ runtime?.connection,
3435
+ runtime?.definition?.id
3395
3436
  );
3396
3437
  const hasSessionCapabilities = serialized.sessionCapabilities
3397
3438
  && typeof serialized.sessionCapabilities === 'object';
@@ -3671,11 +3712,19 @@ export class AcpManager {
3671
3712
  const cwd = path.resolve(options.cwd || process.cwd());
3672
3713
  const { runtimeEntry, createdRuntime, runtimeStoreKey } =
3673
3714
  this.#ensureRuntimeEntry(definition, cwd);
3715
+ const cursor = typeof options.cursor === 'string' ? options.cursor : '';
3716
+ const sessionCapabilities = (
3717
+ typeof runtimeEntry.runtime.getSessionCapabilities === 'function'
3718
+ ? runtimeEntry.runtime.getSessionCapabilities()
3719
+ : {}
3720
+ );
3721
+ const canListAll = !!(options.all && sessionCapabilities.listAll);
3674
3722
 
3675
3723
  try {
3676
3724
  const result = await runtimeEntry.runtime.listSessions({
3677
3725
  cwd,
3678
- cursor: typeof options.cursor === 'string' ? options.cursor : ''
3726
+ all: canListAll,
3727
+ cursor
3679
3728
  });
3680
3729
  this.#clearDefinitionAvailabilityOverride(definition.id);
3681
3730
  if (runtimeEntry.runtime.tabs.size === 0) {
@@ -3696,6 +3745,119 @@ export class AcpManager {
3696
3745
  }
3697
3746
  }
3698
3747
 
3748
+ async listResumeSessions(options) {
3749
+ await this.ensureConfigsLoaded();
3750
+ const definition = this.definitions.find(
3751
+ (entry) => entry.id === options.agentId
3752
+ );
3753
+ if (!definition) {
3754
+ throw new Error('Unknown agent');
3755
+ }
3756
+ const availability = this.getDefinitionAvailability(definition);
3757
+ if (!availability.available) {
3758
+ throw new Error(availability.reason || 'Agent unavailable');
3759
+ }
3760
+
3761
+ const cwd = path.resolve(options.cwd || process.cwd());
3762
+ const { runtimeEntry, createdRuntime, runtimeStoreKey } =
3763
+ this.#ensureRuntimeEntry(definition, cwd);
3764
+ const branchLimit = 500;
3765
+ const mergedLimit = 300;
3766
+
3767
+ const listBranch = async (all) => {
3768
+ const sessions = [];
3769
+ let nextCursor = '';
3770
+ const seenCursors = new Set();
3771
+ for (;;) {
3772
+ const result = await runtimeEntry.runtime.listSessions({
3773
+ cwd,
3774
+ all,
3775
+ cursor: nextCursor
3776
+ });
3777
+ sessions.push(...(Array.isArray(result?.sessions)
3778
+ ? result.sessions
3779
+ : []));
3780
+ if (sessions.length >= branchLimit) {
3781
+ return sessions.slice(0, branchLimit);
3782
+ }
3783
+ const previousCursor = nextCursor;
3784
+ nextCursor = typeof result?.nextCursor === 'string'
3785
+ ? result.nextCursor
3786
+ : '';
3787
+ if (!nextCursor) {
3788
+ break;
3789
+ }
3790
+ if (nextCursor === previousCursor || seenCursors.has(nextCursor)) {
3791
+ break;
3792
+ }
3793
+ seenCursors.add(nextCursor);
3794
+ }
3795
+ return sessions;
3796
+ };
3797
+
3798
+ const cwdPromise = listBranch(false);
3799
+ const allPromise = listBranch(true);
3800
+
3801
+ try {
3802
+ const settled = await Promise.allSettled([
3803
+ cwdPromise,
3804
+ allPromise
3805
+ ]);
3806
+ const cwdResult = settled[0];
3807
+ const allResult = settled[1] || null;
3808
+ const cwdSessions = cwdResult?.status === 'fulfilled'
3809
+ ? cwdResult.value
3810
+ : [];
3811
+ const allSessions = allResult?.status === 'fulfilled'
3812
+ && Array.isArray(allResult.value)
3813
+ ? allResult.value
3814
+ : [];
3815
+
3816
+ if (cwdSessions.length === 0 && allSessions.length === 0) {
3817
+ throw (
3818
+ cwdResult?.reason
3819
+ || allResult?.reason
3820
+ || new Error('Failed to list agent sessions')
3821
+ );
3822
+ }
3823
+
3824
+ const merged = [];
3825
+ const seen = new Set();
3826
+ for (const session of [...cwdSessions, ...allSessions]) {
3827
+ const sessionId = String(session?.sessionId || '').trim();
3828
+ if (!sessionId || seen.has(sessionId)) continue;
3829
+ seen.add(sessionId);
3830
+ merged.push(session);
3831
+ if (merged.length >= mergedLimit) {
3832
+ break;
3833
+ }
3834
+ }
3835
+
3836
+ this.#clearDefinitionAvailabilityOverride(definition.id);
3837
+ if (runtimeEntry.runtime.tabs.size === 0) {
3838
+ runtimeEntry.runtime.scheduleIdleShutdown(async () => {
3839
+ if (runtimeEntry.runtime.tabs.size > 0) return;
3840
+ await this.#disposeRuntimeEntry(
3841
+ runtimeStoreKey,
3842
+ runtimeEntry
3843
+ );
3844
+ });
3845
+ }
3846
+
3847
+ return {
3848
+ sessions: merged,
3849
+ scope: allResult?.status === 'fulfilled'
3850
+ ? 'merged'
3851
+ : 'cwd'
3852
+ };
3853
+ } catch (error) {
3854
+ if (createdRuntime && runtimeEntry.runtime.tabs.size === 0) {
3855
+ await this.#disposeRuntimeEntry(runtimeStoreKey, runtimeEntry);
3856
+ }
3857
+ throw error;
3858
+ }
3859
+ }
3860
+
3699
3861
  async resumeTab(options) {
3700
3862
  await this.ensureConfigsLoaded();
3701
3863
  const definition = this.definitions.find(
package/src/fs-routes.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import { constants as fsConstants } from 'node:fs';
2
2
  import fs from 'node:fs/promises';
3
+ import crypto from 'node:crypto';
3
4
  import path from 'node:path';
4
5
  import process from 'node:process';
5
6
 
@@ -12,6 +13,11 @@ const IMAGE_MIME_TYPES = {
12
13
  '.webp': 'image/webp'
13
14
  };
14
15
 
16
+ const RAW_MIME_TYPES = {
17
+ ...IMAGE_MIME_TYPES,
18
+ '.pdf': 'application/pdf'
19
+ };
20
+
15
21
  export function isSupportedTextBuffer(buffer) {
16
22
  if (!Buffer.isBuffer(buffer) || buffer.length === 0) {
17
23
  return true;
@@ -45,6 +51,110 @@ export function isSupportedTextBuffer(buffer) {
45
51
  }
46
52
  }
47
53
 
54
+ function createFsRouteError(message, status) {
55
+ const error = new Error(message);
56
+ error.status = status;
57
+ return error;
58
+ }
59
+
60
+ function normalizeFsRouteError(error, fallbackMessage = 'File system error') {
61
+ if (error?.status) {
62
+ return error;
63
+ }
64
+
65
+ if (error?.code === 'ENOENT' || error?.code === 'ENOTDIR') {
66
+ const notFoundError = createFsRouteError('File not found', 404);
67
+ notFoundError.code = 'file-not-found';
68
+ return notFoundError;
69
+ }
70
+
71
+ if (error?.code === 'EISDIR') {
72
+ return createFsRouteError('Not a file', 400);
73
+ }
74
+
75
+ const normalizedError = createFsRouteError(
76
+ error?.message || fallbackMessage,
77
+ 500
78
+ );
79
+ if (error?.code) {
80
+ normalizedError.code = error.code;
81
+ }
82
+ return normalizedError;
83
+ }
84
+
85
+ export function buildTextFileVersion(buffer) {
86
+ return crypto
87
+ .createHash('sha256')
88
+ .update(buffer)
89
+ .digest('hex');
90
+ }
91
+
92
+ async function canWriteExistingFile(targetPath) {
93
+ try {
94
+ const handle = await fs.open(targetPath, 'r+');
95
+ await handle.close();
96
+ return true;
97
+ } catch {
98
+ return false;
99
+ }
100
+ }
101
+
102
+ export async function readTextFileSnapshot(fullPath) {
103
+ try {
104
+ const stats = await fs.stat(fullPath);
105
+
106
+ if (!stats.isFile()) {
107
+ throw createFsRouteError('Not a file', 400);
108
+ }
109
+
110
+ if (stats.size > 1024 * 1024 * 5) {
111
+ throw createFsRouteError('File too large', 400);
112
+ }
113
+
114
+ const contentBuffer = await fs.readFile(fullPath);
115
+ if (!isSupportedTextBuffer(contentBuffer)) {
116
+ const error = createFsRouteError('Unsupported file type', 415);
117
+ error.code = 'unsupported-file-type';
118
+ throw error;
119
+ }
120
+
121
+ const decoder = new TextDecoder('utf-8', { fatal: true });
122
+ const content = decoder.decode(contentBuffer);
123
+
124
+ return {
125
+ content,
126
+ readonly: !(await canWriteExistingFile(fullPath)),
127
+ version: buildTextFileVersion(contentBuffer),
128
+ size: stats.size,
129
+ mtimeMs: stats.mtimeMs
130
+ };
131
+ } catch (error) {
132
+ throw normalizeFsRouteError(error, 'Unable to read file');
133
+ }
134
+ }
135
+
136
+ export async function writeTextFileSnapshot(
137
+ fullPath,
138
+ content,
139
+ expectedVersion = '',
140
+ force = false
141
+ ) {
142
+ const current = await readTextFileSnapshot(fullPath);
143
+ if (
144
+ !force
145
+ && expectedVersion
146
+ && expectedVersion !== current.version
147
+ ) {
148
+ const error = createFsRouteError('File version conflict', 409);
149
+ error.code = 'file-version-conflict';
150
+ error.snapshot = current;
151
+ throw error;
152
+ }
153
+
154
+ await fs.writeFile(fullPath, content, 'utf8');
155
+ return await readTextFileSnapshot(fullPath);
156
+ }
157
+
48
158
  function joinRelativePath(basePath, name) {
49
159
  if (!basePath || basePath === '.' || basePath === path.sep) {
50
160
  return name;
@@ -325,50 +435,49 @@ export const setupFsRoutes = (router) => {
325
435
 
326
436
  try {
327
437
  const fullPath = resolvePath(baseDir, filePath);
328
- const stats = await fs.stat(fullPath);
329
-
330
- if (!stats.isFile()) {
331
- ctx.status = 400;
332
- ctx.body = { error: 'Not a file' };
333
- return;
334
- }
335
-
336
- if (stats.size > 1024 * 1024 * 5) { // 5MB limit for now
337
- ctx.status = 400;
338
- ctx.body = { error: 'File too large' };
339
- return;
340
- }
341
-
342
- const contentBuffer = await fs.readFile(fullPath);
343
- if (!isSupportedTextBuffer(contentBuffer)) {
344
- ctx.status = 415;
345
- ctx.body = {
346
- error: 'Unsupported file type',
347
- code: 'unsupported-file-type'
348
- };
349
- return;
438
+ ctx.body = await readTextFileSnapshot(fullPath);
439
+ } catch (err) {
440
+ if ((err?.status || 500) >= 500) {
441
+ console.error('FS Read Error:', err);
350
442
  }
443
+ ctx.status = err?.status || 500;
444
+ ctx.body = {
445
+ error: err.message,
446
+ ...(err?.code ? { code: err.code } : {})
447
+ };
448
+ }
449
+ });
351
450
 
352
- const decoder = new TextDecoder('utf-8', { fatal: true });
353
- const content = decoder.decode(contentBuffer);
354
-
355
- let readonly = false;
356
- try {
357
- const handle = await fs.open(fullPath, 'r+');
358
- await handle.close();
359
- } catch {
360
- readonly = true;
361
- }
451
+ router.get('/api/fs/info', async (ctx) => {
452
+ const filePath = ctx.query.path;
453
+ if (!filePath) {
454
+ ctx.status = 400;
455
+ ctx.body = { error: 'Path required' };
456
+ return;
457
+ }
362
458
 
363
- ctx.body = { content, readonly };
459
+ try {
460
+ const fullPath = resolvePath(baseDir, filePath);
461
+ const snapshot = await readTextFileSnapshot(fullPath);
462
+ ctx.body = {
463
+ readonly: snapshot.readonly,
464
+ version: snapshot.version,
465
+ size: snapshot.size,
466
+ mtimeMs: snapshot.mtimeMs
467
+ };
364
468
  } catch (err) {
365
- console.error('FS Read Error:', err);
366
- ctx.status = 500;
367
- ctx.body = { error: err.message };
469
+ if ((err?.status || 500) >= 500) {
470
+ console.error('FS Info Error:', err);
471
+ }
472
+ ctx.status = err?.status || 500;
473
+ ctx.body = {
474
+ error: err.message,
475
+ ...(err?.code ? { code: err.code } : {})
476
+ };
368
477
  }
369
478
  });
370
479
 
371
- // Raw file access (for images)
480
+ // Raw file access (for previews like images and PDFs)
372
481
  router.get('/api/fs/raw', async (ctx) => {
373
482
  const filePath = ctx.query.path;
374
483
  if (!filePath) {
@@ -380,8 +489,8 @@ export const setupFsRoutes = (router) => {
380
489
  const fullPath = resolvePath(baseDir, filePath);
381
490
  const ext = path.extname(fullPath).toLowerCase();
382
491
 
383
- if (IMAGE_MIME_TYPES[ext]) {
384
- ctx.type = IMAGE_MIME_TYPES[ext];
492
+ if (RAW_MIME_TYPES[ext]) {
493
+ ctx.type = RAW_MIME_TYPES[ext];
385
494
  ctx.body = await fs.readFile(fullPath);
386
495
  } else {
387
496
  ctx.status = 400;
package/src/server.mjs CHANGED
@@ -19,7 +19,10 @@ import { AcpManager } from './acp-manager.mjs';
19
19
  import { SystemMonitor } from './system-monitor.mjs';
20
20
  import { config } from './config.mjs';
21
21
  import { authMiddleware, verifyClient } from './auth.mjs';
22
- import { setupFsRoutes } from './fs-routes.mjs';
22
+ import {
23
+ setupFsRoutes,
24
+ writeTextFileSnapshot
25
+ } from './fs-routes.mjs';
23
26
  import * as persistence from './persistence.mjs';
24
27
  import { alan, network, web } from 'utilitas';
25
28
 
@@ -171,6 +174,22 @@ router.get('/healthz', (ctx) => {
171
174
  ctx.body = { status: 'ok' };
172
175
  });
173
176
 
177
+ app.use(async (ctx, next) => {
178
+ if (ctx.method === 'GET' && ctx.path === '/api/version') {
179
+ ctx.set(
180
+ 'Cache-Control',
181
+ 'no-store, no-cache, must-revalidate, proxy-revalidate'
182
+ );
183
+ ctx.set('Pragma', 'no-cache');
184
+ ctx.set('Expires', '0');
185
+ ctx.body = {
186
+ bootId: SERVER_BOOT_ID
187
+ };
188
+ return;
189
+ }
190
+ await next();
191
+ });
192
+
174
193
  // Serve static files (public) BEFORE auth middleware
175
194
  app.use(serve(publicDir));
176
195
 
@@ -206,6 +225,7 @@ setupFsRoutes(router);
206
225
 
207
226
  // API routes for session management
208
227
  router.all('/api/heartbeat', async (ctx) => {
228
+ const fileWriteResults = [];
209
229
  if (ctx.method === 'POST') {
210
230
  const { updates } = ctx.request.body;
211
231
  if (updates && updates.sessions) {
@@ -223,13 +243,50 @@ router.all('/api/heartbeat', async (ctx) => {
223
243
  });
224
244
  }
225
245
  if (update.fileWrites) {
246
+ const sessionResults = [];
226
247
  for (const file of update.fileWrites) {
227
248
  try {
228
- await fsPromises.writeFile(file.path, file.content);
249
+ const snapshot = await writeTextFileSnapshot(
250
+ file.path,
251
+ file.content,
252
+ file.expectedVersion,
253
+ file.force === true
254
+ );
255
+ sessionResults.push({
256
+ path: file.path,
257
+ status: 'ok',
258
+ version: snapshot.version,
259
+ readonly: snapshot.readonly
260
+ });
229
261
  } catch (e) {
230
- console.error(`[Heartbeat] Write failed: ${file.path}`, e);
262
+ if (e?.status === 409) {
263
+ sessionResults.push({
264
+ path: file.path,
265
+ status: 'conflict',
266
+ version: e.snapshot?.version || '',
267
+ content: e.snapshot?.content || '',
268
+ readonly: !!e.snapshot?.readonly,
269
+ error: e.message
270
+ });
271
+ continue;
272
+ }
273
+ console.error(
274
+ `[Heartbeat] Write failed: ${file.path}`,
275
+ e
276
+ );
277
+ sessionResults.push({
278
+ path: file.path,
279
+ status: 'error',
280
+ error: e?.message || 'Write failed'
281
+ });
231
282
  }
232
283
  }
284
+ if (sessionResults.length > 0) {
285
+ fileWriteResults.push({
286
+ id: update.id,
287
+ fileWrites: sessionResults
288
+ });
289
+ }
233
290
  }
234
291
  }
235
292
  }
@@ -239,6 +296,7 @@ router.all('/api/heartbeat', async (ctx) => {
239
296
  ctx.body = {
240
297
  sessions: terminalManager.listSessions(),
241
298
  agents: await acpManager.listInventory(),
299
+ fileWriteResults,
242
300
  system: systemMonitor.getStats(),
243
301
  runtime: {
244
302
  bootId: SERVER_BOOT_ID
@@ -344,7 +402,7 @@ router.get('/api/agents', async (ctx) => {
344
402
  });
345
403
 
346
404
  router.get('/api/agents/sessions', async (ctx) => {
347
- const { agentId = '', cwd = '', cursor = '' } = ctx.query || {};
405
+ const { agentId = '', cwd = '' } = ctx.query || {};
348
406
  if (!agentId || typeof agentId !== 'string') {
349
407
  ctx.status = 400;
350
408
  ctx.body = { error: 'agentId is required' };
@@ -357,28 +415,14 @@ router.get('/api/agents/sessions', async (ctx) => {
357
415
  }
358
416
 
359
417
  try {
360
- let nextCursor = typeof cursor === 'string' ? cursor : '';
361
- const sessions = [];
362
- const paginate = !!nextCursor;
363
- for (let page = 0; page < 5; page += 1) {
364
- const result = await acpManager.listSessions({
365
- agentId,
366
- cwd,
367
- cursor: nextCursor
368
- });
369
- sessions.push(...(Array.isArray(result?.sessions)
370
- ? result.sessions
371
- : []));
372
- nextCursor = typeof result?.nextCursor === 'string'
373
- ? result.nextCursor
374
- : '';
375
- if (paginate || !nextCursor || sessions.length >= 50) {
376
- break;
377
- }
378
- }
418
+ const result = await acpManager.listResumeSessions({
419
+ agentId,
420
+ cwd
421
+ });
379
422
  ctx.body = {
380
- sessions: sessions.slice(0, 50),
381
- nextCursor
423
+ sessions: Array.isArray(result?.sessions) ? result.sessions : [],
424
+ nextCursor: '',
425
+ scope: typeof result?.scope === 'string' ? result.scope : 'cwd'
382
426
  };
383
427
  } catch (error) {
384
428
  const message = error?.message || 'Failed to list agent sessions';