feat(web): project plan mode through host API

This commit is contained in:
fz
2026-07-24 12:09:04 +08:00
parent bc7a89b81f
commit bc63b5fe00
34 changed files with 515 additions and 16 deletions

View File

@@ -2,7 +2,7 @@
// data source on a real clock; behavior tests need per-case responses and
// deferred-controlled timing). Streams are hand pumps: pushMux/pushHost.
import type {
HostFrame, IApiClient, MuxFrame, RpcRequest, RpcResponse, SessionId,
HostFrame, IApiClient, MuxFrame, PlanModeState, RpcRequest, RpcResponse, SessionId,
} from '../src/client/api.ts'
import { RpcId } from '../src/client/api.ts'
@@ -49,6 +49,10 @@ export class FakeApiClient implements IApiClient {
onPrompt: (payload: unknown) => Promise<RpcResponse<{ accepted: true }>> = () => Promise.resolve(ok({ accepted: true as const }))
onCancel: (payload: unknown) => Promise<RpcResponse<{ accepted: true }>> = () => Promise.resolve(ok({ accepted: true as const }))
onPlanMode: (payload: unknown) => Promise<RpcResponse<PlanModeState | null>> =
() => Promise.resolve(ok(null))
onSetPlanMode: (payload: unknown) => Promise<RpcResponse<PlanModeState | null>> =
() => Promise.resolve(ok(null))
onDescribe: (payload: unknown) => Promise<RpcResponse<{ version: string; cwd: string; attachedSessions: number }>> =
() => Promise.resolve(ok({ version: '0-fake', cwd: '/f', attachedSessions: 0 }))
@@ -65,6 +69,8 @@ export class FakeApiClient implements IApiClient {
this.record('session.history', payload, this.onHistory(payload)),
prompt: (payload: unknown) => this.record('session.prompt', payload, this.onPrompt(payload)),
cancel: (payload: unknown) => this.record('session.cancel', payload, this.onCancel(payload)),
planMode: (payload: unknown) => this.record('session.planMode', payload, this.onPlanMode(payload)),
setPlanMode: (payload: unknown) => this.record('session.setPlanMode', payload, this.onSetPlanMode(payload)),
}
readonly host: IApiClient['host'] = {

View File

@@ -48,6 +48,16 @@ describe('createFixtureApi', () => {
expect(response.result.value.items[1]?.parentSessionId).toBe('fx-alpha') // lineage material
})
it('reports plan mode as an unavailable optional fixture capability', async () => {
const client = new FixtureApiClient()
expect((await client.sessions.planMode({ sessionId: sid('fx-alpha') })).result).toEqual({
ok: true, value: null,
})
expect((await client.sessions.setPlanMode({ sessionId: sid('fx-alpha'), active: true })).result).toEqual({
ok: true, value: null,
})
})
it('pages history backwards on message-boundary cuts with seq-contiguous stitching', async () => {
const api = createFixtureApi()
const tail = await api.sessions.history(req({ sessionId: sid('fx-alpha'), maxMessages: 10 }))