claude-yes 1.31.2 → 1.32.2
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/README.md +225 -21
- package/dist/agent-yes.js +2 -0
- package/dist/amp-yes.js +2 -0
- package/dist/auggie-yes.js +2 -0
- package/dist/claude-yes.js +2 -20432
- package/dist/cli.js +18342 -10955
- package/dist/codex-yes.js +2 -20432
- package/dist/copilot-yes.js +2 -20432
- package/dist/cursor-yes.js +2 -20432
- package/dist/gemini-yes.js +2 -20432
- package/dist/grok-yes.js +2 -20432
- package/dist/index.js +16258 -13586
- package/dist/qwen-yes.js +2 -20432
- package/package.json +93 -81
- package/ts/ReadyManager.spec.ts +10 -10
- package/ts/ReadyManager.ts +1 -1
- package/ts/SUPPORTED_CLIS.ts +4 -0
- package/ts/catcher.spec.ts +69 -70
- package/ts/cli-idle.spec.ts +8 -8
- package/ts/cli.ts +18 -26
- package/ts/defineConfig.ts +4 -4
- package/ts/idleWaiter.spec.ts +9 -9
- package/ts/index.ts +474 -233
- package/ts/logger.ts +22 -0
- package/ts/parseCliArgs.spec.ts +146 -147
- package/ts/parseCliArgs.ts +127 -59
- package/ts/postbuild.ts +29 -15
- package/ts/pty-fix.ts +155 -0
- package/ts/pty.ts +19 -0
- package/ts/removeControlCharacters.spec.ts +37 -38
- package/ts/removeControlCharacters.ts +2 -1
- package/ts/runningLock.spec.ts +119 -125
- package/ts/runningLock.ts +44 -55
- package/ts/session-integration.spec.ts +34 -42
- package/ts/utils.spec.ts +35 -35
- package/ts/utils.ts +7 -7
- package/dist/cli.js.map +0 -365
- package/dist/index.js.map +0 -323
- package/ts/codex-resume.spec.ts +0 -239
- package/ts/codexSessionManager.spec.ts +0 -51
- package/ts/codexSessionManager.test.ts +0 -259
- package/ts/codexSessionManager.ts +0 -312
- package/ts/yesLog.spec.ts +0 -74
- package/ts/yesLog.ts +0 -27
package/ts/codex-resume.spec.ts
DELETED
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
import { afterAll, beforeAll, describe, expect, it } from 'bun:test';
|
|
2
|
-
import { spawn } from 'child_process';
|
|
3
|
-
import { mkdir, rm } from 'fs/promises';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { promisify } from 'util';
|
|
6
|
-
|
|
7
|
-
const sleep = promisify(setTimeout);
|
|
8
|
-
|
|
9
|
-
// Helper function to run codex-yes with proper error handling
|
|
10
|
-
function runCodexYes(
|
|
11
|
-
args: string[],
|
|
12
|
-
cwd: string,
|
|
13
|
-
timeout: number = 30000,
|
|
14
|
-
): Promise<{
|
|
15
|
-
stdout: string;
|
|
16
|
-
stderr: string;
|
|
17
|
-
exitCode: number | null;
|
|
18
|
-
}> {
|
|
19
|
-
return new Promise((resolve) => {
|
|
20
|
-
const child = spawn('node', ['dist/cli.js', 'codex', ...args], {
|
|
21
|
-
cwd,
|
|
22
|
-
stdio: 'pipe',
|
|
23
|
-
env: { ...process.env, VERBOSE: '1' },
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
let stdout = '';
|
|
27
|
-
let stderr = '';
|
|
28
|
-
|
|
29
|
-
child.stdout?.on('data', (data) => {
|
|
30
|
-
stdout += data.toString();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
child.stderr?.on('data', (data) => {
|
|
34
|
-
stderr += data.toString();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const timeoutId = setTimeout(() => {
|
|
38
|
-
child.kill('SIGTERM');
|
|
39
|
-
setTimeout(() => child.kill('SIGKILL'), 5000);
|
|
40
|
-
}, timeout);
|
|
41
|
-
|
|
42
|
-
child.on('exit', (code) => {
|
|
43
|
-
clearTimeout(timeoutId);
|
|
44
|
-
resolve({
|
|
45
|
-
stdout,
|
|
46
|
-
stderr,
|
|
47
|
-
exitCode: code,
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
child.on('error', (error) => {
|
|
52
|
-
clearTimeout(timeoutId);
|
|
53
|
-
resolve({
|
|
54
|
-
stdout,
|
|
55
|
-
stderr: stderr + error.message,
|
|
56
|
-
exitCode: null,
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
describe('Codex Session Restoration', () => {
|
|
63
|
-
const testDir = path.join(__dirname, '../logs');
|
|
64
|
-
const cwd1 = path.join(testDir, 'cwd1');
|
|
65
|
-
const cwd2 = path.join(testDir, 'cwd2');
|
|
66
|
-
|
|
67
|
-
beforeAll(async () => {
|
|
68
|
-
// Create test directories
|
|
69
|
-
await mkdir(cwd1, { recursive: true });
|
|
70
|
-
await mkdir(cwd2, { recursive: true });
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
afterAll(async () => {
|
|
74
|
-
// Clean up test directories
|
|
75
|
-
await rm(testDir, { recursive: true, force: true });
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('should maintain separate sessions for different directories', async () => {
|
|
79
|
-
console.log('\n=== Testing Codex Session Restoration ===\n');
|
|
80
|
-
|
|
81
|
-
// Step 1: Start codex-yes in cwd1 with 60s timeout (background)
|
|
82
|
-
console.log('Step 1: Starting codex-yes in cwd1 (60s timeout)...');
|
|
83
|
-
const cwd1Promise = runCodexYes(
|
|
84
|
-
['-e', '60s', 'hello from cwd1'],
|
|
85
|
-
cwd1,
|
|
86
|
-
70000,
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
// Wait a bit for cwd1 to initialize
|
|
90
|
-
await sleep(2000);
|
|
91
|
-
|
|
92
|
-
// Step 2: Start codex-yes in cwd2 with 5s timeout (foreground)
|
|
93
|
-
console.log('Step 2: Starting codex-yes in cwd2 (5s timeout)...');
|
|
94
|
-
const cwd2Result = await runCodexYes(
|
|
95
|
-
['-e', '5s', 'hello from cwd2'],
|
|
96
|
-
cwd2,
|
|
97
|
-
15000,
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
console.log('Step 2 completed - cwd2 result:');
|
|
101
|
-
console.log('- Exit code:', cwd2Result.exitCode);
|
|
102
|
-
console.log('- Stdout length:', cwd2Result.stdout.length);
|
|
103
|
-
console.log('- Stderr length:', cwd2Result.stderr.length);
|
|
104
|
-
|
|
105
|
-
// Step 3: Wait for cwd1 to complete
|
|
106
|
-
console.log('Step 3: Waiting for cwd1 to complete...');
|
|
107
|
-
const cwd1Result = await cwd1Promise;
|
|
108
|
-
|
|
109
|
-
console.log('Step 3 completed - cwd1 result:');
|
|
110
|
-
console.log('- Exit code:', cwd1Result.exitCode);
|
|
111
|
-
console.log('- Stdout length:', cwd1Result.stdout.length);
|
|
112
|
-
console.log('- Stderr length:', cwd1Result.stderr.length);
|
|
113
|
-
|
|
114
|
-
// Step 4: Test session restoration in cwd1 using --continue
|
|
115
|
-
console.log(
|
|
116
|
-
'Step 4: Testing session restoration in cwd1 with --continue...',
|
|
117
|
-
);
|
|
118
|
-
const continueResult = await runCodexYes(
|
|
119
|
-
['--continue', '-e', '10s', 'hello3 continuing session'],
|
|
120
|
-
cwd1,
|
|
121
|
-
20000,
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
console.log('Step 4 completed - continue result:');
|
|
125
|
-
console.log('- Exit code:', continueResult.exitCode);
|
|
126
|
-
console.log('- Stdout length:', continueResult.stdout.length);
|
|
127
|
-
console.log('- Stderr length:', continueResult.stderr.length);
|
|
128
|
-
|
|
129
|
-
// Analyze results
|
|
130
|
-
console.log('\n=== Analysis ===');
|
|
131
|
-
|
|
132
|
-
// Check that sessions ran (allowing for various exit codes since codex might not be available)
|
|
133
|
-
const cwd1Ran =
|
|
134
|
-
cwd1Result.stdout.length > 0 || cwd1Result.stderr.length > 0;
|
|
135
|
-
const cwd2Ran =
|
|
136
|
-
cwd2Result.stdout.length > 0 || cwd2Result.stderr.length > 0;
|
|
137
|
-
const continueRan =
|
|
138
|
-
continueResult.stdout.length > 0 || continueResult.stderr.length > 0;
|
|
139
|
-
|
|
140
|
-
console.log('Sessions executed:');
|
|
141
|
-
console.log('- cwd1:', cwd1Ran ? 'YES' : 'NO');
|
|
142
|
-
console.log('- cwd2:', cwd2Ran ? 'YES' : 'NO');
|
|
143
|
-
console.log('- continue:', continueRan ? 'YES' : 'NO');
|
|
144
|
-
|
|
145
|
-
// Look for session-related logs in the outputs
|
|
146
|
-
const hasSessionLogs = (output: string) => {
|
|
147
|
-
return (
|
|
148
|
-
output.includes('session|') ||
|
|
149
|
-
output.includes('continue|') ||
|
|
150
|
-
output.includes('restore|') ||
|
|
151
|
-
output.includes('Session ID') ||
|
|
152
|
-
output.includes('resume')
|
|
153
|
-
);
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
const cwd1HasSessionLogs = hasSessionLogs(
|
|
157
|
-
cwd1Result.stdout + cwd1Result.stderr,
|
|
158
|
-
);
|
|
159
|
-
const cwd2HasSessionLogs = hasSessionLogs(
|
|
160
|
-
cwd2Result.stdout + cwd2Result.stderr,
|
|
161
|
-
);
|
|
162
|
-
const continueHasSessionLogs = hasSessionLogs(
|
|
163
|
-
continueResult.stdout + continueResult.stderr,
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
console.log('Session management logs found:');
|
|
167
|
-
console.log('- cwd1:', cwd1HasSessionLogs ? 'YES' : 'NO');
|
|
168
|
-
console.log('- cwd2:', cwd2HasSessionLogs ? 'YES' : 'NO');
|
|
169
|
-
console.log('- continue:', continueHasSessionLogs ? 'YES' : 'NO');
|
|
170
|
-
|
|
171
|
-
// Extract any visible session IDs or relevant logs
|
|
172
|
-
const extractRelevantLogs = (output: string, label: string) => {
|
|
173
|
-
const lines = output.split('\n');
|
|
174
|
-
const relevantLines = lines.filter(
|
|
175
|
-
(line) =>
|
|
176
|
-
line.includes('session|') ||
|
|
177
|
-
line.includes('continue|') ||
|
|
178
|
-
line.includes('restore|') ||
|
|
179
|
-
line.includes('Session') ||
|
|
180
|
-
line.includes('resume') ||
|
|
181
|
-
line.includes('UUID') ||
|
|
182
|
-
/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i.test(
|
|
183
|
-
line,
|
|
184
|
-
),
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
if (relevantLines.length > 0) {
|
|
188
|
-
console.log(`\n${label} relevant logs:`);
|
|
189
|
-
relevantLines.forEach((line) => console.log(` ${line.trim()}`));
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
extractRelevantLogs(cwd1Result.stdout + cwd1Result.stderr, 'CWD1');
|
|
194
|
-
extractRelevantLogs(cwd2Result.stdout + cwd2Result.stderr, 'CWD2');
|
|
195
|
-
extractRelevantLogs(
|
|
196
|
-
continueResult.stdout + continueResult.stderr,
|
|
197
|
-
'CONTINUE',
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
// Basic assertions - the test should at least attempt to run
|
|
201
|
-
expect(cwd1Ran || cwd2Ran || continueRan).toBe(true);
|
|
202
|
-
|
|
203
|
-
// If codex is available and working, we should see some session management
|
|
204
|
-
if (cwd1Result.exitCode === 0 && cwd2Result.exitCode === 0) {
|
|
205
|
-
console.log(
|
|
206
|
-
'\nCodex appears to be working - checking for session management...',
|
|
207
|
-
);
|
|
208
|
-
// At least one of the runs should show session management activity
|
|
209
|
-
expect(
|
|
210
|
-
cwd1HasSessionLogs || cwd2HasSessionLogs || continueHasSessionLogs,
|
|
211
|
-
).toBe(true);
|
|
212
|
-
} else {
|
|
213
|
-
console.log(
|
|
214
|
-
'\nCodex may not be available or working - test completed with basic execution check',
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
console.log('\n=== Test Summary ===');
|
|
219
|
-
console.log('✅ Session restoration test completed');
|
|
220
|
-
console.log('✅ Multiple directories tested');
|
|
221
|
-
console.log('✅ Continue functionality tested');
|
|
222
|
-
console.log('✅ Session isolation verified');
|
|
223
|
-
}, 120000); // 2 minute timeout for the entire test
|
|
224
|
-
|
|
225
|
-
it('should handle missing codex gracefully', async () => {
|
|
226
|
-
console.log('\n=== Testing Error Handling ===\n');
|
|
227
|
-
|
|
228
|
-
// Test with a simple command to ensure our wrapper handles missing codex
|
|
229
|
-
const result = await runCodexYes(['--help'], cwd1, 5000);
|
|
230
|
-
|
|
231
|
-
// Should either work (if codex is installed) or fail gracefully
|
|
232
|
-
const hasOutput = result.stdout.length > 0 || result.stderr.length > 0;
|
|
233
|
-
expect(hasOutput).toBe(true);
|
|
234
|
-
|
|
235
|
-
console.log('Error handling test completed');
|
|
236
|
-
console.log('- Has output:', hasOutput);
|
|
237
|
-
console.log('- Exit code:', result.exitCode);
|
|
238
|
-
});
|
|
239
|
-
});
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'bun:test';
|
|
2
|
-
import {
|
|
3
|
-
extractSessionId,
|
|
4
|
-
extractSessionIdFromSessionMeta,
|
|
5
|
-
} from './codexSessionManager';
|
|
6
|
-
|
|
7
|
-
describe('codexSessionManager', () => {
|
|
8
|
-
describe('extractSessionId', () => {
|
|
9
|
-
it('should extract UUID session ID from output', () => {
|
|
10
|
-
const output =
|
|
11
|
-
'Some text with session ID: 0199e659-0e5f-7843-8876-5a65c64e77c0 in it';
|
|
12
|
-
const sessionId = extractSessionId(output);
|
|
13
|
-
expect(sessionId).toBe('0199e659-0e5f-7843-8876-5a65c64e77c0');
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('should return null if no session ID found', () => {
|
|
17
|
-
const output = 'Some text without session ID';
|
|
18
|
-
const sessionId = extractSessionId(output);
|
|
19
|
-
expect(sessionId).toBe(null);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('should handle uppercase UUIDs', () => {
|
|
23
|
-
const output = 'Session: 0199E659-0E5F-7843-8876-5A65C64E77C0';
|
|
24
|
-
const sessionId = extractSessionId(output);
|
|
25
|
-
expect(sessionId).toBe('0199E659-0E5F-7843-8876-5A65C64E77C0');
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
describe('extractSessionIdFromSessionMeta', () => {
|
|
30
|
-
it('should extract session ID from session metadata JSON', () => {
|
|
31
|
-
const sessionContent = `{"timestamp":"2025-10-15T05:30:20.265Z","type":"session_meta","payload":{"id":"0199e659-0e5f-7843-8876-5a65c64e77c0","timestamp":"2025-10-15T05:30:20.127Z","cwd":"/some/path"}}
|
|
32
|
-
{"timestamp":"2025-10-15T05:30:20.415Z","type":"response_item","payload":{"type":"message","role":"user"}}`;
|
|
33
|
-
|
|
34
|
-
const sessionId = extractSessionIdFromSessionMeta(sessionContent);
|
|
35
|
-
expect(sessionId).toBe('0199e659-0e5f-7843-8876-5a65c64e77c0');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('should fall back to regex extraction if JSON parsing fails', () => {
|
|
39
|
-
const sessionContent =
|
|
40
|
-
'Invalid JSON but contains 0199e659-0e5f-7843-8876-5a65c64e77c0';
|
|
41
|
-
const sessionId = extractSessionIdFromSessionMeta(sessionContent);
|
|
42
|
-
expect(sessionId).toBe('0199e659-0e5f-7843-8876-5a65c64e77c0');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('should return null if no session ID found', () => {
|
|
46
|
-
const sessionContent = 'No session ID here';
|
|
47
|
-
const sessionId = extractSessionIdFromSessionMeta(sessionContent);
|
|
48
|
-
expect(sessionId).toBe(null);
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
});
|
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
|
2
|
-
import { mkdir, rm, writeFile } from 'fs/promises';
|
|
3
|
-
import { tmpdir } from 'os';
|
|
4
|
-
import { join } from 'path';
|
|
5
|
-
import {
|
|
6
|
-
type CodexSession,
|
|
7
|
-
extractSessionId,
|
|
8
|
-
extractSessionIdFromSessionMeta,
|
|
9
|
-
getAllWorkingDirectories,
|
|
10
|
-
getRecentSessionsForCwd,
|
|
11
|
-
getSessionForCwd,
|
|
12
|
-
storeSessionForCwd,
|
|
13
|
-
} from './codexSessionManager';
|
|
14
|
-
|
|
15
|
-
// Create a temporary test directory
|
|
16
|
-
const testDir = join(tmpdir(), 'claude-yes-test-' + Date.now());
|
|
17
|
-
const testCodexDir = join(testDir, '.codex', 'sessions');
|
|
18
|
-
const testConfigDir = join(testDir, '.config', 'cli-yes');
|
|
19
|
-
|
|
20
|
-
// Store original environment
|
|
21
|
-
const originalTestHome = process.env.CLI_YES_TEST_HOME;
|
|
22
|
-
|
|
23
|
-
beforeEach(async () => {
|
|
24
|
-
// Set up test directories
|
|
25
|
-
await mkdir(testCodexDir, { recursive: true });
|
|
26
|
-
await mkdir(testConfigDir, { recursive: true });
|
|
27
|
-
|
|
28
|
-
// Set test home directory
|
|
29
|
-
process.env.CLI_YES_TEST_HOME = testDir;
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
afterEach(async () => {
|
|
33
|
-
// Clean up
|
|
34
|
-
process.env.CLI_YES_TEST_HOME = originalTestHome;
|
|
35
|
-
await rm(testDir, { recursive: true, force: true });
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// Helper function to create a mock codex session file
|
|
39
|
-
async function createMockSessionFile(sessionData: {
|
|
40
|
-
id: string;
|
|
41
|
-
timestamp: string;
|
|
42
|
-
cwd: string;
|
|
43
|
-
git?: any;
|
|
44
|
-
}) {
|
|
45
|
-
const year = new Date(sessionData.timestamp).getFullYear();
|
|
46
|
-
const month = String(new Date(sessionData.timestamp).getMonth() + 1).padStart(
|
|
47
|
-
2,
|
|
48
|
-
'0',
|
|
49
|
-
);
|
|
50
|
-
const day = String(new Date(sessionData.timestamp).getDate()).padStart(
|
|
51
|
-
2,
|
|
52
|
-
'0',
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
const sessionDir = join(testCodexDir, String(year), month, day);
|
|
56
|
-
await mkdir(sessionDir, { recursive: true });
|
|
57
|
-
|
|
58
|
-
const filename = `test-session-${sessionData.id}.jsonl`;
|
|
59
|
-
const filePath = join(sessionDir, filename);
|
|
60
|
-
|
|
61
|
-
const sessionMeta = {
|
|
62
|
-
timestamp: sessionData.timestamp,
|
|
63
|
-
type: 'session_meta',
|
|
64
|
-
payload: {
|
|
65
|
-
id: sessionData.id,
|
|
66
|
-
timestamp: sessionData.timestamp,
|
|
67
|
-
cwd: sessionData.cwd,
|
|
68
|
-
originator: 'codex_cli_rs',
|
|
69
|
-
cli_version: '0.42.0',
|
|
70
|
-
instructions: null,
|
|
71
|
-
git: sessionData.git,
|
|
72
|
-
},
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const content = JSON.stringify(sessionMeta) + '\n';
|
|
76
|
-
await writeFile(filePath, content);
|
|
77
|
-
|
|
78
|
-
return filePath;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
describe('codexSessionManager', () => {
|
|
82
|
-
describe('extractSessionId', () => {
|
|
83
|
-
it('should extract valid session IDs from output', () => {
|
|
84
|
-
const output1 = 'Session ID: 019a4877-5f3c-7763-b573-513cc2d5d291';
|
|
85
|
-
const output2 =
|
|
86
|
-
'Starting session 019a4877-5f3c-7763-b573-513cc2d5d291 for user';
|
|
87
|
-
const output3 = 'No session ID here';
|
|
88
|
-
|
|
89
|
-
expect(extractSessionId(output1)).toBe(
|
|
90
|
-
'019a4877-5f3c-7763-b573-513cc2d5d291',
|
|
91
|
-
);
|
|
92
|
-
expect(extractSessionId(output2)).toBe(
|
|
93
|
-
'019a4877-5f3c-7763-b573-513cc2d5d291',
|
|
94
|
-
);
|
|
95
|
-
expect(extractSessionId(output3)).toBeNull();
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe('extractSessionIdFromSessionMeta', () => {
|
|
100
|
-
it('should extract session ID from valid session metadata', () => {
|
|
101
|
-
const sessionContent = JSON.stringify({
|
|
102
|
-
timestamp: '2025-11-03T06:46:14.123Z',
|
|
103
|
-
type: 'session_meta',
|
|
104
|
-
payload: {
|
|
105
|
-
id: '019a4877-5f3c-7763-b573-513cc2d5d291',
|
|
106
|
-
cwd: '/test/path',
|
|
107
|
-
},
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
expect(extractSessionIdFromSessionMeta(sessionContent)).toBe(
|
|
111
|
-
'019a4877-5f3c-7763-b573-513cc2d5d291',
|
|
112
|
-
);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('should fall back to regex extraction for invalid JSON', () => {
|
|
116
|
-
const invalidContent =
|
|
117
|
-
'Invalid JSON but contains 019a4877-5f3c-7763-b573-513cc2d5d291';
|
|
118
|
-
|
|
119
|
-
expect(extractSessionIdFromSessionMeta(invalidContent)).toBe(
|
|
120
|
-
'019a4877-5f3c-7763-b573-513cc2d5d291',
|
|
121
|
-
);
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
describe('session storage and retrieval', () => {
|
|
126
|
-
it('should store and retrieve session IDs for directories', async () => {
|
|
127
|
-
const cwd = '/test/project';
|
|
128
|
-
const sessionId = '019a4877-5f3c-7763-b573-513cc2d5d291';
|
|
129
|
-
|
|
130
|
-
await storeSessionForCwd(cwd, sessionId);
|
|
131
|
-
const retrieved = await getSessionForCwd(cwd);
|
|
132
|
-
|
|
133
|
-
expect(retrieved).toBe(sessionId);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('should return null for non-existent directories', async () => {
|
|
137
|
-
const result = await getSessionForCwd('/non/existent');
|
|
138
|
-
expect(result).toBeNull();
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
describe('codex session file parsing', () => {
|
|
143
|
-
it('should read sessions from actual codex files', async () => {
|
|
144
|
-
const sessionData = {
|
|
145
|
-
id: '019a4877-5f3c-7763-b573-513cc2d5d291',
|
|
146
|
-
timestamp: '2025-11-03T06:46:14.123Z',
|
|
147
|
-
cwd: '/v1/code/snomiao/claude-yes/tree/main',
|
|
148
|
-
git: {
|
|
149
|
-
commit_hash: 'abc123',
|
|
150
|
-
branch: 'main',
|
|
151
|
-
repository_url: 'git@github.com:snomiao/claude-yes.git',
|
|
152
|
-
},
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
await createMockSessionFile(sessionData);
|
|
156
|
-
|
|
157
|
-
const retrieved = await getSessionForCwd(sessionData.cwd);
|
|
158
|
-
expect(retrieved).toBe(sessionData.id);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it('should get recent sessions for a directory', async () => {
|
|
162
|
-
const cwd = '/test/project';
|
|
163
|
-
const sessions = [
|
|
164
|
-
{
|
|
165
|
-
id: 'session-1',
|
|
166
|
-
timestamp: '2025-11-03T10:00:00.000Z',
|
|
167
|
-
cwd,
|
|
168
|
-
},
|
|
169
|
-
{
|
|
170
|
-
id: 'session-2',
|
|
171
|
-
timestamp: '2025-11-03T09:00:00.000Z',
|
|
172
|
-
cwd,
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
id: 'session-3',
|
|
176
|
-
timestamp: '2025-11-03T08:00:00.000Z',
|
|
177
|
-
cwd,
|
|
178
|
-
},
|
|
179
|
-
];
|
|
180
|
-
|
|
181
|
-
for (const session of sessions) {
|
|
182
|
-
await createMockSessionFile(session);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const recent = await getRecentSessionsForCwd(cwd, 2);
|
|
186
|
-
expect(recent).toHaveLength(2);
|
|
187
|
-
expect(recent[0].id).toBe('session-1'); // Most recent first
|
|
188
|
-
expect(recent[1].id).toBe('session-2');
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it('should get all working directories with counts', async () => {
|
|
192
|
-
const sessions = [
|
|
193
|
-
{
|
|
194
|
-
id: 'session-1',
|
|
195
|
-
timestamp: '2025-11-03T10:00:00.000Z',
|
|
196
|
-
cwd: '/project-a',
|
|
197
|
-
},
|
|
198
|
-
{
|
|
199
|
-
id: 'session-2',
|
|
200
|
-
timestamp: '2025-11-03T09:00:00.000Z',
|
|
201
|
-
cwd: '/project-a',
|
|
202
|
-
},
|
|
203
|
-
{
|
|
204
|
-
id: 'session-3',
|
|
205
|
-
timestamp: '2025-11-03T08:00:00.000Z',
|
|
206
|
-
cwd: '/project-b',
|
|
207
|
-
},
|
|
208
|
-
];
|
|
209
|
-
|
|
210
|
-
for (const session of sessions) {
|
|
211
|
-
await createMockSessionFile(session);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const directories = await getAllWorkingDirectories();
|
|
215
|
-
expect(directories).toHaveLength(2);
|
|
216
|
-
|
|
217
|
-
const projectA = directories.find((d) => d.cwd === '/project-a');
|
|
218
|
-
const projectB = directories.find((d) => d.cwd === '/project-b');
|
|
219
|
-
|
|
220
|
-
expect(projectA?.count).toBe(2);
|
|
221
|
-
expect(projectB?.count).toBe(1);
|
|
222
|
-
|
|
223
|
-
// Should be sorted by last session time (most recent first)
|
|
224
|
-
expect(directories[0].cwd).toBe('/project-a');
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
describe('fallback behavior', () => {
|
|
229
|
-
it('should fall back to stored mapping when no codex files exist', async () => {
|
|
230
|
-
const cwd = '/fallback/test';
|
|
231
|
-
const sessionId = 'fallback-session-id';
|
|
232
|
-
|
|
233
|
-
// Store in mapping but don't create codex file
|
|
234
|
-
await storeSessionForCwd(cwd, sessionId);
|
|
235
|
-
|
|
236
|
-
const retrieved = await getSessionForCwd(cwd);
|
|
237
|
-
expect(retrieved).toBe(sessionId);
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
it('should prefer codex files over stored mapping', async () => {
|
|
241
|
-
const cwd = '/preference/test';
|
|
242
|
-
const storedSessionId = 'stored-session';
|
|
243
|
-
const codexSessionId = 'codex-session';
|
|
244
|
-
|
|
245
|
-
// Store in mapping first
|
|
246
|
-
await storeSessionForCwd(cwd, storedSessionId);
|
|
247
|
-
|
|
248
|
-
// Create codex file with different session ID
|
|
249
|
-
await createMockSessionFile({
|
|
250
|
-
id: codexSessionId,
|
|
251
|
-
timestamp: '2025-11-03T10:00:00.000Z',
|
|
252
|
-
cwd,
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
const retrieved = await getSessionForCwd(cwd);
|
|
256
|
-
expect(retrieved).toBe(codexSessionId); // Should prefer codex file
|
|
257
|
-
});
|
|
258
|
-
});
|
|
259
|
-
});
|