Merge remote-tracking branch 'origin/master' into codex/status-bar-token-metrics

# Conflicts:
#	apps/web/tests/snapshots/fresh-round-trip/ui.expected.md
#	docs/architecture.i18n.yaml
#	docs/event-producer-consumer.md
#	packages/client/connection/src/client/api.ts
#	packages/client/runtime/src/client/index.ts
#	packages/client/runtime/src/client/sessions/manager.ts
#	packages/client/runtime/src/client/sessions/service.ts
#	packages/client/ui-conversation/README.i18n.yaml
#	packages/client/ui-conversation/tests/chat-toolview-slot.spec.tsx
#	packages/host/apiproxy/README.i18n.yaml
#	packages/host/apiproxy/src/api-proxy.ts
This commit is contained in:
Hypatia May
2026-07-29 14:26:43 +08:00
660 changed files with 22719 additions and 5598 deletions

View File

@@ -30,9 +30,12 @@ function snapshotOf(overrides: Partial<ConversationSnapshot> = {}): Conversation
interface BenchOptions {
planEntry?: React.ReactNode
/** The `plan` projection value the standard-kit useProjection serves. */
plan?: { active: boolean; pending: boolean }
modelEntry?: React.ReactNode
/** Hot text-ref lexicon (injects a minimal slash stub exposing only lexicon()). */
lexicon?: ReadonlyMap<'/' | '@', readonly string[]>
permissions?: { options: { value: string; name: string; description?: string }[]; currentValue: string }
draft?: string
running?: boolean
disabled?: boolean
@@ -88,13 +91,15 @@ function bench(over?: BenchOptions) {
items: [], state: 'idle', phase: 'ready', error: null,
baselinesReady: true, recentWorkspaceId: undefined,
})),
useProjection: (() => undefined),
useProjection: ((key: string, selector?: (v: unknown) => unknown) =>
(selector ?? (v => v))(key === 'permissions' ? over?.permissions : key === 'plan' ? over?.plan : undefined)),
useInput: bindSnapshotSelector(shell.state),
inputActions: shell.actions,
keyboard: shell,
useNotices: bindSnapshotSelector(shell.notices),
useLexicon: bindSnapshotSelector(shell.lexicon),
stop,
command: () => Promise.resolve(true),
renderSlot,
variant: over?.variant ?? 'composer',
...(over?.placeholder !== undefined ? { placeholder: over.placeholder } : {}),
@@ -230,6 +235,20 @@ describe('running and lock semantics (queue cut 1)', () => {
const custom = bench({ placeholder: 'Custom placeholder' })
expect(custom.textarea.placeholder).toBe('Custom placeholder')
})
it('the plan projection swaps the placeholder while its effective target is plan mode', () => {
const active = bench({ plan: { active: true, pending: false } })
expect(active.textarea.placeholder).toBe('describe your task to generate plan')
// /plan just ran: pending entry already reads as the plan target.
const entering = bench({ plan: { active: false, pending: true } })
expect(entering.textarea.placeholder).toBe('describe your task to generate plan')
// Pending exit: target is default again.
const leaving = bench({ plan: { active: true, pending: true } })
expect(leaving.textarea.placeholder).toBe('Message the agent')
// Owner placeholder outranks the plan swap.
const custom = bench({ plan: { active: true, pending: false }, placeholder: 'Custom placeholder' })
expect(custom.textarea.placeholder).toBe('Custom placeholder')
})
})
describe('machine pending lock', () => {
@@ -250,7 +269,6 @@ describe('machine pending lock', () => {
expect(shell.snapshot.phase).toBe('submitting')
const textarea = view.container.querySelector('textarea')!
expect(textarea.readOnly).toBe(true)
expect(view.container.querySelector('[data-input-pending]')).not.toBeNull()
expect(view.container.querySelector<HTMLButtonElement>('button[aria-label="Send message"]')!.disabled).toBe(true)
})
})
@@ -352,16 +370,37 @@ describe('strips and variants', () => {
})
describe('placeholder chrome and control seats', () => {
it('renders attach + Access placeholder; plan/model seats render EMPTY without entries (B ruling)', () => {
it('renders attach; the Access chip is absent without the permissions projection; plan/model seats render EMPTY without entries (B ruling)', () => {
const { view, slotCalls } = bench()
expect(view.getByLabelText('Add attachment')).toBeTruthy()
expect((view.getByLabelText('Access mode') as HTMLSelectElement).value).toBe('readonly')
// Capability absent (no projection value): the chip renders nothing.
expect(view.queryByLabelText('Access mode')).toBeNull()
// Both seats dispatched, nothing rendered.
expect(slotCalls.map(c => c.key)).toEqual(['conversation.input.plan', 'conversation.input.model'])
expect(view.queryByLabelText('Plan mode')).toBeNull()
expect(view.queryByLabelText('Model')).toBeNull()
})
it('the Access chip renders the projection value and submits /permission on pick', async () => {
const permissions = {
options: [
{ value: 'workspace-write', name: 'workspace-write' },
{ value: 'danger-full-access', name: 'danger-full-access' },
],
currentValue: 'workspace-write',
}
const { view } = bench({ permissions })
const select = view.getByLabelText('Access mode') as HTMLSelectElement
expect(select.value).toBe('workspace-write')
// Title-case display is presentation only; the option values stay machine names.
expect([...select.options].map(o => o.textContent)).toEqual(['Workspace Write', 'Danger Full Access'])
fireEvent.change(select, { target: { value: 'danger-full-access' } })
// Optimistic pick + disable until admission resolves (command stub resolves true).
expect(select.disabled).toBe(true)
await act(async () => {})
expect(select.disabled).toBe(false)
})
it('a registered entry fills its seat and receives the locked owner prop', () => {
const { view, slotCalls } = bench({
disabled: true,
@@ -377,12 +416,13 @@ describe('placeholder chrome and control seats', () => {
expect(live.slotCalls.every(c => !(c.owner as { locked: boolean }).locked)).toBe(true)
})
it('disabled locks the Access placeholder and attach control (running does not)', () => {
const { view } = bench({ disabled: true })
it('disabled locks the Access chip and attach control (running does not)', () => {
const permissions = { options: [{ value: 'workspace-write', name: 'workspace-write' }], currentValue: 'workspace-write' }
const { view } = bench({ disabled: true, permissions })
expect((view.getByLabelText('Add attachment') as HTMLButtonElement).disabled).toBe(true)
expect((view.getByLabelText('Access mode') as HTMLSelectElement).disabled).toBe(true)
cleanup()
const live = bench({ running: true })
const live = bench({ running: true, permissions })
expect((live.view.getByLabelText('Access mode') as HTMLSelectElement).disabled).toBe(false)
})
})