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:
Chinesezjc
2026-08-12 10:43:23 +08:00
parent 47f254a252
commit b462d5fd69
507 changed files with 3130 additions and 2238 deletions
@@ -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()