From 0804f5ccd2c7c0a8fa0aa3f3efad512e1a6e488b Mon Sep 17 00:00:00 2001 From: mesalogo Date: Sun, 30 Aug 2026 09:29:28 +0800 Subject: [PATCH] fix: preserve complete remote tool details MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Managed SSH Runtime events could complete successfully and then fail local conversation persistence when tool details exceeded a second, smaller renderer-to-Main schema limit. Conversation persistence now stores the complete tool details already accepted from the Runtime, so long remote results no longer leave the final assistant message stuck in a streaming state. Release note: 修复远程项目工具结果较长时会话持久化失败的问题;Runtime 已返回的工具详情现在会完整保存到本地会话。 --- .../remote-host-and-goodbuddy-agent-design.md | 1 + src/main/assistant/assistant-database.test.ts | 18 +++++++++ src/main/ipc.test.ts | 11 ++++- src/shared/assistant-contracts.test.ts | 40 +++++++++++++++++++ src/shared/assistant-contracts.ts | 12 +++--- 5 files changed, 75 insertions(+), 7 deletions(-) diff --git a/docs/architecture/remote-host-and-goodbuddy-agent-design.md b/docs/architecture/remote-host-and-goodbuddy-agent-design.md index feed8a66..8dc5c82e 100644 --- a/docs/architecture/remote-host-and-goodbuddy-agent-design.md +++ b/docs/architecture/remote-host-and-goodbuddy-agent-design.md @@ -207,6 +207,7 @@ Execute 不经过 Ask 的 bubblewrap profile: 调度、跨 channel 超车、批发送或第二套拥塞控制。 - Agent 在把 Main 输入交给 Runtime 前、以及把 Runtime 输出交给 Main 前,先把 ACP frame 写入本机 journal。ACK 只推进 cursor 并裁剪已确认 frame,不表示 channel 已终止。 - Main 持久化 binding identity 和单调 cursor;保留中的 connection lease 即使处于 offline/reconnecting,也可以先把新 cursor 落盘。重连提交不能覆盖 resume 过程中并发落盘的更新。 +- Runtime 事件进入桌面会话后,Renderer 和 Main 将完整工具详情保存到本地 SQLite,不再对已接受的工具输入、输出、错误或摘要施加额外的会话字段长度限制。 - 同一 detached Agent 存活时,短暂 SSH 断线依次执行 `controller/resume`、`runtime/resumeAcpChannel` 和 `runtime/replayAcpChannel`,从 Main 已确认的 cursor 后只重放 Agent 到 Main 的已记录输出。重复 frame 会被确认但不会再次交给上层。若上一代连接只留下没有活动请求的 detached binding,完成精确 controller takeover 后会先有界停止并核对遗留 Runtime process,再用新 channel epoch 重新打开同一 binding 并恢复已有 ACP session;其他 controller、未证明 takeover 或仍有活动请求的 binding 仍被拒绝。 - Main 到 Runtime 的 ACP 输入、模型请求、工具请求和 blob 不自动重放。 - 无法确认外部 Provider 是否已处理的模型调用保持结果未知,避免重复计费或重复副作用。 diff --git a/src/main/assistant/assistant-database.test.ts b/src/main/assistant/assistant-database.test.ts index 4ed608b6..7a820f49 100644 --- a/src/main/assistant/assistant-database.test.ts +++ b/src/main/assistant/assistant-database.test.ts @@ -3299,6 +3299,15 @@ describe('AssistantDatabase', () => { createdAt: 1_775_000_000_001, state: 'complete' as const, status: '已完成', + tools: [ + { + callId: 'remote-tool-call', + name: '远程工具', + state: 'completed' as const, + summary: '完整远程工具结果', + output: '远程输出'.repeat(10_000) + } + ], contextCompressions: [ { state: 'completed' as const, @@ -3350,6 +3359,15 @@ describe('AssistantDatabase', () => { content: '生成完成', state: 'complete', status: '已完成', + tools: [ + { + callId: 'remote-tool-call', + name: '远程工具', + state: 'completed', + summary: '完整远程工具结果', + output: '远程输出'.repeat(10_000) + } + ], contextCompressions: [ { state: 'completed', diff --git a/src/main/ipc.test.ts b/src/main/ipc.test.ts index 432ecdeb..470d84e7 100644 --- a/src/main/ipc.test.ts +++ b/src/main/ipc.test.ts @@ -3917,7 +3917,16 @@ describe('registerIpcHandlers local conversation persistence', () => { role: 'assistant' as const, content: '增量内容', createdAt: 1, - state: 'streaming' as const + state: 'streaming' as const, + tools: [ + { + callId: 'remote-tool-call', + name: '远程工具', + state: 'completed' as const, + summary: '完整远程工具结果', + output: '远程输出'.repeat(10_000) + } + ] } ] } diff --git a/src/shared/assistant-contracts.test.ts b/src/shared/assistant-contracts.test.ts index 2a98d309..e6532e4f 100644 --- a/src/shared/assistant-contracts.test.ts +++ b/src/shared/assistant-contracts.test.ts @@ -6,6 +6,7 @@ import { conversationBranchInputSchema, conversationSnapshotSchema, isUntouchedBuiltInDefaultProject, + localConversationSaveBatchSchema, persistedProjectExecutionSpaceSchema, projectCreateSchema, projectExecutionSpaceSchema, @@ -203,3 +204,42 @@ describe('conversation branch contracts', () => { ).toBe(true) }) }) + +describe('conversation persistence contracts', () => { + it('preserves complete tool details without persistence length limits', () => { + const input = 'input'.repeat(10_000) + const output = 'output'.repeat(20_000) + const error = 'error'.repeat(10_000) + const batch = [ + { + header: { + id: '00000000-0000-4000-8000-000000000301', + title: '远程工具会话', + updatedAt: 1 + }, + messages: [ + { + id: '00000000-0000-4000-8000-000000000302', + role: 'assistant', + content: '', + createdAt: 1, + state: 'complete', + tools: [ + { + callId: 'remote-tool-call', + name: '远程工具', + state: 'completed', + summary: '完整远程工具结果', + input, + output, + error + } + ] + } + ] + } + ] + + expect(localConversationSaveBatchSchema.parse(batch)).toEqual(batch) + }) +}) diff --git a/src/shared/assistant-contracts.ts b/src/shared/assistant-contracts.ts index 13fdf908..2d0e5d77 100644 --- a/src/shared/assistant-contracts.ts +++ b/src/shared/assistant-contracts.ts @@ -133,8 +133,8 @@ export type ConversationQueueItem = z.infer< export const conversationToolActivitySchema = z .object({ - callId: z.string().max(256).optional(), - name: z.string().max(200), + callId: z.string().optional(), + name: z.string(), state: z.enum([ 'pending', 'running', @@ -144,10 +144,10 @@ export const conversationToolActivitySchema = z 'cancelled', 'interrupted' ]), - summary: z.string().max(2_000), - input: z.string().max(4_000).optional(), - output: z.string().max(16_000).optional(), - error: z.string().max(2_000).optional() + summary: z.string(), + input: z.string().optional(), + output: z.string().optional(), + error: z.string().optional() }) .strict()