Merge remote-tracking branch 'origin/master' into xtr/react-loop-simplification

# Conflicts:
#	apps/cli/tests/snapshots/queued-manual-compact/terminal.expected.txt
#	apps/web/tests/snapshots/queue-actions/preserved.expected.md
#	packages/client/runtime/README.i18n.yaml
#	packages/client/runtime/src/client/sessions/session.ts
#	packages/client/ui-conversation/README.md
#	packages/client/ui-conversation/README.zh.md
#	packages/ui/tui/README.i18n.yaml
#	packages/ui/tui/tests/snapshots/untrusted-controls.expected.txt
This commit is contained in:
_Kerman
2026-08-03 17:15:34 +08:00
187 changed files with 4546 additions and 590 deletions

View File

@@ -86,6 +86,17 @@ describe('ProjectionValueStore semantics', () => {
const store = new ProjectionValueStore()
expect(store.faceOf('test/marks')).toBe(store.faceOf('test/marks'))
})
it('publishes one reference-stable whole-value snapshot until a row changes', () => {
const store = new ProjectionValueStore()
const empty = store.values()
expect(store.values()).toBe(empty)
store.apply('test/marks', { marks: ['a'] }, 1)
const populated = store.values()
expect(populated).toEqual({ 'test/marks': { marks: ['a'] } })
expect(populated).not.toBe(empty)
expect(store.values()).toBe(populated)
})
})
describe('Session tail-page seeding', () => {
@@ -167,6 +178,36 @@ describe('manager frame routing', () => {
expect(manager.getListSnapshot().items[0]?.title).toBeUndefined()
})
it('projects every retained value into list rows with stable snapshot identity', async () => {
const api = new FakeApiClient()
const manager = new SessionManager(api)
api.onList = () => Promise.resolve(ok({
items: [{
sessionId: sid('s1'), updatedAt: 1, running: false, blank: false,
projections: {
asOfSeq: 2,
values: { 'test/marks': { marks: ['baseline'] } },
},
}],
}) as never)
await manager.refreshList()
const baseline = manager.getListSnapshot().items[0]?.projectionValues
expect(baseline).toEqual({ 'test/marks': { marks: ['baseline'] } })
expect(manager.getListSnapshot().items[0]?.projectionValues).toBe(baseline)
manager.handleMuxEnvelope({
rpcId: 'p2' as never,
payload: {
type: 'session/projection', sessionId: sid('s1'), key: 'test/marks',
value: { marks: ['live'] }, seq: 3,
} as never,
})
await Promise.resolve()
expect(manager.getListSnapshot().items[0]?.projectionValues)
.toEqual({ 'test/marks': { marks: ['live'] } })
expect(manager.getListSnapshot().items[0]?.projectionValues).not.toBe(baseline)
})
it('drops the projection store with the removed session', async () => {
const api = new FakeApiClient()
const manager = new SessionManager(api)

View File

@@ -430,6 +430,7 @@ describe('live event path', () => {
feed(ev.turnEnd(10, 1, 'aborted')) // no assistant/message ever arrives
const snapshot = session.getSnapshot()
expect(snapshot.partial).toBeNull()
expect(snapshot.turnEnds.get(1)).toBe(10)
const frozen = snapshot.nodes.at(-1)
expect(frozen).toMatchObject({ kind: 'assistant', interrupted: true, blocks: [{ kind: 'text', text: '说到一半' }] })
// Ordered inside the flow: after the user message (seq 7), before any later turn.
@@ -1219,6 +1220,7 @@ describe('reference stability (the memo contract)', () => {
expect(after).not.toBe(before)
expect(after.runningCalls).toBe(before.runningCalls)
expect(after.pending).toBe(before.pending)
expect(after.turnEnds).toBe(before.turnEnds)
// And a mutation on the tracked domain swaps that array.
feed(ev.toolResult(11, 1, 'c1', 'ECHO'))
const resolved = session.getSnapshot()