chore(web): drop plan RPC residue and stale carriers after merge
Sweep the silently auto-merged leftovers of the superseded #587 wire layer: the PlanModeState interface and planMode/setPlanMode entries in the sessions API and rpc map, the fixture plan RPC spec, the composer.controls README paragraph and spec assertions, the old-format plan-mode snapshot, and the two Agent Notes written against the RPC design (to be rewritten for the projection reattachment). ChatView/PendingCard keep the approval-only pending rendering; QuestionComposer improvements ride along unchanged.
This commit is contained in:
@@ -48,59 +48,6 @@ describe('createFixtureApi', () => {
|
||||
expect(response.result.value.items[1]?.parentSessionId).toBe('fx-alpha') // lineage material
|
||||
})
|
||||
|
||||
it('queues fixture plan selections, commits them at prompt boundaries, and preserves pending state on cancel', async () => {
|
||||
const client = new FixtureApiClient()
|
||||
expect((await client.sessions.planMode({ sessionId: sid('fx-alpha') })).result).toEqual({
|
||||
ok: true, value: { active: false },
|
||||
})
|
||||
expect((await client.sessions.setPlanMode({ sessionId: sid('fx-alpha'), active: true })).result).toEqual({
|
||||
ok: true, value: { active: false, pending: true },
|
||||
})
|
||||
expect((await client.sessions.setPlanMode({ sessionId: sid('fx-alpha'), active: true })).result).toEqual({
|
||||
ok: true, value: { active: false, pending: true },
|
||||
})
|
||||
expect((await client.sessions.cancel({ sessionId: sid('fx-alpha') })).result).toEqual({
|
||||
ok: true, value: { accepted: true },
|
||||
})
|
||||
expect((await client.sessions.planMode({ sessionId: sid('fx-alpha') })).result).toEqual({
|
||||
ok: true, value: { active: false, pending: true },
|
||||
})
|
||||
await client.sessions.prompt({
|
||||
sessionId: sid('fx-alpha'), mode: 'queue', content: [{ type: 'text', text: 'commit plan' }],
|
||||
})
|
||||
expect((await client.sessions.planMode({ sessionId: sid('fx-alpha') })).result).toEqual({
|
||||
ok: true, value: { active: true },
|
||||
})
|
||||
expect((await client.sessions.setPlanMode({ sessionId: sid('fx-alpha'), active: false })).result).toEqual({
|
||||
ok: true, value: { active: true, pending: false },
|
||||
})
|
||||
expect((await client.sessions.planMode({ sessionId: sid('missing') })).result).toMatchObject({
|
||||
ok: false, error: { code: 'session-not-found' },
|
||||
})
|
||||
expect((await client.sessions.setPlanMode({ sessionId: sid('missing'), active: true })).result).toMatchObject({
|
||||
ok: false, error: { code: 'session-not-found' },
|
||||
})
|
||||
|
||||
const created = await client.sessions.create({})
|
||||
if (!created.result.ok) throw new Error('create failed')
|
||||
const createdId = created.result.value.sessionId
|
||||
await client.sessions.setPlanMode({ sessionId: createdId, active: true })
|
||||
expect((await client.sessions.setPlanMode({ sessionId: createdId, active: false })).result).toEqual({
|
||||
ok: true, value: { active: false },
|
||||
})
|
||||
// Selecting the committed target clears pending immediately, and repeating
|
||||
// that selection remains a no-op without a redundant plan/mode event.
|
||||
expect((await client.sessions.setPlanMode({ sessionId: createdId, active: false })).result).toEqual({
|
||||
ok: true, value: { active: false },
|
||||
})
|
||||
await client.sessions.prompt({
|
||||
sessionId: createdId, mode: 'queue', content: [{ type: 'text', text: 'remain default' }],
|
||||
})
|
||||
expect((await client.sessions.planMode({ sessionId: createdId })).result).toEqual({
|
||||
ok: true, value: { active: false },
|
||||
})
|
||||
})
|
||||
|
||||
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 }))
|
||||
|
||||
@@ -18,10 +18,6 @@ Per-session UI state for selection and the active view lives in the declared cha
|
||||
|
||||
The composer bar declares session-scoped single seats for `'conversation.input.plan'` and `'conversation.input.model'`, plus list slots for overlay, dock, left, and right input extensions. InputBar renders the model seat immediately before its pending indicator and send/stop button. Feature packages own each control and its state; ui-conversation supplies placement, the `locked` owner prop, and the standard slot shares. The resident no-session shell uses `DisabledInputBar` and therefore dispatches no session-scoped control seats.
|
||||
|
||||
The default composer's bottom row exposes the session-scoped `'conversation.composer.controls'` list slot to the left of the primary action. Mode and policy features contribute controls through that slot; whole-composer takeovers such as questions remain selector-routed entries of the separate `'conversation.composer'` chain. Pending questions render only through that takeover and are omitted from chat-flow placeholders, while approvals remain visible until their own Web response surface exists.
|
||||
|
||||
Prompt submission has a short local admission phase distinct from model generation. The composer clears the draft, prevents a duplicate send, and waits while the session settles the latest mode selection and the Host accepts the prompt. Admission success releases that lock immediately; the independently streamed running state then keeps Stop available for the model turn. Admission failure restores the submitted draft only when the user has not supplied replacement text and surfaces through the ordinary prompt-error strip.
|
||||
|
||||
`src/client/` is organized for the future package split: `contract/` is the sole inter-domain shared face (`slots.ts` slot declarations + composed slot props including the tool-row contract, `views.ts` shared primitives, `tool-call-model.ts`); the `skeleton/`, `chat/`, and `toolviews/` (sample registrants) domain directories import contract files and never each other; `apply.ts` is the only assembly point allowed to import all three domains. The `/client` export surface is the contract only — `apply`/`inject`, the two service classes, and the `contract/` type families; implementation components (skeleton, chat rows) and the store factory stay internal and reach the page exclusively through apply's slot registrations (tests take them via the `./src/*` subpath).
|
||||
|
||||
## Model Experience
|
||||
|
||||
@@ -91,7 +91,6 @@ describe('apply wiring', () => {
|
||||
// Declaring is claiming: the chat entry's registration put the hole on
|
||||
// the ledger with the contract's kind/scope.
|
||||
expect(b.slots.spec('conversation.chat.toolview')).toEqual({ kind: 'keyed', scope: 'session' })
|
||||
expect(b.slots.spec('conversation.composer.controls')).toEqual({ kind: 'list', scope: 'session' })
|
||||
})
|
||||
|
||||
it('occupies the slots + the ring; session entries share one store handle', async () => {
|
||||
@@ -133,7 +132,6 @@ describe('apply wiring', () => {
|
||||
expect(b.slots.entries('conversation.view')).toHaveLength(0)
|
||||
expect(b.slots.entries('conversation.chat.toolview')).toHaveLength(0)
|
||||
expect(b.slots.spec('conversation.chat.toolview')).toBeUndefined()
|
||||
expect(b.slots.spec('conversation.composer.controls')).toBeUndefined()
|
||||
expect(b.slots.entries('details')).toHaveLength(0)
|
||||
expect(b.ctx.get('conversation')).toBeUndefined()
|
||||
})
|
||||
|
||||
@@ -24,8 +24,6 @@ Workspace and Session lists are separate reconnect baselines. `workspace.create`
|
||||
|
||||
The `command.*` and `skill.*` domains expose the host command registry and skill catalog to clients. Every method addresses one session's agent by `sessionId` (a served session always has an Agent; `command.*` resumes cold sessions through the same path as `session.*`, while `skill.list` resolves the project root from the session header without touching the Agent registry). `command.execute` runs a slash-command line host-side with pure admission semantics: the response reports whether the line resolved to a handler plus the minted lifecycle `commandId` when it did (correlating the acknowledgment with the flow node), while the outcome rides the durably logged `command/run`/`command/done` lifecycle pair broadcast on the mux stream; the carrier's request signal cancels the running handler. `host/commands-changed` is the catalog invalidation frame: clients refetch `command.list` instead of diffing.
|
||||
|
||||
Plan mode uses two unary methods instead of deriving current state from a history page: `session.planMode` returns the committed state plus any boundary-pending selection, and `session.setPlanMode` records a selection and returns the same authoritative shape. A present `pending` target must differ from `active`; a net-zero service cleanup intent projects as `{ active }`, and the wire schema rejects equal values. Both methods return `null` when the optional host service is absent; `null` is capability absence, while `{ active: false }` is a supported inactive session. Committed changes still arrive through the raw logged `plan/mode` session event. `session.prompt` may carry a `planMode` target so the host records that selection immediately before accepting the prompt; it fails when the capability is absent and restores the prior target after a synchronous prompt rejection.
|
||||
|
||||
## Carrier layer (`/client` + root)
|
||||
|
||||
`AbstractApiClient` holds every protocol invariant — rpcId minting, envelope wrap/unwrap, zod parsing, SSE frame decoding, unary timeout, microtask-batched envelope observation (`subscribeEnvelopes`) — while platform subclasses supply only the `doFetch` transport aspect. `InProcessApiClient` over `toFetchHandler(api)` is the isomorphic point: the full wire serialization/validation path with no network, used by `dsh -p` headless.
|
||||
|
||||
@@ -24,8 +24,6 @@ export interface RpcMethodMap {
|
||||
'session.selectModel': SessionsApi['selectModel']
|
||||
'session.prompt': SessionsApi['prompt']
|
||||
'session.cancel': SessionsApi['cancel']
|
||||
'session.planMode': SessionsApi['planMode']
|
||||
'session.setPlanMode': SessionsApi['setPlanMode']
|
||||
'host.describe': HostApi['describe']
|
||||
'host.pickDirectory': HostApi['pickDirectory']
|
||||
'host.openPath': HostApi['openPath']
|
||||
|
||||
@@ -145,16 +145,6 @@ export interface SessionSummary {
|
||||
cwd?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Plan collaboration state exposed to clients. `active` is the logged state
|
||||
* shaping the current request; `pending`, when present, is the user's
|
||||
* next-boundary selection and differs from `active`.
|
||||
*/
|
||||
export interface PlanModeState {
|
||||
active: boolean
|
||||
pending?: boolean
|
||||
}
|
||||
|
||||
/** Session-domain unary methods (the map keys session.* of RpcMethodMap). */
|
||||
export interface SessionsApi {
|
||||
/** Lists persisted sessions (updatedAt descending). v1 returns everything; cursor is a reserved seat, unimplemented. */
|
||||
@@ -204,33 +194,10 @@ export interface SessionsApi {
|
||||
}>):
|
||||
Promise<RpcResponse<{ selected: ModelTarget }>>
|
||||
|
||||
/**
|
||||
* Sends a message. `content` is core's ContentBlock[] verbatim and `mode`
|
||||
* maps 1:1 — queue→send, steer→steer. An optional `planMode` target is
|
||||
* admitted atomically with the prompt.
|
||||
*/
|
||||
prompt(request: RpcRequest<{
|
||||
sessionId: SessionId
|
||||
mode: 'queue' | 'steer'
|
||||
content: ContentBlock[]
|
||||
planMode?: boolean
|
||||
}>):
|
||||
/** Sends a message. content is core's ContentBlock[] verbatim; mode maps 1:1 — queue→send, steer→steer. */
|
||||
prompt(request: RpcRequest<{ sessionId: SessionId; mode: 'queue' | 'steer'; content: ContentBlock[] }>):
|
||||
Promise<RpcResponse<{ accepted: true }>>
|
||||
|
||||
/** Stops: clears both FIFOs + aborts the current step (1:1 with agent.cancel). */
|
||||
cancel(request: RpcRequest<{ sessionId: SessionId }>): Promise<RpcResponse<{ accepted: true }>>
|
||||
|
||||
/**
|
||||
* Reads plan collaboration state. `null` means the host did not compose the
|
||||
* optional plan-mode service; it is distinct from inactive state.
|
||||
*/
|
||||
planMode(request: RpcRequest<{ sessionId: SessionId }>): Promise<RpcResponse<PlanModeState | null>>
|
||||
|
||||
/**
|
||||
* Selects plan collaboration state for the next model-request boundary.
|
||||
* The returned state exposes the still-committed value and pending target;
|
||||
* `null` means plan mode is unavailable on this host.
|
||||
*/
|
||||
setPlanMode(request: RpcRequest<{ sessionId: SessionId; active: boolean }>):
|
||||
Promise<RpcResponse<PlanModeState | null>>
|
||||
}
|
||||
|
||||
@@ -45,8 +45,6 @@ function scriptedApi(overrides: {
|
||||
}),
|
||||
prompt: r => ok(r, { accepted: true as const }),
|
||||
cancel: r => ok(r, { accepted: true as const }),
|
||||
planMode: r => ok(r, null),
|
||||
setPlanMode: r => ok(r, null),
|
||||
...overrides.sessions,
|
||||
},
|
||||
host: {
|
||||
|
||||
Reference in New Issue
Block a user