Skip to content

Commit 374e5f9

Browse files
committed
Add parallel session support for multi-agent workflows
- cs start no longer blocks when another session is active in a different directory - Same-directory sessions still blocked (use --resume or --close-stale) - All commands (end, log-ai, status, note, auto-log) now resolve the correct session based on cwd/git root, with fallback to most recent - Enables parallel sub-agents tracking costs independently per worktree
1 parent c771743 commit 374e5f9

1 file changed

Lines changed: 36 additions & 23 deletions

File tree

src/index.ts

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@ function jsonWrap(data: Record<string, any>): Record<string, any> {
6161
return { schemaVersion: SCHEMA_VERSION, codesessionVersion: VERSION, ...data };
6262
}
6363

64+
/** Resolve the active session for the current directory (supports parallel sessions). */
65+
async function resolveActiveSession() {
66+
const cwd = process.cwd();
67+
const gitRoot = await getGitRoot(cwd);
68+
const scopeDir = gitRoot || cwd;
69+
// Prefer session matching this directory/git root
70+
return getActiveSessionForDir(scopeDir) || getActiveSession();
71+
}
72+
6473
function sessionToJSON(session: any, extras?: { files?: any[]; commits?: any[]; aiUsage?: any[]; notes?: any[] }) {
6574
const obj: any = {
6675
schemaVersion: SCHEMA_VERSION,
@@ -181,22 +190,26 @@ program
181190
console.log(chalk.gray(` Closed ${allActive.length} stale session(s)`));
182191
}
183192
} else if (!options.resume) {
184-
// Default: error about active session
185-
const active = allActive[0];
186-
if (options.json) {
187-
jsonError('session_active', `Session "${active.name}" is already active`, {
188-
activeSession: active.name,
189-
id: active.id,
190-
hint: 'Use --resume to reattach or --close-stale to auto-close',
191-
});
192-
} else {
193-
console.log(chalk.yellow(`\nSession "${active.name}" is already active (id: ${active.id}).`));
194-
console.log(chalk.gray(' Options:'));
195-
console.log(chalk.gray(' cs end — end it manually'));
196-
console.log(chalk.gray(' cs start --resume — reuse session for this directory'));
197-
console.log(chalk.gray(' cs start --close-stale — auto-close stale sessions\n'));
193+
// Only block if there's already an active session for THIS directory
194+
// Different directories/git roots can run parallel sessions
195+
const sameDir = getActiveSessionForDir(scopeDir);
196+
if (sameDir) {
197+
if (options.json) {
198+
jsonError('session_active', `Session "${sameDir.name}" is already active for this directory`, {
199+
activeSession: sameDir.name,
200+
id: sameDir.id,
201+
hint: 'Use --resume to reattach or --close-stale to auto-close',
202+
});
203+
} else {
204+
console.log(chalk.yellow(`\nSession "${sameDir.name}" is already active for this directory (id: ${sameDir.id}).`));
205+
console.log(chalk.gray(' Options:'));
206+
console.log(chalk.gray(' cs end — end it manually'));
207+
console.log(chalk.gray(' cs start --resume — reuse session for this directory'));
208+
console.log(chalk.gray(' cs start --close-stale — auto-close stale sessions\n'));
209+
}
210+
return;
198211
}
199-
return;
212+
// Different directory — allow parallel session
200213
}
201214
}
202215

@@ -267,7 +280,7 @@ program
267280
return;
268281
}
269282
} else {
270-
session = getActiveSession();
283+
session = await resolveActiveSession();
271284
}
272285

273286
if (!session) {
@@ -434,7 +447,7 @@ program
434447
.option('--agent <name>', 'Agent name (optional)')
435448
.option('-s, --session <id>', 'Target a specific session by ID', parseInt)
436449
.option('--json', 'Output JSON (for agents)')
437-
.action((options) => {
450+
.action(async (options) => {
438451
let session;
439452
if (options.session) {
440453
session = getSession(options.session);
@@ -447,7 +460,7 @@ program
447460
return;
448461
}
449462
} else {
450-
session = getActiveSession();
463+
session = await resolveActiveSession();
451464
}
452465
if (!session) {
453466
if (options.json) {
@@ -536,12 +549,12 @@ program
536549
.description('Show active session status')
537550
.option('-s, --session <id>', 'Show a specific session by ID', parseInt)
538551
.option('--json', 'Output JSON (for agents)')
539-
.action((options) => {
552+
.action(async (options) => {
540553
let session;
541554
if (options.session) {
542555
session = getSession(options.session);
543556
} else {
544-
session = getActiveSession();
557+
session = await resolveActiveSession();
545558
}
546559
if (!session) {
547560
if (options.json) {
@@ -640,7 +653,7 @@ program
640653
.argument('<message>', 'Note message')
641654
.option('-s, --session <id>', 'Target a specific session by ID', parseInt)
642655
.option('--json', 'Output JSON (for agents)')
643-
.action((message: string, options) => {
656+
.action(async (message: string, options) => {
644657
let session;
645658
if (options.session) {
646659
session = getSession(options.session);
@@ -653,7 +666,7 @@ program
653666
return;
654667
}
655668
} else {
656-
session = getActiveSession();
669+
session = await resolveActiveSession();
657670
}
658671
if (!session) {
659672
if (options.json) {
@@ -757,7 +770,7 @@ program
757770

758771
// Must have an active codesession — if not, exit WITHOUT saving position
759772
// so tokens aren't lost (they'll be picked up on the next call after cs start)
760-
const session = getActiveSession();
773+
const session = await resolveActiveSession();
761774
if (!session) process.exit(0);
762775

763776
// Track position so we don't double-count across multiple Stop events

0 commit comments

Comments
 (0)