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

Conflict resolution notes:

- StatsLine: master redesigned the row into pipe-separated groups with LLM and
  tool wall times. Kept that design and swapped only the token accounting
  source, so counts and durations stay window-scoped while billing and context
  occupancy read the durable projections.
- Generated artifacts (cordis catalog, module graph, event producer/consumer,
  i18n pairing hashes) and web snapshots took master's side; they are
  regenerated and re-recorded after this merge.
- Web e2e goldens and details-panel/timeline assertions took master's side:
  that evolution is unrelated to this branch.
- ui-conversation package.json: kept master's devDependency ordering, re-adding
  only the token-meter entry this branch needs.
This commit is contained in:
Hypatia May
2026-07-30 13:56:39 +08:00
1091 changed files with 45715 additions and 7873 deletions

View File

@@ -483,6 +483,47 @@ describe('createFixtureApi', () => {
expect(seen.map(f => f.type)).toEqual(['host/workspace-changed', 'host/workspace-changed'])
})
it('session.rename covers not-found, blank title, and the accepted append + title frame', async () => {
const api = createFixtureApi()
const abort = new AbortController()
const framesPromise = (async () => {
const frames: MuxFrame[] = []
for await (const envelope of api.events.mux(req({}), abort.signal)) {
frames.push(envelope.payload)
if (frames.some(f => f.type === 'session/projection' && f.key === 'title' && f.value === '重命名')) abort.abort()
}
return frames
})()
await new Promise(resolve => setTimeout(resolve, 10))
const missing = await api.sessions.rename(req({ sessionId: sid('fx-void'), title: 'x' }))
expect(missing.result).toMatchObject({ ok: false, error: { code: 'session-not-found', details: { sessionId: 'fx-void' } } })
const blank = await api.sessions.rename(req({ sessionId: sid('fx-alpha'), title: ' ' }))
expect(blank.result).toMatchObject({ ok: false, error: { code: 'title-invalid', details: { sessionId: 'fx-alpha' } } })
const renamed = await api.sessions.rename(req({ sessionId: sid('fx-alpha'), title: ' 重命名 ' }))
if (!renamed.result.ok) throw new Error('rename failed')
expect(renamed.result.value.title).toBe('重命名')
const acceptedSeq = renamed.result.value.seq
// The response seq addresses the appended title event (the client plane
// has no session/title in its event union — titles ride the projection —
// so the event is located by seq and its payload checked structurally).
const history = await api.sessions.history(req({ sessionId: sid('fx-alpha'), maxMessages: 100 }))
if (!history.result.ok) throw new Error('history failed')
const appended = history.result.value.events.find(entry => entry.event.seq === acceptedSeq)
expect(appended?.event).toMatchObject({
type: 'session/title',
data: { title: '重命名', messageSeqs: [], source: { kind: 'user' } },
})
// Beyond the subscribe-time baseline replay, the append emitted exactly
// one title projection frame carrying the new value at the response seq.
const frames = await framesPromise
const titleFrames = frames.filter(f => f.type === 'session/projection' && f.key === 'title' && f.sessionId === sid('fx-alpha') && f.value === '重命名')
expect(titleFrames).toHaveLength(1)
expect(titleFrames[0]).toMatchObject({ seq: acceptedSeq })
})
it('workspace.insertSessionBefore moves, appends, no-ops, and rejects invalid ids', async () => {
const api = createFixtureApi()
const wsid = 'fx-ws-fixture' as WorkspaceId