chore(lint): enable stricter oxlint rules

This commit is contained in:
Peter Steinberger
2026-05-31 18:43:54 +01:00
parent cb569f6ad9
commit 304e2c83c0
615 changed files with 3603 additions and 3701 deletions

View File

@@ -2,7 +2,7 @@
// Secret scanning alert handler for OpenClaw maintainers.
// Usage: node secret-scanning.mjs <command> [options]
import { execFileSync, spawnSync } from "node:child_process";
import { spawnSync } from "node:child_process";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
@@ -39,7 +39,9 @@ function gh(args, { json = true, allowFailure = false } = {}) {
stderr: proc.stderr,
};
}
if (!json) return proc.stdout;
if (!json) {
return proc.stdout;
}
try {
return JSON.parse(proc.stdout);
} catch {
@@ -70,7 +72,9 @@ export function loadBodyRedactionResult(locationType, resultFile) {
if (!resultFile) {
fail("Body notifications require a redaction result file from redact-body-if-needed");
}
if (!fs.existsSync(resultFile)) fail(`File not found: ${resultFile}`);
if (!fs.existsSync(resultFile)) {
fail(`File not found: ${resultFile}`);
}
const result = JSON.parse(fs.readFileSync(resultFile, "utf8"));
if (typeof result.notify_required !== "boolean") {
@@ -182,10 +186,11 @@ function fetchDiscussionComment(discussionNumber, discussionCommentDbId) {
failOnGraphQLFailure(gql, `Failed to fetch discussion #${discussionNumber}`);
const discussion = gql?.data?.repository?.discussion;
if (!discussion)
if (!discussion) {
fail(
`Discussion #${discussionNumber} not found — it may have been deleted. The alert cannot be processed via this skill.`,
);
}
discussionId = discussion.id;
@@ -205,15 +210,18 @@ function fetchDiscussionComment(discussionNumber, discussionCommentDbId) {
`Failed to fetch replies for discussion comment ${topLevelComment.id}`,
);
const replies = replyPage?.data?.node?.replies;
if (!replies)
if (!replies) {
fail(`Failed to paginate replies for discussion comment ${topLevelComment.id}`);
}
reply = findDiscussionCommentNode(replies.nodes, discussionCommentDbId);
hasMoreReplies = replies.pageInfo.hasNextPage;
replyCursor = replies.pageInfo.endCursor;
}
if (reply) return { discussionId, comment: reply };
if (reply) {
return { discussionId, comment: reply };
}
}
hasNextPage = discussion.comments.pageInfo.hasNextPage;
@@ -241,7 +249,9 @@ function createDiscussionComment(discussionNodeId, body, replyToNodeId) {
* Fetch alert metadata + locations. Never exposes .secret.
*/
function cmdFetchAlert(alertNumber) {
if (!alertNumber) fail("Usage: fetch-alert <number>");
if (!alertNumber) {
fail("Usage: fetch-alert <number>");
}
const alert = gh(["api", `repos/${REPO}/secret-scanning/alerts/${alertNumber}?hide_secret=true`]);
@@ -280,17 +290,23 @@ function cmdFetchAlert(alertNumber) {
* Saves full body to a temp file. Prints metadata + file path to stdout.
*/
function cmdFetchContent(locationJson) {
if (!locationJson) fail("Usage: fetch-content '<location-json>'");
if (!locationJson) {
fail("Usage: fetch-content '<location-json>'");
}
const location = JSON.parse(locationJson);
const type = location.type;
const details = location.details;
if (type === "discussion_comment") {
const commentUrl = details.discussion_comment_url;
if (!commentUrl) fail("No discussion_comment_url in location details");
if (!commentUrl) {
fail("No discussion_comment_url in location details");
}
const urlMatch = commentUrl.match(/discussions\/(\d+)#discussioncomment-(\d+)/);
if (!urlMatch) fail(`Cannot parse discussion comment URL: ${commentUrl}`);
if (!urlMatch) {
fail(`Cannot parse discussion comment URL: ${commentUrl}`);
}
const discussionNumber = urlMatch[1];
const discussionCommentDbId = urlMatch[2];
@@ -298,10 +314,11 @@ function cmdFetchContent(locationJson) {
discussionNumber,
discussionCommentDbId,
);
if (!comment)
if (!comment) {
fail(
`Discussion comment #${discussionCommentDbId} not found in discussion #${discussionNumber}`,
);
}
const bodyFile = tmpFile("body.md");
fs.writeFileSync(bodyFile, comment.body || "");
@@ -334,7 +351,9 @@ function cmdFetchContent(locationJson) {
details.issue_comment_url ||
details.pull_request_comment_url ||
details.pull_request_review_comment_url;
if (!commentUrl) fail(`No comment URL in location details`);
if (!commentUrl) {
fail(`No comment URL in location details`);
}
const comment = gh(["api", commentUrl]);
const bodyFile = tmpFile("body.md");
@@ -378,7 +397,9 @@ function cmdFetchContent(locationJson) {
);
} else if (type === "issue_body") {
const issueUrl = details.issue_body_url || details.issue_url;
if (!issueUrl) fail("No issue URL in location details");
if (!issueUrl) {
fail("No issue URL in location details");
}
const issue = gh(["api", issueUrl]);
const bodyFile = tmpFile("body.md");
@@ -414,7 +435,9 @@ function cmdFetchContent(locationJson) {
);
} else if (type === "pull_request_body") {
const prUrl = details.pull_request_body_url || details.pull_request_url;
if (!prUrl) fail("No PR URL in location details");
if (!prUrl) {
fail("No PR URL in location details");
}
const pr = gh(["api", prUrl]);
const bodyFile = tmpFile("body.md");
@@ -490,7 +513,9 @@ function cmdRedactBody(kind, number, bodyFile) {
if (!kind || !number || !bodyFile) {
fail("Usage: redact-body <issue|pr> <number> <redacted-body-file>");
}
if (!fs.existsSync(bodyFile)) fail(`File not found: ${bodyFile}`);
if (!fs.existsSync(bodyFile)) {
fail(`File not found: ${bodyFile}`);
}
const endpoint =
kind === "pr" ? `repos/${REPO}/pulls/${number}` : `repos/${REPO}/issues/${number}`;
@@ -509,8 +534,12 @@ function cmdRedactBodyIfNeeded(kind, number, currentBodyFile, redactedBodyFile,
"Usage: redact-body-if-needed <issue|pr> <number> <current-body-file> <redacted-body-file> <result-file>",
);
}
if (!fs.existsSync(currentBodyFile)) fail(`File not found: ${currentBodyFile}`);
if (!fs.existsSync(redactedBodyFile)) fail(`File not found: ${redactedBodyFile}`);
if (!fs.existsSync(currentBodyFile)) {
fail(`File not found: ${currentBodyFile}`);
}
if (!fs.existsSync(redactedBodyFile)) {
fail(`File not found: ${redactedBodyFile}`);
}
const currentBody = fs.readFileSync(currentBodyFile, "utf8");
const redactedBody = fs.readFileSync(redactedBodyFile, "utf8");
@@ -541,7 +570,9 @@ function cmdRedactBodyIfNeeded(kind, number, currentBodyFile, redactedBodyFile,
* Delete a comment (and all its edit history).
*/
function cmdDeleteComment(commentId) {
if (!commentId) fail("Usage: delete-comment <comment-id>");
if (!commentId) {
fail("Usage: delete-comment <comment-id>");
}
gh(["api", `repos/${REPO}/issues/comments/${commentId}`, "-X", "DELETE"], { json: false });
console.log(JSON.stringify({ ok: true, deleted_comment_id: Number(commentId) }));
}
@@ -551,7 +582,9 @@ function cmdDeleteComment(commentId) {
* Delete a discussion comment via GraphQL (and all its edit history).
*/
function cmdDeleteDiscussionComment(nodeId) {
if (!nodeId) fail("Usage: delete-discussion-comment <node-id>");
if (!nodeId) {
fail("Usage: delete-discussion-comment <node-id>");
}
const result = ghGraphQL(
`mutation { deleteDiscussionComment(input: { id: "${nodeId}" }) { comment { id } } }`,
);
@@ -566,9 +599,12 @@ function cmdDeleteDiscussionComment(nodeId) {
* Create a new discussion comment via GraphQL.
*/
function cmdRecreateDiscussionComment(discussionNodeId, bodyFile, replyToNodeId) {
if (!discussionNodeId || !bodyFile)
if (!discussionNodeId || !bodyFile) {
fail("Usage: recreate-discussion-comment <discussion-node-id> <body-file> [reply-to-node-id]");
if (!fs.existsSync(bodyFile)) fail(`File not found: ${bodyFile}`);
}
if (!fs.existsSync(bodyFile)) {
fail(`File not found: ${bodyFile}`);
}
const body = fs.readFileSync(bodyFile, "utf8");
const newComment = createDiscussionComment(discussionNodeId, body, replyToNodeId);
@@ -586,8 +622,12 @@ function cmdRecreateDiscussionComment(discussionNodeId, bodyFile, replyToNodeId)
* Create a new comment from a file.
*/
function cmdRecreateComment(issueNumber, bodyFile) {
if (!issueNumber || !bodyFile) fail("Usage: recreate-comment <issue-number> <body-file>");
if (!fs.existsSync(bodyFile)) fail(`File not found: ${bodyFile}`);
if (!issueNumber || !bodyFile) {
fail("Usage: recreate-comment <issue-number> <body-file>");
}
if (!fs.existsSync(bodyFile)) {
fail(`File not found: ${bodyFile}`);
}
const result = gh([
"api",
@@ -715,7 +755,9 @@ function cmdNotify(target, author, locationType, secretTypes, replyToNodeId) {
* Close a secret scanning alert.
*/
function cmdResolve(alertNumber, resolution, comment) {
if (!alertNumber) fail("Usage: resolve <alert-number> [resolution] [comment]");
if (!alertNumber) {
fail("Usage: resolve <alert-number> [resolution] [comment]");
}
const res = resolution || "revoked";
const resComment = comment || "Content redacted and author notified to rotate credentials.";
@@ -773,8 +815,12 @@ function cmdListOpen() {
* Print a formatted summary table from a JSON results file.
*/
function cmdSummary(jsonFile) {
if (!jsonFile) fail("Usage: summary <json-file>");
if (!fs.existsSync(jsonFile)) fail(`File not found: ${jsonFile}`);
if (!jsonFile) {
fail("Usage: summary <json-file>");
}
if (!fs.existsSync(jsonFile)) {
fail(`File not found: ${jsonFile}`);
}
const results = JSON.parse(fs.readFileSync(jsonFile, "utf8"));
const lines = [];

View File

@@ -26,7 +26,7 @@
"eslint/no-return-assign": "error",
"eslint/no-sequences": "error",
"eslint/no-self-compare": "error",
"eslint/no-shadow": "off",
"eslint/no-shadow": "error",
"eslint/no-implicit-coercion": "error",
"eslint/no-var": "error",
"eslint/no-useless-call": "error",
@@ -35,7 +35,7 @@
"eslint/no-useless-constructor": "error",
"eslint/no-useless-rename": "error",
"eslint/no-useless-return": "error",
"eslint/no-unused-vars": "off",
"eslint/no-unused-vars": "error",
"eslint/no-warning-comments": "error",
"eslint/no-unmodified-loop-condition": "error",
"eslint/no-new-wrappers": "error",
@@ -229,15 +229,7 @@
"**/*test-support.ts"
],
"rules": {
"typescript/no-explicit-any": "off",
"typescript/unbound-method": "off",
"eslint/no-unsafe-optional-chaining": "off"
}
},
{
"files": ["src/agents/embedded-agent-runner/run/attempt.ts"],
"rules": {
"eslint/no-shadow": "error"
"typescript/no-explicit-any": "off"
}
}
]

View File

@@ -37,8 +37,8 @@ async function startRealService(state: DeferredServiceState): Promise<AcpRuntime
throw new Error("ACPX runtime service is not started");
}
state.startPromise ??= (async () => {
const { createAcpxRuntimeService } = await loadServiceModule();
const service = createAcpxRuntimeService(state.params);
const { createAcpxRuntimeService: createAcpxRuntimeServiceLocal } = await loadServiceModule();
const service = createAcpxRuntimeServiceLocal(state.params);
state.realService = service;
await service.start(state.ctx as OpenClawPluginServiceContext);
const backend = getAcpRuntimeBackend(ACPX_BACKEND_ID);

View File

@@ -335,7 +335,7 @@ describe("AcpxRuntime fresh reset wrapper", () => {
});
await expect(async () => {
for await (const eventValue of runtime.runTurn({
for await (const ignoredEventValue of runtime.runTurn({
handle: {
sessionKey: "agent:codex:acp:test",
backend: "acpx",
@@ -346,6 +346,7 @@ describe("AcpxRuntime fresh reset wrapper", () => {
mode: "prompt",
requestId: "turn-1",
})) {
void ignoredEventValue;
// no-op
}
}).rejects.toMatchObject({
@@ -568,7 +569,7 @@ describe("AcpxRuntime fresh reset wrapper", () => {
}),
);
for await (const eventValue of runtime.runTurn({
for await (const ignoredEventValue of runtime.runTurn({
handle: {
sessionKey: "agent:codex:acp:test",
backend: "acpx",
@@ -579,6 +580,7 @@ describe("AcpxRuntime fresh reset wrapper", () => {
mode: "prompt",
requestId: "turn-1",
})) {
void ignoredEventValue;
// no-op
}
@@ -599,7 +601,8 @@ describe("AcpxRuntime fresh reset wrapper", () => {
mode: "prompt",
requestId: "turn-2",
});
for await (const eventValue of turn.events) {
for await (const ignoredEventValue of turn.events) {
void ignoredEventValue;
// no-op
}
await turn.result;
@@ -947,16 +950,16 @@ describe("AcpxRuntime fresh reset wrapper", () => {
expect(await wrappedStore.load("agent:codex:acp:binding:test")).toEqual({
acpxRecordId: "stale",
});
expect(baseStore.load).toHaveBeenCalledTimes(1);
expect(baseStore["load"]).toHaveBeenCalledTimes(1);
await runtime.prepareFreshSession({
sessionKey: "agent:codex:acp:binding:test",
});
expect(await wrappedStore.load("agent:codex:acp:binding:test")).toBeUndefined();
expect(baseStore.load).toHaveBeenCalledTimes(1);
expect(baseStore["load"]).toHaveBeenCalledTimes(1);
expect(await wrappedStore.load("agent:codex:acp:binding:test")).toBeUndefined();
expect(baseStore.load).toHaveBeenCalledTimes(1);
expect(baseStore["load"]).toHaveBeenCalledTimes(1);
await wrappedStore.save({
acpxRecordId: "fresh-record",
@@ -966,7 +969,7 @@ describe("AcpxRuntime fresh reset wrapper", () => {
expect(await wrappedStore.load("agent:codex:acp:binding:test")).toEqual({
acpxRecordId: "stale",
});
expect(baseStore.load).toHaveBeenCalledTimes(2);
expect(baseStore["load"]).toHaveBeenCalledTimes(2);
});
it("marks the session fresh after discardPersistentState close", async () => {
@@ -998,7 +1001,7 @@ describe("AcpxRuntime fresh reset wrapper", () => {
discardPersistentState: true,
});
expect(await wrappedStore.load("agent:codex:acp:binding:test")).toBeUndefined();
expect(baseStore.load).toHaveBeenCalledOnce();
expect(baseStore["load"]).toHaveBeenCalledOnce();
});
it("cleans up OpenClaw-owned ACPX process trees after close", async () => {

View File

@@ -605,25 +605,25 @@ function resolveRecallRunChannelContext(params: {
(!explicitProvider || explicitProvider === "webchat")
? runnableExplicitChannel
: undefined;
const resolveReturnValue = (params: {
const resolveReturnValue = (paramsLocal: {
resolvedChannel?: string;
resolvedChannelStrength?: "strong" | "weak";
}) => {
const trustedResolvedChannel =
params.resolvedChannelStrength === "strong" ? params.resolvedChannel : undefined;
paramsLocal.resolvedChannelStrength === "strong" ? paramsLocal.resolvedChannel : undefined;
return {
messageChannel:
trustedExplicitChannel ??
trustedResolvedChannel ??
explicitProvider ??
runnableExplicitChannel ??
params.resolvedChannel,
paramsLocal.resolvedChannel,
messageProvider:
trustedExplicitChannel ??
trustedResolvedChannel ??
explicitProvider ??
runnableExplicitChannel ??
params.resolvedChannel,
paramsLocal.resolvedChannel,
};
};
const resolvedSessionKey =

View File

@@ -1,4 +1,4 @@
import type { Api, Model } from "openclaw/plugin-sdk/llm";
import type { Model } from "openclaw/plugin-sdk/llm";
import { describe, expect, it, vi } from "vitest";
import {
createMantleAnthropicStreamFn,

View File

@@ -365,29 +365,29 @@ export async function createBedrockEmbeddingProvider(
const embedQuery = async (
text: string,
options?: { signal?: AbortSignal },
optionsValue?: { signal?: AbortSignal },
): Promise<number[]> => {
if (!text.trim()) {
return [];
}
if (isCohere) {
return (await embedCohere([text], "search_query", options?.signal))[0] ?? [];
return (await embedCohere([text], "search_query", optionsValue?.signal))[0] ?? [];
}
return embedSingle(text, options?.signal);
return embedSingle(text, optionsValue?.signal);
};
const embedBatch = async (
texts: string[],
options?: { signal?: AbortSignal },
optionsLocal?: { signal?: AbortSignal },
): Promise<number[][]> => {
if (texts.length === 0) {
return [];
}
if (isCohere) {
return embedCohere(texts, "search_document", options?.signal);
return embedCohere(texts, "search_document", optionsLocal?.signal);
}
return Promise.all(
texts.map((t) => (t.trim() ? embedSingle(t, options?.signal) : Promise.resolve([]))),
texts.map((t) => (t.trim() ? embedSingle(t, optionsLocal?.signal) : Promise.resolve([]))),
);
};

View File

@@ -272,7 +272,7 @@ describe("amazon-bedrock provider plugin", () => {
setBedrockAppProfileControlPlaneForTest((region) => ({
async getInferenceProfile(input) {
class GetInferenceProfileCommand {
constructor(readonly input: Record<string, unknown> = {}) {}
constructor(readonly inputLocal: Record<string, unknown> = {}) {}
}
bedrockClientConfigs.push(region ? { region } : {});
return await sendBedrockCommand(new GetInferenceProfileCommand(input));

View File

@@ -127,11 +127,11 @@ export default definePluginEntry({
: undefined;
},
normalizeResolvedModel: ({ model }) => normalizeArceeResolvedModel(model),
normalizeTransport: ({ api, baseUrl }) => {
normalizeTransport: ({ api: apiLocal, baseUrl }) => {
const normalizedBaseUrl = normalizeArceeOpenRouterBaseUrl(baseUrl);
return normalizedBaseUrl && normalizedBaseUrl !== baseUrl
? {
api,
api: apiLocal,
baseUrl: normalizedBaseUrl,
}
: undefined;

View File

@@ -495,7 +495,10 @@ export async function startGatewayBonjourAdvertiser(
return { responder, services };
}
async function stopCycle(cycle: BonjourCycle | null, opts?: { shutdownResponder?: boolean }) {
async function stopCycle(
cycle: BonjourCycle | null,
optsValue?: { shutdownResponder?: boolean },
) {
if (!cycle) {
return;
}
@@ -507,7 +510,7 @@ export async function startGatewayBonjourAdvertiser(
}
}
try {
if (opts?.shutdownResponder) {
if (optsValue?.shutdownResponder) {
await cycle.responder.shutdown();
}
} catch {
@@ -615,7 +618,7 @@ export async function startGatewayBonjourAdvertiser(
}
};
const recreateAdvertiser = async (reason: string, opts?: { stuckState?: boolean }) => {
const recreateAdvertiser = async (reason: string, optsLocal?: { stuckState?: boolean }) => {
if (stopped || disabled) {
return;
}
@@ -624,7 +627,9 @@ export async function startGatewayBonjourAdvertiser(
}
recreatePromise = (async () => {
consecutiveRestarts += 1;
consecutiveStuckStateRestarts = opts?.stuckState ? consecutiveStuckStateRestarts + 1 : 0;
consecutiveStuckStateRestarts = optsLocal?.stuckState
? consecutiveStuckStateRestarts + 1
: 0;
const now = Date.now();
while (
restartTimestamps.length > 0 &&

View File

@@ -530,7 +530,7 @@ export function createBrowserTool(opts?: {
});
const proxyRequest = nodeTarget
? async (opts: {
? async (optsLocal: {
method: string;
path: string;
query?: Record<string, string | number | boolean | undefined>;
@@ -540,12 +540,12 @@ export function createBrowserTool(opts?: {
}) => {
const proxy = await callBrowserProxy({
nodeId: nodeTarget.nodeId,
method: opts.method,
path: opts.path,
query: opts.query,
body: opts.body,
timeoutMs: opts.timeoutMs,
profile: opts.profile,
method: optsLocal.method,
path: optsLocal.path,
query: optsLocal.query,
body: optsLocal.body,
timeoutMs: optsLocal.timeoutMs,
profile: optsLocal.profile,
});
const mapping = await persistProxyFiles(proxy.files);
applyProxyPaths(proxy.result, mapping);

View File

@@ -232,12 +232,13 @@ describe("cdp-proxy-bypass", () => {
describe("withNoProxyForLocalhost concurrency", () => {
it("does not leak NO_PROXY when called concurrently", async () => {
await withIsolatedNoProxyEnv(async () => {
const { withNoProxyForLocalhost } = await import("./cdp-proxy-bypass.js");
const { withNoProxyForLocalhost: withNoProxyForLocalhostScoped } =
await import("./cdp-proxy-bypass.js");
const releaseA = createDeferred();
const enteredA = createDeferred();
const callA = withNoProxyForLocalhost(async () => {
const callA = withNoProxyForLocalhostScoped(async () => {
expect(process.env.NO_PROXY).toContain("localhost");
expect(process.env.NO_PROXY).toContain("[::1]");
enteredA.resolve();
@@ -247,7 +248,7 @@ describe("withNoProxyForLocalhost concurrency", () => {
await enteredA.promise;
const callB = withNoProxyForLocalhost(async () => {
const callB = withNoProxyForLocalhostScoped(async () => {
return "b";
});
@@ -264,21 +265,22 @@ describe("withNoProxyForLocalhost concurrency", () => {
describe("withNoProxyForLocalhost reverse exit order", () => {
it("restores NO_PROXY when first caller exits before second", async () => {
await withIsolatedNoProxyEnv(async () => {
const { withNoProxyForLocalhost } = await import("./cdp-proxy-bypass.js");
const { withNoProxyForLocalhost: withNoProxyForLocalhostItem } =
await import("./cdp-proxy-bypass.js");
const enteredA = createDeferred();
const enteredB = createDeferred();
const releaseA = createDeferred();
const releaseB = createDeferred();
const callA = withNoProxyForLocalhost(async () => {
const callA = withNoProxyForLocalhostItem(async () => {
enteredA.resolve();
await releaseA.promise;
return "a";
});
await enteredA.promise;
const callB = withNoProxyForLocalhost(async () => {
const callB = withNoProxyForLocalhostItem(async () => {
enteredB.resolve();
await releaseB.promise;
return "b";
@@ -306,9 +308,10 @@ describe("withNoProxyForLocalhost preserves user-configured NO_PROXY", () => {
process.env.HTTP_PROXY = "http://proxy:8080";
try {
const { withNoProxyForLocalhost } = await import("./cdp-proxy-bypass.js");
const { withNoProxyForLocalhost: withNoProxyForLocalhostCandidate } =
await import("./cdp-proxy-bypass.js");
await withNoProxyForLocalhost(async () => {
await withNoProxyForLocalhostCandidate(async () => {
// Should not modify since loopback is already covered
expect(process.env.NO_PROXY).toBe(userNoProxy);
return "ok";
@@ -332,9 +335,10 @@ describe("withNoProxyForLocalhost preserves user-configured NO_PROXY", () => {
process.env.HTTP_PROXY = "http://proxy:8080";
try {
const { withNoProxyForLocalhost } = await import("./cdp-proxy-bypass.js");
const { withNoProxyForLocalhost: withNoProxyForLocalhostEntry } =
await import("./cdp-proxy-bypass.js");
await withNoProxyForLocalhost(async () => {
await withNoProxyForLocalhostEntry(async () => {
expect(process.env.NO_PROXY).toBe(`${coveredNoProxy},localhost,127.0.0.1,[::1]`);
expect(process.env.no_proxy).toBe(`${staleLowerNoProxy},localhost,127.0.0.1,[::1]`);
});
@@ -355,9 +359,10 @@ describe("withNoProxyForLocalhost preserves user-configured NO_PROXY", () => {
process.env.HTTP_PROXY = "http://proxy:8080";
try {
const { withNoProxyForLocalhost } = await import("./cdp-proxy-bypass.js");
const { withNoProxyForLocalhost: withNoProxyForLocalhostResult } =
await import("./cdp-proxy-bypass.js");
await withNoProxyForLocalhost(async () => {
await withNoProxyForLocalhostResult(async () => {
expect(process.env.NO_PROXY).toBe(`${lowerNoProxy},localhost,127.0.0.1,[::1]`);
expect(process.env.no_proxy).toBe(`${lowerNoProxy},localhost,127.0.0.1,[::1]`);
});
@@ -378,9 +383,10 @@ describe("withNoProxyForLocalhost preserves user-configured NO_PROXY", () => {
process.env.HTTP_PROXY = "http://proxy:8080";
try {
const { withNoProxyForLocalhost } = await import("./cdp-proxy-bypass.js");
const { withNoProxyForLocalhost: withNoProxyForLocalhostValue } =
await import("./cdp-proxy-bypass.js");
await withNoProxyForLocalhost(async () => {
await withNoProxyForLocalhostValue(async () => {
expect(process.env.NO_PROXY).toBe(`${userNoProxy},localhost,127.0.0.1,[::1]`);
expect(process.env.no_proxy).toBe(`${userNoProxy},localhost,127.0.0.1,[::1]`);
delete process.env.no_proxy;
@@ -402,9 +408,10 @@ describe("withNoProxyForLocalhost preserves user-configured NO_PROXY", () => {
process.env.HTTP_PROXY = "http://proxy:8080";
try {
const { withNoProxyForLocalhost } = await import("./cdp-proxy-bypass.js");
const { withNoProxyForLocalhost: withNoProxyForLocalhostLocal } =
await import("./cdp-proxy-bypass.js");
await withNoProxyForLocalhost(async () => {
await withNoProxyForLocalhostLocal(async () => {
expect(process.env.NO_PROXY).toBe(`${userNoProxy},localhost,127.0.0.1,[::1]`);
expect(process.env.no_proxy).toBe(`${userNoProxy},localhost,127.0.0.1,[::1]`);
});

View File

@@ -490,11 +490,11 @@ describe("chrome MCP page parsing", () => {
url: "about:blank",
type: "page",
});
expect(session.client.callTool).toHaveBeenCalledWith({
expect(session.client["callTool"]).toHaveBeenCalledWith({
name: "new_page",
arguments: { url: "about:blank", timeout: 5000 },
});
const callToolMock = session.client.callTool as unknown as ToolCallMock;
const callToolMock = session.client["callTool"] as unknown as ToolCallMock;
const callNames = callToolMock.mock.calls.map(([call]) => call.name);
expect(callNames).not.toContain("navigate_page");
});
@@ -917,7 +917,7 @@ describe("chrome MCP page parsing", () => {
// intentionally no timeoutMs
});
const callToolMock = session.client.callTool as unknown as ToolCallMock;
const callToolMock = session.client["callTool"] as unknown as ToolCallMock;
const navigateCall = callToolMock.mock.calls.find(
([call]) => call.name === "navigate_page",
)?.[0];

View File

@@ -107,7 +107,7 @@ describe("pw-tools-core", () => {
await fs.writeFile(uploadPath, "fixture", "utf8");
const canonicalUploadPath = await fs.realpath(uploadPath);
const fileChooser = { setFiles: vi.fn(async () => {}) };
const waitForEvent = vi.fn(async (eventValue: string, _opts: unknown) => fileChooser);
const waitForEvent = vi.fn(async (_eventValue: string, _opts: unknown) => fileChooser);
setPwToolsCoreCurrentPage({
waitForEvent,
keyboard: { press: vi.fn(async () => {}) },

View File

@@ -86,10 +86,10 @@ export function registerBrowserAgentDebugRoutes(
ctx,
targetId,
feature: "page errors",
collect: async ({ cdpUrl, targetId, pw }) =>
collect: async ({ cdpUrl, targetId: targetIdValue, pw }) =>
await pw.getPageErrorsViaPlaywright({
cdpUrl,
targetId,
targetId: targetIdValue,
clear,
}),
});
@@ -109,10 +109,10 @@ export function registerBrowserAgentDebugRoutes(
ctx,
targetId,
feature: "network requests",
collect: async ({ cdpUrl, targetId, pw }) =>
collect: async ({ cdpUrl, targetId: targetIdLocal, pw }) =>
await pw.getNetworkRequestsViaPlaywright({
cdpUrl,
targetId,
targetId: targetIdLocal,
filter: normalizeOptionalString(filter),
clear,
}),

View File

@@ -25,14 +25,14 @@ const {
stopKnownBrowserProfilesMock,
trackedTabCleanupMock,
} = vi.hoisted(() => {
const trackedTabCleanupMock = vi.fn();
const trackedTabCleanupMockLocal = vi.fn();
return {
ensureExtensionRelayForProfilesMock: vi.fn(async () => {}),
getPwAiModuleMock: vi.fn(),
isPwAiLoadedMock: vi.fn(() => false),
startTrackedBrowserTabCleanupTimerMock: vi.fn(() => trackedTabCleanupMock),
startTrackedBrowserTabCleanupTimerMock: vi.fn(() => trackedTabCleanupMockLocal),
stopKnownBrowserProfilesMock: vi.fn(async () => {}),
trackedTabCleanupMock,
trackedTabCleanupMock: trackedTabCleanupMockLocal,
};
});

View File

@@ -264,7 +264,7 @@ export function createBrowserRouteContext(opts: ContextOptions): BrowserRouteCon
isTransportAvailable: (timeoutMs) => getDefaultContext().isTransportAvailable(timeoutMs),
isReachable: (timeoutMs, options) => getDefaultContext().isReachable(timeoutMs, options),
listTabs: () => getDefaultContext().listTabs(),
openTab: (url, opts) => getDefaultContext().openTab(url, opts),
openTab: (url, optsLocal) => getDefaultContext().openTab(url, optsLocal),
labelTab: (targetId, label) => getDefaultContext().labelTab(targetId, label),
focusTab: (targetId) => getDefaultContext().focusTab(targetId),
closeTab: (targetId) => getDefaultContext().closeTab(targetId),

View File

@@ -11,14 +11,14 @@ import {
type DescribeFn = ReturnType<typeof vi.fn>;
function makeDeps(
describe: DescribeFn,
describeCandidate: DescribeFn,
overrides?: {
normalizeBrowserScreenshot?: ReturnType<typeof vi.fn>;
saveMediaBuffer?: ReturnType<typeof vi.fn>;
},
) {
return {
describeImageFile: describe as never,
describeImageFile: describeCandidate as never,
normalizeBrowserScreenshot:
(overrides?.normalizeBrowserScreenshot as never) ??
(vi.fn(async (buffer: Buffer) => ({ buffer })) as never),
@@ -41,7 +41,7 @@ async function withTempImage<T>(fn: (filePath: string) => Promise<T>): Promise<T
describe("describeBrowserScreenshot", () => {
it("uses existing image understanding config with a browser screenshot prompt", async () => {
const describe = vi.fn().mockResolvedValue({
const describeEntry = vi.fn().mockResolvedValue({
text: "A login screen.",
provider: "openai",
model: "gpt-vision",
@@ -62,7 +62,7 @@ describe("describeBrowserScreenshot", () => {
activeModel: { provider: "anthropic", model: "claude-sonnet-4.6" },
mediaScope: { sessionKey: "agent:main:telegram:dm:123", channel: "telegram" },
},
makeDeps(describe),
makeDeps(describeEntry),
);
expect(result).toEqual({
@@ -71,7 +71,7 @@ describe("describeBrowserScreenshot", () => {
model: "gpt-vision",
decision: { outcome: "success" },
});
expect(describe).toHaveBeenCalledWith({
expect(describeEntry).toHaveBeenCalledWith({
filePath,
cfg: {
tools: {
@@ -92,7 +92,7 @@ describe("describeBrowserScreenshot", () => {
});
it("resizes screenshots before image understanding when image sanitization is configured", async () => {
const describe = vi.fn().mockResolvedValue({ text: "Small screenshot." });
const describeResult = vi.fn().mockResolvedValue({ text: "Small screenshot." });
const normalizeBrowserScreenshot = vi.fn(async () => ({
buffer: Buffer.from("small"),
contentType: "image/jpeg" as const,
@@ -106,7 +106,7 @@ describe("describeBrowserScreenshot", () => {
filePath,
imageSanitization: { maxDimensionPx: 800 },
},
makeDeps(describe, { normalizeBrowserScreenshot, saveMediaBuffer }),
makeDeps(describeResult, { normalizeBrowserScreenshot, saveMediaBuffer }),
);
});
@@ -114,11 +114,11 @@ describe("describeBrowserScreenshot", () => {
maxSide: 800,
});
expect(saveMediaBuffer).toHaveBeenCalledWith(Buffer.from("small"), "image/jpeg", "browser");
expect(describe.mock.calls[0][0].filePath).toBe("/tmp/resized.jpg");
expect(describeResult.mock.calls[0][0].filePath).toBe("/tmp/resized.jpg");
});
it("returns null when image understanding is skipped or not configured", async () => {
const describe = vi.fn().mockResolvedValue({
const describeValue = vi.fn().mockResolvedValue({
text: undefined,
decision: { outcome: "skipped" },
});
@@ -126,13 +126,13 @@ describe("describeBrowserScreenshot", () => {
await expect(
describeBrowserScreenshot(
{ cfg: { browser: {} }, filePath: "/tmp/screenshot.png" },
makeDeps(describe),
makeDeps(describeValue),
),
).resolves.toBeNull();
});
it("does not pass an incomplete active model to media understanding", async () => {
const describe = vi.fn().mockResolvedValue({ text: "ok" });
const describeLocal = vi.fn().mockResolvedValue({ text: "ok" });
await describeBrowserScreenshot(
{
@@ -144,10 +144,10 @@ describe("describeBrowserScreenshot", () => {
filePath: "/tmp/screenshot.png",
activeModel: { model: "missing-provider" },
},
makeDeps(describe),
makeDeps(describeLocal),
);
expect(describe.mock.calls[0][0].activeModel).toBeUndefined();
expect(describeLocal.mock.calls[0][0].activeModel).toBeUndefined();
});
});

View File

@@ -495,8 +495,10 @@ export function registerBrowserManageCommands(
if (printJsonResult(parent, result)) {
return;
}
const tab = (result as { tab?: BrowserTab }).tab;
defaultRuntime.log(`labeled tab ${tab?.tabId ?? targetId} as ${tab?.label ?? label}`);
const tabValue = (result as { tab?: BrowserTab }).tab;
defaultRuntime.log(
`labeled tab ${tabValue?.tabId ?? targetId} as ${tabValue?.label ?? label}`,
);
});
});
@@ -555,7 +557,7 @@ export function registerBrowserManageCommands(
const parent = parentOpts(cmd);
const profile = parent?.browserProfile;
await runBrowserCommand(async () => {
const tab = await callBrowserRequest<BrowserTab>(
const tabLocal = await callBrowserRequest<BrowserTab>(
parent,
{
method: "POST",
@@ -565,11 +567,11 @@ export function registerBrowserManageCommands(
},
{ timeoutMs: BROWSER_MANAGE_REQUEST_TIMEOUT_MS },
);
if (printJsonResult(parent, tab)) {
if (printJsonResult(parent, tabLocal)) {
return;
}
defaultRuntime.log(
`opened: ${tab.url}\n${tab.tabId ? `tab: ${tab.tabId}\n` : ""}${tab.label ? `label: ${tab.label}\n` : ""}id: ${tab.targetId}`,
`opened: ${tabLocal.url}\n${tabLocal.tabId ? `tab: ${tabLocal.tabId}\n` : ""}${tabLocal.label ? `label: ${tabLocal.label}\n` : ""}id: ${tabLocal.targetId}`,
);
});
});

View File

@@ -216,7 +216,7 @@ describe("codex plugin", () => {
it("enables the native hook relay for public Codex side questions", async () => {
const harness = createCodexAppServerAgentHarness({ pluginConfig: { appServer: {} } });
const runSideQuestion = harness.runSideQuestion;
const runSideQuestion = harness["runSideQuestion"];
const result = { text: "ok" };
runCodexAppServerSideQuestionMock.mockResolvedValueOnce(result);

View File

@@ -266,7 +266,7 @@ describe("codex provider", () => {
listModels: listTestCodexAppServerModels,
});
expect(client.close).toHaveBeenCalledTimes(1);
expect(client["close"]).toHaveBeenCalledTimes(1);
});
it("does not close an active shared app-server client during live discovery", async () => {
@@ -293,8 +293,8 @@ describe("codex provider", () => {
listModels: listTestCodexAppServerModels,
});
expect(activeClient.close).not.toHaveBeenCalled();
expect(discoveryClient.close).toHaveBeenCalledTimes(1);
expect(activeClient["close"]).not.toHaveBeenCalled();
expect(discoveryClient["close"]).toHaveBeenCalledTimes(1);
});
it("resolves arbitrary Codex app-server model ids as text-only until discovered", () => {

View File

@@ -114,7 +114,7 @@ describe("maybeCompactCodexAppServerSession", () => {
);
expect(fake.request).toHaveBeenCalledWith("thread/compact/start", { threadId: "thread-1" });
expect(fake.client.addNotificationHandler).not.toHaveBeenCalled();
expect(fake.client["addNotificationHandler"]).not.toHaveBeenCalled();
expect(result.ok).toBe(true);
expect(result.compacted).toBe(false);
expect(result.result?.tokensBefore).toBe(123);

View File

@@ -1,15 +1,15 @@
import type { AgentToolResult } from "openclaw/plugin-sdk/agent-core";
import {
onInternalDiagnosticEvent,
waitForDiagnosticEventsDrained,
type DiagnosticEventPayload,
} from "openclaw/plugin-sdk/diagnostic-runtime";
import type { AnyAgentTool } from "openclaw/plugin-sdk/agent-harness";
import {
HEARTBEAT_RESPONSE_TOOL_NAME,
embeddedAgentLog,
wrapToolWithBeforeToolCallHook,
} from "openclaw/plugin-sdk/agent-harness-runtime";
import {
onInternalDiagnosticEvent,
waitForDiagnosticEventsDrained,
type DiagnosticEventPayload,
} from "openclaw/plugin-sdk/diagnostic-runtime";
import {
initializeGlobalHookRunner,
resetGlobalHookRunner,
@@ -85,12 +85,6 @@ function requireRecord(value: unknown, label: string): Record<string, unknown> {
}
return value as Record<string, unknown>;
}
function requireArray(value: unknown, label: string): Array<unknown> {
expect(Array.isArray(value), label).toBe(true);
return value as Array<unknown>;
}
function callArg(
mock: { mock: { calls: Array<Array<unknown>> } },
callIndex: number,
@@ -346,9 +340,7 @@ describe("createCodexDynamicToolBridge", () => {
}),
);
const blockedEvents = diagnosticEvents.filter(
(
event,
): event is Extract<DiagnosticEventPayload, { type: "tool.execution.blocked" }> =>
(event): event is Extract<DiagnosticEventPayload, { type: "tool.execution.blocked" }> =>
event.type === "tool.execution.blocked",
);
expect(blockedEvents).toContainEqual(
@@ -889,7 +881,7 @@ describe("createCodexDynamicToolBridge", () => {
it("passes raw tool failure state into agent tool result middleware", async () => {
const registry = createEmptyPluginRegistry();
const handler = vi.fn(async (eventValue: { isError?: boolean }) => undefined);
const handler = vi.fn(async (_eventValue: { isError?: boolean }) => undefined);
registry.agentToolResultMiddlewares.push({
pluginId: "tokenjuice",
pluginName: "Tokenjuice",

View File

@@ -312,7 +312,7 @@ export function createAppServerHarness(
return {
request,
requests,
async waitForMethod(method: string, timeoutMs: number = appServerHarnessWait.timeout) {
waitForMethod: async (method: string, timeoutMs: number = appServerHarnessWait.timeout) => {
await vi.waitFor(
() => {
if (!requests.some((entry) => entry.method === method)) {
@@ -330,15 +330,15 @@ export function createAppServerHarness(
{ interval: 1, timeout: timeoutMs },
);
},
async notify(notification: CodexServerNotification) {
notify: async (notification: CodexServerNotification) => {
await sendNotification(notification);
},
waitForServerRequestHandler,
async handleServerRequest(request: Parameters<AppServerRequestHandler>[0]) {
handleServerRequest: async (requestLocal: Parameters<AppServerRequestHandler>[0]) => {
const handler = await waitForServerRequestHandler();
return handler(request);
return handler(requestLocal);
},
async completeTurn(params: { threadId: string; turnId: string }) {
completeTurn: async (params: { threadId: string; turnId: string }) => {
await sendNotification({
method: "turn/completed",
params: {
@@ -348,7 +348,7 @@ export function createAppServerHarness(
},
});
},
close() {
close: () => {
for (const handler of closeHandlers) {
handler();
}

View File

@@ -334,16 +334,17 @@ describe("runCodexAppServerAttempt context-engine lifecycle", () => {
if (!contextEngine.bootstrap) {
throw new Error("expected bootstrap hook");
}
expect(contextEngine.bootstrap).toHaveBeenCalledTimes(1);
const bootstrapParams = requireFirstCallArg(contextEngine.bootstrap, "bootstrap") as Parameters<
NonNullable<ContextEngine["bootstrap"]>
>[0];
expect(contextEngine["bootstrap"]).toHaveBeenCalledTimes(1);
const bootstrapParams = requireFirstCallArg(
contextEngine["bootstrap"],
"bootstrap",
) as Parameters<NonNullable<ContextEngine["bootstrap"]>>[0];
expect(bootstrapParams.sessionId).toBe("session-1");
expect(bootstrapParams.sessionKey).toBe("agent:main:session-1");
expect(bootstrapParams.sessionFile).toBe(sessionFile);
expect(contextEngine.assemble).toHaveBeenCalledTimes(1);
const assembleParams = requireFirstCallArg(contextEngine.assemble, "assemble") as Parameters<
expect(contextEngine["assemble"]).toHaveBeenCalledTimes(1);
const assembleParams = requireFirstCallArg(contextEngine["assemble"], "assemble") as Parameters<
ContextEngine["assemble"]
>[0];
expect(assembleParams.sessionId).toBe("session-1");
@@ -385,12 +386,13 @@ describe("runCodexAppServerAttempt context-engine lifecycle", () => {
if (!contextEngine.bootstrap) {
throw new Error("expected bootstrap hook");
}
const bootstrapParams = requireFirstCallArg(contextEngine.bootstrap, "bootstrap") as Parameters<
NonNullable<ContextEngine["bootstrap"]>
>[0];
const bootstrapParams = requireFirstCallArg(
contextEngine["bootstrap"],
"bootstrap",
) as Parameters<NonNullable<ContextEngine["bootstrap"]>>[0];
expect(bootstrapParams.sessionKey).toBe("agent:main:main");
const assembleParams = requireFirstCallArg(contextEngine.assemble, "assemble") as Parameters<
const assembleParams = requireFirstCallArg(contextEngine["assemble"], "assemble") as Parameters<
ContextEngine["assemble"]
>[0];
expect(assembleParams.sessionKey).toBe("agent:main:main");
@@ -1640,7 +1642,7 @@ describe("runCodexAppServerAttempt context-engine lifecycle", () => {
await harness.completeTurn();
await run;
const assembleParams = requireFirstCallArg(contextEngine.assemble, "assemble") as Parameters<
const assembleParams = requireFirstCallArg(contextEngine["assemble"], "assemble") as Parameters<
ContextEngine["assemble"]
>[0];
expect(assembleParams.messages.map((message) => message.role)).toEqual([

View File

@@ -1,10 +1,7 @@
import fs from "node:fs/promises";
import path from "node:path";
import {
abortAgentHarnessRun,
embeddedAgentLog,
onAgentEvent,
type AgentEventPayload,
type EmbeddedRunAttemptParams,
} from "openclaw/plugin-sdk/agent-harness-runtime";
import { SessionManager } from "openclaw/plugin-sdk/agent-sessions";
@@ -12,15 +9,10 @@ import {
onInternalDiagnosticEvent,
waitForDiagnosticEventsDrained,
type DiagnosticEventPayload,
type DiagnosticEventPrivateData,
} from "openclaw/plugin-sdk/diagnostic-runtime";
import { initializeGlobalHookRunner, registerInternalHook } from "openclaw/plugin-sdk/hook-runtime";
import { registerPluginCommand } from "openclaw/plugin-sdk/plugin-runtime";
import {
createMockPluginRegistry,
onTrustedInternalDiagnosticEvent,
} from "openclaw/plugin-sdk/plugin-test-runtime";
import { registerSandboxBackend } from "openclaw/plugin-sdk/sandbox";
import { createMockPluginRegistry } from "openclaw/plugin-sdk/plugin-test-runtime";
import { describe, expect, it, vi } from "vitest";
import WebSocket from "ws";
import { CODEX_GPT5_BEHAVIOR_CONTRACT } from "../../prompt-overlay.js";
@@ -32,7 +24,6 @@ import {
getCodexWorkspaceMemoryToolNames,
prependCodexOpenClawPromptContext,
} from "./attempt-context.js";
import * as authBridge from "./auth-bridge.js";
import { resolveCodexAppServerEnvApiKeyCacheKey } from "./auth-bridge.js";
import { CodexAppServerRpcError } from "./client.js";
import { readCodexPluginConfig, resolveCodexAppServerRuntimeOptions } from "./config.js";
@@ -49,10 +40,10 @@ import { buildCodexPluginAppCacheKey } from "./plugin-app-cache-key.js";
import { buildCodexPluginThreadConfig } from "./plugin-thread-config.js";
import type { CodexServerNotification } from "./protocol.js";
import {
createAppServerHarness,
assistantMessage,
createParams,
createAppServerHarness,
createCodexRuntimePlanFixture,
createParams,
createResumeHarness,
createStartedThreadHarness,
fastWait,
@@ -1638,11 +1629,11 @@ describe("runCodexAppServerAttempt", () => {
"assistant",
"toolResult",
]);
const assistantMessage = result.messagesSnapshot[1];
if (assistantMessage?.role !== "assistant") {
const assistantMessageLocal = result.messagesSnapshot[1];
if (assistantMessageLocal?.role !== "assistant") {
throw new Error("expected mirrored assistant tool-call message");
}
expect(assistantMessage.content).toStrictEqual([
expect(assistantMessageLocal.content).toStrictEqual([
{
type: "toolCall",
id: "call-wiki-status-1",

View File

@@ -1217,7 +1217,7 @@ export async function runCodexAppServerAttempt(
},
});
const releaseTurnAfterTerminalDynamicTool = (params: {
const releaseTurnAfterTerminalDynamicTool = (paramsValue: {
call: CodexDynamicToolCallParams;
response: CodexDynamicToolCallResponse;
durationMs: number;
@@ -1226,7 +1226,7 @@ export async function runCodexAppServerAttempt(
!shouldReleaseTurnAfterTerminalDynamicTool({
completed,
aborted: runAbortController.signal.aborted,
responseSuccess: params.response.success,
responseSuccess: paramsValue.response.success,
currentTurnHadNonTerminalDynamicToolResult,
activeAppServerTurnRequests,
activeTurnItemIdsCount: activeTurnItemIds.size,
@@ -1237,22 +1237,22 @@ export async function runCodexAppServerAttempt(
}
pendingTerminalDynamicToolRelease = undefined;
trajectoryRecorder?.recordEvent("turn.dynamic_tool_terminal_release", {
threadId: params.call.threadId,
turnId: params.call.turnId,
toolCallId: params.call.callId,
name: params.call.tool,
durationMs: params.durationMs,
threadId: paramsValue.call.threadId,
turnId: paramsValue.call.turnId,
toolCallId: paramsValue.call.callId,
name: paramsValue.call.tool,
durationMs: paramsValue.durationMs,
});
embeddedAgentLog.info("codex app-server turn released after terminal dynamic tool result", {
threadId: params.call.threadId,
turnId: params.call.turnId,
toolCallId: params.call.callId,
tool: params.call.tool,
durationMs: params.durationMs,
threadId: paramsValue.call.threadId,
turnId: paramsValue.call.turnId,
toolCallId: paramsValue.call.callId,
tool: paramsValue.call.tool,
durationMs: paramsValue.durationMs,
});
interruptCodexTurnBestEffort(client, {
threadId: params.call.threadId,
turnId: params.call.turnId,
threadId: paramsValue.call.threadId,
turnId: paramsValue.call.turnId,
timeoutMs: CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS,
});
completed = true;
@@ -1290,12 +1290,12 @@ export async function runCodexAppServerAttempt(
immediate.unref?.();
};
const scheduleTurnReleaseAfterTerminalDynamicTool = (params: {
const scheduleTurnReleaseAfterTerminalDynamicTool = (paramsLocal: {
call: CodexDynamicToolCallParams;
response: CodexDynamicToolCallResponse;
durationMs: number;
}) => {
pendingTerminalDynamicToolRelease = params;
pendingTerminalDynamicToolRelease = paramsLocal;
scheduleTerminalDynamicToolReleaseCheck();
};
@@ -2090,8 +2090,8 @@ export async function runCodexAppServerAttempt(
steeringQueueRef.current = activeSteeringQueue;
const handle = {
kind: "embedded" as const,
queueMessage: async (text: string, options?: CodexSteeringQueueOptions) =>
activeSteeringQueue.queue(text, options),
queueMessage: async (text: string, optionsLocal?: CodexSteeringQueueOptions) =>
activeSteeringQueue.queue(text, optionsLocal),
isStreaming: () => !completed,
isCompacting: () => projectorRef.current?.isCompacting() ?? false,
sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
@@ -2280,7 +2280,8 @@ export async function runCodexAppServerAttempt(
});
}
if (activeContextEngine) {
const activeContextEnginePluginId = resolveContextEngineOwnerPluginId(activeContextEngine);
const activeContextEnginePluginIdLocal =
resolveContextEngineOwnerPluginId(activeContextEngine);
const finalMessages =
(await readMirroredSessionHistoryMessages(activeSessionFile)) ??
historyMessages.concat(result.messagesSnapshot);
@@ -2301,7 +2302,7 @@ export async function runCodexAppServerAttempt(
cwd: effectiveCwd,
agentDir,
activeAgentId: sessionAgentId,
contextEnginePluginId: activeContextEnginePluginId,
contextEnginePluginId: activeContextEnginePluginIdLocal,
tokenBudget: params.contextTokenBudget,
lastCallUsage: result.attemptUsage,
promptCache: result.promptCache,

View File

@@ -1,6 +1,6 @@
import { EventEmitter } from "node:events";
import { PassThrough, Writable } from "node:stream";
import type { Api, Model } from "openclaw/plugin-sdk/llm";
import type { Model } from "openclaw/plugin-sdk/llm";
import { vi } from "vitest";
import { CodexAppServerClient } from "./client.js";

View File

@@ -335,8 +335,8 @@ function sanitizeValue(value: unknown, depth = 0, key = ""): unknown {
}
if (typeof value === "object") {
const next: Record<string, unknown> = {};
for (const [key, child] of Object.entries(value).slice(0, 100)) {
next[key] = sanitizeValue(child, depth + 1, key);
for (const [keyLocal, child] of Object.entries(value).slice(0, 100)) {
next[keyLocal] = sanitizeValue(child, depth + 1, keyLocal);
}
return next;
}

View File

@@ -218,11 +218,6 @@ function mockCall(mockFn: ReturnType<typeof vi.fn>, callIndex = 0): ReadonlyArra
function mockArg(mockFn: ReturnType<typeof vi.fn>, callIndex: number, argIndex: number) {
return mockCall(mockFn, callIndex)[argIndex];
}
function requireRequestParams(call: unknown[] | undefined): Record<string, unknown> {
return requireRecord(call?.[2], "expected request params object");
}
function requestParams(mockFn: ReturnType<typeof vi.fn>, callIndex = 0): Record<string, unknown> {
return requireRecord(mockArg(mockFn, callIndex, 2), "expected request params object");
}
@@ -347,8 +342,8 @@ describe("codex command", () => {
const requests: Array<{ method: string; params: unknown }> = [];
const deps = createDeps({
codexControlRequest: vi.fn(
async (_pluginConfig: unknown, method: string, requestParams: unknown) => {
requests.push({ method, params: requestParams });
async (_pluginConfig: unknown, method: string, requestParamsValue: unknown) => {
requests.push({ method, params: requestParamsValue });
return {
thread: { id: "thread-123", cwd: "/repo" },
model: "gpt-5.4",
@@ -1757,7 +1752,8 @@ describe("codex command", () => {
it("respects openai-alias explicit order over stale lastGood for API key profiles", async () => {
const config = {};
const now = Date.now();
const ignoredNow = Date.now();
void ignoredNow;
installAuthProfileStore(
{
version: 1,
@@ -2384,12 +2380,14 @@ describe("codex command", () => {
`${secondSessionFile}.codex-app-server.json`,
JSON.stringify({ schemaVersion: 1, threadId: "thread-222", cwd: "/repo" }),
);
const safeCodexControlRequest = vi.fn(async (configForTest, _method, requestParams) => ({
const safeCodexControlRequest = vi.fn(async (configForTest, _method, requestParamsLocal) => ({
ok: true as const,
value: {
threadId:
requestParams && typeof requestParams === "object" && "threadId" in requestParams
? requestParams.threadId
requestParamsLocal &&
typeof requestParamsLocal === "object" &&
"threadId" in requestParamsLocal
? requestParamsLocal.threadId
: undefined,
},
}));

View File

@@ -280,7 +280,7 @@ describe("createCopilotAgentHarness", () => {
await expect(firstDispose).resolves.toBeUndefined();
await expect(secondDispose).resolves.toBeUndefined();
expect(pool.dispose).toHaveBeenCalledTimes(1);
expect(pool["dispose"]).toHaveBeenCalledTimes(1);
});
it("dispose waits for in-flight runAttempt before disposing", async () => {
@@ -301,14 +301,14 @@ describe("createCopilotAgentHarness", () => {
await flushAsyncWork();
expect(pool.dispose).not.toHaveBeenCalled();
expect(pool["dispose"]).not.toHaveBeenCalled();
expect(disposeSettled).toBe(false);
deferred.resolve(ATTEMPT_RESULT);
await expect(attemptPromise).resolves.toBe(ATTEMPT_RESULT);
await expect(disposePromise).resolves.toBeUndefined();
expect(pool.dispose).toHaveBeenCalledTimes(1);
expect(pool["dispose"]).toHaveBeenCalledTimes(1);
});
it("runAttempt after dispose rejects without creating a new pool", async () => {
@@ -348,7 +348,7 @@ describe("createCopilotAgentHarness", () => {
await harness.runAttempt(ATTEMPT_PARAMS);
await expect(harness.dispose?.()).resolves.toBeUndefined();
expect(pool.dispose).not.toHaveBeenCalled();
expect(pool["dispose"]).not.toHaveBeenCalled();
});
it("uses options.pool when supplied", async () => {
@@ -1170,7 +1170,7 @@ describe("createCopilotAgentHarness", () => {
describe("runSideQuestion", () => {
it("is not implemented; /btw falls through to the in-tree PI fallback path", () => {
const harness = createCopilotAgentHarness({ pool: makePoolMock() });
expect(harness.runSideQuestion).toBeUndefined();
expect(harness["runSideQuestion"]).toBeUndefined();
});
});
});

View File

@@ -560,8 +560,7 @@ describe("runCopilotAttempt", () => {
expect(sdk.resumeSession).toHaveBeenCalledTimes(1);
expect(sdk.resumeSession.mock.calls[0]?.[0]).toBe("resume-1");
expect(
(sdk.resumeSession.mock.calls[0]?.[1] as { continuePendingWork?: boolean })
.continuePendingWork,
(sdk.resumeSession.mock.calls[0][1] as { continuePendingWork?: boolean }).continuePendingWork,
).toBe(false);
expect(sdk.createSession).toHaveBeenCalledTimes(0);
});
@@ -754,7 +753,7 @@ describe("runCopilotAttempt", () => {
expect(result.aborted).toBe(true);
expect(result.externalAbort).toBe(true);
expect(sdk.createSession).toHaveBeenCalledTimes(0);
expect(pool.acquire).toHaveBeenCalledTimes(0);
expect(pool["acquire"]).toHaveBeenCalledTimes(0);
});
it("abort path (signal fires after settled)", async () => {
@@ -810,7 +809,7 @@ describe("runCopilotAttempt", () => {
expect(bridgeCall.attemptParams).toBeDefined();
expect(bridgeCall.sessionRef).toBeDefined();
expect(
((sdk.createSession.mock.calls[0] as unknown[] | undefined)?.[0] as { tools?: unknown[] })
((sdk.createSession.mock.calls[0] as unknown[] | undefined)![0] as { tools?: unknown[] })
.tools,
).toBe(sdkTools);
});
@@ -930,8 +929,8 @@ describe("runCopilotAttempt", () => {
"[copilot-attempt] tool-bridge construction failed: bridge failed",
);
expect(sdk.createSession).toHaveBeenCalledTimes(0);
expect(pool.acquire).toHaveBeenCalledTimes(0);
expect(pool.release).toHaveBeenCalledTimes(0);
expect(pool["acquire"]).toHaveBeenCalledTimes(0);
expect(pool["release"]).toHaveBeenCalledTimes(0);
});
it("unsupported providers skip injected tool bridge wiring", async () => {
@@ -958,7 +957,7 @@ describe("runCopilotAttempt", () => {
await runCopilotAttempt(makeParams(), { pool });
const handler = (
(sdk.createSession.mock.calls[0] as unknown[] | undefined)?.[0] as {
(sdk.createSession.mock.calls[0] as unknown[] | undefined)![0] as {
onPermissionRequest: (
request: { kind: string },
invocation: { sessionId: string },
@@ -1371,8 +1370,8 @@ describe("runCopilotAttempt", () => {
expect(getPromptErrorCode(result)).toBe("model_not_supported");
expect(sdk.createSession).toHaveBeenCalledTimes(0);
expect(pool.acquire).toHaveBeenCalledTimes(0);
expect(pool.release).toHaveBeenCalledTimes(0);
expect(pool["acquire"]).toHaveBeenCalledTimes(0);
expect(pool["release"]).toHaveBeenCalledTimes(0);
});
it("acquire failure", async () => {
@@ -1387,7 +1386,7 @@ describe("runCopilotAttempt", () => {
expect(result.promptError).toBe(error);
expect(sdk.createSession).toHaveBeenCalledTimes(0);
expect(pool.release).toHaveBeenCalledTimes(0);
expect(pool["release"]).toHaveBeenCalledTimes(0);
});
it("release failure after a successful send rejects the attempt", async () => {
@@ -1449,7 +1448,7 @@ describe("runCopilotAttempt", () => {
const session = sdk.sessions[0];
expect(session.off).toHaveBeenCalledTimes(session.on.mock.calls.length);
expect(session.disconnect).toHaveBeenCalledTimes(1);
expect(pool.release).toHaveBeenCalledTimes(1);
expect(pool["release"]).toHaveBeenCalledTimes(1);
});
it("cleanup on send error", async () => {
@@ -1467,7 +1466,7 @@ describe("runCopilotAttempt", () => {
expect(result.promptError).toBe(error);
expect(session.off).toHaveBeenCalledTimes(session.on.mock.calls.length);
expect(session.disconnect).toHaveBeenCalledTimes(1);
expect(pool.release).toHaveBeenCalledTimes(1);
expect(pool["release"]).toHaveBeenCalledTimes(1);
});
it("cleanup on disconnect throw", async () => {
@@ -1504,10 +1503,10 @@ describe("runCopilotAttempt", () => {
{ pool },
);
const key = (vi.mocked(pool.acquire).mock.calls[0] as unknown[] | undefined)?.[0] as {
const key = (vi.mocked(pool["acquire"]).mock.calls[0] as unknown[] | undefined)?.[0] as {
authMode: string;
};
const options = (vi.mocked(pool.acquire).mock.calls[0] as unknown[] | undefined)?.[1] as {
const options = (vi.mocked(pool["acquire"]).mock.calls[0] as unknown[] | undefined)?.[1] as {
gitHubToken?: string;
useLoggedInUser?: boolean;
};
@@ -1525,7 +1524,7 @@ describe("runCopilotAttempt", () => {
).rejects.toThrow(
"[copilot-attempt] gitHubToken auth requires profileId+profileVersion (pool keying safety; per Q5/Q1 decisions)",
);
expect(pool.acquire).toHaveBeenCalledTimes(0);
expect(pool["acquire"]).toHaveBeenCalledTimes(0);
expect(sdk.createSession).toHaveBeenCalledTimes(0);
});
@@ -1540,12 +1539,12 @@ describe("runCopilotAttempt", () => {
{ pool },
);
const key = (vi.mocked(pool.acquire).mock.calls[0] as unknown[] | undefined)?.[0] as {
const key = (vi.mocked(pool["acquire"]).mock.calls[0] as unknown[] | undefined)?.[0] as {
authMode: string;
authProfileId?: string;
authProfileVersion?: string;
};
const options = (vi.mocked(pool.acquire).mock.calls[0] as unknown[] | undefined)?.[1] as {
const options = (vi.mocked(pool["acquire"]).mock.calls[0] as unknown[] | undefined)?.[1] as {
gitHubToken?: string;
useLoggedInUser?: boolean;
};
@@ -1926,12 +1925,12 @@ describe("runCopilotAttempt", () => {
const calls = dualWriteMock.dualWriteCopilotTranscriptBestEffort.mock.calls;
const id1 = (
calls[0]?.[0] as {
calls[0][0] as {
messages: Array<{ role: string; __openclaw?: { mirrorIdentity?: string } }>;
}
).messages.find((m) => m.role === "user")?.["__openclaw"]?.mirrorIdentity;
const id2 = (
calls[1]?.[0] as {
calls[1][0] as {
messages: Array<{ role: string; __openclaw?: { mirrorIdentity?: string } }>;
}
).messages.find((m) => m.role === "user")?.["__openclaw"]?.mirrorIdentity;

View File

@@ -1,6 +1,6 @@
import { createHash } from "node:crypto";
import { resolve, join } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { join, resolve } from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
COPILOT_DEFAULT_AGENT_ID,
COPILOT_TOKEN_PROFILE_ERROR,

View File

@@ -70,7 +70,7 @@ describe("createHooksBridge", () => {
hookName: "onPostToolUse",
error: expect.any(Error),
});
expect((onHookError.mock.calls[0]?.[0]?.error as Error).message).toBe("post boom");
expect((onHookError.mock.calls[0][0]!.error as Error).message).toBe("post boom");
});
it("isolates async rejections: returns undefined and notifies onHookError", async () => {

View File

@@ -164,7 +164,7 @@ describe("createTraceContextProvider", () => {
});
await expect(provider()).resolves.toEqual({});
expect(onError).toHaveBeenCalledTimes(1);
expect((onError.mock.calls[0]?.[0] as CopilotTraceContextErrorInfo).part).toBe("traceparent");
expect((onError.mock.calls[0][0] as CopilotTraceContextErrorInfo).part).toBe("traceparent");
});
it("getTracestate failure → partial success (traceparent kept) + notifier called", async () => {
@@ -180,7 +180,7 @@ describe("createTraceContextProvider", () => {
traceparent: "00-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-bbbbbbbbbbbbbbbb-01",
});
expect(onError).toHaveBeenCalledTimes(1);
expect((onError.mock.calls[0]?.[0] as CopilotTraceContextErrorInfo).part).toBe("tracestate");
expect((onError.mock.calls[0][0] as CopilotTraceContextErrorInfo).part).toBe("tracestate");
});
it("default notifier uses console.warn", async () => {

View File

@@ -484,7 +484,7 @@ describe("deepseek provider plugin", () => {
expect(readThinking(capture.payload)?.type).toBe("disabled");
expect(capture.payload).not.toHaveProperty("reasoning_effort");
expect((capture.payload?.messages as Array<Record<string, unknown>>)[1]).not.toHaveProperty(
expect((capture.payload!.messages as Array<Record<string, unknown>>)[1]).not.toHaveProperty(
"reasoning_content",
);
});

View File

@@ -176,15 +176,15 @@ export class PlaywrightDiffScreenshotter implements DiffScreenshotter {
await page.evaluate(() => {
const html = document.documentElement;
const body = document.body;
const frame = document.querySelector(".oc-frame");
const frameLocal = document.querySelector(".oc-frame");
html.style.background = "transparent";
body.style.margin = "0";
body.style.padding = "0";
body.style.background = "transparent";
body.style.setProperty("-webkit-print-color-adjust", "exact");
if (frame instanceof HTMLElement) {
frame.style.margin = "0";
if (frameLocal instanceof HTMLElement) {
frameLocal.style.margin = "0";
}
});

View File

@@ -67,8 +67,8 @@ describe("diffs tool rendered output guards", () => {
mode: "file",
});
expect(screenshotter.screenshotHtml).toHaveBeenCalledTimes(1);
expect((result?.details as Record<string, unknown>).filePath).toMatch(/preview\.png$/);
expect(screenshotter["screenshotHtml"]).toHaveBeenCalledTimes(1);
expect((result.details as Record<string, unknown>).filePath).toMatch(/preview\.png$/);
});
});

View File

@@ -63,7 +63,7 @@ describe("diffs tool", () => {
expect(readTextContent(result, 0)).toContain(
"https://example.com/openclaw/plugins/diffs/view/",
);
expect(String((result?.details as Record<string, unknown>).viewerUrl)).toContain(
expect(String((result.details as Record<string, unknown>).viewerUrl)).toContain(
"https://example.com/openclaw/plugins/diffs/view/",
);
});
@@ -89,7 +89,7 @@ describe("diffs tool", () => {
expect(readTextContent(result, 0)).toContain(
"https://preview.example.com/review/plugins/diffs/view/",
);
expect(String((result?.details as Record<string, unknown>).viewerUrl)).toContain(
expect(String((result.details as Record<string, unknown>).viewerUrl)).toContain(
"https://preview.example.com/review/plugins/diffs/view/",
);
});
@@ -127,7 +127,7 @@ describe("diffs tool", () => {
mode: "image",
});
expect(screenshotter.screenshotHtml).toHaveBeenCalledTimes(1);
expect(screenshotter["screenshotHtml"]).toHaveBeenCalledTimes(1);
expect(readTextContent(result, 0)).toContain("Diff PNG generated at:");
expect(readTextContent(result, 0)).toContain("Use the `message` tool");
expect(result?.content).toHaveLength(1);
@@ -166,10 +166,10 @@ describe("diffs tool", () => {
fileFormat: "pdf",
});
expect(screenshotter.screenshotHtml).toHaveBeenCalledTimes(1);
expect(screenshotter["screenshotHtml"]).toHaveBeenCalledTimes(1);
expect(readTextContent(result, 0)).toContain("Diff PDF generated at:");
expect((result?.details as Record<string, unknown>).format).toBe("pdf");
expect((result?.details as Record<string, unknown>).filePath).toMatch(/preview\.pdf$/);
expect((result.details as Record<string, unknown>).format).toBe("pdf");
expect((result.details as Record<string, unknown>).filePath).toMatch(/preview\.pdf$/);
});
it("accepts mode=file as an alias for file artifact rendering", async () => {
@@ -258,7 +258,7 @@ describe("diffs tool", () => {
after: "two\n",
mode: "file",
});
const filePath = (result?.details as Record<string, unknown>).filePath as string;
const filePath = (result.details as Record<string, unknown>).filePath as string;
const stat = await fs.stat(filePath);
expect(stat.isFile()).toBe(true);
@@ -290,9 +290,9 @@ describe("diffs tool", () => {
imageMaxWidth: "1100",
});
expect((result?.details as Record<string, unknown>).fileQuality).toBe("hq");
expect((result?.details as Record<string, unknown>).fileScale).toBe(2.4);
expect((result?.details as Record<string, unknown>).fileMaxWidth).toBe(1100);
expect((result.details as Record<string, unknown>).fileQuality).toBe("hq");
expect((result.details as Record<string, unknown>).fileScale).toBe(2.4);
expect((result.details as Record<string, unknown>).fileMaxWidth).toBe(1100);
});
it("accepts deprecated format alias for fileFormat", async () => {
@@ -312,8 +312,8 @@ describe("diffs tool", () => {
format: "pdf",
});
expect((result?.details as Record<string, unknown>).fileFormat).toBe("pdf");
expect((result?.details as Record<string, unknown>).filePath).toMatch(/preview\.pdf$/);
expect((result.details as Record<string, unknown>).fileFormat).toBe("pdf");
expect((result.details as Record<string, unknown>).filePath).toMatch(/preview\.pdf$/);
});
it("honors defaults.mode=file when mode is omitted", async () => {
@@ -351,8 +351,8 @@ describe("diffs tool", () => {
expect(result?.content).toHaveLength(1);
expect(readTextContent(result, 0)).toContain("File rendering failed");
expect((result?.details as Record<string, unknown>).fileError).toBe("browser missing");
expect((result?.details as Record<string, unknown>).imageError).toBe("browser missing");
expect((result.details as Record<string, unknown>).fileError).toBe("browser missing");
expect((result.details as Record<string, unknown>).imageError).toBe("browser missing");
});
it("rejects invalid base URLs as tool input errors", async () => {
@@ -433,15 +433,15 @@ describe("diffs tool", () => {
});
expect(readTextContent(result, 0)).toContain("Diff viewer ready.");
expect((result?.details as Record<string, unknown>).mode).toBe("view");
expect((result?.details as Record<string, unknown>).context).toEqual({
expect((result.details as Record<string, unknown>).mode).toBe("view");
expect((result.details as Record<string, unknown>).context).toEqual({
agentId: "main",
sessionId: "session-123",
messageChannel: "discord",
agentAccountId: "default",
});
const viewerPath = String((result?.details as Record<string, unknown>).viewerPath);
const viewerPath = String((result.details as Record<string, unknown>).viewerPath);
const id = extractViewerArtifactId(viewerPath);
const html = await store.readHtml(id);
expect(html).toContain('body data-theme="light"');
@@ -482,13 +482,13 @@ describe("diffs tool", () => {
fileMaxWidth: 1320,
});
expect((result?.details as Record<string, unknown>).mode).toBe("both");
expect(screenshotter.screenshotHtml).toHaveBeenCalledTimes(1);
expect((result?.details as Record<string, unknown>).format).toBe("png");
expect((result?.details as Record<string, unknown>).fileQuality).toBe("print");
expect((result?.details as Record<string, unknown>).fileScale).toBe(2.75);
expect((result?.details as Record<string, unknown>).fileMaxWidth).toBe(1320);
const viewerPath = String((result?.details as Record<string, unknown>).viewerPath);
expect((result.details as Record<string, unknown>).mode).toBe("both");
expect(screenshotter["screenshotHtml"]).toHaveBeenCalledTimes(1);
expect((result.details as Record<string, unknown>).format).toBe("png");
expect((result.details as Record<string, unknown>).fileQuality).toBe("print");
expect((result.details as Record<string, unknown>).fileScale).toBe(2.75);
expect((result.details as Record<string, unknown>).fileMaxWidth).toBe(1320);
const viewerPath = String((result.details as Record<string, unknown>).viewerPath);
const id = extractViewerArtifactId(viewerPath);
const html = await store.readHtml(id);
expect(html).toContain('body data-theme="dark"');
@@ -509,7 +509,7 @@ describe("diffs tool", () => {
mode: "file",
});
expect((result?.details as Record<string, unknown>).context).toEqual({
expect((result.details as Record<string, unknown>).context).toEqual({
agentId: "reviewer",
sessionId: "session-456",
messageChannel: "telegram",
@@ -559,9 +559,9 @@ function expectArtifactOnlyFileResult(
screenshotter: DiffScreenshotter,
result: { details?: unknown } | null | undefined,
) {
expect(screenshotter.screenshotHtml).toHaveBeenCalledTimes(1);
expect((result?.details as Record<string, unknown>).mode).toBe("file");
expect((result?.details as Record<string, unknown>).viewerUrl).toBeUndefined();
expect(screenshotter["screenshotHtml"]).toHaveBeenCalledTimes(1);
expect((result!.details as Record<string, unknown>).mode).toBe("file");
expect((result!.details as Record<string, unknown>).viewerUrl).toBeUndefined();
}
function createPngScreenshotter(

View File

@@ -355,7 +355,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
message: "deleteDays must be an integer from 0 to 7",
}),
});
const senderUserId = normalizeOptionalString(ctx.requesterSenderId);
const senderUserIdLocal = normalizeOptionalString(ctx.requesterSenderId);
return await handleDiscordAction(
{
action: moderation.action,
@@ -366,7 +366,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
until: moderation.until,
reason: moderation.reason,
deleteMessageDays: moderation.deleteMessageDays,
senderUserId,
senderUserId: senderUserIdLocal,
},
cfg,
);

View File

@@ -548,8 +548,8 @@ describe("discordPlugin outbound", () => {
}) => void)
| undefined;
probeDiscordMock.mockReturnValue(
new Promise((resolve) => {
resolveProbe = resolve;
new Promise((resolveLocal) => {
resolveProbe = resolveLocal;
}),
);
monitorDiscordProviderMock.mockResolvedValue(undefined);

View File

@@ -394,10 +394,10 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount, DiscordProbe>
token: account.token,
inputs,
missingTokenNote: "missing Discord token",
resolveWithToken: async ({ token, inputs }) =>
resolveWithToken: async ({ token, inputs: inputsValue }) =>
(await loadDiscordResolveChannelsModule()).resolveDiscordChannelAllowlist({
token,
entries: inputs,
entries: inputsValue,
}),
mapResolved: (entry) => ({
input: entry.input,
@@ -415,10 +415,10 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount, DiscordProbe>
token: account.token,
inputs,
missingTokenNote: "missing Discord token",
resolveWithToken: async ({ token, inputs }) =>
resolveWithToken: async ({ token, inputs: inputsLocal }) =>
(await loadDiscordResolveUsersModule()).resolveDiscordUserAllowlist({
token,
entries: inputs,
entries: inputsLocal,
}),
mapResolved: (entry) => ({
input: entry.input,

View File

@@ -390,7 +390,7 @@ export function buildDiscordComponentMessage(params: {
const lastChild = containerChildren.at(-1);
if (lastChild instanceof Row) {
const row = lastChild;
const hasSelect = row.components.some((entry) => isSelectComponent(entry));
const hasSelect = row.components.some((entryLocal) => isSelectComponent(entryLocal));
if (row.components.length < 5 && !hasSelect) {
row.addComponent(component as Button);
} else {

View File

@@ -238,10 +238,10 @@ const optionComparisonOmittedFields = new Set([
]);
const nullableLocalizationFields = new Set(["description_localizations", "name_localizations"]);
function stableComparableObject(value: unknown, path: string[] = []): unknown {
function stableComparableObject(value: unknown, pathValue: string[] = []): unknown {
if (Array.isArray(value)) {
const normalized = value.map((entry) => stableComparableObject(entry, path));
const key = path.at(-1);
const normalized = value.map((entry) => stableComparableObject(entry, pathValue));
const key = pathValue.at(-1);
if (
key &&
unorderedCommandArrayFields.has(key) &&
@@ -266,7 +266,7 @@ function stableComparableObject(value: unknown, path: string[] = []): unknown {
if (entry === null && nullableLocalizationFields.has(key)) {
return false;
}
if (path.includes("options") && optionComparisonOmittedFields.has(key)) {
if (pathValue.includes("options") && optionComparisonOmittedFields.has(key)) {
return false;
}
if ((key === "required" || key === "autocomplete") && entry === false) {
@@ -277,21 +277,21 @@ function stableComparableObject(value: unknown, path: string[] = []): unknown {
.toSorted(([a], [b]) => a.localeCompare(b))
.map(([key, entry]) => [
key,
shouldNormalizeDescriptionValue(path, key, entry)
shouldNormalizeDescriptionValue(pathValue, key, entry)
? normalizeDescriptionForComparison(entry)
: stableComparableObject(entry, [...path, key]),
: stableComparableObject(entry, [...pathValue, key]),
]),
);
}
function shouldNormalizeDescriptionValue(
path: string[],
pathLocal: string[],
key: string,
entry: unknown,
): entry is string {
return (
typeof entry === "string" &&
(key === "description" || path.at(-1) === "description_localizations")
(key === "description" || pathLocal.at(-1) === "description_localizations")
);
}

View File

@@ -303,7 +303,7 @@ describe("GatewayPlugin", () => {
it("preserves MESSAGE_CREATE author payloads for inbound dispatch", async () => {
const gateway = new GatewayPlugin({ autoInteractions: false });
const dispatchGatewayEvent = vi.fn(async (eventValue: string, dataValue: unknown) => {});
const dispatchGatewayEvent = vi.fn(async (_eventValue: string, _dataValue: unknown) => {});
(gateway as unknown as { client: unknown }).client = {
dispatchGatewayEvent,
};

View File

@@ -717,7 +717,7 @@ describe("RequestClient", () => {
it("dispatches multipart uploads with a multipart/form-data content type", async () => {
const fetchSpy = vi.fn(async (_input: string | URL | Request, init?: RequestInit) => {
expect(init?.headers).toBeInstanceOf(Headers);
expect((init?.headers as Headers).get("Content-Type")).toMatch(
expect((init!.headers as Headers).get("Content-Type")).toMatch(
/^multipart\/form-data; boundary=/,
);
expect(init?.body).not.toBeInstanceOf(FormData);

View File

@@ -45,11 +45,11 @@ async function ensureDmComponentAuthorized(params: {
allowNameMatching: isDangerousNameMatchingEnabled(ctx.discordConfig),
cfg: ctx.cfg,
token: ctx.token,
readStoreAllowFrom: async ({ accountId, dmPolicy }) =>
readStoreAllowFrom: async ({ accountId, dmPolicy: dmPolicyLocal }) =>
await readChannelIngressStoreAllowFromForDmPolicy({
provider: "discord",
accountId,
dmPolicy,
dmPolicy: dmPolicyLocal,
}),
eventKind: "button",
});

View File

@@ -71,7 +71,7 @@ export function normalizeDiscordAllowList(raw: string[] | undefined, prefixes: s
ids.add(maybeId);
continue;
}
const prefix = prefixes.find((entry) => text.startsWith(entry));
const prefix = prefixes.find((entryLocal) => text.startsWith(entryLocal));
if (prefix) {
const candidate = text.slice(prefix.length);
if (candidate) {

View File

@@ -92,7 +92,7 @@ describe("discord exec approval monitor helpers", () => {
await button.run(interaction, { id: "", action: "" });
expect(interaction.reply).toHaveBeenCalledWith({
expect(interaction["reply"]).toHaveBeenCalledWith({
content: "This approval is no longer valid.",
ephemeral: true,
});
@@ -107,7 +107,7 @@ describe("discord exec approval monitor helpers", () => {
await button.run(interaction, { id: "abc", action: "allow-once" });
expect(interaction.reply).toHaveBeenCalledWith({
expect(interaction["reply"]).toHaveBeenCalledWith({
content: "⛔ You are not authorized to approve exec requests.",
ephemeral: true,
});
@@ -123,9 +123,9 @@ describe("discord exec approval monitor helpers", () => {
await button.run(interaction, { id: "abc", action: "allow-once" });
expect(interaction.acknowledge).toHaveBeenCalled();
expect(interaction["acknowledge"]).toHaveBeenCalled();
expect(resolveApproval).toHaveBeenCalledWith("abc", "allow-once");
expect(interaction.followUp).not.toHaveBeenCalled();
expect(interaction["followUp"]).not.toHaveBeenCalled();
});
it("shows a follow-up when gateway resolution fails", async () => {
@@ -137,7 +137,7 @@ describe("discord exec approval monitor helpers", () => {
await button.run(interaction, { id: "abc", action: "deny" });
expect(interaction.followUp).toHaveBeenCalledWith({
expect(interaction["followUp"]).toHaveBeenCalledWith({
content:
"Failed to submit approval decision for **Denied**. The request may have expired or already been resolved.",
ephemeral: true,
@@ -153,8 +153,8 @@ describe("discord exec approval monitor helpers", () => {
await button.run(interaction, { id: "abc", action: "allow-once" });
expect(interaction.acknowledge).toHaveBeenCalled();
expect(interaction.followUp).toHaveBeenCalledWith({
expect(interaction["acknowledge"]).toHaveBeenCalled();
expect(interaction["followUp"]).toHaveBeenCalledWith({
content:
"That approval request is no longer pending. It may have expired or already been resolved.",
ephemeral: true,

View File

@@ -3,7 +3,7 @@ import { beforeAll, describe, expect, it, vi } from "vitest";
import { DISCORD_GATEWAY_TRANSPORT_ACTIVITY_EVENT } from "./gateway-handle.js";
const { GatewayIntents, GatewayPlugin } = vi.hoisted(() => {
const GatewayIntents = {
const GatewayIntentsLocal = {
Guilds: 1 << 0,
GuildMessages: 1 << 1,
MessageContent: 1 << 2,
@@ -31,7 +31,7 @@ const { GatewayIntents, GatewayPlugin } = vi.hoisted(() => {
}
}
class GatewayPlugin {
class GatewayPluginLocal {
options: unknown;
gatewayInfo: unknown;
emitter = new TestEmitter();
@@ -44,12 +44,12 @@ const { GatewayIntents, GatewayPlugin } = vi.hoisted(() => {
this.options = options;
}
async registerClient(clientForTest: unknown): Promise<void> {}
async registerClient(_clientForTest: unknown): Promise<void> {}
connect(_resume = false): void {}
}
return { GatewayIntents, GatewayPlugin };
return { GatewayIntents: GatewayIntentsLocal, GatewayPlugin: GatewayPluginLocal };
});
vi.mock("../internal/gateway.js", () => ({

View File

@@ -92,7 +92,7 @@ export async function deliverDiscordInteractionReply(params: {
components?: TopLevelComponents[],
) => {
const contentPayload = content ? { content } : {};
const payload =
const payloadLocal =
files && files.length > 0
? {
...contentPayload,
@@ -117,12 +117,12 @@ export async function deliverDiscordInteractionReply(params: {
};
await safeDiscordInteractionCall("interaction send", async () => {
if (!preferFollowUp && !hasReplied) {
await interaction.reply(payload);
await interaction.reply(payloadLocal);
hasReplied = true;
firstMessageComponents = undefined;
return;
}
await interaction.followUp(payload);
await interaction.followUp(payloadLocal);
hasReplied = true;
firstMessageComponents = undefined;
});

View File

@@ -29,17 +29,17 @@ const {
unregisterGatewayMock,
waitForDiscordGatewayStopMock,
} = vi.hoisted(() => {
const stopGatewayLoggingMock = vi.fn();
const getDiscordGatewayEmitterMock = vi.fn<() => EventEmitter | undefined>(() => undefined);
const stopGatewayLoggingMockLocal = vi.fn();
const getDiscordGatewayEmitterMockLocal = vi.fn<() => EventEmitter | undefined>(() => undefined);
return {
attachDiscordGatewayLoggingMock: vi.fn(() => stopGatewayLoggingMock),
getDiscordGatewayEmitterMock,
attachDiscordGatewayLoggingMock: vi.fn(() => stopGatewayLoggingMockLocal),
getDiscordGatewayEmitterMock: getDiscordGatewayEmitterMockLocal,
waitForDiscordGatewayStopMock: vi.fn((_params: WaitForDiscordGatewayStopParams) =>
Promise.resolve(),
),
registerGatewayMock: vi.fn(),
unregisterGatewayMock: vi.fn(),
stopGatewayLoggingMock,
stopGatewayLoggingMock: stopGatewayLoggingMockLocal,
};
});

View File

@@ -46,16 +46,16 @@ const {
httpsAgentSpy,
wsProxyAgentSpy,
} = vi.hoisted(() => {
const wsProxyAgentSpy = vi.fn();
const httpsAgentSpy = vi.fn();
const globalFetchMock = vi.fn();
const baseRegisterClientSpy = vi.fn();
const webSocketSpy = vi.fn();
const captureHttpExchangeSpy = vi.fn();
const captureWsEventSpy = vi.fn();
const resolveDebugProxySettingsMock = vi.fn(() => ({ enabled: false }));
const fetchWithSsrFGuardMock = vi.fn(async (params: { url: string; init?: RequestInit }) => {
const source = (await globalFetchMock(params.url, params.init)) as Response;
const wsProxyAgentSpyLocal = vi.fn();
const httpsAgentSpyLocal = vi.fn();
const globalFetchMockLocal = vi.fn();
const baseRegisterClientSpyLocal = vi.fn();
const webSocketSpyLocal = vi.fn();
const captureHttpExchangeSpyLocal = vi.fn();
const captureWsEventSpyLocal = vi.fn();
const resolveDebugProxySettingsMockLocal = vi.fn(() => ({ enabled: false }));
const fetchWithSsrFGuardMockLocal = vi.fn(async (params: { url: string; init?: RequestInit }) => {
const source = (await globalFetchMockLocal(params.url, params.init)) as Response;
const body = await source.text();
return {
response: new Response(body, {
@@ -67,7 +67,7 @@ const {
};
});
const GatewayIntents = {
const GatewayIntentsLocal = {
Guilds: 1 << 0,
GuildMessages: 1 << 1,
MessageContent: 1 << 2,
@@ -79,7 +79,7 @@ const {
GuildVoiceStates: 1 << 8,
} as const;
class GatewayPlugin {
class GatewayPluginLocal {
options: unknown;
gatewayInfo: unknown;
client: unknown;
@@ -93,53 +93,53 @@ const {
this.isConnecting = false;
}
async registerClient(client: unknown) {
baseRegisterClientSpy(client);
baseRegisterClientSpyLocal(client);
}
}
class HttpsAgent {
static lastCreated: HttpsAgent | undefined;
class HttpsAgentLocal {
static lastCreated: HttpsAgentLocal | undefined;
options: unknown;
constructor(options?: unknown) {
this.options = options;
HttpsAgent.lastCreated = this;
httpsAgentSpy(options);
HttpsAgentLocal.lastCreated = this;
httpsAgentSpyLocal(options);
}
}
class HttpsProxyAgent {
static lastCreated: HttpsProxyAgent | undefined;
class HttpsProxyAgentLocal {
static lastCreated: HttpsProxyAgentLocal | undefined;
proxyUrl: string;
constructor(proxyUrl: string) {
if (proxyUrl === "bad-proxy") {
throw new Error("bad proxy");
}
this.proxyUrl = proxyUrl;
HttpsProxyAgent.lastCreated = this;
wsProxyAgentSpy(proxyUrl);
HttpsProxyAgentLocal.lastCreated = this;
wsProxyAgentSpyLocal(proxyUrl);
}
}
return {
baseRegisterClientSpy,
GatewayIntents,
GatewayPlugin,
globalFetchMock,
HttpsAgent,
HttpsProxyAgent,
fetchWithSsrFGuardMock,
getLastAgent: () => HttpsAgent.lastCreated,
getLastProxyAgent: () => HttpsProxyAgent.lastCreated,
captureHttpExchangeSpy,
captureWsEventSpy,
httpsAgentSpy,
resolveDebugProxySettingsMock,
baseRegisterClientSpy: baseRegisterClientSpyLocal,
GatewayIntents: GatewayIntentsLocal,
GatewayPlugin: GatewayPluginLocal,
globalFetchMock: globalFetchMockLocal,
HttpsAgent: HttpsAgentLocal,
HttpsProxyAgent: HttpsProxyAgentLocal,
fetchWithSsrFGuardMock: fetchWithSsrFGuardMockLocal,
getLastAgent: () => HttpsAgentLocal.lastCreated,
getLastProxyAgent: () => HttpsProxyAgentLocal.lastCreated,
captureHttpExchangeSpy: captureHttpExchangeSpyLocal,
captureWsEventSpy: captureWsEventSpyLocal,
httpsAgentSpy: httpsAgentSpyLocal,
resolveDebugProxySettingsMock: resolveDebugProxySettingsMockLocal,
resetLastAgent: () => {
HttpsAgent.lastCreated = undefined;
HttpsProxyAgent.lastCreated = undefined;
HttpsAgentLocal.lastCreated = undefined;
HttpsProxyAgentLocal.lastCreated = undefined;
},
webSocketSpy,
wsProxyAgentSpy,
webSocketSpy: webSocketSpyLocal,
wsProxyAgentSpy: wsProxyAgentSpyLocal,
};
});

View File

@@ -5,16 +5,16 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite
const { undiciFetchMock, agentSpy, envHttpProxyAgentSpy, proxyAgentSpy, createMockUndiciRuntime } =
vi.hoisted(() => {
const undiciFetchMock = vi.fn();
const agentSpy = vi.fn();
const envHttpProxyAgentSpy = vi.fn();
const proxyAgentSpy = vi.fn();
const createMockUndiciRuntime = () => {
const undiciFetchMockLocal = vi.fn();
const agentSpyLocal = vi.fn();
const envHttpProxyAgentSpyLocal = vi.fn();
const proxyAgentSpyLocal = vi.fn();
const createMockUndiciRuntimeLocal = () => {
class Agent {
options: unknown;
constructor(options?: unknown) {
this.options = options;
agentSpy(options);
agentSpyLocal(options);
}
}
class EnvHttpProxyAgent {
@@ -31,7 +31,7 @@ const { undiciFetchMock, agentSpy, envHttpProxyAgentSpy, proxyAgentSpy, createMo
}
}
this.options = options;
envHttpProxyAgentSpy(options);
envHttpProxyAgentSpyLocal(options);
}
}
class ProxyAgent {
@@ -44,22 +44,22 @@ const { undiciFetchMock, agentSpy, envHttpProxyAgentSpy, proxyAgentSpy, createMo
}
this.options = resolved;
this.uri = resolved.uri;
proxyAgentSpy(resolved);
proxyAgentSpyLocal(resolved);
}
}
return {
Agent,
EnvHttpProxyAgent,
ProxyAgent,
fetch: undiciFetchMock,
fetch: undiciFetchMockLocal,
};
};
return {
undiciFetchMock,
agentSpy,
envHttpProxyAgentSpy,
proxyAgentSpy,
createMockUndiciRuntime,
undiciFetchMock: undiciFetchMockLocal,
agentSpy: agentSpyLocal,
envHttpProxyAgentSpy: envHttpProxyAgentSpyLocal,
proxyAgentSpy: proxyAgentSpyLocal,
createMockUndiciRuntime: createMockUndiciRuntimeLocal,
};
});

View File

@@ -274,8 +274,8 @@ export function createThreadBindingManager(params: {
if (!key) {
return null;
}
const existing = BINDINGS_BY_THREAD_ID.get(key);
if (!existing || existing.accountId !== accountId) {
const existingResult = BINDINGS_BY_THREAD_ID.get(key);
if (!existingResult || existingResult.accountId !== accountId) {
return null;
}
const now = Date.now();
@@ -284,8 +284,8 @@ export function createThreadBindingManager(params: {
? Math.max(0, Math.floor(touchParams.at))
: now;
const nextRecord: ThreadBindingRecord = {
...existing,
lastActivityAt: Math.max(existing.lastActivityAt || 0, at),
...existingResult,
lastActivityAt: Math.max(existingResult.lastActivityAt || 0, at),
};
setBindingRecord(nextRecord);
if (touchParams.persist ?? persist) {
@@ -342,7 +342,7 @@ export function createThreadBindingManager(params: {
return null;
}
const existing = manager.getByThreadId(threadId);
const existingValue = manager.getByThreadId(threadId);
const targetSessionKey = normalizeOptionalString(bindParams.targetSessionKey) ?? "";
if (!targetSessionKey) {
return null;
@@ -351,11 +351,11 @@ export function createThreadBindingManager(params: {
const targetKind = normalizeTargetKind(bindParams.targetKind, targetSessionKey);
let webhookId =
normalizeOptionalString(bindParams.webhookId) ??
normalizeOptionalString(existing?.webhookId) ??
normalizeOptionalString(existingValue?.webhookId) ??
"";
let webhookToken =
normalizeOptionalString(bindParams.webhookToken) ??
normalizeOptionalString(existing?.webhookToken) ??
normalizeOptionalString(existingValue?.webhookToken) ??
"";
if (!directConversationBinding && (!webhookId || !webhookToken)) {
const cachedWebhook = findReusableWebhook({ accountId, channelId });
@@ -382,26 +382,29 @@ export function createThreadBindingManager(params: {
targetSessionKey,
agentId:
normalizeOptionalString(bindParams.agentId) ??
normalizeOptionalString(existing?.agentId) ??
normalizeOptionalString(existingValue?.agentId) ??
resolveAgentIdFromSessionKey(targetSessionKey),
label:
normalizeOptionalString(bindParams.label) ?? normalizeOptionalString(existing?.label),
normalizeOptionalString(bindParams.label) ??
normalizeOptionalString(existingValue?.label),
webhookId: webhookId || undefined,
webhookToken: webhookToken || undefined,
boundBy:
normalizeOptionalString(bindParams.boundBy) ??
normalizeOptionalString(existing?.boundBy) ??
normalizeOptionalString(existingValue?.boundBy) ??
"system",
boundAt: now,
lastActivityAt: now,
idleTimeoutMs:
typeof existing?.idleTimeoutMs === "number" ? existing.idleTimeoutMs : idleTimeoutMs,
maxAgeMs: typeof existing?.maxAgeMs === "number" ? existing.maxAgeMs : maxAgeMs,
typeof existingValue?.idleTimeoutMs === "number"
? existingValue.idleTimeoutMs
: idleTimeoutMs,
maxAgeMs: typeof existingValue?.maxAgeMs === "number" ? existingValue.maxAgeMs : maxAgeMs,
metadata:
bindParams.metadata && typeof bindParams.metadata === "object"
? { ...existing?.metadata, ...bindParams.metadata }
: existing?.metadata
? { ...existing.metadata }
? { ...existingValue?.metadata, ...bindParams.metadata }
: existingValue?.metadata
? { ...existingValue.metadata }
: undefined,
};
@@ -424,8 +427,8 @@ export function createThreadBindingManager(params: {
if (!bindingKey) {
return null;
}
const existing = BINDINGS_BY_THREAD_ID.get(bindingKey);
if (!existing || existing.accountId !== accountId) {
const existingLocal = BINDINGS_BY_THREAD_ID.get(bindingKey);
if (!existingLocal || existingLocal.accountId !== accountId) {
return null;
}
const removed = removeBindingRecord(bindingKey);

View File

@@ -64,16 +64,16 @@ function resolveMemberGuildPermissionBits(params: {
guild: Pick<APIGuild, "id" | "roles">;
member: Pick<APIGuildMember, "roles">;
}) {
const rolesById = new Map<string, APIRole>(
const rolesByIdLocal = new Map<string, APIRole>(
(params.guild.roles ?? []).map((role) => [role.id, role]),
);
const everyoneRole = rolesById.get(params.guild.id);
const everyoneRole = rolesByIdLocal.get(params.guild.id);
let permissions = 0n;
if (everyoneRole?.permissions) {
permissions = addPermissionBits(permissions, everyoneRole.permissions);
}
for (const roleId of params.member.roles ?? []) {
const role = rolesById.get(roleId);
const role = rolesByIdLocal.get(roleId);
if (role?.permissions) {
permissions = addPermissionBits(permissions, role.permissions);
}

View File

@@ -137,7 +137,7 @@ describe("resolveDiscordTarget", () => {
it("treats bare numeric ids in allowFrom as users even when channels are the default", async () => {
const listPeers = vi.spyOn(directoryLive, "listDiscordDirectoryPeersLive");
const cfg = {
const cfgCandidate = {
channels: {
discord: {
accounts: {
@@ -150,14 +150,18 @@ describe("resolveDiscordTarget", () => {
} as OpenClawConfig;
expectTargetFields(
await resolveDiscordTarget("123", { cfg, accountId: "default" }, { defaultKind: "channel" }),
await resolveDiscordTarget(
"123",
{ cfg: cfgCandidate, accountId: "default" },
{ defaultKind: "channel" },
),
{ kind: "user", id: "123", normalized: "user:123" },
);
expect(listPeers).not.toHaveBeenCalled();
});
it("uses legacy dm.allowFrom when disambiguating bare numeric ids", async () => {
const cfg = {
const cfgEntry = {
channels: {
discord: {
accounts: {
@@ -170,13 +174,17 @@ describe("resolveDiscordTarget", () => {
} as OpenClawConfig;
expectTargetFields(
await resolveDiscordTarget("456", { cfg, accountId: "default" }, { defaultKind: "channel" }),
await resolveDiscordTarget(
"456",
{ cfg: cfgEntry, accountId: "default" },
{ defaultKind: "channel" },
),
{ kind: "user", id: "456", normalized: "user:456" },
);
});
it("prefers top-level allowFrom over legacy dm.allowFrom for bare numeric ids", async () => {
const cfg = {
const cfgResult = {
channels: {
discord: {
accounts: {
@@ -190,13 +198,17 @@ describe("resolveDiscordTarget", () => {
} as OpenClawConfig;
expectTargetFields(
await resolveDiscordTarget("456", { cfg, accountId: "default" }, { defaultKind: "channel" }),
await resolveDiscordTarget(
"456",
{ cfg: cfgResult, accountId: "default" },
{ defaultKind: "channel" },
),
{ kind: "channel", id: "456", normalized: "channel:456" },
);
});
it("uses account legacy dm.allowFrom before inherited root allowFrom for bare numeric ids", async () => {
const cfg = {
const cfgValue = {
channels: {
discord: {
allowFrom: ["123"],
@@ -210,17 +222,25 @@ describe("resolveDiscordTarget", () => {
} as OpenClawConfig;
expectTargetFields(
await resolveDiscordTarget("456", { cfg, accountId: "work" }, { defaultKind: "channel" }),
await resolveDiscordTarget(
"456",
{ cfg: cfgValue, accountId: "work" },
{ defaultKind: "channel" },
),
{ kind: "user", id: "456", normalized: "user:456" },
);
expectTargetFields(
await resolveDiscordTarget("123", { cfg, accountId: "work" }, { defaultKind: "channel" }),
await resolveDiscordTarget(
"123",
{ cfg: cfgValue, accountId: "work" },
{ defaultKind: "channel" },
),
{ kind: "channel", id: "123", normalized: "channel:123" },
);
});
it("caches username lookups under the configured default account when accountId is omitted", async () => {
const cfg = {
const cfgLocal = {
channels: {
discord: {
defaultAccount: "work",
@@ -237,7 +257,7 @@ describe("resolveDiscordTarget", () => {
{ kind: "user", id: "user:999", name: "Jane" } as const,
]);
expectTargetFields(await resolveDiscordTarget("jane", { cfg }), {
expectTargetFields(await resolveDiscordTarget("jane", { cfg: cfgLocal }), {
kind: "user",
id: "999",
normalized: "user:999",

View File

@@ -57,7 +57,7 @@ const {
handlers: Map<string, EventHandler>;
};
const createConnectionMock = (): MockConnection => {
const createConnectionMockLocal = (): MockConnection => {
const handlers = new Map<string, EventHandler>();
const daveSetPassthroughMode = vi.fn();
const connection: MockConnection = {
@@ -98,9 +98,9 @@ const {
return connection;
};
const getVoiceConnectionMock = vi.fn((): MockConnection | undefined => undefined);
const getVoiceConnectionMockLocal = vi.fn((): MockConnection | undefined => undefined);
const realtimeSessionMock = {
const realtimeSessionMockLocal = {
bridge: { supportsToolResultContinuation: true },
acknowledgeMark: vi.fn(),
close: vi.fn(),
@@ -114,9 +114,9 @@ const {
};
return {
createConnectionMock,
getVoiceConnectionMock,
joinVoiceChannelMock: vi.fn(() => createConnectionMock()),
createConnectionMock: createConnectionMockLocal,
getVoiceConnectionMock: getVoiceConnectionMockLocal,
joinVoiceChannelMock: vi.fn(() => createConnectionMockLocal()),
entersStateMock: vi.fn(async (_target?: unknown, _state?: string, _timeoutMs?: number) => {
return undefined;
}),
@@ -148,7 +148,7 @@ const {
provider: { id: "openai" },
providerConfig: { model: "gpt-realtime-2", voice: "cedar" },
})),
createRealtimeVoiceBridgeSessionMock: vi.fn((_params?: unknown) => realtimeSessionMock),
createRealtimeVoiceBridgeSessionMock: vi.fn((_params?: unknown) => realtimeSessionMockLocal),
controlRealtimeVoiceAgentRunMock: vi.fn<() => Promise<RealtimeVoiceAgentControlResult>>(
async () => ({
ok: false,
@@ -163,7 +163,7 @@ const {
suppress: false,
}),
),
realtimeSessionMock,
realtimeSessionMock: realtimeSessionMockLocal,
decodeOpusStreamMock: vi.fn(),
decodeOpusStreamChunksMock: vi.fn(),
updateVoiceStateMock: vi.fn(),

View File

@@ -683,7 +683,7 @@ export class DiscordVoiceManager {
};
const stopEntry = (
entry: VoiceSessionEntry,
options: { destroyConnection: boolean; reason: string },
optionsLocal: { destroyConnection: boolean; reason: string },
) => {
if (stopped) {
return;
@@ -710,11 +710,11 @@ export class DiscordVoiceManager {
entry.realtime?.close();
entry.realtime = undefined;
player.stop();
if (options.destroyConnection) {
if (optionsLocal.destroyConnection) {
destroyVoiceConnectionSafely({
connection,
voiceSdk,
reason: options.reason,
reason: optionsLocal.reason,
});
}
};

View File

@@ -512,8 +512,8 @@ async function fetchImageBuffer(
const mimeType = response.headers.get("content-type")?.trim() || "image/png";
return {
buffer: await readResponseWithLimit(response, maxBytes, {
onOverflow: ({ maxBytes }) =>
new Error(`fal generated image download exceeds ${maxBytes} bytes`),
onOverflow: ({ maxBytes: maxBytesLocal }) =>
new Error(`fal generated image download exceeds ${maxBytesLocal} bytes`),
}),
mimeType,
};

View File

@@ -207,9 +207,9 @@ async function downloadFalVideo(
let buffer: Buffer;
try {
buffer = await readResponseWithLimit(response, maxBytes, {
onOverflow: ({ maxBytes }) => {
onOverflow: ({ maxBytes: maxBytesLocal }) => {
exceededMaxBytes = true;
return new Error(`fal generated video download exceeds ${maxBytes} bytes`);
return new Error(`fal generated video download exceeds ${maxBytesLocal} bytes`);
},
});
} catch (error) {

View File

@@ -168,14 +168,14 @@ describe("parseFeishuMessageEvent mentionedBot", () => {
});
it("returns mentionedBot=true for post message with at (no top-level mentions)", () => {
const BOT_OPEN_ID = "ou_bot_123";
const BOT_OPEN_IDLocal = "ou_bot_123";
const event = makePostEvent({
content: [
[{ tag: "at", user_id: BOT_OPEN_ID, user_name: "claw" }],
[{ tag: "at", user_id: BOT_OPEN_IDLocal, user_name: "claw" }],
[{ tag: "text", text: "What does this document say" }],
],
});
const ctx = parseFeishuMessageEvent(event, BOT_OPEN_ID);
const ctx = parseFeishuMessageEvent(event, BOT_OPEN_IDLocal);
expect(ctx.mentionedBot).toBe(true);
});

View File

@@ -162,13 +162,6 @@ function buildDefaultResolveRoute(): ResolvedAgentRoute {
matchedBy: "default",
};
}
function createUnboundConfiguredRoute(
route: NonNullable<ConfiguredBindingRoute>["route"],
): ConfiguredBindingRoute {
return { bindingResolution: null, route };
}
function createFeishuBotRuntime(overrides: DeepPartial<PluginRuntime> = {}): PluginRuntime {
return {
channel: {

View File

@@ -808,10 +808,10 @@ export async function handleFeishuMessage(params: {
if (!isGroup && route.matchedBy === "default") {
const dynamicCfg = feishuCfg?.dynamicAgentCreation as DynamicAgentCreationConfig | undefined;
if (dynamicCfg?.enabled) {
const runtime = getFeishuRuntime();
const runtimeLocal = getFeishuRuntime();
const result = await maybeCreateDynamicAgent({
cfg,
runtime,
runtime: runtimeLocal,
senderOpenId: ctx.senderOpenId,
dynamicCfg,
configWritesAllowed: resolveChannelConfigWrites({
@@ -1387,22 +1387,22 @@ export async function handleFeishuMessage(params: {
.map((id) => (id ? normalizeFeishuAllowEntry(id) : ""))
.find((recipient) => recipient === pinnedMainDmOwner)
: undefined;
const buildFeishuInboundLastRouteUpdate = (params: {
const buildFeishuInboundLastRouteUpdate = (paramsLocal: {
accountId: string;
sessionKey: string;
}) => {
const inboundLastRouteSessionKey =
params.sessionKey === route.sessionKey
paramsLocal.sessionKey === route.sessionKey
? resolveInboundLastRouteSessionKey({
route,
sessionKey: params.sessionKey,
sessionKey: paramsLocal.sessionKey,
})
: params.sessionKey;
: paramsLocal.sessionKey;
return {
sessionKey: inboundLastRouteSessionKey,
channel: "feishu" as const,
to: feishuTo,
accountId: params.accountId,
accountId: paramsLocal.accountId,
...(lastRouteThreadId ? { threadId: lastRouteThreadId } : {}),
mainDmOwnerPin:
!isGroup && inboundLastRouteSessionKey === route.mainSessionKey && pinnedMainDmOwner

View File

@@ -279,7 +279,7 @@ describe("feishuPlugin actions", () => {
});
it("honors the selected Feishu account during discovery", () => {
const cfg = {
const cfgLocal = {
channels: {
feishu: {
enabled: true,
@@ -302,7 +302,7 @@ describe("feishuPlugin actions", () => {
},
} as OpenClawConfig;
expect(getDescribedActions(cfg, "default")).toEqual([
expect(getDescribedActions(cfgLocal, "default")).toEqual([
"send",
"read",
"edit",
@@ -314,7 +314,7 @@ describe("feishuPlugin actions", () => {
"channel-info",
"channel-list",
]);
expect(getDescribedActions(cfg, "work")).toEqual([
expect(getDescribedActions(cfgLocal, "work")).toEqual([
"send",
"read",
"edit",

View File

@@ -276,7 +276,7 @@ describe("feishu_doc image fetch hardening", () => {
const call = blockDescendantCreateMock.mock.calls[0]?.[0];
expect(call?.data.children_id).toEqual(["h1", "p1", "h2", "list1"]);
expect((call?.data.descendants as Array<{ block_id: string }>).map((b) => b.block_id)).toEqual([
expect((call!.data.descendants as Array<{ block_id: string }>).map((b) => b.block_id)).toEqual([
"h1",
"p1",
"h2",

View File

@@ -28,8 +28,6 @@ const {
sendMessageFeishuMock,
withReplyDispatcherMock,
} = getFeishuLifecycleTestMocks();
let handlers: Record<string, (data: unknown) => Promise<void>> = {};
let lastRuntime = createRuntimeEnv();
const originalStateDir = process.env.OPENCLAW_STATE_DIR;
const { cfg: lifecycleConfig, account: lifecycleAccount } = createFeishuLifecycleFixture({
@@ -62,9 +60,7 @@ async function setupLifecycleMonitor() {
lastRuntime = createRuntimeEnv();
return setupFeishuLifecycleHandler({
createEventDispatcherMock,
onRegister: (registered) => {
handlers = registered;
},
onRegister: () => {},
runtime: lastRuntime,
cfg: lifecycleConfig,
account: lifecycleAccount,
@@ -77,7 +73,6 @@ describe("Feishu ACP-init failure lifecycle", () => {
beforeEach(() => {
vi.useRealTimers();
resetFeishuLifecycleTestMocks();
handlers = {};
lastRuntime = createRuntimeEnv();
setFeishuLifecycleStateDir("openclaw-feishu-acp-failure");

View File

@@ -31,8 +31,6 @@ const {
touchBindingMock,
withReplyDispatcherMock,
} = getFeishuLifecycleTestMocks();
let handlers: Record<string, (data: unknown) => Promise<void>> = {};
let lastRuntime = createRuntimeEnv();
const originalStateDir = process.env.OPENCLAW_STATE_DIR;
const lifecycleConfig = createFeishuLifecycleConfig({
@@ -77,9 +75,7 @@ async function setupLifecycleMonitor() {
lastRuntime = createRuntimeEnv();
return setupFeishuLifecycleHandler({
createEventDispatcherMock,
onRegister: (registered) => {
handlers = registered;
},
onRegister: () => {},
runtime: lastRuntime,
cfg: lifecycleConfig,
account: lifecycleAccount,
@@ -92,7 +88,6 @@ describe("Feishu bot-menu lifecycle", () => {
beforeEach(() => {
vi.useRealTimers();
resetFeishuLifecycleTestMocks();
handlers = {};
lastRuntime = createRuntimeEnv();
setFeishuLifecycleStateDir("openclaw-feishu-bot-menu");

View File

@@ -33,8 +33,6 @@ const {
touchBindingMock,
withReplyDispatcherMock,
} = getFeishuLifecycleTestMocks();
let handlers: Record<string, (data: unknown) => Promise<void>> = {};
let lastRuntime = createRuntimeEnv();
const originalStateDir = process.env.OPENCLAW_STATE_DIR;
const lifecycleConfig = createFeishuLifecycleConfig({
@@ -104,9 +102,7 @@ async function setupLifecycleMonitor() {
lastRuntime = createRuntimeEnv();
return setupFeishuLifecycleHandler({
createEventDispatcherMock,
onRegister: (registered) => {
handlers = registered;
},
onRegister: () => {},
runtime: lastRuntime,
cfg: lifecycleConfig,
account: lifecycleAccount,
@@ -143,7 +139,6 @@ describe("Feishu card-action lifecycle", () => {
beforeEach(() => {
vi.useRealTimers();
resetFeishuLifecycleTestMocks();
handlers = {};
lastRuntime = createRuntimeEnv();
resetProcessedFeishuCardActionTokensForTests();
setFeishuLifecycleStateDir("openclaw-feishu-card-action");

View File

@@ -166,8 +166,8 @@ export function createFeishuMessageReceiveHandler({
recordProcessedMessage,
getBotOpenId = () => undefined,
getBotName = () => undefined,
resolveSequentialKey = ({ accountId, event }) =>
`feishu:${accountId}:${event.message.chat_id?.trim() || "unknown"}`,
resolveSequentialKey = ({ accountId: accountIdLocal, event }) =>
`feishu:${accountIdLocal}:${event.message.chat_id?.trim() || "unknown"}`,
}: FeishuMessageReceiveHandlerContext): (data: unknown) => Promise<void> {
const inboundDebounceMs = channelRuntime.debounce.resolveInboundDebounceMs({
cfg,

View File

@@ -440,27 +440,27 @@ export function createFeishuReplyDispatcher(params: CreateFeishuReplyDispatcherP
flushStreamingCardUpdate(buildCombinedStreamText(reasoningText, streamText));
};
const sendChunkedTextReply = async (params: {
const sendChunkedTextReply = async (paramsLocal: {
text: string;
useCard: boolean;
infoKind?: string;
sendChunk: (params: { chunk: string; isFirst: boolean }) => Promise<void>;
}) => {
const chunkSource = params.useCard
? params.text
: core.channel.text.convertMarkdownTables(params.text, tableMode);
const chunkSource = paramsLocal.useCard
? paramsLocal.text
: core.channel.text.convertMarkdownTables(paramsLocal.text, tableMode);
const chunks = resolveTextChunksWithFallback(
chunkSource,
core.channel.text.chunkTextWithMode(chunkSource, textChunkLimit, chunkMode),
);
for (const [index, chunk] of chunks.entries()) {
await params.sendChunk({
await paramsLocal.sendChunk({
chunk,
isFirst: index === 0,
});
}
if (params.infoKind === "final") {
deliveredFinalTexts.add(params.text);
if (paramsLocal.infoKind === "final") {
deliveredFinalTexts.add(paramsLocal.text);
}
};
@@ -744,7 +744,7 @@ export function createFeishuReplyDispatcher(params: CreateFeishuReplyDispatcherP
if (!isChannelProgressDraftWorkToolName(payload.name)) {
return;
}
const statusLine = formatChannelProgressDraftLineForEntry(
const statusLineLocal = formatChannelProgressDraftLineForEntry(
account.config,
{
event: "tool",
@@ -756,8 +756,8 @@ export function createFeishuReplyDispatcher(params: CreateFeishuReplyDispatcherP
detailMode: payload.detailMode,
},
);
if (statusLine) {
updateStreamingStatusLine(statusLine);
if (statusLineLocal) {
updateStreamingStatusLine(statusLineLocal);
}
}
: undefined,

View File

@@ -163,7 +163,7 @@ export function createFeishuThreadBindingManager(params: {
if (!normalizedConversationId || !normalizedTargetSessionKey) {
return null;
}
const existing = getState().bindingsByAccountConversation.get(
const existingLocal = getState().bindingsByAccountConversation.get(
resolveBindingKey({ accountId, conversationId: normalizedConversationId }),
);
const now = Date.now();
@@ -171,29 +171,29 @@ export function createFeishuThreadBindingManager(params: {
accountId,
conversationId: normalizedConversationId,
parentConversationId:
normalizeOptionalString(parentConversationId) ?? existing?.parentConversationId,
normalizeOptionalString(parentConversationId) ?? existingLocal?.parentConversationId,
deliveryTo:
typeof metadata?.deliveryTo === "string" && metadata.deliveryTo.trim()
? metadata.deliveryTo.trim()
: existing?.deliveryTo,
: existingLocal?.deliveryTo,
deliveryThreadId:
typeof metadata?.deliveryThreadId === "string" && metadata.deliveryThreadId.trim()
? metadata.deliveryThreadId.trim()
: existing?.deliveryThreadId,
: existingLocal?.deliveryThreadId,
targetKind: toFeishuTargetKind(targetKind),
targetSessionKey: normalizedTargetSessionKey,
agentId:
typeof metadata?.agentId === "string" && metadata.agentId.trim()
? metadata.agentId.trim()
: (existing?.agentId ?? resolveAgentIdFromSessionKey(normalizedTargetSessionKey)),
: (existingLocal?.agentId ?? resolveAgentIdFromSessionKey(normalizedTargetSessionKey)),
label:
typeof metadata?.label === "string" && metadata.label.trim()
? metadata.label.trim()
: existing?.label,
: existingLocal?.label,
boundBy:
typeof metadata?.boundBy === "string" && metadata.boundBy.trim()
? metadata.boundBy.trim()
: existing?.boundBy,
: existingLocal?.boundBy,
boundAt: now,
lastActivityAt: now,
};

View File

@@ -83,12 +83,14 @@ function lastClientAppId(): string | undefined {
describe("feishu tool account routing", () => {
beforeAll(async () => {
({ registerFeishuBitableTools, registerFeishuDriveTools, registerFeishuPermTools } =
await import("./bitable.js").then(async ({ registerFeishuBitableTools }) => ({
registerFeishuBitableTools,
...(await import("./drive.js")),
...(await import("./perm.js")),
...(await import("./wiki.js")),
})));
await import("./bitable.js").then(
async ({ registerFeishuBitableTools: registerFeishuBitableToolsLocal }) => ({
registerFeishuBitableTools: registerFeishuBitableToolsLocal,
...(await import("./drive.js")),
...(await import("./perm.js")),
...(await import("./wiki.js")),
}),
));
({ registerFeishuWikiTools } = await import("./wiki.js"));
});

View File

@@ -1,6 +1,4 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { gzipSync } from "node:zlib";
import type { OpenClawPluginNodeInvokePolicyContext } from "openclaw/plugin-sdk/plugin-entry";
import { afterAll, afterEach, describe, expect, it, vi } from "vitest";

View File

@@ -417,17 +417,17 @@ export async function runFirecrawlSearch(
errorLabel: "Firecrawl Search",
},
async (response) => {
const payload = await readFirecrawlJsonResponse(response, "Firecrawl Search API error");
if (payload.success === false) {
const payloadValue = await readFirecrawlJsonResponse(response, "Firecrawl Search API error");
if (payloadValue.success === false) {
const error =
typeof payload.error === "string"
? payload.error
: typeof payload.message === "string"
? payload.message
typeof payloadValue.error === "string"
? payloadValue.error
: typeof payloadValue.message === "string"
? payloadValue.message
: "unknown error";
throw new Error(`Firecrawl Search API error: ${error}`);
}
return payload;
return payloadValue;
},
);
const result = buildSearchPayload({
@@ -573,19 +573,19 @@ export async function runFirecrawlScrape(
},
},
async (response) => {
const payload = await readFirecrawlJsonResponse(response, "Firecrawl fetch failed");
if (payload.success === false) {
const payloadLocal = await readFirecrawlJsonResponse(response, "Firecrawl fetch failed");
if (payloadLocal.success === false) {
const detail =
typeof payload.error === "string"
? payload.error
: typeof payload.message === "string"
? payload.message
typeof payloadLocal.error === "string"
? payloadLocal.error
: typeof payloadLocal.message === "string"
? payloadLocal.message
: response.statusText;
throw new Error(
`Firecrawl fetch failed (${response.status}): ${wrapWebContent(detail, "web_fetch")}`.trim(),
);
}
return payload;
return payloadLocal;
},
);
const result = parseFirecrawlScrapePayload({

View File

@@ -83,11 +83,6 @@ async function createAgentDir() {
tempDirs.push(dir);
return dir;
}
function registerProviderForTest() {
return registerProviderWithPluginConfig({});
}
function requireFirstMockArg<T>(
mock: { mock: { calls: Array<[T, ...unknown[]]> } },
label: string,

View File

@@ -1997,7 +1997,9 @@ describe("google-meet plugin", () => {
"Chrome observe-only mode does not require a realtime audio bridge",
);
expect(
result.details.checks?.filter((check) => check.id === "chrome-local-audio-device"),
result.details.checks?.filter(
(checkLocal) => checkLocal.id === "chrome-local-audio-device",
),
).toStrictEqual([]);
expect(runCommandWithTimeout).not.toHaveBeenCalled();
} finally {

View File

@@ -46,7 +46,7 @@ describe("google-meet config compatibility", () => {
]);
expect(
(
migration?.config.plugins?.entries?.["google-meet"] as {
migration!.config.plugins!.entries!["google-meet"] as {
config?: { realtime?: Record<string, unknown> };
}
).config?.realtime,
@@ -85,7 +85,7 @@ describe("google-meet config compatibility", () => {
expect(migration.changes).toStrictEqual([]);
expect(
(
migration.config.plugins?.entries?.["google-meet"] as {
migration.config.plugins!.entries!["google-meet"] as {
config?: { realtime?: Record<string, unknown> };
}
).config?.realtime,

View File

@@ -521,8 +521,8 @@ export async function startCommandAgentAudioBridge(params: {
{ onEvent: recordTalkObservabilityEvent },
);
const recentTalkEvents: TalkEvent[] = [];
const emitTalkEvent = (input: TalkEventInput) =>
pushGoogleMeetTalkEvent(recentTalkEvents, talk.emit(input));
const emitTalkEvent = (inputResult: TalkEventInput) =>
pushGoogleMeetTalkEvent(recentTalkEvents, talk.emit(inputResult));
const ensureTalkTurn = () => {
const turn = talk.ensureTurn({
payload: { meetingSessionId: params.meetingSessionId },
@@ -1081,8 +1081,8 @@ export async function startCommandRealtimeAudioBridge(params: {
pushGoogleMeetTalkEvent(recentTalkEvents, event);
}
};
const emitTalkEvent = (input: TalkEventInput): void => {
rememberTalkEvent(talk.emit(input));
const emitTalkEvent = (inputValue: TalkEventInput): void => {
rememberTalkEvent(talk.emit(inputValue));
};
const ensureTalkTurn = (): string => {
const turn = talk.ensureTurn({
@@ -1270,7 +1270,8 @@ export async function startCommandRealtimeAudioBridge(params: {
meetingSessionId: params.meetingSessionId,
requesterSessionKey: params.requesterSessionKey,
transcript,
onTalkEvent: (input) => emitTalkEvent({ ...input, turnId: input.turnId ?? turnId }),
onTalkEvent: (inputLocal) =>
emitTalkEvent({ ...inputLocal, turnId: inputLocal.turnId ?? turnId }),
});
},
onError: (error) => {

View File

@@ -363,13 +363,13 @@ export async function createGeminiEmbeddingProvider(
const embedBatch = async (
texts: string[],
options?: { signal?: AbortSignal },
optionsLocal?: { signal?: AbortSignal },
): Promise<number[][]> => {
return await embedBatchInputs(
texts.map((text) => ({
text,
})),
options,
optionsLocal,
);
};

View File

@@ -145,7 +145,7 @@ describeLive("google plugin live", () => {
expect(result?.provider).toBe("gemini");
expect(typeof result?.content).toBe("string");
expect((result?.content as string).length).toBeGreaterThan(20);
expect((result!.content as string).length).toBeGreaterThan(20);
expect(Array.isArray(result?.citations)).toBe(true);
}, 120_000);
@@ -171,9 +171,9 @@ describeLive("google plugin live", () => {
expect(process.env.GOOGLE_API_KEY).toBeUndefined();
expect(result?.provider).toBe("gemini");
expect(typeof result?.content).toBe("string");
expect((result?.content as string).length).toBeGreaterThan(20);
expect((result!.content as string).length).toBeGreaterThan(20);
expect(Array.isArray(result?.citations)).toBe(true);
expect((result?.citations as unknown[]).length).toBeGreaterThan(0);
expect((result!.citations as unknown[]).length).toBeGreaterThan(0);
});
}, 120_000);
});

View File

@@ -1,15 +1,18 @@
import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
const { createGoogleGenAIMock, generateContentMock } = vi.hoisted(() => {
const generateContentMock = vi.fn();
const createGoogleGenAIMock = vi.fn(() => {
const generateContentMockLocal = vi.fn();
const createGoogleGenAIMockLocal = vi.fn(() => {
return {
models: {
generateContent: generateContentMock,
generateContent: generateContentMockLocal,
},
};
});
return { createGoogleGenAIMock, generateContentMock };
return {
createGoogleGenAIMock: createGoogleGenAIMockLocal,
generateContentMock: generateContentMockLocal,
};
});
vi.mock("./google-genai-runtime.js", () => ({

View File

@@ -21,17 +21,21 @@ type MockGoogleLiveConnectParams = {
};
const { connectMock, createTokenMock, session } = vi.hoisted(() => {
const session: MockGoogleLiveSession = {
const sessionValue: MockGoogleLiveSession = {
close: vi.fn(),
sendClientContent: vi.fn(),
sendRealtimeInput: vi.fn(),
sendToolResponse: vi.fn(),
};
const connectMock = vi.fn(async (_params: MockGoogleLiveConnectParams) => session);
const createTokenMock = vi.fn(async (_params: unknown) => ({
const connectMockLocal = vi.fn(async (_params: MockGoogleLiveConnectParams) => sessionValue);
const createTokenMockLocal = vi.fn(async (_params: unknown) => ({
name: "auth_tokens/browser-session",
}));
return { connectMock, createTokenMock, session };
return {
connectMock: connectMockLocal,
createTokenMock: createTokenMockLocal,
session: sessionValue,
};
});
vi.mock("./google-genai-runtime.js", () => ({
@@ -365,7 +369,7 @@ describe("buildGoogleRealtimeVoiceProvider", () => {
it("creates constrained browser sessions for Google Live Talk", async () => {
const provider = buildGoogleRealtimeVoiceProvider();
const session = await provider.createBrowserSession?.({
const sessionLocal = await provider.createBrowserSession?.({
providerConfig: {
apiKey: "gemini-key",
model: "gemini-live-2.5-flash-preview",
@@ -420,9 +424,9 @@ describe("buildGoogleRealtimeVoiceProvider", () => {
expect(liveConstraints?.config?.tools?.[0]?.functionDeclarations?.[0]?.behavior).toBe(
"NON_BLOCKING",
);
expect(session?.provider).toBe("google");
expect(session?.transport).toBe("provider-websocket");
const websocketSession = session as {
expect(sessionLocal?.provider).toBe("google");
expect(sessionLocal?.transport).toBe("provider-websocket");
const websocketSession = sessionLocal as {
audio: {
inputEncoding: string;
inputSampleRateHz: number;

View File

@@ -10,14 +10,14 @@ const {
googleAuthGetAccessTokenMock,
googleAuthMock,
} = vi.hoisted(() => {
const googleAuthGetAccessTokenMock = vi.fn();
const googleAuthGetAccessTokenMockLocal = vi.fn();
return {
buildGuardedModelFetchMock: vi.fn(),
guardedFetchMock: vi.fn(),
googleAuthGetAccessTokenMock,
googleAuthGetAccessTokenMock: googleAuthGetAccessTokenMockLocal,
googleAuthMock: vi.fn(function GoogleAuthMock() {
return {
getAccessToken: googleAuthGetAccessTokenMock,
getAccessToken: googleAuthGetAccessTokenMockLocal,
};
}),
};

View File

@@ -3,23 +3,28 @@ import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vites
const { createGoogleGenAIMock, downloadMock, generateVideosMock, getVideosOperationMock } =
vi.hoisted(() => {
const generateVideosMock = vi.fn();
const getVideosOperationMock = vi.fn();
const downloadMock = vi.fn();
const createGoogleGenAIMock = vi.fn(() => {
const generateVideosMockLocal = vi.fn();
const getVideosOperationMockLocal = vi.fn();
const downloadMockLocal = vi.fn();
const createGoogleGenAIMockLocal = vi.fn(() => {
return {
models: {
generateVideos: generateVideosMock,
generateVideos: generateVideosMockLocal,
},
operations: {
getVideosOperation: getVideosOperationMock,
getVideosOperation: getVideosOperationMockLocal,
},
files: {
download: downloadMock,
download: downloadMockLocal,
},
};
});
return { createGoogleGenAIMock, downloadMock, generateVideosMock, getVideosOperationMock };
return {
createGoogleGenAIMock: createGoogleGenAIMockLocal,
downloadMock: downloadMockLocal,
generateVideosMock: generateVideosMockLocal,
getVideosOperationMock: getVideosOperationMockLocal,
};
});
vi.mock("./google-genai-runtime.js", () => ({

View File

@@ -287,13 +287,13 @@ export const googlechatOutboundAdapter = {
typeof threadId === "number" ? String(threadId) : (threadId ?? replyToId ?? undefined);
const maxBytes = resolveChannelMediaMaxBytes({
cfg,
resolveChannelLimitMb: ({ cfg, accountId }) =>
resolveChannelLimitMb: ({ cfg: cfgLocal, accountId: accountIdLocal }) =>
(
cfg.channels?.googlechat as
cfgLocal.channels?.googlechat as
| { accounts?: Record<string, { mediaMaxMb?: number }>; mediaMaxMb?: number }
| undefined
)?.accounts?.[accountId]?.mediaMaxMb ??
(cfg.channels?.googlechat as { mediaMaxMb?: number } | undefined)?.mediaMaxMb,
)?.accounts?.[accountIdLocal]?.mediaMaxMb ??
(cfgLocal.channels?.googlechat as { mediaMaxMb?: number } | undefined)?.mediaMaxMb,
accountId,
});
const effectiveMaxBytes = maxBytes ?? (account.config.mediaMaxMb ?? 20) * 1024 * 1024;

View File

@@ -381,12 +381,12 @@ describe("googlechat google auth runtime", () => {
try {
const transport = await getGoogleAuthTransport();
const transportDefaults = transport.defaults as { fetchImplementation?: unknown };
const requestInterceptorAdd = transport.interceptors.request.add as unknown as ReturnType<
typeof vi.fn
>;
const responseInterceptorAdd = transport.interceptors.response.add as unknown as ReturnType<
const requestInterceptorAdd = transport.interceptors.request["add"] as unknown as ReturnType<
typeof vi.fn
>;
const responseInterceptorAdd = transport.interceptors.response[
"add"
] as unknown as ReturnType<typeof vi.fn>;
const requestInterceptor = mockCallArg(requestInterceptorAdd) as
| { resolved?: unknown }
| undefined;
@@ -414,10 +414,10 @@ describe("googlechat google auth runtime", () => {
expect(first).not.toBe(second);
expect(mocks.gaxiosCtor).toHaveBeenCalledTimes(2);
expect(first.interceptors.request.add).toHaveBeenCalledOnce();
expect(first.interceptors.response.add).toHaveBeenCalledOnce();
expect(second.interceptors.request.add).toHaveBeenCalledOnce();
expect(second.interceptors.response.add).toHaveBeenCalledOnce();
expect(first.interceptors.request["add"]).toHaveBeenCalledOnce();
expect(first.interceptors.response["add"]).toHaveBeenCalledOnce();
expect(second.interceptors.request["add"]).toHaveBeenCalledOnce();
expect(second.interceptors.response["add"]).toHaveBeenCalledOnce();
});
it("normalizes Google auth request headers before upstream interceptors run", () => {

View File

@@ -267,9 +267,9 @@ describe("googlechat monitor webhook", () => {
},
});
resolveWebhookTargetWithAuthOrReject.mockImplementation(async ({ isMatch, targets }) => {
for (const target of targets) {
if (await isMatch(target)) {
return target;
for (const targetLocal of targets) {
if (await isMatch(targetLocal)) {
return targetLocal;
}
}
return null;

View File

@@ -221,7 +221,7 @@ async function processMessageWithPipeline(params: {
senderEmail,
rawBody,
statusSink,
logVerbose: (message) => logVerbose(core, runtime, message),
logVerbose: (messageLocal) => logVerbose(core, runtime, messageLocal),
});
if (!access.ok) {
return;

View File

@@ -52,8 +52,8 @@ export async function gradiumTTS(params: {
await assertOkOrThrowProviderError(response, "Gradium API error");
return await readResponseWithLimit(response, maxBytes, {
onOverflow: ({ maxBytes }) =>
new Error(`Gradium TTS audio response exceeds ${maxBytes} bytes`),
onOverflow: ({ maxBytes: maxBytesLocal }) =>
new Error(`Gradium TTS audio response exceeds ${maxBytesLocal} bytes`),
});
} finally {
await release();

View File

@@ -189,20 +189,20 @@ async function runIMessageCliJson(
let stderr = "";
let killEscalation: ReturnType<typeof setTimeout> | null = null;
let settled = false;
const clearTimers = (options: { keepKillEscalation?: boolean } = {}): void => {
const clearTimers = (optionsValue: { keepKillEscalation?: boolean } = {}): void => {
if (timer) {
clearTimeout(timer);
}
if (killEscalation && !options.keepKillEscalation) {
if (killEscalation && !optionsValue.keepKillEscalation) {
clearTimeout(killEscalation);
}
};
const fail = (error: Error, options: { keepKillEscalation?: boolean } = {}): void => {
const fail = (error: Error, optionsLocal: { keepKillEscalation?: boolean } = {}): void => {
if (settled) {
return;
}
settled = true;
clearTimers(options);
clearTimers(optionsLocal);
reject(error);
};
const succeed = (value: Record<string, unknown>): void => {

View File

@@ -201,8 +201,10 @@ function readPersistedTarget(value: unknown): IMessageApprovalReactionTarget | n
return null;
}
const allowedDecisions = target.allowedDecisions
.map((value) => (typeof value === "string" ? normalizeApprovalDecision(value) : null))
.filter((value): value is ExecApprovalReplyDecision => Boolean(value));
.map((valueValue) =>
typeof valueValue === "string" ? normalizeApprovalDecision(valueValue) : null,
)
.filter((valueLocal): valueLocal is ExecApprovalReplyDecision => Boolean(valueLocal));
if (allowedDecisions.length === 0) {
return null;
}

View File

@@ -419,9 +419,9 @@ describe("performIMessageCatchup", () => {
// clamped to `earliestHeldFailureRow.rowid - 1` (== 9) so the next pass
// refetches row 10.
let dispatchCount = 0;
const dispatch = vi.fn<CatchupDispatchFn>(async (row) => {
const dispatch = vi.fn<CatchupDispatchFn>(async (rowLocal) => {
dispatchCount += 1;
if (row.guid === "A") {
if (rowLocal.guid === "A") {
return { ok: false };
}
return { ok: true };

View File

@@ -96,7 +96,7 @@ describe("combineIMessagePayloads", () => {
const payloads = Array.from({ length: 6 }, (_, i) =>
makePayload({
guid: `row-${i}`,
attachments: Array.from({ length: 5 }, (_, j) => ({
attachments: Array.from({ length: 5 }, (_Local, j) => ({
original_path: `/tmp/${i}-${j}.jpg`,
mime_type: "image/jpeg",
})),

View File

@@ -118,7 +118,7 @@ describe("sendMessageIMessage receipts", () => {
expect(result.echoText).toBe("<media:image>");
expect(result.receipt.primaryPlatformMessageId).toBe("p:0/media-guid");
expect(result.receipt.platformMessageIds).toEqual(["p:0/media-guid"]);
expect(client.request).not.toHaveBeenCalled();
expect(client["request"]).not.toHaveBeenCalled();
expect(runCliJson.mock.calls).toEqual([
[["send-attachment", "--chat", "chat-1", "--file", "/tmp/image.png", "--transport", "auto"]],
]);
@@ -162,7 +162,7 @@ describe("sendMessageIMessage receipts", () => {
});
expect(result.messageId).toBe("p:0/media-guid");
expect(client.request).not.toHaveBeenCalled();
expect(client["request"]).not.toHaveBeenCalled();
expect(runCliJson.mock.calls).toEqual([
[["group", "--chat-id", "42"]],
[
@@ -208,7 +208,7 @@ describe("sendMessageIMessage receipts", () => {
expect(runCliJson.mock.calls).toEqual([
[["send-attachment", "--chat", "chat-1", "--file", "/tmp/image.png", "--transport", "auto"]],
]);
expect(client.request).toHaveBeenCalledWith(
expect(client["request"]).toHaveBeenCalledWith(
"send",
expect.objectContaining({
chat_guid: "chat-1",
@@ -233,7 +233,7 @@ describe("sendMessageIMessage receipts", () => {
expect(result.messageId).toBe("12345");
expect(runCliJson.mock.calls).toEqual([[["group", "--chat-id", "42"]]]);
expect(client.request).toHaveBeenCalledWith(
expect(client["request"]).toHaveBeenCalledWith(
"send",
expect.objectContaining({
chat_id: 42,
@@ -259,7 +259,7 @@ describe("sendMessageIMessage receipts", () => {
runCliJson,
}),
).rejects.toThrow("attachment delivery failed");
expect(client.request).not.toHaveBeenCalled();
expect(client["request"]).not.toHaveBeenCalled();
});
it("routes DM handle media-only sends through send-attachment", async () => {
@@ -395,7 +395,7 @@ describe("sendMessageIMessage receipts", () => {
});
expect(runCliJson).not.toHaveBeenCalled();
expect(client.request).toHaveBeenCalledWith(
expect(client["request"]).toHaveBeenCalledWith(
"send",
expect.objectContaining({
chat_identifier: "team-thread",
@@ -545,7 +545,7 @@ describe("sendMessageIMessage receipts", () => {
expect(result.sentText).toBe("literal <media:image> text");
expect(result.echoText).toBe("literal <media:image> text");
expect(client.request).toHaveBeenCalledWith(
expect(client["request"]).toHaveBeenCalledWith(
"send",
expect.objectContaining({
chat_id: 42,
@@ -619,14 +619,14 @@ describe("sendMessageIMessage receipts", () => {
it("recovers approval prompt GUID without resending when rpc send times out", async () => {
const client = createRejectingClient(new Error("imsg rpc timeout (send)"));
const createClient = vi.fn(async () => client);
const createClientLocal = vi.fn(async () => client);
const runCliJson = vi.fn();
const resolveSentMessageGuidImpl = vi.fn(async () => "p:0/fallback-guid");
const approvalText = createApprovalText();
const result = await sendMessageIMessage("chat_id:42", approvalText, {
config: IMESSAGE_TEST_CFG,
createClient,
createClient: createClientLocal,
runCliJson,
service: "sms",
dbPath: "/Users/me/Library/Messages/chat.db",
@@ -635,7 +635,7 @@ describe("sendMessageIMessage receipts", () => {
expect(result.messageId).toBe("p:0/fallback-guid");
expect(result.guid).toBe("p:0/fallback-guid");
expect(client.stop).toHaveBeenCalledOnce();
expect(client["stop"]).toHaveBeenCalledOnce();
expect(runCliJson).not.toHaveBeenCalled();
expect(resolveSentMessageGuidImpl).toHaveBeenCalledWith({
dbPath: "/Users/me/Library/Messages/chat.db",

View File

@@ -106,10 +106,13 @@ export function createIMessagePluginBase(params: {
return {
...base,
messaging: {
resolveInboundAttachmentRoots: (params) =>
resolveIMessageAttachmentRoots({ accountId: params.accountId, cfg: params.cfg }),
resolveRemoteInboundAttachmentRoots: (params) =>
resolveIMessageRemoteAttachmentRoots({ accountId: params.accountId, cfg: params.cfg }),
resolveInboundAttachmentRoots: (paramsValue) =>
resolveIMessageAttachmentRoots({ accountId: paramsValue.accountId, cfg: paramsValue.cfg }),
resolveRemoteInboundAttachmentRoots: (paramsLocal) =>
resolveIMessageRemoteAttachmentRoots({
accountId: paramsLocal.accountId,
cfg: paramsLocal.cfg,
}),
},
} as Pick<
ChannelPlugin<ResolvedIMessageAccount>,

View File

@@ -207,7 +207,7 @@ export async function handleIrcInbound(params: {
providerKey: "irc",
accountId: account.accountId,
blockedLabel: GROUP_POLICY_BLOCKED_LABEL.channel,
log: (message) => runtime.log?.(message),
log: (messageLocal) => runtime.log?.(messageLocal),
});
const groupMatch = resolveIrcGroupMatch({

View File

@@ -75,11 +75,11 @@ describe("downloadLineMedia", () => {
saveMediaStreamMock.mockReset();
saveMediaStreamMock.mockImplementation(
async (stream: AsyncIterable<Buffer>, contentType?: string, subdir?: string) => {
const chunks: Buffer[] = [];
const chunksLocal: Buffer[] = [];
for await (const chunk of stream) {
chunks.push(Buffer.from(chunk));
chunksLocal.push(Buffer.from(chunk));
}
const buffer = Buffer.concat(chunks);
const buffer = Buffer.concat(chunksLocal);
return {
path: `/home/user/.openclaw/media/${subdir ?? "unknown"}/saved-media`,
contentType: detectMockContentType(buffer, contentType),

View File

@@ -91,7 +91,7 @@ export function createInfoCard(title: string, body: string, footer?: string): Fl
*/
export function createListCard(title: string, items: ListItem[]): FlexBubble {
const itemContents: FlexComponent[] = items.slice(0, 8).map((item, index) => {
const itemContents: FlexComponent[] = [
const itemContentsLocal: FlexComponent[] = [
{
type: "text",
text: item.title,
@@ -103,7 +103,7 @@ export function createListCard(title: string, items: ListItem[]): FlexBubble {
];
if (item.subtitle) {
itemContents.push({
itemContentsLocal.push({
type: "text",
text: item.subtitle,
size: "sm",
@@ -140,7 +140,7 @@ export function createListCard(title: string, items: ListItem[]): FlexBubble {
{
type: "box",
layout: "vertical",
contents: itemContents,
contents: itemContentsLocal,
flex: 1,
} as FlexBox,
],

View File

@@ -14,11 +14,14 @@ import {
} from "./rich-menu.js";
const { setRichMenuImageMock, MessagingApiBlobClientMock } = vi.hoisted(() => {
const setRichMenuImageMock = vi.fn();
const MessagingApiBlobClientMock = vi.fn(function () {
return { setRichMenuImage: setRichMenuImageMock };
const setRichMenuImageMockLocal = vi.fn();
const MessagingApiBlobClientMockLocal = vi.fn(function () {
return { setRichMenuImage: setRichMenuImageMockLocal };
});
return { setRichMenuImageMock, MessagingApiBlobClientMock };
return {
setRichMenuImageMock: setRichMenuImageMockLocal,
MessagingApiBlobClientMock: MessagingApiBlobClientMockLocal,
};
});
vi.mock("@line/bot-sdk", () => ({

Some files were not shown because too many files have changed in this diff Show More