Merge remote-tracking branch 'origin/master' into worktree/web-multimodal-image-input

# Conflicts:
#	.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml
#	docs/config-catalog.md
#	docs/cordis-catalog/events.md
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/core.md
#	docs/core-data-structures/core.zh.md
#	docs/core-data-structures/llm-streaming.i18n.yaml
#	docs/core-data-structures/llm-streaming.md
#	docs/core-data-structures/llm-streaming.zh.md
#	docs/event-producer-consumer.md
#	docs/module-graph.md
#	docs/subsystems/attachment.i18n.yaml
#	docs/subsystems/attachment.md
#	docs/subsystems/attachment.zh.md
#	docs/subsystems/core.i18n.yaml
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl
#	packages/README.i18n.yaml
#	packages/README.md
#	packages/README.zh.md
#	packages/client/runtime/package.json
#	packages/client/ui-conversation/package.json
#	packages/client/ui-conversation/src/client/chat/AssistantMarkdown.tsx
#	packages/client/ui-conversation/src/client/chat/ChatView.tsx
#	packages/client/ui-conversation/src/client/chat/MessageItem.tsx
#	packages/client/ui-conversation/src/client/index.ts
#	packages/client/ui-conversation/tests/input-bar.spec.tsx
#	packages/compact/compact-basic/README.i18n.yaml
#	packages/compact/compact-basic/README.md
#	packages/compact/compact-basic/README.zh.md
#	packages/host/apiproxy/src/api-proxy.ts
#	packages/host/apiproxy/src/api/index.ts
#	packages/host/apiproxy/src/api/sessions.ts
#	packages/host/apiproxy/src/index.ts
#	packages/host/apiproxy/tests/api-proxy-models.spec.ts
#	packages/self-modification/tool-cordis/src/api-catalog.ts
#	pnpm-lock.yaml
#	scripts/type-equiv.manifest.json
This commit is contained in:
Yichen Jiang
2026-08-09 23:33:35 +08:00
3271 changed files with 69578 additions and 24953 deletions

View File

