claude-remote-cli 2.15.4 → 2.15.6
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.
- package/dist/frontend/assets/index-6brRnAUY.js +47 -0
- package/dist/frontend/index.html +1 -1
- package/dist/server/index.js +18 -6
- package/dist/server/mobile-input-pipeline.js +107 -0
- package/dist/server/sessions.js +161 -7
- package/dist/test/mobile-input.test.js +163 -0
- package/dist/test/sessions.test.js +153 -1
- package/package.json +1 -1
- package/dist/frontend/assets/index-yU3_SO20.js +0 -47
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { describe, it, afterEach } from 'node:test';
|
|
2
2
|
import assert from 'node:assert';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
3
6
|
import * as sessions from '../server/sessions.js';
|
|
4
|
-
import { resolveTmuxSpawn, generateTmuxSessionName } from '../server/sessions.js';
|
|
7
|
+
import { resolveTmuxSpawn, generateTmuxSessionName, serializeAll, restoreFromDisk } from '../server/sessions.js';
|
|
5
8
|
// Track created session IDs so we can clean up after each test
|
|
6
9
|
const createdIds = [];
|
|
7
10
|
afterEach(() => {
|
|
@@ -425,4 +428,153 @@ describe('sessions', () => {
|
|
|
425
428
|
done();
|
|
426
429
|
});
|
|
427
430
|
});
|
|
431
|
+
it('create accepts a predetermined id', () => {
|
|
432
|
+
const result = sessions.create({
|
|
433
|
+
id: 'custom-id-12345678',
|
|
434
|
+
repoName: 'test-repo',
|
|
435
|
+
repoPath: '/tmp',
|
|
436
|
+
command: '/bin/echo',
|
|
437
|
+
args: ['hello'],
|
|
438
|
+
});
|
|
439
|
+
createdIds.push(result.id);
|
|
440
|
+
assert.strictEqual(result.id, 'custom-id-12345678');
|
|
441
|
+
const session = sessions.get('custom-id-12345678');
|
|
442
|
+
assert.ok(session);
|
|
443
|
+
});
|
|
444
|
+
it('create accepts initialScrollback', () => {
|
|
445
|
+
const result = sessions.create({
|
|
446
|
+
repoName: 'test-repo',
|
|
447
|
+
repoPath: '/tmp',
|
|
448
|
+
command: '/bin/echo',
|
|
449
|
+
args: ['hello'],
|
|
450
|
+
initialScrollback: ['prior output\r\n'],
|
|
451
|
+
});
|
|
452
|
+
createdIds.push(result.id);
|
|
453
|
+
const session = sessions.get(result.id);
|
|
454
|
+
assert.ok(session);
|
|
455
|
+
assert.ok(session.scrollback.length >= 1);
|
|
456
|
+
assert.strictEqual(session.scrollback[0], 'prior output\r\n');
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
describe('session persistence', () => {
|
|
460
|
+
let tmpDir;
|
|
461
|
+
afterEach(() => {
|
|
462
|
+
// Clean up any sessions created during tests
|
|
463
|
+
for (const s of sessions.list()) {
|
|
464
|
+
try {
|
|
465
|
+
sessions.kill(s.id);
|
|
466
|
+
}
|
|
467
|
+
catch { /* ignore */ }
|
|
468
|
+
}
|
|
469
|
+
// Clean up temp directory
|
|
470
|
+
if (tmpDir) {
|
|
471
|
+
try {
|
|
472
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
473
|
+
}
|
|
474
|
+
catch { /* ignore */ }
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
function createTmpDir() {
|
|
478
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'crc-test-'));
|
|
479
|
+
return tmpDir;
|
|
480
|
+
}
|
|
481
|
+
it('serializeAll writes pending-sessions.json and scrollback files', () => {
|
|
482
|
+
const configDir = createTmpDir();
|
|
483
|
+
const s = sessions.create({
|
|
484
|
+
repoName: 'test-repo',
|
|
485
|
+
repoPath: '/tmp',
|
|
486
|
+
command: '/bin/cat',
|
|
487
|
+
args: [],
|
|
488
|
+
});
|
|
489
|
+
// Manually push some scrollback
|
|
490
|
+
const session = sessions.get(s.id);
|
|
491
|
+
assert.ok(session);
|
|
492
|
+
session.scrollback.push('hello world');
|
|
493
|
+
serializeAll(configDir);
|
|
494
|
+
// Check pending-sessions.json
|
|
495
|
+
const pendingPath = path.join(configDir, 'pending-sessions.json');
|
|
496
|
+
assert.ok(fs.existsSync(pendingPath), 'pending-sessions.json should exist');
|
|
497
|
+
const pending = JSON.parse(fs.readFileSync(pendingPath, 'utf-8'));
|
|
498
|
+
assert.strictEqual(pending.version, 1);
|
|
499
|
+
assert.ok(pending.timestamp);
|
|
500
|
+
assert.strictEqual(pending.sessions.length, 1);
|
|
501
|
+
assert.strictEqual(pending.sessions[0].id, s.id);
|
|
502
|
+
assert.strictEqual(pending.sessions[0].repoPath, '/tmp');
|
|
503
|
+
// Check scrollback file
|
|
504
|
+
const scrollbackPath = path.join(configDir, 'scrollback', s.id + '.buf');
|
|
505
|
+
assert.ok(fs.existsSync(scrollbackPath), 'scrollback file should exist');
|
|
506
|
+
const scrollbackData = fs.readFileSync(scrollbackPath, 'utf-8');
|
|
507
|
+
assert.ok(scrollbackData.includes('hello world'));
|
|
508
|
+
});
|
|
509
|
+
it('restoreFromDisk restores sessions with original IDs', async () => {
|
|
510
|
+
const configDir = createTmpDir();
|
|
511
|
+
// Create and serialize a session
|
|
512
|
+
const s = sessions.create({
|
|
513
|
+
repoName: 'test-repo',
|
|
514
|
+
repoPath: '/tmp',
|
|
515
|
+
command: '/bin/cat',
|
|
516
|
+
args: [],
|
|
517
|
+
displayName: 'my-session',
|
|
518
|
+
});
|
|
519
|
+
const originalId = s.id;
|
|
520
|
+
const session = sessions.get(originalId);
|
|
521
|
+
assert.ok(session);
|
|
522
|
+
session.scrollback.push('saved output');
|
|
523
|
+
serializeAll(configDir);
|
|
524
|
+
// Kill the original session
|
|
525
|
+
sessions.kill(originalId);
|
|
526
|
+
assert.strictEqual(sessions.get(originalId), undefined);
|
|
527
|
+
// Restore
|
|
528
|
+
const restored = await restoreFromDisk(configDir);
|
|
529
|
+
assert.strictEqual(restored, 1);
|
|
530
|
+
// Verify session exists with original ID
|
|
531
|
+
const restoredSession = sessions.get(originalId);
|
|
532
|
+
assert.ok(restoredSession, 'restored session should exist');
|
|
533
|
+
assert.strictEqual(restoredSession.repoPath, '/tmp');
|
|
534
|
+
assert.strictEqual(restoredSession.displayName, 'my-session');
|
|
535
|
+
// Scrollback should be restored
|
|
536
|
+
assert.ok(restoredSession.scrollback.length >= 1);
|
|
537
|
+
assert.strictEqual(restoredSession.scrollback[0], 'saved output');
|
|
538
|
+
// pending-sessions.json should be cleaned up
|
|
539
|
+
assert.ok(!fs.existsSync(path.join(configDir, 'pending-sessions.json')));
|
|
540
|
+
});
|
|
541
|
+
it('restoreFromDisk ignores stale files (>5 min old)', async () => {
|
|
542
|
+
const configDir = createTmpDir();
|
|
543
|
+
// Write a stale pending file
|
|
544
|
+
const staleTime = new Date(Date.now() - 6 * 60 * 1000).toISOString();
|
|
545
|
+
const pending = {
|
|
546
|
+
version: 1,
|
|
547
|
+
timestamp: staleTime,
|
|
548
|
+
sessions: [{ id: 'stale-id', type: 'repo', agent: 'claude', root: '', repoName: 'test', repoPath: '/tmp', worktreeName: '', branchName: '', displayName: 'test', createdAt: staleTime, lastActivity: staleTime, useTmux: false, tmuxSessionName: '', customCommand: null, cwd: '/tmp' }],
|
|
549
|
+
};
|
|
550
|
+
fs.writeFileSync(path.join(configDir, 'pending-sessions.json'), JSON.stringify(pending));
|
|
551
|
+
const restored = await restoreFromDisk(configDir);
|
|
552
|
+
assert.strictEqual(restored, 0, 'should not restore stale sessions');
|
|
553
|
+
assert.ok(!fs.existsSync(path.join(configDir, 'pending-sessions.json')), 'stale file should be deleted');
|
|
554
|
+
});
|
|
555
|
+
it('restoreFromDisk handles missing scrollback gracefully', async () => {
|
|
556
|
+
const configDir = createTmpDir();
|
|
557
|
+
// Create a session, serialize, then delete scrollback file
|
|
558
|
+
const s = sessions.create({
|
|
559
|
+
repoName: 'test-repo',
|
|
560
|
+
repoPath: '/tmp',
|
|
561
|
+
command: '/bin/cat',
|
|
562
|
+
args: [],
|
|
563
|
+
});
|
|
564
|
+
serializeAll(configDir);
|
|
565
|
+
sessions.kill(s.id);
|
|
566
|
+
// Delete scrollback file
|
|
567
|
+
const scrollbackPath = path.join(configDir, 'scrollback', s.id + '.buf');
|
|
568
|
+
try {
|
|
569
|
+
fs.unlinkSync(scrollbackPath);
|
|
570
|
+
}
|
|
571
|
+
catch { /* ignore */ }
|
|
572
|
+
const restored = await restoreFromDisk(configDir);
|
|
573
|
+
assert.strictEqual(restored, 1, 'should still restore without scrollback');
|
|
574
|
+
});
|
|
575
|
+
it('restoreFromDisk returns 0 when no pending file exists', async () => {
|
|
576
|
+
const configDir = createTmpDir();
|
|
577
|
+
const restored = await restoreFromDisk(configDir);
|
|
578
|
+
assert.strictEqual(restored, 0);
|
|
579
|
+
});
|
|
428
580
|
});
|