mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-17 02:30:45 +08:00
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:
parent
6171ec0dc1
commit
e1bc9dd0d0
7 changed files with 325 additions and 190 deletions
|
|
@ -595,6 +595,16 @@ export class QqHarnessBridge {
|
|||
const content = hasImages
|
||||
? await promptContentForMessage(promptMessage, { signal: this.#signal })
|
||||
: undefined;
|
||||
// 过程流:把一次 Turn 的中间过程逐条推送给用户——
|
||||
// 模型说明文本、每个 Tool call、失败的工具错误详情,最后是完整回答。
|
||||
let pendingStepText = null;
|
||||
const pushNotice = async (text) => {
|
||||
try {
|
||||
await this.#bot.sendText(target, text);
|
||||
} catch (error) {
|
||||
this.#logger.warn?.('[dsh-im:qq] unable to send a turn progress notice:', error);
|
||||
}
|
||||
};
|
||||
let answer;
|
||||
let artifacts = [];
|
||||
try {
|
||||
|
|
@ -609,14 +619,24 @@ export class QqHarnessBridge {
|
|||
timeoutMs: this.#replyTimeoutMs,
|
||||
signal: this.#signal,
|
||||
control: { owner: this, key },
|
||||
// 中间过程逐条推送:每个工具调用发一条独立消息;text 帧(正文流)
|
||||
// 不逐帧推送,最终答案一次性发送;status 帧忽略。
|
||||
onUpdate: async (update) => {
|
||||
if (update.type !== 'tool' || !nonEmptyString(update.name)) return;
|
||||
try {
|
||||
await this.#bot.sendText(target, `正在使用${update.name}…`);
|
||||
} catch (error) {
|
||||
this.#logger.warn?.('[dsh-im:qq] unable to send a tool progress notice:', error);
|
||||
if (update.type === 'text') {
|
||||
// 当前 step 的累积文本:暂存,等下一个工具或结束时一次性推送。
|
||||
pendingStepText = update.text;
|
||||
return;
|
||||
}
|
||||
if (update.type === 'tool') {
|
||||
if (nonEmptyString(pendingStepText)) {
|
||||
await pushNotice(pendingStepText.trim());
|
||||
}
|
||||
pendingStepText = null;
|
||||
await pushNotice(`Tool call ${update.name}`);
|
||||
return;
|
||||
}
|
||||
if (update.error) {
|
||||
const label = nonEmptyString(update.toolName)
|
||||
? `Tool call ${update.toolName}` : 'Tool call';
|
||||
await pushNotice(`${label}\nError: ${update.error}`);
|
||||
}
|
||||
},
|
||||
onInteraction: (interaction) => this.#handleInteraction(interaction, {
|
||||
|
|
@ -635,7 +655,12 @@ export class QqHarnessBridge {
|
|||
]);
|
||||
}
|
||||
this.#signal?.throwIfAborted();
|
||||
const displayAnswer = answerTextForDelivery(answer, artifacts);
|
||||
const answerText = answerTextForDelivery(answer, artifacts);
|
||||
// 结束前仍有一段未推送的说明文本(且不是最终回答本身)时补发。
|
||||
if (nonEmptyString(pendingStepText) && pendingStepText.trim() !== answerText.trim()) {
|
||||
await pushNotice(pendingStepText.trim());
|
||||
}
|
||||
const displayAnswer = answerText;
|
||||
let textReceipt = null;
|
||||
let textSendError = null;
|
||||
try {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue