mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-11 04:00:38 +00:00
refactor(api): converge the Remote failure vocabulary and client surface
Single RemoteError with a merge-extensible, domain-prefixed code map; owners throw at the failure point; streams surface marked failures; clients consume ctx.remote directly with isRemoteFailure as the only discrimination point and construct no failure instances.
This commit is contained in:
@@ -6,7 +6,7 @@ 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 { TypertRemoteFailure } from '@deepseek-ai/dsh-typert-protocol'
|
||||
import { RemoteError } from '@deepseek-ai/dsh-typert-protocol'
|
||||
import WorkspaceRegistry from '@deepseek-ai/dsh-workspace'
|
||||
import type { WorkspaceId } from '@deepseek-ai/dsh-workspace/types'
|
||||
import WorkspaceController from '../src/index.ts'
|
||||
@@ -14,6 +14,12 @@ import { WorkspaceFeed } from '../src/feed.ts'
|
||||
import type { WorkspaceFollowFrame } from '../src/types.ts'
|
||||
import { MemoryStorageBackend } from '../../../storage/storage-domain/tests/helpers/memory-backend.ts'
|
||||
|
||||
declare module '@deepseek-ai/dsh-typert-protocol' {
|
||||
interface RemoteErrorDetailsMap {
|
||||
'fixture/failure': {}
|
||||
}
|
||||
}
|
||||
|
||||
const roots: Context[] = []
|
||||
|
||||
afterEach(async () => {
|
||||
@@ -94,34 +100,29 @@ describe('WorkspaceController commands', () => {
|
||||
const second = await controller.create({ path: stageDir(root, 'second') })
|
||||
|
||||
await expect(controller.create({ path: join(root, 'missing') })).rejects.toMatchObject({
|
||||
failure: { code: 'workspace-invalid-path', details: { path: join(root, 'missing') } },
|
||||
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({ failure: { code: 'bad-request' } })
|
||||
.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({ failure: { code: 'workspace-name-conflict' } })
|
||||
.rejects.toMatchObject({ code: 'workspace/name-conflict' })
|
||||
await expect(controller.delete({ workspaceId: 'missing' as WorkspaceId }))
|
||||
.rejects.toMatchObject({ failure: { code: 'workspace-not-found' } })
|
||||
.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 TypertRemoteFailure({
|
||||
code: 'fixture-failure',
|
||||
message: 'already mapped',
|
||||
details: {},
|
||||
})
|
||||
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({
|
||||
failure: { code: 'workspace-invalid-path' },
|
||||
})
|
||||
await expect(plainFailure).rejects.toMatchObject({ code: 'workspace/invalid-path' })
|
||||
await expect(plainFailure).rejects.toThrow('plain failure')
|
||||
resolveByPath.mockRestore()
|
||||
|
||||
@@ -168,7 +169,7 @@ describe('WorkspaceController commands', () => {
|
||||
gate.resolve(undefined)
|
||||
await blocker
|
||||
await expect(deletion).resolves.toEqual({ deleted: true })
|
||||
await expect(staleRename).rejects.toMatchObject({ failure: { code: 'workspace-not-found' } })
|
||||
await expect(staleRename).rejects.toMatchObject({ code: 'workspace/not-found' })
|
||||
})
|
||||
|
||||
it('reorders Workspaces and Sessions and archives only known Sessions', async () => {
|
||||
@@ -182,7 +183,7 @@ describe('WorkspaceController commands', () => {
|
||||
workspaceIds: [first.workspace.workspaceId, second.workspace.workspaceId],
|
||||
})
|
||||
await expect(controller.insertBefore({ workspaceId: 'missing' as WorkspaceId }))
|
||||
.rejects.toMatchObject({ failure: { code: 'workspace-not-found' } })
|
||||
.rejects.toMatchObject({ code: 'workspace/not-found' })
|
||||
|
||||
const session = ctx.sessions.create(SessionId('session-one'), {
|
||||
meta: { cwd: first.workspace.path },
|
||||
@@ -197,26 +198,24 @@ describe('WorkspaceController commands', () => {
|
||||
await expect(controller.insertSessionBefore({
|
||||
workspaceId: first.workspace.workspaceId,
|
||||
sessionId: SessionId('missing-session'),
|
||||
})).rejects.toMatchObject({ failure: { code: 'workspace-move-invalid' } })
|
||||
})).rejects.toMatchObject({ code: 'workspace/move-invalid' })
|
||||
await expect(controller.insertSessionBefore({
|
||||
workspaceId: first.workspace.workspaceId,
|
||||
sessionId: session.id,
|
||||
beforeSessionId: SessionId('missing-anchor'),
|
||||
})).rejects.toMatchObject({
|
||||
failure: {
|
||||
code: 'workspace-move-invalid',
|
||||
details: { beforeSessionId: 'missing-anchor' },
|
||||
},
|
||||
code: 'workspace/move-invalid',
|
||||
details: { beforeSessionId: 'missing-anchor' },
|
||||
})
|
||||
await expect(controller.insertSessionBefore({
|
||||
workspaceId: 'missing' as WorkspaceId,
|
||||
sessionId: session.id,
|
||||
})).rejects.toMatchObject({ failure: { code: 'workspace-not-found' } })
|
||||
})).rejects.toMatchObject({ code: 'workspace/not-found' })
|
||||
|
||||
await expect(controller.archiveSession({ sessionId: session.id }))
|
||||
.resolves.toEqual({ archivedSessionIds: [session.id] })
|
||||
await expect(controller.archiveSession({ sessionId: SessionId('unknown') }))
|
||||
.rejects.toMatchObject({ failure: { code: 'session-not-found' } })
|
||||
.rejects.toMatchObject({ code: 'session/not-found' })
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user