chore: Enable more lint rules, disable some that trigger a lot. Will clean up later.

This commit is contained in:
cpojer
2026-01-31 16:03:28 +09:00
parent 481f696a87
commit 15792b153f
292 changed files with 643 additions and 699 deletions

View File

@@ -7,13 +7,24 @@
],
"categories": {
"correctness": "error",
"perf": "error"
"perf": "error",
"suspicious": "error",
},
"rules": {
"eslint-plugin-unicorn/prefer-array-find": "off",
"eslint/no-await-in-loop": "off",
"oxc/no-accumulating-spread": "off",
"oxc/no-map-spread": "off"
"oxc/no-map-spread": "off",
"typescript/no-unsafe-type-assertion": "off",
"typescript/no-unnecessary-template-expression": "off",
"unicorn/consistent-function-scoping": "off",
"typescript/no-extraneous-class": "off",
"oxc/no-async-endpoint-handlers": "off",
"eslint/no-useless-concat": "off",
"eslint/no-unused-vars": "off",
"eslint/no-new": "off",
"eslint/preserve-caught-error": "off",
"unicorn/require-post-message-target-origin": "off",
},
"ignorePatterns": ["src/canvas-host/a2ui/a2ui.bundle.js"]
}

View File

@@ -161,7 +161,7 @@ export function createAnthropicPayloadLogger(params: {
const wrapStreamFn: AnthropicPayloadLogger["wrapStreamFn"] = (streamFn) => {
const wrapped: StreamFn = (model, context, options) => {
if (!isAnthropicModel(model as Model<Api>)) {
if (!isAnthropicModel(model)) {
return streamFn(model, context, options);
}
const nextOnPayload = (payload: unknown) => {

View File

@@ -85,7 +85,7 @@ function applyReplacements(
replacements: Array<[number, number, string[]]>,
): string[] {
const result = [...lines];
for (const [startIndex, oldLen, newLines] of [...replacements].reverse()) {
for (const [startIndex, oldLen, newLines] of [...replacements].toReversed()) {
for (let i = 0; i < oldLen; i += 1) {
if (startIndex < result.length) {
result.splice(startIndex, 1);

View File

@@ -169,7 +169,7 @@ export function buildAuthHealthSummary(params: {
warnAfterMs,
}),
)
.sort((a, b) => {
.toSorted((a, b) => {
if (a.provider !== b.provider) {
return a.provider.localeCompare(b.provider);
}
@@ -236,7 +236,7 @@ export function buildAuthHealthSummary(params: {
}
}
const providers = Array.from(providersMap.values()).sort((a, b) =>
const providers = Array.from(providersMap.values()).toSorted((a, b) =>
a.provider.localeCompare(b.provider),
);

View File

@@ -9,9 +9,7 @@ export function resolveAuthProfileDisplayLabel(params: {
const { cfg, store, profileId } = params;
const profile = store.profiles[profileId];
const configEmail = cfg?.auth?.profiles?.[profileId]?.email?.trim();
const email =
configEmail ||
(profile && "email" in profile ? (profile.email as string | undefined)?.trim() : undefined);
const email = configEmail || (profile && "email" in profile ? profile.email?.trim() : undefined);
if (email) return `${profileId} (${email})`;
return profileId;
}

View File

@@ -62,7 +62,7 @@ async function refreshOAuthTokenWithLock(params: {
const newCredentials = await refreshQwenPortalCredentials(cred);
return { apiKey: newCredentials.access, newCredentials };
})()
: await getOAuthApiKey(cred.provider as OAuthProvider, oauthCreds);
: await getOAuthApiKey(cred.provider, oauthCreds);
if (!result) return null;
store.profiles[params.profileId] = {
...cred,
@@ -233,6 +233,7 @@ export async function resolveApiKeyForProfile(params: {
`OAuth token refresh failed for ${cred.provider}: ${message}. ` +
"Please try again or re-authenticate." +
(hint ? `\n\n${hint}` : ""),
{ cause: error },
);
}
}

View File

@@ -112,7 +112,7 @@ export function resolveAuthProfileOrder(params: {
}
const cooldownSorted = inCooldown
.sort((a, b) => a.cooldownUntil - b.cooldownUntil)
.toSorted((a, b) => a.cooldownUntil - b.cooldownUntil)
.map((entry) => entry.profileId);
const ordered = [...available, ...cooldownSorted];
@@ -163,7 +163,7 @@ function orderProfilesByMode(order: string[], store: AuthProfileStore): string[]
// Primary sort: type preference (oauth > token > api_key).
// Secondary sort: lastUsed (oldest first for round-robin within type).
const sorted = scored
.sort((a, b) => {
.toSorted((a, b) => {
// First by type (oauth > token > api_key)
if (a.typeScore !== b.typeScore) return a.typeScore - b.typeScore;
// Then by lastUsed (oldest first)
@@ -177,7 +177,7 @@ function orderProfilesByMode(order: string[], store: AuthProfileStore): string[]
profileId,
cooldownUntil: resolveProfileUnusableUntil(store.usageStats?.[profileId] ?? {}) ?? now,
}))
.sort((a, b) => a.cooldownUntil - b.cooldownUntil)
.toSorted((a, b) => a.cooldownUntil - b.cooldownUntil)
.map((entry) => entry.profileId);
return [...sorted, ...cooldownSorted];

View File

@@ -45,7 +45,7 @@ export function suggestOAuthProfileIdForLegacyDefault(params: {
const byEmail = oauthProfiles.find((id) => {
const cred = params.store.profiles[id];
if (!cred || cred.type !== "oauth") return false;
const email = (cred.email as string | undefined)?.trim();
const email = cred.email?.trim();
return email === configuredEmail || id === `${providerKey}:${configuredEmail}`;
});
if (byEmail) return byEmail;
@@ -93,11 +93,10 @@ export function repairOAuthProfileIdMismatch(params: {
}
const toCred = params.store.profiles[toProfileId];
const toEmail =
toCred?.type === "oauth" ? (toCred.email as string | undefined)?.trim() : undefined;
const toEmail = toCred?.type === "oauth" ? toCred.email?.trim() : undefined;
const nextProfiles = {
...(params.cfg.auth?.profiles as Record<string, AuthProfileConfig> | undefined),
...params.cfg.auth?.profiles,
} as Record<string, AuthProfileConfig>;
delete nextProfiles[legacyProfileId];
nextProfiles[toProfileId] = {

View File

@@ -912,6 +912,7 @@ export function createExecTool(
if (!nodeQuery && String(err).includes("node required")) {
throw new Error(
"exec host=node requires a node id when multiple nodes are available (set tools.exec.node or exec.node).",
{ cause: err },
);
}
throw err;
@@ -941,11 +942,11 @@ export function createExecTool(
let allowlistSatisfied = false;
if (hostAsk === "on-miss" && hostSecurity === "allowlist" && analysisOk) {
try {
const approvalsSnapshot = (await callGatewayTool(
const approvalsSnapshot = await callGatewayTool(
"exec.approvals.node.get",
{ timeoutMs: 10_000 },
{ nodeId },
)) as { file?: unknown } | null;
);
const approvalsFile =
approvalsSnapshot && typeof approvalsSnapshot === "object"
? approvalsSnapshot.file
@@ -1016,7 +1017,7 @@ export function createExecTool(
void (async () => {
let decision: string | null = null;
try {
const decisionResult = (await callGatewayTool(
const decisionResult = await callGatewayTool(
"exec.approval.request",
{ timeoutMs: DEFAULT_APPROVAL_REQUEST_TIMEOUT_MS },
{
@@ -1031,7 +1032,7 @@ export function createExecTool(
sessionKey: defaults?.sessionKey,
timeoutMs: DEFAULT_APPROVAL_TIMEOUT_MS,
},
)) as { decision?: string } | null;
);
decision =
decisionResult && typeof decisionResult === "object"
? (decisionResult.decision ?? null)
@@ -1124,20 +1125,11 @@ export function createExecTool(
}
const startedAt = Date.now();
const raw = (await callGatewayTool(
const raw = await callGatewayTool(
"node.invoke",
{ timeoutMs: invokeTimeoutMs },
buildInvokeParams(false, null),
)) as {
payload?: {
exitCode?: number;
timedOut?: boolean;
success?: boolean;
stdout?: string;
stderr?: string;
error?: string | null;
};
};
);
const payload = raw?.payload ?? {};
return {
content: [
@@ -1197,7 +1189,7 @@ export function createExecTool(
void (async () => {
let decision: string | null = null;
try {
const decisionResult = (await callGatewayTool(
const decisionResult = await callGatewayTool(
"exec.approval.request",
{ timeoutMs: DEFAULT_APPROVAL_REQUEST_TIMEOUT_MS },
{
@@ -1212,7 +1204,7 @@ export function createExecTool(
sessionKey: defaults?.sessionKey,
timeoutMs: DEFAULT_APPROVAL_TIMEOUT_MS,
},
)) as { decision?: string } | null;
);
decision =
decisionResult && typeof decisionResult === "object"
? (decisionResult.decision ?? null)

View File

@@ -116,7 +116,7 @@ export function createProcessTool(
exitSignal: s.exitSignal ?? undefined,
}));
const lines = [...running, ...finished]
.sort((a, b) => b.startedAt - a.startedAt)
.toSorted((a, b) => b.startedAt - a.startedAt)
.map((s) => {
const label = s.name ? truncateMiddle(s.name, 80) : truncateMiddle(s.command, 120);
return `${s.sessionId} ${pad(s.status, 9)} ${formatDuration(s.runtimeMs)} :: ${label}`;

View File

@@ -32,7 +32,7 @@ function normalizeProviderFilter(filter?: string[]): string[] {
const normalized = new Set(
filter.map((entry) => entry.trim().toLowerCase()).filter((entry) => entry.length > 0),
);
return Array.from(normalized).sort();
return Array.from(normalized).toSorted();
}
function buildCacheKey(params: {
@@ -168,7 +168,7 @@ export async function discoverBedrockModels(params: {
}),
);
}
return discovered.sort((a, b) => a.name.localeCompare(b.name));
return discovered.toSorted((a, b) => a.name.localeCompare(b.name));
})();
if (refreshIntervalSeconds > 0) {

View File

@@ -147,7 +147,7 @@ function stableStringify(value: unknown): string {
return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
}
const record = value as Record<string, unknown>;
const keys = Object.keys(record).sort();
const keys = Object.keys(record).toSorted();
const entries = keys.map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`);
return `{${entries.join(",")}}`;
}
@@ -249,9 +249,9 @@ export function createCacheTrace(params: CacheTraceInit): CacheTrace | null {
const wrapped: StreamFn = (model, context, options) => {
recordStage("stream:context", {
model: {
id: (model as Model<Api>)?.id,
provider: (model as Model<Api>)?.provider,
api: (model as Model<Api>)?.api,
id: model?.id,
provider: model?.provider,
api: model?.api,
},
system: (context as { system?: unknown }).system,
messages: (context as { messages?: AgentMessage[] }).messages ?? [],

View File

@@ -159,7 +159,7 @@ function buildModelAliasLines(cfg?: OpenClawConfig) {
entries.push({ alias, model });
}
return entries
.sort((a, b) => a.alias.localeCompare(b.alias))
.toSorted((a, b) => a.alias.localeCompare(b.alias))
.map((entry) => `- ${entry.alias}: ${entry.model}`);
}
@@ -228,7 +228,7 @@ export function normalizeCliModel(modelId: string, backend: CliBackendConfig): s
function toUsage(raw: Record<string, unknown>): CliUsage | undefined {
const pick = (key: string) =>
typeof raw[key] === "number" && raw[key] > 0 ? (raw[key] as number) : undefined;
typeof raw[key] === "number" && raw[key] > 0 ? raw[key] : undefined;
const input = pick("input_tokens") ?? pick("inputTokens");
const output = pick("output_tokens") ?? pick("outputTokens");
const cacheRead =

View File

@@ -128,8 +128,8 @@ describe("pruneHistoryForContextShare", () => {
const allIds = [
...pruned.droppedMessagesList.map((m) => m.timestamp),
...pruned.messages.map((m) => m.timestamp),
].sort((a, b) => a - b);
const originalIds = messages.map((m) => m.timestamp).sort((a, b) => a - b);
].toSorted((a, b) => a - b);
const originalIds = messages.map((m) => m.timestamp).toSorted((a, b) => a - b);
expect(allIds).toEqual(originalIds);
});

View File

@@ -34,13 +34,11 @@ function resolveProviderConfig(
const matched = Object.entries(providers).find(
([key]) => normalizeProviderId(key) === normalized,
);
return matched?.[1] as ModelProviderConfig | undefined;
return matched?.[1];
}
return (
(providers[normalized] as ModelProviderConfig | undefined) ??
(Object.entries(providers).find(([key]) => normalizeProviderId(key) === normalized)?.[1] as
| ModelProviderConfig
| undefined)
Object.entries(providers).find(([key]) => normalizeProviderId(key) === normalized)?.[1]
);
}

View File

@@ -83,9 +83,7 @@ export async function loadModelCatalog(params?: {
? entry.contextWindow
: undefined;
const reasoning = typeof entry?.reasoning === "boolean" ? entry.reasoning : undefined;
const input = Array.isArray(entry?.input)
? (entry.input as Array<"text" | "image">)
: undefined;
const input = Array.isArray(entry?.input) ? entry.input : undefined;
models.push({ id, name, provider, contextWindow, reasoning, input });
}

View File

@@ -9,7 +9,7 @@ export function normalizeModelCompat(model: Model<Api>): Model<Api> {
const isZai = model.provider === "zai" || baseUrl.includes("api.z.ai");
if (!isZai || !isOpenAiCompletionsModel(model)) return model;
const openaiModel = model as Model<"openai-completions">;
const openaiModel = model;
const compat = openaiModel.compat ?? undefined;
if (compat?.supportsDeveloperRole === false) return model;

View File

@@ -224,7 +224,7 @@ export async function runWithModelFallback<T>(params: {
let lastError: unknown;
for (let i = 0; i < candidates.length; i += 1) {
const candidate = candidates[i] as ModelCandidate;
const candidate = candidates[i];
if (authStore) {
const profileIds = resolveAuthProfileOrder({
cfg: params.cfg,
@@ -330,7 +330,7 @@ export async function runWithImageModelFallback<T>(params: {
let lastError: unknown;
for (let i = 0; i < candidates.length; i += 1) {
const candidate = candidates[i] as ModelCandidate;
const candidate = candidates[i];
try {
const result = await params.run(candidate.provider, candidate.model);
return {

View File

@@ -325,7 +325,7 @@ async function mapWithConcurrency<T, R>(
opts?: { onProgress?: (completed: number, total: number) => void },
): Promise<R[]> {
const limit = Math.max(1, Math.floor(concurrency));
const results = Array.from({ length: items.length }) as R[];
const results = Array.from({ length: items.length });
let nextIndex = 0;
let completed = 0;
@@ -334,7 +334,7 @@ async function mapWithConcurrency<T, R>(
const current = nextIndex;
nextIndex += 1;
if (current >= items.length) return;
results[current] = await fn(items[current] as T, current);
results[current] = await fn(items[current], current);
completed += 1;
opts?.onProgress?.(completed, items.length);
}

View File

@@ -79,7 +79,7 @@ export async function ensureOpenClawModelsJson(
const cfg = config ?? loadConfig();
const agentDir = agentDirOverride?.trim() ? agentDirOverride.trim() : resolveOpenClawAgentDir();
const explicitProviders = (cfg.models?.providers ?? {}) as Record<string, ProviderConfig>;
const explicitProviders = cfg.models?.providers ?? {};
const implicitProviders = await resolveImplicitProviders({ agentDir });
const providers: Record<string, ProviderConfig> = mergeProviders({
implicit: implicitProviders,

View File

@@ -167,7 +167,7 @@ describeLive("live models (profile keys)", () => {
const agentDir = resolveOpenClawAgentDir();
const authStorage = discoverAuthStorage(agentDir);
const modelRegistry = discoverModels(authStorage, agentDir);
const models = modelRegistry.getAll() as Array<Model<Api>>;
const models = modelRegistry.getAll();
const rawModels = process.env.OPENCLAW_LIVE_MODELS?.trim();
const useModern = rawModels === "modern" || rawModels === "all";

View File

@@ -160,7 +160,7 @@ function stableStringify(value: unknown): string {
return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
}
const record = value as Record<string, unknown>;
const keys = Object.keys(record).sort();
const keys = Object.keys(record).toSorted();
const entries = keys.map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`);
return `{${entries.join(",")}}`;
}

View File

@@ -60,7 +60,7 @@ export async function sanitizeSessionMessagesImages(
const toolMsg = msg as Extract<AgentMessage, { role: "toolResult" }>;
const content = Array.isArray(toolMsg.content) ? toolMsg.content : [];
const nextContent = (await sanitizeContentBlocksImages(
content as ContentBlock[],
content,
label,
)) as unknown as typeof toolMsg.content;
out.push({ ...toolMsg, content: nextContent });

View File

@@ -87,16 +87,16 @@ export function downgradeOpenAIReasoningBlocks(messages: AgentMessage[]): AgentM
}
const record = block as OpenAIThinkingBlock;
if (record.type !== "thinking") {
nextContent.push(block as AssistantContentBlock);
nextContent.push(block);
continue;
}
const signature = parseOpenAIReasoningSignature(record.thinkingSignature);
if (!signature) {
nextContent.push(block as AssistantContentBlock);
nextContent.push(block);
continue;
}
if (hasFollowingNonThinkingBlock(assistantMsg.content, i)) {
nextContent.push(block as AssistantContentBlock);
nextContent.push(block);
continue;
}
changed = true;

View File

@@ -65,7 +65,7 @@ function createStreamFnWithExtraParams(
const underlying = baseStreamFn ?? streamSimple;
const wrappedStreamFn: StreamFn = (model, context, options) =>
underlying(model as Model<Api>, context, {
underlying(model, context, {
...streamParams,
...options,
});

View File

@@ -60,7 +60,7 @@ function sanitizeAntigravityThinkingBlocks(messages: AgentMessage[]): AgentMessa
out.push(msg);
continue;
}
const assistant = msg as Extract<AgentMessage, { role: "assistant" }>;
const assistant = msg;
if (!Array.isArray(assistant.content)) {
out.push(msg);
continue;

View File

@@ -46,7 +46,7 @@ export function buildModelAliasLines(cfg?: OpenClawConfig) {
entries.push({ alias, model });
}
return entries
.sort((a, b) => a.alias.localeCompare(b.alias))
.toSorted((a, b) => a.alias.localeCompare(b.alias))
.map((entry) => `- ${entry.alias}: ${entry.model}`);
}

View File

@@ -82,11 +82,7 @@ vi.mock("../defaults.js", () => ({
}));
vi.mock("../failover-error.js", () => ({
FailoverError: class extends Error {
constructor(msg: string) {
super(msg);
}
},
FailoverError: class extends Error {},
resolveFailoverStatus: vi.fn(),
}));

View File

@@ -854,7 +854,7 @@ export async function runEmbeddedAttempt(
const lastAssistant = messagesSnapshot
.slice()
.reverse()
.toReversed()
.find((m) => (m as AgentMessage)?.role === "assistant") as AssistantMessage | undefined;
const toolMetasNormalized = toolMetas

View File

@@ -162,7 +162,7 @@ export function handleMessageEnd(
const msg = evt.message;
if (msg?.role !== "assistant") return;
const assistantMessage = msg as AssistantMessage;
const assistantMessage = msg;
promoteThinkingTagsToBlocks(assistantMessage);
const rawText = extractAssistantText(assistantMessage);

View File

@@ -14,7 +14,7 @@ export function setCompactionSafeguardRuntime(
return;
}
const key = sessionManager as object;
const key = sessionManager;
if (value === null) {
REGISTRY.delete(key);
return;
@@ -30,5 +30,5 @@ export function getCompactionSafeguardRuntime(
return null;
}
return REGISTRY.get(sessionManager as object) ?? null;
return REGISTRY.get(sessionManager) ?? null;
}

View File

@@ -117,8 +117,8 @@ function computeFileLists(fileOps: FileOperations): {
modifiedFiles: string[];
} {
const modified = new Set([...fileOps.edited, ...fileOps.written]);
const readFiles = [...fileOps.read].filter((f) => !modified.has(f)).sort();
const modifiedFiles = [...modified].sort();
const readFiles = [...fileOps.read].filter((f) => !modified.has(f)).toSorted();
const modifiedFiles = [...modified].toSorted();
return { readFiles, modifiedFiles };
}

View File

@@ -1,4 +1,3 @@
import type { AgentMessage } from "@mariozechner/pi-agent-core";
import type { ContextEvent, ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
import { pruneContextMessages } from "./pruner.js";
@@ -21,7 +20,7 @@ export default function contextPruningExtension(api: ExtensionAPI): void {
}
const next = pruneContextMessages({
messages: event.messages as AgentMessage[],
messages: event.messages,
settings: runtime.settings,
ctx,
isToolPrunable: runtime.isToolPrunable,

View File

@@ -20,7 +20,7 @@ export function setContextPruningRuntime(
return;
}
const key = sessionManager as object;
const key = sessionManager;
if (value === null) {
REGISTRY.delete(key);
return;
@@ -36,5 +36,5 @@ export function getContextPruningRuntime(
return null;
}
return REGISTRY.get(sessionManager as object) ?? null;
return REGISTRY.get(sessionManager) ?? null;
}

View File

@@ -32,7 +32,7 @@ export function toToolDefinitions(tools: AnyAgentTool[]): ToolDefinition[] {
label: tool.label ?? name,
description: tool.description ?? "",
// biome-ignore lint/suspicious/noExplicitAny: TypeBox schema from pi-agent-core uses a different module instance.
parameters: tool.parameters as any,
parameters: tool.parameters,
execute: async (
toolCallId,
params,

View File

@@ -117,7 +117,7 @@ describe("createOpenClawCodingTools", () => {
return {
name: tool.name,
type: schema?.type,
keys: schema ? Object.keys(schema).sort() : null,
keys: schema ? Object.keys(schema).toSorted() : null,
};
})
.filter((entry) => entry.type !== "object");

View File

@@ -272,11 +272,7 @@ export function createOpenClawReadTool(base: AnyAgentTool): AnyAgentTool {
normalized ??
(params && typeof params === "object" ? (params as Record<string, unknown>) : undefined);
assertRequiredParams(record, CLAUDE_PARAM_GROUPS.read, base.name);
const result = (await base.execute(
toolCallId,
normalized ?? params,
signal,
)) as AgentToolResult<unknown>;
const result = await base.execute(toolCallId, normalized ?? params, signal);
const filePath = typeof record?.path === "string" ? String(record.path) : "<unknown>";
const normalizedResult = await normalizeReadImageResult(result, filePath);
return sanitizeToolResultImages(normalizedResult, `read:${filePath}`);

View File

@@ -247,7 +247,7 @@ export function createOpenClawCodingTools(options?: {
// Wrap with param normalization for Claude Code compatibility
return [wrapToolParamNormalization(createEditTool(workspaceRoot), CLAUDE_PARAM_GROUPS.edit)];
}
return [tool as AnyAgentTool];
return [tool];
});
const { cleanupMs: cleanupMsOverride, ...execDefaults } = options?.exec ?? {};
const execTool = createExecTool({
@@ -338,13 +338,13 @@ export function createOpenClawCodingTools(options?: {
];
const coreToolNames = new Set(
tools
.filter((tool) => !getPluginToolMeta(tool as AnyAgentTool))
.filter((tool) => !getPluginToolMeta(tool))
.map((tool) => normalizeToolName(tool.name))
.filter(Boolean),
);
const pluginGroups = buildPluginToolGroups({
tools,
toolMeta: (tool) => getPluginToolMeta(tool as AnyAgentTool),
toolMeta: (tool) => getPluginToolMeta(tool),
});
const resolvePolicy = (policy: typeof profilePolicy, label: string) => {
const resolved = stripPluginOnlyAllowlist(policy, pluginGroups, coreToolNames);

View File

@@ -20,14 +20,14 @@ function normalizeForHash(value: unknown): unknown {
.filter((item): item is unknown => item !== undefined);
const primitives = normalized.filter(isPrimitive);
if (primitives.length === normalized.length) {
return [...primitives].sort((a, b) =>
return [...primitives].toSorted((a, b) =>
primitiveToString(a).localeCompare(primitiveToString(b)),
);
}
return normalized;
}
if (value && typeof value === "object") {
const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b));
const entries = Object.entries(value).toSorted(([a], [b]) => a.localeCompare(b));
const normalized: Record<string, unknown> = {};
for (const [key, entryValue] of entries) {
const next = normalizeForHash(entryValue);

View File

@@ -95,7 +95,7 @@ export function repairToolUseResultPairing(messages: AgentMessage[]): ToolUseRep
};
for (let i = 0; i < messages.length; i += 1) {
const msg = messages[i] as AgentMessage;
const msg = messages[i];
if (!msg || typeof msg !== "object") {
out.push(msg);
continue;
@@ -129,7 +129,7 @@ export function repairToolUseResultPairing(messages: AgentMessage[]): ToolUseRep
let j = i + 1;
for (; j < messages.length; j += 1) {
const next = messages[j] as AgentMessage;
const next = messages[j];
if (!next || typeof next !== "object") {
remainder.push(next);
continue;

View File

@@ -57,7 +57,7 @@ describe("buildWorkspaceSkillCommandSpecs", () => {
reservedNames: new Set(["help"]),
});
const names = commands.map((entry) => entry.name).sort();
const names = commands.map((entry) => entry.name).toSorted();
expect(names).toEqual(["hello_world", "hello_world_2", "help_2"]);
expect(commands.find((entry) => entry.skillName === "hidden-skill")).toBeUndefined();
});

View File

@@ -62,7 +62,7 @@ describe("buildWorkspaceSkillSnapshot", () => {
expect(snapshot.prompt).toContain("visible-skill");
expect(snapshot.prompt).not.toContain("hidden-skill");
expect(snapshot.skills.map((skill) => skill.name).sort()).toEqual([
expect(snapshot.skills.map((skill) => skill.name).toSorted()).toEqual([
"hidden-skill",
"visible-skill",
]);

View File

@@ -39,7 +39,7 @@ export function resolveSkillsInstallPreferences(config?: OpenClawConfig) {
const manager = managerRaw.toLowerCase();
const nodeManager =
manager === "pnpm" || manager === "yarn" || manager === "bun" || manager === "npm"
? (manager as "npm" | "pnpm" | "yarn" | "bun")
? manager
: "npm";
return { preferBrew, nodeManager };
}

View File

@@ -30,7 +30,7 @@ export function resolveConfigPath(config: OpenClawConfig | undefined, pathStr: s
export function isConfigPathTruthy(config: OpenClawConfig | undefined, pathStr: string): boolean {
const value = resolveConfigPath(config, pathStr);
if (value === undefined && pathStr in DEFAULT_CONFIG_VALUES) {
return DEFAULT_CONFIG_VALUES[pathStr] === true;
return DEFAULT_CONFIG_VALUES[pathStr];
}
return isTruthy(value);
}

View File

@@ -41,7 +41,7 @@ function parseInstallSpec(input: unknown): SkillInstallSpec | undefined {
}
const spec: SkillInstallSpec = {
kind: kind as SkillInstallSpec["kind"],
kind: kind,
};
if (typeof raw.id === "string") spec.id = raw.id;
@@ -78,7 +78,7 @@ export function resolveOpenClawMetadata(
const raw = getFrontmatterValue(frontmatter, "metadata");
if (!raw) return undefined;
try {
const parsed = JSON5.parse(raw) as Record<string, unknown>;
const parsed = JSON5.parse(raw);
if (!parsed || typeof parsed !== "object") return undefined;
const metadataRawCandidates = [MANIFEST_KEY, ...LEGACY_MANIFEST_KEYS];
let metadataRaw: unknown;

View File

@@ -327,19 +327,14 @@ export async function runSubagentAnnounceFlow(params: {
let outcome: SubagentRunOutcome | undefined = params.outcome;
if (!reply && params.waitForCompletion !== false) {
const waitMs = Math.min(params.timeoutMs, 60_000);
const wait = (await callGateway({
const wait = await callGateway({
method: "agent.wait",
params: {
runId: params.childRunId,
timeoutMs: waitMs,
},
timeoutMs: waitMs + 2000,
})) as {
status?: string;
error?: string;
startedAt?: number;
endedAt?: number;
};
});
if (wait?.status === "timeout") {
outcome = { status: "timeout" };
} else if (wait?.status === "error") {

View File

@@ -170,8 +170,7 @@ function ensureListener() {
}
const phase = evt.data?.phase;
if (phase === "start") {
const startedAt =
typeof evt.data?.startedAt === "number" ? (evt.data.startedAt as number) : undefined;
const startedAt = typeof evt.data?.startedAt === "number" ? evt.data.startedAt : undefined;
if (startedAt) {
entry.startedAt = startedAt;
persistSubagentRuns();
@@ -179,11 +178,10 @@ function ensureListener() {
return;
}
if (phase !== "end" && phase !== "error") return;
const endedAt =
typeof evt.data?.endedAt === "number" ? (evt.data.endedAt as number) : Date.now();
const endedAt = typeof evt.data?.endedAt === "number" ? evt.data.endedAt : Date.now();
entry.endedAt = endedAt;
if (phase === "error") {
const error = typeof evt.data?.error === "string" ? (evt.data.error as string) : undefined;
const error = typeof evt.data?.error === "string" ? evt.data.error : undefined;
entry.outcome = { status: "error", error };
} else {
entry.outcome = { status: "ok" };
@@ -284,14 +282,14 @@ export function registerSubagentRun(params: {
async function waitForSubagentCompletion(runId: string, waitTimeoutMs: number) {
try {
const timeoutMs = Math.max(1, Math.floor(waitTimeoutMs));
const wait = (await callGateway({
const wait = await callGateway({
method: "agent.wait",
params: {
runId,
timeoutMs,
},
timeoutMs: timeoutMs + 10_000,
})) as { status?: string; startedAt?: number; endedAt?: number; error?: string };
});
if (wait?.status !== "ok" && wait?.status !== "error") return;
const entry = subagentRuns.get(runId);
if (!entry) return;

View File

@@ -264,7 +264,7 @@ export function buildAgentSystemPrompt(params: {
const name = resolveToolName(tool);
return summary ? `- ${name}: ${summary}` : `- ${name}`;
});
for (const tool of extraTools.sort()) {
for (const tool of extraTools.toSorted()) {
const summary = coreToolSummaries[tool] ?? externalToolSummaries.get(tool);
const name = resolveToolName(tool);
toolLines.push(summary ? `- ${name}: ${summary}` : `- ${name}`);

View File

@@ -91,7 +91,7 @@ async function resizeImageBase64IfNeeded(params: {
const sideGrid = [sideStart, 1800, 1600, 1400, 1200, 1000, 800]
.map((v) => Math.min(params.maxDimensionPx, v))
.filter((v, i, arr) => v > 0 && arr.indexOf(v) === i)
.sort((a, b) => b - a);
.toSorted((a, b) => b - a);
let smallest: { buffer: Buffer; size: number } | null = null;
for (const side of sideGrid) {
@@ -191,7 +191,7 @@ export async function sanitizeImageBlocks(
): Promise<{ images: ImageContent[]; dropped: number }> {
if (images.length === 0) return { images, dropped: 0 };
const sanitized = await sanitizeContentBlocksImages(images as ToolContentBlock[], label, opts);
const next = sanitized.filter(isImageBlock) as ImageContent[];
const next = sanitized.filter(isImageBlock);
return { images: next, dropped: Math.max(0, images.length - next.length) };
}

View File

@@ -9,10 +9,10 @@ export async function readLatestAssistantReply(params: {
sessionKey: string;
limit?: number;
}): Promise<string | undefined> {
const history = (await callGateway({
const history = await callGateway({
method: "chat.history",
params: { sessionKey: params.sessionKey, limit: params.limit ?? 50 },
})) as { messages?: unknown[] };
});
const filtered = stripToolMessages(Array.isArray(history?.messages) ? history.messages : []);
const last = filtered.length > 0 ? filtered[filtered.length - 1] : undefined;
return last ? extractAssistantText(last) : undefined;
@@ -27,7 +27,7 @@ export async function runAgentStep(params: {
lane?: string;
}): Promise<string | undefined> {
const stepIdem = crypto.randomUUID();
const response = (await callGateway({
const response = await callGateway({
method: "agent",
params: {
message: params.message,
@@ -39,19 +39,19 @@ export async function runAgentStep(params: {
extraSystemPrompt: params.extraSystemPrompt,
},
timeoutMs: 10_000,
})) as { runId?: string; acceptedAt?: number };
});
const stepRunId = typeof response?.runId === "string" && response.runId ? response.runId : "";
const resolvedRunId = stepRunId || stepIdem;
const stepWaitMs = Math.min(params.timeoutMs, 60_000);
const wait = (await callGateway({
const wait = await callGateway({
method: "agent.wait",
params: {
runId: resolvedRunId,
timeoutMs: stepWaitMs,
},
timeoutMs: stepWaitMs + 2000,
})) as { status?: string };
});
if (wait?.status !== "ok") return undefined;
return await readLatestAssistantReply({ sessionKey: params.sessionKey });
}

View File

@@ -72,7 +72,9 @@ export function createAgentsListTool(opts?: {
}
const all = Array.from(allowed);
const rest = all.filter((id) => id !== requesterAgentId).sort((a, b) => a.localeCompare(b));
const rest = all
.filter((id) => id !== requesterAgentId)
.toSorted((a, b) => a.localeCompare(b));
const ordered = [requesterAgentId, ...rest];
const agents: AgentListEntry[] = ordered.map((id) => ({
id,

View File

@@ -93,7 +93,7 @@ async function resolveBrowserNodeTarget(params: {
if (params.target === "node") {
if (browserNodes.length === 1) {
const node = browserNodes[0]!;
const node = browserNodes[0];
return { nodeId: node.nodeId, label: node.displayName ?? node.remoteIp ?? node.nodeId };
}
throw new Error(
@@ -104,7 +104,7 @@ async function resolveBrowserNodeTarget(params: {
if (mode === "manual") return null;
if (browserNodes.length === 1) {
const node = browserNodes[0]!;
const node = browserNodes[0];
return { nodeId: node.nodeId, label: node.displayName ?? node.remoteIp ?? node.nodeId };
}
return null;
@@ -123,7 +123,7 @@ async function callBrowserProxy(params: {
typeof params.timeoutMs === "number" && Number.isFinite(params.timeoutMs)
? Math.max(1, Math.floor(params.timeoutMs))
: DEFAULT_BROWSER_PROXY_TIMEOUT_MS;
const payload = (await callGatewayTool(
const payload = await callGatewayTool(
"node.invoke",
{ timeoutMs: gatewayTimeoutMs },
{
@@ -139,11 +139,7 @@ async function callBrowserProxy(params: {
},
idempotencyKey: crypto.randomUUID(),
},
)) as {
ok?: boolean;
payload?: BrowserProxyResult;
payloadJSON?: string | null;
};
);
const parsed =
payload?.payload ??
(typeof payload?.payloadJSON === "string" && payload.payloadJSON
@@ -414,7 +410,7 @@ export function createBrowserTool(opts?: {
const snapshotDefaults = loadConfig().browser?.snapshotDefaults;
const format =
params.snapshotFormat === "ai" || params.snapshotFormat === "aria"
? (params.snapshotFormat as "ai" | "aria")
? params.snapshotFormat
: "ai";
const mode =
params.mode === "efficient"
@@ -697,10 +693,12 @@ export function createBrowserTool(opts?: {
if (!tabs.length) {
throw new Error(
"No Chrome tabs are attached via the OpenClaw Browser Relay extension. Click the toolbar icon on the tab you want to control (badge ON), then retry.",
{ cause: err },
);
}
throw new Error(
`Chrome tab not found (stale targetId?). Run action=tabs profile="chrome" and use one of the returned targetIds.`,
{ cause: err },
);
}
throw err;

View File

@@ -103,10 +103,10 @@ async function buildReminderContextLines(params: {
const { mainKey, alias } = resolveMainSessionAlias(cfg);
const resolvedKey = resolveInternalSessionKey({ key: sessionKey, alias, mainKey });
try {
const res = (await callGatewayTool("chat.history", params.gatewayOpts, {
const res = await callGatewayTool("chat.history", params.gatewayOpts, {
sessionKey: resolvedKey,
limit: maxMessages,
})) as { messages?: unknown[] };
});
const messages = Array.isArray(res?.messages) ? res.messages : [];
const parsed = messages
.map((msg) => extractMessageText(msg as ChatMessage))

View File

@@ -267,10 +267,10 @@ async function runImagePrompt(params: {
}
const context = buildImageContext(params.prompt, params.base64, params.mimeType);
const message = (await complete(model, context, {
const message = await complete(model, context, {
apiKey,
maxTokens: 512,
})) as AssistantMessage;
});
const text = coerceImageAssistantText({
message,
provider: model.provider,

View File

@@ -307,7 +307,7 @@ function buildMessageToolDescription(options?: {
if (channelActions.length > 0) {
// Always include "send" as a base action
const allActions = new Set(["send", ...channelActions]);
const actionList = Array.from(allActions).sort().join(", ");
const actionList = Array.from(allActions).toSorted().join(", ");
return `${baseDescription} Current channel (${options.currentChannel}) supports: ${actionList}.`;
}
}

View File

@@ -199,7 +199,7 @@ export function createNodesTool(options?: {
const details: Array<Record<string, unknown>> = [];
for (const facing of facings) {
const raw = (await callGatewayTool("node.invoke", gatewayOpts, {
const raw = await callGatewayTool("node.invoke", gatewayOpts, {
nodeId,
command: "camera.snap",
params: {
@@ -211,7 +211,7 @@ export function createNodesTool(options?: {
deviceId,
},
idempotencyKey: crypto.randomUUID(),
})) as { payload?: unknown };
});
const payload = parseCameraSnapPayload(raw?.payload);
const normalizedFormat = payload.format.toLowerCase();
if (
@@ -250,12 +250,12 @@ export function createNodesTool(options?: {
case "camera_list": {
const node = readStringParam(params, "node", { required: true });
const nodeId = await resolveNodeId(gatewayOpts, node);
const raw = (await callGatewayTool("node.invoke", gatewayOpts, {
const raw = await callGatewayTool("node.invoke", gatewayOpts, {
nodeId,
command: "camera.list",
params: {},
idempotencyKey: crypto.randomUUID(),
})) as { payload?: unknown };
});
const payload =
raw && typeof raw.payload === "object" && raw.payload !== null ? raw.payload : {};
return jsonResult(payload);
@@ -280,7 +280,7 @@ export function createNodesTool(options?: {
typeof params.deviceId === "string" && params.deviceId.trim()
? params.deviceId.trim()
: undefined;
const raw = (await callGatewayTool("node.invoke", gatewayOpts, {
const raw = await callGatewayTool("node.invoke", gatewayOpts, {
nodeId,
command: "camera.clip",
params: {
@@ -291,7 +291,7 @@ export function createNodesTool(options?: {
deviceId,
},
idempotencyKey: crypto.randomUUID(),
})) as { payload?: unknown };
});
const payload = parseCameraClipPayload(raw?.payload);
const filePath = cameraTempPath({
kind: "clip",
@@ -326,7 +326,7 @@ export function createNodesTool(options?: {
: 0;
const includeAudio =
typeof params.includeAudio === "boolean" ? params.includeAudio : true;
const raw = (await callGatewayTool("node.invoke", gatewayOpts, {
const raw = await callGatewayTool("node.invoke", gatewayOpts, {
nodeId,
command: "screen.record",
params: {
@@ -337,7 +337,7 @@ export function createNodesTool(options?: {
includeAudio,
},
idempotencyKey: crypto.randomUUID(),
})) as { payload?: unknown };
});
const payload = parseScreenRecordPayload(raw?.payload);
const filePath =
typeof params.outPath === "string" && params.outPath.trim()
@@ -373,7 +373,7 @@ export function createNodesTool(options?: {
Number.isFinite(params.locationTimeoutMs)
? params.locationTimeoutMs
: undefined;
const raw = (await callGatewayTool("node.invoke", gatewayOpts, {
const raw = await callGatewayTool("node.invoke", gatewayOpts, {
nodeId,
command: "location.get",
params: {
@@ -382,7 +382,7 @@ export function createNodesTool(options?: {
timeoutMs: locationTimeoutMs,
},
idempotencyKey: crypto.randomUUID(),
})) as { payload?: unknown };
});
return jsonResult(raw?.payload ?? {});
}
case "run": {
@@ -423,7 +423,7 @@ export function createNodesTool(options?: {
typeof params.needsScreenRecording === "boolean"
? params.needsScreenRecording
: undefined;
const raw = (await callGatewayTool("node.invoke", gatewayOpts, {
const raw = await callGatewayTool("node.invoke", gatewayOpts, {
nodeId,
command: "system.run",
params: {
@@ -437,7 +437,7 @@ export function createNodesTool(options?: {
},
timeoutMs: invokeTimeoutMs,
idempotencyKey: crypto.randomUUID(),
})) as { payload?: unknown };
});
return jsonResult(raw?.payload ?? {});
}
default:
@@ -454,6 +454,7 @@ export function createNodesTool(options?: {
const message = err instanceof Error ? err.message : String(err);
throw new Error(
`agent=${agentLabel} node=${nodeLabel} gateway=${gatewayLabel} action=${action}: ${message}`,
{ cause: err },
);
}
},

View File

@@ -71,10 +71,10 @@ function normalizeNodeKey(value: string) {
async function loadNodes(opts: GatewayCallOptions): Promise<NodeListNode[]> {
try {
const res = (await callGatewayTool("node.list", opts, {})) as unknown;
const res = await callGatewayTool("node.list", opts, {});
return parseNodeList(res);
} catch {
const res = (await callGatewayTool("node.pair.list", opts, {})) as unknown;
const res = await callGatewayTool("node.pair.list", opts, {});
const { paired } = parsePairingList(res);
return paired.map((n) => ({
nodeId: n.nodeId,

View File

@@ -20,14 +20,14 @@ export async function resolveAnnounceTarget(params: {
}
try {
const list = (await callGateway({
const list = await callGateway({
method: "sessions.list",
params: {
includeGlobal: true,
includeUnknown: true,
limit: 200,
},
})) as { sessions?: Array<Record<string, unknown>> };
});
const sessions = Array.isArray(list?.sessions) ? list.sessions : [];
const match =
sessions.find((entry) => entry?.key === params.sessionKey) ??

View File

@@ -135,7 +135,7 @@ async function resolveSessionKeyFromSessionId(params: {
}): Promise<SessionReferenceResolution> {
try {
// Resolve via gateway so we respect store routing and visibility rules.
const result = (await callGateway({
const result = await callGateway({
method: "sessions.resolve",
params: {
sessionId: params.sessionId,
@@ -143,7 +143,7 @@ async function resolveSessionKeyFromSessionId(params: {
includeGlobal: !params.restrictToSpawned,
includeUnknown: !params.restrictToSpawned,
},
})) as { key?: unknown };
});
const key = typeof result?.key === "string" ? result.key.trim() : "";
if (!key) {
throw new Error(
@@ -188,13 +188,13 @@ async function resolveSessionKeyFromKey(params: {
}): Promise<SessionReferenceResolution | null> {
try {
// Try key-based resolution first so non-standard keys keep working.
const result = (await callGateway({
const result = await callGateway({
method: "sessions.resolve",
params: {
key: params.key,
spawnedBy: params.restrictToSpawned ? params.requesterInternalKey : undefined,
},
})) as { key?: unknown };
});
const key = typeof result?.key === "string" ? result.key.trim() : "";
if (!key) return null;
return {

View File

@@ -28,7 +28,7 @@ async function isSpawnedSessionAllowed(params: {
targetSessionKey: string;
}): Promise<boolean> {
try {
const list = (await callGateway({
const list = await callGateway({
method: "sessions.list",
params: {
includeGlobal: false,
@@ -36,7 +36,7 @@ async function isSpawnedSessionAllowed(params: {
limit: 500,
spawnedBy: params.requesterSessionKey,
},
})) as { sessions?: Array<Record<string, unknown>> };
});
const sessions = Array.isArray(list?.sessions) ? list.sessions : [];
return sessions.some((entry) => entry?.key === params.targetSessionKey);
} catch {
@@ -126,10 +126,10 @@ export function createSessionsHistoryTool(opts?: {
? Math.max(1, Math.floor(params.limit))
: undefined;
const includeTools = Boolean(params.includeTools);
const result = (await callGateway({
const result = await callGateway({
method: "chat.history",
params: { sessionKey: resolvedKey, limit },
})) as { messages?: unknown[] };
});
const rawMessages = Array.isArray(result?.messages) ? result.messages : [];
const messages = includeTools ? rawMessages : stripToolMessages(rawMessages);
return jsonResult({

View File

@@ -79,7 +79,7 @@ export function createSessionsListTool(opts?: {
: 0;
const messageLimit = Math.min(messageLimitRaw, 20);
const list = (await callGateway({
const list = await callGateway({
method: "sessions.list",
params: {
limit,
@@ -88,10 +88,7 @@ export function createSessionsListTool(opts?: {
includeUnknown: !restrictToSpawned,
spawnedBy: restrictToSpawned ? requesterInternalKey : undefined,
},
})) as {
path?: string;
sessions?: Array<Record<string, unknown>>;
};
});
const sessions = Array.isArray(list?.sessions) ? list.sessions : [];
const storePath = typeof list?.path === "string" ? list.path : undefined;
@@ -187,10 +184,10 @@ export function createSessionsListTool(opts?: {
alias,
mainKey,
});
const history = (await callGateway({
const history = await callGateway({
method: "chat.history",
params: { sessionKey: resolvedKey, limit: messageLimit },
})) as { messages?: unknown[] };
});
const rawMessages = Array.isArray(history?.messages) ? history.messages : [];
const filtered = stripToolMessages(rawMessages);
row.messages = filtered.length > messageLimit ? filtered.slice(-messageLimit) : filtered;

View File

@@ -33,14 +33,14 @@ export async function runSessionsSendA2AFlow(params: {
let latestReply = params.roundOneReply;
if (!primaryReply && params.waitRunId) {
const waitMs = Math.min(params.announceTimeoutMs, 60_000);
const wait = (await callGateway({
const wait = await callGateway({
method: "agent.wait",
params: {
runId: params.waitRunId,
timeoutMs: waitMs,
},
timeoutMs: waitMs + 2000,
})) as { status?: string };
});
if (wait?.status === "ok") {
primaryReply = await readLatestAssistantReply({
sessionKey: params.targetSessionKey,

View File

@@ -81,11 +81,11 @@ export function createSessionsSendTool(opts?: {
}
const listSessions = async (listParams: Record<string, unknown>) => {
const result = (await callGateway({
const result = await callGateway({
method: "sessions.list",
params: listParams,
timeoutMs: 10_000,
})) as { sessions?: Array<Record<string, unknown>> };
});
return Array.isArray(result?.sessions) ? result.sessions : [];
};
@@ -136,11 +136,11 @@ export function createSessionsSendTool(opts?: {
};
let resolvedKey = "";
try {
const resolved = (await callGateway({
const resolved = await callGateway({
method: "sessions.resolve",
params: resolveParams,
timeoutMs: 10_000,
})) as { key?: unknown };
});
resolvedKey = typeof resolved?.key === "string" ? resolved.key.trim() : "";
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
@@ -283,11 +283,11 @@ export function createSessionsSendTool(opts?: {
if (timeoutSeconds === 0) {
try {
const response = (await callGateway({
const response = await callGateway({
method: "agent",
params: sendParams,
timeoutMs: 10_000,
})) as { runId?: string; acceptedAt?: number };
});
if (typeof response?.runId === "string" && response.runId) {
runId = response.runId;
}
@@ -311,11 +311,11 @@ export function createSessionsSendTool(opts?: {
}
try {
const response = (await callGateway({
const response = await callGateway({
method: "agent",
params: sendParams,
timeoutMs: 10_000,
})) as { runId?: string; acceptedAt?: number };
});
if (typeof response?.runId === "string" && response.runId) {
runId = response.runId;
}
@@ -333,14 +333,14 @@ export function createSessionsSendTool(opts?: {
let waitStatus: string | undefined;
let waitError: string | undefined;
try {
const wait = (await callGateway({
const wait = await callGateway({
method: "agent.wait",
params: {
runId,
timeoutMs,
},
timeoutMs: timeoutMs + 2000,
})) as { status?: string; error?: string };
});
waitStatus = typeof wait?.status === "string" ? wait.status : undefined;
waitError = typeof wait?.error === "string" ? wait.error : undefined;
} catch (err) {
@@ -371,10 +371,10 @@ export function createSessionsSendTool(opts?: {
});
}
const history = (await callGateway({
const history = await callGateway({
method: "chat.history",
params: { sessionKey: resolvedKey, limit: 50 },
})) as { messages?: unknown[] };
});
const filtered = stripToolMessages(Array.isArray(history?.messages) ? history.messages : []);
const last = filtered.length > 0 ? filtered[filtered.length - 1] : undefined;
const reply = last ? extractAssistantText(last) : undefined;

View File

@@ -84,9 +84,7 @@ export function createSessionsSpawnTool(opts?: {
const modelOverride = readStringParam(params, "model");
const thinkingOverrideRaw = readStringParam(params, "thinking");
const cleanup =
params.cleanup === "keep" || params.cleanup === "delete"
? (params.cleanup as "keep" | "delete")
: "keep";
params.cleanup === "keep" || params.cleanup === "delete" ? params.cleanup : "keep";
const requesterOrigin = normalizeDeliveryContext({
channel: opts?.agentChannel,
accountId: opts?.agentAccountId,
@@ -211,7 +209,7 @@ export function createSessionsSpawnTool(opts?: {
const childIdem = crypto.randomUUID();
let childRunId: string = childIdem;
try {
const response = (await callGateway({
const response = await callGateway({
method: "agent",
params: {
message: task,
@@ -230,7 +228,7 @@ export function createSessionsSpawnTool(opts?: {
groupSpace: opts?.agentGroupSpace ?? undefined,
},
timeoutMs: 10_000,
})) as { runId?: string };
});
if (typeof response?.runId === "string" && response.runId) {
childRunId = response.runId;
}

View File

@@ -115,7 +115,7 @@ function formatZonedTimestamp(date: Date, timeZone?: string): string | undefined
const hh = pick("hour");
const min = pick("minute");
const tz = [...parts]
.reverse()
.toReversed()
.find((part) => part.type === "timeZoneName")
?.value?.trim();
if (!yyyy || !mm || !dd || !hh || !min) return undefined;

View File

@@ -38,7 +38,7 @@ function formatListTop(
entries: Array<{ name: string; value: number }>,
cap: number,
): { lines: string[]; omitted: number } {
const sorted = [...entries].sort((a, b) => b.value - a.value);
const sorted = [...entries].toSorted((a, b) => b.value - a.value);
const top = sorted.slice(0, cap);
const omitted = Math.max(0, sorted.length - top.length);
const lines = top.map((e) => `- ${e.name}: ${formatCharsAndTokens(e.value)}`);
@@ -263,7 +263,7 @@ export async function buildContextReply(params: HandleCommandsParams): Promise<R
);
const toolPropsLines = report.tools.entries
.filter((t) => t.propertiesCount != null)
.sort((a, b) => (b.propertiesCount ?? 0) - (a.propertiesCount ?? 0))
.toSorted((a, b) => (b.propertiesCount ?? 0) - (a.propertiesCount ?? 0))
.slice(0, 30)
.map((t) => `- ${t.name}: ${t.propertiesCount} params`);

View File

@@ -154,7 +154,7 @@ export async function resolveModelsCommandReply(params: {
add(resolvedDefault.provider, resolvedDefault.model);
addModelConfigEntries();
const providers = [...byProvider.keys()].sort();
const providers = [...byProvider.keys()].toSorted();
if (!provider) {
const lines: string[] = [
@@ -181,7 +181,7 @@ export async function resolveModelsCommandReply(params: {
return { text: lines.join("\n") };
}
const models = [...(byProvider.get(provider) ?? new Set<string>())].sort();
const models = [...(byProvider.get(provider) ?? new Set<string>())].toSorted();
const total = models.length;
if (total === 0) {

View File

@@ -289,7 +289,7 @@ export const handleStopCommand: CommandHandler = async (params, allowTextCommand
params.sessionStore[abortTarget.key] = abortTarget.entry;
if (params.storePath) {
await updateSessionStore(params.storePath, (store) => {
store[abortTarget.key] = abortTarget.entry as SessionEntry;
store[abortTarget.key] = abortTarget.entry;
});
}
} else if (params.command.abortKey) {
@@ -336,7 +336,7 @@ export const handleAbortTrigger: CommandHandler = async (params, allowTextComman
params.sessionStore[abortTarget.key] = abortTarget.entry;
if (params.storePath) {
await updateSessionStore(params.storePath, (store) => {
store[abortTarget.key] = abortTarget.entry as SessionEntry;
store[abortTarget.key] = abortTarget.entry;
});
}
} else if (params.command.abortKey) {

View File

@@ -316,10 +316,10 @@ export const handleSubagentsCommand: CommandHandler = async (params, allowTextCo
reply: { text: `⚠️ ${resolved.error ?? "Unknown subagent."}` },
};
}
const history = (await callGateway({
const history = await callGateway({
method: "chat.history",
params: { sessionKey: resolved.entry.childSessionKey, limit },
})) as { messages?: unknown[] };
});
const rawMessages = Array.isArray(history?.messages) ? history.messages : [];
const filtered = includeTools ? rawMessages : stripToolMessages(rawMessages);
const lines = formatLogLines(filtered as ChatMessage[]);
@@ -349,7 +349,7 @@ export const handleSubagentsCommand: CommandHandler = async (params, allowTextCo
const idempotencyKey = crypto.randomUUID();
let runId: string = idempotencyKey;
try {
const response = (await callGateway({
const response = await callGateway({
method: "agent",
params: {
message,
@@ -360,7 +360,7 @@ export const handleSubagentsCommand: CommandHandler = async (params, allowTextCo
lane: AGENT_LANE_SUBAGENT,
},
timeoutMs: 10_000,
})) as { runId?: string };
});
if (response?.runId) runId = response.runId;
} catch (err) {
const messageText =
@@ -369,11 +369,11 @@ export const handleSubagentsCommand: CommandHandler = async (params, allowTextCo
}
const waitMs = 30_000;
const wait = (await callGateway({
const wait = await callGateway({
method: "agent.wait",
params: { runId, timeoutMs: waitMs },
timeoutMs: waitMs + 2000,
})) as { status?: string; error?: string };
});
if (wait?.status === "timeout") {
return {
shouldContinue: false,
@@ -389,10 +389,10 @@ export const handleSubagentsCommand: CommandHandler = async (params, allowTextCo
};
}
const history = (await callGateway({
const history = await callGateway({
method: "chat.history",
params: { sessionKey: resolved.entry.childSessionKey, limit: 50 },
})) as { messages?: unknown[] };
});
const filtered = stripToolMessages(Array.isArray(history?.messages) ? history.messages : []);
const last = filtered.length > 0 ? filtered[filtered.length - 1] : undefined;
const replyText = last ? extractAssistantText(last) : undefined;

View File

@@ -54,8 +54,7 @@ function resolveExecDefaults(params: {
(agentExec?.ask as ExecAsk | undefined) ??
(globalExec?.ask as ExecAsk | undefined) ??
"on-miss",
node:
(params.sessionEntry?.execNode as string | undefined) ?? agentExec?.node ?? globalExec?.node,
node: params.sessionEntry?.execNode ?? agentExec?.node ?? globalExec?.node,
};
}

View File

@@ -196,8 +196,8 @@ export async function applyInlineDirectiveOverrides(params: {
model,
contextTokens,
resolvedThinkLevel: resolvedDefaultThinkLevel,
resolvedVerboseLevel: (currentVerboseLevel ?? "off") as VerboseLevel,
resolvedReasoningLevel: (currentReasoningLevel ?? "off") as ReasoningLevel,
resolvedVerboseLevel: currentVerboseLevel ?? "off",
resolvedReasoningLevel: currentReasoningLevel ?? "off",
resolvedElevatedLevel,
resolveDefaultThinkingLevel: async () => resolvedDefaultThinkLevel,
isGroup,

View File

@@ -339,20 +339,20 @@ export async function resolveReplyDirectives(params: {
});
const defaultActivation = defaultGroupActivation(requireMention);
const resolvedThinkLevel =
(directives.thinkLevel as ThinkLevel | undefined) ??
directives.thinkLevel ??
(sessionEntry?.thinkingLevel as ThinkLevel | undefined) ??
(agentCfg?.thinkingDefault as ThinkLevel | undefined);
const resolvedVerboseLevel =
(directives.verboseLevel as VerboseLevel | undefined) ??
directives.verboseLevel ??
(sessionEntry?.verboseLevel as VerboseLevel | undefined) ??
(agentCfg?.verboseDefault as VerboseLevel | undefined);
const resolvedReasoningLevel: ReasoningLevel =
(directives.reasoningLevel as ReasoningLevel | undefined) ??
directives.reasoningLevel ??
(sessionEntry?.reasoningLevel as ReasoningLevel | undefined) ??
"off";
const resolvedElevatedLevel = elevatedAllowed
? ((directives.elevatedLevel as ElevatedLevel | undefined) ??
? (directives.elevatedLevel ??
(sessionEntry?.elevatedLevel as ElevatedLevel | undefined) ??
(agentCfg?.elevatedDefault as ElevatedLevel | undefined) ??
"on")

View File

@@ -50,7 +50,7 @@ export function resolveGroupRequireMention(params: {
}
export function defaultGroupActivation(requireMention: boolean): "always" | "mention" {
return requireMention === false ? "always" : "mention";
return !requireMention ? "always" : "mention";
}
export function buildGroupIntro(params: {

View File

@@ -436,7 +436,7 @@ export function resolveModelDirectiveSelection(params: {
});
return Object.assign({ candidate }, details);
})
.sort((a, b) => {
.toSorted((a, b) => {
if (b.score !== a.score) return b.score - a.score;
if (a.isDefault !== b.isDefault) return a.isDefault ? -1 : 1;
if (a.variantMatchCount !== b.variantMatchCount)

View File

@@ -87,7 +87,7 @@ export async function prependSystemEvents(params: {
const min = pick("minute");
const sec = pick("second");
const tz = [...parts]
.reverse()
.toReversed()
.find((part) => part.type === "timeZoneName")
?.value?.trim();
if (!yyyy || !mm || !dd || !hh || !min || !sec) return undefined;

View File

@@ -235,10 +235,9 @@ export async function initSessionState(params: {
const baseEntry = !isNewSession && freshEntry ? entry : undefined;
// Track the originating channel/to for announce routing (subagent announce-back).
const lastChannelRaw = (ctx.OriginatingChannel as string | undefined) || baseEntry?.lastChannel;
const lastToRaw = (ctx.OriginatingTo as string | undefined) || ctx.To || baseEntry?.lastTo;
const lastAccountIdRaw = (ctx.AccountId as string | undefined) || baseEntry?.lastAccountId;
const lastThreadIdRaw =
(ctx.MessageThreadId as string | number | undefined) || baseEntry?.lastThreadId;
const lastToRaw = ctx.OriginatingTo || ctx.To || baseEntry?.lastTo;
const lastAccountIdRaw = ctx.AccountId || baseEntry?.lastAccountId;
const lastThreadIdRaw = ctx.MessageThreadId || baseEntry?.lastThreadId;
const deliveryFields = normalizeSessionDeliveryFields({
deliveryContext: {
channel: lastChannelRaw,

View File

@@ -42,7 +42,7 @@ export function formatRunStatus(entry: SubagentRunRecord) {
}
export function sortSubagentRuns(runs: SubagentRunRecord[]) {
return [...runs].sort((a, b) => {
return [...runs].toSorted((a, b) => {
const aTime = a.startedAt ?? a.createdAt ?? 0;
const bTime = b.startedAt ?? b.createdAt ?? 0;
return bTime - aTime;

View File

@@ -296,7 +296,7 @@ export async function snapshotDom(opts: {
awaitPromise: true,
returnByValue: true,
});
const value = evaluated.result?.value as unknown;
const value = evaluated.result?.value;
if (!value || typeof value !== "object") return { nodes: [] };
const nodes = (value as { nodes?: unknown }).nodes;
return { nodes: Array.isArray(nodes) ? (nodes as DomSnapshotNode[]) : [] };

View File

@@ -458,7 +458,7 @@ export async function ensureChromeExtensionRelayServer(opts: {
if ("error" in parsed && typeof parsed.error === "string" && parsed.error.trim()) {
pending.reject(new Error(parsed.error));
} else {
pending.resolve((parsed as ExtensionResponseMessage).result);
pending.resolve(parsed.result);
}
return;
}

View File

@@ -183,18 +183,18 @@ describe("color allocation", () => {
it("allocates next unused color from palette", () => {
// biome-ignore lint/style/noNonNullAssertion: Test file with known array
const usedColors = new Set([PROFILE_COLORS[0]!.toUpperCase()]);
const usedColors = new Set([PROFILE_COLORS[0].toUpperCase()]);
expect(allocateColor(usedColors)).toBe(PROFILE_COLORS[1]);
});
it("skips multiple used colors", () => {
const usedColors = new Set([
// biome-ignore lint/style/noNonNullAssertion: Test file with known array
PROFILE_COLORS[0]!.toUpperCase(),
PROFILE_COLORS[0].toUpperCase(),
// biome-ignore lint/style/noNonNullAssertion: Test file with known array
PROFILE_COLORS[1]!.toUpperCase(),
PROFILE_COLORS[1].toUpperCase(),
// biome-ignore lint/style/noNonNullAssertion: Test file with known array
PROFILE_COLORS[2]!.toUpperCase(),
PROFILE_COLORS[2].toUpperCase(),
]);
expect(allocateColor(usedColors)).toBe(PROFILE_COLORS[3]);
});

View File

@@ -91,7 +91,7 @@ export function allocateColor(usedColors: Set<string>): string {
// All colors used, cycle based on count
const index = usedColors.size % PROFILE_COLORS.length;
// biome-ignore lint/style/noNonNullAssertion: Array is non-empty constant
return PROFILE_COLORS[index] ?? PROFILE_COLORS[0]!;
return PROFILE_COLORS[index] ?? PROFILE_COLORS[0];
}
export function getUsedColors(

View File

@@ -84,7 +84,7 @@ describe("pw-role-snapshot", () => {
expect(res.snapshot).toContain('- button "Save"');
expect(res.snapshot).not.toContain("navigation");
expect(res.snapshot).not.toContain("heading");
expect(Object.keys(res.refs).sort()).toEqual(["e5", "e7"]);
expect(Object.keys(res.refs).toSorted()).toEqual(["e5", "e7"]);
expect(res.refs.e5).toMatchObject({ role: "link", name: "Home" });
expect(res.refs.e7).toMatchObject({ role: "button", name: "Save" });
});

View File

@@ -55,13 +55,13 @@ describe("pw-tools-core", () => {
() =>
new Promise((r) => {
resolve1 = r;
}) as Promise<unknown>,
}),
)
.mockImplementationOnce(
() =>
new Promise((r) => {
resolve2 = r;
}) as Promise<unknown>,
}),
);
currentPage = {

View File

@@ -93,7 +93,7 @@ export async function responseBodyViaPlaywright(opts: {
bodyText = new TextDecoder("utf-8").decode(buf);
}
} catch (err) {
throw new Error(`Failed to read response body for "${url}": ${String(err)}`);
throw new Error(`Failed to read response body for "${url}": ${String(err)}`, { cause: err });
}
const trimmed = bodyText.length > maxChars ? bodyText.slice(0, maxChars) : bodyText;

View File

@@ -200,6 +200,6 @@ export async function pdfViaPlaywright(opts: {
}): Promise<{ buffer: Buffer }> {
const page = await getPageForTargetId(opts);
ensurePageState(page);
const buffer = await (page as Page).pdf({ printBackground: true });
const buffer = await page.pdf({ printBackground: true });
return { buffer };
}

View File

@@ -136,7 +136,8 @@ export async function setTimezoneViaPlaywright(opts: {
} catch (err) {
const msg = String(err);
if (msg.includes("Timezone override is already in effect")) return;
if (msg.includes("Invalid timezone")) throw new Error(`Invalid timezone ID: ${timezoneId}`);
if (msg.includes("Invalid timezone"))
throw new Error(`Invalid timezone ID: ${timezoneId}`, { cause: err });
throw err;
}
});

View File

@@ -207,7 +207,7 @@ export function registerBrowserAgentActRoutes(
loadStateRaw === "load" ||
loadStateRaw === "domcontentloaded" ||
loadStateRaw === "networkidle"
? (loadStateRaw as "load" | "domcontentloaded" | "networkidle")
? loadStateRaw
: undefined;
const fn = toStringOrEmpty(body.fn) || undefined;
const timeoutMs = toNumber(body.timeoutMs) ?? undefined;

View File

@@ -53,7 +53,7 @@ export function registerBrowserAgentStorageRoutes(
secure: toBoolean(cookie.secure) ?? undefined,
sameSite:
cookie.sameSite === "Lax" || cookie.sameSite === "None" || cookie.sameSite === "Strict"
? (cookie.sameSite as "Lax" | "None" | "Strict")
? cookie.sameSite
: undefined,
},
});
@@ -270,7 +270,7 @@ export function registerBrowserAgentStorageRoutes(
const schemeRaw = toStringOrEmpty(body.colorScheme);
const colorScheme =
schemeRaw === "dark" || schemeRaw === "light" || schemeRaw === "no-preference"
? (schemeRaw as "dark" | "light" | "no-preference")
? schemeRaw
: schemeRaw === "none"
? null
: undefined;

View File

@@ -27,7 +27,7 @@ export async function normalizeBrowserScreenshot(
const sideGrid = [sideStart, 1800, 1600, 1400, 1200, 1000, 800]
.map((v) => Math.min(maxSide, v))
.filter((v, i, arr) => v > 0 && arr.indexOf(v) === i)
.sort((a, b) => b - a);
.toSorted((a, b) => b - a);
let smallest: { buffer: Buffer; size: number } | null = null;

View File

@@ -286,11 +286,11 @@ describe("browser control server", () => {
async () => {
const base = await startServerAndBase();
const select = (await postJson(`${base}/act`, {
const select = await postJson(`${base}/act`, {
kind: "select",
ref: "5",
values: ["a", "b"],
})) as { ok: boolean };
});
expect(select.ok).toBe(true);
expect(pwMocks.selectOptionViaPlaywright).toHaveBeenCalledWith({
cdpUrl: cdpBaseUrl,
@@ -299,10 +299,10 @@ describe("browser control server", () => {
values: ["a", "b"],
});
const fill = (await postJson(`${base}/act`, {
const fill = await postJson(`${base}/act`, {
kind: "fill",
fields: [{ ref: "6", type: "textbox", value: "hello" }],
})) as { ok: boolean };
});
expect(fill.ok).toBe(true);
expect(pwMocks.fillFormViaPlaywright).toHaveBeenCalledWith({
cdpUrl: cdpBaseUrl,
@@ -310,11 +310,11 @@ describe("browser control server", () => {
fields: [{ ref: "6", type: "textbox", value: "hello" }],
});
const resize = (await postJson(`${base}/act`, {
const resize = await postJson(`${base}/act`, {
kind: "resize",
width: 800,
height: 600,
})) as { ok: boolean };
});
expect(resize.ok).toBe(true);
expect(pwMocks.resizeViewportViaPlaywright).toHaveBeenCalledWith({
cdpUrl: cdpBaseUrl,
@@ -323,10 +323,10 @@ describe("browser control server", () => {
height: 600,
});
const wait = (await postJson(`${base}/act`, {
const wait = await postJson(`${base}/act`, {
kind: "wait",
timeMs: 5,
})) as { ok: boolean };
});
expect(wait.ok).toBe(true);
expect(pwMocks.waitForViaPlaywright).toHaveBeenCalledWith({
cdpUrl: cdpBaseUrl,
@@ -336,10 +336,10 @@ describe("browser control server", () => {
textGone: undefined,
});
const evalRes = (await postJson(`${base}/act`, {
const evalRes = await postJson(`${base}/act`, {
kind: "evaluate",
fn: "() => 1",
})) as { ok: boolean; result?: unknown };
});
expect(evalRes.ok).toBe(true);
expect(evalRes.result).toBe("ok");
expect(pwMocks.evaluateViaPlaywright).toHaveBeenCalledWith({
@@ -358,17 +358,17 @@ describe("browser control server", () => {
cfgEvaluateEnabled = false;
const base = await startServerAndBase();
const waitRes = (await postJson(`${base}/act`, {
const waitRes = await postJson(`${base}/act`, {
kind: "wait",
fn: "() => window.ready === true",
})) as { error?: string };
});
expect(waitRes.error).toContain("browser.evaluateEnabled=false");
expect(pwMocks.waitForViaPlaywright).not.toHaveBeenCalled();
const res = (await postJson(`${base}/act`, {
const res = await postJson(`${base}/act`, {
kind: "evaluate",
fn: "() => 1",
})) as { error?: string };
});
expect(res.error).toContain("browser.evaluateEnabled=false");
expect(pwMocks.evaluateViaPlaywright).not.toHaveBeenCalled();
@@ -441,17 +441,14 @@ describe("browser control server", () => {
expect(consoleRes.ok).toBe(true);
expect(Array.isArray(consoleRes.messages)).toBe(true);
const pdf = (await postJson(`${base}/pdf`, {})) as {
ok: boolean;
path?: string;
};
const pdf = await postJson(`${base}/pdf`, {});
expect(pdf.ok).toBe(true);
expect(typeof pdf.path).toBe("string");
const shot = (await postJson(`${base}/screenshot`, {
const shot = await postJson(`${base}/screenshot`, {
element: "body",
type: "jpeg",
})) as { ok: boolean; path?: string };
});
expect(shot.ok).toBe(true);
expect(typeof shot.path).toBe("string");
});

View File

@@ -306,9 +306,9 @@ describe("browser control server", () => {
it("agent contract: navigation + common act commands", async () => {
const base = await startServerAndBase();
const nav = (await postJson(`${base}/navigate`, {
const nav = await postJson(`${base}/navigate`, {
url: "https://example.com",
})) as { ok: boolean; targetId?: string };
});
expect(nav.ok).toBe(true);
expect(typeof nav.targetId).toBe("string");
expect(pwMocks.navigateViaPlaywright).toHaveBeenCalledWith({
@@ -317,12 +317,12 @@ describe("browser control server", () => {
url: "https://example.com",
});
const click = (await postJson(`${base}/act`, {
const click = await postJson(`${base}/act`, {
kind: "click",
ref: "1",
button: "left",
modifiers: ["Shift"],
})) as { ok: boolean };
});
expect(click.ok).toBe(true);
expect(pwMocks.clickViaPlaywright).toHaveBeenNthCalledWith(1, {
cdpUrl: cdpBaseUrl,
@@ -343,11 +343,11 @@ describe("browser control server", () => {
/'selector' is not supported/i,
);
const type = (await postJson(`${base}/act`, {
const type = await postJson(`${base}/act`, {
kind: "type",
ref: "1",
text: "",
})) as { ok: boolean };
});
expect(type.ok).toBe(true);
expect(pwMocks.typeViaPlaywright).toHaveBeenNthCalledWith(1, {
cdpUrl: cdpBaseUrl,
@@ -358,10 +358,10 @@ describe("browser control server", () => {
slowly: false,
});
const press = (await postJson(`${base}/act`, {
const press = await postJson(`${base}/act`, {
kind: "press",
key: "Enter",
})) as { ok: boolean };
});
expect(press.ok).toBe(true);
expect(pwMocks.pressKeyViaPlaywright).toHaveBeenCalledWith({
cdpUrl: cdpBaseUrl,
@@ -369,10 +369,10 @@ describe("browser control server", () => {
key: "Enter",
});
const hover = (await postJson(`${base}/act`, {
const hover = await postJson(`${base}/act`, {
kind: "hover",
ref: "2",
})) as { ok: boolean };
});
expect(hover.ok).toBe(true);
expect(pwMocks.hoverViaPlaywright).toHaveBeenCalledWith({
cdpUrl: cdpBaseUrl,
@@ -380,10 +380,10 @@ describe("browser control server", () => {
ref: "2",
});
const scroll = (await postJson(`${base}/act`, {
const scroll = await postJson(`${base}/act`, {
kind: "scrollIntoView",
ref: "2",
})) as { ok: boolean };
});
expect(scroll.ok).toBe(true);
expect(pwMocks.scrollIntoViewViaPlaywright).toHaveBeenCalledWith({
cdpUrl: cdpBaseUrl,
@@ -391,11 +391,11 @@ describe("browser control server", () => {
ref: "2",
});
const drag = (await postJson(`${base}/act`, {
const drag = await postJson(`${base}/act`, {
kind: "drag",
startRef: "3",
endRef: "4",
})) as { ok: boolean };
});
expect(drag.ok).toBe(true);
expect(pwMocks.dragViaPlaywright).toHaveBeenCalledWith({
cdpUrl: cdpBaseUrl,

View File

@@ -347,7 +347,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
const deleteMessageDays = readNumberParam(actionParams, "deleteDays", {
integer: true,
});
const discordAction = action as "timeout" | "kick" | "ban";
const discordAction = action;
return await handleDiscordAction(
{
action: discordAction,

View File

@@ -268,7 +268,7 @@ export function listChannelPluginCatalogEntries(
return Array.from(resolved.values())
.map(({ entry }) => entry)
.sort((a, b) => {
.toSorted((a, b) => {
const orderA = a.meta.order ?? 999;
const orderB = b.meta.order ?? 999;
if (orderA !== orderB) return orderA - orderB;

View File

@@ -30,7 +30,7 @@ export function setAccountEnabledInConfigSection(params: {
} as OpenClawConfig;
}
const baseAccounts = (base?.accounts ?? {}) as Record<string, Record<string, unknown>>;
const baseAccounts = base?.accounts ?? {};
const existing = baseAccounts[accountKey] ?? {};
return {
...params.cfg,

View File

@@ -30,7 +30,7 @@ describe("directory (config-backed)", () => {
query: null,
limit: null,
});
expect(peers?.map((e) => e.id).sort()).toEqual([
expect(peers?.map((e) => e.id).toSorted()).toEqual([
"user:u123",
"user:u234",
"user:u777",
@@ -73,7 +73,7 @@ describe("directory (config-backed)", () => {
query: null,
limit: null,
});
expect(peers?.map((e) => e.id).sort()).toEqual(["user:111", "user:12345", "user:222"]);
expect(peers?.map((e) => e.id).toSorted()).toEqual(["user:111", "user:12345", "user:222"]);
const groups = await listDiscordDirectoryGroupsFromConfig({
cfg,
@@ -81,7 +81,7 @@ describe("directory (config-backed)", () => {
query: null,
limit: null,
});
expect(groups?.map((e) => e.id).sort()).toEqual(["channel:555", "channel:666"]);
expect(groups?.map((e) => e.id).toSorted()).toEqual(["channel:555", "channel:666"]);
});
it("lists Telegram peers/groups from config", async () => {
@@ -102,7 +102,7 @@ describe("directory (config-backed)", () => {
query: null,
limit: null,
});
expect(peers?.map((e) => e.id).sort()).toEqual(["123", "456", "@alice", "@bob"]);
expect(peers?.map((e) => e.id).toSorted()).toEqual(["123", "456", "@alice", "@bob"]);
const groups = await listTelegramDirectoryGroupsFromConfig({
cfg,

View File

@@ -28,7 +28,7 @@ function dedupeChannels(channels: ChannelPlugin[]): ChannelPlugin[] {
export function listChannelPlugins(): ChannelPlugin[] {
const combined = dedupeChannels(listPluginChannels());
return combined.sort((a, b) => {
return combined.toSorted((a, b) => {
const indexA = CHAT_CHANNEL_ORDER.indexOf(a.id as ChatChannelId);
const indexB = CHAT_CHANNEL_ORDER.indexOf(b.id as ChatChannelId);
const orderA = a.meta.order ?? (indexA === -1 ? 999 : indexA);

View File

@@ -33,11 +33,11 @@ export async function promptChannelAccessPolicy(params: {
options.push({ value: "disabled", label: "Disabled (block all channels)" });
}
const initialValue = params.currentPolicy ?? "allowlist";
return (await params.prompter.select({
return await params.prompter.select({
message: `${params.label} access`,
options,
initialValue,
})) as ChannelAccessPolicy;
});
}
export async function promptChannelAllowlist(params: {

View File

@@ -4,7 +4,7 @@ import type { PromptAccountId, PromptAccountIdParams } from "../onboarding-types
export const promptAccountId: PromptAccountId = async (params: PromptAccountIdParams) => {
const existingIds = params.listAccountIds(params.cfg);
const initial = params.currentId?.trim() || params.defaultAccountId || DEFAULT_ACCOUNT_ID;
const choice = (await params.prompter.select({
const choice = await params.prompter.select({
message: `${params.label} account`,
options: [
...existingIds.map((id) => ({
@@ -14,7 +14,7 @@ export const promptAccountId: PromptAccountId = async (params: PromptAccountIdPa
{ value: "__new__", label: "Add a new account" },
],
initialValue: initial,
})) as string;
});
if (choice !== "__new__") return normalizeAccountId(choice);

View File

@@ -107,13 +107,13 @@ async function promptWhatsAppAllowFrom(
"WhatsApp DM access",
);
const phoneMode = (await prompter.select({
const phoneMode = await prompter.select({
message: "WhatsApp phone setup",
options: [
{ value: "personal", label: "This is my personal phone number" },
{ value: "separate", label: "Separate phone just for OpenClaw" },
],
})) as "personal" | "separate";
});
if (phoneMode === "personal") {
await prompter.note(
@@ -187,13 +187,13 @@ async function promptWhatsAppAllowFrom(
{ value: "list", label: "Set allowFrom to specific numbers" },
] as const);
const mode = (await prompter.select({
const mode = await prompter.select({
message: "WhatsApp allowFrom (optional pre-allowlist)",
options: allowOptions.map((opt) => ({
value: opt.value,
label: opt.label,
})),
})) as (typeof allowOptions)[number]["value"];
});
if (mode === "keep") {
// Keep allowFrom as-is.

View File

@@ -27,7 +27,7 @@ function getSessionRecipients(cfg: OpenClawConfig) {
updatedAt: entry?.updatedAt ?? 0,
}))
.filter(({ to }) => to.length > 1)
.sort((a, b) => b.updatedAt - a.updatedAt);
.toSorted((a, b) => b.updatedAt - a.updatedAt);
// Dedupe while preserving recency ordering.
const seen = new Set<string>();

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