fix(cli): keep empty agent replies silent

* fix(cli): keep empty agent replies silent

* fix(commands): preserve empty gateway status summaries

---------

Co-authored-by: Peter Steinberger <steipete@steipete-macstudio.local>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
Peter Steinberger
2026-05-03 07:47:59 +01:00
committed by GitHub
parent c7bbb3f9af
commit 95cee64ca6
6 changed files with 65 additions and 2 deletions

View File

@@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Agents/sessions: keep delayed `sessions_send` A2A replies alive after soft wait-window timeouts, while preserving terminal run timeouts and avoiding stale target replies in requester sessions. Fixes #76443. Thanks @ryswork1993 and @vincentkoc.
- CLI/sessions: keep intentional empty agent replies silent after tool-delivered channel output, instead of surfacing a misleading "No reply from agent." fallback. Thanks @vincentkoc.
- Config/doctor: cap `.clobbered.*` forensic snapshots per config path and serialize snapshot writes so repeated `doctor --fix` recovery loops cannot flood the config directory. Fixes #76454; carries forward #65649. Thanks @JUSTICEESSIELP, @rsnow, and @vincentkoc.
- Feishu: suppress duplicate text when replies send native voice media while preserving captions for ordinary audio files and falling back to text plus attachment links when voice uploads fail.
- Feishu: keep packaged Feishu startup from bundling the Lark SDK's ESM `__dirname` path by loading the SDK as a plugin-local runtime dependency. Fixes #76291 and #76494. (#76392) Thanks @zqchris.

View File

@@ -350,7 +350,6 @@ export async function deliverAgentCommandResult(params: {
}
if (!payloads || payloads.length === 0) {
runtime.log("No reply from agent.");
return { payloads: [], meta: resultMeta };
}

View File

@@ -147,6 +147,42 @@ describe("agentCliCommand", () => {
});
});
it("stays silent when the gateway returns an intentional empty reply", async () => {
await withTempStore(async () => {
callGateway.mockResolvedValue({
runId: "idem-1",
status: "ok",
summary: "completed",
result: {
payloads: [],
meta: { stub: true },
},
});
await agentCliCommand({ message: "hi", to: "+1555" }, runtime);
expect(runtime.log).not.toHaveBeenCalled();
});
});
it("logs non-ok gateway summaries when payloads are empty", async () => {
await withTempStore(async () => {
callGateway.mockResolvedValue({
runId: "idem-1",
status: "timeout",
summary: "aborted",
result: {
payloads: [],
meta: { aborted: true },
},
});
await agentCliCommand({ message: "hi", to: "+1555" }, runtime);
expect(runtime.log).toHaveBeenCalledWith("aborted");
});
});
it("passes model overrides through gateway requests", async () => {
await withTempStore(async () => {
mockGatewaySuccessReply();

View File

@@ -201,7 +201,9 @@ async function agentViaGatewayCommand(opts: AgentCliOpts, runtime: RuntimeEnv) {
const payloads = result?.payloads ?? [];
if (payloads.length === 0) {
runtime.log(response?.summary ? response.summary : "No reply from agent.");
if (response?.status !== "ok") {
runtime.log(response?.summary ? response.summary : "No reply from agent.");
}
return response;
}

View File

@@ -378,6 +378,16 @@ describe("agentCommand ACP runtime routing", () => {
});
});
it("keeps no-reply ACP turns silent", async () => {
await withAcpSessionEnv(async () => {
const { assistantEvents, logLines } = await runAcpTurnWithAssistantEvents(["NO_REPLY"]);
expect(assistantEvents.map((event) => event.text).filter(Boolean)).toEqual([]);
expect(logLines.some((line) => line.includes("NO_REPLY"))).toBe(false);
expect(logLines).toEqual([]);
});
});
it("fails closed for ACP-shaped session keys missing ACP metadata", async () => {
await withTempHome(async (home) => {
const storePath = path.join(home, "sessions.json");

View File

@@ -200,6 +200,21 @@ describe("deliverAgentCommandResult", () => {
);
});
it("stays silent for intentional empty payloads", async () => {
const runtime = createRuntime();
await runDelivery({
opts: {
message: "hello",
},
runtime,
payloads: [],
});
expect(runtime.log).not.toHaveBeenCalled();
expect(mocks.deliverOutboundPayloads).not.toHaveBeenCalled();
});
it("uses runContext turn source over stale session last route", async () => {
await runDelivery({
opts: {