refactor: apply repository naming contract

Apply the accepted pre-release package, service, type, directory, and role renames as one repository-wide change.
This commit is contained in:
Tianyi Cui
2026-08-13 00:36:22 +08:00
parent 101df7cf58
commit a2d0f7f411
3281 changed files with 21730 additions and 21592 deletions

View File

@@ -12,8 +12,8 @@ import TypertRegistry from '@deepseek-ai/dsh-typert-registry'
import * as RuntimeClient from '../src/client/index.ts'
import type { ConversationNodeDefinition } from '../src/client/contract/conversation.ts'
import { Session } from '../src/client/sessions/session.ts'
import type { SessionsService } from '../src/client/sessions/service.ts'
import type { WorkspacesService } from '../src/client/workspaces/service.ts'
import type { SessionRuntime } from '../src/client/sessions/service.ts'
import type { WorkspaceRuntime } from '../src/client/workspaces/service.ts'
import { FakeApiClient, fakeRemote, ok } from './fake-api.client.ts'
interface Bench {
@@ -58,7 +58,7 @@ describe('runtime client apply', () => {
it('mounts slots, Sessions, and Workspaces and fans host frames into both managers', async () => {
const bench = await mount()
expect(bench.ctx.get('slots') !== undefined).toBe(true)
// The built-in 'root' declaration ships with this package's SlotsService
// The built-in 'root' declaration ships with this package's SlotRegistry
// (the SlotMap 'root' merge lives here).
expect(bench.ctx.slots.spec('root')).toEqual({ kind: 'single', scope: 'root' })
const sessions = bench.ctx.get('sessions')
@@ -66,8 +66,8 @@ describe('runtime client apply', () => {
expect(sessions !== undefined).toBe(true)
expect(workspaces !== undefined).toBe(true)
// The bound the wire schema enforces, not a per-connection negotiation.
expect((sessions as SessionsService).searchResultLimit).toBe(SESSION_SEARCH_RESULT_LIMIT)
if (workspaces === undefined) throw new Error('WorkspacesService missing after runtime apply')
expect((sessions as SessionRuntime).searchResultLimit).toBe(SESSION_SEARCH_RESULT_LIMIT)
if (workspaces === undefined) throw new Error('WorkspaceRuntime missing after runtime apply')
expect(bench.sinks).toBeDefined()
// Frame sinks reach the object layer: a host session-added lands in the list store.
@@ -107,8 +107,8 @@ describe('runtime client apply', () => {
bench.sinks?.onConnected?.({ version: '0', cwd: '/f', attachedSessions: 0, canOpenPath: true })
await flushMicrotasks()
const sessions = bench.ctx.get('sessions') as SessionsService
const workspaces = bench.ctx.get('workspaces') as WorkspacesService
const sessions = bench.ctx.get('sessions') as SessionRuntime
const workspaces = bench.ctx.get('workspaces') as WorkspaceRuntime
expect(bench.api.callsOf('session.create')).toEqual([{ workspaceId: 'w-recent' }])
expect(sessions.list.getSnapshot().current).toBe('fk-new')
@@ -121,7 +121,7 @@ describe('runtime client apply', () => {
it('wires registry changes into resident Sessions during the runtime apply pass', async () => {
const bench = await mount()
const sessions = bench.ctx.get('sessions') as SessionsService
const sessions = bench.ctx.get('sessions') as SessionRuntime
bench.sinks?.onHostEnvelope?.({
rpcId: 'r-registry' as never,
payload: { type: 'host/session-added', blank: true, sessionId: 's-registry' } as never,

View File

@@ -730,7 +730,7 @@ describe('ConversationNodeAssembler', () => {
role: 'start',
}
}
if ((event.type as string) === 'compact/start') {
if ((event.type as string) === 'compaction/start') {
return {
id: (event.data as unknown as { compactionId: string }).compactionId,
role: 'start',
@@ -758,7 +758,7 @@ describe('ConversationNodeAssembler', () => {
input(at(2, 'step/start', { turn: 1, step: 1 })),
input(at(3, 'turn/start', { turn: 2 })),
input(at(4, 'command/run', { commandId: 'command', name: 'x' })),
input(at(5, 'compact/start', { compactionId: 'compact', turn: null })),
input(at(5, 'compaction/start', { compactionId: 'compact', turn: null })),
], false)
assembler.flush()

View File

@@ -7,7 +7,7 @@ import type {
ConversationNodeDefinition, ConversationViewDefinition, ConversationViewNode,
} from '../src/client/contract/conversation.ts'
import { Session } from '../src/client/sessions/session.ts'
import { SessionsService } from '../src/client/sessions/service.ts'
import { SessionRuntime } from '../src/client/sessions/service.ts'
import { FakeApiClient, fakeRemote, ok } from './fake-api.client.ts'
function eventDefinition(kind: string): ConversationNodeDefinition<null> {
@@ -145,7 +145,7 @@ describe('Conversation registries', () => {
api.onList = () => Promise.resolve(ok({
items: [{ sessionId, updatedAt: 1, running: false, blank: true }],
}) as never)
const sessions = new SessionsService(ctx, api, fakeRemote())
const sessions = new SessionRuntime(ctx, api, fakeRemote())
await sessions.refresh()
await Promise.resolve()
sessions.scope(sessionId)

View File

@@ -105,9 +105,9 @@ export const ev = {
...text === undefined ? {} : { text },
...sourceEventSeq === undefined ? {} : { sourceEventSeq },
} }),
/** A compaction's log-only `compact/summary` record. */
/** A compaction's log-only `compaction/summary` record. */
compactSummary: (seq: number, summary: string, start: number, end: number): SessionEvent =>
at(seq, { type: 'compact/summary', data: {
at(seq, { type: 'compaction/summary', data: {
summary: text(summary),
shadowedRange: { start, end },
shadowedSeqs: [start, end],

View File

@@ -5,13 +5,13 @@
*/
import { Context } from '@deepseek-ai/cordis'
import { describe, expect, it } from 'vitest'
import InvariantService from '@deepseek-ai/dsh-invariants'
import InvariantRegistry from '@deepseek-ai/dsh-invariants'
import * as RuntimeInvariant from '../src/invariant.ts'
import { SlotsService } from '../src/client/slots.ts'
import { SlotRegistry } from '../src/client/slots.ts'
async function setup(): Promise<Context> {
const ctx = new Context()
await ctx.plugin(InvariantService, { enabled: true })
await ctx.plugin(InvariantRegistry, { enabled: true })
await ctx.plugin(RuntimeInvariant).await()
return ctx
}
@@ -24,7 +24,7 @@ describe('runtime slots/changed invariant', () => {
it('passes foreign events and a legitimate mutation-then-emission sequence', async () => {
const ctx = await setup()
expect(() => { emit(ctx, 'unrelated/event', 'x') }).not.toThrow()
await ctx.plugin(SlotsService).await() // fiber must reach ACTIVE — the audit reads strict ctx.get
await ctx.plugin(SlotRegistry).await() // fiber must reach ACTIVE — the audit reads strict ctx.get
// A real registration bumps the version first and re-emits through
// onMutate — the audit sees version > 0 and stays quiet. (Erased call:
// the typed register face rides the wave-1 ui-slots types.)
@@ -36,7 +36,7 @@ describe('runtime slots/changed invariant', () => {
const ctx = await setup()
expect(() => { emit(ctx, 'slots/changed', '') }).toThrow(/without a slot key/)
expect(() => { emit(ctx, 'slots/changed', 42) }).toThrow(/without a slot key/)
await ctx.plugin(SlotsService).await()
await ctx.plugin(SlotRegistry).await()
// Hand-emitted key that never saw a mutation: version 0 → violation.
expect(() => { emit(ctx, 'slots/changed', 'never-mutated') })
.toThrow(/before any mutation bumped its version/)

View File

@@ -1152,32 +1152,32 @@ describe('completed reminder', () => {
})
})
describe('background-task mirror', () => {
describe('background-job mirror', () => {
const view = (over: Partial<{ id: string; status: string; label: string }> = {}) => ({
id: 'bash-1', kind: 'bash', label: 'pnpm run build', status: 'running', startedAt: 5, ...over,
})
const tasksFrame = (sessionId: SessionId, tasks: unknown[]) =>
({ rpcId: 't' as never, payload: { type: 'session/tasks', sessionId, tasks } as never })
const tasksFrame = (sessionId: SessionId, jobs: unknown[]) =>
({ rpcId: 't' as never, payload: { type: 'session/jobs', sessionId, jobs } as never })
it('mirrors the whole set last-wins, keyed per session, with no Session instance needed', () => {
const manager = new SessionManager(new FakeApiClient(), fakeRemote())
manager.handleMuxEnvelope(tasksFrame(S1, [view()]))
manager.handleMuxEnvelope(tasksFrame(S2, [view({ id: 'pwsh-1', label: 'other' })]))
const first = manager.getListSnapshot().tasksBySession
const first = manager.getListSnapshot().jobsBySession
expect(first[S1]).toEqual([view()])
expect(first[S2]?.[0]?.label).toBe('other')
// Last-wins: the newer whole set replaces, it does not merge.
manager.handleMuxEnvelope(tasksFrame(S1, [view({ status: 'completed' })]))
expect(manager.getListSnapshot().tasksBySession[S1]).toEqual([view({ status: 'completed' })])
expect(manager.getListSnapshot().jobsBySession[S1]).toEqual([view({ status: 'completed' })])
})
it('stores an emptied set as an absent key so absence and [] read alike', () => {
const manager = new SessionManager(new FakeApiClient(), fakeRemote())
manager.handleMuxEnvelope(tasksFrame(S1, [view()]))
expect(S1 in manager.getListSnapshot().tasksBySession).toBe(true)
expect(S1 in manager.getListSnapshot().jobsBySession).toBe(true)
manager.handleMuxEnvelope(tasksFrame(S1, []))
expect(S1 in manager.getListSnapshot().tasksBySession).toBe(false)
expect(S1 in manager.getListSnapshot().jobsBySession).toBe(false)
})
it('clears the mirror on re-subscribe, because a task-free generation sends no baseline', () => {
@@ -1187,7 +1187,7 @@ describe('background-task mirror', () => {
rpcId: 's' as never,
payload: { type: 'session/subscribed', sessionId: S1, lastSeq: 3 },
})
expect(S1 in manager.getListSnapshot().tasksBySession).toBe(false)
expect(S1 in manager.getListSnapshot().jobsBySession).toBe(false)
})
it('drops the rows when the session is removed, whichever stream lands first', () => {
@@ -1195,7 +1195,7 @@ describe('background-task mirror', () => {
manager.handleHostEnvelope({ rpcId: 'a' as never, payload: { type: 'host/session-added', blank: true, sessionId: S1 } })
manager.handleMuxEnvelope(tasksFrame(S1, [view()]))
manager.handleHostEnvelope({ rpcId: 'r' as never, payload: { type: 'host/session-removed', sessionId: S1 } })
expect(S1 in manager.getListSnapshot().tasksBySession).toBe(false)
expect(S1 in manager.getListSnapshot().jobsBySession).toBe(false)
})
it('notifies list subscribers so an open header re-renders without a poll', async () => {

View File

@@ -1,5 +1,5 @@
/**
* SessionsService: list store projection (manager → {ids, byId, current}
* SessionRuntime: list store projection (manager → {ids, byId, current}
* with derived titles), the migrated current-selection account (open
* validation, persisted mask semantics, cell resolution), scope-tree
* lifecycle (lazy mint / frozen survival / removed teardown with staged
@@ -9,7 +9,7 @@
import { Context } from '@deepseek-ai/cordis'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { SessionId } from '@deepseek-ai/dsh-api-remotes/client'
import { SessionCreateError, SessionsService, scopeOf } from '../src/client/sessions/service.ts'
import { SessionCreateError, SessionRuntime, scopeOf } from '../src/client/sessions/service.ts'
import { FakeApiClient, deferred, err, fakeRemote, ok } from './fake-api.client.ts'
const sid = (s: string): SessionId => s as SessionId
@@ -17,13 +17,13 @@ const sid = (s: string): SessionId => s as SessionId
interface Bench {
ctx: Context
api: FakeApiClient
svc: SessionsService
svc: SessionRuntime
}
function bench(): Bench {
const ctx = new Context()
const api = new FakeApiClient()
const svc = new SessionsService(ctx, api, fakeRemote())
const svc = new SessionRuntime(ctx, api, fakeRemote())
return { ctx, api, svc }
}

View File

@@ -1,5 +1,5 @@
/**
* SlotsService terminal-design account:
* SlotRegistry terminal-design account:
* built-in 'root', the three load-time throws (duplicate declaration /
* undeclared contribution / cross-scope store handle), the renderer installation
* contract (double install / not installed / non-root key), store instance
@@ -9,7 +9,7 @@ import { Context } from '@deepseek-ai/cordis'
import { describe, expect, it, vi } from 'vitest'
import type { FC } from 'react'
import type { SlotRendererHost } from '@deepseek-ai/dsh-client-ui-slots'
import { SlotsService } from '../src/client/slots.ts'
import { SlotRegistry } from '../src/client/slots.ts'
// Test-only slot keys (merged so the typed entries/spec faces accept them).
declare module '@deepseek-ai/dsh-client-ui-slots' {
@@ -36,13 +36,13 @@ interface ErasedService {
interface Bench {
ctx: Context
svc: SlotsService
svc: SlotRegistry
erased: ErasedService
}
async function boot(): Promise<Bench> {
const ctx = new Context()
const fiber = ctx.plugin(SlotsService)
const fiber = ctx.plugin(SlotRegistry)
await fiber
// Service accessor (ctx.get reads the reflect store, which Service-class
// plugins do not write; the accessor is the product path).

View File

@@ -1,9 +1,9 @@
import { Context } from '@deepseek-ai/cordis'
import { describe, expect, it, vi } from 'vitest'
import type { SessionId, WorkspaceId, WorkspaceView } from '@deepseek-ai/dsh-api-remotes/client'
import { SessionsService } from '../src/client/sessions/service.ts'
import { SessionRuntime } from '../src/client/sessions/service.ts'
import { WorkspaceManager } from '../src/client/workspaces/manager.ts'
import { DirectoryBrowseError, WorkspaceCreateError, WorkspacesService } from '../src/client/workspaces/service.ts'
import { DirectoryBrowseError, WorkspaceCreateError, WorkspaceRuntime } from '../src/client/workspaces/service.ts'
import { FakeApiClient, deferred, err, fakeRemote, ok } from './fake-api.client.ts'
const sid = (id: string): SessionId => id as SessionId
@@ -187,12 +187,12 @@ describe('WorkspaceManager', () => {
})
})
describe('WorkspacesService', () => {
describe('WorkspaceRuntime', () => {
it('feeds readiness and recent-Workspace targeting without changing Host order', async () => {
const ctx = new Context()
const api = new FakeApiClient()
const sessions = new SessionsService(ctx, api, fakeRemote())
const workspaces = new WorkspacesService(ctx, api, sessions)
const sessions = new SessionRuntime(ctx, api, fakeRemote())
const workspaces = new WorkspaceRuntime(ctx, api, sessions)
api.onWorkspaceList = () => Promise.resolve(ok({
items: [
workspace('stable-first', [], '2026-01-03T00:00:00.000Z'),
@@ -219,8 +219,8 @@ describe('WorkspacesService', () => {
it('connectWorkspace reuses the workspace-member blank session and creates otherwise', async () => {
const ctx = new Context()
const api = new FakeApiClient()
const sessions = new SessionsService(ctx, api, fakeRemote())
const workspaces = new WorkspacesService(ctx, api, sessions)
const sessions = new SessionRuntime(ctx, api, fakeRemote())
const workspaces = new WorkspaceRuntime(ctx, api, sessions)
api.onWorkspaceList = () => Promise.resolve(ok({
items: [workspace('alpha', [sid('s-blank')]), workspace('beta'), workspace('gamma')] as never[],
}))
@@ -278,8 +278,8 @@ describe('WorkspacesService', () => {
it('a rejected first prompt keeps the blank session eligible for connectWorkspace reuse', async () => {
const ctx = new Context()
const api = new FakeApiClient()
const sessions = new SessionsService(ctx, api, fakeRemote())
const workspaces = new WorkspacesService(ctx, api, sessions)
const sessions = new SessionRuntime(ctx, api, fakeRemote())
const workspaces = new WorkspaceRuntime(ctx, api, sessions)
api.onWorkspaceList = () => Promise.resolve(ok({ items: [workspace('alpha', [sid('s-blank')])] as never[] }))
api.onList = () => Promise.resolve(ok({
items: [{ sessionId: sid('s-blank'), updatedAt: 2, running: false, blank: true, cwd: '/w/alpha' }] as never[],
@@ -298,8 +298,8 @@ describe('WorkspacesService', () => {
it('returns created Workspaces and preserves Host business errors', async () => {
const ctx = new Context()
const api = new FakeApiClient()
const sessions = new SessionsService(ctx, api, fakeRemote())
const workspaces = new WorkspacesService(ctx, api, sessions)
const sessions = new SessionRuntime(ctx, api, fakeRemote())
const workspaces = new WorkspaceRuntime(ctx, api, sessions)
api.onWorkspaceCreate = () => Promise.resolve(ok({
workspace: { ...workspace('picked'), path: '/w/alpha', title: 'alpha' }, created: true,
}))
@@ -317,8 +317,8 @@ describe('WorkspacesService', () => {
it('passes native directory selection and cancellation through without local state', async () => {
const ctx = new Context()
const api = new FakeApiClient()
const sessions = new SessionsService(ctx, api, fakeRemote())
const workspaces = new WorkspacesService(ctx, api, sessions)
const sessions = new SessionRuntime(ctx, api, fakeRemote())
const workspaces = new WorkspaceRuntime(ctx, api, sessions)
api.onPickDirectory = () => Promise.resolve(ok({ path: '/w/alpha' }))
await expect(workspaces.pickDirectory()).resolves.toBe('/w/alpha')
api.onPickDirectory = () => Promise.resolve(ok({ path: null }))
@@ -331,7 +331,7 @@ describe('WorkspacesService', () => {
it('passes listings and creation through the browse wire, wrapping business failures', async () => {
const ctx = new Context()
const api = new FakeApiClient()
const workspaces = new WorkspacesService(ctx, api, new SessionsService(ctx, api, fakeRemote()))
const workspaces = new WorkspaceRuntime(ctx, api, new SessionRuntime(ctx, api, fakeRemote()))
const listing = { path: '/home/u', home: '/home/u', crumbs: [{ name: '/', path: '/', hidden: false }], entries: [{ name: 'p', path: '/home/u/p', hidden: false }], truncated: false }
api.onListDirectory = () => Promise.resolve(ok(listing))
await expect(workspaces.listDirectory()).resolves.toEqual(listing)
@@ -352,8 +352,8 @@ describe('WorkspacesService', () => {
it('opens a filesystem path through the host without local state', async () => {
const ctx = new Context()
const api = new FakeApiClient()
const sessions = new SessionsService(ctx, api, fakeRemote())
const workspaces = new WorkspacesService(ctx, api, sessions)
const sessions = new SessionRuntime(ctx, api, fakeRemote())
const workspaces = new WorkspaceRuntime(ctx, api, sessions)
await expect(workspaces.openPath('/w/alpha/a.ts')).resolves.toBeUndefined()
expect(api.callsOf('host.openPath')).toEqual([{ path: '/w/alpha/a.ts' }])
api.onOpenPath = () => Promise.resolve(err({ code: 'internal', message: 'boom', details: {} }))
@@ -363,8 +363,8 @@ describe('WorkspacesService', () => {
it('deletes a Workspace or preserves it when the Host rejects deletion', async () => {
const ctx = new Context()
const api = new FakeApiClient()
const sessions = new SessionsService(ctx, api, fakeRemote())
const workspaces = new WorkspacesService(ctx, api, sessions)
const sessions = new SessionRuntime(ctx, api, fakeRemote())
const workspaces = new WorkspaceRuntime(ctx, api, sessions)
api.onWorkspaceList = () => Promise.resolve(ok({ items: [workspace('alpha')] as never[] }))
await workspaces.refresh()
await expect(workspaces.delete(wid('alpha'))).resolves.toBeUndefined()
@@ -379,7 +379,7 @@ describe('WorkspacesService', () => {
it('moves a Workspace through the durable order RPC and surfaces Host rejection', async () => {
const ctx = new Context()
const api = new FakeApiClient()
const workspaces = new WorkspacesService(ctx, api, new SessionsService(ctx, api, fakeRemote()))
const workspaces = new WorkspaceRuntime(ctx, api, new SessionRuntime(ctx, api, fakeRemote()))
api.onWorkspaceList = () => Promise.resolve(ok({
items: [workspace('one'), workspace('two')] as never[],
}))
@@ -402,8 +402,8 @@ describe('WorkspacesService', () => {
it('targets New Session at explicit, current-session, then recent Workspaces and clears with none', async () => {
const ctx = new Context()
const api = new FakeApiClient()
const sessions = new SessionsService(ctx, api, fakeRemote())
const workspaces = new WorkspacesService(ctx, api, sessions)
const sessions = new SessionRuntime(ctx, api, fakeRemote())
const workspaces = new WorkspaceRuntime(ctx, api, sessions)
api.onWorkspaceList = () => Promise.resolve(ok({
items: [
workspace('current-home', [sid('current')]),
@@ -435,8 +435,8 @@ describe('WorkspacesService', () => {
const emptyCtx = new Context()
const emptyApi = new FakeApiClient()
const emptySessions = new SessionsService(emptyCtx, emptyApi, fakeRemote())
const emptyWorkspaces = new WorkspacesService(emptyCtx, emptyApi, emptySessions)
const emptySessions = new SessionRuntime(emptyCtx, emptyApi, fakeRemote())
const emptyWorkspaces = new WorkspaceRuntime(emptyCtx, emptyApi, emptySessions)
const clear = vi.spyOn(emptySessions, 'clear')
emptyWorkspaces.startSession()
expect(clear).toHaveBeenCalledOnce()
@@ -445,8 +445,8 @@ describe('WorkspacesService', () => {
it('archives a session, projects the set from the response, list, and frame, and clears only the current one', async () => {
const ctx = new Context()
const api = new FakeApiClient()
const sessions = new SessionsService(ctx, api, fakeRemote())
const workspaces = new WorkspacesService(ctx, api, sessions)
const sessions = new SessionRuntime(ctx, api, fakeRemote())
const workspaces = new WorkspaceRuntime(ctx, api, sessions)
api.onList = () => Promise.resolve(ok({
items: [
{ sessionId: sid('s-open'), updatedAt: 2, running: false, blank: false },
@@ -491,8 +491,8 @@ describe('WorkspacesService', () => {
it('clears a current archived by a remote frame and shields the set from a stale in-flight baseline', async () => {
const ctx = new Context()
const api = new FakeApiClient()
const sessions = new SessionsService(ctx, api, fakeRemote())
const workspaces = new WorkspacesService(ctx, api, sessions)
const sessions = new SessionRuntime(ctx, api, fakeRemote())
const workspaces = new WorkspaceRuntime(ctx, api, sessions)
api.onList = () => Promise.resolve(ok({
items: [{ sessionId: sid('s-open'), updatedAt: 1, running: false, blank: false }],
}) as never)
@@ -525,8 +525,8 @@ describe('startInitialSelection', () => {
function bench() {
const ctx = new Context()
const api = new FakeApiClient()
const sessions = new SessionsService(ctx, api, fakeRemote())
const workspaces = new WorkspacesService(ctx, api, sessions)
const sessions = new SessionRuntime(ctx, api, fakeRemote())
const workspaces = new WorkspaceRuntime(ctx, api, sessions)
return { api, sessions, workspaces }
}