test: fix rebased local gates

This commit is contained in:
Peter Steinberger
2026-04-18 01:49:54 +01:00
parent 27f34f0491
commit c8d722d093
13 changed files with 81 additions and 85 deletions

View File

@@ -2,13 +2,13 @@ import type { ClawdbotConfig, RuntimeEnv } from "../runtime-api.js";
import { resolveFeishuRuntimeAccount } from "./accounts.js";
import { handleFeishuMessage, type FeishuMessageEvent } from "./bot.js";
import { decodeFeishuCardAction, buildFeishuCardActionTextFallback } from "./card-interaction.js";
import { createFeishuClient } from "./client.js";
import {
createApprovalCard,
FEISHU_APPROVAL_CANCEL_ACTION,
FEISHU_APPROVAL_CONFIRM_ACTION,
FEISHU_APPROVAL_REQUEST_ACTION,
} from "./card-ux-approval.js";
import { createFeishuClient } from "./client.js";
import { sendCardFeishu, sendMessageFeishu } from "./send.js";
export type FeishuCardActionEvent = {
@@ -234,7 +234,10 @@ async function resolveCardActionChatType(params: {
normalizeResolvedCardActionChatType(response.data?.chat_mode) ??
normalizeResolvedCardActionChatType(response.data?.chat_type);
if (resolvedChatType) {
resolvedChatTypeCache.set(cacheKey, { value: resolvedChatType, expiresAt: now + CHAT_TYPE_CACHE_TTL_MS });
resolvedChatTypeCache.set(cacheKey, {
value: resolvedChatType,
expiresAt: now + CHAT_TYPE_CACHE_TTL_MS,
});
return resolvedChatType;
}
params.log(

View File

@@ -724,9 +724,7 @@ export function createTaskFlowWebhookRequestHandler(params: {
return false;
}
const resolvedSecret = await resolveTargetSecret(candidate);
return Boolean(
resolvedSecret && timingSafeEquals(resolvedSecret, presentedSecret),
);
return Boolean(resolvedSecret && timingSafeEquals(resolvedSecret, presentedSecret));
},
});
if (!target) {

View File

@@ -1,6 +1,6 @@
import { NON_ENV_SECRETREF_MARKER } from "openclaw/plugin-sdk/provider-auth-runtime";
import { createNonExitingRuntime } from "openclaw/plugin-sdk/runtime-env";
import { withEnv } from "openclaw/plugin-sdk/testing";
import { withEnv, withEnvAsync } from "openclaw/plugin-sdk/testing";
import { describe, expect, it, vi } from "vitest";
import { createWizardPrompter } from "../../test/helpers/wizard-prompter.js";
import { resolveXaiCatalogEntry } from "./model-definitions.js";
@@ -79,7 +79,7 @@ describe("xai web search config resolution", () => {
});
it("treats unresolved non-env SecretRefs as missing credentials instead of throwing", async () => {
await withEnv({ XAI_API_KEY: undefined }, async () => {
await withEnvAsync({ XAI_API_KEY: undefined }, async () => {
const provider = createXaiWebSearchProvider();
const maybeTool = provider.createTool({
config: {

View File

@@ -45,12 +45,9 @@ const GROQ_TOO_MANY_REQUESTS_MESSAGE =
"429 Too Many Requests: Too many requests were sent in a given timeframe.";
const GROQ_SERVICE_UNAVAILABLE_MESSAGE =
"503 Service Unavailable: The server is temporarily unable to handle the request due to overloading or maintenance."; // pragma: allowlist secret
const PLAIN_INTERNAL_SERVER_ERROR_STATUS_SAMPLE =
"Proxy notice: Status: Internal Server Error";
const MIXED_INTERNAL_SERVER_ERROR_STATUS_SAMPLE =
`${PLAIN_INTERNAL_SERVER_ERROR_STATUS_SAMPLE}; upstream connect error`;
const INTERNAL_SERVER_ERROR_STATUS_WITH_500_SAMPLE =
`${PLAIN_INTERNAL_SERVER_ERROR_STATUS_SAMPLE}; code:500`;
const PLAIN_INTERNAL_SERVER_ERROR_STATUS_SAMPLE = "Proxy notice: Status: Internal Server Error";
const MIXED_INTERNAL_SERVER_ERROR_STATUS_SAMPLE = `${PLAIN_INTERNAL_SERVER_ERROR_STATUS_SAMPLE}; upstream connect error`;
const INTERNAL_SERVER_ERROR_STATUS_WITH_500_SAMPLE = `${PLAIN_INTERNAL_SERVER_ERROR_STATUS_SAMPLE}; code:500`;
function expectMessageMatches(
matcher: (message: string) => boolean,

View File

@@ -137,7 +137,10 @@ export function applyFinalEffectiveToolPolicy(
isSubagentSessionKey(params.sessionKey) && params.sessionKey
? resolveSubagentToolPolicyForSession(params.config, params.sessionKey)
: undefined;
const ownerFiltered = applyOwnerOnlyToolPolicy(params.bundledTools, params.senderIsOwner === true);
const ownerFiltered = applyOwnerOnlyToolPolicy(
params.bundledTools,
params.senderIsOwner === true,
);
// Suppress unavailable-core-tool warnings on every step of this pass.
// `applyToolPolicyPipeline` infers `coreToolNames` from the `tools` array
// it's filtering, and this pass only sees the bundled MCP/LSP subset.

View File

@@ -10,8 +10,8 @@ import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js";
import { enqueueCommandInLane } from "../../process/command-queue.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import { sanitizeForLog } from "../../terminal/ansi.js";
import { isMarkdownCapableMessageChannel } from "../../utils/message-channel.js";
import { resolveUserPath } from "../../utils.js";
import { isMarkdownCapableMessageChannel } from "../../utils/message-channel.js";
import { resolveOpenClawAgentDir } from "../agent-paths.js";
import {
hasConfiguredModelFallbacks,

View File

@@ -15,7 +15,11 @@ import type { SessionEntry } from "../../config/sessions/types.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { logVerbose } from "../../globals.js";
import { clearCommandLane, getQueueSize } from "../../process/command-queue.js";
import { isAcpSessionKey, isSubagentSessionKey, normalizeMainKey } from "../../routing/session-key.js";
import {
isAcpSessionKey,
isSubagentSessionKey,
normalizeMainKey,
} from "../../routing/session-key.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import { isReasoningTagProvider } from "../../utils/provider-utils.js";
import { hasControlCommand } from "../command-detection.js";

View File

@@ -154,12 +154,15 @@ describe("buildGatewayCronService", () => {
state.cron as unknown as {
state?: {
deps?: {
enqueueSystemEvent?: (optsText: string, opts?: {
agentId?: string;
sessionKey?: string;
contextKey?: string;
trusted?: boolean;
}) => void;
enqueueSystemEvent?: (
optsText: string,
opts?: {
agentId?: string;
sessionKey?: string;
contextKey?: string;
trusted?: boolean;
},
) => void;
};
};
}

View File

@@ -72,9 +72,9 @@ vi.mock("../../agents/agent-scope.js", () => ({
}));
vi.mock("../../auto-reply/reply/session-reset-prompt.js", async () => {
const actual = await vi.importActual<typeof import("../../auto-reply/reply/session-reset-prompt.js")>(
"../../auto-reply/reply/session-reset-prompt.js",
);
const actual = await vi.importActual<
typeof import("../../auto-reply/reply/session-reset-prompt.js")
>("../../auto-reply/reply/session-reset-prompt.js");
return {
...actual,
resolveBareResetBootstrapFileAccess: mocks.resolveBareResetBootstrapFileAccess,
@@ -1278,45 +1278,48 @@ describe("gateway agent handler", () => {
});
it("uses request model override when resolving bare /new bootstrap file access", async () => {
await withTempDir({ prefix: "openclaw-gateway-reset-model-override-" }, async (workspaceDir) => {
await fs.writeFile(`${workspaceDir}/BOOTSTRAP.md`, "bootstrap ritual", "utf-8");
mocks.loadConfigReturn = {
agents: {
defaults: {
workspace: workspaceDir,
await withTempDir(
{ prefix: "openclaw-gateway-reset-model-override-" },
async (workspaceDir) => {
await fs.writeFile(`${workspaceDir}/BOOTSTRAP.md`, "bootstrap ritual", "utf-8");
mocks.loadConfigReturn = {
agents: {
defaults: {
workspace: workspaceDir,
},
},
},
};
mockSessionResetSuccess({ reason: "new" });
primeMainAgentRun({ sessionId: "reset-session-id", cfg: mocks.loadConfigReturn });
};
mockSessionResetSuccess({ reason: "new" });
primeMainAgentRun({ sessionId: "reset-session-id", cfg: mocks.loadConfigReturn });
await invokeAgent(
{
message: "/new",
sessionKey: "agent:main:main",
provider: "openai",
model: "gpt-5.4-mini",
idempotencyKey: "test-idem-new-bootstrap-model-override",
},
{
reqId: "4-bootstrap-model-override",
client: {
connect: { scopes: ["operator.admin"] },
internal: { allowModelOverride: true },
} as AgentHandlerArgs["client"],
},
);
await invokeAgent(
{
message: "/new",
sessionKey: "agent:main:main",
provider: "openai",
model: "gpt-5.4-mini",
idempotencyKey: "test-idem-new-bootstrap-model-override",
},
{
reqId: "4-bootstrap-model-override",
client: {
connect: { scopes: ["operator.admin"] },
internal: { allowModelOverride: true },
} as AgentHandlerArgs["client"],
},
);
await waitForAssertion(() =>
expect(mocks.resolveBareResetBootstrapFileAccess).toHaveBeenCalled(),
);
expect(mocks.resolveBareResetBootstrapFileAccess).toHaveBeenCalledWith(
expect.objectContaining({
modelProvider: "openai",
modelId: "gpt-5.4-mini",
}),
);
});
await waitForAssertion(() =>
expect(mocks.resolveBareResetBootstrapFileAccess).toHaveBeenCalled(),
);
expect(mocks.resolveBareResetBootstrapFileAccess).toHaveBeenCalledWith(
expect.objectContaining({
modelProvider: "openai",
modelId: "gpt-5.4-mini",
}),
);
},
);
});
it("rejects malformed agent session keys early in agent handler", async () => {

View File

@@ -991,9 +991,7 @@ describe("gateway send mirroring", () => {
// forced to false so a non-admin scoped caller cannot unlock owner-only
// channel actions.
setActivePluginRegistry(
createTestRegistry([
{ pluginId: "whatsapp", source: "test", plugin: reactPlugin },
]),
createTestRegistry([{ pluginId: "whatsapp", source: "test", plugin: reactPlugin }]),
"send-test-owner-derive-non-admin",
);
await runMessageActionRequest(
@@ -1011,9 +1009,7 @@ describe("gateway send mirroring", () => {
// Full operator (admin-scoped): the trusted runtime is allowed to
// forward the real channel-sender ownership bit. Wire true → true.
setActivePluginRegistry(
createTestRegistry([
{ pluginId: "whatsapp", source: "test", plugin: reactPlugin },
]),
createTestRegistry([{ pluginId: "whatsapp", source: "test", plugin: reactPlugin }]),
"send-test-owner-derive-admin-true",
);
await runMessageActionRequest(
@@ -1031,9 +1027,7 @@ describe("gateway send mirroring", () => {
// Full operator forwarding a non-owner sender: wire false → false
// (admin scope does not inflate ownership on its own).
setActivePluginRegistry(
createTestRegistry([
{ pluginId: "whatsapp", source: "test", plugin: reactPlugin },
]),
createTestRegistry([{ pluginId: "whatsapp", source: "test", plugin: reactPlugin }]),
"send-test-owner-derive-admin-false",
);
await runMessageActionRequest(

View File

@@ -26,6 +26,7 @@ import {
normalizeOptionalString,
readStringValue,
} from "../../shared/string-coerce.js";
import { ADMIN_SCOPE } from "../method-scopes.js";
import {
ErrorCodes,
errorShape,
@@ -34,7 +35,6 @@ import {
validatePollParams,
validateSendParams,
} from "../protocol/index.js";
import { ADMIN_SCOPE } from "../method-scopes.js";
import { formatForLog } from "../ws-log.js";
import type { GatewayRequestContext, GatewayRequestHandlers } from "./types.js";
@@ -231,8 +231,7 @@ export const sendHandlers: GatewayRequestHandlers = {
// from unlocking owner-only channel actions by setting
// `senderIsOwner: true` on the request.
const callerScopes = client?.connect?.scopes ?? [];
const callerIsFullOperator =
Array.isArray(callerScopes) && callerScopes.includes(ADMIN_SCOPE);
const callerIsFullOperator = Array.isArray(callerScopes) && callerScopes.includes(ADMIN_SCOPE);
const senderIsOwner = callerIsFullOperator && request.senderIsOwner === true;
const idem = request.idempotencyKey;
const dedupeKey = `message.action:${idem}`;

View File

@@ -9,14 +9,8 @@ describe("sanitizeExecApprovalDisplayText", () => {
["echo hi\u200Bthere", "echo hi\\u{200B}there"],
["date\u3164\uFFA0\u115F\u1160가", "date\\u{3164}\\u{FFA0}\\u{115F}\\u{1160}가"],
["echo safe\n\rcurl https://example.test", "echo safe\\u{A}\\u{D}curl https://example.test"],
[
"echo ok\u2028curl https://example.test",
"echo ok\\u{2028}curl https://example.test",
],
[
"echo ok\u2029curl https://example.test",
"echo ok\\u{2029}curl https://example.test",
],
["echo ok\u2028curl https://example.test", "echo ok\\u{2028}curl https://example.test"],
["echo ok\u2029curl https://example.test", "echo ok\\u{2029}curl https://example.test"],
])("sanitizes exec approval display text for %j", (input, expected) => {
expect(sanitizeExecApprovalDisplayText(input)).toBe(expected);
});

View File

@@ -1,4 +1,5 @@
import { listBundledChannelPlugins } from "../../../src/channels/plugins/bundled.js";
import { normalizeChannelMeta } from "../../../src/channels/plugins/meta-normalization.js";
import type { ChannelPlugin } from "../../../src/channels/plugins/types.js";
type PluginContractEntry = {
@@ -11,10 +12,7 @@ export function getPluginContractRegistry(): PluginContractEntry[] {
id: plugin.id,
plugin: {
...plugin,
meta: {
...plugin.meta,
id: plugin.id,
},
meta: normalizeChannelMeta({ id: plugin.id, meta: plugin.meta }),
},
}));
}