fix(channels): pass raw progress detail to drafts · openclaw/openclaw@c979ed3
vincentkoc
·
2026-05-04
·
via Recent Commits to openclaw:main
File tree
matrix/src/matrix/monitor
slack/src/monitor/message-handler
| Original file line number | Diff line number | Diff line change |
|---|
@@ -52,6 +52,7 @@ Docs: https://docs.openclaw.ai
|
52 | 52 | - Channels/CLI: keep `openclaw channels list --json` usable when provider usage fetching fails, and report per-provider usage errors without aborting the channel list. Refs #67595. |
53 | 53 | - 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. |
54 | 54 | - 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. |
| 55 | +- 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. |
55 | 56 | - 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. |
56 | 57 | - 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. |
57 | 58 | - Gateway/systemd: preserve operator-added secrets in the Gateway env file across re-stage while clearing OpenClaw-managed keys (such as `OPENCLAW_GATEWAY_TOKEN`) so a fresh staging value is never shadowed by a stale env-file copy; operator secrets are also retained when the state-dir `.env` is empty. Fixes #76860. Thanks @hclsys. |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -102,6 +102,7 @@ type DispatchInboundParams = {
|
102 | 102 | name?: string; |
103 | 103 | phase?: string; |
104 | 104 | args?: Record<string, unknown>; |
| 105 | +detailMode?: "explain" | "raw"; |
105 | 106 | }) => Promise<void> | void; |
106 | 107 | onItemEvent?: (payload: { |
107 | 108 | progressText?: string; |
@@ -1534,6 +1535,38 @@ describe("processDiscordMessage draft streaming", () => {
|
1534 | 1535 | ); |
1535 | 1536 | }); |
1536 | 1537 | |
| 1538 | +it("uses raw tool-progress detail in Discord progress drafts", async () => { |
| 1539 | +const draftStream = createMockDraftStreamForTest(); |
| 1540 | + |
| 1541 | +dispatchInboundMessage.mockImplementationOnce(async (params?: DispatchInboundParams) => { |
| 1542 | +await params?.replyOptions?.onToolStart?.({ |
| 1543 | +name: "exec", |
| 1544 | +phase: "start", |
| 1545 | +args: { command: "pnpm test -- --watch=false" }, |
| 1546 | +detailMode: "raw", |
| 1547 | +}); |
| 1548 | +await params?.replyOptions?.onItemEvent?.({ progressText: "done" }); |
| 1549 | +return createNoQueuedDispatchResult(); |
| 1550 | +}); |
| 1551 | + |
| 1552 | +const ctx = await createAutomaticSourceDeliveryContext({ |
| 1553 | +discordConfig: { |
| 1554 | +streaming: { |
| 1555 | +mode: "progress", |
| 1556 | +progress: { |
| 1557 | +label: "Shelling", |
| 1558 | +}, |
| 1559 | +}, |
| 1560 | +}, |
| 1561 | +}); |
| 1562 | + |
| 1563 | +await runProcessDiscordMessage(ctx); |
| 1564 | + |
| 1565 | +expect(draftStream.update).toHaveBeenCalledWith( |
| 1566 | +"Shelling\n🛠️ Exec: run tests, `pnpm test -- --watch=false`\n• done", |
| 1567 | +); |
| 1568 | +}); |
| 1569 | + |
1537 | 1570 | it("keeps Discord progress lines across assistant boundaries", async () => { |
1538 | 1571 | const draftStream = createMockDraftStreamForTest(); |
1539 | 1572 | |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -91,6 +91,7 @@ type ToolStartPayload = {
|
91 | 91 | name?: string; |
92 | 92 | phase?: string; |
93 | 93 | args?: Record<string, unknown>; |
| 94 | +detailMode?: "explain" | "raw"; |
94 | 95 | }; |
95 | 96 | |
96 | 97 | function readToolStringArg(args: Record<string, unknown>, key: string): string | undefined { |
@@ -668,12 +669,15 @@ export async function processDiscordMessage(
|
668 | 669 | await maybeBindStatusReactionsToToolReaction(payload); |
669 | 670 | await statusReactions.setTool(payload.name); |
670 | 671 | await draftPreview.pushToolProgress( |
671 | | -formatChannelProgressDraftLine({ |
672 | | -event: "tool", |
673 | | -name: payload.name, |
674 | | -phase: payload.phase, |
675 | | -args: payload.args, |
676 | | -}), |
| 672 | +formatChannelProgressDraftLine( |
| 673 | +{ |
| 674 | +event: "tool", |
| 675 | +name: payload.name, |
| 676 | +phase: payload.phase, |
| 677 | +args: payload.args, |
| 678 | +}, |
| 679 | +payload.detailMode ? { detailMode: payload.detailMode } : undefined, |
| 680 | +), |
677 | 681 | { toolName: payload.name }, |
678 | 682 | ); |
679 | 683 | }, |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -1580,12 +1580,15 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
1580 | 1580 | onToolStart: async (payload) => { |
1581 | 1581 | const toolName = payload.name?.trim(); |
1582 | 1582 | await pushPreviewToolProgress( |
1583 | | -formatChannelProgressDraftLine({ |
1584 | | -event: "tool", |
1585 | | -name: toolName, |
1586 | | -phase: payload.phase, |
1587 | | -args: payload.args, |
1588 | | -}), |
| 1583 | +formatChannelProgressDraftLine( |
| 1584 | +{ |
| 1585 | +event: "tool", |
| 1586 | +name: toolName, |
| 1587 | +phase: payload.phase, |
| 1588 | +args: payload.args, |
| 1589 | +}, |
| 1590 | +payload.detailMode ? { detailMode: payload.detailMode } : undefined, |
| 1591 | +), |
1589 | 1592 | { toolName }, |
1590 | 1593 | ); |
1591 | 1594 | }, |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -381,14 +381,18 @@ export function createMSTeamsReplyDispatcher(params: {
|
381 | 381 | name?: string; |
382 | 382 | phase?: string; |
383 | 383 | args?: Record<string, unknown>; |
| 384 | +detailMode?: "explain" | "raw"; |
384 | 385 | }) => { |
385 | 386 | await streamController.pushProgressLine( |
386 | | -formatChannelProgressDraftLine({ |
387 | | -event: "tool", |
388 | | -name: payload.name, |
389 | | -phase: payload.phase, |
390 | | -args: payload.args, |
391 | | -}), |
| 387 | +formatChannelProgressDraftLine( |
| 388 | +{ |
| 389 | +event: "tool", |
| 390 | +name: payload.name, |
| 391 | +phase: payload.phase, |
| 392 | +args: payload.args, |
| 393 | +}, |
| 394 | +payload.detailMode ? { detailMode: payload.detailMode } : undefined, |
| 395 | +), |
392 | 396 | { toolName: payload.name }, |
393 | 397 | ); |
394 | 398 | }, |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -1084,12 +1084,15 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
|
1084 | 1084 | await statusReactions.setTool(payload.name); |
1085 | 1085 | } |
1086 | 1086 | await pushPreviewToolProgress( |
1087 | | -formatChannelProgressDraftLine({ |
1088 | | -event: "tool", |
1089 | | -name: payload.name, |
1090 | | -phase: payload.phase, |
1091 | | -args: payload.args, |
1092 | | -}), |
| 1087 | +formatChannelProgressDraftLine( |
| 1088 | +{ |
| 1089 | +event: "tool", |
| 1090 | +name: payload.name, |
| 1091 | +phase: payload.phase, |
| 1092 | +args: payload.args, |
| 1093 | +}, |
| 1094 | +payload.detailMode ? { detailMode: payload.detailMode } : undefined, |
| 1095 | +), |
1093 | 1096 | { toolName: payload.name }, |
1094 | 1097 | ); |
1095 | 1098 | }, |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -1174,12 +1174,15 @@ export const dispatchTelegramMessage = async ({
|
1174 | 1174 | await statusReactionController.setTool(toolName); |
1175 | 1175 | } |
1176 | 1176 | await pushPreviewToolProgress( |
1177 | | -formatChannelProgressDraftLine({ |
1178 | | -event: "tool", |
1179 | | -name: toolName, |
1180 | | -phase: payload.phase, |
1181 | | -args: payload.args, |
1182 | | -}), |
| 1177 | +formatChannelProgressDraftLine( |
| 1178 | +{ |
| 1179 | +event: "tool", |
| 1180 | +name: toolName, |
| 1181 | +phase: payload.phase, |
| 1182 | +args: payload.args, |
| 1183 | +}, |
| 1184 | +payload.detailMode ? { detailMode: payload.detailMode } : undefined, |
| 1185 | +), |
1183 | 1186 | { toolName }, |
1184 | 1187 | ); |
1185 | 1188 | }, |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -85,6 +85,7 @@ export type GetReplyOptions = {
|
85 | 85 | name?: string; |
86 | 86 | phase?: string; |
87 | 87 | args?: Record<string, unknown>; |
| 88 | +detailMode?: "explain" | "raw"; |
88 | 89 | }) => Promise<void> | void; |
89 | 90 | /** Called when a concrete work item starts, updates, or completes. */ |
90 | 91 | onItemEvent?: (payload: { |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -1142,6 +1142,39 @@ describe("runAgentTurnWithFallback", () => {
|
1142 | 1142 | }); |
1143 | 1143 | }); |
1144 | 1144 | |
| 1145 | +it("forwards raw tool progress detail mode to tool-start reply options", async () => { |
| 1146 | +const onToolStart = vi.fn(); |
| 1147 | +state.runEmbeddedPiAgentMock.mockImplementationOnce(async (params: EmbeddedAgentParams) => { |
| 1148 | +await params.onAgentEvent?.({ |
| 1149 | +stream: "tool", |
| 1150 | +data: { |
| 1151 | +name: "exec", |
| 1152 | +phase: "start", |
| 1153 | +args: { command: "pnpm test -- --watch=false" }, |
| 1154 | +}, |
| 1155 | +}); |
| 1156 | +return { payloads: [{ text: "final" }], meta: {} }; |
| 1157 | +}); |
| 1158 | + |
| 1159 | +const runAgentTurnWithFallback = await getRunAgentTurnWithFallback(); |
| 1160 | +const result = await runAgentTurnWithFallback({ |
| 1161 | + ...createMinimalRunAgentTurnParams({ |
| 1162 | +opts: { |
| 1163 | + onToolStart, |
| 1164 | +} satisfies GetReplyOptions, |
| 1165 | +}), |
| 1166 | +toolProgressDetail: "raw", |
| 1167 | +}); |
| 1168 | + |
| 1169 | +expect(result.kind).toBe("success"); |
| 1170 | +expect(onToolStart).toHaveBeenCalledWith({ |
| 1171 | +name: "exec", |
| 1172 | +phase: "start", |
| 1173 | +args: { command: "pnpm test -- --watch=false" }, |
| 1174 | +detailMode: "raw", |
| 1175 | +}); |
| 1176 | +}); |
| 1177 | + |
1145 | 1178 | it("publishes Codex app-server telemetry to agent event subscribers", async () => { |
1146 | 1179 | const agentEvents = await import("../../infra/agent-events.js"); |
1147 | 1180 | const emitAgentEvent = vi.mocked(agentEvents.emitAgentEvent); |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -1537,6 +1537,7 @@ export async function runAgentTurnWithFallback(params: {
|
1537 | 1537 | evt.data.args && typeof evt.data.args === "object" |
1538 | 1538 | ? (evt.data.args as Record<string, unknown>) |
1539 | 1539 | : undefined, |
| 1540 | +detailMode: params.toolProgressDetail, |
1540 | 1541 | }); |
1541 | 1542 | } |
1542 | 1543 | } |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。