mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 20:31:22 +00:00
fix: land NO_REPLY announce suppression and auth scope assertions
Landed follow-up for #27535 and aligned shared-auth gateway expectations after #27498. Co-authored-by: kevinWangSheng <118158941+kevinWangSheng@users.noreply.github.com>
This commit is contained in:
@@ -184,10 +184,16 @@ function isDirectOpenAIBaseUrl(baseUrl: unknown): boolean {
|
||||
|
||||
try {
|
||||
const host = new URL(baseUrl).hostname.toLowerCase();
|
||||
return host === "api.openai.com" || host === "chatgpt.com" || host.endsWith(".openai.azure.com");
|
||||
return (
|
||||
host === "api.openai.com" || host === "chatgpt.com" || host.endsWith(".openai.azure.com")
|
||||
);
|
||||
} catch {
|
||||
const normalized = baseUrl.toLowerCase();
|
||||
return normalized.includes("api.openai.com") || normalized.includes("chatgpt.com") || normalized.includes(".openai.azure.com");
|
||||
return (
|
||||
normalized.includes("api.openai.com") ||
|
||||
normalized.includes("chatgpt.com") ||
|
||||
normalized.includes(".openai.azure.com")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -435,6 +435,23 @@ describe("subagent announce formatting", () => {
|
||||
expect(sessionsDeleteSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("suppresses completion delivery when subagent reply is NO_REPLY", async () => {
|
||||
const didAnnounce = await runSubagentAnnounceFlow({
|
||||
childSessionKey: "agent:main:subagent:test",
|
||||
childRunId: "run-direct-completion-no-reply",
|
||||
requesterSessionKey: "agent:main:main",
|
||||
requesterDisplayKey: "main",
|
||||
requesterOrigin: { channel: "slack", to: "channel:C123", accountId: "acct-1" },
|
||||
...defaultOutcomeAnnounce,
|
||||
expectsCompletionMessage: true,
|
||||
roundOneReply: " NO_REPLY ",
|
||||
});
|
||||
|
||||
expect(didAnnounce).toBe(true);
|
||||
expect(sendSpy).not.toHaveBeenCalled();
|
||||
expect(agentSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("retries completion direct send on transient channel-unavailable errors", async () => {
|
||||
sendSpy
|
||||
.mockRejectedValueOnce(new Error("Error: No active WhatsApp Web listener (account: default)"))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { resolveQueueSettings } from "../auto-reply/reply/queue.js";
|
||||
import { SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js";
|
||||
import { isSilentReplyText, SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js";
|
||||
import { DEFAULT_SUBAGENT_MAX_SPAWN_DEPTH } from "../config/agent-limits.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import {
|
||||
@@ -1161,6 +1161,9 @@ export async function runSubagentAnnounceFlow(params: {
|
||||
if (isAnnounceSkip(reply)) {
|
||||
return true;
|
||||
}
|
||||
if (isSilentReplyText(reply, SILENT_REPLY_TOKEN)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!outcome) {
|
||||
outcome = { status: "unknown" };
|
||||
|
||||
@@ -416,13 +416,14 @@ describe("gateway server auth/connect", () => {
|
||||
opts: Parameters<typeof connectReq>[1];
|
||||
expectConnectOk: boolean;
|
||||
expectConnectError?: string;
|
||||
expectStatusOk?: boolean;
|
||||
expectStatusError?: string;
|
||||
}> = [
|
||||
{
|
||||
name: "operator + valid shared token => connected with zero scopes",
|
||||
name: "operator + valid shared token => connected with preserved scopes",
|
||||
opts: { role: "operator", token, device: null },
|
||||
expectConnectOk: true,
|
||||
expectStatusError: "missing scope",
|
||||
expectStatusOk: true,
|
||||
},
|
||||
{
|
||||
name: "node + valid shared token => rejected without device",
|
||||
@@ -449,12 +450,14 @@ describe("gateway server auth/connect", () => {
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (scenario.expectStatusError) {
|
||||
if (scenario.expectStatusOk !== undefined) {
|
||||
const status = await rpcReq(ws, "status");
|
||||
expect(status.ok, scenario.name).toBe(false);
|
||||
expect(status.error?.message ?? "", scenario.name).toContain(
|
||||
scenario.expectStatusError,
|
||||
);
|
||||
expect(status.ok, scenario.name).toBe(scenario.expectStatusOk);
|
||||
if (!scenario.expectStatusOk && scenario.expectStatusError) {
|
||||
expect(status.error?.message ?? "", scenario.name).toContain(
|
||||
scenario.expectStatusError,
|
||||
);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
ws.close();
|
||||
@@ -811,8 +814,7 @@ describe("gateway server auth/connect", () => {
|
||||
const res = await connectReq(ws, { token: "secret", device: null });
|
||||
expect(res.ok).toBe(true);
|
||||
const status = await rpcReq(ws, "status");
|
||||
expect(status.ok).toBe(false);
|
||||
expect(status.error?.message).toContain("missing scope");
|
||||
expect(status.ok).toBe(true);
|
||||
const health = await rpcReq(ws, "health");
|
||||
expect(health.ok).toBe(true);
|
||||
ws.close();
|
||||
@@ -896,8 +898,7 @@ describe("gateway server auth/connect", () => {
|
||||
}
|
||||
if (tc.expectStatusChecks) {
|
||||
const status = await rpcReq(ws, "status");
|
||||
expect(status.ok).toBe(false);
|
||||
expect(status.error?.message ?? "").toContain("missing scope");
|
||||
expect(status.ok).toBe(true);
|
||||
const health = await rpcReq(ws, "health");
|
||||
expect(health.ok).toBe(true);
|
||||
}
|
||||
@@ -923,8 +924,7 @@ describe("gateway server auth/connect", () => {
|
||||
});
|
||||
expect(res.ok).toBe(true);
|
||||
const status = await rpcReq(ws, "status");
|
||||
expect(status.ok).toBe(false);
|
||||
expect(status.error?.message ?? "").toContain("missing scope");
|
||||
expect(status.ok).toBe(true);
|
||||
const health = await rpcReq(ws, "health");
|
||||
expect(health.ok).toBe(true);
|
||||
ws.close();
|
||||
@@ -946,8 +946,7 @@ describe("gateway server auth/connect", () => {
|
||||
});
|
||||
expect(res.ok).toBe(true);
|
||||
const status = await rpcReq(ws, "status");
|
||||
expect(status.ok).toBe(false);
|
||||
expect(status.error?.message ?? "").toContain("missing scope");
|
||||
expect(status.ok).toBe(true);
|
||||
const health = await rpcReq(ws, "health");
|
||||
expect(health.ok).toBe(true);
|
||||
ws.close();
|
||||
|
||||
Reference in New Issue
Block a user