diff --git a/packages/cli/src/commands/push.ts b/packages/cli/src/commands/push.ts index ed374be..6c6ae6c 100644 --- a/packages/cli/src/commands/push.ts +++ b/packages/cli/src/commands/push.ts @@ -40,10 +40,16 @@ function toTuiSessions(sessions: LocalSession[]): TuiSession[] { })); } -function findSession(sessions: LocalSession[], ref: string) { - const index = Number(ref); - if (Number.isInteger(index) && index >= 1 && index <= sessions.length) return sessions[index - 1]; - return sessions.find((s) => s.key === ref || s.id === ref || s.id.startsWith(ref)) ?? null; +export function findSession(sessions: LocalSession[], ref: string) { + const wanted = ref.trim(); + if (!wanted) return null; + const exact = sessions.filter((s) => s.key === wanted || s.id === wanted); + if (exact.length > 1) throw new Error(`Session "${ref}" is ambiguous; use a full harness:id key.`); + if (exact.length === 1) return exact[0]; + if (/^[1-9]\d*$/.test(wanted)) return sessions[Number(wanted) - 1] ?? null; + const matches = sessions.filter((s) => s.id.startsWith(wanted)); + if (matches.length > 1) throw new Error(`Session "${ref}" is ambiguous; use a full harness:id key.`); + return matches[0] ?? null; } /** diff --git a/packages/cli/test/session-selection.test.ts b/packages/cli/test/session-selection.test.ts new file mode 100644 index 0000000..62c8e9c --- /dev/null +++ b/packages/cli/test/session-selection.test.ts @@ -0,0 +1,22 @@ +import assert from 'node:assert/strict'; +import { test } from 'node:test'; +import { findSession } from '../src/commands/push'; +import type { LocalSession } from '../src/harnesses'; + +const session = (id: string, harness = 'claude') => ({id, harness, key: `${harness}:${id}`} as LocalSession); +test('prefixes must identify exactly one session and exact IDs take precedence', () => { + const sessions = [session('abc-1'), session('abc-2'), session('abc')]; + assert.throws(() => findSession(sessions, 'ab'), /ambiguous/); + assert.equal(findSession(sessions, 'abc'), sessions[2]); + assert.equal(findSession(sessions, ' claude:abc-1 '), sessions[0]); + assert.equal(findSession(sessions, ''), null); + assert.equal(findSession(sessions, 'missing'), null); + assert.equal(findSession(sessions, '2'), sessions[1]); + assert.equal(findSession(sessions, '999'), null); + assert.equal(findSession(sessions, '1e0'), null); + const sameId = [session('same'), session('same', 'pi')]; + assert.throws(() => findSession(sameId, 'same'), /ambiguous/); + assert.equal(findSession(sameId, 'pi:same'), sameId[1]); + const numeric = [session('other'), session('1')]; + assert.equal(findSession(numeric, '1'), numeric[1]); +});