ccmanager 2.8.0 → 2.9.0
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/cli.test.js +13 -2
- package/dist/components/App.js +125 -50
- package/dist/components/App.test.js +270 -0
- package/dist/components/ConfigureShortcuts.js +82 -8
- package/dist/components/DeleteWorktree.js +39 -5
- package/dist/components/DeleteWorktree.test.d.ts +1 -0
- package/dist/components/DeleteWorktree.test.js +128 -0
- package/dist/components/LoadingSpinner.d.ts +8 -0
- package/dist/components/LoadingSpinner.js +37 -0
- package/dist/components/LoadingSpinner.test.d.ts +1 -0
- package/dist/components/LoadingSpinner.test.js +187 -0
- package/dist/components/Menu.js +64 -16
- package/dist/components/Menu.recent-projects.test.js +32 -11
- package/dist/components/Menu.test.js +136 -4
- package/dist/components/MergeWorktree.js +79 -18
- package/dist/components/MergeWorktree.test.d.ts +1 -0
- package/dist/components/MergeWorktree.test.js +227 -0
- package/dist/components/NewWorktree.js +88 -9
- package/dist/components/NewWorktree.test.d.ts +1 -0
- package/dist/components/NewWorktree.test.js +244 -0
- package/dist/components/ProjectList.js +44 -13
- package/dist/components/ProjectList.recent-projects.test.js +8 -3
- package/dist/components/ProjectList.test.js +105 -8
- package/dist/components/RemoteBranchSelector.test.js +3 -1
- package/dist/hooks/useGitStatus.d.ts +11 -0
- package/dist/hooks/useGitStatus.js +70 -12
- package/dist/hooks/useGitStatus.test.js +30 -23
- package/dist/services/configurationManager.d.ts +75 -0
- package/dist/services/configurationManager.effect.test.d.ts +1 -0
- package/dist/services/configurationManager.effect.test.js +407 -0
- package/dist/services/configurationManager.js +246 -0
- package/dist/services/globalSessionOrchestrator.test.js +0 -8
- package/dist/services/projectManager.d.ts +98 -2
- package/dist/services/projectManager.js +228 -59
- package/dist/services/projectManager.test.js +242 -2
- package/dist/services/sessionManager.d.ts +44 -2
- package/dist/services/sessionManager.effect.test.d.ts +1 -0
- package/dist/services/sessionManager.effect.test.js +321 -0
- package/dist/services/sessionManager.js +216 -65
- package/dist/services/sessionManager.statePersistence.test.js +18 -9
- package/dist/services/sessionManager.test.js +40 -36
- package/dist/services/worktreeService.d.ts +356 -26
- package/dist/services/worktreeService.js +793 -353
- package/dist/services/worktreeService.test.js +294 -313
- package/dist/types/errors.d.ts +74 -0
- package/dist/types/errors.js +31 -0
- package/dist/types/errors.test.d.ts +1 -0
- package/dist/types/errors.test.js +201 -0
- package/dist/types/index.d.ts +5 -17
- package/dist/utils/claudeDir.d.ts +58 -6
- package/dist/utils/claudeDir.js +103 -8
- package/dist/utils/claudeDir.test.d.ts +1 -0
- package/dist/utils/claudeDir.test.js +108 -0
- package/dist/utils/concurrencyLimit.d.ts +5 -0
- package/dist/utils/concurrencyLimit.js +11 -0
- package/dist/utils/concurrencyLimit.test.js +40 -1
- package/dist/utils/gitStatus.d.ts +36 -8
- package/dist/utils/gitStatus.js +170 -88
- package/dist/utils/gitStatus.test.js +12 -9
- package/dist/utils/hookExecutor.d.ts +41 -6
- package/dist/utils/hookExecutor.js +75 -32
- package/dist/utils/hookExecutor.test.js +73 -20
- package/dist/utils/terminalCapabilities.d.ts +18 -0
- package/dist/utils/terminalCapabilities.js +81 -0
- package/dist/utils/terminalCapabilities.test.d.ts +1 -0
- package/dist/utils/terminalCapabilities.test.js +104 -0
- package/dist/utils/testHelpers.d.ts +106 -0
- package/dist/utils/testHelpers.js +153 -0
- package/dist/utils/testHelpers.test.d.ts +1 -0
- package/dist/utils/testHelpers.test.js +114 -0
- package/dist/utils/worktreeConfig.d.ts +77 -2
- package/dist/utils/worktreeConfig.js +156 -16
- package/dist/utils/worktreeConfig.test.d.ts +1 -0
- package/dist/utils/worktreeConfig.test.js +39 -0
- package/package.json +4 -4
- package/dist/integration-tests/devcontainer.integration.test.js +0 -101
- /package/dist/{integration-tests/devcontainer.integration.test.d.ts → components/App.test.d.ts} +0 -0
|
@@ -7,6 +7,8 @@ import { configurationManager } from './configurationManager.js';
|
|
|
7
7
|
import { executeStatusHook } from '../utils/hookExecutor.js';
|
|
8
8
|
import { createStateDetector } from './stateDetector.js';
|
|
9
9
|
import { STATE_PERSISTENCE_DURATION_MS, STATE_CHECK_INTERVAL_MS, } from '../constants/statePersistence.js';
|
|
10
|
+
import { Effect } from 'effect';
|
|
11
|
+
import { ProcessError, ConfigError } from '../types/errors.js';
|
|
10
12
|
const { Terminal } = pkg;
|
|
11
13
|
const execAsync = promisify(exec);
|
|
12
14
|
export class SessionManager extends EventEmitter {
|
|
@@ -86,29 +88,78 @@ export class SessionManager extends EventEmitter {
|
|
|
86
88
|
this.emit('sessionCreated', session);
|
|
87
89
|
return session;
|
|
88
90
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Create session with command preset using Effect-based error handling
|
|
93
|
+
*
|
|
94
|
+
* @param {string} worktreePath - Path to the worktree
|
|
95
|
+
* @param {string} [presetId] - Optional preset ID, uses default if not provided
|
|
96
|
+
* @returns {Effect.Effect<Session, ProcessError | ConfigError, never>} Effect that may fail with ProcessError (spawn failure) or ConfigError (invalid preset)
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* // Use Effect.match for type-safe error handling
|
|
101
|
+
* const result = await Effect.runPromise(
|
|
102
|
+
* Effect.match(effect, {
|
|
103
|
+
* onFailure: (error) => ({ type: 'error', message: error.message }),
|
|
104
|
+
* onSuccess: (session) => ({ type: 'success', data: session })
|
|
105
|
+
* })
|
|
106
|
+
* );
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
createSessionWithPresetEffect(worktreePath, presetId) {
|
|
110
|
+
return Effect.tryPromise({
|
|
111
|
+
try: async () => {
|
|
112
|
+
// Check if session already exists
|
|
113
|
+
const existing = this.sessions.get(worktreePath);
|
|
114
|
+
if (existing) {
|
|
115
|
+
return existing;
|
|
116
|
+
}
|
|
117
|
+
// Get preset configuration
|
|
118
|
+
let preset = presetId
|
|
119
|
+
? configurationManager.getPresetById(presetId)
|
|
120
|
+
: null;
|
|
121
|
+
if (!preset) {
|
|
122
|
+
preset = configurationManager.getDefaultPreset();
|
|
123
|
+
}
|
|
124
|
+
// Validate preset exists
|
|
125
|
+
if (!preset) {
|
|
126
|
+
throw new ConfigError({
|
|
127
|
+
configPath: 'configuration',
|
|
128
|
+
reason: 'validation',
|
|
129
|
+
details: presetId
|
|
130
|
+
? `Preset with ID '${presetId}' not found and no default preset available`
|
|
131
|
+
: 'No default preset available',
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
const command = preset.command;
|
|
135
|
+
const args = preset.args || [];
|
|
136
|
+
const commandConfig = {
|
|
137
|
+
command: preset.command,
|
|
138
|
+
args: preset.args,
|
|
139
|
+
fallbackArgs: preset.fallbackArgs,
|
|
140
|
+
};
|
|
141
|
+
// Spawn the process - fallback will be handled by setupExitHandler
|
|
142
|
+
const ptyProcess = await this.spawn(command, args, worktreePath);
|
|
143
|
+
return this.createSessionInternal(worktreePath, ptyProcess, commandConfig, {
|
|
144
|
+
isPrimaryCommand: true,
|
|
145
|
+
detectionStrategy: preset.detectionStrategy,
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
catch: (error) => {
|
|
149
|
+
// If it's already a ConfigError, return it
|
|
150
|
+
if (error instanceof ConfigError) {
|
|
151
|
+
return error;
|
|
152
|
+
}
|
|
153
|
+
// Otherwise, wrap in ProcessError
|
|
154
|
+
return new ProcessError({
|
|
155
|
+
command: presetId
|
|
156
|
+
? `createSessionWithPreset (preset: ${presetId})`
|
|
157
|
+
: 'createSessionWithPreset (default preset)',
|
|
158
|
+
message: error instanceof Error
|
|
159
|
+
? error.message
|
|
160
|
+
: 'Failed to create session with preset',
|
|
161
|
+
});
|
|
162
|
+
},
|
|
112
163
|
});
|
|
113
164
|
}
|
|
114
165
|
setupDataHandler(session) {
|
|
@@ -213,8 +264,8 @@ export class SessionManager extends EventEmitter {
|
|
|
213
264
|
session.state = detectedState;
|
|
214
265
|
session.pendingState = undefined;
|
|
215
266
|
session.pendingStateStart = undefined;
|
|
216
|
-
// Execute status hook asynchronously (non-blocking)
|
|
217
|
-
void executeStatusHook(oldState, detectedState, session);
|
|
267
|
+
// Execute status hook asynchronously (non-blocking) using Effect
|
|
268
|
+
void Effect.runPromise(executeStatusHook(oldState, detectedState, session));
|
|
218
269
|
this.emit('sessionStateChanged', session);
|
|
219
270
|
}
|
|
220
271
|
}
|
|
@@ -280,49 +331,149 @@ export class SessionManager extends EventEmitter {
|
|
|
280
331
|
this.emit('sessionDestroyed', session);
|
|
281
332
|
}
|
|
282
333
|
}
|
|
334
|
+
/**
|
|
335
|
+
* Terminate session and cleanup resources using Effect-based error handling
|
|
336
|
+
*
|
|
337
|
+
* @param {string} worktreePath - Path to the worktree
|
|
338
|
+
* @returns {Effect.Effect<void, ProcessError, never>} Effect that may fail with ProcessError if session does not exist or cleanup fails
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* ```typescript
|
|
342
|
+
* // Terminate session with error handling
|
|
343
|
+
* const result = await Effect.runPromise(
|
|
344
|
+
* Effect.match(effect, {
|
|
345
|
+
* onFailure: (error) => ({ type: 'error', message: error.message }),
|
|
346
|
+
* onSuccess: () => ({ type: 'success' })
|
|
347
|
+
* })
|
|
348
|
+
* );
|
|
349
|
+
* ```
|
|
350
|
+
*/
|
|
351
|
+
terminateSessionEffect(worktreePath) {
|
|
352
|
+
return Effect.try({
|
|
353
|
+
try: () => {
|
|
354
|
+
const session = this.sessions.get(worktreePath);
|
|
355
|
+
if (!session) {
|
|
356
|
+
throw new ProcessError({
|
|
357
|
+
command: 'terminateSession',
|
|
358
|
+
message: `Session not found for worktree: ${worktreePath}`,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
// Clear the state check interval
|
|
362
|
+
if (session.stateCheckInterval) {
|
|
363
|
+
clearInterval(session.stateCheckInterval);
|
|
364
|
+
}
|
|
365
|
+
// Try to kill the process - don't fail if process is already dead
|
|
366
|
+
try {
|
|
367
|
+
session.process.kill();
|
|
368
|
+
}
|
|
369
|
+
catch (_error) {
|
|
370
|
+
// Process might already be dead, this is acceptable
|
|
371
|
+
}
|
|
372
|
+
// Clean up any pending timer
|
|
373
|
+
const timer = this.busyTimers.get(worktreePath);
|
|
374
|
+
if (timer) {
|
|
375
|
+
clearTimeout(timer);
|
|
376
|
+
this.busyTimers.delete(worktreePath);
|
|
377
|
+
}
|
|
378
|
+
// Remove from sessions map and cleanup
|
|
379
|
+
this.sessions.delete(worktreePath);
|
|
380
|
+
this.waitingWithBottomBorder.delete(session.id);
|
|
381
|
+
this.emit('sessionDestroyed', session);
|
|
382
|
+
},
|
|
383
|
+
catch: (error) => {
|
|
384
|
+
// If it's already a ProcessError, return it
|
|
385
|
+
if (error instanceof ProcessError) {
|
|
386
|
+
return error;
|
|
387
|
+
}
|
|
388
|
+
// Otherwise, wrap in ProcessError
|
|
389
|
+
return new ProcessError({
|
|
390
|
+
command: 'terminateSession',
|
|
391
|
+
message: error instanceof Error
|
|
392
|
+
? error.message
|
|
393
|
+
: `Failed to terminate session for ${worktreePath}`,
|
|
394
|
+
});
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
}
|
|
283
398
|
getAllSessions() {
|
|
284
399
|
return Array.from(this.sessions.values());
|
|
285
400
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
401
|
+
/**
|
|
402
|
+
* Create session with devcontainer integration using Effect-based error handling
|
|
403
|
+
* @returns Effect that may fail with ProcessError (container/spawn failure) or ConfigError (invalid preset)
|
|
404
|
+
*/
|
|
405
|
+
createSessionWithDevcontainerEffect(worktreePath, devcontainerConfig, presetId) {
|
|
406
|
+
return Effect.tryPromise({
|
|
407
|
+
try: async () => {
|
|
408
|
+
// Check if session already exists
|
|
409
|
+
const existing = this.sessions.get(worktreePath);
|
|
410
|
+
if (existing) {
|
|
411
|
+
return existing;
|
|
412
|
+
}
|
|
413
|
+
// Execute devcontainer up command first
|
|
414
|
+
try {
|
|
415
|
+
await execAsync(devcontainerConfig.upCommand, { cwd: worktreePath });
|
|
416
|
+
}
|
|
417
|
+
catch (error) {
|
|
418
|
+
throw new ProcessError({
|
|
419
|
+
command: devcontainerConfig.upCommand,
|
|
420
|
+
message: `Failed to start devcontainer: ${error instanceof Error ? error.message : String(error)}`,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
// Get preset configuration
|
|
424
|
+
let preset = presetId
|
|
425
|
+
? configurationManager.getPresetById(presetId)
|
|
426
|
+
: null;
|
|
427
|
+
if (!preset) {
|
|
428
|
+
preset = configurationManager.getDefaultPreset();
|
|
429
|
+
}
|
|
430
|
+
// Validate preset exists
|
|
431
|
+
if (!preset) {
|
|
432
|
+
throw new ConfigError({
|
|
433
|
+
configPath: 'configuration',
|
|
434
|
+
reason: 'validation',
|
|
435
|
+
details: presetId
|
|
436
|
+
? `Preset with ID '${presetId}' not found and no default preset available`
|
|
437
|
+
: 'No default preset available',
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
// Parse the exec command to extract arguments
|
|
441
|
+
const execParts = devcontainerConfig.execCommand.split(/\s+/);
|
|
442
|
+
const devcontainerCmd = execParts[0] || 'devcontainer';
|
|
443
|
+
const execArgs = execParts.slice(1);
|
|
444
|
+
// Build the full command: devcontainer exec [args] -- [preset command] [preset args]
|
|
445
|
+
const fullArgs = [
|
|
446
|
+
...execArgs,
|
|
447
|
+
'--',
|
|
448
|
+
preset.command,
|
|
449
|
+
...(preset.args || []),
|
|
450
|
+
];
|
|
451
|
+
// Spawn the process within devcontainer
|
|
452
|
+
const ptyProcess = await this.spawn(devcontainerCmd, fullArgs, worktreePath);
|
|
453
|
+
const commandConfig = {
|
|
454
|
+
command: preset.command,
|
|
455
|
+
args: preset.args,
|
|
456
|
+
fallbackArgs: preset.fallbackArgs,
|
|
457
|
+
};
|
|
458
|
+
return this.createSessionInternal(worktreePath, ptyProcess, commandConfig, {
|
|
459
|
+
isPrimaryCommand: true,
|
|
460
|
+
detectionStrategy: preset.detectionStrategy,
|
|
461
|
+
devcontainerConfig,
|
|
462
|
+
});
|
|
463
|
+
},
|
|
464
|
+
catch: (error) => {
|
|
465
|
+
// If it's already a ConfigError or ProcessError, return it
|
|
466
|
+
if (error instanceof ConfigError || error instanceof ProcessError) {
|
|
467
|
+
return error;
|
|
468
|
+
}
|
|
469
|
+
// Otherwise, wrap in ProcessError
|
|
470
|
+
return new ProcessError({
|
|
471
|
+
command: `createSessionWithDevcontainer (${devcontainerConfig.execCommand})`,
|
|
472
|
+
message: error instanceof Error
|
|
473
|
+
? error.message
|
|
474
|
+
: 'Failed to create session with devcontainer',
|
|
475
|
+
});
|
|
476
|
+
},
|
|
326
477
|
});
|
|
327
478
|
}
|
|
328
479
|
destroy() {
|
|
@@ -3,7 +3,9 @@ import { SessionManager } from './sessionManager.js';
|
|
|
3
3
|
import { spawn } from 'node-pty';
|
|
4
4
|
import { EventEmitter } from 'events';
|
|
5
5
|
import { STATE_PERSISTENCE_DURATION_MS, STATE_CHECK_INTERVAL_MS, } from '../constants/statePersistence.js';
|
|
6
|
-
vi.mock('node-pty')
|
|
6
|
+
vi.mock('node-pty', () => ({
|
|
7
|
+
spawn: vi.fn(),
|
|
8
|
+
}));
|
|
7
9
|
vi.mock('./configurationManager.js', () => ({
|
|
8
10
|
configurationManager: {
|
|
9
11
|
getConfig: vi.fn().mockReturnValue({
|
|
@@ -72,7 +74,8 @@ describe('SessionManager - State Persistence', () => {
|
|
|
72
74
|
vi.clearAllMocks();
|
|
73
75
|
});
|
|
74
76
|
it('should not change state immediately when detected state changes', async () => {
|
|
75
|
-
const
|
|
77
|
+
const { Effect } = await import('effect');
|
|
78
|
+
const session = await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/path'));
|
|
76
79
|
const eventEmitter = eventEmitters.get('/test/path');
|
|
77
80
|
// Initial state should be busy
|
|
78
81
|
expect(session.state).toBe('busy');
|
|
@@ -86,7 +89,8 @@ describe('SessionManager - State Persistence', () => {
|
|
|
86
89
|
expect(session.pendingStateStart).toBeDefined();
|
|
87
90
|
});
|
|
88
91
|
it('should change state after persistence duration is met', async () => {
|
|
89
|
-
const
|
|
92
|
+
const { Effect } = await import('effect');
|
|
93
|
+
const session = await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/path'));
|
|
90
94
|
const eventEmitter = eventEmitters.get('/test/path');
|
|
91
95
|
const stateChangeHandler = vi.fn();
|
|
92
96
|
sessionManager.on('sessionStateChanged', stateChangeHandler);
|
|
@@ -107,7 +111,8 @@ describe('SessionManager - State Persistence', () => {
|
|
|
107
111
|
expect(stateChangeHandler).toHaveBeenCalledWith(session);
|
|
108
112
|
});
|
|
109
113
|
it('should cancel pending state if detected state changes again before persistence', async () => {
|
|
110
|
-
const
|
|
114
|
+
const { Effect } = await import('effect');
|
|
115
|
+
const session = await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/path'));
|
|
111
116
|
const eventEmitter = eventEmitters.get('/test/path');
|
|
112
117
|
// Initial state should be busy
|
|
113
118
|
expect(session.state).toBe('busy');
|
|
@@ -125,7 +130,8 @@ describe('SessionManager - State Persistence', () => {
|
|
|
125
130
|
expect(session.pendingState).toBe('waiting_input');
|
|
126
131
|
});
|
|
127
132
|
it('should clear pending state if detected state returns to current state', async () => {
|
|
128
|
-
const
|
|
133
|
+
const { Effect } = await import('effect');
|
|
134
|
+
const session = await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/path'));
|
|
129
135
|
const eventEmitter = eventEmitters.get('/test/path');
|
|
130
136
|
// Initial state should be busy
|
|
131
137
|
expect(session.state).toBe('busy');
|
|
@@ -145,7 +151,8 @@ describe('SessionManager - State Persistence', () => {
|
|
|
145
151
|
expect(session.pendingStateStart).toBeUndefined();
|
|
146
152
|
});
|
|
147
153
|
it('should not confirm state changes that do not persist long enough', async () => {
|
|
148
|
-
const
|
|
154
|
+
const { Effect } = await import('effect');
|
|
155
|
+
const session = await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/path'));
|
|
149
156
|
const eventEmitter = eventEmitters.get('/test/path');
|
|
150
157
|
const stateChangeHandler = vi.fn();
|
|
151
158
|
sessionManager.on('sessionStateChanged', stateChangeHandler);
|
|
@@ -170,7 +177,8 @@ describe('SessionManager - State Persistence', () => {
|
|
|
170
177
|
expect(stateChangeHandler).not.toHaveBeenCalled();
|
|
171
178
|
});
|
|
172
179
|
it('should properly clean up pending state when session is destroyed', async () => {
|
|
173
|
-
const
|
|
180
|
+
const { Effect } = await import('effect');
|
|
181
|
+
const session = await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/path'));
|
|
174
182
|
const eventEmitter = eventEmitters.get('/test/path');
|
|
175
183
|
// Simulate output that would trigger idle state
|
|
176
184
|
eventEmitter.emit('data', 'Some output without busy indicators');
|
|
@@ -185,8 +193,9 @@ describe('SessionManager - State Persistence', () => {
|
|
|
185
193
|
expect(destroyedSession).toBeUndefined();
|
|
186
194
|
});
|
|
187
195
|
it('should handle multiple sessions with independent state persistence', async () => {
|
|
188
|
-
const
|
|
189
|
-
const
|
|
196
|
+
const { Effect } = await import('effect');
|
|
197
|
+
const session1 = await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/path1'));
|
|
198
|
+
const session2 = await Effect.runPromise(sessionManager.createSessionWithPresetEffect('/test/path2'));
|
|
190
199
|
const eventEmitter1 = eventEmitters.get('/test/path1');
|
|
191
200
|
const eventEmitter2 = eventEmitters.get('/test/path2');
|
|
192
201
|
// Both should start as busy
|