import { existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { basename, join } from 'node:path' import { afterEach, describe, expect, it, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' import SessionStore, { SessionId } from '@deepseek-ai/dsh-session' import Storage from '@deepseek-ai/dsh-storage' import { DomainFacility } from '@deepseek-ai/dsh-storage-domain' import { RemoteError, type PeerId, type Principal, type RemoteInvocation } from '@deepseek-ai/dsh-typert-protocol' import WorkspaceRegistry from '@deepseek-ai/dsh-workspace' import type { WorkspaceId } from '@deepseek-ai/dsh-workspace/types' import RemoteAccessService, { type RemoteAccessCondition, type RemoteAccessRole } from '@deepseek-ai/dsh-api-remote-access' import WorkspaceController from '../src/index.ts' import { DEFAULT_WORKSPACE_DIRECTORY } from '../src/default-workspace.ts' import { WorkspaceFeed } from '../src/feed.ts' import type { WorkspaceFollowFrame } from '../src/types.ts' import { MemoryStorageBackend } from '../../../storage/storage-domain/tests/helpers/memory-backend.ts' // The controller relays whatever families the providers report; this suite merges its own. declare module '@deepseek-ai/dsh-workspace/types' { interface SessionActivityKindMap { probe: true 'probe-items': true } } declare module '@deepseek-ai/dsh-typert-protocol' { interface RemoteErrorDetailsMap { 'fixture/failure': {} } } const roots: Context[] = [] /** Workspace roots created per test, removed after their context settles. */ const tempDirs: string[] = [] afterEach(async () => { await Promise.all(roots.splice(0).map(ctx => ctx.fiber.dispose())) for (const dir of tempDirs.splice(0)) rmSync(dir, { recursive: true, force: true }) }) interface Deferred { readonly promise: Promise resolve(value: T): void } function deferred(): Deferred { let resolve!: (value: T) => void const promise = new Promise((settle) => { resolve = settle }) return { promise, resolve } } async function harness(options: { systemDocuments?: boolean } = {}) { const root = realpathSync.native(mkdtempSync(join(tmpdir(), 'dsh-workspace-controller-'))) tempDirs.push(root) const ctx = new Context() roots.push(ctx) await ctx.plugin(SessionStore) await ctx.plugin(Storage) ctx.storage.backend.register('memory', new MemoryStorageBackend()) const storageDomain = new DomainFacility(ctx, { backend: 'memory', routes: {} }) ctx.storage.mount('domain', storageDomain) ctx.provide('storageDomain', storageDomain) ctx.provide('sessionPersistence', { list: () => Promise.resolve([]) } as never) await ctx.plugin(WorkspaceRegistry) const dispose = (): void => {} ctx.provide('typert', { lookups: { configure: () => dispose }, contexts: { configureHost: () => dispose }, resources: { register: vi.fn(() => dispose) }, } as never) new RemoteAccessService(ctx) const controller = new WorkspaceController(ctx, options.systemDocuments === true ? {} : { documentsDirectory: root }) return { controller, ctx, root, storageDomain } } declare module '@deepseek-ai/dsh-typert-protocol' { interface PrincipalMap { /** Test-only non-owner caller shared by the access specs. */ 'test-guest': { readonly kind: 'test-guest'; readonly guest: string } } } /** Opens the Workspace follow stream; each Workspace and Session in it still needs its own role. */ const FOLLOWER: RemoteAccessRole<'global'> = { name: 'test:workspace-follower', resource: 'global', actions: ['workspace/follow'] } /** Shows one Workspace in the follow stream. */ const WORKSPACE_VIEWER: RemoteAccessRole<'workspace'> = { name: 'test:workspace-viewer', resource: 'workspace', actions: ['workspace/follow'] } /** Shows one Session id in the follow stream. */ const SESSION_VIEWER: RemoteAccessRole<'session'> = { name: 'test:session-viewer', resource: 'session', actions: ['workspace/follow'] } /** The Context a Gateway dispatch of `workspace/follow` binds for `principal`. */ function followerContext(ctx: Context, principal: Principal): Context { const invocation: RemoteInvocation = { request: { namespace: 'workspace', method: 'follow', args: {} }, service: 'workspaceController', peer: { id: 'workspace-access-peer' as PeerId, ctx, principal, interactions: 'answer', signal: new AbortController().signal, dispose: async () => {} }, signal: new AbortController().signal, uplink: (): AsyncIterable => ({ [Symbol.asyncIterator]: () => ({ next: async () => ({ done: true, value: undefined }) }) }), } return ctx.extend({ invocation }) } function stageDir(root: string, name: string): string { const path = join(root, name) mkdirSync(path, { recursive: true }) return path } async function nextFrame( iterator: AsyncIterator, ): Promise { const next = await iterator.next() if (next.done === true) throw new Error('Workspace stream ended before the expected frame') return next.value } describe('WorkspaceController commands', () => { it('serializes concurrent path adoption and preserves an existing title', async () => { const { controller, root } = await harness() const path = stageDir(root, 'alpha') const results = await Promise.all([ controller.create({ path }), controller.create({ path }), ]) const created = results.find(result => result.created) const resolved = results.find(result => !result.created) expect(created).toMatchObject({ workspace: { path, title: 'alpha' } }) expect(resolved?.workspace.workspaceId).toBe(created?.workspace.workspaceId) const workspaceId = created?.workspace.workspaceId if (workspaceId === undefined) throw new Error('fixture did not create a Workspace') await controller.rename({ workspaceId, title: 'renamed' }) await expect(controller.create({ path })).resolves.toMatchObject({ created: false, workspace: { workspaceId, title: 'renamed' }, }) }) it('maps invalid paths, blank names, conflicts, and unknown ids to stable failures', async () => { const { controller, root } = await harness() const first = await controller.create({ path: stageDir(root, 'first') }) const second = await controller.create({ path: stageDir(root, 'second') }) await expect(controller.create({ path: join(root, 'missing') })).rejects.toMatchObject({ code: 'workspace/invalid-path', details: { path: join(root, 'missing') }, }) expect(existsSync(join(root, 'missing'))).toBe(false) await expect(controller.rename({ workspaceId: first.workspace.workspaceId, title: ' ' })) .rejects.toMatchObject({ code: 'gateway/bad-request' }) await controller.rename({ workspaceId: first.workspace.workspaceId, title: 'occupied' }) await expect(controller.rename({ workspaceId: second.workspace.workspaceId, title: ' occupied ' })) .rejects.toMatchObject({ code: 'workspace/name-conflict' }) await expect(controller.delete({ workspaceId: 'missing' as WorkspaceId })) .rejects.toMatchObject({ code: 'workspace/not-found' }) }) it('preserves Remote failures and propagates unexpected registry failures', async () => { const { controller, ctx, root } = await harness() const remoteFailure = new RemoteError('fixture/failure', 'already mapped', {}) const resolveByPath = vi.spyOn(ctx.workspaceRegistry, 'resolveByPath') .mockRejectedValueOnce(remoteFailure) .mockRejectedValueOnce('plain failure') await expect(controller.create({ path: stageDir(root, 'remote-failure') })) .rejects.toBe(remoteFailure) const plainFailure = controller.create({ path: stageDir(root, 'plain-failure') }) await expect(plainFailure).rejects.toMatchObject({ code: 'workspace/invalid-path' }) await expect(plainFailure).rejects.toThrow('plain failure') resolveByPath.mockRestore() const created = await controller.create({ path: stageDir(root, 'created') }) const workspace = ctx.workspaceRegistry.get(created.workspace.workspaceId) if (workspace === undefined) throw new Error('fixture Workspace disappeared') const orderFailure = new Error('order storage failed') vi.spyOn(ctx.workspaceRegistry, 'insertBefore').mockRejectedValueOnce(orderFailure) await expect(controller.insertBefore({ workspaceId: created.workspace.workspaceId })) .rejects.toBe(orderFailure) const moveFailure = new Error('membership storage failed') vi.spyOn(workspace, 'insertSessionBefore').mockRejectedValueOnce(moveFailure) await expect(controller.insertSessionBefore({ workspaceId: created.workspace.workspaceId, sessionId: SessionId('session'), })).rejects.toBe(moveFailure) const archiveFailure = new Error('archive storage failed') vi.spyOn(ctx.workspaceRegistry, 'archiveSession').mockRejectedValueOnce(archiveFailure) await expect(controller.archiveSession({ sessionId: SessionId('session') })) .rejects.toBe(archiveFailure) const unarchiveFailure = new Error('unarchive storage failed') vi.spyOn(ctx.workspaceRegistry, 'unarchiveSession').mockRejectedValueOnce(unarchiveFailure) await expect(controller.unarchiveSession({ sessionId: SessionId('session') })) .rejects.toBe(unarchiveFailure) const pinFailure = new Error('pin storage failed') vi.spyOn(ctx.workspaceRegistry, 'pinSession').mockRejectedValueOnce(pinFailure) await expect(controller.pinSession({ sessionId: SessionId('session') })) .rejects.toBe(pinFailure) const unpinFailure = new Error('unpin storage failed') vi.spyOn(ctx.workspaceRegistry, 'unpinSession').mockRejectedValueOnce(unpinFailure) await expect(controller.unpinSession({ sessionId: SessionId('session') })) .rejects.toBe(unpinFailure) }) it('resolves queued Workspace identities when their operation starts', async () => { const { controller, ctx, root } = await harness() const target = await controller.create({ path: stageDir(root, 'target') }) const blockerPath = stageDir(root, 'blocker') const gate = deferred() const originalResolveByPath = ctx.workspaceRegistry.resolveByPath.bind(ctx.workspaceRegistry) const resolveByPath = vi.spyOn(ctx.workspaceRegistry, 'resolveByPath') resolveByPath.mockImplementationOnce(async (path) => { await gate.promise return originalResolveByPath(path) }) const blocker = controller.create({ path: blockerPath }) const deletion = controller.delete({ workspaceId: target.workspace.workspaceId }) const staleRename = controller.rename({ workspaceId: target.workspace.workspaceId, title: 'must-not-land', }) gate.resolve(undefined) await blocker await expect(deletion).resolves.toEqual({ deleted: true }) await expect(staleRename).rejects.toMatchObject({ code: 'workspace/not-found' }) }) it('reorders Workspaces and Sessions and archives only known Sessions', async () => { const { controller, ctx, root } = await harness() const first = await controller.create({ path: stageDir(root, 'first') }) const second = await controller.create({ path: stageDir(root, 'second') }) await expect(controller.insertBefore({ workspaceId: first.workspace.workspaceId, beforeWorkspaceId: second.workspace.workspaceId, })).resolves.toEqual({ workspaceIds: [first.workspace.workspaceId, second.workspace.workspaceId], }) await expect(controller.insertBefore({ workspaceId: 'missing' as WorkspaceId })) .rejects.toMatchObject({ code: 'workspace/not-found' }) const session = ctx.sessions.create(SessionId('session-one'), { meta: { cwd: first.workspace.path }, }) const workspace = ctx.workspaceRegistry.get(first.workspace.workspaceId) if (workspace === undefined) throw new Error('fixture Workspace disappeared') await workspace.attachSession(session.id) await expect(controller.insertSessionBefore({ workspaceId: first.workspace.workspaceId, sessionId: session.id, })).resolves.toMatchObject({ workspace: { sessionIds: [session.id] } }) await expect(controller.insertSessionBefore({ workspaceId: first.workspace.workspaceId, sessionId: SessionId('missing-session'), })).rejects.toMatchObject({ code: 'workspace/move-invalid' }) await expect(controller.insertSessionBefore({ workspaceId: first.workspace.workspaceId, sessionId: session.id, beforeSessionId: SessionId('missing-anchor'), })).rejects.toMatchObject({ code: 'workspace/move-invalid', details: { beforeSessionId: 'missing-anchor' }, }) await expect(controller.insertSessionBefore({ workspaceId: 'missing' as WorkspaceId, sessionId: session.id, })).rejects.toMatchObject({ code: 'workspace/not-found' }) // A session reported active by the registry's activity waterfall is a // stable business failure carrying what still runs, and nothing is written. const activity = [{ kind: 'probe' as const }, { kind: 'probe-items' as const, items: [{ id: 'item-1', label: 'build' }] }] const stopReporting = ctx.on('workspace/session-activity', async ({ sessionId }, next) => sessionId === session.id ? [...activity, ...(await next())] : next()) await expect(controller.archiveSession({ sessionId: session.id })).rejects.toMatchObject({ code: 'workspace/session-active', details: { sessionId: session.id, activity }, }) expect([...ctx.workspaceRegistry.archivedSessionIds]).toEqual([]) // Asking to stop the work archives the still-active Session and reaches // the stop providers first. const stops: string[] = [] const stopListening = ctx.on('workspace/session-stop', ({ sessionId }) => { stops.push(String(sessionId)) }) await expect(controller.archiveSession({ sessionId: session.id, stopActivity: true })) .resolves.toEqual({ archivedSessionIds: [session.id] }) expect(stops).toEqual([String(session.id)]) stopListening() await expect(controller.unarchiveSession({ sessionId: session.id })) .resolves.toEqual({ archivedSessionIds: [] }) stopReporting() await expect(controller.archiveSession({ sessionId: session.id })) .resolves.toEqual({ archivedSessionIds: [session.id] }) await expect(controller.archiveSession({ sessionId: SessionId('unknown') })) .rejects.toMatchObject({ code: 'session/not-found' }) await expect(controller.unarchiveSession({ sessionId: session.id })) .resolves.toEqual({ archivedSessionIds: [] }) // Unarchive is idempotent: an id that is not archived is not an error. await expect(controller.unarchiveSession({ sessionId: session.id })) .resolves.toEqual({ archivedSessionIds: [] }) }) it('pins only known unarchived Sessions and unpins idempotently', async () => { const { controller, ctx, root } = await harness() const created = await controller.create({ path: stageDir(root, 'pins') }) const session = ctx.sessions.create(SessionId('pin-me'), { meta: { cwd: created.workspace.path }, }) await expect(controller.pinSession({ sessionId: session.id })) .resolves.toEqual({ pinnedSessionIds: [session.id] }) await expect(controller.pinSession({ sessionId: SessionId('unknown') })) .rejects.toMatchObject({ code: 'session/not-found' }) // Pinning an archived Session is a caller error, not a missing session. const archived = ctx.sessions.create(SessionId('stored'), { meta: { cwd: created.workspace.path }, }) await controller.archiveSession({ sessionId: archived.id }) await expect(controller.pinSession({ sessionId: archived.id })) .rejects.toMatchObject({ code: 'gateway/bad-request' }) await expect(controller.unpinSession({ sessionId: session.id })) .resolves.toEqual({ pinnedSessionIds: [] }) // Unpin is idempotent: an id that is not pinned is not an error. await expect(controller.unpinSession({ sessionId: session.id })) .resolves.toEqual({ pinnedSessionIds: [] }) }) }) describe('WorkspaceController follow', () => { it('seeds a new feed from existing rows and rejects an inconsistent registry commit', async () => { const { ctx, root } = await harness() const existing = await ctx.workspaceRegistry.create(stageDir(root, 'existing')) const feed = new WorkspaceFeed(ctx) expect(feed.baseline()).toMatchObject({ items: [{ workspaceId: existing.id }], }) expect(() => { ctx.emit('domain/changed', { domain: 'workspace', table: '', key: '', operation: 'put', value: { initialized: true, workspaceIds: ['missing'], archivedSessionIds: [], pinnedSessionIds: [], }, }) }).toThrow('references missing Workspace "missing"') }) it('starts a fresh feed with existing pins and follows their removal', async () => { const { controller, ctx, root } = await harness() const session = ctx.sessions.create(SessionId('already-pinned'), { meta: { cwd: root } }) await controller.pinSession({ sessionId: session.id }) const feed = new WorkspaceFeed(ctx) const abort = new AbortController() const iterator = feed.follow(abort.signal)[Symbol.asyncIterator]() try { await expect(nextFrame(iterator)).resolves.toMatchObject({ type: 'baseline', value: { pinnedSessionIds: [session.id] }, }) await controller.unpinSession({ sessionId: session.id }) await expect(nextFrame(iterator)).resolves.toEqual({ type: 'pinned', pinnedSessionIds: [] }) } finally { abort.abort() await iterator.return?.() } }) it('starts with a complete baseline and emits committed increments in domain order', async () => { const { controller, ctx, root } = await harness() const abort = new AbortController() const iterator = controller.follow(abort.signal)[Symbol.asyncIterator]() await expect(nextFrame(iterator)).resolves.toEqual({ type: 'baseline', value: { items: [], archivedSessionIds: [], pinnedSessionIds: [] }, }) const first = await controller.create({ path: stageDir(root, 'first') }) await expect(nextFrame(iterator)).resolves.toMatchObject({ type: 'upsert', workspace: { workspaceId: first.workspace.workspaceId }, }) await expect(nextFrame(iterator)).resolves.toEqual({ type: 'order', workspaceIds: [first.workspace.workspaceId], }) await controller.rename({ workspaceId: first.workspace.workspaceId, title: 'renamed' }) await expect(nextFrame(iterator)).resolves.toMatchObject({ type: 'upsert', workspace: { title: 'renamed' }, }) const second = await controller.create({ path: stageDir(root, 'second') }) await expect(nextFrame(iterator)).resolves.toMatchObject({ type: 'upsert', workspace: { workspaceId: second.workspace.workspaceId }, }) await expect(nextFrame(iterator)).resolves.toEqual({ type: 'order', workspaceIds: [second.workspace.workspaceId, first.workspace.workspaceId], }) await controller.insertBefore({ workspaceId: first.workspace.workspaceId, beforeWorkspaceId: second.workspace.workspaceId, }) await expect(nextFrame(iterator)).resolves.toEqual({ type: 'order', workspaceIds: [first.workspace.workspaceId, second.workspace.workspaceId], }) const session = ctx.sessions.create(SessionId('archived'), { meta: { cwd: first.workspace.path }, }) await controller.archiveSession({ sessionId: session.id }) await expect(nextFrame(iterator)).resolves.toEqual({ type: 'archived', archivedSessionIds: [session.id], }) // Unarchive rides the same complete-set increment: no new frame type. await controller.unarchiveSession({ sessionId: session.id }) await expect(nextFrame(iterator)).resolves.toEqual({ type: 'archived', archivedSessionIds: [], }) await controller.pinSession({ sessionId: session.id }) await expect(nextFrame(iterator)).resolves.toEqual({ type: 'pinned', pinnedSessionIds: [session.id], }) // Unpin rides the same complete-set increment: no new frame type. await controller.unpinSession({ sessionId: session.id }) await expect(nextFrame(iterator)).resolves.toEqual({ type: 'pinned', pinnedSessionIds: [], }) await controller.delete({ workspaceId: second.workspace.workspaceId }) await expect(nextFrame(iterator)).resolves.toEqual({ type: 'order', workspaceIds: [first.workspace.workspaceId], }) await expect(nextFrame(iterator)).resolves.toEqual({ type: 'remove', workspaceId: second.workspace.workspaceId, }) abort.abort() await expect(iterator.next()).resolves.toEqual({ done: true, value: undefined }) }) it('follows only the Workspaces and Sessions a non-owner may follow', async () => { const { controller, ctx, root } = await harness() // oxlint-disable-next-line typescript/unbound-method -- the assertion inspects the spy and never calls it. expect(ctx.typert.resources.register).toHaveBeenCalledWith('workspace', { field: 'workspaceId' }) const shown = await controller.create({ path: stageDir(root, 'shown') }) const hiddenWorkspace = await controller.create({ path: stageDir(root, 'hidden') }) const shownId = shown.workspace.workspaceId const hiddenId = hiddenWorkspace.workspace.workspaceId const shared = ctx.sessions.create(SessionId('shared'), { meta: { cwd: shown.workspace.path } }) const secret = ctx.sessions.create(SessionId('secret'), { meta: { cwd: shown.workspace.path } }) const workspace = ctx.workspaceRegistry.get(shownId) if (workspace === undefined) throw new Error('fixture Workspace disappeared') await workspace.attachSession(shared.id) await workspace.attachSession(secret.id) await controller.pinSession({ sessionId: shared.id }) await controller.pinSession({ sessionId: secret.id }) ctx.remoteAccess.registerPolicy('test-guest', { bindings: () => [ { role: FOLLOWER, resource: { kind: 'global' } }, { role: WORKSPACE_VIEWER, resource: { kind: 'workspace', id: shownId } }, { role: SESSION_VIEWER, resource: { kind: 'session', id: shared.id } }, ], }) const guest: Principal = { kind: 'test-guest', guest: 'workspace-controller' } const abort = new AbortController() const iterator = followerContext(ctx, guest).workspaceController.follow(abort.signal)[Symbol.asyncIterator]() await expect(nextFrame(iterator)).resolves.toEqual({ type: 'baseline', value: { items: [expect.objectContaining({ workspaceId: shownId, sessionIds: [shared.id] })], archivedSessionIds: [], pinnedSessionIds: [shared.id], }, }) await controller.rename({ workspaceId: hiddenId, title: 'still hidden' }) await controller.rename({ workspaceId: shownId, title: 'renamed' }) await expect(nextFrame(iterator)).resolves.toMatchObject({ type: 'upsert', workspace: { workspaceId: shownId, title: 'renamed', sessionIds: [shared.id] }, }) await controller.unpinSession({ sessionId: secret.id }) await expect(nextFrame(iterator)).resolves.toEqual({ type: 'pinned', pinnedSessionIds: [shared.id] }) await controller.unpinSession({ sessionId: shared.id }) await controller.archiveSession({ sessionId: secret.id }) await controller.archiveSession({ sessionId: shared.id }) await expect(nextFrame(iterator)).resolves.toEqual({ type: 'pinned', pinnedSessionIds: [] }) await expect(nextFrame(iterator)).resolves.toEqual({ type: 'archived', archivedSessionIds: [] }) await expect(nextFrame(iterator)).resolves.toEqual({ type: 'archived', archivedSessionIds: [shared.id] }) await controller.delete({ workspaceId: hiddenId }) await expect(nextFrame(iterator)).resolves.toEqual({ type: 'order', workspaceIds: [shownId] }) await controller.delete({ workspaceId: shownId }) await expect(nextFrame(iterator)).resolves.toEqual({ type: 'order', workspaceIds: [] }) await expect(nextFrame(iterator)).resolves.toEqual({ type: 'remove', workspaceId: shownId }) abort.abort() await expect(iterator.next()).resolves.toEqual({ done: true, value: undefined }) }) it('sends a non-owner a replace increment when a delivered Workspace loses visibility, so it can reappear', async () => { const { controller, ctx, root } = await harness() const first = (await controller.create({ path: stageDir(root, 'first') })).workspace.workspaceId const second = (await controller.create({ path: stageDir(root, 'second') })).workspace.workspaceId // One Remote call reads its bindings once, so visibility changes during the stream come from the condition. const allowedWorkspaces = new Set([first, second]) const stillShared: RemoteAccessCondition<'workspace'> = ({ resource }) => allowedWorkspaces.has(resource.id) ? { kind: 'allow' } : { kind: 'deny', reason: 'test:unshared' } ctx.remoteAccess.registerPolicy('test-guest', { bindings: () => [ { role: FOLLOWER, resource: { kind: 'global' } }, ...[first, second].map(id => ({ role: WORKSPACE_VIEWER, resource: { kind: 'workspace', id } as const, condition: stillShared })), ], }) const abort = new AbortController() const iterator = followerContext(ctx, { kind: 'test-guest', guest: 'withdrawn' }).workspaceController.follow(abort.signal)[Symbol.asyncIterator]() const ids = (frame: WorkspaceFollowFrame): string[] => frame.type === 'baseline' || frame.type === 'replace' ? frame.value.items.map(item => item.workspaceId).sort() : [] const opening = await nextFrame(iterator) expect(opening.type).toBe('baseline') expect(ids(opening)).toEqual([first, second].sort()) // Revoked: the next change replaces the state instead of sending a `remove` the Client would never undo. allowedWorkspaces.delete(first) await controller.rename({ workspaceId: first, title: 'revoked' }) const withoutFirst = await nextFrame(iterator) expect(withoutFirst.type).toBe('replace') expect(ids(withoutFirst)).toEqual([second]) allowedWorkspaces.delete(second) const third = (await controller.create({ path: stageDir(root, 'third') })).workspace.workspaceId const empty = await nextFrame(iterator) expect(empty.type).toBe('replace') expect(ids(empty)).toEqual([]) // A deletion reaches only a Client that received the Workspace. await controller.delete({ workspaceId: third }) await expect(nextFrame(iterator)).resolves.toEqual({ type: 'order', workspaceIds: [] }) // Restored: the Workspace reappears through an ordinary upsert. allowedWorkspaces.add(first) await controller.rename({ workspaceId: first, title: 'restored' }) await expect(nextFrame(iterator)).resolves.toMatchObject({ type: 'upsert', workspace: { workspaceId: first, title: 'restored' } }) abort.abort() await expect(iterator.next()).resolves.toEqual({ done: true, value: undefined }) }) it('ignores unrelated domain writes and closes active followers on disposal', async () => { const { controller, ctx, root } = await harness() const abort = new AbortController() const iterator = controller.follow(abort.signal)[Symbol.asyncIterator]() await nextFrame(iterator) ctx.emit('domain/changed', { domain: 'other', table: 'records', key: 'x', operation: 'put', value: {}, }) ctx.emit('domain/changed', { domain: 'workspace', table: '', key: '', operation: 'deleted', }) ctx.emit('domain/changed', { domain: 'workspace', table: 'other', key: 'x', operation: 'put', value: {}, }) ctx.emit('domain/changed', { domain: 'workspace', table: 'workspaces', key: 'unknown', operation: 'deleted', }) const pending = iterator.next() const created = await controller.create({ path: stageDir(root, 'visible') }) await expect(pending).resolves.toMatchObject({ value: { type: 'upsert' } }) await expect(iterator.next()).resolves.toEqual({ done: false, value: { type: 'order', workspaceIds: [created.workspace.workspaceId] }, }) const closing = iterator.next() await ctx.fiber.dispose() roots.splice(roots.indexOf(ctx), 1) await expect(closing).resolves.toEqual({ done: true, value: undefined }) }) }) describe('first-use Remote', () => { it('reuses an initialized Workspace without looking up system Documents', async () => { const { controller, ctx, root } = await harness({ systemDocuments: true }) const workspace = await ctx.workspaceRegistry.initializeDefault(async () => root) const signal = AbortSignal.abort() await expect(controller.initializeDefault(signal)) .resolves.toMatchObject({ workspace: { workspaceId: workspace!.id, path: root, title: basename(root) } }) }) it('returns a durable Workspace named after its fixed directory without allocating a Session', async () => { const { controller, ctx, root } = await harness() const signal = new AbortController().signal const result = await controller.initializeDefault(signal) expect(result!.workspace.path).toBe(join(root, 'deepseek-harness', DEFAULT_WORKSPACE_DIRECTORY)) expect(result!.workspace.title).toBe(DEFAULT_WORKSPACE_DIRECTORY) expect(existsSync(result!.workspace.path)).toBe(true) expect(ctx.sessions.list()).toEqual([]) expect(await controller.initializeDefault(signal)).toEqual(result) }) it('skips ineligible first use and propagates preparation failures', async () => { const { controller, ctx, root } = await harness() await ctx.workspaceRegistry.create(root) await expect(controller.initializeDefault(new AbortController().signal)) .resolves.toBeUndefined() vi.spyOn(ctx.workspaceRegistry, 'initializeDefault').mockRejectedValueOnce(new Error('permission denied')) await expect(controller.initializeDefault(new AbortController().signal)) .rejects.toThrow('permission denied') }) })