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
No known key found for this signature in database
GPG key ID: C29F94A3201118AF
292 changed files with 643 additions and 699 deletions

View file

@ -7,13 +7,24 @@
], ],
"categories": { "categories": {
"correctness": "error", "correctness": "error",
"perf": "error" "perf": "error",
"suspicious": "error",
}, },
"rules": { "rules": {
"eslint-plugin-unicorn/prefer-array-find": "off", "eslint-plugin-unicorn/prefer-array-find": "off",
"eslint/no-await-in-loop": "off", "eslint/no-await-in-loop": "off",
"oxc/no-accumulating-spread": "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"] "ignorePatterns": ["src/canvas-host/a2ui/a2ui.bundle.js"]
} }

View file

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

View file

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

View file

@ -169,7 +169,7 @@ export function buildAuthHealthSummary(params: {
warnAfterMs, warnAfterMs,
}), }),
) )
.sort((a, b) => { .toSorted((a, b) => {
if (a.provider !== b.provider) { if (a.provider !== b.provider) {
return a.provider.localeCompare(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), a.provider.localeCompare(b.provider),
); );

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -116,7 +116,7 @@ export function createProcessTool(
exitSignal: s.exitSignal ?? undefined, exitSignal: s.exitSignal ?? undefined,
})); }));
const lines = [...running, ...finished] const lines = [...running, ...finished]
.sort((a, b) => b.startedAt - a.startedAt) .toSorted((a, b) => b.startedAt - a.startedAt)
.map((s) => { .map((s) => {
const label = s.name ? truncateMiddle(s.name, 80) : truncateMiddle(s.command, 120); const label = s.name ? truncateMiddle(s.name, 80) : truncateMiddle(s.command, 120);
return `${s.sessionId} ${pad(s.status, 9)} ${formatDuration(s.runtimeMs)} :: ${label}`; 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( const normalized = new Set(
filter.map((entry) => entry.trim().toLowerCase()).filter((entry) => entry.length > 0), filter.map((entry) => entry.trim().toLowerCase()).filter((entry) => entry.length > 0),
); );
return Array.from(normalized).sort(); return Array.from(normalized).toSorted();
} }
function buildCacheKey(params: { 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) { if (refreshIntervalSeconds > 0) {

View file

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

View file

@ -159,7 +159,7 @@ function buildModelAliasLines(cfg?: OpenClawConfig) {
entries.push({ alias, model }); entries.push({ alias, model });
} }
return entries return entries
.sort((a, b) => a.alias.localeCompare(b.alias)) .toSorted((a, b) => a.alias.localeCompare(b.alias))
.map((entry) => `- ${entry.alias}: ${entry.model}`); .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 { function toUsage(raw: Record<string, unknown>): CliUsage | undefined {
const pick = (key: string) => 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 input = pick("input_tokens") ?? pick("inputTokens");
const output = pick("output_tokens") ?? pick("outputTokens"); const output = pick("output_tokens") ?? pick("outputTokens");
const cacheRead = const cacheRead =

View file

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

View file

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

View file

@ -83,9 +83,7 @@ export async function loadModelCatalog(params?: {
? entry.contextWindow ? entry.contextWindow
: undefined; : undefined;
const reasoning = typeof entry?.reasoning === "boolean" ? entry.reasoning : undefined; const reasoning = typeof entry?.reasoning === "boolean" ? entry.reasoning : undefined;
const input = Array.isArray(entry?.input) const input = Array.isArray(entry?.input) ? entry.input : undefined;
? (entry.input as Array<"text" | "image">)
: undefined;
models.push({ id, name, provider, contextWindow, reasoning, input }); 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"); const isZai = model.provider === "zai" || baseUrl.includes("api.z.ai");
if (!isZai || !isOpenAiCompletionsModel(model)) return model; if (!isZai || !isOpenAiCompletionsModel(model)) return model;
const openaiModel = model as Model<"openai-completions">; const openaiModel = model;
const compat = openaiModel.compat ?? undefined; const compat = openaiModel.compat ?? undefined;
if (compat?.supportsDeveloperRole === false) return model; if (compat?.supportsDeveloperRole === false) return model;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -14,7 +14,7 @@ export function setCompactionSafeguardRuntime(
return; return;
} }
const key = sessionManager as object; const key = sessionManager;
if (value === null) { if (value === null) {
REGISTRY.delete(key); REGISTRY.delete(key);
return; return;
@ -30,5 +30,5 @@ export function getCompactionSafeguardRuntime(
return null; 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[]; modifiedFiles: string[];
} { } {
const modified = new Set([...fileOps.edited, ...fileOps.written]); const modified = new Set([...fileOps.edited, ...fileOps.written]);
const readFiles = [...fileOps.read].filter((f) => !modified.has(f)).sort(); const readFiles = [...fileOps.read].filter((f) => !modified.has(f)).toSorted();
const modifiedFiles = [...modified].sort(); const modifiedFiles = [...modified].toSorted();
return { readFiles, modifiedFiles }; 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 type { ContextEvent, ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
import { pruneContextMessages } from "./pruner.js"; import { pruneContextMessages } from "./pruner.js";
@ -21,7 +20,7 @@ export default function contextPruningExtension(api: ExtensionAPI): void {
} }
const next = pruneContextMessages({ const next = pruneContextMessages({
messages: event.messages as AgentMessage[], messages: event.messages,
settings: runtime.settings, settings: runtime.settings,
ctx, ctx,
isToolPrunable: runtime.isToolPrunable, isToolPrunable: runtime.isToolPrunable,

View file

@ -20,7 +20,7 @@ export function setContextPruningRuntime(
return; return;
} }
const key = sessionManager as object; const key = sessionManager;
if (value === null) { if (value === null) {
REGISTRY.delete(key); REGISTRY.delete(key);
return; return;
@ -36,5 +36,5 @@ export function getContextPruningRuntime(
return null; 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, label: tool.label ?? name,
description: tool.description ?? "", description: tool.description ?? "",
// biome-ignore lint/suspicious/noExplicitAny: TypeBox schema from pi-agent-core uses a different module instance. // 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 ( execute: async (
toolCallId, toolCallId,
params, params,

View file

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

View file

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

View file

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

View file

@ -20,14 +20,14 @@ function normalizeForHash(value: unknown): unknown {
.filter((item): item is unknown => item !== undefined); .filter((item): item is unknown => item !== undefined);
const primitives = normalized.filter(isPrimitive); const primitives = normalized.filter(isPrimitive);
if (primitives.length === normalized.length) { if (primitives.length === normalized.length) {
return [...primitives].sort((a, b) => return [...primitives].toSorted((a, b) =>
primitiveToString(a).localeCompare(primitiveToString(b)), primitiveToString(a).localeCompare(primitiveToString(b)),
); );
} }
return normalized; return normalized;
} }
if (value && typeof value === "object") { 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> = {}; const normalized: Record<string, unknown> = {};
for (const [key, entryValue] of entries) { for (const [key, entryValue] of entries) {
const next = normalizeForHash(entryValue); 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) { for (let i = 0; i < messages.length; i += 1) {
const msg = messages[i] as AgentMessage; const msg = messages[i];
if (!msg || typeof msg !== "object") { if (!msg || typeof msg !== "object") {
out.push(msg); out.push(msg);
continue; continue;
@ -129,7 +129,7 @@ export function repairToolUseResultPairing(messages: AgentMessage[]): ToolUseRep
let j = i + 1; let j = i + 1;
for (; j < messages.length; j += 1) { for (; j < messages.length; j += 1) {
const next = messages[j] as AgentMessage; const next = messages[j];
if (!next || typeof next !== "object") { if (!next || typeof next !== "object") {
remainder.push(next); remainder.push(next);
continue; continue;

View file

@ -57,7 +57,7 @@ describe("buildWorkspaceSkillCommandSpecs", () => {
reservedNames: new Set(["help"]), 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(names).toEqual(["hello_world", "hello_world_2", "help_2"]);
expect(commands.find((entry) => entry.skillName === "hidden-skill")).toBeUndefined(); 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).toContain("visible-skill");
expect(snapshot.prompt).not.toContain("hidden-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", "hidden-skill",
"visible-skill", "visible-skill",
]); ]);

View file

@ -39,7 +39,7 @@ export function resolveSkillsInstallPreferences(config?: OpenClawConfig) {
const manager = managerRaw.toLowerCase(); const manager = managerRaw.toLowerCase();
const nodeManager = const nodeManager =
manager === "pnpm" || manager === "yarn" || manager === "bun" || manager === "npm" manager === "pnpm" || manager === "yarn" || manager === "bun" || manager === "npm"
? (manager as "npm" | "pnpm" | "yarn" | "bun") ? manager
: "npm"; : "npm";
return { preferBrew, nodeManager }; 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 { export function isConfigPathTruthy(config: OpenClawConfig | undefined, pathStr: string): boolean {
const value = resolveConfigPath(config, pathStr); const value = resolveConfigPath(config, pathStr);
if (value === undefined && pathStr in DEFAULT_CONFIG_VALUES) { if (value === undefined && pathStr in DEFAULT_CONFIG_VALUES) {
return DEFAULT_CONFIG_VALUES[pathStr] === true; return DEFAULT_CONFIG_VALUES[pathStr];
} }
return isTruthy(value); return isTruthy(value);
} }

View file

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

View file

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

View file

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

View file

@ -264,7 +264,7 @@ export function buildAgentSystemPrompt(params: {
const name = resolveToolName(tool); const name = resolveToolName(tool);
return summary ? `- ${name}: ${summary}` : `- ${name}`; 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 summary = coreToolSummaries[tool] ?? externalToolSummaries.get(tool);
const name = resolveToolName(tool); const name = resolveToolName(tool);
toolLines.push(summary ? `- ${name}: ${summary}` : `- ${name}`); 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] const sideGrid = [sideStart, 1800, 1600, 1400, 1200, 1000, 800]
.map((v) => Math.min(params.maxDimensionPx, v)) .map((v) => Math.min(params.maxDimensionPx, v))
.filter((v, i, arr) => v > 0 && arr.indexOf(v) === i) .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; let smallest: { buffer: Buffer; size: number } | null = null;
for (const side of sideGrid) { for (const side of sideGrid) {
@ -191,7 +191,7 @@ export async function sanitizeImageBlocks(
): Promise<{ images: ImageContent[]; dropped: number }> { ): Promise<{ images: ImageContent[]; dropped: number }> {
if (images.length === 0) return { images, dropped: 0 }; if (images.length === 0) return { images, dropped: 0 };
const sanitized = await sanitizeContentBlocksImages(images as ToolContentBlock[], label, opts); 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) }; return { images: next, dropped: Math.max(0, images.length - next.length) };
} }

View file

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

View file

@ -72,7 +72,9 @@ export function createAgentsListTool(opts?: {
} }
const all = Array.from(allowed); 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 ordered = [requesterAgentId, ...rest];
const agents: AgentListEntry[] = ordered.map((id) => ({ const agents: AgentListEntry[] = ordered.map((id) => ({
id, id,

View file

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

View file

@ -103,10 +103,10 @@ async function buildReminderContextLines(params: {
const { mainKey, alias } = resolveMainSessionAlias(cfg); const { mainKey, alias } = resolveMainSessionAlias(cfg);
const resolvedKey = resolveInternalSessionKey({ key: sessionKey, alias, mainKey }); const resolvedKey = resolveInternalSessionKey({ key: sessionKey, alias, mainKey });
try { try {
const res = (await callGatewayTool("chat.history", params.gatewayOpts, { const res = await callGatewayTool("chat.history", params.gatewayOpts, {
sessionKey: resolvedKey, sessionKey: resolvedKey,
limit: maxMessages, limit: maxMessages,
})) as { messages?: unknown[] }; });
const messages = Array.isArray(res?.messages) ? res.messages : []; const messages = Array.isArray(res?.messages) ? res.messages : [];
const parsed = messages const parsed = messages
.map((msg) => extractMessageText(msg as ChatMessage)) .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 context = buildImageContext(params.prompt, params.base64, params.mimeType);
const message = (await complete(model, context, { const message = await complete(model, context, {
apiKey, apiKey,
maxTokens: 512, maxTokens: 512,
})) as AssistantMessage; });
const text = coerceImageAssistantText({ const text = coerceImageAssistantText({
message, message,
provider: model.provider, provider: model.provider,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -38,7 +38,7 @@ function formatListTop(
entries: Array<{ name: string; value: number }>, entries: Array<{ name: string; value: number }>,
cap: number, cap: number,
): { lines: string[]; omitted: 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 top = sorted.slice(0, cap);
const omitted = Math.max(0, sorted.length - top.length); const omitted = Math.max(0, sorted.length - top.length);
const lines = top.map((e) => `- ${e.name}: ${formatCharsAndTokens(e.value)}`); 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 const toolPropsLines = report.tools.entries
.filter((t) => t.propertiesCount != null) .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) .slice(0, 30)
.map((t) => `- ${t.name}: ${t.propertiesCount} params`); .map((t) => `- ${t.name}: ${t.propertiesCount} params`);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -87,7 +87,7 @@ export async function prependSystemEvents(params: {
const min = pick("minute"); const min = pick("minute");
const sec = pick("second"); const sec = pick("second");
const tz = [...parts] const tz = [...parts]
.reverse() .toReversed()
.find((part) => part.type === "timeZoneName") .find((part) => part.type === "timeZoneName")
?.value?.trim(); ?.value?.trim();
if (!yyyy || !mm || !dd || !hh || !min || !sec) return undefined; 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; const baseEntry = !isNewSession && freshEntry ? entry : undefined;
// Track the originating channel/to for announce routing (subagent announce-back). // Track the originating channel/to for announce routing (subagent announce-back).
const lastChannelRaw = (ctx.OriginatingChannel as string | undefined) || baseEntry?.lastChannel; const lastChannelRaw = (ctx.OriginatingChannel as string | undefined) || baseEntry?.lastChannel;
const lastToRaw = (ctx.OriginatingTo as string | undefined) || ctx.To || baseEntry?.lastTo; const lastToRaw = ctx.OriginatingTo || ctx.To || baseEntry?.lastTo;
const lastAccountIdRaw = (ctx.AccountId as string | undefined) || baseEntry?.lastAccountId; const lastAccountIdRaw = ctx.AccountId || baseEntry?.lastAccountId;
const lastThreadIdRaw = const lastThreadIdRaw = ctx.MessageThreadId || baseEntry?.lastThreadId;
(ctx.MessageThreadId as string | number | undefined) || baseEntry?.lastThreadId;
const deliveryFields = normalizeSessionDeliveryFields({ const deliveryFields = normalizeSessionDeliveryFields({
deliveryContext: { deliveryContext: {
channel: lastChannelRaw, channel: lastChannelRaw,

View file

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

View file

@ -296,7 +296,7 @@ export async function snapshotDom(opts: {
awaitPromise: true, awaitPromise: true,
returnByValue: true, returnByValue: true,
}); });
const value = evaluated.result?.value as unknown; const value = evaluated.result?.value;
if (!value || typeof value !== "object") return { nodes: [] }; if (!value || typeof value !== "object") return { nodes: [] };
const nodes = (value as { nodes?: unknown }).nodes; const nodes = (value as { nodes?: unknown }).nodes;
return { nodes: Array.isArray(nodes) ? (nodes as DomSnapshotNode[]) : [] }; 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()) { if ("error" in parsed && typeof parsed.error === "string" && parsed.error.trim()) {
pending.reject(new Error(parsed.error)); pending.reject(new Error(parsed.error));
} else { } else {
pending.resolve((parsed as ExtensionResponseMessage).result); pending.resolve(parsed.result);
} }
return; return;
} }

View file

@ -183,18 +183,18 @@ describe("color allocation", () => {
it("allocates next unused color from palette", () => { it("allocates next unused color from palette", () => {
// biome-ignore lint/style/noNonNullAssertion: Test file with known array // 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]); expect(allocateColor(usedColors)).toBe(PROFILE_COLORS[1]);
}); });
it("skips multiple used colors", () => { it("skips multiple used colors", () => {
const usedColors = new Set([ const usedColors = new Set([
// biome-ignore lint/style/noNonNullAssertion: Test file with known array // 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 // 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 // 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]); 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 // All colors used, cycle based on count
const index = usedColors.size % PROFILE_COLORS.length; const index = usedColors.size % PROFILE_COLORS.length;
// biome-ignore lint/style/noNonNullAssertion: Array is non-empty constant // 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( export function getUsedColors(

View file

@ -84,7 +84,7 @@ describe("pw-role-snapshot", () => {
expect(res.snapshot).toContain('- button "Save"'); expect(res.snapshot).toContain('- button "Save"');
expect(res.snapshot).not.toContain("navigation"); expect(res.snapshot).not.toContain("navigation");
expect(res.snapshot).not.toContain("heading"); 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.e5).toMatchObject({ role: "link", name: "Home" });
expect(res.refs.e7).toMatchObject({ role: "button", name: "Save" }); expect(res.refs.e7).toMatchObject({ role: "button", name: "Save" });
}); });

View file

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

View file

@ -93,7 +93,7 @@ export async function responseBodyViaPlaywright(opts: {
bodyText = new TextDecoder("utf-8").decode(buf); bodyText = new TextDecoder("utf-8").decode(buf);
} }
} catch (err) { } 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; const trimmed = bodyText.length > maxChars ? bodyText.slice(0, maxChars) : bodyText;

View file

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

View file

@ -136,7 +136,8 @@ export async function setTimezoneViaPlaywright(opts: {
} catch (err) { } catch (err) {
const msg = String(err); const msg = String(err);
if (msg.includes("Timezone override is already in effect")) return; 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; throw err;
} }
}); });

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -30,7 +30,7 @@ describe("directory (config-backed)", () => {
query: null, query: null,
limit: null, limit: null,
}); });
expect(peers?.map((e) => e.id).sort()).toEqual([ expect(peers?.map((e) => e.id).toSorted()).toEqual([
"user:u123", "user:u123",
"user:u234", "user:u234",
"user:u777", "user:u777",
@ -73,7 +73,7 @@ describe("directory (config-backed)", () => {
query: null, query: null,
limit: 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({ const groups = await listDiscordDirectoryGroupsFromConfig({
cfg, cfg,
@ -81,7 +81,7 @@ describe("directory (config-backed)", () => {
query: null, query: null,
limit: 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 () => { it("lists Telegram peers/groups from config", async () => {
@ -102,7 +102,7 @@ describe("directory (config-backed)", () => {
query: null, query: null,
limit: 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({ const groups = await listTelegramDirectoryGroupsFromConfig({
cfg, cfg,

View file

@ -28,7 +28,7 @@ function dedupeChannels(channels: ChannelPlugin[]): ChannelPlugin[] {
export function listChannelPlugins(): ChannelPlugin[] { export function listChannelPlugins(): ChannelPlugin[] {
const combined = dedupeChannels(listPluginChannels()); 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 indexA = CHAT_CHANNEL_ORDER.indexOf(a.id as ChatChannelId);
const indexB = CHAT_CHANNEL_ORDER.indexOf(b.id as ChatChannelId); const indexB = CHAT_CHANNEL_ORDER.indexOf(b.id as ChatChannelId);
const orderA = a.meta.order ?? (indexA === -1 ? 999 : indexA); 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)" }); options.push({ value: "disabled", label: "Disabled (block all channels)" });
} }
const initialValue = params.currentPolicy ?? "allowlist"; const initialValue = params.currentPolicy ?? "allowlist";
return (await params.prompter.select({ return await params.prompter.select({
message: `${params.label} access`, message: `${params.label} access`,
options, options,
initialValue, initialValue,
})) as ChannelAccessPolicy; });
} }
export async function promptChannelAllowlist(params: { 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) => { export const promptAccountId: PromptAccountId = async (params: PromptAccountIdParams) => {
const existingIds = params.listAccountIds(params.cfg); const existingIds = params.listAccountIds(params.cfg);
const initial = params.currentId?.trim() || params.defaultAccountId || DEFAULT_ACCOUNT_ID; 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`, message: `${params.label} account`,
options: [ options: [
...existingIds.map((id) => ({ ...existingIds.map((id) => ({
@ -14,7 +14,7 @@ export const promptAccountId: PromptAccountId = async (params: PromptAccountIdPa
{ value: "__new__", label: "Add a new account" }, { value: "__new__", label: "Add a new account" },
], ],
initialValue: initial, initialValue: initial,
})) as string; });
if (choice !== "__new__") return normalizeAccountId(choice); if (choice !== "__new__") return normalizeAccountId(choice);

View file

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

View file

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

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