refactor(packages): dissolve ui/ and rename sdk/ to scaffold/

git mv per the regrouping RFC: the five human-collaboration seams and
tui join packages/interaction/, app-boot becomes packages/boot/, and
jsonrpc joins the renamed scaffold/ (formerly sdk/) as its server half
beside client/protocol/create-sdk/helper/scripts/telemetry, whose
folders drop the legacy sdk- prefix. Three new group README triplets
replace the ui/ and sdk/ ones; tsconfig references/paths/globs,
knip keys, vitest globs, gate scripts, catalogs, docs, and the
lockfile follow. Adds the four settled FIXME rename markers
(dsh-sdk-server, dsh-sdk-telemetry, dsh-sdk-helper, dsh-sdk-scripts).

The scaffold folders diverge from their npm names until those renames
land, so tsconfig.base.json maps the three affected names explicitly
beside the group wildcard. Also repairs two pre-existing stale-path
classes the strengthened sweep surfaced: docs/web-styling.md's retired
web-ui host package and type-model spec fixture-literal joins.

app-boot's three Loader-composition specs time out at the default 5s
under full-suite parallel load on this filesystem (pre-existing;
pass isolated with --testTimeout=30000); interaction/scaffold/boot
suites otherwise green (687 passed).
This commit is contained in:
Tianyi Cui
2026-07-30 03:13:49 +08:00
parent 7e445c3a67
commit 3fc35c91ff
351 changed files with 368 additions and 311 deletions

View File

@@ -0,0 +1,53 @@
import { describe, expect, it } from 'vitest'
import { Context, Service } from 'cordis'
import SessionStore, { type Session, type SessionEvent } from '@deepseek-ai/dsh-session'
import * as PermissionInvariant from '@deepseek-ai/dsh-permission/invariant'
import InvariantService from '@deepseek-ai/dsh-invariants'
class PermissionProbe extends Service {
readonly names = ['safe', 'trusted']
constructor(ctx: Context) {
super(ctx, 'permission')
}
}
async function setup(): Promise<Context> {
const ctx = new Context()
await ctx.plugin(SessionStore)
await ctx.plugin(PermissionProbe)
await ctx.plugin(InvariantService, { enabled: true })
await ctx.plugin(PermissionInvariant)
return ctx
}
function presetEvent(preset: string): SessionEvent {
return { type: 'permission/preset', seq: 0, time: 0, data: { preset } }
}
describe('permission invariants', () => {
it('accepts configured preset events and ignores other session data', async () => {
const ctx = await setup()
expect(() => { ctx.emit('session/event', {} as Session, presetEvent('safe')) }).not.toThrow()
expect(() => { ctx.emit('session/event', {} as Session, {
type: 'turn/end', seq: 0, time: 0, data: {},
} as SessionEvent) }).not.toThrow()
expect(() => { ctx.emit('tools/change') }).not.toThrow()
})
it('rejects a durable preset that the active table cannot resolve', async () => {
const ctx = await setup()
expect(() => { ctx.emit('session/event', {} as Session, presetEvent('missing')) })
.toThrow(/unknown preset "missing"/)
})
it('rejects an unknown preset already present on late registration', async () => {
const ctx = new Context()
await ctx.plugin(SessionStore)
await ctx.plugin(PermissionProbe)
ctx.sessions.create().append('permission/preset', { preset: 'missing' })
await ctx.plugin(InvariantService, { enabled: true })
await expect(ctx.plugin(PermissionInvariant).then(() => undefined)).rejects.toThrow(/unknown preset "missing"/)
})
})

View File

