feat(qq): stream the full turn feed as discrete messages

Show what the agent is actually doing during a turn, the way Claude
Code does: interim explanation text, every Tool call, and the error
detail when a tool fails, each as its own pushed message before the
final markdown answer.

- HarnessReplyTracker.consume now returns every frame of a polling
  batch in order (same-batch text frames collapse to the latest)
  instead of dropping all but the last event, which silently ate
  tool-call frames that shared a batch with their result
- tool frames carry callId; status frames carry the correlated
  toolName and a flattened error text parsed from the tool/result
  error payload (message, or name: code)
- the QQ bridge holds interim step text until the next tool call
  (or the end of the turn) pushes it, emits 'Tool call <name>' per
  call, emits 'Tool call <name>\nError: <reason>' for failures, and
  skips re-sending interim text that already equals the final answer

Other channels keep their existing behaviour: the status frame still
reads '正在整理结果…' and they ignore the new optional fields.
This commit is contained in:
jonah.fu 2026-08-23 22:39:29 +08:00
parent 6171ec0dc1
commit e1bc9dd0d0
7 changed files with 325 additions and 190 deletions

View file

@ -399,7 +399,7 @@ test('reply tracker associates only the Harness turn created by the DingTalk pro
data: { turn: 9, step: 0, chunk: { type: 'text-delta', index: 0, text: '钉钉' } },
} },
]);
assert.deepEqual(update, { type: 'text', text: '钉钉' });
assert.deepEqual(update, [{ type: 'text', text: '钉钉' }]);
tracker.consume([
{ event: {
seq: 6,