mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:50:43 +00:00
fix(feishu): share streaming tool progress labels
This commit is contained in:
@@ -57,6 +57,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/messaging: deliver distinct final commentary after same-target `message` tool sends while still deduping text/media already sent by the tool, so short closing remarks are no longer silently dropped. Fixes #76915. Thanks @hclsys.
|
||||
- Agents/messaging: preserve string thread IDs when matching message-tool reply dedupe routes, avoiding precision loss on numeric-looking topic IDs before channel plugin comparison. Thanks @vincentkoc.
|
||||
- Channels/streaming: honor `agents.defaults.toolProgressDetail: "raw"` in Slack, Discord, Telegram, Matrix, and Microsoft Teams progress drafts, so tool-start lines include raw command/detail output when debugging. Thanks @vincentkoc.
|
||||
- Feishu: use the shared channel progress formatter for streaming-card tool status lines, including raw command/detail output and message-tool filtering. Thanks @vincentkoc.
|
||||
- Mattermost: use the shared progress draft formatter for tool status previews, including raw command/detail output when `agents.defaults.toolProgressDetail: "raw"` is enabled. Thanks @vincentkoc.
|
||||
- OpenAI Codex: honor `auth.order.openai-codex` when starting app-server clients without an explicit auth profile, so status/model probes and implicit startup use the configured Codex account instead of falling back to the default profile. Thanks @vincentkoc.
|
||||
- OpenAI Codex: let SSRF-guarded provider requests inherit OpenClaw's undici IPv4/IPv6 fallback policy, so ChatGPT-backed Codex runs recover on IPv4-working hosts when DNS still returns unreachable IPv6 addresses. Fixes #76857. Thanks @jplavoiemtl and @SymbolStar.
|
||||
|
||||
@@ -1101,7 +1101,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("shows transient tool status on streaming cards but omits it from the final close", async () => {
|
||||
it("shows shared transient tool status on streaming cards but omits it from the final close", async () => {
|
||||
resolveFeishuAccountMock.mockReturnValue({
|
||||
accountId: "main",
|
||||
appId: "app_id",
|
||||
@@ -1124,12 +1124,70 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
|
||||
const updateTexts = streamingInstances[0].update.mock.calls.map((call: unknown[]) =>
|
||||
typeof call[0] === "string" ? call[0] : "",
|
||||
);
|
||||
expect(updateTexts.some((text) => text.includes("Using: web_search"))).toBe(true);
|
||||
expect(updateTexts.some((text) => text.includes("🔎 Web Search"))).toBe(true);
|
||||
expect(streamingInstances[0].close).toHaveBeenCalledWith("final answer", {
|
||||
note: "Agent: agent",
|
||||
});
|
||||
});
|
||||
|
||||
it("shows raw command detail in streaming card tool status", async () => {
|
||||
resolveFeishuAccountMock.mockReturnValue({
|
||||
accountId: "main",
|
||||
appId: "app_id",
|
||||
appSecret: "app_secret",
|
||||
domain: "feishu",
|
||||
config: {
|
||||
renderMode: "card",
|
||||
streaming: true,
|
||||
},
|
||||
});
|
||||
|
||||
const { result, options } = createDispatcherHarness({
|
||||
runtime: createRuntimeLogger(),
|
||||
});
|
||||
await options.onReplyStart?.();
|
||||
result.replyOptions.onToolStart?.({
|
||||
name: "exec",
|
||||
args: { command: "pnpm test -- --watch=false" },
|
||||
detailMode: "raw",
|
||||
});
|
||||
result.replyOptions.onPartialReply?.({ text: "final answer" });
|
||||
await options.onIdle?.();
|
||||
|
||||
const updateTexts = streamingInstances[0].update.mock.calls.map((call: unknown[]) =>
|
||||
typeof call[0] === "string" ? call[0] : "",
|
||||
);
|
||||
expect(
|
||||
updateTexts.some((text) => text.includes("🛠️ Exec: run tests, `pnpm test -- --watch=false`")),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("omits message-like tools from streaming card status", async () => {
|
||||
resolveFeishuAccountMock.mockReturnValue({
|
||||
accountId: "main",
|
||||
appId: "app_id",
|
||||
appSecret: "app_secret",
|
||||
domain: "feishu",
|
||||
config: {
|
||||
renderMode: "card",
|
||||
streaming: true,
|
||||
},
|
||||
});
|
||||
|
||||
const { result, options } = createDispatcherHarness({
|
||||
runtime: createRuntimeLogger(),
|
||||
});
|
||||
await options.onReplyStart?.();
|
||||
result.replyOptions.onToolStart?.({ name: "message" });
|
||||
result.replyOptions.onPartialReply?.({ text: "final answer" });
|
||||
await options.onIdle?.();
|
||||
|
||||
const updateTexts = streamingInstances[0].update.mock.calls.map((call: unknown[]) =>
|
||||
typeof call[0] === "string" ? call[0] : "",
|
||||
);
|
||||
expect(updateTexts.some((text) => text.includes("Message"))).toBe(false);
|
||||
});
|
||||
|
||||
it("does not suppress a later final after error closeout", async () => {
|
||||
resolveFeishuAccountMock.mockReturnValue({
|
||||
accountId: "main",
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback";
|
||||
import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";
|
||||
import {
|
||||
formatChannelProgressDraftLine,
|
||||
isChannelProgressDraftWorkToolName,
|
||||
} from "openclaw/plugin-sdk/channel-streaming";
|
||||
import {
|
||||
resolveSendableOutboundReplyParts,
|
||||
resolveTextChunksWithFallback,
|
||||
@@ -695,10 +699,29 @@ export function createFeishuReplyDispatcher(params: CreateFeishuReplyDispatcherP
|
||||
: undefined,
|
||||
onReasoningEnd: reasoningPreviewEnabled ? () => {} : undefined,
|
||||
onToolStart: streamingEnabled
|
||||
? (payload: { name?: string; phase?: string }) => {
|
||||
updateStreamingStatusLine(
|
||||
`🔧 **Using: ${payload.name ?? payload.phase ?? "tool"}...**`,
|
||||
? (payload: {
|
||||
name?: string;
|
||||
phase?: string;
|
||||
args?: Record<string, unknown>;
|
||||
detailMode?: "explain" | "raw";
|
||||
}) => {
|
||||
if (!isChannelProgressDraftWorkToolName(payload.name)) {
|
||||
return;
|
||||
}
|
||||
const statusLine = formatChannelProgressDraftLine(
|
||||
{
|
||||
event: "tool",
|
||||
name: payload.name,
|
||||
phase: payload.phase,
|
||||
args: payload.args,
|
||||
},
|
||||
{
|
||||
detailMode: payload.detailMode,
|
||||
},
|
||||
);
|
||||
if (statusLine) {
|
||||
updateStreamingStatusLine(statusLine);
|
||||
}
|
||||
}
|
||||
: undefined,
|
||||
onAssistantMessageStart: streamingEnabled
|
||||
|
||||
Reference in New Issue
Block a user