@@ -74,7 +74,7 @@ describe('deriveTrajectoryLayout', () => {
content: [{ type: 'text', text: 'a.txt' }], isError: false, callView: null, resultView: null,
},
] as unknown as ConversationSnapshot['nodes']
const turns = deriveTrajectoryLayout({ codeDispatches: new Map(), nodes, partial: null, runningCalls: [] })
const turns = deriveTrajectoryLayout({ nodes, partial: null, runningCalls: [] })
expect(turns).toHaveLength(1)
expect(turns[0]?.turn).toBe(1)
const kinds = turns[0]?.groups.flatMap(g => g.cells.map(c => c.kind))
@@ -90,12 +90,11 @@ describe('deriveTrajectoryLayout', () => {
it('adds runningCalls not already present and leaves their time blank', () => {
const turns = deriveTrajectoryLayout({
codeDispatches: new Map(),
nodes: [],
partial: null,
runningCalls: [{
callId: 'r1', name: 'bash', argsRaw: '{"command":"pwd"}',
turn: 1, step: 2, time: 9_000, callView: null,
turn: 1, step: 2, time: 9_000, callView: null, subCalls: [],
}],
})
expect(turns[0]?.groups.map(g => g.title)).toEqual(['Step 2'])
@@ -119,7 +118,6 @@ describe('deriveTrajectoryLayout', () => {
startedAt: 3_000, completedAt: null, status: 'running',
} as unknown as RequestView
const base = deriveTrajectoryLayout({
codeDispatches: new Map(),
nodes,
partial: { ...partial, blocks: [] },
requests: [request],
@@ -152,12 +150,11 @@ describe('deriveTrajectoryLayout', () => {
}],
}
const base = deriveTrajectoryLayout({
codeDispatches: new Map(),
nodes: [],
partial: { ...partial, blocks: [] },
runningCalls: [{
callId: 'c1', name: 'bash', argsRaw: '{"command":"pwd"}',
turn: 1, step: 1, time: 9_000, callView: null,
turn: 1, step: 1, time: 9_000, callView: null, subCalls: [],
}],
})
@@ -180,7 +177,7 @@ describe('deriveTrajectoryLayout', () => {
usage: { inputTokens: 1, outputTokens: 2, reasoningTokens: 3 },
},
] as unknown as ConversationSnapshot['nodes']
const turns = deriveTrajectoryLayout({ codeDispatches: new Map(), nodes, partial: null, runningCalls: [] })
const turns = deriveTrajectoryLayout({ nodes, partial: null, runningCalls: [] })
const cells = turns[0]?.groups.flatMap(g => g.cells) ?? []
expect(cells.find(c => c.kind === 'message')?.timeSeconds).toBeNull()
expect(turns[0]?.groups.find(g => g.title === 'Step 1')?.description).toBeUndefined()
@@ -206,7 +203,7 @@ describe('deriveTrajectoryLayout', () => {
content: [], isError: false, callView: null, resultView: null,
},
] as unknown as ConversationSnapshot['nodes']
const turns = deriveTrajectoryLayout({ codeDispatches: new Map(), nodes, partial: null, runningCalls: [] })
const turns = deriveTrajectoryLayout({ nodes, partial: null, runningCalls: [] })
expect(turns[0]?.groups[0]?.description).toBe('3,000 ms bash×2')
})
@@ -223,7 +220,7 @@ describe('deriveTrajectoryLayout', () => {
blocks: [{ kind: 'text', text: 'ok2' }],
},
] as unknown as ConversationSnapshot['nodes']
const turns = deriveTrajectoryLayout({ codeDispatches: new Map(), nodes, partial: null, runningCalls: [] })
const turns = deriveTrajectoryLayout({ nodes, partial: null, runningCalls: [] })
expect(turns.map(t => t.turn)).toEqual([1, 2])
expect(turns[0]?.groups.flatMap(g => g.cells.map(c => c.text))).toEqual(['first', 'ok1'])
expect(turns[1]?.groups.flatMap(g => g.cells.map(c => c.text))).toEqual(['second', 'ok2'])
@@ -254,7 +251,6 @@ describe('deriveTrajectoryLayout', () => {
}
const turns = deriveTrajectoryLayout({
codeDispatches: new Map(),
nodes,
partial: null,
runningCalls: [],
@@ -280,7 +276,7 @@ describe('deriveTrajectoryLayout', () => {
usage: { inputTokens: 11, outputTokens: 22, reasoningTokens: 3 },
},
] as unknown as ConversationSnapshot['nodes']
const turns = deriveTrajectoryLayout({ codeDispatches: new Map(), nodes, partial: null, runningCalls: [] })
const turns = deriveTrajectoryLayout({ nodes, partial: null, runningCalls: [] })
const message = turns[0]?.groups.flatMap(g => g.cells).find(c => c.kind === 'message')
expect(message).toMatchObject({
text: '…', input: 11, output: 22, think: 3,
@@ -295,7 +291,7 @@ describe('deriveTrajectoryLayout', () => {
}] as unknown as ConversationSnapshot['nodes']
const turns = deriveTrajectoryLayout({
codeDispatches: new Map(), nodes, partial: null, runningCalls: [],
nodes, partial: null, runningCalls: [],
})
const message = turns[0]?.groups.flatMap(group => group.cells)
.find(cell => cell.kind === 'message')
@@ -333,7 +329,7 @@ describe('deriveTrajectoryLayout', () => {
blocks: [{ kind: 'text', text: 'done' }],
},
] as unknown as ConversationSnapshot['nodes']
const turns = deriveTrajectoryLayout({ codeDispatches: new Map(), nodes, partial: null, runningCalls: [] })
const turns = deriveTrajectoryLayout({ nodes, partial: null, runningCalls: [] })
const cells = turns[0]?.groups.flatMap(g => g.cells) ?? []
const message = cells.find(c => c.kind === 'message' && c.text === 'done')
// From the compaction marker at 9.5s, not from context at 9s or the earlier surfaces.
@@ -352,7 +348,7 @@ describe('deriveTrajectoryLayout', () => {
},
] as unknown as ConversationSnapshot['nodes']
const turns = deriveTrajectoryLayout({
codeDispatches: new Map(), nodes, partial: null, runningCalls: [],
nodes, partial: null, runningCalls: [],
})
const message = turns[0]?.groups.flatMap(group => group.cells)
.find(cell => cell.kind === 'message')
@@ -372,6 +368,7 @@ describe('run_code sub-dispatch cells', () => {
kind: 'tool-result', seq: 3, time: 9_000, callId: 'p1',
call: { name: 'run_code', argsRaw: '{"code":"…","description":"批量读取"}' }, callTime: 6_200,
content: [{ type: 'text', text: 'done' }], isError: false, callView: null, resultView: null,
subCalls: [],
},
] as unknown as ConversationSnapshot['nodes']
@@ -380,14 +377,18 @@ describe('run_code sub-dispatch cells', () => {
callId: `p1:code:${n}`,
call: { name, argsRaw: '{"x":1}' }, callTime: start,
content: [{ type: 'text' as const, text: 'ok' }], isError: false, callView: null, resultView: null,
subCalls: [],
})
const withSubCalls = (subCalls: readonly ReturnType<typeof settledSub>[] | readonly object[]) =>
runCodeNodes.map(node => node.kind === 'tool-result' ? { ...node, subCalls } : node) as ConversationSnapshot['nodes']
it('nests settled sub-cells after their parent Tool cell with real durations', () => {
const codeDispatches = new Map([['p1', [
const subCalls = [
settledSub(1, 'bash', 6_300, 7_300),
settledSub(2, 'read', 7_300, 7_800),
]]]) as unknown as ConversationSnapshot['codeDispatches']
const turns = deriveTrajectoryLayout({ codeDispatches, nodes: runCodeNodes, partial: null, runningCalls: [] })
]
const turns = deriveTrajectoryLayout({ nodes: withSubCalls(subCalls), partial: null, runningCalls: [] })
const cells = turns[0]!.groups.flatMap(g => g.cells)
expect(cells.map(c => c.kind)).toEqual(['message', 'tool', 'subtool', 'subtool'])
expect(cells[0]?.text).toBe('Tool call only')
@@ -400,11 +401,29 @@ describe('run_code sub-dispatch cells', () => {
it('a running (unsettled) sub-call renders a subtool cell with blank time', () => {
const running = {
callId: 'p1:code:1', name: 'grep', argsRaw: '{"pattern":"x"}',
turn: 0, step: 0, time: 6_400, callView: null,
turn: 0, step: 0, time: 6_400, callView: null, subCalls: [],
}
const codeDispatches = new Map([['p1', [running]]]) as unknown as ConversationSnapshot['codeDispatches']
const turns = deriveTrajectoryLayout({ codeDispatches, nodes: runCodeNodes, partial: null, runningCalls: [] })
const turns = deriveTrajectoryLayout({ nodes: withSubCalls([running]), partial: null, runningCalls: [] })
const sub = turns[0]!.groups.flatMap(g => g.cells).find(c => c.kind === 'subtool')
expect(sub).toMatchObject({ text: 'grep · {"pattern":"x"}', timeSeconds: null })
})
it('recursively flattens nested child calls immediately after their parent', () => {
const leaf = {
...settledSub(2, 'read', 7_300, 7_800),
callId: 'p1:code:1:code:1',
}
const child = {
...settledSub(1, 'run_code', 6_300, 8_000),
subCalls: [leaf],
}
const turns = deriveTrajectoryLayout({ nodes: withSubCalls([child]), partial: null, runningCalls: [] })
const cells = turns[0]!.groups.flatMap(group => group.cells)
expect(cells.map(cell => cell.kind)).toEqual(['message', 'tool', 'subtool', 'subtool'])
expect(cells.slice(2).map(cell => cell.callId)).toEqual([
'p1:code:1',
'p1:code:1:code:1',
])
expect(cells.map(cell => cell.index)).toEqual([1, 2, 3, 4])
})
})

View File

@@ -82,7 +82,6 @@ function historySnapshot(
interruptedNodes: [],
partial: null,
runningCalls: [],
codeDispatches: new Map(),
...inspection,
},
}
@@ -115,7 +114,7 @@ function standaloneDuration(): Pick<
function fakeSession(nodes: ConversationSnapshot['nodes']) {
const store = createSnapshotStore({
nodes, pending: [], partial: null,
runningCalls: [] as ConversationSnapshot['runningCalls'], codeDispatches: new Map(),
runningCalls: [] as ConversationSnapshot['runningCalls'],
})
return { store, useSession: bindSnapshotSelector(store) as unknown as UseSession<ConversationSnapshot> }
}
@@ -186,7 +185,7 @@ function mount(slots: SlotsService, nodes: ConversationSnapshot['nodes'] = NODES
running: false, removed: false, promptError: null, nodes,
pending: [],
openState: 'open' as const, hasMore: true, loadingOlder: false,
partial: null, runningCalls: [] as ConversationSnapshot['runningCalls'], codeDispatches: new Map(),
partial: null, runningCalls: [] as ConversationSnapshot['runningCalls'],
})
const useSession = bindSnapshotSelector(sessionSnapshot) as unknown as UseSession<ConversationSnapshot>
const chat = createChatStore().create()