From 10844da6f1544356e4531188c8ed8f268e9df875 Mon Sep 17 00:00:00 2001 From: Ryder Sun Date: Fri, 6 Nov 2026 23:15:25 +0800 Subject: [PATCH] Keep incomplete group contributions unresolved Treat failed and truncated peer replies as blockers until a complete successful contribution replaces them, avoiding false task completion. Checks: focused natural discussion regressions; desktop pack. --- ...orkspace-auto-runner-natural-decisions.cjs | 5 ++-- ...l-workspace-v4-natural-discussion.test.cjs | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/desktop/src/workspace/local-workspace-auto-runner-natural-decisions.cjs b/desktop/src/workspace/local-workspace-auto-runner-natural-decisions.cjs index 620a815..a3c6ae4 100644 --- a/desktop/src/workspace/local-workspace-auto-runner-natural-decisions.cjs +++ b/desktop/src/workspace/local-workspace-auto-runner-natural-decisions.cjs @@ -68,8 +68,9 @@ const v4NaturalDecisionMethods = { const peerBlocker = messages.find(message => { if (message.agentKind === owner) return false const status = message.trace?.context?.taskDecision?.status + const contribution = this.v4NaturalMessageContent(message) return status === 'blocked' || status === 'needs-human' - || explicitTaskFailure(this.v4NaturalMessageContent(message).text) + || contribution.partial || explicitTaskFailure(contribution.text) }) const ownerSawBlocker = peerBlocker && ownerMessage && controller.discussionStyle === 'sequential' @@ -165,7 +166,7 @@ const v4NaturalDecisionMethods = { )) for (const { message } of messages) { const contribution = this.v4NaturalMessageContent(message) - if (explicitTaskFailure(contribution.text)) { + if (contribution.partial || explicitTaskFailure(contribution.text)) { unresolved.add(message.agentKind) continue } diff --git a/desktop/test/workspace/local-workspace-v4-natural-discussion.test.cjs b/desktop/test/workspace/local-workspace-v4-natural-discussion.test.cjs index b1617e0..96a8795 100644 --- a/desktop/test/workspace/local-workspace-v4-natural-discussion.test.cjs +++ b/desktop/test/workspace/local-workspace-v4-natural-discussion.test.cjs @@ -476,6 +476,24 @@ test('Natural discussion retains an explicit peer failure without a structured d assert.deepEqual(runner.v4NaturalUnresolvedBlockers({ id: 'group' }, { runId: 'run' }, 'task', 3), []) }) +test('Natural discussion keeps failed and truncated peer contributions unresolved until a complete retry', () => { + const messages = [] + const runner = { ...v4NaturalDecisionMethods, state: () => ({ messages }), + v4NaturalMessageMatchesBinding: () => true, v4NaturalMessageContent: naturalDiscussionMethods.v4NaturalMessageContent } + const group = { id: 'group' } + const controller = { runId: 'run' } + const decision = { status: 'completed', reason: 'Done', deliverables: ['answer'] } + const peer = (round, trace) => ({ groupId: 'group', threadRootId: 'task', role: 'agent', + agentKind: 'hermes', content: 'The answer.', + trace: { phase: 'discussion', round, runId: 'run', context: { taskDecision: decision }, ...trace } }) + messages.push(peer(1, { status: 'failed' })) + assert.deepEqual(runner.v4NaturalUnresolvedBlockers(group, controller, 'task', 1), ['hermes']) + messages.push(peer(2, { truncated: true })) + assert.deepEqual(runner.v4NaturalUnresolvedBlockers(group, controller, 'task', 2), ['hermes']) + messages.push(peer(3, { status: 'completed' })) + assert.deepEqual(runner.v4NaturalUnresolvedBlockers(group, controller, 'task', 3), []) +}) + test('Natural discussion forwards a peer failure without a structured decision to the owner', async () => { const peer = { agentKind: 'hermes', content: '权限不足。', trace: {} } let notice @@ -490,6 +508,18 @@ test('Natural discussion forwards a peer failure without a structured decision t assert.equal(notice[3], 'system.autoPeerBlockerForwarded') }) +test('Natural discussion forwards an incomplete peer response to the owner', async () => { + const peer = { agentKind: 'hermes', content: 'Incomplete answer.', trace: { truncated: true } } + const runner = { ...v4NaturalDecisionMethods, + v4NaturalDiscussionRoundMessages: () => [peer], v4NaturalOwner: () => 'codex', + v4NaturalMessageContent: naturalDiscussionMethods.v4NaturalMessageContent, + agentLabel: kind => kind, v4NaturalNotice: () => {}, + } + assert.deepEqual(await runner.v4NaturalRoundOutcome( + { id: 'group' }, { runId: 'run', discussionStyle: 'agent-led' }, 'task', 1, ['codex', 'hermes'], + ), { nextKinds: ['codex'], ownerReview: true }) +}) + for (const discussionStyle of ['agent-led', 'sequential']) { test(`Natural ${discussionStyle} never completes a peer failure without a structured decision`, async t => { const { directory, options } = fixture()