mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
test+docs: 回显生命周期、去重与预览移交的覆盖,README 与 Agent Note
新增 sendSession 回显编排、ChatView 回显渲染与 rpcId 去重、control 队列 rpcId 投影、HistoricalImageCache.seed、MessageImage 预览 arm 的测试;四个包 README 双语更新;Agent Note 记录 rpcId 关联与延帧退休决策。
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/api/session-controller/README.md
|
||||
README.md: 815276847f538f99352e0f0e55fc19af5da67470
|
||||
README.zh.md: 51bf5a98c62aaa3fcb2156c029416a4d26515f0b
|
||||
README.md: dc04e595c9c4bf029ed427d5a1214802041c11c0
|
||||
README.zh.md: b34c8ad018e4e2c0d95ac0b8aa1954742f9ecfa4
|
||||
|
||||
@@ -29,6 +29,8 @@ Each endpoint states its activation policy. List, search, attachment, history pa
|
||||
|
||||
The Client adapter exposes `SessionEventStream`, a Gateway `RemoteJournalStream` bound to one ordinary or direct-subagent address. It opens follow before the initial page, publishes only contiguous `replace`, `prepend`, and `append` changes, and repairs reconnect or sequence gaps through a tail page. Ordinary records cover `[event.seq, event.seq]`; packed rows cover `[event.seq, event.seq + memberCount - 1]`. A business, persistence, or unresolved continuity failure terminates the stream, while only physical carrier loss selects automatic resumption. `SessionControlStream` is a Gateway `RemoteSnapshotStream`; every generation opens with a complete process-local baseline, so reconnect replaces queue, jobs, and projection state instead of treating transient values as durable events.
|
||||
|
||||
The Session object also carries local submission echoes: `session.beginSubmission` inserts one into `SessionSnapshot.pendingSubmissions` synchronously, before the caller serializes and prompts, so a conversation UI can show the message on the submit click's own frame. The prompt's `requestId` is the correlation identity — the Host already echoes it as the durable user source's `rpcId`, and queue occurrences project it as `SessionQueuedItem.rpcId`. An echo retires one animation frame after its durable event or queue occurrence is observed (the delay keeps it renderable until the transcript node is), immediately when its identified prompt fails or is abandoned, and as failed on disposal; each retirement fires the registered `onRetire` callback exactly once. Echoes are Client memory only — reload and reconnect rebuild the conversation from durable events alone.
|
||||
|
||||
-----
|
||||
|
||||
<a id="configuration"></a>
|
||||
|
||||
@@ -29,6 +29,8 @@ kind: "package-reference"
|
||||
|
||||
Client adapter 提供 `SessionEventStream`,即绑定到一个普通 Session 或 direct subagent address 的 Gateway `RemoteJournalStream`。它在读取首个 page 前打开 follow,只发布连续的 `replace`、`prepend` 和 `append` 变更,并通过 tail page 修复重连或 seq 缺口。普通 record 覆盖 `[event.seq, event.seq]`,packed row 覆盖 `[event.seq, event.seq + memberCount - 1]`。业务、persistence 或无法恢复的连续性错误会终止 stream,只有物理载体断开才触发自动恢复。`SessionControlStream` 是 Gateway `RemoteSnapshotStream`;每代都以完整的进程本地 baseline 开始,因此重连会替换 queue、jobs 和 projection 状态,而不会把瞬态值当作 durable event。
|
||||
|
||||
Session 对象还承载本地提交回显:`session.beginSubmission` 在调用方序列化与 prompt 之前,同步把一条回显写入 `SessionSnapshot.pendingSubmissions`,会话 UI 因此能在点击提交的当帧显示消息。prompt 的 `requestId` 就是关联标识,Host 本就把它回显为 durable user source 的 `rpcId`,queue occurrence 也把它投影为 `SessionQueuedItem.rpcId`。回显在观察到其 durable event 或 queue occurrence 后延迟一个动画帧退休(该延迟保证 transcript 节点可渲染之前回显仍在),带标识的 prompt 失败或被放弃时立即退休,销毁时按 failed 退休;每次退休恰好触发一次注册的 `onRetire` 回调。回显只存在于 Client 内存,刷新与重连只从 durable event 重建会话。
|
||||
|
||||
-----
|
||||
|
||||
<a id="configuration"></a>
|
||||
|
||||
@@ -69,6 +69,30 @@ describe('Session control queue projection', () => {
|
||||
await iterator.next()
|
||||
})
|
||||
|
||||
it('projects the prompt rpcId from a user-rpc source and omits it elsewhere', async () => {
|
||||
const { control, inbox } = await harness()
|
||||
const identified = createUserMessage({
|
||||
content: [{ type: 'text', text: 'browser prompt' }],
|
||||
source: { kind: 'user', rpcId: 'req-42' as never },
|
||||
})
|
||||
inbox.append('next-turn', identified)
|
||||
inbox.append('next-step', message('plain steering'))
|
||||
|
||||
const abort = new AbortController()
|
||||
const iterator = control.control(abort.signal)[Symbol.asyncIterator]()
|
||||
const opened = await iterator.next()
|
||||
if (opened.done || opened.value.type !== 'baseline') throw new Error('missing baseline')
|
||||
const items = opened.value.value.queues['queue-session' as SessionId] ?? []
|
||||
expect(items).toMatchObject([
|
||||
{ id: identified.id, placement: 'queued', rpcId: 'req-42' },
|
||||
{ id: expect.anything(), placement: 'steering' },
|
||||
])
|
||||
expect('rpcId' in (items[1] ?? {})).toBe(false)
|
||||
|
||||
abort.abort()
|
||||
await iterator.next()
|
||||
})
|
||||
|
||||
it('ignores inbox events without the exact live Agent session', async () => {
|
||||
const { ctx, control, agent, inbox } = await harness()
|
||||
const abort = new AbortController()
|
||||
|
||||
Reference in New Issue
Block a user