fix: preserve complete remote tool details
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 已返回的工具详情现在会完整保存到本地会话。
This commit is contained in:
@@ -207,6 +207,7 @@ Execute 不经过 Ask 的 bubblewrap profile:
|
|||||||
调度、跨 channel 超车、批发送或第二套拥塞控制。
|
调度、跨 channel 超车、批发送或第二套拥塞控制。
|
||||||
- Agent 在把 Main 输入交给 Runtime 前、以及把 Runtime 输出交给 Main 前,先把 ACP frame 写入本机 journal。ACK 只推进 cursor 并裁剪已确认 frame,不表示 channel 已终止。
|
- Agent 在把 Main 输入交给 Runtime 前、以及把 Runtime 输出交给 Main 前,先把 ACP frame 写入本机 journal。ACK 只推进 cursor 并裁剪已确认 frame,不表示 channel 已终止。
|
||||||
- Main 持久化 binding identity 和单调 cursor;保留中的 connection lease 即使处于 offline/reconnecting,也可以先把新 cursor 落盘。重连提交不能覆盖 resume 过程中并发落盘的更新。
|
- 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 仍被拒绝。
|
- 同一 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 不自动重放。
|
- Main 到 Runtime 的 ACP 输入、模型请求、工具请求和 blob 不自动重放。
|
||||||
- 无法确认外部 Provider 是否已处理的模型调用保持结果未知,避免重复计费或重复副作用。
|
- 无法确认外部 Provider 是否已处理的模型调用保持结果未知,避免重复计费或重复副作用。
|
||||||
|
|||||||
@@ -3299,6 +3299,15 @@ describe('AssistantDatabase', () => {
|
|||||||
createdAt: 1_775_000_000_001,
|
createdAt: 1_775_000_000_001,
|
||||||
state: 'complete' as const,
|
state: 'complete' as const,
|
||||||
status: '已完成',
|
status: '已完成',
|
||||||
|
tools: [
|
||||||
|
{
|
||||||
|
callId: 'remote-tool-call',
|
||||||
|
name: '远程工具',
|
||||||
|
state: 'completed' as const,
|
||||||
|
summary: '完整远程工具结果',
|
||||||
|
output: '远程输出'.repeat(10_000)
|
||||||
|
}
|
||||||
|
],
|
||||||
contextCompressions: [
|
contextCompressions: [
|
||||||
{
|
{
|
||||||
state: 'completed' as const,
|
state: 'completed' as const,
|
||||||
@@ -3350,6 +3359,15 @@ describe('AssistantDatabase', () => {
|
|||||||
content: '生成完成',
|
content: '生成完成',
|
||||||
state: 'complete',
|
state: 'complete',
|
||||||
status: '已完成',
|
status: '已完成',
|
||||||
|
tools: [
|
||||||
|
{
|
||||||
|
callId: 'remote-tool-call',
|
||||||
|
name: '远程工具',
|
||||||
|
state: 'completed',
|
||||||
|
summary: '完整远程工具结果',
|
||||||
|
output: '远程输出'.repeat(10_000)
|
||||||
|
}
|
||||||
|
],
|
||||||
contextCompressions: [
|
contextCompressions: [
|
||||||
{
|
{
|
||||||
state: 'completed',
|
state: 'completed',
|
||||||
|
|||||||
+10
-1
@@ -3917,7 +3917,16 @@ describe('registerIpcHandlers local conversation persistence', () => {
|
|||||||
role: 'assistant' as const,
|
role: 'assistant' as const,
|
||||||
content: '增量内容',
|
content: '增量内容',
|
||||||
createdAt: 1,
|
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)
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
conversationBranchInputSchema,
|
conversationBranchInputSchema,
|
||||||
conversationSnapshotSchema,
|
conversationSnapshotSchema,
|
||||||
isUntouchedBuiltInDefaultProject,
|
isUntouchedBuiltInDefaultProject,
|
||||||
|
localConversationSaveBatchSchema,
|
||||||
persistedProjectExecutionSpaceSchema,
|
persistedProjectExecutionSpaceSchema,
|
||||||
projectCreateSchema,
|
projectCreateSchema,
|
||||||
projectExecutionSpaceSchema,
|
projectExecutionSpaceSchema,
|
||||||
@@ -203,3 +204,42 @@ describe('conversation branch contracts', () => {
|
|||||||
).toBe(true)
|
).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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -133,8 +133,8 @@ export type ConversationQueueItem = z.infer<
|
|||||||
|
|
||||||
export const conversationToolActivitySchema = z
|
export const conversationToolActivitySchema = z
|
||||||
.object({
|
.object({
|
||||||
callId: z.string().max(256).optional(),
|
callId: z.string().optional(),
|
||||||
name: z.string().max(200),
|
name: z.string(),
|
||||||
state: z.enum([
|
state: z.enum([
|
||||||
'pending',
|
'pending',
|
||||||
'running',
|
'running',
|
||||||
@@ -144,10 +144,10 @@ export const conversationToolActivitySchema = z
|
|||||||
'cancelled',
|
'cancelled',
|
||||||
'interrupted'
|
'interrupted'
|
||||||
]),
|
]),
|
||||||
summary: z.string().max(2_000),
|
summary: z.string(),
|
||||||
input: z.string().max(4_000).optional(),
|
input: z.string().optional(),
|
||||||
output: z.string().max(16_000).optional(),
|
output: z.string().optional(),
|
||||||
error: z.string().max(2_000).optional()
|
error: z.string().optional()
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user