Merge remote-tracking branch 'origin/master' into feat/web-message-feedback-ui
Adapt to two contract changes master introduced: - The generated Remote face now wraps every business result in RemoteResult, folding carrier failures into an ok:false branch instead of rejecting. The controller reads that envelope at its three call sites and maps a carrier failure onto the same settled shape the controls already render; three specs cover the new branch. - Client packages split their tsconfig into host and client halves, and the host aggregate now compiles any test not named *.client.spec.*. Rename this package's specs to the client convention and drop the ../connection project reference, which pointed at a solution file that no longer carries the client sources. Keep master's mount loop with its rollback-on-failure in api-remotes and add messageFeedbackRemote to it.
This commit is contained in:
@@ -32,7 +32,7 @@
|
||||
"dsh": {
|
||||
"client": {
|
||||
"inject": [
|
||||
"@deepseek-ai/dsh-client-connection",
|
||||
"@deepseek-ai/dsh-api-remotes",
|
||||
"@deepseek-ai/dsh-client-locale",
|
||||
"@deepseek-ai/dsh-client-ui-conversation"
|
||||
],
|
||||
@@ -45,7 +45,7 @@
|
||||
},
|
||||
"license": "BSD-3-Clause",
|
||||
"peerDependencies": {
|
||||
"@deepseek-ai/dsh-client-connection": "workspace:^",
|
||||
"@deepseek-ai/dsh-api-remotes": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-locale": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-runtime": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-conversation": "workspace:^",
|
||||
@@ -57,7 +57,7 @@
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-client-connection": "workspace:^",
|
||||
"@deepseek-ai/dsh-api-remotes": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-locale": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-runtime": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-test-runtime": "workspace:^",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* projection pair through the standard-kit `useProjection`; zero client-side
|
||||
* plan state.
|
||||
*/
|
||||
import type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client'
|
||||
import type {} from '@deepseek-ai/dsh-api-remotes/client'
|
||||
import type { ClientContext, SessionId } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
// Type-only: pulls the ui-conversation SlotMap merge (the input.plan seat).
|
||||
import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
|
||||
@@ -39,8 +39,8 @@ export interface PlanChipInjected {
|
||||
exitPlanMode: () => Promise<string | null>
|
||||
}
|
||||
|
||||
/** Required services: the seat's slot registry, transport, and locale registry. */
|
||||
export const inject = ['slots', 'connection', 'locale']
|
||||
/** Required services: the seat's slot registry, commands Remote, and locale registry. */
|
||||
export const inject = ['slots', 'remote', 'remote.commands', 'locale']
|
||||
|
||||
/**
|
||||
* Client plugin body: register the plan chip over the command channel.
|
||||
@@ -55,10 +55,9 @@ export function apply(ctx: ClientContext): void {
|
||||
inject: (sessionId: SessionId): PlanChipInjected => ({
|
||||
// Failure strings stay English (error-surface policy: not localized).
|
||||
exitPlanMode: async () => {
|
||||
const connection = ctx.get('connection') as ConnectionHandle
|
||||
const { result } = await connection.api.commands.execute({ sessionId, line: '/plan off' })
|
||||
const result = await ctx.remote.commands.execute(sessionId, '/plan off')
|
||||
if (!result.ok) return `${result.error.message} (${result.error.code})`
|
||||
if (!result.value.matched) return 'unknown command: /plan off'
|
||||
if (result.value === undefined) return 'unknown command: /plan off'
|
||||
return null
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -25,16 +25,18 @@ async function bench() {
|
||||
name: 'root',
|
||||
children: { 'conversation.input.plan': { kind: 'single', scope: 'session' } },
|
||||
} as never, () => null)
|
||||
const execute = vi.fn((_payload: { sessionId: SessionId; line: string }) =>
|
||||
Promise.resolve({ result: { ok: true as const, value: { matched: true as const, commandId: 'c1' } } }))
|
||||
ctx.provide('connection', { api: { commands: { execute } } })
|
||||
const execute = vi.fn((_sessionId: SessionId, _line: string) =>
|
||||
Promise.resolve({ ok: true, value: { commandId: 'c1', result: { kind: 'success' as const } } }))
|
||||
const commandsRemote = { execute }
|
||||
ctx.provide('remote', { commands: commandsRemote })
|
||||
ctx.provide('remote.commands', commandsRemote)
|
||||
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', 'locale'])
|
||||
expect(inject).toEqual(['slots', 'remote', 'remote.commands', 'locale'])
|
||||
})
|
||||
|
||||
it('node-half apply is an intentional no-op', () => {
|
||||
@@ -44,7 +46,8 @@ describe('ui-plan browser apply', () => {
|
||||
it('waits until conversation declares the plan seat', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SlotsService).await()
|
||||
ctx.provide('connection', {})
|
||||
ctx.provide('remote', { commands: {} })
|
||||
ctx.provide('remote.commands', {})
|
||||
ctx.provide('locale', new LocaleService(ctx))
|
||||
const fiber = ctx.plugin({ inject: [...inject], apply })
|
||||
await fiber.await()
|
||||
@@ -65,18 +68,18 @@ describe('ui-plan browser apply', () => {
|
||||
const injected = (entry.inject as unknown as (id: SessionId) => PlanChipInjected)(SID)
|
||||
|
||||
await expect(injected.exitPlanMode()).resolves.toBeNull()
|
||||
expect(b.execute).toHaveBeenLastCalledWith({ sessionId: SID, line: '/plan off' })
|
||||
expect(b.execute).toHaveBeenLastCalledWith(SID, '/plan off')
|
||||
|
||||
// Business failure folds to the composer-visible line.
|
||||
// Business failure folds to the composer-visible line: the generated method
|
||||
// reports the RPC failure in its error branch.
|
||||
b.execute.mockResolvedValueOnce({
|
||||
result: { ok: false as const, error: { code: 'session-not-found', message: 'gone', details: {} } },
|
||||
ok: false,
|
||||
error: { code: 'session-not-found', message: 'gone', details: {} },
|
||||
} as never)
|
||||
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)
|
||||
b.execute.mockResolvedValueOnce({ ok: true, value: undefined } as never)
|
||||
await expect(injected.exitPlanMode()).resolves.toBe('unknown command: /plan off')
|
||||
|
||||
await fiber.dispose()
|
||||
@@ -8,15 +8,15 @@
|
||||
"src"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../api/remotes/tsconfig.client.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../vendor/cordis"
|
||||
},
|
||||
{
|
||||
"path": "../runtime"
|
||||
},
|
||||
{
|
||||
"path": "../connection"
|
||||
},
|
||||
{
|
||||
"path": "../locale"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user