Merge remote-tracking branch 'origin/master' into mergebot/pr1005

# Conflicts:
#	docs/module-graph.md
#	packages/client/ui-conversation/README.i18n.yaml
#	packages/client/ui-conversation/README.md
#	packages/client/ui-conversation/README.zh.md
#	packages/client/ui-conversation/src/client/apply.ts
#	packages/client/ui-conversation/src/client/skeleton/InputBar.tsx
#	packages/client/ui-conversation/tests/input-bar.spec.tsx
#	packages/client/ui-conversation/tests/input-matrix.spec.tsx
#	packages/client/ui-plan/README.i18n.yaml
#	packages/client/ui-plan/README.md
#	packages/client/ui-plan/README.zh.md
#	packages/client/ui-plan/src/client/PlanModeControl.tsx
#	packages/client/ui-plan/src/client/index.ts
#	packages/client/ui-plan/tests/browser-plugin.spec.ts
#	packages/client/ui-plan/tests/plan-mode-control.spec.tsx
This commit is contained in:
imccyu
2026-07-31 03:47:24 +08:00
238 changed files with 3273 additions and 1304 deletions

View File

@@ -9,6 +9,7 @@ import { Context } from 'cordis'
import { describe, expect, it, vi } from 'vitest'
import { SlotsService } from '@deepseek-ai/dsh-client-runtime/client'
import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'
import { LocaleService } from '@deepseek-ai/dsh-client-locale/client'
import { PlanChip } from '../src/client/PlanModeControl.tsx'
import type { PlanChipInjected } from '../src/client/index.ts'
import { apply, inject } from '../src/client/index.ts'
@@ -28,12 +29,13 @@ async function bench() {
Promise.resolve({ result: { ok: true as const, value: { matched: true as const, commandId: 'c1' } } }))
ctx.provide('connection', { api: { commands: { execute } } })
ctx.provide('conversation', {})
ctx.provide('locale', new LocaleService(ctx))
return { ctx, slots, execute }
}
describe('ui-plan browser apply', () => {
it('declares every service it binds', () => {
expect(inject).toEqual(['slots', 'connection', 'conversation'])
expect(inject).toEqual(['slots', 'connection', 'conversation', 'locale'])
})
it('node-half apply is an intentional no-op', () => {
@@ -45,6 +47,7 @@ describe('ui-plan browser apply', () => {
await ctx.plugin(SlotsService).await()
ctx.provide('connection', {})
ctx.provide('conversation', {})
ctx.provide('locale', new LocaleService(ctx))
await expect(ctx.plugin({ inject: [...inject], apply }))
.rejects.toThrow(/slot "conversation.input.plan" is not declared/)
})
@@ -64,13 +67,13 @@ describe('ui-plan browser apply', () => {
b.execute.mockResolvedValueOnce({
result: { ok: false as const, error: { code: 'session-not-found', message: 'gone', details: {} } },
} as never)
await expect(injected.exitPlanMode()).resolves.toBe('gone(session-not-found)')
await expect(injected.exitPlanMode()).resolves.toBe('gone (session-not-found)')
// Unmatched admission (plan-mode not composed host-side) is also a failure line.
b.execute.mockResolvedValueOnce({
result: { ok: true as const, value: { matched: false as const } },
} as never)
await expect(injected.exitPlanMode()).resolves.toBe('未知命令:/plan off')
await expect(injected.exitPlanMode()).resolves.toBe('unknown command: /plan off')
await fiber.dispose()
expect(b.slots.entries('conversation.input.plan')).toHaveLength(0)

View File

@@ -11,9 +11,15 @@ import { createSnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
import { bindSnapshotSelector } from '@deepseek-ai/dsh-client-web-react'
import type { PlanProjection } from '@deepseek-ai/dsh-plan-mode/client'
import { PlanChip, type PlanChipProps } from '../src/client/PlanModeControl.tsx'
import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime'
import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts'
import { zh } from '../src/client/locales.ts'
afterEach(cleanup)
// The framework-injected t seat, stubbed over the zh dictionaries (the default locale).
const t: PlanChipProps['t'] = makeTranslate(zh, commonZh)
function setup(
plan: PlanProjection | undefined,
exitPlanMode = vi.fn(() => Promise.resolve<string | null>(null)),
@@ -22,12 +28,12 @@ function setup(
const store = createSnapshotStore<{ value: PlanProjection | undefined }>({ value: plan })
const useProjection = (_key: string, selector?: (v: unknown) => unknown) =>
bindSnapshotSelector(store)(s => (selector ?? (v => v))(s.value))
const props = { useProjection, locked, exitPlanMode } as unknown as PlanChipProps
const props = { useProjection, locked, exitPlanMode, t } as unknown as PlanChipProps
const view = render(<PlanChip {...props} />)
return { store, exitPlanMode, view }
}
const chip = () => screen.getByRole('button', { name: 'Plan mode on, press to turn off' })
const chip = () => screen.getByRole('button', { name: 'plan mode 已开启,按下关闭' })
describe('PlanChip', () => {
it('renders nothing for an absent capability or a default-mode target', () => {
@@ -60,7 +66,7 @@ describe('PlanChip', () => {
resolve(null)
store.set({ value: { active: true, pending: true } })
await waitFor(() => {
expect(screen.queryByRole('button', { name: 'Plan mode on, press to turn off' })).toBeNull()
expect(screen.queryByRole('button', { name: 'plan mode 已开启,按下关闭' })).toBeNull()
})
})
@@ -76,7 +82,7 @@ describe('PlanChip', () => {
.mockRejectedValueOnce('socket closed')
setup({ active: true, pending: false }, exitPlanMode)
fireEvent.click(chip())
expect((await screen.findByText('退出 plan mode 失败')).getAttribute('title')).toBe('host said no')
expect((await screen.findByText('failed to exit plan mode')).getAttribute('title')).toBe('host said no')
expect(chip()).toBeTruthy()
fireEvent.click(chip())