@@ -0,0 +1,301 @@
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import SessionStore, { Session, SessionId } from '@deepseek-ai/dsh-session'
import type { SandboxMode } from '@deepseek-ai/dsh-sandbox'
import type { ApprovalPolicy } from '@deepseek-ai/dsh-user-approval'
import PermissionService, {
CUSTOM_PRESET, effectivePermissionPreset, PERMISSION_SETTINGS_NAMESPACE,
} from '@deepseek-ai/dsh-permission'
import type { Config } from '@deepseek-ai/dsh-permission'
import { Settings } from '@deepseek-ai/dsh-settings'
import type { SettingsNamespace } from '@deepseek-ai/dsh-settings'
/** Writable memory provider for the permission/settings lifecycle specs. */
class MemorySettings extends Settings {
readonly doc: Record<string, unknown> = {}
readonly writable = true
protected load(): Promise<Record<string, unknown>> {
return Promise.resolve(structuredClone(this.doc))
}
protected persist(ns: SettingsNamespace, section: Record<string, unknown>): Promise<void> {
this.doc[ns] = structuredClone(section)
return Promise.resolve()
}
}
async function mounted(options: {
config?: Config
bashDefault?: SandboxMode | undefined
approvalDefault?: ApprovalPolicy | undefined
} = {}): Promise<Context> {
const ctx = new Context()
await ctx.plugin(SessionStore)
ctx.provide('bash', {
sandboxMode: 'bashDefault' in options ? options.bashDefault : 'workspace-write',
resolve() { throw new Error('permission tests do not execute bash') },
run() { throw new Error('permission tests do not execute bash') },
start() { throw new Error('permission tests do not execute bash') },
})
ctx.provide('approval', { config: { policy: 'approvalDefault' in options ? options.approvalDefault : 'ask' } })
await ctx.plugin(PermissionService, options.config ?? {})
return ctx
}
function freshSession(id: string): Session {
return Session.create(SessionId(id))
}
async function mountedStore(options: { approvalDefault?: ApprovalPolicy | undefined } = {}): Promise<Context> {
const ctx = new Context()
await ctx.plugin(SessionStore)
await ctx.plugin(MemorySettings)
ctx.provide('bash', {
sandboxMode: 'workspace-write',
resolve() { throw new Error('permission tests do not execute bash') },
run() { throw new Error('permission tests do not execute bash') },
start() { throw new Error('permission tests do not execute bash') },
})
ctx.provide('approval', {
config: { policy: 'approvalDefault' in options ? options.approvalDefault : 'ask' },
})
await ctx.plugin(PermissionService, {})
return ctx
}
describe('effectivePermissionPreset', () => {
it('folds to the last event, or undefined without one', () => {
const session = freshSession('sess-fold')
expect(effectivePermissionPreset(session.events)).toBeUndefined()
session.append('permission/preset', { preset: 'danger-full-access' })
session.append('permission/preset', { preset: 'workspace-write' })
expect(effectivePermissionPreset(session.events)).toBe('workspace-write')
// The backward scan steps over non-preset events to the latest selection.
session.append('sandbox/mode', { mode: 'read-only' })
expect(effectivePermissionPreset(session.events)).toBe('workspace-write')
})
})
describe('PermissionService', () => {
it('advertises the preset table in declaration order and resolves bundles', async () => {
const ctx = await mounted()
expect(ctx.permission.names).toEqual(['workspace-write', 'danger-full-access'])
expect(ctx.permission.resolve('danger-full-access')).toMatchObject({ sandbox: 'danger-full-access', approval: 'never' })
expect(() => ctx.permission.resolve('plan')).toThrow(/unknown preset "plan"/)
})
it('current() derives from the effective knobs: composition defaults hit workspace-write, a switch hits its preset', async () => {
const ctx = await mounted()
const session = freshSession('sess-current')
expect(ctx.permission.current(session.events)).toBe('workspace-write')
ctx.permission.set(session, 'danger-full-access')
expect(ctx.permission.current(session.events)).toBe('danger-full-access')
})
it('a knob state matching no table entry derives custom — a state, not an error', async () => {
const ctx = await mounted()
const session = freshSession('sess-custom')
session.append('sandbox/mode', { mode: 'read-only' })
expect(ctx.permission.current(session.events)).toBe(CUSTOM_PRESET)
ctx.permission.set(session, 'danger-full-access')
expect(ctx.permission.current(session.events)).toBe('danger-full-access')
expect(() => ctx.permission.resolve(CUSTOM_PRESET)).toThrow(/unknown preset/)
})
it('composition defaults outside the table still derive custom when an explicit new-session default is configured', async () => {
const ctx = await mounted({
approvalDefault: 'never',
config: { defaultPreset: 'workspace-write' },
})
const session = freshSession('sess-defaults-custom')
expect(ctx.permission.current(session.events)).toBe(CUSTOM_PRESET)
})
it('the fold breaks bundle ties; a stale fold no longer matching falls back to table order', async () => {
const ctx = await mounted({ config: { presets: {
'workspace-write': { sandbox: 'workspace-write', approval: 'ask' },
agentish: { sandbox: 'workspace-write', approval: 'ask' },
'danger-full-access': { sandbox: 'danger-full-access', approval: 'never' },
} } })
const session = freshSession('sess-tie')
ctx.permission.set(session, 'agentish')
expect(ctx.permission.current(session.events)).toBe('agentish')
session.append('approval/policy', { policy: 'never' })
session.append('sandbox/mode', { mode: 'danger-full-access' })
expect(ctx.permission.current(session.events)).toBe('danger-full-access')
})
it('set() writes through: one preset event plus both knob events', async () => {
const ctx = await mounted()
const session = freshSession('sess-set')
ctx.permission.set(session, 'danger-full-access')
expect(session.events.map(e => [e.type, e.data])).toEqual([
['permission/preset', { preset: 'danger-full-access' }],
['sandbox/mode', { mode: 'danger-full-access' }],
['approval/policy', { policy: 'never' }],
])
})
it('set() to the current preset is a no-op when the knobs already match (clicks are not switches)', async () => {
const ctx = await mounted()
const session = freshSession('sess-noop')
ctx.permission.set(session, 'workspace-write')
expect(session.events).toHaveLength(0)
})
it('re-asserting a preset from a drifted (custom) state re-records the choice and repairs the knob', async () => {
const ctx = await mounted()
const session = freshSession('sess-drift')
ctx.permission.set(session, 'danger-full-access')
// Re-selecting from a drifted state records the choice and repairs only
// the changed knob.
session.append('sandbox/mode', { mode: 'read-only' })
ctx.permission.set(session, 'danger-full-access')
const tail = session.events.slice(4)
expect(tail.map(e => [e.type, e.data])).toEqual([
['permission/preset', { preset: 'danger-full-access' }],
['sandbox/mode', { mode: 'danger-full-access' }],
])
})
it('rejects composition over a non-confining executor at load', async () => {
await expect(mounted({ bashDefault: undefined }))
.rejects.toThrow(/does not confine/)
})
it('optionOf() presents shipped labels/descriptions, falls back to the raw key, and fixes custom', async () => {
const ctx = await mounted()
expect(ctx.permission.optionOf('danger-full-access')).toEqual({ value: 'danger-full-access', name: 'danger-full-access', description: 'Full file access without approval prompts.' })
expect(ctx.permission.optionOf('custom')).toEqual({ value: 'custom', name: 'Custom', description: 'Current sandbox and approval settings do not match a preset.' })
const bare = await mounted({ config: { presets: { plain: { sandbox: 'workspace-write', approval: 'ask' } } } })
expect(bare.permission.optionOf('plain')).toEqual({ value: 'plain', name: 'plain' })
expect(() => ctx.permission.optionOf('plan')).toThrow(/unknown preset/)
})
it('rejects a table entry named custom (reserved for the derived state)', async () => {
await expect(mounted({ config: { presets: { custom: { sandbox: 'read-only', approval: 'ask' } } } }))
.rejects.toThrow(/reserved for the derived not-a-preset state/)
})
it('requires an explicit default when composition defaults match no preset', async () => {
await expect(mounted({ approvalDefault: 'never' }))
.rejects.toThrow(/configure defaultPreset explicitly/)
})
it('reads a schema-less approval stand-in as the ask default', async () => {
const ctx = await mounted({ approvalDefault: undefined })
const session = freshSession('sess-standin')
ctx.permission.set(session, 'workspace-write')
expect(session.events).toHaveLength(0)
expect(ctx.permission.current(session.events)).toBe('workspace-write')
})
})
describe('new-session default', () => {
it('pins the current setting into each new session without changing earlier sessions', async () => {
const ctx = await mountedStore()
const first = ctx.sessions.create(SessionId('first'))
expect(first.events.map(event => [event.type, event.data])).toEqual([
['permission/preset', { preset: 'workspace-write' }],
['sandbox/mode', { mode: 'workspace-write' }],
['approval/policy', { policy: 'ask' }],
])
await ctx.settings.update(PERMISSION_SETTINGS_NAMESPACE, {
defaultPreset: 'danger-full-access',
})
expect(ctx.permission.defaultPreset).toBe('danger-full-access')
const second = ctx.sessions.create(SessionId('second'))
expect(ctx.permission.current(first.events)).toBe('workspace-write')
expect(ctx.permission.current(second.events)).toBe('danger-full-access')
expect(second.events.map(event => event.type)).toEqual([
'permission/preset', 'sandbox/mode', 'approval/policy',
])
})
it('preserves a seeded legacy session instead of applying the latest user default', async () => {
const ctx = await mountedStore()
await ctx.settings.update(PERMISSION_SETTINGS_NAMESPACE, {
defaultPreset: 'danger-full-access',
})
const legacy = freshSession('legacy-source')
legacy.append('turn/start', { turn: 1 })
legacy.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
const resumed = ctx.sessions.create(SessionId('legacy-resumed'), { seed: legacy.events })
expect(ctx.permission.current(resumed.events)).toBe('workspace-write')
expect(resumed.events.slice(-3).map(event => event.type)).toEqual([
'permission/preset', 'sandbox/mode', 'approval/policy',
])
})
it('preserves composition defaults when an empty stored session resumes', async () => {
const ctx = await mountedStore()
await ctx.settings.update(PERMISSION_SETTINGS_NAMESPACE, {
defaultPreset: 'danger-full-access',
})
const resumed = ctx.sessions.create(SessionId('empty-resumed'), { seed: [] })
expect(ctx.permission.current(resumed.events)).toBe('workspace-write')
expect(resumed.events.map(event => event.type)).toEqual([
'session/end-seed', 'permission/preset', 'sandbox/mode', 'approval/policy',
])
})
it('pins sessions that already exist when the service remounts', async () => {
const ctx = new Context()
await ctx.plugin(SessionStore)
ctx.provide('bash', {
sandboxMode: 'workspace-write',
resolve() { throw new Error('permission tests do not execute bash') },
run() { throw new Error('permission tests do not execute bash') },
start() { throw new Error('permission tests do not execute bash') },
})
ctx.provide('approval', { config: { policy: 'ask' } })
const existing = ctx.sessions.create(SessionId('existing-before-permission'))
expect(existing.events).toEqual([])
await ctx.plugin(PermissionService, {})
expect(existing.events.map(event => event.type)).toEqual([
'permission/preset', 'sandbox/mode', 'approval/policy',
])
expect(ctx.permission.current(existing.events)).toBe('workspace-write')
})
it('fills only missing legacy facts and preserves an unmatched seeded combination', async () => {
const ctx = await mountedStore()
const partial = freshSession('partial-source')
partial.append('sandbox/mode', { mode: 'workspace-write' })
partial.append('approval/policy', { policy: 'ask' })
const resumed = ctx.sessions.create(SessionId('partial-resumed'), { seed: partial.events })
expect(resumed.events.at(-1)).toMatchObject({
type: 'permission/preset',
data: { preset: 'workspace-write' },
})
const custom = freshSession('custom-source')
custom.append('sandbox/mode', { mode: 'read-only' })
custom.append('approval/policy', { policy: 'never' })
const unmatched = ctx.sessions.create(SessionId('custom-resumed'), { seed: custom.events })
expect(ctx.permission.current(unmatched.events)).toBe(CUSTOM_PRESET)
expect(unmatched.events.at(-1)?.type).toBe('session/end-seed')
})
it('materializes ask when a legacy seed and approval stand-in omit the policy', async () => {
const ctx = await mountedStore({ approvalDefault: undefined })
const partial = freshSession('approval-fallback-source')
partial.append('sandbox/mode', { mode: 'workspace-write' })
const resumed = ctx.sessions.create(SessionId('approval-fallback-resumed'), { seed: partial.events })
expect(resumed.events.at(-1)).toMatchObject({
type: 'approval/policy',
data: { policy: 'ask' },
})
})
it('rejects a stored default outside the configured preset table', async () => {
const ctx = await mountedStore()
await expect(ctx.settings.update(PERMISSION_SETTINGS_NAMESPACE, {
defaultPreset: 'missing',
})).rejects.toThrow()
expect(ctx.permission.defaultPreset).toBe('workspace-write')
})
})

