/** Session creation, blank-Session reuse, and Agent preset identity through `session.create`. */ import { mkdtempSync, realpathSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { Context } from '@deepseek-ai/cordis' import AgentRegistry from '@deepseek-ai/dsh-agent' import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' import type { Agent, AgentFactory } from '@deepseek-ai/dsh-agent' import { agentPresetProjectionDefinition } from '@deepseek-ai/dsh-agent-preset-registry' import SessionStore, { SESSION_FORMAT_VERSION, SessionId, SessionSeq } from '@deepseek-ai/dsh-session' import type { Session, SessionEvent, SessionHeader } from '@deepseek-ai/dsh-session' import { RemoteError } from '@deepseek-ai/dsh-typert-protocol' import type { WorkspaceId } from '@deepseek-ai/dsh-workspace' import { afterEach, describe, expect, it, vi } from 'vitest' import { createSessionTestRemote, testSessionPersistence } from './test-remote.ts' /** Booted contexts and their temp roots, torn down after each test. */ const contexts: Context[] = [] const tempDirs: string[] = [] afterEach(async () => { await Promise.all(contexts.splice(0).map(ctx => ctx.fiber.dispose())) for (const dir of tempDirs.splice(0)) rmSync(dir, { recursive: true, force: true }) }) function stubAgent(session: Session): Agent { return { id: session.id, session, status: 'idle' } as unknown as Agent } /** A stored Session the persistence double serves until an Agent resumes it. */ interface StoredSession { readonly header: SessionHeader readonly events: readonly SessionEvent[] } interface HarnessOptions { /** Declared presets; the first is the default. Omitted, no roster is composed. */ readonly presets?: readonly string[] /** Stored Sessions keyed by id. */ readonly stored?: ReadonlyMap /** Stored Sessions whose writer another owner holds. */ readonly held?: ReadonlySet /** Stored Sessions another resume in this process publishes while this one waits for the writer. */ readonly publishedByOtherResume?: ReadonlySet /** Stored Sessions whose storage fails to open for writing. */ readonly unreadable?: ReadonlySet /** Events a resume finds beyond the stored log, as when another Host wrote after the observation. */ readonly writtenSinceObservation?: ReadonlyMap /** Live Sessions whose preset switch the roster refuses as already started. */ readonly locked?: ReadonlySet /** Declared presets whose activation failed. */ readonly broken?: ReadonlySet /** Requested presets whose resolution waits for the paired promise. */ readonly resolving?: ReadonlyMap> /** Workspace placement served to `workspaceId` requests. */ readonly workspace?: { readonly members: readonly SessionId[]; readonly archived: readonly SessionId[] } } const WORKSPACE = 'workspace-1' as WorkspaceId async function harness(options: HarnessOptions = {}) { const cwd = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-session-preset-'))) tempDirs.push(cwd) const ctx = new Context() contexts.push(ctx) await ctx.plugin(SessionStore) await ctx.plugin(SessionProjectionRegistry) await ctx.plugin(AgentRegistry) const preset = await ctx.sessionProjections.register(agentPresetProjectionDefinition) const presets = options.presets const selections: { sessionId: SessionId; agentPreset: string }[] = [] const resumed: SessionId[] = [] if (presets !== undefined) { const known = (id?: string): string => { const wanted = id ?? presets[0] ?? '' if (!presets.includes(wanted)) { throw new RemoteError('agent-preset/not-found', `Unknown agent preset: ${wanted}`, { agentPreset: wanted, available: [...presets], }) } return wanted } const usable = (id?: string): string => { const wanted = known(id) if (options.broken?.has(wanted) === true) { throw new RemoteError('agent-preset/invalid', 'activation failed', { agentPreset: wanted, reason: 'activation failed' }) } return wanted } ctx.provide('agentPresets', { defaultId: presets[0], presetForSession: (session: Session) => preset.stateOf(session), resolve: (id?: string) => Promise.resolve(id === undefined ? undefined : options.resolving?.get(id)) .then(() => ({ id: known(id) })), mount: (_ctx: Context, id?: string) => Promise.resolve().then(() => ({ id: usable(id) })), select: async (agent: Agent, agentPreset: string) => { usable(agentPreset) if (options.locked?.has(agent.id) === true) { throw new RemoteError('agent-preset/locked', 'This session has already started', { sessionId: agent.id, agentPreset, }) } selections.push({ sessionId: agent.id, agentPreset }) agent.session.append('agent-preset/selected', { agentPreset }) return agentPreset }, } as never) } const stored = options.stored ?? new Map() ctx.provide('sessionPersistence', testSessionPersistence(ctx, { list: () => Promise.resolve([...stored.values()].map(record => record.header)), inspect: (id: SessionId) => { const record = stored.get(id) return Promise.resolve(record === undefined ? undefined : { meta: record.header, events: [...record.events] }) }, }) as never) const { members, archived } = options.workspace ?? { members: [], archived: [] } const workspace = { id: WORKSPACE, path: cwd, sessionIds: members, attachSession: () => Promise.resolve() } ctx.provide('workspaceRegistry', { get: (id: WorkspaceId) => id === WORKSPACE ? workspace : undefined, list: () => [workspace], archivedSessionIds: archived, } as never) /** Enter the Session for setup, then announce creation as the Agent factory does. */ const publish = async ( session: Session, setup: Parameters[1]['setup'], ): Promise<{ agent: Agent; dispose: () => Promise }> => { const agent = stubAgent(session) ;(agent as { ctx?: Context }).ctx = ctx const detach = ctx.effect(() => ctx.sessions.enter(session)) try { ctx.sessions.announce(session) const commit = await setup?.(ctx, agent) commit?.commit() const unregister = await ctx.agents.register(agent) return { agent, dispose: async () => { await unregister(); await detach() } } } catch (error) { await detach() throw error } } const factory: AgentFactory = { async createAgent(_ownerCtx, create) { const session = ctx.sessions.prepare(create.sessionId, create.meta === undefined ? {} : { meta: create.meta }) return publish(session, create.setup) }, async resume(_ownerCtx, resume) { const id = resume.resumeSessionId if (options.held?.has(id) === true) { throw Object.assign(new Error(`session "${id}" is written by another owner`), { name: 'SessionAlreadyOwnedError' }) } const record = stored.get(id) if (record === undefined || options.unreadable?.has(id) === true) { throw new Error(`test persistence cannot open Session "${id}" for writing`) } if (options.publishedByOtherResume?.has(id) === true) { await publish(ctx.sessions.prepare(id, { meta: record.header, seed: [...record.events] }), undefined) throw Object.assign(new Error(`session "${id}" is written by this process`), { name: 'SessionAlreadyOwnedError' }) } resumed.push(id) const session = ctx.sessions.prepare(id, { meta: record.header, seed: [...record.events, ...options.writtenSinceObservation?.get(id) ?? []], }) return publish(session, resume.setup) }, } ctx.agents.setFactory(factory) const remote = await createSessionTestRemote(ctx, { defaultModelSelection: () => ({ provider: 'test', model: 'test-model' }), cwd, }) const presetOf = (id: SessionId): string | null | undefined => { const session = ctx.sessions.get(id) return session === undefined ? undefined : preset.stateOf(session) } const selectedEvents = (id: SessionId): unknown[] => { const session = ctx.sessions.get(id) return session === undefined ? [] : session.snapshotEvents() .filter(event => event.type === 'agent-preset/selected') .map(event => event.data) } return { ctx, remote, cwd, selections, resumed, presetOf, selectedEvents } } function storedHeader(id: SessionId, cwd: string, extra: Partial = {}): SessionHeader { return { version: SESSION_FORMAT_VERSION, id, createdAt: 1, isSeeded: false, cwd, ...extra } } function modelSelection(seq: number): SessionEvent { return { type: 'model/selection', seq: SessionSeq(seq), time: 1, data: { provider: 'test', model: 'test-model' } } } function presetSelection(seq: number, agentPreset: string): SessionEvent { return { type: 'agent-preset/selected', seq: SessionSeq(seq), time: 1, data: { agentPreset } } } function completedTurn(seq: number): SessionEvent[] { return [ { type: 'turn/start', seq: SessionSeq(seq), time: 1, data: { turn: 1 } }, { type: 'turn/end', seq: SessionSeq(seq + 1), time: 2, data: { turn: 1, reason: { kind: 'completed' } } }, ] } describe('session.create Agent preset identity', () => { it('records the requested preset on the Session header', async () => { const { ctx, remote } = await harness({ presets: ['standard', 'minimal'] }) const created = await remote.create({ sessionId: SessionId('s1'), agentPreset: 'minimal' }) expect(created.ok).toBe(true) expect(ctx.sessions.get(SessionId('s1'))?.header.agentPreset).toBe('minimal') }) it('records the roster default when the caller names no preset', async () => { const { ctx, remote } = await harness({ presets: ['standard', 'minimal'] }) await remote.create({ sessionId: SessionId('s2') }) expect(ctx.sessions.get(SessionId('s2'))?.header.agentPreset).toBe('standard') }) it('rejects an unknown preset', async () => { const { remote } = await harness({ presets: ['standard'] }) const response = await remote.create({ sessionId: SessionId('s3'), agentPreset: 'nope' }) expect(response).toMatchObject({ ok: false, error: { code: 'agent-preset/not-found' } }) }) it('leaves the header preset-less when no roster is composed', async () => { const { ctx, remote } = await harness() await remote.create({ sessionId: SessionId('s6') }) expect(ctx.sessions.get(SessionId('s6'))?.header.agentPreset).toBeUndefined() }) }) describe('session.create reuse of a live blank Session', () => { it('recomposes a blank Session under the default when the caller names no preset', async () => { const { remote, selections, presetOf } = await harness({ presets: ['standard', 'minimal'] }) await remote.create({ sessionId: SessionId('draft'), agentPreset: 'minimal' }) const reused = await remote.create({ sessionId: SessionId('draft') }) expect(reused).toMatchObject({ ok: true, value: { sessionId: 'draft', agentPreset: 'standard' } }) expect(selections).toEqual([{ sessionId: 'draft', agentPreset: 'standard' }]) expect(presetOf(SessionId('draft'))).toBe('standard') }) it('recomposes a blank Session under the named preset', async () => { const { remote, selections } = await harness({ presets: ['standard', 'minimal'] }) await remote.create({ sessionId: SessionId('draft') }) const reused = await remote.create({ sessionId: SessionId('draft'), agentPreset: 'minimal' }) expect(reused).toMatchObject({ ok: true, value: { sessionId: 'draft', agentPreset: 'minimal' } }) expect(selections).toEqual([{ sessionId: 'draft', agentPreset: 'minimal' }]) }) it('keeps a blank Session that already runs the requested preset', async () => { const { ctx, remote, selections } = await harness({ presets: ['standard', 'minimal'] }) await remote.create({ sessionId: SessionId('draft') }) ctx.sessions.get(SessionId('draft'))?.append('agent-preset/selected', { agentPreset: 'minimal' }) const reused = await remote.create({ sessionId: SessionId('draft'), agentPreset: 'minimal' }) expect(reused).toMatchObject({ ok: true, value: { sessionId: 'draft', agentPreset: 'minimal' } }) expect(selections).toEqual([]) }) it('validates the requested preset before touching the blank Session', async () => { const { remote, selections, presetOf } = await harness({ presets: ['standard', 'minimal'] }) await remote.create({ sessionId: SessionId('draft'), agentPreset: 'minimal' }) const response = await remote.create({ sessionId: SessionId('draft'), agentPreset: 'deleted' }) expect(response).toMatchObject({ ok: false, error: { code: 'agent-preset/not-found' } }) expect(selections).toEqual([]) expect(presetOf(SessionId('draft'))).toBe('minimal') }) it('keeps the blank Session unchanged when the requested preset fails to activate', async () => { const { remote, selections, presetOf } = await harness({ presets: ['standard', 'broken'], broken: new Set(['broken']), }) await remote.create({ sessionId: SessionId('draft') }) const response = await remote.create({ sessionId: SessionId('draft'), agentPreset: 'broken' }) expect(response).toMatchObject({ ok: false, error: { code: 'agent-preset/invalid' } }) expect(selections).toEqual([]) expect(presetOf(SessionId('draft'))).toBe('standard') }) it('creates a new Session instead of reusing one that has started', async () => { const { ctx, remote, selections, presetOf } = await harness({ presets: ['standard', 'minimal'] }) await remote.create({ sessionId: SessionId('talk'), agentPreset: 'minimal' }) const talk = ctx.sessions.get(SessionId('talk')) talk?.append('turn/start', { turn: 1 }) talk?.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) const created = await remote.create({ sessionId: SessionId('talk') }) if (!created.ok) throw new Error(`create failed: ${created.error.message}`) expect(created.value.sessionId).not.toBe('talk') expect(created.value.agentPreset).toBe('standard') expect(selections).toEqual([]) expect(presetOf(SessionId('talk'))).toBe('minimal') }) it('creates a new Session when a turn starts before the preset switch', async () => { const { remote } = await harness({ presets: ['standard', 'minimal'], locked: new Set([SessionId('racing')]), }) await remote.create({ sessionId: SessionId('racing'), agentPreset: 'minimal' }) const created = await remote.create({ sessionId: SessionId('racing') }) if (!created.ok) throw new Error(`create failed: ${created.error.message}`) expect(created.value.sessionId).not.toBe('racing') }) it('serializes concurrent reuse of one Session in request order', async () => { const { remote, selections, presetOf } = await harness({ presets: ['standard', 'minimal'] }) await remote.create({ sessionId: SessionId('draft') }) const [first, second] = await Promise.all([ remote.create({ sessionId: SessionId('draft'), agentPreset: 'minimal' }), remote.create({ sessionId: SessionId('draft') }), ]) expect(first).toMatchObject({ ok: true, value: { sessionId: 'draft' } }) expect(second).toMatchObject({ ok: true, value: { sessionId: 'draft', agentPreset: 'standard' } }) expect(selections).toEqual([ { sessionId: 'draft', agentPreset: 'minimal' }, { sessionId: 'draft', agentPreset: 'standard' }, ]) expect(presetOf(SessionId('draft'))).toBe('standard') }) it('claims one Session in arrival order when an earlier preset resolves later', async () => { const minimal = Promise.withResolvers() const { remote, selections, presetOf } = await harness({ presets: ['standard', 'minimal'], resolving: new Map([['minimal', minimal.promise]]), }) await remote.create({ sessionId: SessionId('draft') }) const first = remote.create({ sessionId: SessionId('draft'), agentPreset: 'minimal' }) const second = remote.create({ sessionId: SessionId('draft') }) await new Promise(resolve => setTimeout(resolve, 0)) minimal.resolve(undefined) expect(await first).toMatchObject({ ok: true, value: { sessionId: 'draft', agentPreset: 'minimal' } }) expect(await second).toMatchObject({ ok: true, value: { sessionId: 'draft', agentPreset: 'standard' } }) expect(selections).toEqual([ { sessionId: 'draft', agentPreset: 'minimal' }, { sessionId: 'draft', agentPreset: 'standard' }, ]) expect(presetOf(SessionId('draft'))).toBe('standard') }) it('shares one creation between concurrent callers of a new identity', async () => { const { ctx, remote } = await harness({ presets: ['standard'] }) const results = await Promise.all([ remote.create({ sessionId: SessionId('fresh') }), remote.create({ sessionId: SessionId('fresh') }), ]) expect(results).toMatchObject([ { ok: true, value: { sessionId: 'fresh' } }, { ok: true, value: { sessionId: 'fresh' } }, ]) expect(ctx.agents.list().map(agent => agent.id)).toEqual(['fresh']) }) it('refuses a Session in another directory', async () => { const { ctx, remote } = await harness({ presets: ['standard'] }) const elsewhere = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-session-preset-elsewhere-'))) tempDirs.push(elsewhere) await remote.create({ sessionId: SessionId('other'), cwd: elsewhere }) const response = await remote.create({ sessionId: SessionId('other') }) expect(response).toMatchObject({ ok: false, error: { code: 'session/conflict', details: { sessionId: 'other', existingCwd: elsewhere } }, }) expect(ctx.agents.list().map(agent => agent.id)).toEqual(['other']) }) it('refuses a Session in another directory before resolving the requested preset', async () => { const { remote } = await harness({ presets: ['standard'] }) const elsewhere = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-session-preset-elsewhere-'))) tempDirs.push(elsewhere) await remote.create({ sessionId: SessionId('other'), cwd: elsewhere }) const response = await remote.create({ sessionId: SessionId('other'), agentPreset: 'nope' }) expect(response).toMatchObject({ ok: false, error: { code: 'session/conflict', details: { sessionId: 'other' } } }) }) it('leaves a subagent identity to subagent routing', async () => { const { ctx, remote, cwd } = await harness({ presets: ['standard'] }) ctx.sessions.create(SessionId('child'), { meta: { cwd, parentSession: SessionId('parent'), origin: 'subagent' }, }) const response = await remote.create({ sessionId: SessionId('child') }) expect(response).toMatchObject({ ok: false, error: { code: 'session/agent-busy' } }) }) it('reuses a preset-less blank Session unchanged when no roster is composed', async () => { const { ctx, remote } = await harness() await remote.create({ sessionId: SessionId('s5') }) const reused = await remote.create({ sessionId: SessionId('s5') }) expect(reused).toMatchObject({ ok: true, value: { sessionId: 's5' } }) expect(ctx.sessions.get(SessionId('s5'))?.header.agentPreset).toBeUndefined() }) it('explains why a preset-less Session cannot be adopted under one', async () => { const { remote } = await harness() await remote.create({ sessionId: SessionId('s7') }) const response = await remote.create({ sessionId: SessionId('s7'), agentPreset: 'standard' }) expect(response).toMatchObject({ ok: false, error: { code: 'agent-preset/conflict', details: { sessionId: 's7', requestedPreset: 'standard', }, }, }) if (response.ok) throw new Error('unreachable') expect('existingPreset' in response.error.details).toBe(false) expect(response.error.message).toBe('the deployment composes no agent preset roster, so session "s7" cannot run preset "standard"') }) }) describe('session.create reuse of a stored blank Session', () => { it('resumes a blank Session whose recorded preset is no longer declared under the default', async () => { const draft = SessionId('stale-draft') const cwd = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-session-preset-stored-'))) tempDirs.push(cwd) const { remote, resumed, presetOf, selectedEvents } = await harness({ presets: ['standard', 'minimal'], stored: new Map([[draft, { header: storedHeader(draft, cwd, { agentPreset: 'liangshen' }), events: [modelSelection(0)] }]]), }) const reused = await remote.create({ sessionId: draft, cwd }) expect(reused).toMatchObject({ ok: true, value: { sessionId: 'stale-draft', agentPreset: 'standard' } }) expect(resumed).toEqual(['stale-draft']) expect(presetOf(draft)).toBe('standard') expect(selectedEvents(draft)).toEqual([{ agentPreset: 'standard' }]) }) it('resumes a blank Session under the default even when its previous choice is still declared', async () => { const draft = SessionId('picked-draft') const cwd = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-session-preset-stored-'))) tempDirs.push(cwd) const { remote, presetOf, selectedEvents } = await harness({ presets: ['standard', 'minimal'], stored: new Map([[draft, { header: storedHeader(draft, cwd, { agentPreset: 'standard' }), events: [presetSelection(0, 'minimal')] }]]), }) const reused = await remote.create({ sessionId: draft, cwd }) expect(reused).toMatchObject({ ok: true, value: { sessionId: 'picked-draft', agentPreset: 'standard' } }) expect(presetOf(draft)).toBe('standard') expect(selectedEvents(draft)).toEqual([{ agentPreset: 'minimal' }, { agentPreset: 'standard' }]) }) it('resumes a blank Session under its recorded preset when the caller names it', async () => { const draft = SessionId('restored-draft') const cwd = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-session-preset-stored-'))) tempDirs.push(cwd) const { remote, presetOf, selectedEvents } = await harness({ presets: ['standard', 'minimal'], stored: new Map([[draft, { header: storedHeader(draft, cwd, { agentPreset: 'standard' }), events: [presetSelection(0, 'minimal')] }]]), }) const reused = await remote.create({ sessionId: draft, cwd, agentPreset: 'minimal' }) expect(reused).toMatchObject({ ok: true, value: { sessionId: 'restored-draft', agentPreset: 'minimal' } }) expect(presetOf(draft)).toBe('minimal') expect(selectedEvents(draft)).toEqual([{ agentPreset: 'minimal' }]) }) it('rejects a named preset that is no longer declared without resuming the Session', async () => { const draft = SessionId('stale-draft') const cwd = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-session-preset-stored-'))) tempDirs.push(cwd) const { ctx, remote, resumed } = await harness({ presets: ['standard'], stored: new Map([[draft, { header: storedHeader(draft, cwd, { agentPreset: 'liangshen' }), events: [modelSelection(0)] }]]), }) const response = await remote.create({ sessionId: draft, cwd, agentPreset: 'liangshen' }) expect(response).toMatchObject({ ok: false, error: { code: 'agent-preset/not-found', details: { agentPreset: 'liangshen', available: ['standard'] } }, }) expect(resumed).toEqual([]) expect(ctx.agents.list()).toEqual([]) }) it('creates a new Session instead of resuming one that has started', async () => { const talk = SessionId('stored-talk') const cwd = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-session-preset-stored-'))) tempDirs.push(cwd) const { remote, resumed } = await harness({ presets: ['standard'], stored: new Map([[talk, { header: storedHeader(talk, cwd, { agentPreset: 'liangshen' }), events: completedTurn(0) }]]), }) const created = await remote.create({ sessionId: talk, cwd }) if (!created.ok) throw new Error(`create failed: ${created.error.message}`) expect(created.value.sessionId).not.toBe('stored-talk') expect(created.value.agentPreset).toBe('standard') expect(resumed).toEqual([]) }) it('creates a new Session when another owner holds the blank Session', async () => { const draft = SessionId('held-draft') const cwd = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-session-preset-stored-'))) tempDirs.push(cwd) const { ctx, remote } = await harness({ presets: ['standard'], stored: new Map([[draft, { header: storedHeader(draft, cwd, { agentPreset: 'standard' }), events: [modelSelection(0)] }]]), held: new Set([draft]), }) const created = await remote.create({ sessionId: draft, cwd }) if (!created.ok) throw new Error(`create failed: ${created.error.message}`) expect(created.value.sessionId).not.toBe('held-draft') expect(ctx.sessions.get(draft)).toBeUndefined() }) it('reuses the Agent another resume published while holding the writer', async () => { const draft = SessionId('contended-draft') const cwd = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-session-preset-stored-'))) tempDirs.push(cwd) const { remote, selections, presetOf } = await harness({ presets: ['standard', 'minimal'], stored: new Map([[draft, { header: storedHeader(draft, cwd, { agentPreset: 'minimal' }), events: [modelSelection(0)] }]]), publishedByOtherResume: new Set([draft]), }) const reused = await remote.create({ sessionId: draft, cwd }) expect(reused).toMatchObject({ ok: true, value: { sessionId: 'contended-draft', agentPreset: 'standard' } }) expect(selections).toEqual([{ sessionId: 'contended-draft', agentPreset: 'standard' }]) expect(presetOf(draft)).toBe('standard') }) it('reports an unreadable Session instead of creating another one', async () => { const { ctx, remote } = await harness({ presets: ['standard'] }) vi.spyOn(ctx.sessionQuery, 'observeSession').mockRejectedValue(new Error('storage offline')) const response = await remote.create({ sessionId: SessionId('unreadable') }) expect(response).toMatchObject({ ok: false, error: { code: 'gateway/internal' } }) expect(ctx.agents.list()).toEqual([]) }) it('reports a storage failure instead of creating another Session', async () => { const draft = SessionId('unreadable-draft') const cwd = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-session-preset-stored-'))) tempDirs.push(cwd) const { ctx, remote } = await harness({ presets: ['standard'], stored: new Map([[draft, { header: storedHeader(draft, cwd, { agentPreset: 'standard' }), events: [modelSelection(0)] }]]), unreadable: new Set([draft]), }) const response = await remote.create({ sessionId: draft, cwd }) expect(response).toMatchObject({ ok: false, error: { code: 'gateway/internal' } }) expect(ctx.agents.list()).toEqual([]) }) it('creates a new Session when the blank Session started after it was observed', async () => { const draft = SessionId('overtaken-draft') const cwd = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-session-preset-stored-'))) tempDirs.push(cwd) const { ctx, remote, resumed } = await harness({ presets: ['standard', 'minimal'], stored: new Map([[draft, { header: storedHeader(draft, cwd, { agentPreset: 'minimal' }), events: [modelSelection(0)] }]]), writtenSinceObservation: new Map([[draft, completedTurn(1)]]), }) const created = await remote.create({ sessionId: draft, cwd }) if (!created.ok) throw new Error(`create failed: ${created.error.message}`) expect(created.value.sessionId).not.toBe('overtaken-draft') expect(resumed).toEqual(['overtaken-draft']) expect(ctx.sessions.get(draft)).toBeUndefined() }) it('refuses a stored Session in another directory', async () => { const other = SessionId('stored-other') const cwd = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-session-preset-stored-'))) tempDirs.push(cwd) const { remote, resumed } = await harness({ presets: ['standard'], stored: new Map([[other, { header: storedHeader(other, '/elsewhere'), events: [modelSelection(0)] }]]), }) const response = await remote.create({ sessionId: other, cwd }) expect(response).toMatchObject({ ok: false, error: { code: 'session/conflict', details: { sessionId: 'stored-other', existingCwd: '/elsewhere' } }, }) expect(resumed).toEqual([]) }) it('leaves a stored subagent identity to subagent routing', async () => { const child = SessionId('stored-child') const cwd = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-session-preset-stored-'))) tempDirs.push(cwd) const { remote, resumed } = await harness({ presets: ['standard'], stored: new Map([[child, { header: storedHeader(child, cwd, { parentSession: SessionId('parent'), origin: 'subagent' }), events: [modelSelection(0)], }]]), }) const response = await remote.create({ sessionId: child, cwd }) expect(response).toMatchObject({ ok: false, error: { code: 'session/agent-busy' } }) expect(resumed).toEqual([]) }) }) describe('session.create Workspace placement', () => { it('reuses a blank member of the requested Workspace', async () => { const draft = SessionId('member-draft') const { remote } = await harness({ presets: ['standard'], workspace: { members: [draft], archived: [] } }) await remote.create({ sessionId: draft }) const reused = await remote.create({ sessionId: draft, workspaceId: WORKSPACE }) expect(reused).toMatchObject({ ok: true, value: { sessionId: 'member-draft' } }) }) it.each([ { name: 'outside the Workspace', members: [], archived: [] }, { name: 'archived', members: [SessionId('placed-draft')], archived: [SessionId('placed-draft')] }, ])('creates a new Session instead of reusing a blank Session $name', async ({ members, archived }) => { const draft = SessionId('placed-draft') const { remote, selections, presetOf } = await harness({ presets: ['standard', 'minimal'], workspace: { members, archived } }) await remote.create({ sessionId: draft, agentPreset: 'minimal' }) const created = await remote.create({ sessionId: draft, workspaceId: WORKSPACE }) if (!created.ok) throw new Error(`create failed: ${created.error.message}`) expect(created.value.sessionId).not.toBe('placed-draft') expect(selections).toEqual([]) expect(presetOf(draft)).toBe('minimal') }) })