Skip to content

Commit b664541

Browse files
committed
reply: make progress updates respect verbose
1 parent 1a47675 commit b664541

3 files changed

Lines changed: 115 additions & 5 deletions

File tree

src/agents/agent-scope.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ type ResolvedAgentConfig = {
3737
agentDir?: string;
3838
model?: AgentEntry["model"];
3939
thinkingDefault?: AgentEntry["thinkingDefault"];
40+
verboseDefault?: AgentEntry["verboseDefault"];
4041
reasoningDefault?: AgentEntry["reasoningDefault"];
4142
fastModeDefault?: AgentEntry["fastModeDefault"];
4243
skills?: AgentEntry["skills"];
@@ -142,6 +143,7 @@ export function resolveAgentConfig(
142143
? entry.model
143144
: undefined,
144145
thinkingDefault: entry.thinkingDefault,
146+
verboseDefault: entry.verboseDefault,
145147
reasoningDefault: entry.reasoningDefault,
146148
fastModeDefault: entry.fastModeDefault,
147149
skills: Array.isArray(entry.skills) ? entry.skills : undefined,

src/auto-reply/reply/dispatch-from-config.test.ts

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,9 +1125,16 @@ describe("dispatchReplyFromConfig", () => {
11251125
expect(dispatcher.sendFinalReply).toHaveBeenCalledTimes(1);
11261126
});
11271127

1128-
it("renders plain-text plan updates and concise approval progress for direct sessions", async () => {
1128+
it("renders plain-text plan updates and concise approval progress when verbose is enabled", async () => {
11291129
setNoAbort();
1130-
const cfg = emptyConfig;
1130+
const cfg = {
1131+
...emptyConfig,
1132+
agents: {
1133+
defaults: {
1134+
verboseDefault: "on",
1135+
},
1136+
},
1137+
} satisfies OpenClawConfig;
11311138
const dispatcher = createDispatcher();
11321139
const ctx = buildTestCtx({
11331140
Provider: "telegram",
@@ -1168,9 +1175,16 @@ describe("dispatchReplyFromConfig", () => {
11681175
expect(dispatcher.sendFinalReply).toHaveBeenCalledWith({ text: "done" });
11691176
});
11701177

1171-
it("renders concise patch summaries for direct sessions", async () => {
1178+
it("renders concise patch summaries when verbose is enabled", async () => {
11721179
setNoAbort();
1173-
const cfg = emptyConfig;
1180+
const cfg = {
1181+
...emptyConfig,
1182+
agents: {
1183+
defaults: {
1184+
verboseDefault: "on",
1185+
},
1186+
},
1187+
} satisfies OpenClawConfig;
11741188
const dispatcher = createDispatcher();
11751189
const ctx = buildTestCtx({
11761190
Provider: "telegram",
@@ -1199,6 +1213,55 @@ describe("dispatchReplyFromConfig", () => {
11991213
expect(dispatcher.sendToolResult).toHaveBeenCalledTimes(1);
12001214
expect(dispatcher.sendFinalReply).toHaveBeenCalledWith({ text: "done" });
12011215
});
1216+
1217+
it("suppresses plan and working-status progress when session verbose is off", async () => {
1218+
setNoAbort();
1219+
sessionStoreMocks.currentEntry = {
1220+
verboseLevel: "off",
1221+
};
1222+
const cfg = {
1223+
...emptyConfig,
1224+
agents: {
1225+
defaults: {
1226+
verboseDefault: "on",
1227+
},
1228+
},
1229+
} satisfies OpenClawConfig;
1230+
const dispatcher = createDispatcher();
1231+
const ctx = buildTestCtx({
1232+
Provider: "telegram",
1233+
ChatType: "direct",
1234+
SessionKey: "agent:main:main",
1235+
});
1236+
1237+
const replyResolver = async (
1238+
_ctx: MsgContext,
1239+
opts?: GetReplyOptions,
1240+
_cfg?: OpenClawConfig,
1241+
) => {
1242+
await opts?.onPlanUpdate?.({
1243+
phase: "update",
1244+
explanation: "Inspect code, patch it, run tests.",
1245+
steps: ["Inspect code", "Patch code", "Run tests"],
1246+
});
1247+
await opts?.onApprovalEvent?.({
1248+
phase: "requested",
1249+
status: "pending",
1250+
command: "pnpm test",
1251+
});
1252+
await opts?.onPatchSummary?.({
1253+
phase: "end",
1254+
title: "apply patch",
1255+
summary: "1 added, 2 modified",
1256+
});
1257+
return { text: "done" } satisfies ReplyPayload;
1258+
};
1259+
1260+
await dispatchReplyFromConfig({ ctx, cfg, dispatcher, replyResolver });
1261+
1262+
expect(dispatcher.sendToolResult).not.toHaveBeenCalled();
1263+
expect(dispatcher.sendFinalReply).toHaveBeenCalledWith({ text: "done" });
1264+
});
12021265
it("delivers deterministic exec approval tool payloads for native commands", async () => {
12031266
setNoAbort();
12041267
const cfg = emptyConfig;

src/auto-reply/reply/dispatch-from-config.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
22
import { isParentOwnedBackgroundAcpSession } from "../../acp/session-interaction-mode.js";
3-
import { resolveSessionAgentId } from "../../agents/agent-scope.js";
3+
import { resolveAgentConfig, resolveSessionAgentId } from "../../agents/agent-scope.js";
44
import {
55
resolveConversationBindingRecord,
66
touchConversationBindingRecord,
@@ -39,6 +39,7 @@ import { resolveSendPolicy } from "../../sessions/send-policy.js";
3939
import { normalizeTtsAutoMode, resolveConfiguredTtsMode } from "../../tts/tts-config.js";
4040
import { normalizeMessageChannel } from "../../utils/message-channel.js";
4141
import type { FinalizedMsgContext } from "../templating.js";
42+
import { normalizeVerboseLevel } from "../thinking.js";
4243
import {
4344
getReplyPayloadMetadata,
4445
type BlockReplyContext,
@@ -123,6 +124,7 @@ const resolveSessionStoreLookup = (
123124
cfg: OpenClawConfig,
124125
): {
125126
sessionKey?: string;
127+
storePath?: string;
126128
entry?: SessionEntry;
127129
} => {
128130
const targetSessionKey =
@@ -137,15 +139,39 @@ const resolveSessionStoreLookup = (
137139
const store = loadSessionStore(storePath);
138140
return {
139141
sessionKey,
142+
storePath,
140143
entry: resolveSessionStoreEntry({ store, sessionKey }).existing,
141144
};
142145
} catch {
143146
return {
144147
sessionKey,
148+
storePath,
145149
};
146150
}
147151
};
148152

153+
const createShouldEmitVerboseProgress = (params: {
154+
sessionKey?: string;
155+
storePath?: string;
156+
fallbackLevel: string;
157+
}) => {
158+
return () => {
159+
if (params.sessionKey && params.storePath) {
160+
try {
161+
const store = loadSessionStore(params.storePath);
162+
const entry = resolveSessionStoreEntry({ store, sessionKey: params.sessionKey }).existing;
163+
const currentLevel = normalizeVerboseLevel(String(entry?.verboseLevel ?? ""));
164+
if (currentLevel) {
165+
return currentLevel !== "off";
166+
}
167+
} catch {
168+
// Ignore transient store read failures and fall back to the current dispatch snapshot.
169+
}
170+
}
171+
return params.fallbackLevel !== "off";
172+
};
173+
};
174+
149175
export type DispatchFromConfigResult = {
150176
queuedFinal: boolean;
151177
counts: Record<ReplyDispatchKind, number>;
@@ -221,6 +247,21 @@ export async function dispatchReplyFromConfig(params: {
221247

222248
const sessionStoreEntry = resolveSessionStoreLookup(ctx, cfg);
223249
const acpDispatchSessionKey = sessionStoreEntry.sessionKey ?? sessionKey;
250+
const sessionAgentId = resolveSessionAgentId({ sessionKey: acpDispatchSessionKey, config: cfg });
251+
const sessionAgentCfg = resolveAgentConfig(cfg, sessionAgentId);
252+
const shouldEmitVerboseProgress = createShouldEmitVerboseProgress({
253+
sessionKey: acpDispatchSessionKey,
254+
storePath: sessionStoreEntry.storePath,
255+
fallbackLevel:
256+
normalizeVerboseLevel(
257+
String(
258+
sessionStoreEntry.entry?.verboseLevel ??
259+
sessionAgentCfg?.verboseDefault ??
260+
cfg.agents?.defaults?.verboseDefault ??
261+
"",
262+
),
263+
) ?? "off",
264+
});
224265
// Restore route thread context only from the active turn or the thread-scoped session key.
225266
// Do not read thread ids from the normalised session store here: `origin.threadId` can be
226267
// folded back into lastThreadId/deliveryContext during store normalisation and resurrect a
@@ -650,6 +691,7 @@ export async function dispatchReplyFromConfig(params: {
650691
const maybeSendWorkingStatus = (label: string) => {
651692
const normalizedLabel = normalizeWorkingLabel(label);
652693
if (
694+
!shouldEmitVerboseProgress() ||
653695
!shouldSendToolStartStatuses ||
654696
!normalizedLabel ||
655697
toolStartStatusCount >= 2 ||
@@ -668,6 +710,9 @@ export async function dispatchReplyFromConfig(params: {
668710
dispatcher.sendToolResult(payload);
669711
};
670712
const sendPlanUpdate = (payload: { explanation?: string; steps?: string[] }) => {
713+
if (!shouldEmitVerboseProgress()) {
714+
return;
715+
}
671716
const replyPayload: ReplyPayload = {
672717
text: formatPlanUpdateText(payload),
673718
};

0 commit comments

Comments
 (0)