View File

@@ -0,0 +1,133 @@
/**
* The `permissions` projection unit and the `/permission` command: mounting
* the permission service beside the projection registry serves the whole
* select (table options + effective current value, `custom` appended exactly
* while derived) folded from the three knob events over the composition
* defaults; the command child registers `/permission` whose handler switches
* through `permission.set` (bare invocation reports, unknown names error);
* compositions without either registry are unaffected; unmounting the
* service removes the key (HMR safety).
*/
import { describe, expect, it, vi } from 'vitest'
import { Context } from 'cordis'
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
import type { Session } from '@deepseek-ai/dsh-session'
import type { Agent } from '@deepseek-ai/dsh-agent'
import { createScope } from '@deepseek-ai/dsh-scope'
import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
import CommandService from '@deepseek-ai/dsh-commands'
import PermissionService from '@deepseek-ai/dsh-permission'
import type { Config } from '@deepseek-ai/dsh-permission'
import ApprovalService from '@deepseek-ai/dsh-user-approval'
async function harness(options: { withPermission?: boolean; config?: Config } = {}): Promise<{ ctx: Context; session: Session }> {
const ctx = new Context()
await ctx.plugin(SessionStore)
await ctx.plugin(SessionProjectionRegistry)
await ctx.plugin(CommandService)
ctx.provide('bash', {
sandboxMode: 'workspace-write',
resolve() { throw new Error('permission tests do not execute bash') },
run() { throw new Error('permission tests do not execute bash') },
start() { throw new Error('permission tests do not execute bash') },
})
await ctx.plugin(ApprovalService)
if (options.withPermission !== false) await ctx.plugin(PermissionService, options.config ?? {})
return { ctx, session: ctx.sessions.create(SessionId('perm-projected')) }
}
/** Mint a scoped agent over a live session (the command executor's addressing shape). */
async function agentFor(ctx: Context, session: Session) {
const inject = vi.fn<Agent['inject']>()
const agent = { id: session.id, session, inject } as unknown as Agent
await ctx.plugin(Object.assign((inner: Context) => { createScope(inner, agent) }, { inject: ['commands'] }))
return { agent, inject }
}
describe('permissions projection unit', () => {
it('serves the pinned new-session default select', async () => {
const { ctx, session } = await harness()
const value = ctx.sessionProjections.snapshot(session).values.permissions
expect(value).toMatchObject({ currentValue: 'workspace-write' })
expect(value?.options.map(option => option.value)).toEqual(['workspace-write', 'danger-full-access'])
})
it('folds the knob events and notifies the change feed per knob append', async () => {
const { ctx, session } = await harness()
const changes: { key: string; value: unknown; seq: number }[] = []
ctx.sessionProjections.onChanged((_session, key, value, seq) => {
changes.push({ key, value, seq })
})
ctx.permission.set(session, 'danger-full-access')
// set() appends preset + sandbox/mode + approval/policy: three knob transitions.
expect(changes).toHaveLength(3)
expect(changes.at(-1)).toMatchObject({ key: 'permissions', value: { currentValue: 'danger-full-access' } })
// Unrelated event: same-reference apply, no notification.
session.append('turn/start', { turn: 1 })
expect(changes).toHaveLength(3)
})
it('appends custom as a current-only option when the knobs match no preset', async () => {
const { ctx, session } = await harness()
session.append('sandbox/mode', { mode: 'read-only' })
const value = ctx.sessionProjections.snapshot(session).values.permissions
expect(value?.currentValue).toBe('custom')
expect(value?.options.at(-1)).toMatchObject({ value: 'custom', name: 'Custom' })
})
it('has no permissions key without the service, and drops it on unload (HMR safety)', async () => {
const { ctx, session } = await harness({ withPermission: false })
expect('permissions' in ctx.sessionProjections.snapshot(session).values).toBe(false)
const fiber = await ctx.plugin(PermissionService, {})
expect(ctx.sessionProjections.snapshot(session).values.permissions).toMatchObject({ currentValue: 'workspace-write' })
await fiber.dispose()
expect('permissions' in ctx.sessionProjections.snapshot(session).values).toBe(false)
})
})
describe('/permission command', () => {
it('switches through permission.set and logs the lifecycle pair', async () => {
const { ctx, session } = await harness()
const { agent, inject } = await agentFor(ctx, session)
const execution = await ctx.commands.execute(agent, '/permission danger-full-access', new AbortController().signal)
expect(execution?.result).toEqual({ kind: 'success', text: 'preset danger-full-access' })
expect(ctx.permission.current(session.events)).toBe('danger-full-access')
expect(inject.mock.calls[0]?.[0]).toMatchObject({
content: [{
type: 'text',
text: 'The approval policy changed from "ask" to "never" (changed by the user).',
}],
})
const run = session.events.find(event => event.type === 'command/run')
expect(run?.data).toMatchObject({ name: 'permission', args: ' danger-full-access' })
})
it('reports the current preset and the table on bare invocation', async () => {
const { ctx, session } = await harness()
const { agent } = await agentFor(ctx, session)
const execution = await ctx.commands.execute(agent, '/permission', new AbortController().signal)
expect(execution?.result).toEqual({
kind: 'success',
text: 'current preset workspace-write (available: workspace-write, danger-full-access)',
})
expect(session.events.filter(event => event.type === 'permission/preset')).toHaveLength(1)
})
it('rejects an unknown preset without touching the log', async () => {
const { ctx, session } = await harness()
const { agent } = await agentFor(ctx, session)
const before = session.events.filter(event =>
event.type !== 'command/run' && event.type !== 'command/done')
const execution = await ctx.commands.execute(agent, '/permission yolo', new AbortController().signal)
// The error text carries the same no-self-labelling rule as the success
// texts: `permission · unknown preset "yolo" (…)`, not `unknown permission
// preset`, which the row's own title already says.
expect(execution?.result).toEqual({
kind: 'error',
text: 'unknown preset "yolo" (available: workspace-write, danger-full-access)',
})
expect(session.events.filter(event =>
event.type !== 'command/run' && event.type !== 'command/done')).toEqual(before)
})
})