fix(feishu): thread failure and error notices to the triggering message

Failure notices (turn failures, sub-flow errors from list/card/preset/
model/compact/stop/bind/switch paths, batch-input results, and the
card-queue rate-limit notice) were sent as fresh messages keyed only by
chat_id, so in topic groups they landed in the group's default area
instead of the topic the interaction belongs to.

- #sendFailure accepts options.replyTo; every call site forwards the
  anchor available in its context (command message, card message, or
  watch/list anchor)
- #handleMessageFailure and #finishBatchResult thread to the triggering
  message
- /repair stays private-chat-only by design; proactive delivery keeps
  its existing behavior (no thread context exists there)

- lib/index.js: regenerated host bundle
This commit is contained in:
evanfang0054 2026-08-31 21:25:13 +08:00
parent 6d815e0254
commit 82e7127cb4
2 changed files with 25 additions and 22 deletions

File diff suppressed because one or more lines are too long

View file

@ -774,7 +774,7 @@ export class FeishuHarnessBridge {
await this.#state.markSeen(messageId);
this.#status.lastMessageAt = new Date().toISOString();
this.#status.messagesReceived += 1;
if (result?.message) await this.#send(event.message.chat_id, result.message);
if (result?.message) await this.#send(event.message.chat_id, result.message, { replyTo: event.message.message_id });
this.#status.lastError = null;
})
.then(() => this.#finishReaction(messageId, processingReaction, 'DONE'))
@ -843,7 +843,7 @@ export class FeishuHarnessBridge {
const text = options.appendMessage
? `${messageFailureText(failure)}\n\n${options.appendMessage}`
: messageFailureText(failure);
await this.#send(chatId, text).catch(() => undefined);
await this.#send(chatId, text, { replyTo: options.replyTo }).catch(() => undefined);
return failure;
}
@ -874,6 +874,7 @@ export class FeishuHarnessBridge {
await this.#send(
event.message.chat_id,
failureText,
{ replyTo: event.message.message_id },
).catch(() => undefined);
}
@ -1630,7 +1631,7 @@ export class FeishuHarnessBridge {
}
this.#logger.warn?.('[dsh-feishu] card action queue is full; dropping callbacks');
let tracked;
tracked = this.#send(entry.chatId, t('操作过于频繁,请稍后再试。'))
tracked = this.#send(entry.chatId, t('操作过于频繁,请稍后再试。'), { replyTo: entry.messageId ?? null })
.catch(() => undefined)
.finally(() => {
this.#cardActionTasks.delete(tracked);
@ -1711,7 +1712,7 @@ export class FeishuHarnessBridge {
})
.catch(async (error) => {
if (this.#signal?.aborted) return;
await this.#sendFailure(entry.chatId, error, { logLabel: 'card action' });
await this.#sendFailure(entry.chatId, error, { logLabel: 'card action', replyTo: entry.messageId });
})
.finally(() => {
if (this.#cardActionInFlight.get(dedupeKey) === tracked) {
@ -2081,7 +2082,7 @@ export class FeishuHarnessBridge {
},
);
} catch (error) {
await this.#sendFailure(chatId, error, { logLabel: 'session list' });
await this.#sendFailure(chatId, error, { logLabel: 'session list', replyTo });
}
}
@ -2098,7 +2099,7 @@ export class FeishuHarnessBridge {
{ key, updateMessageId, replyTo },
);
} catch (error) {
await this.#sendFailure(chatId, error, { logLabel: 'workspace list' });
await this.#sendFailure(chatId, error, { logLabel: 'workspace list', replyTo });
}
}
@ -2114,6 +2115,7 @@ export class FeishuHarnessBridge {
} catch (error) {
await this.#sendFailure(chatId, error, {
logLabel: 'session binding',
replyTo,
userMessage: t('绑定失败:{message}', { message: safeErrorText(error) }),
});
}
@ -2127,6 +2129,7 @@ export class FeishuHarnessBridge {
} catch (error) {
await this.#sendFailure(chatId, error, {
logLabel: 'workspace switch',
replyTo,
userMessage: t('切换失败:{message}', { message: safeErrorText(error) }),
});
}
@ -2350,7 +2353,7 @@ export class FeishuHarnessBridge {
catalog._currentId = settings.agentPreset;
await this.#sendCard(chatId, presetCard(catalog), { key, updateMessageId });
} catch (error) {
await this.#sendFailure(chatId, error, { logLabel: 'preset card' });
await this.#sendFailure(chatId, error, { logLabel: 'preset card', replyTo: updateMessageId });
}
}
@ -2375,7 +2378,7 @@ export class FeishuHarnessBridge {
}
await this.#sendCard(chatId, modelCard(catalog), { key, updateMessageId });
} catch (error) {
await this.#sendFailure(chatId, error, { logLabel: 'model card' });
await this.#sendFailure(chatId, error, { logLabel: 'model card', replyTo: updateMessageId });
}
}
@ -2403,7 +2406,7 @@ export class FeishuHarnessBridge {
}
await this.#send(chatId, lines.join('\n'), { replyTo });
} catch (error) {
await this.#sendFailure(chatId, error, { logLabel: 'status text' });
await this.#sendFailure(chatId, error, { logLabel: 'status text', replyTo });
}
}
@ -2455,7 +2458,7 @@ export class FeishuHarnessBridge {
await this.#sendCard(chatId, statusCard(info), { key, updateMessageId });
} catch (error) {
await this.#sendFailure(chatId, error, { logLabel: 'status card' });
await this.#sendFailure(chatId, error, { logLabel: 'status card', replyTo: updateMessageId });
}
}
@ -2480,7 +2483,7 @@ export class FeishuHarnessBridge {
);
await this.#send(chatId, result?.message || t('上下文压缩失败。'), { replyTo });
} catch (error) {
await this.#sendFailure(chatId, error, { logLabel: 'compact' });
await this.#sendFailure(chatId, error, { logLabel: 'compact', replyTo });
}
}
@ -2503,7 +2506,7 @@ export class FeishuHarnessBridge {
}
await this.#send(chatId, result?.message || t('/stop 执行完成。'), { replyTo });
} catch (error) {
await this.#sendFailure(chatId, error, { logLabel: 'stop' });
await this.#sendFailure(chatId, error, { logLabel: 'stop', replyTo });
}
}
@ -2543,7 +2546,7 @@ export class FeishuHarnessBridge {
if (reply) await this.#send(chatId, reply, { replyTo });
}
} catch (error) {
await this.#sendFailure(chatId, error, { logLabel: 'preset reset' });
await this.#sendFailure(chatId, error, { logLabel: 'preset reset', replyTo: updateMessageId });
return;
}
try {
@ -2566,7 +2569,7 @@ export class FeishuHarnessBridge {
if (reply) await this.#send(chatId, reply, { replyTo });
}
} catch (error) {
await this.#sendFailure(chatId, error, { logLabel: 'preset selection' });
await this.#sendFailure(chatId, error, { logLabel: 'preset selection', replyTo: updateMessageId });
return;
}
try {
@ -2598,7 +2601,7 @@ export class FeishuHarnessBridge {
if (reply) await this.#send(chatId, reply, { replyTo });
}
} catch (error) {
await this.#sendFailure(chatId, error, { logLabel: 'model selection' });
await this.#sendFailure(chatId, error, { logLabel: 'model selection', replyTo: updateMessageId });
return;
}
try {