diff --git a/.oxlintrc.json b/.oxlintrc.json index 0347622a24..a809db4d72 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -7,6 +7,7 @@ "suspicious": "error" }, "rules": { + "curly": "error", "eslint-plugin-unicorn/prefer-array-find": "off", "eslint/no-await-in-loop": "off", "eslint/no-new": "off", diff --git a/src/acp/client.ts b/src/acp/client.ts index e235e013c5..db84c5eb96 100644 --- a/src/acp/client.ts +++ b/src/acp/client.ts @@ -27,7 +27,9 @@ export type AcpClientHandle = { }; function toArgs(value: string[] | string | undefined): string[] { - if (!value) return []; + if (!value) { + return []; + } return Array.isArray(value) ? value : [value]; } @@ -41,7 +43,9 @@ function buildServerArgs(opts: AcpClientOptions): string[] { function printSessionUpdate(notification: SessionNotification): void { const update = notification.update; - if (!("sessionUpdate" in update)) return; + if (!("sessionUpdate" in update)) { + return; + } switch (update.sessionUpdate) { case "agent_message_chunk": { @@ -62,7 +66,9 @@ function printSessionUpdate(notification: SessionNotification): void { } case "available_commands_update": { const names = update.availableCommands?.map((cmd) => `/${cmd.name}`).join(" "); - if (names) console.log(`\n[commands] ${names}`); + if (names) { + console.log(`\n[commands] ${names}`); + } return; } default: diff --git a/src/acp/event-mapper.ts b/src/acp/event-mapper.ts index 1c488f27d5..5e1179f1cb 100644 --- a/src/acp/event-mapper.ts +++ b/src/acp/event-mapper.ts @@ -15,7 +15,9 @@ export function extractTextFromPrompt(prompt: ContentBlock[]): string { } if (block.type === "resource") { const resource = block.resource as { text?: string } | undefined; - if (resource?.text) parts.push(resource.text); + if (resource?.text) { + parts.push(resource.text); + } continue; } if (block.type === "resource_link") { @@ -31,9 +33,13 @@ export function extractTextFromPrompt(prompt: ContentBlock[]): string { export function extractAttachmentsFromPrompt(prompt: ContentBlock[]): GatewayAttachment[] { const attachments: GatewayAttachment[] = []; for (const block of prompt) { - if (block.type !== "image") continue; + if (block.type !== "image") { + continue; + } const image = block as ImageContent; - if (!image.data || !image.mimeType) continue; + if (!image.data || !image.mimeType) { + continue; + } attachments.push({ type: "image", mimeType: image.mimeType, @@ -48,7 +54,9 @@ export function formatToolTitle( args: Record | undefined, ): string { const base = name ?? "tool"; - if (!args || Object.keys(args).length === 0) return base; + if (!args || Object.keys(args).length === 0) { + return base; + } const parts = Object.entries(args).map(([key, value]) => { const raw = typeof value === "string" ? value : JSON.stringify(value); const safe = raw.length > 100 ? `${raw.slice(0, 100)}...` : raw; @@ -58,16 +66,30 @@ export function formatToolTitle( } export function inferToolKind(name?: string): ToolKind { - if (!name) return "other"; + if (!name) { + return "other"; + } const normalized = name.toLowerCase(); - if (normalized.includes("read")) return "read"; - if (normalized.includes("write") || normalized.includes("edit")) return "edit"; - if (normalized.includes("delete") || normalized.includes("remove")) return "delete"; - if (normalized.includes("move") || normalized.includes("rename")) return "move"; - if (normalized.includes("search") || normalized.includes("find")) return "search"; + if (normalized.includes("read")) { + return "read"; + } + if (normalized.includes("write") || normalized.includes("edit")) { + return "edit"; + } + if (normalized.includes("delete") || normalized.includes("remove")) { + return "delete"; + } + if (normalized.includes("move") || normalized.includes("rename")) { + return "move"; + } + if (normalized.includes("search") || normalized.includes("find")) { + return "search"; + } if (normalized.includes("exec") || normalized.includes("run") || normalized.includes("bash")) { return "execute"; } - if (normalized.includes("fetch") || normalized.includes("http")) return "fetch"; + if (normalized.includes("fetch") || normalized.includes("http")) { + return "fetch"; + } return "other"; } diff --git a/src/acp/meta.ts b/src/acp/meta.ts index 277b7491ab..eccd865dbd 100644 --- a/src/acp/meta.ts +++ b/src/acp/meta.ts @@ -2,10 +2,14 @@ export function readString( meta: Record | null | undefined, keys: string[], ): string | undefined { - if (!meta) return undefined; + if (!meta) { + return undefined; + } for (const key of keys) { const value = meta[key]; - if (typeof value === "string" && value.trim()) return value.trim(); + if (typeof value === "string" && value.trim()) { + return value.trim(); + } } return undefined; } @@ -14,10 +18,14 @@ export function readBool( meta: Record | null | undefined, keys: string[], ): boolean | undefined { - if (!meta) return undefined; + if (!meta) { + return undefined; + } for (const key of keys) { const value = meta[key]; - if (typeof value === "boolean") return value; + if (typeof value === "boolean") { + return value; + } } return undefined; } @@ -26,10 +34,14 @@ export function readNumber( meta: Record | null | undefined, keys: string[], ): number | undefined { - if (!meta) return undefined; + if (!meta) { + return undefined; + } for (const key of keys) { const value = meta[key]; - if (typeof value === "number" && Number.isFinite(value)) return value; + if (typeof value === "number" && Number.isFinite(value)) { + return value; + } } return undefined; } diff --git a/src/acp/session-mapper.ts b/src/acp/session-mapper.ts index 8101617700..6fa8b047e8 100644 --- a/src/acp/session-mapper.ts +++ b/src/acp/session-mapper.ts @@ -12,7 +12,9 @@ export type AcpSessionMeta = { }; export function parseSessionMeta(meta: unknown): AcpSessionMeta { - if (!meta || typeof meta !== "object") return {}; + if (!meta || typeof meta !== "object") { + return {}; + } const record = meta as Record; return { sessionKey: readString(record, ["sessionKey", "session", "key"]), @@ -45,7 +47,9 @@ export async function resolveSessionKey(params: { } if (params.meta.sessionKey) { - if (!requireExisting) return params.meta.sessionKey; + if (!requireExisting) { + return params.meta.sessionKey; + } const resolved = await params.gateway.request<{ ok: true; key: string }>("sessions.resolve", { key: params.meta.sessionKey, }); @@ -66,7 +70,9 @@ export async function resolveSessionKey(params: { } if (requestedKey) { - if (!requireExisting) return requestedKey; + if (!requireExisting) { + return requestedKey; + } const resolved = await params.gateway.request<{ ok: true; key: string }>("sessions.resolve", { key: requestedKey, }); @@ -86,6 +92,8 @@ export async function resetSessionIfNeeded(params: { opts: AcpServerOptions; }): Promise { const resetSession = params.meta.resetSession ?? params.opts.resetSession ?? false; - if (!resetSession) return; + if (!resetSession) { + return; + } await params.gateway.request("sessions.reset", { key: params.sessionKey }); } diff --git a/src/acp/session.ts b/src/acp/session.ts index 427364be67..ecec6fb8d0 100644 --- a/src/acp/session.ts +++ b/src/acp/session.ts @@ -39,7 +39,9 @@ export function createInMemorySessionStore(): AcpSessionStore { const setActiveRun: AcpSessionStore["setActiveRun"] = (sessionId, runId, abortController) => { const session = sessions.get(sessionId); - if (!session) return; + if (!session) { + return; + } session.activeRunId = runId; session.abortController = abortController; runIdToSessionId.set(runId, sessionId); @@ -47,17 +49,25 @@ export function createInMemorySessionStore(): AcpSessionStore { const clearActiveRun: AcpSessionStore["clearActiveRun"] = (sessionId) => { const session = sessions.get(sessionId); - if (!session) return; - if (session.activeRunId) runIdToSessionId.delete(session.activeRunId); + if (!session) { + return; + } + if (session.activeRunId) { + runIdToSessionId.delete(session.activeRunId); + } session.activeRunId = null; session.abortController = null; }; const cancelActiveRun: AcpSessionStore["cancelActiveRun"] = (sessionId) => { const session = sessions.get(sessionId); - if (!session?.abortController) return false; + if (!session?.abortController) { + return false; + } session.abortController.abort(); - if (session.activeRunId) runIdToSessionId.delete(session.activeRunId); + if (session.activeRunId) { + runIdToSessionId.delete(session.activeRunId); + } session.abortController = null; session.activeRunId = null; return true; diff --git a/src/acp/translator.ts b/src/acp/translator.ts index 4b92f3c6cf..7475cff226 100644 --- a/src/acp/translator.ts +++ b/src/acp/translator.ts @@ -210,7 +210,9 @@ export class AcpGatewayAgent implements Agent { if (!session) { throw new Error(`Session ${params.sessionId} not found`); } - if (!params.modeId) return {}; + if (!params.modeId) { + return {}; + } try { await this.gateway.request("sessions.patch", { key: session.sessionKey, @@ -276,7 +278,9 @@ export class AcpGatewayAgent implements Agent { async cancel(params: CancelNotification): Promise { const session = this.sessionStore.getSession(params.sessionId); - if (!session) return; + if (!session) { + return; + } this.sessionStore.cancelActiveRun(params.sessionId); try { @@ -294,24 +298,38 @@ export class AcpGatewayAgent implements Agent { private async handleAgentEvent(evt: EventFrame): Promise { const payload = evt.payload as Record | undefined; - if (!payload) return; + if (!payload) { + return; + } const stream = payload.stream as string | undefined; const data = payload.data as Record | undefined; const sessionKey = payload.sessionKey as string | undefined; - if (!stream || !data || !sessionKey) return; + if (!stream || !data || !sessionKey) { + return; + } - if (stream !== "tool") return; + if (stream !== "tool") { + return; + } const phase = data.phase as string | undefined; const name = data.name as string | undefined; const toolCallId = data.toolCallId as string | undefined; - if (!toolCallId) return; + if (!toolCallId) { + return; + } const pending = this.findPendingBySessionKey(sessionKey); - if (!pending) return; + if (!pending) { + return; + } if (phase === "start") { - if (!pending.toolCalls) pending.toolCalls = new Set(); - if (pending.toolCalls.has(toolCallId)) return; + if (!pending.toolCalls) { + pending.toolCalls = new Set(); + } + if (pending.toolCalls.has(toolCallId)) { + return; + } pending.toolCalls.add(toolCallId); const args = data.args as Record | undefined; await this.connection.sessionUpdate({ @@ -344,17 +362,25 @@ export class AcpGatewayAgent implements Agent { private async handleChatEvent(evt: EventFrame): Promise { const payload = evt.payload as Record | undefined; - if (!payload) return; + if (!payload) { + return; + } const sessionKey = payload.sessionKey as string | undefined; const state = payload.state as string | undefined; const runId = payload.runId as string | undefined; const messageData = payload.message as Record | undefined; - if (!sessionKey || !state) return; + if (!sessionKey || !state) { + return; + } const pending = this.findPendingBySessionKey(sessionKey); - if (!pending) return; - if (runId && pending.idempotencyKey !== runId) return; + if (!pending) { + return; + } + if (runId && pending.idempotencyKey !== runId) { + return; + } if (state === "delta" && messageData) { await this.handleDeltaEvent(pending.sessionId, messageData); @@ -381,10 +407,14 @@ export class AcpGatewayAgent implements Agent { const content = messageData.content as Array<{ type: string; text?: string }> | undefined; const fullText = content?.find((c) => c.type === "text")?.text ?? ""; const pending = this.pendingPrompts.get(sessionId); - if (!pending) return; + if (!pending) { + return; + } const sentSoFar = pending.sentTextLength ?? 0; - if (fullText.length <= sentSoFar) return; + if (fullText.length <= sentSoFar) { + return; + } const newText = fullText.slice(sentSoFar); pending.sentTextLength = fullText.length; @@ -407,7 +437,9 @@ export class AcpGatewayAgent implements Agent { private findPendingBySessionKey(sessionKey: string): PendingPrompt | undefined { for (const pending of this.pendingPrompts.values()) { - if (pending.sessionKey === sessionKey) return pending; + if (pending.sessionKey === sessionKey) { + return pending; + } } return undefined; } diff --git a/src/agents/agent-paths.ts b/src/agents/agent-paths.ts index c6fe47fc6c..d5ce3dd86f 100644 --- a/src/agents/agent-paths.ts +++ b/src/agents/agent-paths.ts @@ -7,14 +7,20 @@ import { resolveUserPath } from "../utils.js"; export function resolveOpenClawAgentDir(): string { const override = process.env.OPENCLAW_AGENT_DIR?.trim() || process.env.PI_CODING_AGENT_DIR?.trim(); - if (override) return resolveUserPath(override); + if (override) { + return resolveUserPath(override); + } const defaultAgentDir = path.join(resolveStateDir(), "agents", DEFAULT_AGENT_ID, "agent"); return resolveUserPath(defaultAgentDir); } export function ensureOpenClawAgentEnv(): string { const dir = resolveOpenClawAgentDir(); - if (!process.env.OPENCLAW_AGENT_DIR) process.env.OPENCLAW_AGENT_DIR = dir; - if (!process.env.PI_CODING_AGENT_DIR) process.env.PI_CODING_AGENT_DIR = dir; + if (!process.env.OPENCLAW_AGENT_DIR) { + process.env.OPENCLAW_AGENT_DIR = dir; + } + if (!process.env.PI_CODING_AGENT_DIR) { + process.env.PI_CODING_AGENT_DIR = dir; + } return dir; } diff --git a/src/agents/agent-scope.ts b/src/agents/agent-scope.ts index 27363a51de..0d8c637c6b 100644 --- a/src/agents/agent-scope.ts +++ b/src/agents/agent-scope.ts @@ -34,18 +34,24 @@ let defaultAgentWarned = false; function listAgents(cfg: OpenClawConfig): AgentEntry[] { const list = cfg.agents?.list; - if (!Array.isArray(list)) return []; + if (!Array.isArray(list)) { + return []; + } return list.filter((entry): entry is AgentEntry => Boolean(entry && typeof entry === "object")); } export function listAgentIds(cfg: OpenClawConfig): string[] { const agents = listAgents(cfg); - if (agents.length === 0) return [DEFAULT_AGENT_ID]; + if (agents.length === 0) { + return [DEFAULT_AGENT_ID]; + } const seen = new Set(); const ids: string[] = []; for (const entry of agents) { const id = normalizeAgentId(entry?.id); - if (seen.has(id)) continue; + if (seen.has(id)) { + continue; + } seen.add(id); ids.push(id); } @@ -54,7 +60,9 @@ export function listAgentIds(cfg: OpenClawConfig): string[] { export function resolveDefaultAgentId(cfg: OpenClawConfig): string { const agents = listAgents(cfg); - if (agents.length === 0) return DEFAULT_AGENT_ID; + if (agents.length === 0) { + return DEFAULT_AGENT_ID; + } const defaults = agents.filter((agent) => agent?.default); if (defaults.length > 1 && !defaultAgentWarned) { defaultAgentWarned = true; @@ -94,7 +102,9 @@ export function resolveAgentConfig( ): ResolvedAgentConfig | undefined { const id = normalizeAgentId(agentId); const entry = resolveAgentEntry(cfg, id); - if (!entry) return undefined; + if (!entry) { + return undefined; + } return { name: typeof entry.name === "string" ? entry.name : undefined, workspace: typeof entry.workspace === "string" ? entry.workspace : undefined, @@ -116,8 +126,12 @@ export function resolveAgentConfig( export function resolveAgentModelPrimary(cfg: OpenClawConfig, agentId: string): string | undefined { const raw = resolveAgentConfig(cfg, agentId)?.model; - if (!raw) return undefined; - if (typeof raw === "string") return raw.trim() || undefined; + if (!raw) { + return undefined; + } + if (typeof raw === "string") { + return raw.trim() || undefined; + } const primary = raw.primary?.trim(); return primary || undefined; } @@ -127,20 +141,28 @@ export function resolveAgentModelFallbacksOverride( agentId: string, ): string[] | undefined { const raw = resolveAgentConfig(cfg, agentId)?.model; - if (!raw || typeof raw === "string") return undefined; + if (!raw || typeof raw === "string") { + return undefined; + } // Important: treat an explicitly provided empty array as an override to disable global fallbacks. - if (!Object.hasOwn(raw, "fallbacks")) return undefined; + if (!Object.hasOwn(raw, "fallbacks")) { + return undefined; + } return Array.isArray(raw.fallbacks) ? raw.fallbacks : undefined; } export function resolveAgentWorkspaceDir(cfg: OpenClawConfig, agentId: string) { const id = normalizeAgentId(agentId); const configured = resolveAgentConfig(cfg, id)?.workspace?.trim(); - if (configured) return resolveUserPath(configured); + if (configured) { + return resolveUserPath(configured); + } const defaultAgentId = resolveDefaultAgentId(cfg); if (id === defaultAgentId) { const fallback = cfg.agents?.defaults?.workspace?.trim(); - if (fallback) return resolveUserPath(fallback); + if (fallback) { + return resolveUserPath(fallback); + } return DEFAULT_AGENT_WORKSPACE_DIR; } return path.join(os.homedir(), ".openclaw", `workspace-${id}`); @@ -149,7 +171,9 @@ export function resolveAgentWorkspaceDir(cfg: OpenClawConfig, agentId: string) { export function resolveAgentDir(cfg: OpenClawConfig, agentId: string) { const id = normalizeAgentId(agentId); const configured = resolveAgentConfig(cfg, id)?.agentDir?.trim(); - if (configured) return resolveUserPath(configured); + if (configured) { + return resolveUserPath(configured); + } const root = resolveStateDir(process.env, os.homedir); return path.join(root, "agents", id, "agent"); } diff --git a/src/agents/anthropic-payload-log.ts b/src/agents/anthropic-payload-log.ts index cba6ddf28a..415159ada1 100644 --- a/src/agents/anthropic-payload-log.ts +++ b/src/agents/anthropic-payload-log.ts @@ -52,7 +52,9 @@ function resolvePayloadLogConfig(env: NodeJS.ProcessEnv): PayloadLogConfig { function getWriter(filePath: string): PayloadLogWriter { const existing = writers.get(filePath); - if (existing) return existing; + if (existing) { + return existing; + } const dir = path.dirname(filePath); const ready = fs.mkdir(dir, { recursive: true }).catch(() => undefined); @@ -75,8 +77,12 @@ function getWriter(filePath: string): PayloadLogWriter { function safeJsonStringify(value: unknown): string | null { try { return JSON.stringify(value, (_key, val) => { - if (typeof val === "bigint") return val.toString(); - if (typeof val === "function") return "[Function]"; + if (typeof val === "bigint") { + return val.toString(); + } + if (typeof val === "function") { + return "[Function]"; + } if (val instanceof Error) { return { name: val.name, message: val.message, stack: val.stack }; } @@ -91,8 +97,12 @@ function safeJsonStringify(value: unknown): string | null { } function formatError(error: unknown): string | undefined { - if (error instanceof Error) return error.message; - if (typeof error === "string") return error; + if (error instanceof Error) { + return error.message; + } + if (typeof error === "string") { + return error; + } if (typeof error === "number" || typeof error === "boolean" || typeof error === "bigint") { return String(error); } @@ -104,7 +114,9 @@ function formatError(error: unknown): string | undefined { function digest(value: unknown): string | undefined { const serialized = safeJsonStringify(value); - if (!serialized) return undefined; + if (!serialized) { + return undefined; + } return crypto.createHash("sha256").update(serialized).digest("hex"); } @@ -140,7 +152,9 @@ export function createAnthropicPayloadLogger(params: { }): AnthropicPayloadLogger | null { const env = params.env ?? process.env; const cfg = resolvePayloadLogConfig(env); - if (!cfg.enabled) return null; + if (!cfg.enabled) { + return null; + } const writer = getWriter(cfg.filePath); const base: Omit = { @@ -155,7 +169,9 @@ export function createAnthropicPayloadLogger(params: { const record = (event: PayloadLogEvent) => { const line = safeJsonStringify(event); - if (!line) return; + if (!line) { + return; + } writer.write(`${line}\n`); }; diff --git a/src/agents/anthropic.setup-token.live.test.ts b/src/agents/anthropic.setup-token.live.test.ts index de31fd7f41..8ff3d6f33f 100644 --- a/src/agents/anthropic.setup-token.live.test.ts +++ b/src/agents/anthropic.setup-token.live.test.ts @@ -46,8 +46,12 @@ function listSetupTokenProfiles(store: { }): string[] { return Object.entries(store.profiles) .filter(([, cred]) => { - if (cred.type !== "token") return false; - if (normalizeProviderId(cred.provider) !== "anthropic") return false; + if (cred.type !== "token") { + return false; + } + if (normalizeProviderId(cred.provider) !== "anthropic") { + return false; + } return isSetupToken(cred.token); }) .map(([id]) => id); @@ -56,7 +60,9 @@ function listSetupTokenProfiles(store: { function pickSetupTokenProfile(candidates: string[]): string { const preferred = ["anthropic:setup-token-test", "anthropic:setup-token", "anthropic:default"]; for (const id of preferred) { - if (candidates.includes(id)) return id; + if (candidates.includes(id)) { + return id; + } } return candidates[0] ?? ""; } @@ -124,7 +130,9 @@ function pickModel(models: Array>, raw?: string): Model | null { const normalized = raw?.trim() ?? ""; if (normalized) { const parsed = parseModelRef(normalized, "anthropic"); - if (!parsed) return null; + if (!parsed) { + return null; + } return ( models.find( (model) => @@ -141,7 +149,9 @@ function pickModel(models: Array>, raw?: string): Model | null { ]; for (const id of preferred) { const match = models.find((model) => model.id === id); - if (match) return match; + if (match) { + return match; + } } return models[0] ?? null; } diff --git a/src/agents/apply-patch-update.ts b/src/agents/apply-patch-update.ts index e5d5367015..87d8b97f46 100644 --- a/src/agents/apply-patch-update.ts +++ b/src/agents/apply-patch-update.ts @@ -104,21 +104,33 @@ function seekSequence( start: number, eof: boolean, ): number | null { - if (pattern.length === 0) return start; - if (pattern.length > lines.length) return null; + if (pattern.length === 0) { + return start; + } + if (pattern.length > lines.length) { + return null; + } const maxStart = lines.length - pattern.length; const searchStart = eof && lines.length >= pattern.length ? maxStart : start; - if (searchStart > maxStart) return null; + if (searchStart > maxStart) { + return null; + } for (let i = searchStart; i <= maxStart; i += 1) { - if (linesMatch(lines, pattern, i, (value) => value)) return i; + if (linesMatch(lines, pattern, i, (value) => value)) { + return i; + } } for (let i = searchStart; i <= maxStart; i += 1) { - if (linesMatch(lines, pattern, i, (value) => value.trimEnd())) return i; + if (linesMatch(lines, pattern, i, (value) => value.trimEnd())) { + return i; + } } for (let i = searchStart; i <= maxStart; i += 1) { - if (linesMatch(lines, pattern, i, (value) => value.trim())) return i; + if (linesMatch(lines, pattern, i, (value) => value.trim())) { + return i; + } } for (let i = searchStart; i <= maxStart; i += 1) { if (linesMatch(lines, pattern, i, (value) => normalizePunctuation(value.trim()))) { diff --git a/src/agents/apply-patch.ts b/src/agents/apply-patch.ts index 045bb712d3..275e623187 100644 --- a/src/agents/apply-patch.ts +++ b/src/agents/apply-patch.ts @@ -183,22 +183,32 @@ function recordSummary( bucket: keyof ApplyPatchSummary, value: string, ) { - if (seen[bucket].has(value)) return; + if (seen[bucket].has(value)) { + return; + } seen[bucket].add(value); summary[bucket].push(value); } function formatSummary(summary: ApplyPatchSummary): string { const lines = ["Success. Updated the following files:"]; - for (const file of summary.added) lines.push(`A ${file}`); - for (const file of summary.modified) lines.push(`M ${file}`); - for (const file of summary.deleted) lines.push(`D ${file}`); + for (const file of summary.added) { + lines.push(`A ${file}`); + } + for (const file of summary.modified) { + lines.push(`M ${file}`); + } + for (const file of summary.deleted) { + lines.push(`D ${file}`); + } return lines.join("\n"); } async function ensureDir(filePath: string) { const parent = path.dirname(filePath); - if (!parent || parent === ".") return; + if (!parent || parent === ".") { + return; + } await fs.mkdir(parent, { recursive: true }); } @@ -231,21 +241,31 @@ function normalizeUnicodeSpaces(value: string): string { function expandPath(filePath: string): string { const normalized = normalizeUnicodeSpaces(filePath); - if (normalized === "~") return os.homedir(); - if (normalized.startsWith("~/")) return os.homedir() + normalized.slice(1); + if (normalized === "~") { + return os.homedir(); + } + if (normalized.startsWith("~/")) { + return os.homedir() + normalized.slice(1); + } return normalized; } function resolvePathFromCwd(filePath: string, cwd: string): string { const expanded = expandPath(filePath); - if (path.isAbsolute(expanded)) return path.normalize(expanded); + if (path.isAbsolute(expanded)) { + return path.normalize(expanded); + } return path.resolve(cwd, expanded); } function toDisplayPath(resolved: string, cwd: string): string { const relative = path.relative(cwd, resolved); - if (!relative || relative === "") return path.basename(resolved); - if (relative.startsWith("..") || path.isAbsolute(relative)) return resolved; + if (!relative || relative === "") { + return path.basename(resolved); + } + if (relative.startsWith("..") || path.isAbsolute(relative)) { + return resolved; + } return relative; } @@ -275,7 +295,9 @@ function parsePatchText(input: string): { hunks: Hunk[]; patch: string } { function checkPatchBoundariesLenient(lines: string[]): string[] { const strictError = checkPatchBoundariesStrict(lines); - if (!strictError) return lines; + if (!strictError) { + return lines; + } if (lines.length < 4) { throw new Error(strictError); @@ -285,7 +307,9 @@ function checkPatchBoundariesLenient(lines: string[]): string[] { if ((first === "< { await fs.rm(tempDir, { recursive: true, force: true }); tempDir = null; } - if (previousStateDir === undefined) delete process.env.OPENCLAW_STATE_DIR; - else process.env.OPENCLAW_STATE_DIR = previousStateDir; - if (previousAgentDir === undefined) delete process.env.OPENCLAW_AGENT_DIR; - else process.env.OPENCLAW_AGENT_DIR = previousAgentDir; - if (previousPiAgentDir === undefined) delete process.env.PI_CODING_AGENT_DIR; - else process.env.PI_CODING_AGENT_DIR = previousPiAgentDir; - if (previousChutesClientId === undefined) delete process.env.CHUTES_CLIENT_ID; - else process.env.CHUTES_CLIENT_ID = previousChutesClientId; + if (previousStateDir === undefined) { + delete process.env.OPENCLAW_STATE_DIR; + } else { + process.env.OPENCLAW_STATE_DIR = previousStateDir; + } + if (previousAgentDir === undefined) { + delete process.env.OPENCLAW_AGENT_DIR; + } else { + process.env.OPENCLAW_AGENT_DIR = previousAgentDir; + } + if (previousPiAgentDir === undefined) { + delete process.env.PI_CODING_AGENT_DIR; + } else { + process.env.PI_CODING_AGENT_DIR = previousPiAgentDir; + } + if (previousChutesClientId === undefined) { + delete process.env.CHUTES_CLIENT_ID; + } else { + process.env.CHUTES_CLIENT_ID = previousChutesClientId; + } }); it("refreshes expired Chutes OAuth credentials", async () => { @@ -59,7 +71,9 @@ describe("auth-profiles (chutes)", () => { const fetchSpy = vi.fn(async (input: string | URL) => { const url = typeof input === "string" ? input : input.toString(); - if (url !== CHUTES_TOKEN_ENDPOINT) return new Response("not found", { status: 404 }); + if (url !== CHUTES_TOKEN_ENDPOINT) { + return new Response("not found", { status: 404 }); + } return new Response( JSON.stringify({ access_token: "at_new", diff --git a/src/agents/auth-profiles/display.ts b/src/agents/auth-profiles/display.ts index 538f560ade..f0c0e6492c 100644 --- a/src/agents/auth-profiles/display.ts +++ b/src/agents/auth-profiles/display.ts @@ -10,6 +10,8 @@ export function resolveAuthProfileDisplayLabel(params: { const profile = store.profiles[profileId]; const configEmail = cfg?.auth?.profiles?.[profileId]?.email?.trim(); const email = configEmail || (profile && "email" in profile ? profile.email?.trim() : undefined); - if (email) return `${profileId} (${email})`; + if (email) { + return `${profileId} (${email})`; + } return profileId; } diff --git a/src/agents/auth-profiles/doctor.ts b/src/agents/auth-profiles/doctor.ts index 6be588d52b..ee743a0600 100644 --- a/src/agents/auth-profiles/doctor.ts +++ b/src/agents/auth-profiles/doctor.ts @@ -12,7 +12,9 @@ export function formatAuthDoctorHint(params: { profileId?: string; }): string { const providerKey = normalizeProviderId(params.provider); - if (providerKey !== "anthropic") return ""; + if (providerKey !== "anthropic") { + return ""; + } const legacyProfileId = params.profileId ?? "anthropic:default"; const suggested = suggestOAuthProfileIdForLegacyDefault({ @@ -21,7 +23,9 @@ export function formatAuthDoctorHint(params: { provider: providerKey, legacyProfileId, }); - if (!suggested || suggested === legacyProfileId) return ""; + if (!suggested || suggested === legacyProfileId) { + return ""; + } const storeOauthProfiles = listProfilesForProvider(params.store, providerKey) .filter((id) => params.store.profiles[id]?.type === "oauth") diff --git a/src/agents/auth-profiles/external-cli-sync.ts b/src/agents/auth-profiles/external-cli-sync.ts index d1fa31f23f..6b721d4dc8 100644 --- a/src/agents/auth-profiles/external-cli-sync.ts +++ b/src/agents/auth-profiles/external-cli-sync.ts @@ -8,8 +8,12 @@ import { import type { AuthProfileCredential, AuthProfileStore, OAuthCredential } from "./types.js"; function shallowEqualOAuthCredentials(a: OAuthCredential | undefined, b: OAuthCredential): boolean { - if (!a) return false; - if (a.type !== "oauth") return false; + if (!a) { + return false; + } + if (a.type !== "oauth") { + return false; + } return ( a.provider === b.provider && a.access === b.access && @@ -23,12 +27,18 @@ function shallowEqualOAuthCredentials(a: OAuthCredential | undefined, b: OAuthCr } function isExternalProfileFresh(cred: AuthProfileCredential | undefined, now: number): boolean { - if (!cred) return false; - if (cred.type !== "oauth" && cred.type !== "token") return false; + if (!cred) { + return false; + } + if (cred.type !== "oauth" && cred.type !== "token") { + return false; + } if (cred.provider !== "qwen-portal") { return false; } - if (typeof cred.expires !== "number") return true; + if (typeof cred.expires !== "number") { + return true; + } return cred.expires > now + EXTERNAL_CLI_NEAR_EXPIRY_MS; } diff --git a/src/agents/auth-profiles/oauth.fallback-to-main-agent.test.ts b/src/agents/auth-profiles/oauth.fallback-to-main-agent.test.ts index ab546a4e8b..90197b991c 100644 --- a/src/agents/auth-profiles/oauth.fallback-to-main-agent.test.ts +++ b/src/agents/auth-profiles/oauth.fallback-to-main-agent.test.ts @@ -31,12 +31,21 @@ describe("resolveApiKeyForProfile fallback to main agent", () => { vi.unstubAllGlobals(); // Restore original environment - if (previousStateDir === undefined) delete process.env.OPENCLAW_STATE_DIR; - else process.env.OPENCLAW_STATE_DIR = previousStateDir; - if (previousAgentDir === undefined) delete process.env.OPENCLAW_AGENT_DIR; - else process.env.OPENCLAW_AGENT_DIR = previousAgentDir; - if (previousPiAgentDir === undefined) delete process.env.PI_CODING_AGENT_DIR; - else process.env.PI_CODING_AGENT_DIR = previousPiAgentDir; + if (previousStateDir === undefined) { + delete process.env.OPENCLAW_STATE_DIR; + } else { + process.env.OPENCLAW_STATE_DIR = previousStateDir; + } + if (previousAgentDir === undefined) { + delete process.env.OPENCLAW_AGENT_DIR; + } else { + process.env.OPENCLAW_AGENT_DIR = previousAgentDir; + } + if (previousPiAgentDir === undefined) { + delete process.env.PI_CODING_AGENT_DIR; + } else { + process.env.PI_CODING_AGENT_DIR = previousPiAgentDir; + } await fs.rm(tmpDir, { recursive: true, force: true }); }); diff --git a/src/agents/auth-profiles/oauth.ts b/src/agents/auth-profiles/oauth.ts index d8c762d6b9..faeb063a43 100644 --- a/src/agents/auth-profiles/oauth.ts +++ b/src/agents/auth-profiles/oauth.ts @@ -36,7 +36,9 @@ async function refreshOAuthTokenWithLock(params: { const store = ensureAuthProfileStore(params.agentDir); const cred = store.profiles[params.profileId]; - if (!cred || cred.type !== "oauth") return null; + if (!cred || cred.type !== "oauth") { + return null; + } if (Date.now() < cred.expires) { return { @@ -63,7 +65,9 @@ async function refreshOAuthTokenWithLock(params: { return { apiKey: newCredentials.access, newCredentials }; })() : await getOAuthApiKey(cred.provider, oauthCreds); - if (!result) return null; + if (!result) { + return null; + } store.profiles[params.profileId] = { ...cred, ...result.newCredentials, @@ -91,10 +95,16 @@ async function tryResolveOAuthProfile(params: { }): Promise<{ apiKey: string; provider: string; email?: string } | null> { const { cfg, store, profileId } = params; const cred = store.profiles[profileId]; - if (!cred || cred.type !== "oauth") return null; + if (!cred || cred.type !== "oauth") { + return null; + } const profileConfig = cfg?.auth?.profiles?.[profileId]; - if (profileConfig && profileConfig.provider !== cred.provider) return null; - if (profileConfig && profileConfig.mode !== cred.type) return null; + if (profileConfig && profileConfig.provider !== cred.provider) { + return null; + } + if (profileConfig && profileConfig.mode !== cred.type) { + return null; + } if (Date.now() < cred.expires) { return { @@ -108,7 +118,9 @@ async function tryResolveOAuthProfile(params: { profileId, agentDir: params.agentDir, }); - if (!refreshed) return null; + if (!refreshed) { + return null; + } return { apiKey: refreshed.apiKey, provider: cred.provider, @@ -124,12 +136,18 @@ export async function resolveApiKeyForProfile(params: { }): Promise<{ apiKey: string; provider: string; email?: string } | null> { const { cfg, store, profileId } = params; const cred = store.profiles[profileId]; - if (!cred) return null; + if (!cred) { + return null; + } const profileConfig = cfg?.auth?.profiles?.[profileId]; - if (profileConfig && profileConfig.provider !== cred.provider) return null; + if (profileConfig && profileConfig.provider !== cred.provider) { + return null; + } if (profileConfig && profileConfig.mode !== cred.type) { // Compatibility: treat "oauth" config as compatible with stored token profiles. - if (!(profileConfig.mode === "oauth" && cred.type === "token")) return null; + if (!(profileConfig.mode === "oauth" && cred.type === "token")) { + return null; + } } if (cred.type === "api_key") { @@ -137,7 +155,9 @@ export async function resolveApiKeyForProfile(params: { } if (cred.type === "token") { const token = cred.token?.trim(); - if (!token) return null; + if (!token) { + return null; + } if ( typeof cred.expires === "number" && Number.isFinite(cred.expires) && @@ -161,7 +181,9 @@ export async function resolveApiKeyForProfile(params: { profileId, agentDir: params.agentDir, }); - if (!result) return null; + if (!result) { + return null; + } return { apiKey: result.apiKey, provider: cred.provider, @@ -191,7 +213,9 @@ export async function resolveApiKeyForProfile(params: { profileId: fallbackProfileId, agentDir: params.agentDir, }); - if (fallbackResolved) return fallbackResolved; + if (fallbackResolved) { + return fallbackResolved; + } } catch { // keep original error } diff --git a/src/agents/auth-profiles/order.ts b/src/agents/auth-profiles/order.ts index 6e2b5baaf3..5f26f23f73 100644 --- a/src/agents/auth-profiles/order.ts +++ b/src/agents/auth-profiles/order.ts @@ -11,7 +11,9 @@ function resolveProfileUnusableUntil(stats: { const values = [stats.cooldownUntil, stats.disabledUntil] .filter((value): value is number => typeof value === "number") .filter((value) => Number.isFinite(value) && value > 0); - if (values.length === 0) return null; + if (values.length === 0) { + return null; + } return Math.max(...values); } @@ -26,17 +28,25 @@ export function resolveAuthProfileOrder(params: { const now = Date.now(); const storedOrder = (() => { const order = store.order; - if (!order) return undefined; + if (!order) { + return undefined; + } for (const [key, value] of Object.entries(order)) { - if (normalizeProviderId(key) === providerKey) return value; + if (normalizeProviderId(key) === providerKey) { + return value; + } } return undefined; })(); const configuredOrder = (() => { const order = cfg?.auth?.order; - if (!order) return undefined; + if (!order) { + return undefined; + } for (const [key, value] of Object.entries(order)) { - if (normalizeProviderId(key) === providerKey) return value; + if (normalizeProviderId(key) === providerKey) { + return value; + } } return undefined; })(); @@ -49,12 +59,18 @@ export function resolveAuthProfileOrder(params: { const baseOrder = explicitOrder ?? (explicitProfiles.length > 0 ? explicitProfiles : listProfilesForProvider(store, providerKey)); - if (baseOrder.length === 0) return []; + if (baseOrder.length === 0) { + return []; + } const filtered = baseOrder.filter((profileId) => { const cred = store.profiles[profileId]; - if (!cred) return false; - if (normalizeProviderId(cred.provider) !== providerKey) return false; + if (!cred) { + return false; + } + if (normalizeProviderId(cred.provider) !== providerKey) { + return false; + } const profileConfig = cfg?.auth?.profiles?.[profileId]; if (profileConfig) { if (normalizeProviderId(profileConfig.provider) !== providerKey) { @@ -62,12 +78,18 @@ export function resolveAuthProfileOrder(params: { } if (profileConfig.mode !== cred.type) { const oauthCompatible = profileConfig.mode === "oauth" && cred.type === "token"; - if (!oauthCompatible) return false; + if (!oauthCompatible) { + return false; + } } } - if (cred.type === "api_key") return Boolean(cred.key?.trim()); + if (cred.type === "api_key") { + return Boolean(cred.key?.trim()); + } if (cred.type === "token") { - if (!cred.token?.trim()) return false; + if (!cred.token?.trim()) { + return false; + } if ( typeof cred.expires === "number" && Number.isFinite(cred.expires) && @@ -85,7 +107,9 @@ export function resolveAuthProfileOrder(params: { }); const deduped: string[] = []; for (const entry of filtered) { - if (!deduped.includes(entry)) deduped.push(entry); + if (!deduped.includes(entry)) { + deduped.push(entry); + } } // If user specified explicit order (store override or config), respect it @@ -165,7 +189,9 @@ function orderProfilesByMode(order: string[], store: AuthProfileStore): string[] const sorted = scored .toSorted((a, b) => { // 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) return a.lastUsed - b.lastUsed; }) diff --git a/src/agents/auth-profiles/paths.ts b/src/agents/auth-profiles/paths.ts index d663370c43..3b4ca0a69d 100644 --- a/src/agents/auth-profiles/paths.ts +++ b/src/agents/auth-profiles/paths.ts @@ -23,7 +23,9 @@ export function resolveAuthStorePathForDisplay(agentDir?: string): string { } export function ensureAuthStoreFile(pathname: string) { - if (fs.existsSync(pathname)) return; + if (fs.existsSync(pathname)) { + return; + } const payload: AuthProfileStore = { version: AUTH_STORE_VERSION, profiles: {}, diff --git a/src/agents/auth-profiles/profiles.ts b/src/agents/auth-profiles/profiles.ts index b14b83324a..ed9204e7bf 100644 --- a/src/agents/auth-profiles/profiles.ts +++ b/src/agents/auth-profiles/profiles.ts @@ -19,7 +19,9 @@ export async function setAuthProfileOrder(params: { const deduped: string[] = []; for (const entry of sanitized) { - if (!deduped.includes(entry)) deduped.push(entry); + if (!deduped.includes(entry)) { + deduped.push(entry); + } } return await updateAuthProfileStoreWithLock({ @@ -27,7 +29,9 @@ export async function setAuthProfileOrder(params: { updater: (store) => { store.order = store.order ?? {}; if (deduped.length === 0) { - if (!store.order[providerKey]) return false; + if (!store.order[providerKey]) { + return false; + } delete store.order[providerKey]; if (Object.keys(store.order).length === 0) { store.order = undefined; @@ -68,7 +72,9 @@ export async function markAuthProfileGood(params: { agentDir, updater: (freshStore) => { const profile = freshStore.profiles[profileId]; - if (!profile || profile.provider !== provider) return false; + if (!profile || profile.provider !== provider) { + return false; + } freshStore.lastGood = { ...freshStore.lastGood, [provider]: profileId }; return true; }, @@ -78,7 +84,9 @@ export async function markAuthProfileGood(params: { return; } const profile = store.profiles[profileId]; - if (!profile || profile.provider !== provider) return; + if (!profile || profile.provider !== provider) { + return; + } store.lastGood = { ...store.lastGood, [provider]: profileId }; saveAuthProfileStore(store, agentDir); } diff --git a/src/agents/auth-profiles/repair.ts b/src/agents/auth-profiles/repair.ts index a4575dea94..f7d2fa0583 100644 --- a/src/agents/auth-profiles/repair.ts +++ b/src/agents/auth-profiles/repair.ts @@ -6,13 +6,17 @@ import type { AuthProfileIdRepairResult, AuthProfileStore } from "./types.js"; function getProfileSuffix(profileId: string): string { const idx = profileId.indexOf(":"); - if (idx < 0) return ""; + if (idx < 0) { + return ""; + } return profileId.slice(idx + 1); } function isEmailLike(value: string): boolean { const trimmed = value.trim(); - if (!trimmed) return false; + if (!trimmed) { + return false; + } return trimmed.includes("@") && trimmed.includes("."); } @@ -24,7 +28,9 @@ export function suggestOAuthProfileIdForLegacyDefault(params: { }): string | null { const providerKey = normalizeProviderId(params.provider); const legacySuffix = getProfileSuffix(params.legacyProfileId); - if (legacySuffix !== "default") return null; + if (legacySuffix !== "default") { + return null; + } const legacyCfg = params.cfg?.auth?.profiles?.[params.legacyProfileId]; if ( @@ -38,27 +44,39 @@ export function suggestOAuthProfileIdForLegacyDefault(params: { const oauthProfiles = listProfilesForProvider(params.store, providerKey).filter( (id) => params.store.profiles[id]?.type === "oauth", ); - if (oauthProfiles.length === 0) return null; + if (oauthProfiles.length === 0) { + return null; + } const configuredEmail = legacyCfg?.email?.trim(); if (configuredEmail) { const byEmail = oauthProfiles.find((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?.trim(); return email === configuredEmail || id === `${providerKey}:${configuredEmail}`; }); - if (byEmail) return byEmail; + if (byEmail) { + return byEmail; + } } const lastGood = params.store.lastGood?.[providerKey] ?? params.store.lastGood?.[params.provider]; - if (lastGood && oauthProfiles.includes(lastGood)) return lastGood; + if (lastGood && oauthProfiles.includes(lastGood)) { + return lastGood; + } const nonLegacy = oauthProfiles.filter((id) => id !== params.legacyProfileId); - if (nonLegacy.length === 1) return nonLegacy[0] ?? null; + if (nonLegacy.length === 1) { + return nonLegacy[0] ?? null; + } const emailLike = nonLegacy.filter((id) => isEmailLike(getProfileSuffix(id))); - if (emailLike.length === 1) return emailLike[0] ?? null; + if (emailLike.length === 1) { + return emailLike[0] ?? null; + } return null; } @@ -107,17 +125,25 @@ export function repairOAuthProfileIdMismatch(params: { const providerKey = normalizeProviderId(params.provider); const nextOrder = (() => { const order = params.cfg.auth?.order; - if (!order) return undefined; + if (!order) { + return undefined; + } const resolvedKey = Object.keys(order).find((key) => normalizeProviderId(key) === providerKey); - if (!resolvedKey) return order; + if (!resolvedKey) { + return order; + } const existing = order[resolvedKey]; - if (!Array.isArray(existing)) return order; + if (!Array.isArray(existing)) { + return order; + } const replaced = existing .map((id) => (id === legacyProfileId ? toProfileId : id)) .filter((id): id is string => typeof id === "string" && id.trim().length > 0); const deduped: string[] = []; for (const entry of replaced) { - if (!deduped.includes(entry)) deduped.push(entry); + if (!deduped.includes(entry)) { + deduped.push(entry); + } } return { ...order, [resolvedKey]: deduped }; })(); diff --git a/src/agents/auth-profiles/session-override.test.ts b/src/agents/auth-profiles/session-override.test.ts index ddff62bc3f..671053102b 100644 --- a/src/agents/auth-profiles/session-override.test.ts +++ b/src/agents/auth-profiles/session-override.test.ts @@ -53,8 +53,11 @@ describe("resolveSessionAuthProfileOverride", () => { expect(resolved).toBe("zai:work"); expect(sessionEntry.authProfileOverride).toBe("zai:work"); } finally { - if (prevStateDir === undefined) delete process.env.OPENCLAW_STATE_DIR; - else process.env.OPENCLAW_STATE_DIR = prevStateDir; + if (prevStateDir === undefined) { + delete process.env.OPENCLAW_STATE_DIR; + } else { + process.env.OPENCLAW_STATE_DIR = prevStateDir; + } await fs.rm(tmpDir, { recursive: true, force: true }); } }); diff --git a/src/agents/auth-profiles/session-override.ts b/src/agents/auth-profiles/session-override.ts index 58e9c89b74..5ed395820c 100644 --- a/src/agents/auth-profiles/session-override.ts +++ b/src/agents/auth-profiles/session-override.ts @@ -13,7 +13,9 @@ function isProfileForProvider(params: { store: ReturnType; }): boolean { const entry = params.store.profiles[params.profileId]; - if (!entry?.provider) return false; + if (!entry?.provider) { + return false; + } return normalizeProviderId(entry.provider) === normalizeProviderId(params.provider); } @@ -56,7 +58,9 @@ export async function resolveSessionAuthProfileOverride(params: { storePath, isNewSession, } = params; - if (!sessionEntry || !sessionStore || !sessionKey) return sessionEntry?.authProfileOverride; + if (!sessionEntry || !sessionStore || !sessionKey) { + return sessionEntry?.authProfileOverride; + } const store = ensureAuthProfileStore(agentDir, { allowKeychainPrompt: false }); const order = resolveAuthProfileOrder({ cfg, store, provider }); @@ -77,16 +81,22 @@ export async function resolveSessionAuthProfileOverride(params: { current = undefined; } - if (order.length === 0) return undefined; + if (order.length === 0) { + return undefined; + } const pickFirstAvailable = () => order.find((profileId) => !isProfileInCooldown(store, profileId)) ?? order[0]; const pickNextAvailable = (active: string) => { const startIndex = order.indexOf(active); - if (startIndex < 0) return pickFirstAvailable(); + if (startIndex < 0) { + return pickFirstAvailable(); + } for (let offset = 1; offset <= order.length; offset += 1) { const candidate = order[(startIndex + offset) % order.length]; - if (!isProfileInCooldown(store, candidate)) return candidate; + if (!isProfileInCooldown(store, candidate)) { + return candidate; + } } return order[startIndex] ?? order[0]; }; @@ -117,7 +127,9 @@ export async function resolveSessionAuthProfileOverride(params: { next = pickFirstAvailable(); } - if (!next) return current; + if (!next) { + return current; + } const shouldPersist = next !== sessionEntry.authProfileOverride || sessionEntry.authProfileOverrideSource !== "auto" || diff --git a/src/agents/auth-profiles/store.ts b/src/agents/auth-profiles/store.ts index ae4a999b93..c8a3162215 100644 --- a/src/agents/auth-profiles/store.ts +++ b/src/agents/auth-profiles/store.ts @@ -48,12 +48,18 @@ export async function updateAuthProfileStoreWithLock(params: { } function coerceLegacyStore(raw: unknown): LegacyAuthStore | null { - if (!raw || typeof raw !== "object") return null; + if (!raw || typeof raw !== "object") { + return null; + } const record = raw as Record; - if ("profiles" in record) return null; + if ("profiles" in record) { + return null; + } const entries: LegacyAuthStore = {}; for (const [key, value] of Object.entries(record)) { - if (!value || typeof value !== "object") continue; + if (!value || typeof value !== "object") { + continue; + } const typed = value as Partial; if (typed.type !== "api_key" && typed.type !== "oauth" && typed.type !== "token") { continue; @@ -67,29 +73,41 @@ function coerceLegacyStore(raw: unknown): LegacyAuthStore | null { } function coerceAuthStore(raw: unknown): AuthProfileStore | null { - if (!raw || typeof raw !== "object") return null; + if (!raw || typeof raw !== "object") { + return null; + } const record = raw as Record; - if (!record.profiles || typeof record.profiles !== "object") return null; + if (!record.profiles || typeof record.profiles !== "object") { + return null; + } const profiles = record.profiles as Record; const normalized: Record = {}; for (const [key, value] of Object.entries(profiles)) { - if (!value || typeof value !== "object") continue; + if (!value || typeof value !== "object") { + continue; + } const typed = value as Partial; if (typed.type !== "api_key" && typed.type !== "oauth" && typed.type !== "token") { continue; } - if (!typed.provider) continue; + if (!typed.provider) { + continue; + } normalized[key] = typed as AuthProfileCredential; } const order = record.order && typeof record.order === "object" ? Object.entries(record.order as Record).reduce( (acc, [provider, value]) => { - if (!Array.isArray(value)) return acc; + if (!Array.isArray(value)) { + return acc; + } const list = value .map((entry) => (typeof entry === "string" ? entry.trim() : "")) .filter(Boolean); - if (list.length === 0) return acc; + if (list.length === 0) { + return acc; + } acc[provider] = list; return acc; }, @@ -115,9 +133,15 @@ function mergeRecord( base?: Record, override?: Record, ): Record | undefined { - if (!base && !override) return undefined; - if (!base) return { ...override }; - if (!override) return { ...base }; + if (!base && !override) { + return undefined; + } + if (!base) { + return { ...override }; + } + if (!override) { + return { ...base }; + } return { ...base, ...override }; } @@ -145,13 +169,19 @@ function mergeAuthProfileStores( function mergeOAuthFileIntoStore(store: AuthProfileStore): boolean { const oauthPath = resolveOAuthPath(); const oauthRaw = loadJsonFile(oauthPath); - if (!oauthRaw || typeof oauthRaw !== "object") return false; + if (!oauthRaw || typeof oauthRaw !== "object") { + return false; + } const oauthEntries = oauthRaw as Record; let mutated = false; for (const [provider, creds] of Object.entries(oauthEntries)) { - if (!creds || typeof creds !== "object") continue; + if (!creds || typeof creds !== "object") { + continue; + } const profileId = `${provider}:default`; - if (store.profiles[profileId]) continue; + if (store.profiles[profileId]) { + continue; + } store.profiles[profileId] = { type: "oauth", provider, diff --git a/src/agents/auth-profiles/usage.ts b/src/agents/auth-profiles/usage.ts index 82a77c7fed..1839637d5d 100644 --- a/src/agents/auth-profiles/usage.ts +++ b/src/agents/auth-profiles/usage.ts @@ -7,7 +7,9 @@ function resolveProfileUnusableUntil(stats: ProfileUsageStats): number | null { const values = [stats.cooldownUntil, stats.disabledUntil] .filter((value): value is number => typeof value === "number") .filter((value) => Number.isFinite(value) && value > 0); - if (values.length === 0) return null; + if (values.length === 0) { + return null; + } return Math.max(...values); } @@ -16,7 +18,9 @@ function resolveProfileUnusableUntil(stats: ProfileUsageStats): number | null { */ export function isProfileInCooldown(store: AuthProfileStore, profileId: string): boolean { const stats = store.usageStats?.[profileId]; - if (!stats) return false; + if (!stats) { + return false; + } const unusableUntil = resolveProfileUnusableUntil(stats); return unusableUntil ? Date.now() < unusableUntil : false; } @@ -34,7 +38,9 @@ export async function markAuthProfileUsed(params: { const updated = await updateAuthProfileStoreWithLock({ agentDir, updater: (freshStore) => { - if (!freshStore.profiles[profileId]) return false; + if (!freshStore.profiles[profileId]) { + return false; + } freshStore.usageStats = freshStore.usageStats ?? {}; freshStore.usageStats[profileId] = { ...freshStore.usageStats[profileId], @@ -52,7 +58,9 @@ export async function markAuthProfileUsed(params: { store.usageStats = updated.usageStats; return; } - if (!store.profiles[profileId]) return; + if (!store.profiles[profileId]) { + return; + } store.usageStats = store.usageStats ?? {}; store.usageStats[profileId] = { @@ -97,9 +105,13 @@ function resolveAuthCooldownConfig(params: { const cooldowns = params.cfg?.auth?.cooldowns; const billingOverride = (() => { const map = cooldowns?.billingBackoffHoursByProvider; - if (!map) return undefined; + if (!map) { + return undefined; + } for (const [key, value] of Object.entries(map)) { - if (normalizeProviderId(key) === params.providerId) return value; + if (normalizeProviderId(key) === params.providerId) { + return value; + } } return undefined; })(); @@ -139,7 +151,9 @@ export function resolveProfileUnusableUntilForDisplay( profileId: string, ): number | null { const stats = store.usageStats?.[profileId]; - if (!stats) return null; + if (!stats) { + return null; + } return resolveProfileUnusableUntil(stats); } @@ -200,7 +214,9 @@ export async function markAuthProfileFailure(params: { agentDir, updater: (freshStore) => { const profile = freshStore.profiles[profileId]; - if (!profile) return false; + if (!profile) { + return false; + } freshStore.usageStats = freshStore.usageStats ?? {}; const existing = freshStore.usageStats[profileId] ?? {}; @@ -224,7 +240,9 @@ export async function markAuthProfileFailure(params: { store.usageStats = updated.usageStats; return; } - if (!store.profiles[profileId]) return; + if (!store.profiles[profileId]) { + return; + } store.usageStats = store.usageStats ?? {}; const existing = store.usageStats[profileId] ?? {}; @@ -275,7 +293,9 @@ export async function clearAuthProfileCooldown(params: { const updated = await updateAuthProfileStoreWithLock({ agentDir, updater: (freshStore) => { - if (!freshStore.usageStats?.[profileId]) return false; + if (!freshStore.usageStats?.[profileId]) { + return false; + } freshStore.usageStats[profileId] = { ...freshStore.usageStats[profileId], @@ -289,7 +309,9 @@ export async function clearAuthProfileCooldown(params: { store.usageStats = updated.usageStats; return; } - if (!store.usageStats?.[profileId]) return; + if (!store.usageStats?.[profileId]) { + return; + } store.usageStats[profileId] = { ...store.usageStats[profileId], diff --git a/src/agents/bash-process-registry.ts b/src/agents/bash-process-registry.ts index 278b111998..5d48da89ce 100644 --- a/src/agents/bash-process-registry.ts +++ b/src/agents/bash-process-registry.ts @@ -7,7 +7,9 @@ const MAX_JOB_TTL_MS = 3 * 60 * 60 * 1000; // 3 hours const DEFAULT_PENDING_OUTPUT_CHARS = 30_000; function clampTtl(value: number | undefined) { - if (!value || Number.isNaN(value)) return DEFAULT_JOB_TTL_MS; + if (!value || Number.isNaN(value)) { + return DEFAULT_JOB_TTL_MS; + } return Math.min(Math.max(value, MIN_JOB_TTL_MS), MAX_JOB_TTL_MS); } @@ -155,7 +157,9 @@ export function markBackgrounded(session: ProcessSession) { function moveToFinished(session: ProcessSession, status: ProcessStatus) { runningSessions.delete(session.id); - if (!session.backgrounded) return; + if (!session.backgrounded) { + return; + } finishedSessions.set(session.id, { id: session.id, command: session.command, @@ -174,18 +178,24 @@ function moveToFinished(session: ProcessSession, status: ProcessStatus) { } export function tail(text: string, max = 2000) { - if (text.length <= max) return text; + if (text.length <= max) { + return text; + } return text.slice(text.length - max); } function sumPendingChars(buffer: string[]) { let total = 0; - for (const chunk of buffer) total += chunk.length; + for (const chunk of buffer) { + total += chunk.length; + } return total; } function capPendingBuffer(buffer: string[], pendingChars: number, cap: number) { - if (pendingChars <= cap) return pendingChars; + if (pendingChars <= cap) { + return pendingChars; + } const last = buffer.at(-1); if (last && last.length >= cap) { buffer.length = 0; @@ -205,7 +215,9 @@ function capPendingBuffer(buffer: string[], pendingChars: number, cap: number) { } export function trimWithCap(text: string, max: number) { - if (text.length <= max) return text; + if (text.length <= max) { + return text; + } return text.slice(text.length - max); } @@ -228,7 +240,9 @@ export function resetProcessRegistryForTests() { } export function setJobTtlMs(value?: number) { - if (value === undefined || Number.isNaN(value)) return; + if (value === undefined || Number.isNaN(value)) { + return; + } jobTtlMs = clampTtl(value); stopSweeper(); startSweeper(); @@ -244,13 +258,17 @@ function pruneFinishedSessions() { } function startSweeper() { - if (sweeper) return; + if (sweeper) { + return; + } sweeper = setInterval(pruneFinishedSessions, Math.max(30_000, jobTtlMs / 6)); sweeper.unref?.(); } function stopSweeper() { - if (!sweeper) return; + if (!sweeper) { + return; + } clearInterval(sweeper); sweeper = null; } diff --git a/src/agents/bash-tools.exec.background-abort.test.ts b/src/agents/bash-tools.exec.background-abort.test.ts index 5914541bb3..686a30217b 100644 --- a/src/agents/bash-tools.exec.background-abort.test.ts +++ b/src/agents/bash-tools.exec.background-abort.test.ts @@ -39,7 +39,9 @@ test("background exec is not killed when tool signal aborts", async () => { expect(running?.exited).toBe(false); } finally { const pid = running?.pid; - if (pid) killProcessTree(pid); + if (pid) { + killProcessTree(pid); + } } }); @@ -76,7 +78,9 @@ test("background exec still times out after tool signal abort", async () => { expect(finished?.status).toBe("failed"); } finally { const pid = running?.pid; - if (pid) killProcessTree(pid); + if (pid) { + killProcessTree(pid); + } } }); @@ -105,7 +109,9 @@ test("yielded background exec is not killed when tool signal aborts", async () = expect(running?.exited).toBe(false); } finally { const pid = running?.pid; - if (pid) killProcessTree(pid); + if (pid) { + killProcessTree(pid); + } } }); @@ -135,6 +141,8 @@ test("yielded background exec still times out", async () => { expect(finished?.status).toBe("failed"); } finally { const pid = running?.pid; - if (pid) killProcessTree(pid); + if (pid) { + killProcessTree(pid); + } } }); diff --git a/src/agents/bash-tools.exec.path.test.ts b/src/agents/bash-tools.exec.path.test.ts index 3400f747e0..05ed50b70e 100644 --- a/src/agents/bash-tools.exec.path.test.ts +++ b/src/agents/bash-tools.exec.path.test.ts @@ -61,7 +61,9 @@ describe("exec PATH login shell merge", () => { }); it("merges login-shell PATH for host=gateway", async () => { - if (isWin) return; + if (isWin) { + return; + } process.env.PATH = "/usr/bin"; const { createExecTool } = await import("./bash-tools.exec.js"); @@ -79,7 +81,9 @@ describe("exec PATH login shell merge", () => { }); it("skips login-shell PATH when env.PATH is provided", async () => { - if (isWin) return; + if (isWin) { + return; + } process.env.PATH = "/usr/bin"; const { createExecTool } = await import("./bash-tools.exec.js"); diff --git a/src/agents/bash-tools.exec.ts b/src/agents/bash-tools.exec.ts index d226ce2adc..364567ae87 100644 --- a/src/agents/bash-tools.exec.ts +++ b/src/agents/bash-tools.exec.ts @@ -252,13 +252,19 @@ function normalizeNotifyOutput(value: string) { } function normalizePathPrepend(entries?: string[]) { - if (!Array.isArray(entries)) return []; + if (!Array.isArray(entries)) { + return []; + } const seen = new Set(); const normalized: string[] = []; for (const entry of entries) { - if (typeof entry !== "string") continue; + if (typeof entry !== "string") { + continue; + } const trimmed = entry.trim(); - if (!trimmed || seen.has(trimmed)) continue; + if (!trimmed || seen.has(trimmed)) { + continue; + } seen.add(trimmed); normalized.push(trimmed); } @@ -266,7 +272,9 @@ function normalizePathPrepend(entries?: string[]) { } function mergePathPrepend(existing: string | undefined, prepend: string[]) { - if (prepend.length === 0) return existing; + if (prepend.length === 0) { + return existing; + } const partsExisting = (existing ?? "") .split(path.delimiter) .map((part) => part.trim()) @@ -274,7 +282,9 @@ function mergePathPrepend(existing: string | undefined, prepend: string[]) { const merged: string[] = []; const seen = new Set(); for (const part of [...prepend, ...partsExisting]) { - if (seen.has(part)) continue; + if (seen.has(part)) { + continue; + } seen.add(part); merged.push(part); } @@ -286,27 +296,43 @@ function applyPathPrepend( prepend: string[], options?: { requireExisting?: boolean }, ) { - if (prepend.length === 0) return; - if (options?.requireExisting && !env.PATH) return; + if (prepend.length === 0) { + return; + } + if (options?.requireExisting && !env.PATH) { + return; + } const merged = mergePathPrepend(env.PATH, prepend); - if (merged) env.PATH = merged; + if (merged) { + env.PATH = merged; + } } function applyShellPath(env: Record, shellPath?: string | null) { - if (!shellPath) return; + if (!shellPath) { + return; + } const entries = shellPath .split(path.delimiter) .map((part) => part.trim()) .filter(Boolean); - if (entries.length === 0) return; + if (entries.length === 0) { + return; + } const merged = mergePathPrepend(env.PATH, entries); - if (merged) env.PATH = merged; + if (merged) { + env.PATH = merged; + } } function maybeNotifyOnExit(session: ProcessSession, status: "completed" | "failed") { - if (!session.backgrounded || !session.notifyOnExit || session.exitNotified) return; + if (!session.backgrounded || !session.notifyOnExit || session.exitNotified) { + return; + } const sessionKey = session.sessionKey?.trim(); - if (!sessionKey) return; + if (!sessionKey) { + return; + } session.exitNotified = true; const exitLabel = session.exitSignal ? `signal ${session.exitSignal}` @@ -329,13 +355,17 @@ function resolveApprovalRunningNoticeMs(value?: number) { if (typeof value !== "number" || !Number.isFinite(value)) { return DEFAULT_APPROVAL_RUNNING_NOTICE_MS; } - if (value <= 0) return 0; + if (value <= 0) { + return 0; + } return Math.floor(value); } function emitExecSystemEvent(text: string, opts: { sessionKey?: string; contextKey?: string }) { const sessionKey = opts.sessionKey?.trim(); - if (!sessionKey) return; + if (!sessionKey) { + return; + } enqueueSystemEvent(text, { sessionKey, contextKey: opts.contextKey }); requestHeartbeatNow({ reason: "exec-event" }); } @@ -528,13 +558,17 @@ async function runExecProcess(opts: { let resolveFn: ((outcome: ExecProcessOutcome) => void) | null = null; const settle = (outcome: ExecProcessOutcome) => { - if (settled) return; + if (settled) { + return; + } settled = true; resolveFn?.(outcome); }; const finalizeTimeout = () => { - if (session.exited) return; + if (session.exited) { + return; + } markExited(session, null, "SIGKILL", "failed"); maybeNotifyOnExit(session, "failed"); const aggregated = session.aggregated.trim(); @@ -567,7 +601,9 @@ async function runExecProcess(opts: { } const emitUpdate = () => { - if (!opts.onUpdate) return; + if (!opts.onUpdate) { + return; + } const tailText = session.tail || session.aggregated; const warningText = opts.warnings.length ? `${opts.warnings.join("\n")}\n\n` : ""; opts.onUpdate({ @@ -619,8 +655,12 @@ async function runExecProcess(opts: { const promise = new Promise((resolve) => { resolveFn = resolve; const handleExit = (code: number | null, exitSignal: NodeJS.Signals | number | null) => { - if (timeoutTimer) clearTimeout(timeoutTimer); - if (timeoutFinalizeTimer) clearTimeout(timeoutFinalizeTimer); + if (timeoutTimer) { + clearTimeout(timeoutTimer); + } + if (timeoutFinalizeTimer) { + clearTimeout(timeoutFinalizeTimer); + } const durationMs = Date.now() - startedAt; const wasSignal = exitSignal != null; const isSuccess = code === 0 && !wasSignal && !timedOut; @@ -631,7 +671,9 @@ async function runExecProcess(opts: { session.stdin.destroyed = true; } - if (settled) return; + if (settled) { + return; + } const aggregated = session.aggregated.trim(); if (!isSuccess) { const reason = timedOut @@ -675,8 +717,12 @@ async function runExecProcess(opts: { }); child.once("error", (err) => { - if (timeoutTimer) clearTimeout(timeoutTimer); - if (timeoutFinalizeTimer) clearTimeout(timeoutFinalizeTimer); + if (timeoutTimer) { + clearTimeout(timeoutTimer); + } + if (timeoutFinalizeTimer) { + clearTimeout(timeoutFinalizeTimer); + } markExited(session, null, null, "failed"); maybeNotifyOnExit(session, "failed"); const aggregated = session.aggregated.trim(); @@ -795,8 +841,12 @@ export function createExecTool( const contextParts: string[] = []; const provider = defaults?.messageProvider?.trim(); const sessionKey = defaults?.sessionKey?.trim(); - if (provider) contextParts.push(`provider=${provider}`); - if (sessionKey) contextParts.push(`session=${sessionKey}`); + if (provider) { + contextParts.push(`provider=${provider}`); + } + if (sessionKey) { + contextParts.push(`session=${sessionKey}`); + } if (!elevatedDefaults?.enabled) { gates.push("enabled (tools.elevated.enabled / agents.list[].tools.elevated.enabled)"); } else { @@ -1098,7 +1148,9 @@ export function createExecTool( { sessionKey: notifySessionKey, contextKey }, ); } finally { - if (runningTimer) clearTimeout(runningTimer); + if (runningTimer) { + clearTimeout(runningTimer); + } } })(); @@ -1267,7 +1319,9 @@ export function createExecTool( if (allowlistMatches.length > 0) { const seen = new Set(); for (const match of allowlistMatches) { - if (seen.has(match.pattern)) continue; + if (seen.has(match.pattern)) { + continue; + } seen.add(match.pattern); recordAllowlistUse( approvals.file, @@ -1317,7 +1371,9 @@ export function createExecTool( } const outcome = await run.promise; - if (runningTimer) clearTimeout(runningTimer); + if (runningTimer) { + clearTimeout(runningTimer); + } const output = normalizeNotifyOutput( tail(outcome.aggregated || "", DEFAULT_NOTIFY_TAIL_CHARS), ); @@ -1357,7 +1413,9 @@ export function createExecTool( if (allowlistMatches.length > 0) { const seen = new Set(); for (const match of allowlistMatches) { - if (seen.has(match.pattern)) continue; + if (seen.has(match.pattern)) { + continue; + } seen.add(match.pattern); recordAllowlistUse( approvals.file, @@ -1396,12 +1454,15 @@ export function createExecTool( // Tool-call abort should not kill backgrounded sessions; timeouts still must. const onAbortSignal = () => { - if (yielded || run.session.backgrounded) return; + if (yielded || run.session.backgrounded) { + return; + } run.kill(); }; - if (signal?.aborted) onAbortSignal(); - else if (signal) { + if (signal?.aborted) { + onAbortSignal(); + } else if (signal) { signal.addEventListener("abort", onAbortSignal, { once: true }); } @@ -1430,8 +1491,12 @@ export function createExecTool( }); const onYieldNow = () => { - if (yieldTimer) clearTimeout(yieldTimer); - if (yielded) return; + if (yieldTimer) { + clearTimeout(yieldTimer); + } + if (yielded) { + return; + } yielded = true; markBackgrounded(run.session); resolveRunning(); @@ -1442,7 +1507,9 @@ export function createExecTool( onYieldNow(); } else { yieldTimer = setTimeout(() => { - if (yielded) return; + if (yielded) { + return; + } yielded = true; markBackgrounded(run.session); resolveRunning(); @@ -1452,8 +1519,12 @@ export function createExecTool( run.promise .then((outcome) => { - if (yieldTimer) clearTimeout(yieldTimer); - if (yielded || run.session.backgrounded) return; + if (yieldTimer) { + clearTimeout(yieldTimer); + } + if (yielded || run.session.backgrounded) { + return; + } if (outcome.status === "failed") { reject(new Error(outcome.reason ?? "Command failed.")); return; @@ -1475,8 +1546,12 @@ export function createExecTool( }); }) .catch((err) => { - if (yieldTimer) clearTimeout(yieldTimer); - if (yielded || run.session.backgrounded) return; + if (yieldTimer) { + clearTimeout(yieldTimer); + } + if (yielded || run.session.backgrounded) { + return; + } reject(err as Error); }); }); diff --git a/src/agents/bash-tools.process.ts b/src/agents/bash-tools.process.ts index 272c2cb4ac..b498537b3b 100644 --- a/src/agents/bash-tools.process.ts +++ b/src/agents/bash-tools.process.ts @@ -337,8 +337,11 @@ export function createProcessTool( } await new Promise((resolve, reject) => { stdin.write(params.data ?? "", (err) => { - if (err) reject(err); - else resolve(); + if (err) { + reject(err); + } else { + resolve(); + } }); }); if (params.eof) { @@ -414,8 +417,11 @@ export function createProcessTool( } await new Promise((resolve, reject) => { stdin.write(data, (err) => { - if (err) reject(err); - else resolve(); + if (err) { + reject(err); + } else { + resolve(); + } }); }); return { @@ -472,8 +478,11 @@ export function createProcessTool( } await new Promise((resolve, reject) => { stdin.write("\r", (err) => { - if (err) reject(err); - else resolve(); + if (err) { + reject(err); + } else { + resolve(); + } }); }); return { @@ -540,8 +549,11 @@ export function createProcessTool( } await new Promise((resolve, reject) => { stdin.write(payload, (err) => { - if (err) reject(err); - else resolve(); + if (err) { + reject(err); + } else { + resolve(); + } }); }); return { diff --git a/src/agents/bash-tools.shared.ts b/src/agents/bash-tools.shared.ts index 8c2d16fb25..d3965e8bc1 100644 --- a/src/agents/bash-tools.shared.ts +++ b/src/agents/bash-tools.shared.ts @@ -38,9 +38,13 @@ export function buildSandboxEnv(params: { export function coerceEnv(env?: NodeJS.ProcessEnv | Record) { const record: Record = {}; - if (!env) return record; + if (!env) { + return record; + } for (const [key, value] of Object.entries(env)) { - if (typeof value === "string") record[key] = value; + if (typeof value === "string") { + record[key] = value; + } } return record; } @@ -53,7 +57,9 @@ export function buildDockerExecArgs(params: { tty: boolean; }) { const args = ["exec", "-i"]; - if (params.tty) args.push("-t"); + if (params.tty) { + args.push("-t"); + } if (params.workdir) { args.push("-w", params.workdir); } @@ -122,7 +128,9 @@ export function resolveWorkdir(workdir: string, warnings: string[]) { const fallback = current ?? homedir(); try { const stats = statSync(workdir); - if (stats.isDirectory()) return workdir; + if (stats.isDirectory()) { + return workdir; + } } catch { // ignore, fallback below } @@ -145,13 +153,17 @@ export function clampNumber( min: number, max: number, ) { - if (value === undefined || Number.isNaN(value)) return defaultValue; + if (value === undefined || Number.isNaN(value)) { + return defaultValue; + } return Math.min(Math.max(value, min), max); } export function readEnvInt(key: string) { const raw = process.env[key]; - if (!raw) return undefined; + if (!raw) { + return undefined; + } const parsed = Number.parseInt(raw, 10); return Number.isFinite(parsed) ? parsed : undefined; } @@ -165,7 +177,9 @@ export function chunkString(input: string, limit = CHUNK_LIMIT) { } export function truncateMiddle(str: string, max: number) { - if (str.length <= max) return str; + if (str.length <= max) { + return str; + } const half = Math.floor((max - 3) / 2); return `${sliceUtf16Safe(str, 0, half)}...${sliceUtf16Safe(str, -half)}`; } @@ -175,7 +189,9 @@ export function sliceLogLines( offset?: number, limit?: number, ): { slice: string; totalLines: number; totalChars: number } { - if (!text) return { slice: "", totalLines: 0, totalChars: 0 }; + if (!text) { + return { slice: "", totalLines: 0, totalChars: 0 }; + } const normalized = text.replace(/\r\n/g, "\n"); const lines = normalized.split("\n"); if (lines.length > 0 && lines[lines.length - 1] === "") { @@ -198,11 +214,17 @@ export function sliceLogLines( export function deriveSessionName(command: string): string | undefined { const tokens = tokenizeCommand(command); - if (tokens.length === 0) return undefined; + if (tokens.length === 0) { + return undefined; + } const verb = tokens[0]; let target = tokens.slice(1).find((t) => !t.startsWith("-")); - if (!target) target = tokens[1]; - if (!target) return verb; + if (!target) { + target = tokens[1]; + } + if (!target) { + return verb; + } const cleaned = truncateMiddle(stripQuotes(target), 48); return `${stripQuotes(verb)} ${cleaned}`; } @@ -224,15 +246,21 @@ function stripQuotes(value: string): string { } export function formatDuration(ms: number) { - if (ms < 1000) return `${ms}ms`; + if (ms < 1000) { + return `${ms}ms`; + } const seconds = Math.floor(ms / 1000); - if (seconds < 60) return `${seconds}s`; + if (seconds < 60) { + return `${seconds}s`; + } const minutes = Math.floor(seconds / 60); const rem = seconds % 60; return `${minutes}m${rem.toString().padStart(2, "0")}s`; } export function pad(str: string, width: number) { - if (str.length >= width) return str; + if (str.length >= width) { + return str; + } return str + " ".repeat(width - str.length); } diff --git a/src/agents/bash-tools.test.ts b/src/agents/bash-tools.test.ts index bfb7605ef7..e0e6bce26e 100644 --- a/src/agents/bash-tools.test.ts +++ b/src/agents/bash-tools.test.ts @@ -11,7 +11,9 @@ import { sanitizeBinaryOutput } from "./shell-utils.js"; const isWin = process.platform === "win32"; const resolveShellFromPath = (name: string) => { const envPath = process.env.PATH ?? ""; - if (!envPath) return undefined; + if (!envPath) { + return undefined; + } const entries = envPath.split(path.delimiter).filter(Boolean); for (const entry of entries) { const candidate = path.join(entry, name); @@ -71,11 +73,15 @@ describe("exec tool backgrounding", () => { const originalShell = process.env.SHELL; beforeEach(() => { - if (!isWin && defaultShell) process.env.SHELL = defaultShell; + if (!isWin && defaultShell) { + process.env.SHELL = defaultShell; + } }); afterEach(() => { - if (!isWin) process.env.SHELL = originalShell; + if (!isWin) { + process.env.SHELL = originalShell; + } }); it( @@ -301,12 +307,16 @@ describe("exec PATH handling", () => { const originalShell = process.env.SHELL; beforeEach(() => { - if (!isWin && defaultShell) process.env.SHELL = defaultShell; + if (!isWin && defaultShell) { + process.env.SHELL = defaultShell; + } }); afterEach(() => { process.env.PATH = originalPath; - if (!isWin) process.env.SHELL = originalShell; + if (!isWin) { + process.env.SHELL = originalShell; + } }); it("prepends configured path entries", async () => { diff --git a/src/agents/bedrock-discovery.ts b/src/agents/bedrock-discovery.ts index 70e495e5c3..43411e42ae 100644 --- a/src/agents/bedrock-discovery.ts +++ b/src/agents/bedrock-discovery.ts @@ -28,7 +28,9 @@ const discoveryCache = new Map(); let hasLoggedBedrockError = false; function normalizeProviderFilter(filter?: string[]): string[] { - if (!filter || filter.length === 0) return []; + if (!filter || filter.length === 0) { + return []; + } const normalized = new Set( filter.map((entry) => entry.trim().toLowerCase()).filter((entry) => entry.length > 0), ); @@ -59,10 +61,16 @@ function mapInputModalities(summary: BedrockModelSummary): Array<"text" | "image const mapped = new Set<"text" | "image">(); for (const modality of inputs) { const lower = modality.toLowerCase(); - if (lower === "text") mapped.add("text"); - if (lower === "image") mapped.add("image"); + if (lower === "text") { + mapped.add("text"); + } + if (lower === "image") { + mapped.add("image"); + } + } + if (mapped.size === 0) { + mapped.add("text"); } - if (mapped.size === 0) mapped.add("text"); return Array.from(mapped); } @@ -82,21 +90,35 @@ function resolveDefaultMaxTokens(config?: BedrockDiscoveryConfig): number { } function matchesProviderFilter(summary: BedrockModelSummary, filter: string[]): boolean { - if (filter.length === 0) return true; + if (filter.length === 0) { + return true; + } const providerName = summary.providerName ?? (typeof summary.modelId === "string" ? summary.modelId.split(".")[0] : undefined); const normalized = providerName?.trim().toLowerCase(); - if (!normalized) return false; + if (!normalized) { + return false; + } return filter.includes(normalized); } function shouldIncludeSummary(summary: BedrockModelSummary, filter: string[]): boolean { - if (!summary.modelId?.trim()) return false; - if (!matchesProviderFilter(summary, filter)) return false; - if (summary.responseStreamingSupported !== true) return false; - if (!includesTextModalities(summary.outputModalities)) return false; - if (!isActive(summary)) return false; + if (!summary.modelId?.trim()) { + return false; + } + if (!matchesProviderFilter(summary, filter)) { + return false; + } + if (summary.responseStreamingSupported !== true) { + return false; + } + if (!includesTextModalities(summary.outputModalities)) { + return false; + } + if (!isActive(summary)) { + return false; + } return true; } @@ -160,7 +182,9 @@ export async function discoverBedrockModels(params: { const response = await client.send(new ListFoundationModelsCommand({})); const discovered: ModelDefinitionConfig[] = []; for (const summary of response.modelSummaries ?? []) { - if (!shouldIncludeSummary(summary, providerFilter)) continue; + if (!shouldIncludeSummary(summary, providerFilter)) { + continue; + } discovered.push( toModelDefinition(summary, { contextWindow: defaultContextWindow, diff --git a/src/agents/bootstrap-files.ts b/src/agents/bootstrap-files.ts index 5c7931b262..99692c7ac6 100644 --- a/src/agents/bootstrap-files.ts +++ b/src/agents/bootstrap-files.ts @@ -12,7 +12,9 @@ export function makeBootstrapWarn(params: { sessionLabel: string; warn?: (message: string) => void; }): ((message: string) => void) | undefined { - if (!params.warn) return undefined; + if (!params.warn) { + return undefined; + } return (message: string) => params.warn?.(`${message} (sessionKey=${params.sessionLabel})`); } diff --git a/src/agents/cache-trace.ts b/src/agents/cache-trace.ts index 1ea62aee19..bde9ac7cce 100644 --- a/src/agents/cache-trace.ts +++ b/src/agents/cache-trace.ts @@ -104,7 +104,9 @@ function resolveCacheTraceConfig(params: CacheTraceInit): CacheTraceConfig { function getWriter(filePath: string): CacheTraceWriter { const existing = writers.get(filePath); - if (existing) return existing; + if (existing) { + return existing; + } const dir = path.dirname(filePath); const ready = fs.mkdir(dir, { recursive: true }).catch(() => undefined); @@ -125,10 +127,18 @@ function getWriter(filePath: string): CacheTraceWriter { } function stableStringify(value: unknown): string { - if (value === null || value === undefined) return String(value); - if (typeof value === "number" && !Number.isFinite(value)) return JSON.stringify(String(value)); - if (typeof value === "bigint") return JSON.stringify(value.toString()); - if (typeof value !== "object") return JSON.stringify(value) ?? "null"; + if (value === null || value === undefined) { + return String(value); + } + if (typeof value === "number" && !Number.isFinite(value)) { + return JSON.stringify(String(value)); + } + if (typeof value === "bigint") { + return JSON.stringify(value.toString()); + } + if (typeof value !== "object") { + return JSON.stringify(value) ?? "null"; + } if (value instanceof Error) { return stableStringify({ name: value.name, @@ -174,8 +184,12 @@ function summarizeMessages(messages: AgentMessage[]): { function safeJsonStringify(value: unknown): string | null { try { return JSON.stringify(value, (_key, val) => { - if (typeof val === "bigint") return val.toString(); - if (typeof val === "function") return "[Function]"; + if (typeof val === "bigint") { + return val.toString(); + } + if (typeof val === "function") { + return "[Function]"; + } if (val instanceof Error) { return { name: val.name, message: val.message, stack: val.stack }; } @@ -191,7 +205,9 @@ function safeJsonStringify(value: unknown): string | null { export function createCacheTrace(params: CacheTraceInit): CacheTrace | null { const cfg = resolveCacheTraceConfig(params); - if (!cfg.enabled) return null; + if (!cfg.enabled) { + return null; + } const writer = params.writer ?? getWriter(cfg.filePath); let seq = 0; @@ -221,8 +237,12 @@ export function createCacheTrace(params: CacheTraceInit): CacheTrace | null { event.system = payload.system; event.systemDigest = digest(payload.system); } - if (payload.options) event.options = payload.options; - if (payload.model) event.model = payload.model; + if (payload.options) { + event.options = payload.options; + } + if (payload.model) { + event.model = payload.model; + } const messages = payload.messages; if (Array.isArray(messages)) { @@ -236,11 +256,17 @@ export function createCacheTrace(params: CacheTraceInit): CacheTrace | null { } } - if (payload.note) event.note = payload.note; - if (payload.error) event.error = payload.error; + if (payload.note) { + event.note = payload.note; + } + if (payload.error) { + event.error = payload.error; + } const line = safeJsonStringify(event); - if (!line) return; + if (!line) { + return; + } writer.write(`${line}\n`); }; diff --git a/src/agents/channel-tools.ts b/src/agents/channel-tools.ts index 7d2ace4373..30fdb1bbad 100644 --- a/src/agents/channel-tools.ts +++ b/src/agents/channel-tools.ts @@ -17,9 +17,13 @@ export function listChannelSupportedActions(params: { cfg?: OpenClawConfig; channel?: string; }): ChannelMessageActionName[] { - if (!params.channel) return []; + if (!params.channel) { + return []; + } const plugin = getChannelPlugin(params.channel as Parameters[0]); - if (!plugin?.actions?.listActions) return []; + if (!plugin?.actions?.listActions) { + return []; + } const cfg = params.cfg ?? ({} as OpenClawConfig); return runPluginListActions(plugin, cfg); } @@ -32,7 +36,9 @@ export function listAllChannelSupportedActions(params: { }): ChannelMessageActionName[] { const actions = new Set(); for (const plugin of listChannelPlugins()) { - if (!plugin.actions?.listActions) continue; + if (!plugin.actions?.listActions) { + continue; + } const cfg = params.cfg ?? ({} as OpenClawConfig); const channelActions = runPluginListActions(plugin, cfg); for (const action of channelActions) { @@ -47,9 +53,13 @@ export function listChannelAgentTools(params: { cfg?: OpenClawConfig }): Channel const tools: ChannelAgentTool[] = []; for (const plugin of listChannelPlugins()) { const entry = plugin.agentTools; - if (!entry) continue; + if (!entry) { + continue; + } const resolved = typeof entry === "function" ? entry(params) : entry; - if (Array.isArray(resolved)) tools.push(...resolved); + if (Array.isArray(resolved)) { + tools.push(...resolved); + } } return tools; } @@ -60,10 +70,14 @@ export function resolveChannelMessageToolHints(params: { accountId?: string | null; }): string[] { const channelId = normalizeAnyChannelId(params.channel); - if (!channelId) return []; + if (!channelId) { + return []; + } const dock = getChannelDock(channelId); const resolve = dock?.agentPrompt?.messageToolHints; - if (!resolve) return []; + if (!resolve) { + return []; + } const cfg = params.cfg ?? ({} as OpenClawConfig); return (resolve({ cfg, accountId: params.accountId }) ?? []) .map((entry) => entry.trim()) @@ -76,7 +90,9 @@ function runPluginListActions( plugin: ChannelPlugin, cfg: OpenClawConfig, ): ChannelMessageActionName[] { - if (!plugin.actions?.listActions) return []; + if (!plugin.actions?.listActions) { + return []; + } try { const listed = plugin.actions.listActions({ cfg }); return Array.isArray(listed) ? listed : []; @@ -89,7 +105,9 @@ function runPluginListActions( function logListActionsError(pluginId: string, err: unknown) { const message = err instanceof Error ? err.message : String(err); const key = `${pluginId}:${message}`; - if (loggedListActionErrors.has(key)) return; + if (loggedListActionErrors.has(key)) { + return; + } loggedListActionErrors.add(key); const stack = err instanceof Error && err.stack ? err.stack : null; const details = stack ?? message; diff --git a/src/agents/chutes-oauth.test.ts b/src/agents/chutes-oauth.test.ts index 8881901b3f..55f90e4461 100644 --- a/src/agents/chutes-oauth.test.ts +++ b/src/agents/chutes-oauth.test.ts @@ -61,7 +61,9 @@ describe("chutes-oauth", () => { it("refreshes tokens using stored client id and falls back to old refresh token", async () => { const fetchFn: typeof fetch = async (input, init) => { const url = String(input); - if (url !== CHUTES_TOKEN_ENDPOINT) return new Response("not found", { status: 404 }); + if (url !== CHUTES_TOKEN_ENDPOINT) { + return new Response("not found", { status: 404 }); + } expect(init?.method).toBe("POST"); const body = init?.body as URLSearchParams; expect(String(body.get("grant_type"))).toBe("refresh_token"); diff --git a/src/agents/chutes-oauth.ts b/src/agents/chutes-oauth.ts index 4890cb8e66..8207e9dc85 100644 --- a/src/agents/chutes-oauth.ts +++ b/src/agents/chutes-oauth.ts @@ -39,13 +39,17 @@ export function parseOAuthCallbackInput( expectedState: string, ): { code: string; state: string } | { error: string } { const trimmed = input.trim(); - if (!trimmed) return { error: "No input provided" }; + if (!trimmed) { + return { error: "No input provided" }; + } try { const url = new URL(trimmed); const code = url.searchParams.get("code"); const state = url.searchParams.get("state"); - if (!code) return { error: "Missing 'code' parameter in URL" }; + if (!code) { + return { error: "Missing 'code' parameter in URL" }; + } if (!state) { return { error: "Missing 'state' parameter. Paste the full URL." }; } @@ -71,9 +75,13 @@ export async function fetchChutesUserInfo(params: { const response = await fetchFn(CHUTES_USERINFO_ENDPOINT, { headers: { Authorization: `Bearer ${params.accessToken}` }, }); - if (!response.ok) return null; + if (!response.ok) { + return null; + } const data = (await response.json()) as unknown; - if (!data || typeof data !== "object") return null; + if (!data || typeof data !== "object") { + return null; + } const typed = data as ChutesUserInfo; return typed; } @@ -119,7 +127,9 @@ export async function exchangeChutesCodeForTokens(params: { const refresh = data.refresh_token?.trim(); const expiresIn = data.expires_in ?? 0; - if (!access) throw new Error("Chutes token exchange returned no access_token"); + if (!access) { + throw new Error("Chutes token exchange returned no access_token"); + } if (!refresh) { throw new Error("Chutes token exchange returned no refresh_token"); } @@ -160,7 +170,9 @@ export async function refreshChutesTokens(params: { client_id: clientId, refresh_token: refreshToken, }); - if (clientSecret) body.set("client_secret", clientSecret); + if (clientSecret) { + body.set("client_secret", clientSecret); + } const response = await fetchFn(CHUTES_TOKEN_ENDPOINT, { method: "POST", @@ -181,7 +193,9 @@ export async function refreshChutesTokens(params: { const newRefresh = data.refresh_token?.trim(); const expiresIn = data.expires_in ?? 0; - if (!access) throw new Error("Chutes token refresh returned no access_token"); + if (!access) { + throw new Error("Chutes token refresh returned no access_token"); + } return { ...params.credential, diff --git a/src/agents/claude-cli-runner.test.ts b/src/agents/claude-cli-runner.test.ts index ad95c97c4f..6ec758e76b 100644 --- a/src/agents/claude-cli-runner.test.ts +++ b/src/agents/claude-cli-runner.test.ts @@ -20,7 +20,9 @@ function createDeferred() { async function waitForCalls(mockFn: { mock: { calls: unknown[][] } }, count: number) { for (let i = 0; i < 50; i += 1) { - if (mockFn.mock.calls.length >= count) return; + if (mockFn.mock.calls.length >= count) { + return; + } await new Promise((resolve) => setTimeout(resolve, 0)); } throw new Error(`Expected ${count} calls, got ${mockFn.mock.calls.length}`); diff --git a/src/agents/cli-backends.ts b/src/agents/cli-backends.ts index da120859b7..a747a724f0 100644 --- a/src/agents/cli-backends.ts +++ b/src/agents/cli-backends.ts @@ -83,13 +83,17 @@ function pickBackendConfig( normalizedId: string, ): CliBackendConfig | undefined { for (const [key, entry] of Object.entries(config)) { - if (normalizeBackendKey(key) === normalizedId) return entry; + if (normalizeBackendKey(key) === normalizedId) { + return entry; + } } return undefined; } function mergeBackendConfig(base: CliBackendConfig, override?: CliBackendConfig): CliBackendConfig { - if (!override) return { ...base }; + if (!override) { + return { ...base }; + } return { ...base, ...override, @@ -126,18 +130,26 @@ export function resolveCliBackendConfig( if (normalized === "claude-cli") { const merged = mergeBackendConfig(DEFAULT_CLAUDE_BACKEND, override); const command = merged.command?.trim(); - if (!command) return null; + if (!command) { + return null; + } return { id: normalized, config: { ...merged, command } }; } if (normalized === "codex-cli") { const merged = mergeBackendConfig(DEFAULT_CODEX_BACKEND, override); const command = merged.command?.trim(); - if (!command) return null; + if (!command) { + return null; + } return { id: normalized, config: { ...merged, command } }; } - if (!override) return null; + if (!override) { + return null; + } const command = override.command?.trim(); - if (!command) return null; + if (!command) { + return null; + } return { id: normalized, config: { ...override, command } }; } diff --git a/src/agents/cli-credentials.ts b/src/agents/cli-credentials.ts index 5ca5629ff8..a8095e0688 100644 --- a/src/agents/cli-credentials.ts +++ b/src/agents/cli-credentials.ts @@ -112,7 +112,9 @@ function readCodexKeychainCredentials(options?: { execSync?: ExecSyncFn; }): CodexCliCredential | null { const platform = options?.platform ?? process.platform; - if (platform !== "darwin") return null; + if (platform !== "darwin") { + return null; + } const execSyncImpl = options?.execSync ?? execSync; const codexHome = resolveCodexHomePath(); @@ -132,8 +134,12 @@ function readCodexKeychainCredentials(options?: { const tokens = parsed.tokens as Record | undefined; const accessToken = tokens?.access_token; const refreshToken = tokens?.refresh_token; - if (typeof accessToken !== "string" || !accessToken) return null; - if (typeof refreshToken !== "string" || !refreshToken) return null; + if (typeof accessToken !== "string" || !accessToken) { + return null; + } + if (typeof refreshToken !== "string" || !refreshToken) { + return null; + } // No explicit expiry stored; treat as fresh for an hour from last_refresh or now. const lastRefreshRaw = parsed.last_refresh; @@ -167,15 +173,23 @@ function readCodexKeychainCredentials(options?: { function readQwenCliCredentials(options?: { homeDir?: string }): QwenCliCredential | null { const credPath = resolveQwenCliCredentialsPath(options?.homeDir); const raw = loadJsonFile(credPath); - if (!raw || typeof raw !== "object") return null; + if (!raw || typeof raw !== "object") { + return null; + } const data = raw as Record; const accessToken = data.access_token; const refreshToken = data.refresh_token; const expiresAt = data.expiry_date; - if (typeof accessToken !== "string" || !accessToken) return null; - if (typeof refreshToken !== "string" || !refreshToken) return null; - if (typeof expiresAt !== "number" || !Number.isFinite(expiresAt)) return null; + if (typeof accessToken !== "string" || !accessToken) { + return null; + } + if (typeof refreshToken !== "string" || !refreshToken) { + return null; + } + if (typeof expiresAt !== "number" || !Number.isFinite(expiresAt)) { + return null; + } return { type: "oauth", @@ -197,14 +211,20 @@ function readClaudeCliKeychainCredentials( const data = JSON.parse(result.trim()); const claudeOauth = data?.claudeAiOauth; - if (!claudeOauth || typeof claudeOauth !== "object") return null; + if (!claudeOauth || typeof claudeOauth !== "object") { + return null; + } const accessToken = claudeOauth.accessToken; const refreshToken = claudeOauth.refreshToken; const expiresAt = claudeOauth.expiresAt; - if (typeof accessToken !== "string" || !accessToken) return null; - if (typeof expiresAt !== "number" || expiresAt <= 0) return null; + if (typeof accessToken !== "string" || !accessToken) { + return null; + } + if (typeof expiresAt !== "number" || expiresAt <= 0) { + return null; + } if (typeof refreshToken === "string" && refreshToken) { return { @@ -246,18 +266,26 @@ export function readClaudeCliCredentials(options?: { const credPath = resolveClaudeCliCredentialsPath(options?.homeDir); const raw = loadJsonFile(credPath); - if (!raw || typeof raw !== "object") return null; + if (!raw || typeof raw !== "object") { + return null; + } const data = raw as Record; const claudeOauth = data.claudeAiOauth as Record | undefined; - if (!claudeOauth || typeof claudeOauth !== "object") return null; + if (!claudeOauth || typeof claudeOauth !== "object") { + return null; + } const accessToken = claudeOauth.accessToken; const refreshToken = claudeOauth.refreshToken; const expiresAt = claudeOauth.expiresAt; - if (typeof accessToken !== "string" || !accessToken) return null; - if (typeof expiresAt !== "number" || expiresAt <= 0) return null; + if (typeof accessToken !== "string" || !accessToken) { + return null; + } + if (typeof expiresAt !== "number" || expiresAt <= 0) { + return null; + } if (typeof refreshToken === "string" && refreshToken) { return { @@ -362,11 +390,15 @@ export function writeClaudeCliFileCredentials( try { const raw = loadJsonFile(credPath); - if (!raw || typeof raw !== "object") return false; + if (!raw || typeof raw !== "object") { + return false; + } const data = raw as Record; const existingOauth = data.claudeAiOauth as Record | undefined; - if (!existingOauth || typeof existingOauth !== "object") return false; + if (!existingOauth || typeof existingOauth !== "object") { + return false; + } data.claudeAiOauth = { ...existingOauth, @@ -416,21 +448,31 @@ export function readCodexCliCredentials(options?: { platform: options?.platform, execSync: options?.execSync, }); - if (keychain) return keychain; + if (keychain) { + return keychain; + } const authPath = resolveCodexCliAuthPath(); const raw = loadJsonFile(authPath); - if (!raw || typeof raw !== "object") return null; + if (!raw || typeof raw !== "object") { + return null; + } const data = raw as Record; const tokens = data.tokens as Record | undefined; - if (!tokens || typeof tokens !== "object") return null; + if (!tokens || typeof tokens !== "object") { + return null; + } const accessToken = tokens.access_token; const refreshToken = tokens.refresh_token; - if (typeof accessToken !== "string" || !accessToken) return null; - if (typeof refreshToken !== "string" || !refreshToken) return null; + if (typeof accessToken !== "string" || !accessToken) { + return null; + } + if (typeof refreshToken !== "string" || !refreshToken) { + return null; + } let expires: number; try { diff --git a/src/agents/cli-runner.ts b/src/agents/cli-runner.ts index 3d9e12dece..910a9da00f 100644 --- a/src/agents/cli-runner.ts +++ b/src/agents/cli-runner.ts @@ -228,12 +228,20 @@ export async function runCliAgent(params: { const stdout = result.stdout.trim(); const stderr = result.stderr.trim(); if (logOutputText) { - if (stdout) log.info(`cli stdout:\n${stdout}`); - if (stderr) log.info(`cli stderr:\n${stderr}`); + if (stdout) { + log.info(`cli stdout:\n${stdout}`); + } + if (stderr) { + log.info(`cli stderr:\n${stderr}`); + } } if (shouldLogVerbose()) { - if (stdout) log.debug(`cli stdout:\n${stdout}`); - if (stderr) log.debug(`cli stderr:\n${stderr}`); + if (stdout) { + log.debug(`cli stdout:\n${stdout}`); + } + if (stderr) { + log.debug(`cli stderr:\n${stderr}`); + } } if (result.code !== 0) { @@ -278,7 +286,9 @@ export async function runCliAgent(params: { }, }; } catch (err) { - if (err instanceof FailoverError) throw err; + if (err instanceof FailoverError) { + throw err; + } const message = err instanceof Error ? err.message : String(err); if (isFailoverErrorMessage(message)) { const reason = classifyFailoverReason(message) ?? "unknown"; diff --git a/src/agents/cli-runner/helpers.ts b/src/agents/cli-runner/helpers.ts index de3268974d..7a39eaa974 100644 --- a/src/agents/cli-runner/helpers.ts +++ b/src/agents/cli-runner/helpers.ts @@ -25,19 +25,29 @@ export async function cleanupResumeProcesses( backend: CliBackendConfig, sessionId: string, ): Promise { - if (process.platform === "win32") return; + if (process.platform === "win32") { + return; + } const resumeArgs = backend.resumeArgs ?? []; - if (resumeArgs.length === 0) return; - if (!resumeArgs.some((arg) => arg.includes("{sessionId}"))) return; + if (resumeArgs.length === 0) { + return; + } + if (!resumeArgs.some((arg) => arg.includes("{sessionId}"))) { + return; + } const commandToken = path.basename(backend.command ?? "").trim(); - if (!commandToken) return; + if (!commandToken) { + return; + } const resumeTokens = resumeArgs.map((arg) => arg.replaceAll("{sessionId}", sessionId)); const pattern = [commandToken, ...resumeTokens] .filter(Boolean) .map((token) => escapeRegex(token)) .join(".*"); - if (!pattern) return; + if (!pattern) { + return; + } try { await runExec("pkill", ["-f", pattern]); @@ -48,14 +58,18 @@ export async function cleanupResumeProcesses( function buildSessionMatchers(backend: CliBackendConfig): RegExp[] { const commandToken = path.basename(backend.command ?? "").trim(); - if (!commandToken) return []; + if (!commandToken) { + return []; + } const matchers: RegExp[] = []; const sessionArg = backend.sessionArg?.trim(); const sessionArgs = backend.sessionArgs ?? []; const resumeArgs = backend.resumeArgs ?? []; const addMatcher = (args: string[]) => { - if (args.length === 0) return; + if (args.length === 0) { + return; + } const tokens = [commandToken, ...args]; const pattern = tokens .map((token, index) => { @@ -80,7 +94,9 @@ function buildSessionMatchers(backend: CliBackendConfig): RegExp[] { } function tokenToRegex(token: string): string { - if (!token.includes("{sessionId}")) return escapeRegex(token); + if (!token.includes("{sessionId}")) { + return escapeRegex(token); + } const parts = token.split("{sessionId}").map((part) => escapeRegex(part)); return parts.join("\\S+"); } @@ -93,24 +109,38 @@ export async function cleanupSuspendedCliProcesses( backend: CliBackendConfig, threshold = 10, ): Promise { - if (process.platform === "win32") return; + if (process.platform === "win32") { + return; + } const matchers = buildSessionMatchers(backend); - if (matchers.length === 0) return; + if (matchers.length === 0) { + return; + } try { const { stdout } = await runExec("ps", ["-ax", "-o", "pid=,stat=,command="]); const suspended: number[] = []; for (const line of stdout.split("\n")) { const trimmed = line.trim(); - if (!trimmed) continue; + if (!trimmed) { + continue; + } const match = /^(\d+)\s+(\S+)\s+(.*)$/.exec(trimmed); - if (!match) continue; + if (!match) { + continue; + } const pid = Number(match[1]); const stat = match[2] ?? ""; const command = match[3] ?? ""; - if (!Number.isFinite(pid)) continue; - if (!stat.includes("T")) continue; - if (!matchers.some((matcher) => matcher.test(command))) continue; + if (!Number.isFinite(pid)) { + continue; + } + if (!stat.includes("T")) { + continue; + } + if (!matchers.some((matcher) => matcher.test(command))) { + continue; + } suspended.push(pid); } @@ -153,9 +183,13 @@ function buildModelAliasLines(cfg?: OpenClawConfig) { const entries: Array<{ alias: string; model: string }> = []; for (const [keyRaw, entryRaw] of Object.entries(models)) { const model = String(keyRaw ?? "").trim(); - if (!model) continue; + if (!model) { + continue; + } const alias = String((entryRaw as { alias?: string } | undefined)?.alias ?? "").trim(); - if (!alias) continue; + if (!alias) { + continue; + } entries.push({ alias, model }); } return entries @@ -217,12 +251,18 @@ export function buildSystemPrompt(params: { export function normalizeCliModel(modelId: string, backend: CliBackendConfig): string { const trimmed = modelId.trim(); - if (!trimmed) return trimmed; + if (!trimmed) { + return trimmed; + } const direct = backend.modelAliases?.[trimmed]; - if (direct) return direct; + if (direct) { + return direct; + } const lower = trimmed.toLowerCase(); const mapped = backend.modelAliases?.[lower]; - if (mapped) return mapped; + if (mapped) { + return mapped; + } return trimmed; } @@ -235,7 +275,9 @@ function toUsage(raw: Record): CliUsage | undefined { pick("cache_read_input_tokens") ?? pick("cached_input_tokens") ?? pick("cacheRead"); const cacheWrite = pick("cache_write_input_tokens") ?? pick("cacheWrite"); const total = pick("total_tokens") ?? pick("total"); - if (!input && !output && !cacheRead && !cacheWrite && !total) return undefined; + if (!input && !output && !cacheRead && !cacheWrite && !total) { + return undefined; + } return { input, output, cacheRead, cacheWrite, total }; } @@ -244,15 +286,30 @@ function isRecord(value: unknown): value is Record { } function collectText(value: unknown): string { - if (!value) return ""; - if (typeof value === "string") return value; - if (Array.isArray(value)) return value.map((entry) => collectText(entry)).join(""); - if (!isRecord(value)) return ""; - if (typeof value.text === "string") return value.text; - if (typeof value.content === "string") return value.content; - if (Array.isArray(value.content)) + if (!value) { + return ""; + } + if (typeof value === "string") { + return value; + } + if (Array.isArray(value)) { + return value.map((entry) => collectText(entry)).join(""); + } + if (!isRecord(value)) { + return ""; + } + if (typeof value.text === "string") { + return value.text; + } + if (typeof value.content === "string") { + return value.content; + } + if (Array.isArray(value.content)) { return value.content.map((entry) => collectText(entry)).join(""); - if (isRecord(value.message)) return collectText(value.message); + } + if (isRecord(value.message)) { + return collectText(value.message); + } return ""; } @@ -268,21 +325,27 @@ function pickSessionId( ]; for (const field of fields) { const value = parsed[field]; - if (typeof value === "string" && value.trim()) return value.trim(); + if (typeof value === "string" && value.trim()) { + return value.trim(); + } } return undefined; } export function parseCliJson(raw: string, backend: CliBackendConfig): CliOutput | null { const trimmed = raw.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } let parsed: unknown; try { parsed = JSON.parse(trimmed); } catch { return null; } - if (!isRecord(parsed)) return null; + if (!isRecord(parsed)) { + return null; + } const sessionId = pickSessionId(parsed, backend); const usage = isRecord(parsed.usage) ? toUsage(parsed.usage) : undefined; const text = @@ -298,7 +361,9 @@ export function parseCliJsonl(raw: string, backend: CliBackendConfig): CliOutput .split(/\r?\n/g) .map((line) => line.trim()) .filter(Boolean); - if (lines.length === 0) return null; + if (lines.length === 0) { + return null; + } let sessionId: string | undefined; let usage: CliUsage | undefined; const texts: string[] = []; @@ -309,8 +374,12 @@ export function parseCliJsonl(raw: string, backend: CliBackendConfig): CliOutput } catch { continue; } - if (!isRecord(parsed)) continue; - if (!sessionId) sessionId = pickSessionId(parsed, backend); + if (!isRecord(parsed)) { + continue; + } + if (!sessionId) { + sessionId = pickSessionId(parsed, backend); + } if (!sessionId && typeof parsed.thread_id === "string") { sessionId = parsed.thread_id.trim(); } @@ -326,7 +395,9 @@ export function parseCliJsonl(raw: string, backend: CliBackendConfig): CliOutput } } const text = texts.join("\n").trim(); - if (!text) return null; + if (!text) { + return null; + } return { text, sessionId, usage }; } @@ -336,11 +407,19 @@ export function resolveSystemPromptUsage(params: { systemPrompt?: string; }): string | null { const systemPrompt = params.systemPrompt?.trim(); - if (!systemPrompt) return null; + if (!systemPrompt) { + return null; + } const when = params.backend.systemPromptWhen ?? "first"; - if (when === "never") return null; - if (when === "first" && !params.isNewSession) return null; - if (!params.backend.systemPromptArg?.trim()) return null; + if (when === "never") { + return null; + } + if (when === "first" && !params.isNewSession) { + return null; + } + if (!params.backend.systemPromptArg?.trim()) { + return null; + } return systemPrompt; } @@ -350,9 +429,15 @@ export function resolveSessionIdToSend(params: { }): { sessionId?: string; isNew: boolean } { const mode = params.backend.sessionMode ?? "always"; const existing = params.cliSessionId?.trim(); - if (mode === "none") return { sessionId: undefined, isNew: !existing }; - if (mode === "existing") return { sessionId: existing, isNew: !existing }; - if (existing) return { sessionId: existing, isNew: false }; + if (mode === "none") { + return { sessionId: undefined, isNew: !existing }; + } + if (mode === "existing") { + return { sessionId: existing, isNew: !existing }; + } + if (existing) { + return { sessionId: existing, isNew: false }; + } return { sessionId: crypto.randomUUID(), isNew: true }; } @@ -372,15 +457,25 @@ export function resolvePromptInput(params: { backend: CliBackendConfig; prompt: function resolveImageExtension(mimeType: string): string { const normalized = mimeType.toLowerCase(); - if (normalized.includes("png")) return "png"; - if (normalized.includes("jpeg") || normalized.includes("jpg")) return "jpg"; - if (normalized.includes("gif")) return "gif"; - if (normalized.includes("webp")) return "webp"; + if (normalized.includes("png")) { + return "png"; + } + if (normalized.includes("jpeg") || normalized.includes("jpg")) { + return "jpg"; + } + if (normalized.includes("gif")) { + return "gif"; + } + if (normalized.includes("webp")) { + return "webp"; + } return "bin"; } export function appendImagePathsToPrompt(prompt: string, paths: string[]): string { - if (!paths.length) return prompt; + if (!paths.length) { + return prompt; + } const trimmed = prompt.trimEnd(); const separator = trimmed ? "\n\n" : ""; return `${trimmed}${separator}${paths.join("\n")}`; diff --git a/src/agents/cli-session.ts b/src/agents/cli-session.ts index e1df7cf9b2..1c9df998ce 100644 --- a/src/agents/cli-session.ts +++ b/src/agents/cli-session.ts @@ -5,13 +5,19 @@ export function getCliSessionId( entry: SessionEntry | undefined, provider: string, ): string | undefined { - if (!entry) return undefined; + if (!entry) { + return undefined; + } const normalized = normalizeProviderId(provider); const fromMap = entry.cliSessionIds?.[normalized]; - if (fromMap?.trim()) return fromMap.trim(); + if (fromMap?.trim()) { + return fromMap.trim(); + } if (normalized === "claude-cli") { const legacy = entry.claudeCliSessionId?.trim(); - if (legacy) return legacy; + if (legacy) { + return legacy; + } } return undefined; } @@ -19,7 +25,9 @@ export function getCliSessionId( export function setCliSessionId(entry: SessionEntry, provider: string, sessionId: string): void { const normalized = normalizeProviderId(provider); const trimmed = sessionId.trim(); - if (!trimmed) return; + if (!trimmed) { + return; + } const existing = entry.cliSessionIds ?? {}; entry.cliSessionIds = { ...existing }; entry.cliSessionIds[normalized] = trimmed; diff --git a/src/agents/compaction.ts b/src/agents/compaction.ts index a884473076..1cebfe28d9 100644 --- a/src/agents/compaction.ts +++ b/src/agents/compaction.ts @@ -18,7 +18,9 @@ export function estimateMessagesTokens(messages: AgentMessage[]): number { } function normalizeParts(parts: number, messageCount: number): number { - if (!Number.isFinite(parts) || parts <= 1) return 1; + if (!Number.isFinite(parts) || parts <= 1) { + return 1; + } return Math.min(Math.max(1, Math.floor(parts)), Math.max(1, messageCount)); } @@ -26,9 +28,13 @@ export function splitMessagesByTokenShare( messages: AgentMessage[], parts = DEFAULT_PARTS, ): AgentMessage[][] { - if (messages.length === 0) return []; + if (messages.length === 0) { + return []; + } const normalizedParts = normalizeParts(parts, messages.length); - if (normalizedParts <= 1) return [messages]; + if (normalizedParts <= 1) { + return [messages]; + } const totalTokens = estimateMessagesTokens(messages); const targetTokens = totalTokens / normalizedParts; @@ -63,7 +69,9 @@ export function chunkMessagesByMaxTokens( messages: AgentMessage[], maxTokens: number, ): AgentMessage[][] { - if (messages.length === 0) return []; + if (messages.length === 0) { + return []; + } const chunks: AgentMessage[][] = []; let currentChunk: AgentMessage[] = []; @@ -100,7 +108,9 @@ export function chunkMessagesByMaxTokens( * When messages are large, we use smaller chunks to avoid exceeding model limits. */ export function computeAdaptiveChunkRatio(messages: AgentMessage[], contextWindow: number): number { - if (messages.length === 0) return BASE_CHUNK_RATIO; + if (messages.length === 0) { + return BASE_CHUNK_RATIO; + } const totalTokens = estimateMessagesTokens(messages); const avgTokens = totalTokens / messages.length; @@ -320,7 +330,9 @@ export function pruneHistoryForContextShare(params: { while (keptMessages.length > 0 && estimateMessagesTokens(keptMessages) > budgetTokens) { const chunks = splitMessagesByTokenShare(keptMessages, parts); - if (chunks.length <= 1) break; + if (chunks.length <= 1) { + break; + } const [dropped, ...rest] = chunks; droppedChunks += 1; droppedMessages += dropped.length; diff --git a/src/agents/context-window-guard.ts b/src/agents/context-window-guard.ts index 5650e3d313..bd32e0ab83 100644 --- a/src/agents/context-window-guard.ts +++ b/src/agents/context-window-guard.ts @@ -11,7 +11,9 @@ export type ContextWindowInfo = { }; function normalizePositiveInt(value: unknown): number | null { - if (typeof value !== "number" || !Number.isFinite(value)) return null; + if (typeof value !== "number" || !Number.isFinite(value)) { + return null; + } const int = Math.floor(value); return int > 0 ? int : null; } @@ -24,7 +26,9 @@ export function resolveContextWindowInfo(params: { defaultTokens: number; }): ContextWindowInfo { const fromModel = normalizePositiveInt(params.modelContextWindow); - if (fromModel) return { tokens: fromModel, source: "model" }; + if (fromModel) { + return { tokens: fromModel, source: "model" }; + } const fromModelsConfig = (() => { const providers = params.cfg?.models?.providers as @@ -35,10 +39,14 @@ export function resolveContextWindowInfo(params: { const match = models.find((m) => m?.id === params.modelId); return normalizePositiveInt(match?.contextWindow); })(); - if (fromModelsConfig) return { tokens: fromModelsConfig, source: "modelsConfig" }; + if (fromModelsConfig) { + return { tokens: fromModelsConfig, source: "modelsConfig" }; + } const fromAgentConfig = normalizePositiveInt(params.cfg?.agents?.defaults?.contextTokens); - if (fromAgentConfig) return { tokens: fromAgentConfig, source: "agentContextTokens" }; + if (fromAgentConfig) { + return { tokens: fromAgentConfig, source: "agentContextTokens" }; + } return { tokens: Math.floor(params.defaultTokens), source: "default" }; } diff --git a/src/agents/context.ts b/src/agents/context.ts index 8d78088d95..b3683e235f 100644 --- a/src/agents/context.ts +++ b/src/agents/context.ts @@ -18,7 +18,9 @@ const loadPromise = (async () => { const modelRegistry = discoverModels(authStorage, agentDir); const models = modelRegistry.getAll() as ModelEntry[]; for (const m of models) { - if (!m?.id) continue; + if (!m?.id) { + continue; + } if (typeof m.contextWindow === "number" && m.contextWindow > 0) { MODEL_CACHE.set(m.id, m.contextWindow); } @@ -29,7 +31,9 @@ const loadPromise = (async () => { })(); export function lookupContextTokens(modelId?: string): number | undefined { - if (!modelId) return undefined; + if (!modelId) { + return undefined; + } // Best-effort: kick off loading, but don't block. void loadPromise; return MODEL_CACHE.get(modelId); diff --git a/src/agents/date-time.ts b/src/agents/date-time.ts index 1ee5c2d5b2..dde0788de2 100644 --- a/src/agents/date-time.ts +++ b/src/agents/date-time.ts @@ -20,8 +20,12 @@ export function resolveUserTimezone(configured?: string): string { } export function resolveUserTimeFormat(preference?: TimeFormatPreference): ResolvedTimeFormat { - if (preference === "12" || preference === "24") return preference; - if (cachedTimeFormat) return cachedTimeFormat; + if (preference === "12" || preference === "24") { + return preference; + } + if (cachedTimeFormat) { + return cachedTimeFormat; + } cachedTimeFormat = detectSystemTimeFormat() ? "24" : "12"; return cachedTimeFormat; } @@ -29,7 +33,9 @@ export function resolveUserTimeFormat(preference?: TimeFormatPreference): Resolv export function normalizeTimestamp( raw: unknown, ): { timestampMs: number; timestampUtc: string } | undefined { - if (raw == null) return undefined; + if (raw == null) { + return undefined; + } let timestampMs: number | undefined; if (raw instanceof Date) { @@ -38,7 +44,9 @@ export function normalizeTimestamp( timestampMs = raw < 1_000_000_000_000 ? Math.round(raw * 1000) : Math.round(raw); } else if (typeof raw === "string") { const trimmed = raw.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } if (/^\d+(\.\d+)?$/.test(trimmed)) { const num = Number(trimmed); if (Number.isFinite(num)) { @@ -52,11 +60,15 @@ export function normalizeTimestamp( } } else { const parsed = Date.parse(trimmed); - if (!Number.isNaN(parsed)) timestampMs = parsed; + if (!Number.isNaN(parsed)) { + timestampMs = parsed; + } } } - if (timestampMs === undefined || !Number.isFinite(timestampMs)) return undefined; + if (timestampMs === undefined || !Number.isFinite(timestampMs)) { + return undefined; + } return { timestampMs, timestampUtc: new Date(timestampMs).toISOString() }; } @@ -65,7 +77,9 @@ export function withNormalizedTimestamp>( rawTimestamp: unknown, ): T & { timestampMs?: number; timestampUtc?: string } { const normalized = normalizeTimestamp(rawTimestamp); - if (!normalized) return value; + if (!normalized) { + return value; + } return { ...value, timestampMs: @@ -86,8 +100,12 @@ function detectSystemTimeFormat(): boolean { encoding: "utf8", timeout: 500, }).trim(); - if (result === "1") return true; - if (result === "0") return false; + if (result === "1") { + return true; + } + if (result === "0") { + return false; + } } catch { // Not set, fall through } @@ -99,8 +117,12 @@ function detectSystemTimeFormat(): boolean { 'powershell -Command "(Get-Culture).DateTimeFormat.ShortTimePattern"', { encoding: "utf8", timeout: 1000 }, ).trim(); - if (result.startsWith("H")) return true; - if (result.startsWith("h")) return false; + if (result.startsWith("H")) { + return true; + } + if (result.startsWith("h")) { + return false; + } } catch { // Fall through } @@ -116,7 +138,9 @@ function detectSystemTimeFormat(): boolean { } function ordinalSuffix(day: number): string { - if (day >= 11 && day <= 13) return "th"; + if (day >= 11 && day <= 13) { + return "th"; + } switch (day % 10) { case 1: return "st"; @@ -148,10 +172,13 @@ export function formatUserTime( }).formatToParts(date); const map: Record = {}; for (const part of parts) { - if (part.type !== "literal") map[part.type] = part.value; + if (part.type !== "literal") { + map[part.type] = part.value; + } } - if (!map.weekday || !map.year || !map.month || !map.day || !map.hour || !map.minute) + if (!map.weekday || !map.year || !map.month || !map.day || !map.hour || !map.minute) { return undefined; + } const dayNum = parseInt(map.day, 10); const suffix = ordinalSuffix(dayNum); const timePart = use24Hour diff --git a/src/agents/docs-path.ts b/src/agents/docs-path.ts index 60500e66b8..61fdfdabc3 100644 --- a/src/agents/docs-path.ts +++ b/src/agents/docs-path.ts @@ -12,7 +12,9 @@ export async function resolveOpenClawDocsPath(params: { const workspaceDir = params.workspaceDir?.trim(); if (workspaceDir) { const workspaceDocs = path.join(workspaceDir, "docs"); - if (fs.existsSync(workspaceDocs)) return workspaceDocs; + if (fs.existsSync(workspaceDocs)) { + return workspaceDocs; + } } const packageRoot = await resolveOpenClawPackageRoot({ @@ -20,7 +22,9 @@ export async function resolveOpenClawDocsPath(params: { argv1: params.argv1, moduleUrl: params.moduleUrl, }); - if (!packageRoot) return null; + if (!packageRoot) { + return null; + } const packageDocs = path.join(packageRoot, "docs"); return fs.existsSync(packageDocs) ? packageDocs : null; diff --git a/src/agents/failover-error.ts b/src/agents/failover-error.ts index 5026394f39..3a100c324d 100644 --- a/src/agents/failover-error.ts +++ b/src/agents/failover-error.ts @@ -56,11 +56,15 @@ export function resolveFailoverStatus(reason: FailoverReason): number | undefine } function getStatusCode(err: unknown): number | undefined { - if (!err || typeof err !== "object") return undefined; + if (!err || typeof err !== "object") { + return undefined; + } const candidate = (err as { status?: unknown; statusCode?: unknown }).status ?? (err as { statusCode?: unknown }).statusCode; - if (typeof candidate === "number") return candidate; + if (typeof candidate === "number") { + return candidate; + } if (typeof candidate === "string" && /^\d+$/.test(candidate)) { return Number(candidate); } @@ -68,67 +72,107 @@ function getStatusCode(err: unknown): number | undefined { } function getErrorName(err: unknown): string { - if (!err || typeof err !== "object") return ""; + if (!err || typeof err !== "object") { + return ""; + } return "name" in err ? String(err.name) : ""; } function getErrorCode(err: unknown): string | undefined { - if (!err || typeof err !== "object") return undefined; + if (!err || typeof err !== "object") { + return undefined; + } const candidate = (err as { code?: unknown }).code; - if (typeof candidate !== "string") return undefined; + if (typeof candidate !== "string") { + return undefined; + } const trimmed = candidate.trim(); return trimmed ? trimmed : undefined; } function getErrorMessage(err: unknown): string { - if (err instanceof Error) return err.message; - if (typeof err === "string") return err; + if (err instanceof Error) { + return err.message; + } + if (typeof err === "string") { + return err; + } if (typeof err === "number" || typeof err === "boolean" || typeof err === "bigint") { return String(err); } - if (typeof err === "symbol") return err.description ?? ""; + if (typeof err === "symbol") { + return err.description ?? ""; + } if (err && typeof err === "object") { const message = (err as { message?: unknown }).message; - if (typeof message === "string") return message; + if (typeof message === "string") { + return message; + } } return ""; } function hasTimeoutHint(err: unknown): boolean { - if (!err) return false; - if (getErrorName(err) === "TimeoutError") return true; + if (!err) { + return false; + } + if (getErrorName(err) === "TimeoutError") { + return true; + } const message = getErrorMessage(err); return Boolean(message && TIMEOUT_HINT_RE.test(message)); } export function isTimeoutError(err: unknown): boolean { - if (hasTimeoutHint(err)) return true; - if (!err || typeof err !== "object") return false; - if (getErrorName(err) !== "AbortError") return false; + if (hasTimeoutHint(err)) { + return true; + } + if (!err || typeof err !== "object") { + return false; + } + if (getErrorName(err) !== "AbortError") { + return false; + } const message = getErrorMessage(err); - if (message && ABORT_TIMEOUT_RE.test(message)) return true; + if (message && ABORT_TIMEOUT_RE.test(message)) { + return true; + } const cause = "cause" in err ? (err as { cause?: unknown }).cause : undefined; const reason = "reason" in err ? (err as { reason?: unknown }).reason : undefined; return hasTimeoutHint(cause) || hasTimeoutHint(reason); } export function resolveFailoverReasonFromError(err: unknown): FailoverReason | null { - if (isFailoverError(err)) return err.reason; + if (isFailoverError(err)) { + return err.reason; + } const status = getStatusCode(err); - if (status === 402) return "billing"; - if (status === 429) return "rate_limit"; - if (status === 401 || status === 403) return "auth"; - if (status === 408) return "timeout"; + if (status === 402) { + return "billing"; + } + if (status === 429) { + return "rate_limit"; + } + if (status === 401 || status === 403) { + return "auth"; + } + if (status === 408) { + return "timeout"; + } const code = (getErrorCode(err) ?? "").toUpperCase(); if (["ETIMEDOUT", "ESOCKETTIMEDOUT", "ECONNRESET", "ECONNABORTED"].includes(code)) { return "timeout"; } - if (isTimeoutError(err)) return "timeout"; + if (isTimeoutError(err)) { + return "timeout"; + } const message = getErrorMessage(err); - if (!message) return null; + if (!message) { + return null; + } return classifyFailoverReason(message); } @@ -163,9 +207,13 @@ export function coerceToFailoverError( profileId?: string; }, ): FailoverError | null { - if (isFailoverError(err)) return err; + if (isFailoverError(err)) { + return err; + } const reason = resolveFailoverReasonFromError(err); - if (!reason) return null; + if (!reason) { + return null; + } const message = getErrorMessage(err) || String(err); const status = getStatusCode(err) ?? resolveFailoverStatus(reason); diff --git a/src/agents/identity-avatar.ts b/src/agents/identity-avatar.ts index 2b42f09728..f70e271a0b 100644 --- a/src/agents/identity-avatar.ts +++ b/src/agents/identity-avatar.ts @@ -22,7 +22,9 @@ function normalizeAvatarValue(value: string | undefined | null): string | null { function resolveAvatarSource(cfg: OpenClawConfig, agentId: string): string | null { const fromConfig = normalizeAvatarValue(resolveAgentIdentity(cfg, agentId)?.avatar); - if (fromConfig) return fromConfig; + if (fromConfig) { + return fromConfig; + } const workspace = resolveAgentWorkspaceDir(cfg, agentId); const fromIdentity = normalizeAvatarValue(loadAgentIdentityFromWorkspace(workspace)?.avatar); return fromIdentity; @@ -47,7 +49,9 @@ function resolveExistingPath(value: string): string { function isPathWithin(root: string, target: string): boolean { const relative = path.relative(root, target); - if (!relative) return true; + if (!relative) { + return true; + } return !relative.startsWith("..") && !path.isAbsolute(relative); } diff --git a/src/agents/identity-file.ts b/src/agents/identity-file.ts index 1d07492a44..8b9ca21399 100644 --- a/src/agents/identity-file.ts +++ b/src/agents/identity-file.ts @@ -42,20 +42,38 @@ export function parseIdentityMarkdown(content: string): AgentIdentityFile { for (const line of lines) { const cleaned = line.trim().replace(/^\s*-\s*/, ""); const colonIndex = cleaned.indexOf(":"); - if (colonIndex === -1) continue; + if (colonIndex === -1) { + continue; + } const label = cleaned.slice(0, colonIndex).replace(/[*_]/g, "").trim().toLowerCase(); const value = cleaned .slice(colonIndex + 1) .replace(/^[*_]+|[*_]+$/g, "") .trim(); - if (!value) continue; - if (isIdentityPlaceholder(value)) continue; - if (label === "name") identity.name = value; - if (label === "emoji") identity.emoji = value; - if (label === "creature") identity.creature = value; - if (label === "vibe") identity.vibe = value; - if (label === "theme") identity.theme = value; - if (label === "avatar") identity.avatar = value; + if (!value) { + continue; + } + if (isIdentityPlaceholder(value)) { + continue; + } + if (label === "name") { + identity.name = value; + } + if (label === "emoji") { + identity.emoji = value; + } + if (label === "creature") { + identity.creature = value; + } + if (label === "vibe") { + identity.vibe = value; + } + if (label === "theme") { + identity.theme = value; + } + if (label === "avatar") { + identity.avatar = value; + } } return identity; } @@ -75,7 +93,9 @@ export function loadIdentityFromFile(identityPath: string): AgentIdentityFile | try { const content = fs.readFileSync(identityPath, "utf-8"); const parsed = parseIdentityMarkdown(content); - if (!identityHasValues(parsed)) return null; + if (!identityHasValues(parsed)) { + return null; + } return parsed; } catch { return null; diff --git a/src/agents/identity.ts b/src/agents/identity.ts index 7c0a87cc38..72305fc191 100644 --- a/src/agents/identity.ts +++ b/src/agents/identity.ts @@ -12,7 +12,9 @@ export function resolveAgentIdentity( export function resolveAckReaction(cfg: OpenClawConfig, agentId: string): string { const configured = cfg.messages?.ackReaction; - if (configured !== undefined) return configured.trim(); + if (configured !== undefined) { + return configured.trim(); + } const emoji = resolveAgentIdentity(cfg, agentId)?.emoji?.trim(); return emoji || DEFAULT_ACK_REACTION; } @@ -22,7 +24,9 @@ export function resolveIdentityNamePrefix( agentId: string, ): string | undefined { const name = resolveAgentIdentity(cfg, agentId)?.name?.trim(); - if (!name) return undefined; + if (!name) { + return undefined; + } return `[${name}]`; } @@ -37,10 +41,14 @@ export function resolveMessagePrefix( opts?: { configured?: string; hasAllowFrom?: boolean; fallback?: string }, ): string { const configured = opts?.configured ?? cfg.messages?.messagePrefix; - if (configured !== undefined) return configured; + if (configured !== undefined) { + return configured; + } const hasAllowFrom = opts?.hasAllowFrom === true; - if (hasAllowFrom) return ""; + if (hasAllowFrom) { + return ""; + } return resolveIdentityNamePrefix(cfg, agentId) ?? opts?.fallback ?? "[openclaw]"; } @@ -76,7 +84,9 @@ export function resolveHumanDelayConfig( ): HumanDelayConfig | undefined { const defaults = cfg.agents?.defaults?.humanDelay; const overrides = resolveAgentConfig(cfg, agentId)?.humanDelay; - if (!defaults && !overrides) return undefined; + if (!defaults && !overrides) { + return undefined; + } return { mode: overrides?.mode ?? defaults?.mode, minMs: overrides?.minMs ?? defaults?.minMs, diff --git a/src/agents/live-auth-keys.ts b/src/agents/live-auth-keys.ts index 37aaec9cc9..8266d4a1b5 100644 --- a/src/agents/live-auth-keys.ts +++ b/src/agents/live-auth-keys.ts @@ -1,7 +1,9 @@ const KEY_SPLIT_RE = /[\s,;]+/g; function parseKeyList(raw?: string | null): string[] { - if (!raw) return []; + if (!raw) { + return []; + } return raw .split(KEY_SPLIT_RE) .map((value) => value.trim()) @@ -11,9 +13,13 @@ function parseKeyList(raw?: string | null): string[] { function collectEnvPrefixedKeys(prefix: string): string[] { const keys: string[] = []; for (const [name, value] of Object.entries(process.env)) { - if (!name.startsWith(prefix)) continue; + if (!name.startsWith(prefix)) { + continue; + } const trimmed = value?.trim(); - if (!trimmed) continue; + if (!trimmed) { + continue; + } keys.push(trimmed); } return keys; @@ -21,7 +27,9 @@ function collectEnvPrefixedKeys(prefix: string): string[] { export function collectAnthropicApiKeys(): string[] { const forcedSingle = process.env.OPENCLAW_LIVE_ANTHROPIC_KEY?.trim(); - if (forcedSingle) return [forcedSingle]; + if (forcedSingle) { + return [forcedSingle]; + } const fromList = parseKeyList(process.env.OPENCLAW_LIVE_ANTHROPIC_KEYS); const fromEnv = collectEnvPrefixedKeys("ANTHROPIC_API_KEY"); @@ -29,33 +37,61 @@ export function collectAnthropicApiKeys(): string[] { const seen = new Set(); const add = (value?: string) => { - if (!value) return; - if (seen.has(value)) return; + if (!value) { + return; + } + if (seen.has(value)) { + return; + } seen.add(value); }; - for (const value of fromList) add(value); - if (primary) add(primary); - for (const value of fromEnv) add(value); + for (const value of fromList) { + add(value); + } + if (primary) { + add(primary); + } + for (const value of fromEnv) { + add(value); + } return Array.from(seen); } export function isAnthropicRateLimitError(message: string): boolean { const lower = message.toLowerCase(); - if (lower.includes("rate_limit")) return true; - if (lower.includes("rate limit")) return true; - if (lower.includes("429")) return true; + if (lower.includes("rate_limit")) { + return true; + } + if (lower.includes("rate limit")) { + return true; + } + if (lower.includes("429")) { + return true; + } return false; } export function isAnthropicBillingError(message: string): boolean { const lower = message.toLowerCase(); - if (lower.includes("credit balance")) return true; - if (lower.includes("insufficient credit")) return true; - if (lower.includes("insufficient credits")) return true; - if (lower.includes("payment required")) return true; - if (lower.includes("billing") && lower.includes("disabled")) return true; - if (lower.includes("402")) return true; + if (lower.includes("credit balance")) { + return true; + } + if (lower.includes("insufficient credit")) { + return true; + } + if (lower.includes("insufficient credits")) { + return true; + } + if (lower.includes("payment required")) { + return true; + } + if (lower.includes("billing") && lower.includes("disabled")) { + return true; + } + if (lower.includes("402")) { + return true; + } return false; } diff --git a/src/agents/live-model-filter.ts b/src/agents/live-model-filter.ts index 6d15d77f1e..5871cf55a0 100644 --- a/src/agents/live-model-filter.ts +++ b/src/agents/live-model-filter.ts @@ -32,7 +32,9 @@ function matchesAny(id: string, values: string[]): boolean { export function isModernModelRef(ref: ModelRef): boolean { const provider = ref.provider?.trim().toLowerCase() ?? ""; const id = ref.id?.trim().toLowerCase() ?? ""; - if (!provider || !id) return false; + if (!provider || !id) { + return false; + } if (provider === "anthropic") { return matchesPrefix(id, ANTHROPIC_PREFIXES); diff --git a/src/agents/memory-search.ts b/src/agents/memory-search.ts index 3d3f3c324f..50f3f302dd 100644 --- a/src/agents/memory-search.ts +++ b/src/agents/memory-search.ts @@ -94,17 +94,25 @@ function normalizeSources( const normalized = new Set<"memory" | "sessions">(); const input = sources?.length ? sources : DEFAULT_SOURCES; for (const source of input) { - if (source === "memory") normalized.add("memory"); - if (source === "sessions" && sessionMemoryEnabled) normalized.add("sessions"); + if (source === "memory") { + normalized.add("memory"); + } + if (source === "sessions" && sessionMemoryEnabled) { + normalized.add("sessions"); + } + } + if (normalized.size === 0) { + normalized.add("memory"); } - if (normalized.size === 0) normalized.add("memory"); return Array.from(normalized); } function resolveStorePath(agentId: string, raw?: string): string { const stateDir = resolveStateDir(process.env, os.homedir); const fallback = path.join(stateDir, "memory", `${agentId}.sqlite`); - if (!raw) return fallback; + if (!raw) { + return fallback; + } const withToken = raw.includes("{agentId}") ? raw.replaceAll("{agentId}", agentId) : raw; return resolveUserPath(withToken); } @@ -286,6 +294,8 @@ export function resolveMemorySearchConfig( const defaults = cfg.agents?.defaults?.memorySearch; const overrides = resolveAgentConfig(cfg, agentId)?.memorySearch; const resolved = mergeConfig(defaults, overrides, agentId); - if (!resolved.enabled) return null; + if (!resolved.enabled) { + return null; + } return resolved; } diff --git a/src/agents/minimax-vlm.ts b/src/agents/minimax-vlm.ts index 4244877adf..c7077173a4 100644 --- a/src/agents/minimax-vlm.ts +++ b/src/agents/minimax-vlm.ts @@ -45,11 +45,17 @@ export async function minimaxUnderstandImage(params: { modelBaseUrl?: string; }): Promise { const apiKey = params.apiKey.trim(); - if (!apiKey) throw new Error("MiniMax VLM: apiKey required"); + if (!apiKey) { + throw new Error("MiniMax VLM: apiKey required"); + } const prompt = params.prompt.trim(); - if (!prompt) throw new Error("MiniMax VLM: prompt required"); + if (!prompt) { + throw new Error("MiniMax VLM: prompt required"); + } const imageDataUrl = params.imageDataUrl.trim(); - if (!imageDataUrl) throw new Error("MiniMax VLM: imageDataUrl required"); + if (!imageDataUrl) { + throw new Error("MiniMax VLM: imageDataUrl required"); + } if (!/^data:image\/(png|jpeg|webp);base64,/i.test(imageDataUrl)) { throw new Error("MiniMax VLM: imageDataUrl must be a base64 data:image/(png|jpeg|webp) URL"); } diff --git a/src/agents/model-auth.ts b/src/agents/model-auth.ts index 1f88ddc7f4..28f0620690 100644 --- a/src/agents/model-auth.ts +++ b/src/agents/model-auth.ts @@ -28,7 +28,9 @@ function resolveProviderConfig( ): ModelProviderConfig | undefined { const providers = cfg?.models?.providers ?? {}; const direct = providers[provider] as ModelProviderConfig | undefined; - if (direct) return direct; + if (direct) { + return direct; + } const normalized = normalizeProviderId(provider); if (normalized === provider) { const matched = Object.entries(providers).find( @@ -74,11 +76,15 @@ function resolveEnvSourceLabel(params: { } export function resolveAwsSdkEnvVarName(env: NodeJS.ProcessEnv = process.env): string | undefined { - if (env[AWS_BEARER_ENV]?.trim()) return AWS_BEARER_ENV; + if (env[AWS_BEARER_ENV]?.trim()) { + return AWS_BEARER_ENV; + } if (env[AWS_ACCESS_KEY_ENV]?.trim() && env[AWS_SECRET_KEY_ENV]?.trim()) { return AWS_ACCESS_KEY_ENV; } - if (env[AWS_PROFILE_ENV]?.trim()) return AWS_PROFILE_ENV; + if (env[AWS_PROFILE_ENV]?.trim()) { + return AWS_PROFILE_ENV; + } return undefined; } @@ -232,7 +238,9 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null { const applied = new Set(getShellEnvAppliedKeys()); const pick = (envVar: string): EnvApiKeyResult | null => { const value = process.env[envVar]?.trim(); - if (!value) return null; + if (!value) { + return null; + } const source = applied.has(envVar) ? `shell env: ${envVar}` : `env: ${envVar}`; return { apiKey: value, source }; }; @@ -255,7 +263,9 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null { if (normalized === "google-vertex") { const envKey = getEnvApiKey(normalized); - if (!envKey) return null; + if (!envKey) { + return null; + } return { apiKey: envKey, source: "gcloud adc" }; } @@ -289,7 +299,9 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null { opencode: "OPENCODE_API_KEY", }; const envVar = envMap[normalized]; - if (!envVar) return null; + if (!envVar) { + return null; + } return pick(envVar); } @@ -299,10 +311,14 @@ export function resolveModelAuthMode( store?: AuthProfileStore, ): ModelAuthMode | undefined { const resolved = provider?.trim(); - if (!resolved) return undefined; + if (!resolved) { + return undefined; + } const authOverride = resolveProviderAuthOverride(cfg, resolved); - if (authOverride === "aws-sdk") return "aws-sdk"; + if (authOverride === "aws-sdk") { + return "aws-sdk"; + } const authStore = store ?? ensureAuthProfileStore(); const profiles = listProfilesForProvider(authStore, resolved); @@ -315,10 +331,18 @@ export function resolveModelAuthMode( const distinct = ["oauth", "token", "api_key"].filter((k) => modes.has(k as "oauth" | "token" | "api_key"), ); - if (distinct.length >= 2) return "mixed"; - if (modes.has("oauth")) return "oauth"; - if (modes.has("token")) return "token"; - if (modes.has("api_key")) return "api-key"; + if (distinct.length >= 2) { + return "mixed"; + } + if (modes.has("oauth")) { + return "oauth"; + } + if (modes.has("token")) { + return "token"; + } + if (modes.has("api_key")) { + return "api-key"; + } } if (authOverride === undefined && normalizeProviderId(resolved) === "amazon-bedrock") { @@ -330,7 +354,9 @@ export function resolveModelAuthMode( return envKey.source.includes("OAUTH_TOKEN") ? "oauth" : "api-key"; } - if (getCustomProviderApiKey(cfg, resolved)) return "api-key"; + if (getCustomProviderApiKey(cfg, resolved)) { + return "api-key"; + } return "unknown"; } @@ -355,6 +381,8 @@ export async function getApiKeyForModel(params: { export function requireApiKey(auth: ResolvedProviderAuth, provider: string): string { const key = auth.apiKey?.trim(); - if (key) return key; + if (key) { + return key; + } throw new Error(`No API key resolved for provider "${provider}" (auth mode: ${auth.mode}).`); } diff --git a/src/agents/model-catalog.ts b/src/agents/model-catalog.ts index 791980875b..3ae2a12045 100644 --- a/src/agents/model-catalog.ts +++ b/src/agents/model-catalog.ts @@ -45,14 +45,18 @@ export async function loadModelCatalog(params?: { if (params?.useCache === false) { modelCatalogPromise = null; } - if (modelCatalogPromise) return modelCatalogPromise; + if (modelCatalogPromise) { + return modelCatalogPromise; + } modelCatalogPromise = (async () => { const models: ModelCatalogEntry[] = []; const sortModels = (entries: ModelCatalogEntry[]) => entries.sort((a, b) => { const p = a.provider.localeCompare(b.provider); - if (p !== 0) return p; + if (p !== 0) { + return p; + } return a.name.localeCompare(b.name); }); try { @@ -74,9 +78,13 @@ export async function loadModelCatalog(params?: { const entries = Array.isArray(registry) ? registry : registry.getAll(); for (const entry of entries) { const id = String(entry?.id ?? "").trim(); - if (!id) continue; + if (!id) { + continue; + } const provider = String(entry?.provider ?? "").trim(); - if (!provider) continue; + if (!provider) { + continue; + } const name = String(entry?.name ?? id).trim() || id; const contextWindow = typeof entry?.contextWindow === "number" && entry.contextWindow > 0 diff --git a/src/agents/model-compat.ts b/src/agents/model-compat.ts index 3874526ecf..e7b428e844 100644 --- a/src/agents/model-compat.ts +++ b/src/agents/model-compat.ts @@ -7,11 +7,15 @@ function isOpenAiCompletionsModel(model: Model): model is Model<"openai-com export function normalizeModelCompat(model: Model): Model { const baseUrl = model.baseUrl ?? ""; 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; const compat = openaiModel.compat ?? undefined; - if (compat?.supportsDeveloperRole === false) return model; + if (compat?.supportsDeveloperRole === false) { + return model; + } openaiModel.compat = compat ? { ...compat, supportsDeveloperRole: false } diff --git a/src/agents/model-fallback.test.ts b/src/agents/model-fallback.test.ts index d6aea4782b..497e08756c 100644 --- a/src/agents/model-fallback.test.ts +++ b/src/agents/model-fallback.test.ts @@ -158,7 +158,9 @@ describe("runWithModelFallback", () => { }, }); const run = vi.fn().mockImplementation(async (providerId, modelId) => { - if (providerId === "fallback") return "ok"; + if (providerId === "fallback") { + return "ok"; + } throw new Error(`unexpected provider: ${providerId}/${modelId}`); }); @@ -219,7 +221,9 @@ describe("runWithModelFallback", () => { }, }); const run = vi.fn().mockImplementation(async (providerId) => { - if (providerId === provider) return "ok"; + if (providerId === provider) { + return "ok"; + } return "unexpected"; }); diff --git a/src/agents/model-fallback.ts b/src/agents/model-fallback.ts index 89a35f4dd7..2ad35b3993 100644 --- a/src/agents/model-fallback.ts +++ b/src/agents/model-fallback.ts @@ -35,8 +35,12 @@ type FallbackAttempt = { }; function isAbortError(err: unknown): boolean { - if (!err || typeof err !== "object") return false; - if (isFailoverError(err)) return false; + if (!err || typeof err !== "object") { + return false; + } + if (isFailoverError(err)) { + return false; + } const name = "name" in err ? String(err.name) : ""; // Only treat explicit AbortError names as user aborts. // Message-based checks (e.g., "aborted") can mask timeouts and skip fallback. @@ -55,11 +59,15 @@ function buildAllowedModelKeys( const modelMap = cfg?.agents?.defaults?.models ?? {}; return Object.keys(modelMap); })(); - if (rawAllowlist.length === 0) return null; + if (rawAllowlist.length === 0) { + return null; + } const keys = new Set(); for (const raw of rawAllowlist) { const parsed = parseModelRef(String(raw ?? ""), defaultProvider); - if (!parsed) continue; + if (!parsed) { + continue; + } keys.add(modelKey(parsed.provider, parsed.model)); } return keys.size > 0 ? keys : null; @@ -79,10 +87,16 @@ function resolveImageFallbackCandidates(params: { const candidates: ModelCandidate[] = []; const addCandidate = (candidate: ModelCandidate, enforceAllowlist: boolean) => { - if (!candidate.provider || !candidate.model) return; + if (!candidate.provider || !candidate.model) { + return; + } const key = modelKey(candidate.provider, candidate.model); - if (seen.has(key)) return; - if (enforceAllowlist && allowlist && !allowlist.has(key)) return; + if (seen.has(key)) { + return; + } + if (enforceAllowlist && allowlist && !allowlist.has(key)) { + return; + } seen.add(key); candidates.push(candidate); }; @@ -93,7 +107,9 @@ function resolveImageFallbackCandidates(params: { defaultProvider: params.defaultProvider, aliasIndex, }); - if (!resolved) return; + if (!resolved) { + return; + } addCandidate(resolved.ref, enforceAllowlist); }; @@ -105,7 +121,9 @@ function resolveImageFallbackCandidates(params: { | string | undefined; const primary = typeof imageModel === "string" ? imageModel.trim() : imageModel?.primary; - if (primary?.trim()) addRaw(primary, false); + if (primary?.trim()) { + addRaw(primary, false); + } } const imageFallbacks = (() => { @@ -153,10 +171,16 @@ function resolveFallbackCandidates(params: { const candidates: ModelCandidate[] = []; const addCandidate = (candidate: ModelCandidate, enforceAllowlist: boolean) => { - if (!candidate.provider || !candidate.model) return; + if (!candidate.provider || !candidate.model) { + return; + } const key = modelKey(candidate.provider, candidate.model); - if (seen.has(key)) return; - if (enforceAllowlist && allowlist && !allowlist.has(key)) return; + if (seen.has(key)) { + return; + } + if (enforceAllowlist && allowlist && !allowlist.has(key)) { + return; + } seen.add(key); candidates.push(candidate); }; @@ -164,12 +188,16 @@ function resolveFallbackCandidates(params: { addCandidate({ provider, model }, false); const modelFallbacks = (() => { - if (params.fallbacksOverride !== undefined) return params.fallbacksOverride; + if (params.fallbacksOverride !== undefined) { + return params.fallbacksOverride; + } const model = params.cfg?.agents?.defaults?.model as | { fallbacks?: string[] } | string | undefined; - if (model && typeof model === "object") return model.fallbacks ?? []; + if (model && typeof model === "object") { + return model.fallbacks ?? []; + } return []; })(); @@ -179,7 +207,9 @@ function resolveFallbackCandidates(params: { defaultProvider, aliasIndex, }); - if (!resolved) continue; + if (!resolved) { + continue; + } addCandidate(resolved.ref, true); } @@ -253,13 +283,17 @@ export async function runWithModelFallback(params: { attempts, }; } catch (err) { - if (shouldRethrowAbort(err)) throw err; + if (shouldRethrowAbort(err)) { + throw err; + } const normalized = coerceToFailoverError(err, { provider: candidate.provider, model: candidate.model, }) ?? err; - if (!isFailoverError(normalized)) throw err; + if (!isFailoverError(normalized)) { + throw err; + } lastError = normalized; const described = describeFailoverError(normalized); @@ -281,7 +315,9 @@ export async function runWithModelFallback(params: { } } - if (attempts.length <= 1 && lastError) throw lastError; + if (attempts.length <= 1 && lastError) { + throw lastError; + } const summary = attempts.length > 0 ? attempts @@ -340,7 +376,9 @@ export async function runWithImageModelFallback(params: { attempts, }; } catch (err) { - if (shouldRethrowAbort(err)) throw err; + if (shouldRethrowAbort(err)) { + throw err; + } lastError = err; attempts.push({ provider: candidate.provider, @@ -357,7 +395,9 @@ export async function runWithImageModelFallback(params: { } } - if (attempts.length <= 1 && lastError) throw lastError; + if (attempts.length <= 1 && lastError) { + throw lastError; + } const summary = attempts.length > 0 ? attempts diff --git a/src/agents/model-scan.ts b/src/agents/model-scan.ts index 47d90f92d7..aba84d74a1 100644 --- a/src/agents/model-scan.ts +++ b/src/agents/model-scan.ts @@ -85,9 +85,15 @@ export type OpenRouterScanOptions = { type OpenAIModel = Model<"openai-completions">; function normalizeCreatedAtMs(value: unknown): number | null { - if (typeof value !== "number" || !Number.isFinite(value)) return null; - if (value <= 0) return null; - if (value > 1e12) return Math.round(value); + if (typeof value !== "number" || !Number.isFinite(value)) { + return null; + } + if (value <= 0) { + return null; + } + if (value > 1e12) { + return Math.round(value); + } return Math.round(value * 1000); } @@ -97,16 +103,24 @@ function inferParamBFromIdOrName(text: string): number | null { let best: number | null = null; for (const match of matches) { const numRaw = match[1]; - if (!numRaw) continue; + if (!numRaw) { + continue; + } const value = Number(numRaw); - if (!Number.isFinite(value) || value <= 0) continue; - if (best === null || value > best) best = value; + if (!Number.isFinite(value) || value <= 0) { + continue; + } + if (best === null || value > best) { + best = value; + } } return best; } function parseModality(modality: string | null): Array<"text" | "image"> { - if (!modality) return ["text"]; + if (!modality) { + return ["text"]; + } const normalized = modality.toLowerCase(); const parts = normalized.split(/[^a-z]+/).filter(Boolean); const hasImage = parts.includes("image"); @@ -114,17 +128,27 @@ function parseModality(modality: string | null): Array<"text" | "image"> { } function parseNumberString(value: unknown): number | null { - if (typeof value === "number" && Number.isFinite(value)) return value; - if (typeof value !== "string") return null; + if (typeof value === "number" && Number.isFinite(value)) { + return value; + } + if (typeof value !== "string") { + return null; + } const trimmed = value.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const num = Number(trimmed); - if (!Number.isFinite(num)) return null; + if (!Number.isFinite(num)) { + return null; + } return num; } function parseOpenRouterPricing(value: unknown): OpenRouterModelPricing | null { - if (!value || typeof value !== "object") return null; + if (!value || typeof value !== "object") { + return null; + } const obj = value as Record; const prompt = parseNumberString(obj.prompt); const completion = parseNumberString(obj.completion); @@ -133,7 +157,9 @@ function parseOpenRouterPricing(value: unknown): OpenRouterModelPricing | null { const webSearch = parseNumberString(obj.web_search) ?? 0; const internalReasoning = parseNumberString(obj.internal_reasoning) ?? 0; - if (prompt === null || completion === null) return null; + if (prompt === null || completion === null) { + return null; + } return { prompt, completion, @@ -145,8 +171,12 @@ function parseOpenRouterPricing(value: unknown): OpenRouterModelPricing | null { } function isFreeOpenRouterModel(entry: OpenRouterModelMeta): boolean { - if (entry.id.endsWith(":free")) return true; - if (!entry.pricing) return false; + if (entry.id.endsWith(":free")) { + return true; + } + if (!entry.pricing) { + return false; + } return entry.pricing.prompt === 0 && entry.pricing.completion === 0; } @@ -175,10 +205,14 @@ async function fetchOpenRouterModels(fetchImpl: typeof fetch): Promise { - if (!entry || typeof entry !== "object") return null; + if (!entry || typeof entry !== "object") { + return null; + } const obj = entry as Record; const id = typeof obj.id === "string" ? obj.id.trim() : ""; - if (!id) return null; + if (!id) { + return null; + } const name = typeof obj.name === "string" && obj.name.trim() ? obj.name.trim() : id; const contextLength = @@ -311,7 +345,9 @@ async function probeImage( } function ensureImageInput(model: OpenAIModel): OpenAIModel { - if (model.input.includes("image")) return model; + if (model.input.includes("image")) { + return model; + } return { ...model, input: Array.from(new Set([...model.input, "image"])), @@ -333,7 +369,9 @@ async function mapWithConcurrency( while (true) { const current = nextIndex; nextIndex += 1; - if (current >= items.length) return; + if (current >= items.length) { + return; + } results[current] = await fn(items[current], current); completed += 1; opts?.onProgress?.(completed, items.length); @@ -369,19 +407,27 @@ export async function scanOpenRouterModels( const now = Date.now(); const filtered = catalog.filter((entry) => { - if (!isFreeOpenRouterModel(entry)) return false; + if (!isFreeOpenRouterModel(entry)) { + return false; + } if (providerFilter) { const prefix = entry.id.split("/")[0]?.toLowerCase() ?? ""; - if (prefix !== providerFilter) return false; + if (prefix !== providerFilter) { + return false; + } } if (minParamB > 0) { const params = entry.inferredParamB ?? 0; - if (params < minParamB) return false; + if (params < minParamB) { + return false; + } } if (maxAgeDays > 0 && entry.createdAtMs) { const ageMs = now - entry.createdAtMs; const ageDays = ageMs / (24 * 60 * 60 * 1000); - if (ageDays > maxAgeDays) return false; + if (ageDays > maxAgeDays) { + return false; + } } return true; }); diff --git a/src/agents/model-selection.ts b/src/agents/model-selection.ts index 40f2224e01..82a4537946 100644 --- a/src/agents/model-selection.ts +++ b/src/agents/model-selection.ts @@ -26,39 +26,63 @@ export function modelKey(provider: string, model: string) { export function normalizeProviderId(provider: string): string { const normalized = provider.trim().toLowerCase(); - if (normalized === "z.ai" || normalized === "z-ai") return "zai"; - if (normalized === "opencode-zen") return "opencode"; - if (normalized === "qwen") return "qwen-portal"; - if (normalized === "kimi-code") return "kimi-coding"; + if (normalized === "z.ai" || normalized === "z-ai") { + return "zai"; + } + if (normalized === "opencode-zen") { + return "opencode"; + } + if (normalized === "qwen") { + return "qwen-portal"; + } + if (normalized === "kimi-code") { + return "kimi-coding"; + } return normalized; } export function isCliProvider(provider: string, cfg?: OpenClawConfig): boolean { const normalized = normalizeProviderId(provider); - if (normalized === "claude-cli") return true; - if (normalized === "codex-cli") return true; + if (normalized === "claude-cli") { + return true; + } + if (normalized === "codex-cli") { + return true; + } const backends = cfg?.agents?.defaults?.cliBackends ?? {}; return Object.keys(backends).some((key) => normalizeProviderId(key) === normalized); } function normalizeAnthropicModelId(model: string): string { const trimmed = model.trim(); - if (!trimmed) return trimmed; + if (!trimmed) { + return trimmed; + } const lower = trimmed.toLowerCase(); - if (lower === "opus-4.5") return "claude-opus-4-5"; - if (lower === "sonnet-4.5") return "claude-sonnet-4-5"; + if (lower === "opus-4.5") { + return "claude-opus-4-5"; + } + if (lower === "sonnet-4.5") { + return "claude-sonnet-4-5"; + } return trimmed; } function normalizeProviderModelId(provider: string, model: string): string { - if (provider === "anthropic") return normalizeAnthropicModelId(model); - if (provider === "google") return normalizeGoogleModelId(model); + if (provider === "anthropic") { + return normalizeAnthropicModelId(model); + } + if (provider === "google") { + return normalizeGoogleModelId(model); + } return model; } export function parseModelRef(raw: string, defaultProvider: string): ModelRef | null { const trimmed = raw.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const slash = trimmed.indexOf("/"); if (slash === -1) { const provider = normalizeProviderId(defaultProvider); @@ -68,7 +92,9 @@ export function parseModelRef(raw: string, defaultProvider: string): ModelRef | const providerRaw = trimmed.slice(0, slash).trim(); const provider = normalizeProviderId(providerRaw); const model = trimmed.slice(slash + 1).trim(); - if (!provider || !model) return null; + if (!provider || !model) { + return null; + } const normalizedModel = normalizeProviderModelId(provider, model); return { provider, model: normalizedModel }; } @@ -83,9 +109,13 @@ export function buildModelAliasIndex(params: { const rawModels = params.cfg.agents?.defaults?.models ?? {}; for (const [keyRaw, entryRaw] of Object.entries(rawModels)) { const parsed = parseModelRef(String(keyRaw ?? ""), params.defaultProvider); - if (!parsed) continue; + if (!parsed) { + continue; + } const alias = String((entryRaw as { alias?: string } | undefined)?.alias ?? "").trim(); - if (!alias) continue; + if (!alias) { + continue; + } const aliasKey = normalizeAliasKey(alias); byAlias.set(aliasKey, { alias, ref: parsed }); const key = modelKey(parsed.provider, parsed.model); @@ -103,7 +133,9 @@ export function resolveModelRefFromString(params: { aliasIndex?: ModelAliasIndex; }): { ref: ModelRef; alias?: string } | null { const trimmed = params.raw.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } if (!trimmed.includes("/")) { const aliasKey = normalizeAliasKey(trimmed); const aliasMatch = params.aliasIndex?.byAlias.get(aliasKey); @@ -112,7 +144,9 @@ export function resolveModelRefFromString(params: { } } const parsed = parseModelRef(trimmed, params.defaultProvider); - if (!parsed) return null; + if (!parsed) { + return null; + } return { ref: parsed }; } @@ -123,7 +157,9 @@ export function resolveConfiguredModelRef(params: { }): ModelRef { const rawModel = (() => { const raw = params.cfg.agents?.defaults?.model as { primary?: string } | string | undefined; - if (typeof raw === "string") return raw.trim(); + if (typeof raw === "string") { + return raw.trim(); + } return raw?.primary?.trim() ?? ""; })(); if (rawModel) { @@ -135,7 +171,9 @@ export function resolveConfiguredModelRef(params: { if (!trimmed.includes("/")) { const aliasKey = normalizeAliasKey(trimmed); const aliasMatch = aliasIndex.byAlias.get(aliasKey); - if (aliasMatch) return aliasMatch.ref; + if (aliasMatch) { + return aliasMatch.ref; + } // Default to anthropic if no provider is specified, but warn as this is deprecated. console.warn( @@ -149,7 +187,9 @@ export function resolveConfiguredModelRef(params: { defaultProvider: params.defaultProvider, aliasIndex, }); - if (resolved) return resolved.ref; + if (resolved) { + return resolved.ref; + } } return { provider: params.defaultProvider, model: params.defaultModel }; } @@ -209,7 +249,9 @@ export function buildAllowedModelSet(params: { const catalogKeys = new Set(params.catalog.map((entry) => modelKey(entry.provider, entry.id))); if (allowAny) { - if (defaultKey) catalogKeys.add(defaultKey); + if (defaultKey) { + catalogKeys.add(defaultKey); + } return { allowAny: true, allowedCatalog: params.catalog, @@ -221,7 +263,9 @@ export function buildAllowedModelSet(params: { const configuredProviders = (params.cfg.models?.providers ?? {}) as Record; for (const raw of rawAllowlist) { const parsed = parseModelRef(String(raw), params.defaultProvider); - if (!parsed) continue; + if (!parsed) { + continue; + } const key = modelKey(parsed.provider, parsed.model); const providerKey = normalizeProviderId(parsed.provider); if (isCliProvider(parsed.provider, params.cfg)) { @@ -244,7 +288,9 @@ export function buildAllowedModelSet(params: { ); if (allowedCatalog.length === 0 && allowedKeys.size === 0) { - if (defaultKey) catalogKeys.add(defaultKey); + if (defaultKey) { + catalogKeys.add(defaultKey); + } return { allowAny: true, allowedCatalog: params.catalog, @@ -296,7 +342,9 @@ export function resolveAllowedModelRef(params: { error: string; } { const trimmed = params.raw.trim(); - if (!trimmed) return { error: "invalid model: empty" }; + if (!trimmed) { + return { error: "invalid model: empty" }; + } const aliasIndex = buildModelAliasIndex({ cfg: params.cfg, @@ -307,7 +355,9 @@ export function resolveAllowedModelRef(params: { defaultProvider: params.defaultProvider, aliasIndex, }); - if (!resolved) return { error: `invalid model: ${trimmed}` }; + if (!resolved) { + return { error: `invalid model: ${trimmed}` }; + } const status = getModelRefStatus({ cfg: params.cfg, @@ -330,11 +380,15 @@ export function resolveThinkingDefault(params: { catalog?: ModelCatalogEntry[]; }): ThinkLevel { const configured = params.cfg.agents?.defaults?.thinkingDefault; - if (configured) return configured; + if (configured) { + return configured; + } const candidate = params.catalog?.find( (entry) => entry.provider === params.provider && entry.id === params.model, ); - if (candidate?.reasoning) return "low"; + if (candidate?.reasoning) { + return "low"; + } return "off"; } @@ -347,7 +401,9 @@ export function resolveHooksGmailModel(params: { defaultProvider: string; }): ModelRef | null { const hooksModel = params.cfg.hooks?.gmail?.model; - if (!hooksModel?.trim()) return null; + if (!hooksModel?.trim()) { + return null; + } const aliasIndex = buildModelAliasIndex({ cfg: params.cfg, diff --git a/src/agents/models-config.falls-back-default-baseurl-token-exchange-fails.test.ts b/src/agents/models-config.falls-back-default-baseurl-token-exchange-fails.test.ts index fe1257bf26..7117785874 100644 --- a/src/agents/models-config.falls-back-default-baseurl-token-exchange-fails.test.ts +++ b/src/agents/models-config.falls-back-default-baseurl-token-exchange-fails.test.ts @@ -126,12 +126,21 @@ describe("models-config", () => { expect(parsed.providers["github-copilot"]?.baseUrl).toBe("https://api.copilot.example"); } finally { - if (previous === undefined) delete process.env.COPILOT_GITHUB_TOKEN; - else process.env.COPILOT_GITHUB_TOKEN = previous; - if (previousGh === undefined) delete process.env.GH_TOKEN; - else process.env.GH_TOKEN = previousGh; - if (previousGithub === undefined) delete process.env.GITHUB_TOKEN; - else process.env.GITHUB_TOKEN = previousGithub; + if (previous === undefined) { + delete process.env.COPILOT_GITHUB_TOKEN; + } else { + process.env.COPILOT_GITHUB_TOKEN = previous; + } + if (previousGh === undefined) { + delete process.env.GH_TOKEN; + } else { + process.env.GH_TOKEN = previousGh; + } + if (previousGithub === undefined) { + delete process.env.GITHUB_TOKEN; + } else { + process.env.GITHUB_TOKEN = previousGithub; + } } }); }); diff --git a/src/agents/models-config.fills-missing-provider-apikey-from-env-var.test.ts b/src/agents/models-config.fills-missing-provider-apikey-from-env-var.test.ts index 5a96f2a281..35f8779b49 100644 --- a/src/agents/models-config.fills-missing-provider-apikey-from-env-var.test.ts +++ b/src/agents/models-config.fills-missing-provider-apikey-from-env-var.test.ts @@ -85,8 +85,11 @@ describe("models-config", () => { const ids = parsed.providers.minimax?.models?.map((model) => model.id); expect(ids).toContain("MiniMax-VL-01"); } finally { - if (prevKey === undefined) delete process.env.MINIMAX_API_KEY; - else process.env.MINIMAX_API_KEY = prevKey; + if (prevKey === undefined) { + delete process.env.MINIMAX_API_KEY; + } else { + process.env.MINIMAX_API_KEY = prevKey; + } } }); }); diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index ec81c74384..5a04dad120 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -135,7 +135,9 @@ function normalizeApiKeyConfig(value: string): string { function resolveEnvApiKeyVarName(provider: string): string | undefined { const resolved = resolveEnvApiKey(provider); - if (!resolved) return undefined; + if (!resolved) { + return undefined; + } const match = /^(?:env: |shell env: )([A-Z0-9_]+)$/.exec(resolved.source); return match ? match[1] : undefined; } @@ -151,16 +153,26 @@ function resolveApiKeyFromProfiles(params: { const ids = listProfilesForProvider(params.store, params.provider); for (const id of ids) { const cred = params.store.profiles[id]; - if (!cred) continue; - if (cred.type === "api_key") return cred.key; - if (cred.type === "token") return cred.token; + if (!cred) { + continue; + } + if (cred.type === "api_key") { + return cred.key; + } + if (cred.type === "token") { + return cred.token; + } } return undefined; } export function normalizeGoogleModelId(id: string): string { - if (id === "gemini-3-pro") return "gemini-3-pro-preview"; - if (id === "gemini-3-flash") return "gemini-3-flash-preview"; + if (id === "gemini-3-pro") { + return "gemini-3-pro-preview"; + } + if (id === "gemini-3-flash") { + return "gemini-3-flash-preview"; + } return id; } @@ -168,7 +180,9 @@ function normalizeGoogleProvider(provider: ProviderConfig): ProviderConfig { let mutated = false; const models = provider.models.map((model) => { const nextId = normalizeGoogleModelId(model.id); - if (nextId === model.id) return model; + if (nextId === model.id) { + return model; + } mutated = true; return { ...model, id: nextId }; }); @@ -180,7 +194,9 @@ export function normalizeProviders(params: { agentDir: string; }): ModelsConfig["providers"] { const { providers } = params; - if (!providers) return providers; + if (!providers) { + return providers; + } const authStore = ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false, }); @@ -230,7 +246,9 @@ export function normalizeProviders(params: { if (normalizedKey === "google") { const googleNormalized = normalizeGoogleProvider(normalizedProvider); - if (googleNormalized !== normalizedProvider) mutated = true; + if (googleNormalized !== normalizedProvider) { + mutated = true; + } normalizedProvider = googleNormalized; } @@ -428,7 +446,9 @@ export async function resolveImplicitCopilotProvider(params: { const envToken = env.COPILOT_GITHUB_TOKEN ?? env.GH_TOKEN ?? env.GITHUB_TOKEN; const githubToken = (envToken ?? "").trim(); - if (!hasProfile && !githubToken) return null; + if (!hasProfile && !githubToken) { + return null; + } let selectedGithubToken = githubToken; if (!selectedGithubToken && hasProfile) { @@ -484,12 +504,18 @@ export async function resolveImplicitBedrockProvider(params: { const discoveryConfig = params.config?.models?.bedrockDiscovery; const enabled = discoveryConfig?.enabled; const hasAwsCreds = resolveAwsSdkEnvVarName(env) !== undefined; - if (enabled === false) return null; - if (enabled !== true && !hasAwsCreds) return null; + if (enabled === false) { + return null; + } + if (enabled !== true && !hasAwsCreds) { + return null; + } const region = discoveryConfig?.region ?? env.AWS_REGION ?? env.AWS_DEFAULT_REGION ?? "us-east-1"; const models = await discoverBedrockModels({ region, config: discoveryConfig }); - if (models.length === 0) return null; + if (models.length === 0) { + return null; + } return { baseUrl: `https://bedrock-runtime.${region}.amazonaws.com`, diff --git a/src/agents/models-config.skips-writing-models-json-no-env-token.test.ts b/src/agents/models-config.skips-writing-models-json-no-env-token.test.ts index a11aeeb95e..306622dca9 100644 --- a/src/agents/models-config.skips-writing-models-json-no-env-token.test.ts +++ b/src/agents/models-config.skips-writing-models-json-no-env-token.test.ts @@ -79,24 +79,51 @@ describe("models-config", () => { await expect(fs.stat(path.join(agentDir, "models.json"))).rejects.toThrow(); expect(result.wrote).toBe(false); } finally { - if (previous === undefined) delete process.env.COPILOT_GITHUB_TOKEN; - else process.env.COPILOT_GITHUB_TOKEN = previous; - if (previousGh === undefined) delete process.env.GH_TOKEN; - else process.env.GH_TOKEN = previousGh; - if (previousGithub === undefined) delete process.env.GITHUB_TOKEN; - else process.env.GITHUB_TOKEN = previousGithub; - if (previousKimiCode === undefined) delete process.env.KIMI_API_KEY; - else process.env.KIMI_API_KEY = previousKimiCode; - if (previousMinimax === undefined) delete process.env.MINIMAX_API_KEY; - else process.env.MINIMAX_API_KEY = previousMinimax; - if (previousMoonshot === undefined) delete process.env.MOONSHOT_API_KEY; - else process.env.MOONSHOT_API_KEY = previousMoonshot; - if (previousSynthetic === undefined) delete process.env.SYNTHETIC_API_KEY; - else process.env.SYNTHETIC_API_KEY = previousSynthetic; - if (previousVenice === undefined) delete process.env.VENICE_API_KEY; - else process.env.VENICE_API_KEY = previousVenice; - if (previousXiaomi === undefined) delete process.env.XIAOMI_API_KEY; - else process.env.XIAOMI_API_KEY = previousXiaomi; + if (previous === undefined) { + delete process.env.COPILOT_GITHUB_TOKEN; + } else { + process.env.COPILOT_GITHUB_TOKEN = previous; + } + if (previousGh === undefined) { + delete process.env.GH_TOKEN; + } else { + process.env.GH_TOKEN = previousGh; + } + if (previousGithub === undefined) { + delete process.env.GITHUB_TOKEN; + } else { + process.env.GITHUB_TOKEN = previousGithub; + } + if (previousKimiCode === undefined) { + delete process.env.KIMI_API_KEY; + } else { + process.env.KIMI_API_KEY = previousKimiCode; + } + if (previousMinimax === undefined) { + delete process.env.MINIMAX_API_KEY; + } else { + process.env.MINIMAX_API_KEY = previousMinimax; + } + if (previousMoonshot === undefined) { + delete process.env.MOONSHOT_API_KEY; + } else { + process.env.MOONSHOT_API_KEY = previousMoonshot; + } + if (previousSynthetic === undefined) { + delete process.env.SYNTHETIC_API_KEY; + } else { + process.env.SYNTHETIC_API_KEY = previousSynthetic; + } + if (previousVenice === undefined) { + delete process.env.VENICE_API_KEY; + } else { + process.env.VENICE_API_KEY = previousVenice; + } + if (previousXiaomi === undefined) { + delete process.env.XIAOMI_API_KEY; + } else { + process.env.XIAOMI_API_KEY = previousXiaomi; + } } }); }); @@ -146,8 +173,11 @@ describe("models-config", () => { expect(ids).toContain("MiniMax-M2.1"); expect(ids).toContain("MiniMax-VL-01"); } finally { - if (prevKey === undefined) delete process.env.MINIMAX_API_KEY; - else process.env.MINIMAX_API_KEY = prevKey; + if (prevKey === undefined) { + delete process.env.MINIMAX_API_KEY; + } else { + process.env.MINIMAX_API_KEY = prevKey; + } } }); }); @@ -179,8 +209,11 @@ describe("models-config", () => { const ids = parsed.providers.synthetic?.models?.map((model) => model.id); expect(ids).toContain("hf:MiniMaxAI/MiniMax-M2.1"); } finally { - if (prevKey === undefined) delete process.env.SYNTHETIC_API_KEY; - else process.env.SYNTHETIC_API_KEY = prevKey; + if (prevKey === undefined) { + delete process.env.SYNTHETIC_API_KEY; + } else { + process.env.SYNTHETIC_API_KEY = prevKey; + } } }); }); diff --git a/src/agents/models-config.ts b/src/agents/models-config.ts index fd5ab37304..8270a7a0c9 100644 --- a/src/agents/models-config.ts +++ b/src/agents/models-config.ts @@ -22,10 +22,14 @@ function isRecord(value: unknown): value is Record { function mergeProviderModels(implicit: ProviderConfig, explicit: ProviderConfig): ProviderConfig { const implicitModels = Array.isArray(implicit.models) ? implicit.models : []; const explicitModels = Array.isArray(explicit.models) ? explicit.models : []; - if (implicitModels.length === 0) return { ...implicit, ...explicit }; + if (implicitModels.length === 0) { + return { ...implicit, ...explicit }; + } const getId = (model: unknown): string => { - if (!model || typeof model !== "object") return ""; + if (!model || typeof model !== "object") { + return ""; + } const id = (model as { id?: unknown }).id; return typeof id === "string" ? id.trim() : ""; }; @@ -35,8 +39,12 @@ function mergeProviderModels(implicit: ProviderConfig, explicit: ProviderConfig) ...explicitModels, ...implicitModels.filter((model) => { const id = getId(model); - if (!id) return false; - if (seen.has(id)) return false; + if (!id) { + return false; + } + if (seen.has(id)) { + return false; + } seen.add(id); return true; }), @@ -56,7 +64,9 @@ function mergeProviders(params: { const out: Record = params.implicit ? { ...params.implicit } : {}; for (const [key, explicit] of Object.entries(params.explicit ?? {})) { const providerKey = key.trim(); - if (!providerKey) continue; + if (!providerKey) { + continue; + } const implicit = out[providerKey]; out[providerKey] = implicit ? mergeProviderModels(implicit, explicit) : explicit; } diff --git a/src/agents/models-config.uses-first-github-copilot-profile-env-tokens.test.ts b/src/agents/models-config.uses-first-github-copilot-profile-env-tokens.test.ts index b5ca651d33..fe5321d648 100644 --- a/src/agents/models-config.uses-first-github-copilot-profile-env-tokens.test.ts +++ b/src/agents/models-config.uses-first-github-copilot-profile-env-tokens.test.ts @@ -100,12 +100,21 @@ describe("models-config", () => { expect.objectContaining({ githubToken: "alpha-token" }), ); } finally { - if (previous === undefined) delete process.env.COPILOT_GITHUB_TOKEN; - else process.env.COPILOT_GITHUB_TOKEN = previous; - if (previousGh === undefined) delete process.env.GH_TOKEN; - else process.env.GH_TOKEN = previousGh; - if (previousGithub === undefined) delete process.env.GITHUB_TOKEN; - else process.env.GITHUB_TOKEN = previousGithub; + if (previous === undefined) { + delete process.env.COPILOT_GITHUB_TOKEN; + } else { + process.env.COPILOT_GITHUB_TOKEN = previous; + } + if (previousGh === undefined) { + delete process.env.GH_TOKEN; + } else { + process.env.GH_TOKEN = previousGh; + } + if (previousGithub === undefined) { + delete process.env.GITHUB_TOKEN; + } else { + process.env.GITHUB_TOKEN = previousGithub; + } } }); }); diff --git a/src/agents/models.profiles.live.test.ts b/src/agents/models.profiles.live.test.ts index 15f2d369ee..77ce14eb27 100644 --- a/src/agents/models.profiles.live.test.ts +++ b/src/agents/models.profiles.live.test.ts @@ -23,7 +23,9 @@ const describeLive = LIVE ? describe : describe.skip; function parseProviderFilter(raw?: string): Set | null { const trimmed = raw?.trim(); - if (!trimmed || trimmed === "all") return null; + if (!trimmed || trimmed === "all") { + return null; + } const ids = trimmed .split(",") .map((s) => s.trim()) @@ -33,7 +35,9 @@ function parseProviderFilter(raw?: string): Set | null { function parseModelFilter(raw?: string): Set | null { const trimmed = raw?.trim(); - if (!trimmed || trimmed === "all") return null; + if (!trimmed || trimmed === "all") { + return null; + } const ids = trimmed .split(",") .map((s) => s.trim()) @@ -47,19 +51,35 @@ function logProgress(message: string): void { function isGoogleModelNotFoundError(err: unknown): boolean { const msg = String(err); - if (!/not found/i.test(msg)) return false; - if (/models\/.+ is not found for api version/i.test(msg)) return true; - if (/"status"\\s*:\\s*"NOT_FOUND"/.test(msg)) return true; - if (/"code"\\s*:\\s*404/.test(msg)) return true; + if (!/not found/i.test(msg)) { + return false; + } + if (/models\/.+ is not found for api version/i.test(msg)) { + return true; + } + if (/"status"\\s*:\\s*"NOT_FOUND"/.test(msg)) { + return true; + } + if (/"code"\\s*:\\s*404/.test(msg)) { + return true; + } return false; } function isModelNotFoundErrorMessage(raw: string): boolean { const msg = raw.trim(); - if (!msg) return false; - if (/\b404\b/.test(msg) && /not[_-]?found/i.test(msg)) return true; - if (/not_found_error/i.test(msg)) return true; - if (/model:\s*[a-z0-9._-]+/i.test(msg) && /not[_-]?found/i.test(msg)) return true; + if (!msg) { + return false; + } + if (/\b404\b/.test(msg) && /not[_-]?found/i.test(msg)) { + return true; + } + if (/not_found_error/i.test(msg)) { + return true; + } + if (/model:\s*[a-z0-9._-]+/i.test(msg) && /not[_-]?found/i.test(msg)) { + return true; + } return false; } @@ -74,7 +94,9 @@ function isInstructionsRequiredError(raw: string): boolean { function toInt(value: string | undefined, fallback: number): number { const trimmed = value?.trim(); - if (!trimmed) return fallback; + if (!trimmed) { + return fallback; + } const parsed = Number.parseInt(trimmed, 10); return Number.isFinite(parsed) ? parsed : fallback; } @@ -82,10 +104,14 @@ function toInt(value: string | undefined, fallback: number): number { function resolveTestReasoning( model: Model, ): "minimal" | "low" | "medium" | "high" | "xhigh" | undefined { - if (!model.reasoning) return undefined; + if (!model.reasoning) { + return undefined; + } const id = model.id.toLowerCase(); if (model.provider === "openai" || model.provider === "openai-codex") { - if (id.includes("pro")) return "high"; + if (id.includes("pro")) { + return "high"; + } return "medium"; } return "low"; @@ -142,7 +168,9 @@ async function completeOkWithRetry(params: { }; const first = await runOnce(); - if (first.text.length > 0) return first; + if (first.text.length > 0) { + return first; + } return await runOnce(); } @@ -185,9 +213,13 @@ describeLive("live models (profile keys)", () => { }> = []; for (const model of models) { - if (providers && !providers.has(model.provider)) continue; + if (providers && !providers.has(model.provider)) { + continue; + } const id = `${model.provider}/${model.id}`; - if (filter && !filter.has(id)) continue; + if (filter && !filter.has(id)) { + continue; + } if (!filter && useModern) { if (!isModernModelRef({ provider: model.provider, id: model.id })) { continue; diff --git a/src/agents/openai-responses.reasoning-replay.test.ts b/src/agents/openai-responses.reasoning-replay.test.ts index 053d08a459..d5e1e2cf58 100644 --- a/src/agents/openai-responses.reasoning-replay.test.ts +++ b/src/agents/openai-responses.reasoning-replay.test.ts @@ -25,11 +25,18 @@ function installFailingFetchCapture() { const fetchImpl: typeof fetch = async (_input, init) => { const rawBody = init?.body; const bodyText = (() => { - if (!rawBody) return ""; - if (typeof rawBody === "string") return rawBody; - if (rawBody instanceof Uint8Array) return Buffer.from(rawBody).toString("utf8"); - if (rawBody instanceof ArrayBuffer) + if (!rawBody) { + return ""; + } + if (typeof rawBody === "string") { + return rawBody; + } + if (rawBody instanceof Uint8Array) { + return Buffer.from(rawBody).toString("utf8"); + } + if (rawBody instanceof ArrayBuffer) { return Buffer.from(new Uint8Array(rawBody)).toString("utf8"); + } return String(rawBody); })(); lastBody = bodyText ? (JSON.parse(bodyText) as unknown) : undefined; diff --git a/src/agents/openclaw-gateway-tool.test.ts b/src/agents/openclaw-gateway-tool.test.ts index 7d2e3951fb..54b8301230 100644 --- a/src/agents/openclaw-gateway-tool.test.ts +++ b/src/agents/openclaw-gateway-tool.test.ts @@ -30,7 +30,9 @@ describe("gateway tool", () => { config: { commands: { restart: true } }, }).find((candidate) => candidate.name === "gateway"); expect(tool).toBeDefined(); - if (!tool) throw new Error("missing gateway tool"); + if (!tool) { + throw new Error("missing gateway tool"); + } const result = await tool.execute("call1", { action: "restart", @@ -78,7 +80,9 @@ describe("gateway tool", () => { agentSessionKey: "agent:main:whatsapp:dm:+15555550123", }).find((candidate) => candidate.name === "gateway"); expect(tool).toBeDefined(); - if (!tool) throw new Error("missing gateway tool"); + if (!tool) { + throw new Error("missing gateway tool"); + } const raw = '{\n agents: { defaults: { workspace: "~/openclaw" } }\n}\n'; await tool.execute("call2", { @@ -104,7 +108,9 @@ describe("gateway tool", () => { agentSessionKey: "agent:main:whatsapp:dm:+15555550123", }).find((candidate) => candidate.name === "gateway"); expect(tool).toBeDefined(); - if (!tool) throw new Error("missing gateway tool"); + if (!tool) { + throw new Error("missing gateway tool"); + } const raw = '{\n channels: { telegram: { groups: { "*": { requireMention: false } } } }\n}\n'; await tool.execute("call4", { @@ -130,7 +136,9 @@ describe("gateway tool", () => { agentSessionKey: "agent:main:whatsapp:dm:+15555550123", }).find((candidate) => candidate.name === "gateway"); expect(tool).toBeDefined(); - if (!tool) throw new Error("missing gateway tool"); + if (!tool) { + throw new Error("missing gateway tool"); + } await tool.execute("call3", { action: "update.run", diff --git a/src/agents/openclaw-tools.agents.test.ts b/src/agents/openclaw-tools.agents.test.ts index bc64c99fc4..e56557bba1 100644 --- a/src/agents/openclaw-tools.agents.test.ts +++ b/src/agents/openclaw-tools.agents.test.ts @@ -33,7 +33,9 @@ describe("agents_list", () => { const tool = createOpenClawTools({ agentSessionKey: "main", }).find((candidate) => candidate.name === "agents_list"); - if (!tool) throw new Error("missing agents_list tool"); + if (!tool) { + throw new Error("missing agents_list tool"); + } const result = await tool.execute("call1", {}); expect(result.details).toMatchObject({ @@ -70,7 +72,9 @@ describe("agents_list", () => { const tool = createOpenClawTools({ agentSessionKey: "main", }).find((candidate) => candidate.name === "agents_list"); - if (!tool) throw new Error("missing agents_list tool"); + if (!tool) { + throw new Error("missing agents_list tool"); + } const result = await tool.execute("call2", {}); const agents = ( @@ -110,7 +114,9 @@ describe("agents_list", () => { const tool = createOpenClawTools({ agentSessionKey: "main", }).find((candidate) => candidate.name === "agents_list"); - if (!tool) throw new Error("missing agents_list tool"); + if (!tool) { + throw new Error("missing agents_list tool"); + } const result = await tool.execute("call3", {}); expect(result.details).toMatchObject({ @@ -145,7 +151,9 @@ describe("agents_list", () => { const tool = createOpenClawTools({ agentSessionKey: "main", }).find((candidate) => candidate.name === "agents_list"); - if (!tool) throw new Error("missing agents_list tool"); + if (!tool) { + throw new Error("missing agents_list tool"); + } const result = await tool.execute("call4", {}); const agents = ( diff --git a/src/agents/openclaw-tools.camera.test.ts b/src/agents/openclaw-tools.camera.test.ts index ee01a6bdb6..802a8c662f 100644 --- a/src/agents/openclaw-tools.camera.test.ts +++ b/src/agents/openclaw-tools.camera.test.ts @@ -37,7 +37,9 @@ describe("nodes camera_snap", () => { }); const tool = createOpenClawTools().find((candidate) => candidate.name === "nodes"); - if (!tool) throw new Error("missing nodes tool"); + if (!tool) { + throw new Error("missing nodes tool"); + } const result = await tool.execute("call1", { action: "camera_snap", @@ -73,7 +75,9 @@ describe("nodes camera_snap", () => { }); const tool = createOpenClawTools().find((candidate) => candidate.name === "nodes"); - if (!tool) throw new Error("missing nodes tool"); + if (!tool) { + throw new Error("missing nodes tool"); + } await tool.execute("call1", { action: "camera_snap", @@ -114,7 +118,9 @@ describe("nodes run", () => { }); const tool = createOpenClawTools().find((candidate) => candidate.name === "nodes"); - if (!tool) throw new Error("missing nodes tool"); + if (!tool) { + throw new Error("missing nodes tool"); + } await tool.execute("call1", { action: "run", diff --git a/src/agents/openclaw-tools.session-status.test.ts b/src/agents/openclaw-tools.session-status.test.ts index ea79297b5a..99bce1757e 100644 --- a/src/agents/openclaw-tools.session-status.test.ts +++ b/src/agents/openclaw-tools.session-status.test.ts @@ -94,7 +94,9 @@ describe("session_status tool", () => { (candidate) => candidate.name === "session_status", ); expect(tool).toBeDefined(); - if (!tool) throw new Error("missing session_status tool"); + if (!tool) { + throw new Error("missing session_status tool"); + } const result = await tool.execute("call1", {}); const details = result.details as { ok?: boolean; statusText?: string }; @@ -115,7 +117,9 @@ describe("session_status tool", () => { (candidate) => candidate.name === "session_status", ); expect(tool).toBeDefined(); - if (!tool) throw new Error("missing session_status tool"); + if (!tool) { + throw new Error("missing session_status tool"); + } await expect(tool.execute("call2", { sessionKey: "nope" })).rejects.toThrow( "Unknown sessionId", @@ -138,7 +142,9 @@ describe("session_status tool", () => { (candidate) => candidate.name === "session_status", ); expect(tool).toBeDefined(); - if (!tool) throw new Error("missing session_status tool"); + if (!tool) { + throw new Error("missing session_status tool"); + } const result = await tool.execute("call3", { sessionKey: sessionId }); const details = result.details as { ok?: boolean; sessionKey?: string }; @@ -160,7 +166,9 @@ describe("session_status tool", () => { (candidate) => candidate.name === "session_status", ); expect(tool).toBeDefined(); - if (!tool) throw new Error("missing session_status tool"); + if (!tool) { + throw new Error("missing session_status tool"); + } const result = await tool.execute("call4", { sessionKey: "temp:slug-generator" }); const details = result.details as { ok?: boolean; sessionKey?: string }; @@ -182,7 +190,9 @@ describe("session_status tool", () => { (candidate) => candidate.name === "session_status", ); expect(tool).toBeDefined(); - if (!tool) throw new Error("missing session_status tool"); + if (!tool) { + throw new Error("missing session_status tool"); + } await expect(tool.execute("call5", { sessionKey: "agent:other:main" })).rejects.toThrow( "Agent-to-agent status is disabled", @@ -222,7 +232,9 @@ describe("session_status tool", () => { (candidate) => candidate.name === "session_status", ); expect(tool).toBeDefined(); - if (!tool) throw new Error("missing session_status tool"); + if (!tool) { + throw new Error("missing session_status tool"); + } const result = await tool.execute("call6", { sessionKey: "main" }); const details = result.details as { ok?: boolean; sessionKey?: string }; @@ -247,7 +259,9 @@ describe("session_status tool", () => { (candidate) => candidate.name === "session_status", ); expect(tool).toBeDefined(); - if (!tool) throw new Error("missing session_status tool"); + if (!tool) { + throw new Error("missing session_status tool"); + } await tool.execute("call3", { model: "default" }); expect(updateSessionStoreMock).toHaveBeenCalled(); diff --git a/src/agents/openclaw-tools.sessions.test.ts b/src/agents/openclaw-tools.sessions.test.ts index cdf353618e..d486780cda 100644 --- a/src/agents/openclaw-tools.sessions.test.ts +++ b/src/agents/openclaw-tools.sessions.test.ts @@ -39,7 +39,9 @@ describe("sessions tools", () => { const byName = (name: string) => { const tool = tools.find((candidate) => candidate.name === name); expect(tool).toBeDefined(); - if (!tool) throw new Error(`missing ${name} tool`); + if (!tool) { + throw new Error(`missing ${name} tool`); + } return tool; }; @@ -56,7 +58,9 @@ describe("sessions tools", () => { const properties = schema.properties ?? {}; const value = properties[prop] as { type?: unknown } | undefined; expect(value).toBeDefined(); - if (!value) throw new Error(`missing ${toolName} schema prop: ${prop}`); + if (!value) { + throw new Error(`missing ${toolName} schema prop: ${prop}`); + } return value; }; @@ -120,7 +124,9 @@ describe("sessions tools", () => { const tool = createOpenClawTools().find((candidate) => candidate.name === "sessions_list"); expect(tool).toBeDefined(); - if (!tool) throw new Error("missing sessions_list tool"); + if (!tool) { + throw new Error("missing sessions_list tool"); + } const result = await tool.execute("call1", { messageLimit: 1 }); const details = result.details as { @@ -157,7 +163,9 @@ describe("sessions tools", () => { const tool = createOpenClawTools().find((candidate) => candidate.name === "sessions_history"); expect(tool).toBeDefined(); - if (!tool) throw new Error("missing sessions_history tool"); + if (!tool) { + throw new Error("missing sessions_history tool"); + } const result = await tool.execute("call3", { sessionKey: "main" }); const details = result.details as { messages?: unknown[] }; @@ -193,7 +201,9 @@ describe("sessions tools", () => { const tool = createOpenClawTools().find((candidate) => candidate.name === "sessions_history"); expect(tool).toBeDefined(); - if (!tool) throw new Error("missing sessions_history tool"); + if (!tool) { + throw new Error("missing sessions_history tool"); + } const result = await tool.execute("call5", { sessionKey: sessionId }); const details = result.details as { messages?: unknown[] }; @@ -220,7 +230,9 @@ describe("sessions tools", () => { const tool = createOpenClawTools().find((candidate) => candidate.name === "sessions_history"); expect(tool).toBeDefined(); - if (!tool) throw new Error("missing sessions_history tool"); + if (!tool) { + throw new Error("missing sessions_history tool"); + } const result = await tool.execute("call6", { sessionKey: sessionId }); const details = result.details as { status?: string; error?: string }; @@ -295,7 +307,9 @@ describe("sessions tools", () => { agentChannel: "discord", }).find((candidate) => candidate.name === "sessions_send"); expect(tool).toBeDefined(); - if (!tool) throw new Error("missing sessions_send tool"); + if (!tool) { + throw new Error("missing sessions_send tool"); + } const fire = await tool.execute("call5", { sessionKey: "main", @@ -395,7 +409,9 @@ describe("sessions tools", () => { agentChannel: "discord", }).find((candidate) => candidate.name === "sessions_send"); expect(tool).toBeDefined(); - if (!tool) throw new Error("missing sessions_send tool"); + if (!tool) { + throw new Error("missing sessions_send tool"); + } const result = await tool.execute("call7", { sessionKey: sessionId, @@ -485,7 +501,9 @@ describe("sessions tools", () => { agentChannel: "discord", }).find((candidate) => candidate.name === "sessions_send"); expect(tool).toBeDefined(); - if (!tool) throw new Error("missing sessions_send tool"); + if (!tool) { + throw new Error("missing sessions_send tool"); + } const waited = await tool.execute("call7", { sessionKey: targetKey, diff --git a/src/agents/openclaw-tools.subagents.sessions-spawn-allows-cross-agent-spawning-configured.test.ts b/src/agents/openclaw-tools.subagents.sessions-spawn-allows-cross-agent-spawning-configured.test.ts index db9e55ca52..a95f6aed6a 100644 --- a/src/agents/openclaw-tools.subagents.sessions-spawn-allows-cross-agent-spawning-configured.test.ts +++ b/src/agents/openclaw-tools.subagents.sessions-spawn-allows-cross-agent-spawning-configured.test.ts @@ -73,7 +73,9 @@ describe("openclaw-tools: subagents", () => { agentSessionKey: "main", agentChannel: "whatsapp", }).find((candidate) => candidate.name === "sessions_spawn"); - if (!tool) throw new Error("missing sessions_spawn tool"); + if (!tool) { + throw new Error("missing sessions_spawn tool"); + } const result = await tool.execute("call7", { task: "do thing", @@ -124,7 +126,9 @@ describe("openclaw-tools: subagents", () => { agentSessionKey: "main", agentChannel: "whatsapp", }).find((candidate) => candidate.name === "sessions_spawn"); - if (!tool) throw new Error("missing sessions_spawn tool"); + if (!tool) { + throw new Error("missing sessions_spawn tool"); + } const result = await tool.execute("call8", { task: "do thing", diff --git a/src/agents/openclaw-tools.subagents.sessions-spawn-announces-agent-wait-lifecycle-events.test.ts b/src/agents/openclaw-tools.subagents.sessions-spawn-announces-agent-wait-lifecycle-events.test.ts index 7a262fe89c..78d656dc39 100644 --- a/src/agents/openclaw-tools.subagents.sessions-spawn-announces-agent-wait-lifecycle-events.test.ts +++ b/src/agents/openclaw-tools.subagents.sessions-spawn-announces-agent-wait-lifecycle-events.test.ts @@ -93,7 +93,9 @@ describe("openclaw-tools: subagents", () => { agentSessionKey: "discord:group:req", agentChannel: "discord", }).find((candidate) => candidate.name === "sessions_spawn"); - if (!tool) throw new Error("missing sessions_spawn tool"); + if (!tool) { + throw new Error("missing sessions_spawn tool"); + } const result = await tool.execute("call1b", { task: "do thing", diff --git a/src/agents/openclaw-tools.subagents.sessions-spawn-applies-model-child-session.test.ts b/src/agents/openclaw-tools.subagents.sessions-spawn-applies-model-child-session.test.ts index 10925b1f26..7801acb2e2 100644 --- a/src/agents/openclaw-tools.subagents.sessions-spawn-applies-model-child-session.test.ts +++ b/src/agents/openclaw-tools.subagents.sessions-spawn-applies-model-child-session.test.ts @@ -69,7 +69,9 @@ describe("openclaw-tools: subagents", () => { agentSessionKey: "discord:group:req", agentSurface: "discord", }).find((candidate) => candidate.name === "sessions_spawn"); - if (!tool) throw new Error("missing sessions_spawn tool"); + if (!tool) { + throw new Error("missing sessions_spawn tool"); + } const result = await tool.execute("call3", { task: "do thing", @@ -112,7 +114,9 @@ describe("openclaw-tools: subagents", () => { agentSessionKey: "discord:group:req", agentChannel: "discord", }).find((candidate) => candidate.name === "sessions_spawn"); - if (!tool) throw new Error("missing sessions_spawn tool"); + if (!tool) { + throw new Error("missing sessions_spawn tool"); + } const result = await tool.execute("call-thinking", { task: "do thing", @@ -143,7 +147,9 @@ describe("openclaw-tools: subagents", () => { agentSessionKey: "discord:group:req", agentChannel: "discord", }).find((candidate) => candidate.name === "sessions_spawn"); - if (!tool) throw new Error("missing sessions_spawn tool"); + if (!tool) { + throw new Error("missing sessions_spawn tool"); + } const result = await tool.execute("call-thinking-invalid", { task: "do thing", @@ -180,7 +186,9 @@ describe("openclaw-tools: subagents", () => { agentSessionKey: "agent:main:main", agentChannel: "discord", }).find((candidate) => candidate.name === "sessions_spawn"); - if (!tool) throw new Error("missing sessions_spawn tool"); + if (!tool) { + throw new Error("missing sessions_spawn tool"); + } const result = await tool.execute("call-default-model", { task: "do thing", diff --git a/src/agents/openclaw-tools.subagents.sessions-spawn-normalizes-allowlisted-agent-ids.test.ts b/src/agents/openclaw-tools.subagents.sessions-spawn-normalizes-allowlisted-agent-ids.test.ts index 5c991fc7cf..e2a2cd86cc 100644 --- a/src/agents/openclaw-tools.subagents.sessions-spawn-normalizes-allowlisted-agent-ids.test.ts +++ b/src/agents/openclaw-tools.subagents.sessions-spawn-normalizes-allowlisted-agent-ids.test.ts @@ -74,7 +74,9 @@ describe("openclaw-tools: subagents", () => { agentSessionKey: "main", agentChannel: "whatsapp", }).find((candidate) => candidate.name === "sessions_spawn"); - if (!tool) throw new Error("missing sessions_spawn tool"); + if (!tool) { + throw new Error("missing sessions_spawn tool"); + } const result = await tool.execute("call10", { task: "do thing", @@ -111,7 +113,9 @@ describe("openclaw-tools: subagents", () => { agentSessionKey: "main", agentChannel: "whatsapp", }).find((candidate) => candidate.name === "sessions_spawn"); - if (!tool) throw new Error("missing sessions_spawn tool"); + if (!tool) { + throw new Error("missing sessions_spawn tool"); + } const result = await tool.execute("call9", { task: "do thing", @@ -175,7 +179,9 @@ describe("openclaw-tools: subagents", () => { agentSessionKey: "discord:group:req", agentChannel: "discord", }).find((candidate) => candidate.name === "sessions_spawn"); - if (!tool) throw new Error("missing sessions_spawn tool"); + if (!tool) { + throw new Error("missing sessions_spawn tool"); + } const result = await tool.execute("call1", { task: "do thing", @@ -187,7 +193,9 @@ describe("openclaw-tools: subagents", () => { runId: "run-1", }); - if (!childRunId) throw new Error("missing child runId"); + if (!childRunId) { + throw new Error("missing child runId"); + } emitAgentEvent({ runId: childRunId, stream: "lifecycle", @@ -277,7 +285,9 @@ describe("openclaw-tools: subagents", () => { agentChannel: "whatsapp", agentAccountId: "kev", }).find((candidate) => candidate.name === "sessions_spawn"); - if (!tool) throw new Error("missing sessions_spawn tool"); + if (!tool) { + throw new Error("missing sessions_spawn tool"); + } const result = await tool.execute("call2", { task: "do thing", @@ -289,7 +299,9 @@ describe("openclaw-tools: subagents", () => { runId: "run-1", }); - if (!childRunId) throw new Error("missing child runId"); + if (!childRunId) { + throw new Error("missing child runId"); + } emitAgentEvent({ runId: childRunId, stream: "lifecycle", diff --git a/src/agents/openclaw-tools.subagents.sessions-spawn-prefers-per-agent-subagent-model.test.ts b/src/agents/openclaw-tools.subagents.sessions-spawn-prefers-per-agent-subagent-model.test.ts index f750d40a10..5003ddbfc3 100644 --- a/src/agents/openclaw-tools.subagents.sessions-spawn-prefers-per-agent-subagent-model.test.ts +++ b/src/agents/openclaw-tools.subagents.sessions-spawn-prefers-per-agent-subagent-model.test.ts @@ -63,7 +63,9 @@ describe("openclaw-tools: subagents", () => { agentSessionKey: "agent:research:main", agentChannel: "discord", }).find((candidate) => candidate.name === "sessions_spawn"); - if (!tool) throw new Error("missing sessions_spawn tool"); + if (!tool) { + throw new Error("missing sessions_spawn tool"); + } const result = await tool.execute("call-agent-model", { task: "do thing", @@ -112,7 +114,9 @@ describe("openclaw-tools: subagents", () => { agentSessionKey: "main", agentChannel: "whatsapp", }).find((candidate) => candidate.name === "sessions_spawn"); - if (!tool) throw new Error("missing sessions_spawn tool"); + if (!tool) { + throw new Error("missing sessions_spawn tool"); + } const result = await tool.execute("call4", { task: "do thing", @@ -147,7 +151,9 @@ describe("openclaw-tools: subagents", () => { agentSessionKey: "main", agentChannel: "whatsapp", }).find((candidate) => candidate.name === "sessions_spawn"); - if (!tool) throw new Error("missing sessions_spawn tool"); + if (!tool) { + throw new Error("missing sessions_spawn tool"); + } const result = await tool.execute("call5", { task: "do thing", diff --git a/src/agents/openclaw-tools.subagents.sessions-spawn-resolves-main-announce-target-from.test.ts b/src/agents/openclaw-tools.subagents.sessions-spawn-resolves-main-announce-target-from.test.ts index b8e9116daa..d261e9ae36 100644 --- a/src/agents/openclaw-tools.subagents.sessions-spawn-resolves-main-announce-target-from.test.ts +++ b/src/agents/openclaw-tools.subagents.sessions-spawn-resolves-main-announce-target-from.test.ts @@ -99,7 +99,9 @@ describe("openclaw-tools: subagents", () => { agentSessionKey: "main", agentChannel: "whatsapp", }).find((candidate) => candidate.name === "sessions_spawn"); - if (!tool) throw new Error("missing sessions_spawn tool"); + if (!tool) { + throw new Error("missing sessions_spawn tool"); + } const result = await tool.execute("call2", { task: "do thing", @@ -111,7 +113,9 @@ describe("openclaw-tools: subagents", () => { runId: "run-1", }); - if (!childRunId) throw new Error("missing child runId"); + if (!childRunId) { + throw new Error("missing child runId"); + } emitAgentEvent({ runId: childRunId, stream: "lifecycle", @@ -159,7 +163,9 @@ describe("openclaw-tools: subagents", () => { agentSessionKey: "main", agentChannel: "whatsapp", }).find((candidate) => candidate.name === "sessions_spawn"); - if (!tool) throw new Error("missing sessions_spawn tool"); + if (!tool) { + throw new Error("missing sessions_spawn tool"); + } const result = await tool.execute("call6", { task: "do thing", diff --git a/src/agents/pi-embedded-block-chunker.ts b/src/agents/pi-embedded-block-chunker.ts index 649488334b..ca9ce9cf6e 100644 --- a/src/agents/pi-embedded-block-chunker.ts +++ b/src/agents/pi-embedded-block-chunker.ts @@ -25,7 +25,9 @@ export class EmbeddedBlockChunker { } append(text: string) { - if (!text) return; + if (!text) { + return; + } this.#buffer += text; } @@ -47,7 +49,9 @@ export class EmbeddedBlockChunker { const { force, emit } = params; const minChars = Math.max(1, Math.floor(this.#chunking.minChars)); const maxChars = Math.max(minChars, Math.floor(this.#chunking.maxChars)); - if (this.#buffer.length < minChars && !force) return; + if (this.#buffer.length < minChars && !force) { + return; + } if (force && this.#buffer.length <= maxChars) { if (this.#buffer.trim().length > 0) { @@ -103,14 +107,20 @@ export class EmbeddedBlockChunker { this.#buffer = stripLeadingNewlines(this.#buffer.slice(nextStart)); } - if (this.#buffer.length < minChars && !force) return; - if (this.#buffer.length < maxChars && !force) return; + if (this.#buffer.length < minChars && !force) { + return; + } + if (this.#buffer.length < maxChars && !force) { + return; + } } } #pickSoftBreakIndex(buffer: string, minCharsOverride?: number): BreakResult { const minChars = Math.max(1, Math.floor(minCharsOverride ?? this.#chunking.minChars)); - if (buffer.length < minChars) return { index: -1 }; + if (buffer.length < minChars) { + return { index: -1 }; + } const fenceSpans = parseFenceSpans(buffer); const preference = this.#chunking.breakPreference ?? "paragraph"; @@ -119,8 +129,12 @@ export class EmbeddedBlockChunker { while (paragraphIdx !== -1) { const candidates = [paragraphIdx, paragraphIdx + 1]; for (const candidate of candidates) { - if (candidate < minChars) continue; - if (candidate < 0 || candidate >= buffer.length) continue; + if (candidate < minChars) { + continue; + } + if (candidate < 0 || candidate >= buffer.length) { + continue; + } if (isSafeFenceBreak(fenceSpans, candidate)) { return { index: candidate }; } @@ -144,13 +158,17 @@ export class EmbeddedBlockChunker { let sentenceIdx = -1; for (const match of matches) { const at = match.index ?? -1; - if (at < minChars) continue; + if (at < minChars) { + continue; + } const candidate = at + 1; if (isSafeFenceBreak(fenceSpans, candidate)) { sentenceIdx = candidate; } } - if (sentenceIdx >= minChars) return { index: sentenceIdx }; + if (sentenceIdx >= minChars) { + return { index: sentenceIdx }; + } } return { index: -1 }; @@ -159,7 +177,9 @@ export class EmbeddedBlockChunker { #pickBreakIndex(buffer: string, minCharsOverride?: number): BreakResult { const minChars = Math.max(1, Math.floor(minCharsOverride ?? this.#chunking.minChars)); const maxChars = Math.max(minChars, Math.floor(this.#chunking.maxChars)); - if (buffer.length < minChars) return { index: -1 }; + if (buffer.length < minChars) { + return { index: -1 }; + } const window = buffer.slice(0, Math.min(maxChars, buffer.length)); const fenceSpans = parseFenceSpans(buffer); @@ -169,8 +189,12 @@ export class EmbeddedBlockChunker { while (paragraphIdx >= minChars) { const candidates = [paragraphIdx, paragraphIdx + 1]; for (const candidate of candidates) { - if (candidate < minChars) continue; - if (candidate < 0 || candidate >= buffer.length) continue; + if (candidate < minChars) { + continue; + } + if (candidate < 0 || candidate >= buffer.length) { + continue; + } if (isSafeFenceBreak(fenceSpans, candidate)) { return { index: candidate }; } @@ -194,13 +218,17 @@ export class EmbeddedBlockChunker { let sentenceIdx = -1; for (const match of matches) { const at = match.index ?? -1; - if (at < minChars) continue; + if (at < minChars) { + continue; + } const candidate = at + 1; if (isSafeFenceBreak(fenceSpans, candidate)) { sentenceIdx = candidate; } } - if (sentenceIdx >= minChars) return { index: sentenceIdx }; + if (sentenceIdx >= minChars) { + return { index: sentenceIdx }; + } } if (preference === "newline" && buffer.length < maxChars) { @@ -214,7 +242,9 @@ export class EmbeddedBlockChunker { } if (buffer.length >= maxChars) { - if (isSafeFenceBreak(fenceSpans, maxChars)) return { index: maxChars }; + if (isSafeFenceBreak(fenceSpans, maxChars)) { + return { index: maxChars }; + } const fence = findFenceSpanAt(fenceSpans, maxChars); if (fence) { return { @@ -234,6 +264,8 @@ export class EmbeddedBlockChunker { function stripLeadingNewlines(value: string): string { let i = 0; - while (i < value.length && value[i] === "\n") i++; + while (i < value.length && value[i] === "\n") { + i++; + } return i > 0 ? value.slice(i) : value; } diff --git a/src/agents/pi-embedded-helpers/bootstrap.ts b/src/agents/pi-embedded-helpers/bootstrap.ts index 3f94039045..9283c666a6 100644 --- a/src/agents/pi-embedded-helpers/bootstrap.ts +++ b/src/agents/pi-embedded-helpers/bootstrap.ts @@ -20,13 +20,19 @@ type ThoughtSignatureSanitizeOptions = { function isBase64Signature(value: string): boolean { const trimmed = value.trim(); - if (!trimmed) return false; + if (!trimmed) { + return false; + } const compact = trimmed.replace(/\s+/g, ""); - if (!/^[A-Za-z0-9+/=_-]+$/.test(compact)) return false; + if (!/^[A-Za-z0-9+/=_-]+$/.test(compact)) { + return false; + } const isUrl = compact.includes("-") || compact.includes("_"); try { const buf = Buffer.from(compact, isUrl ? "base64url" : "base64"); - if (buf.length === 0) return false; + if (buf.length === 0) { + return false; + } const encoded = buf.toString(isUrl ? "base64url" : "base64"); const normalize = (input: string) => input.replace(/=+$/g, ""); return normalize(encoded) === normalize(compact); @@ -45,7 +51,9 @@ export function stripThoughtSignatures( content: T, options?: ThoughtSignatureSanitizeOptions, ): T { - if (!Array.isArray(content)) return content; + if (!Array.isArray(content)) { + return content; + } const allowBase64Only = options?.allowBase64Only ?? false; const includeCamelCase = options?.includeCamelCase ?? false; const shouldStripSignature = (value: unknown): boolean => { @@ -55,7 +63,9 @@ export function stripThoughtSignatures( return typeof value !== "string" || !isBase64Signature(value); }; return content.map((block) => { - if (!block || typeof block !== "object") return block; + if (!block || typeof block !== "object") { + return block; + } const rec = block as ContentBlockWithSignature; const stripSnake = shouldStripSignature(rec.thought_signature); const stripCamel = includeCamelCase ? shouldStripSignature(rec.thoughtSignature) : false; @@ -63,8 +73,12 @@ export function stripThoughtSignatures( return block; } const next = { ...rec }; - if (stripSnake) delete next.thought_signature; - if (stripCamel) delete next.thoughtSignature; + if (stripSnake) { + delete next.thought_signature; + } + if (stripCamel) { + delete next.thoughtSignature; + } return next; }) as T; } @@ -162,7 +176,9 @@ export function buildBootstrapContextFiles( continue; } const trimmed = trimBootstrapContent(file.content ?? "", file.name, maxChars); - if (!trimmed.content) continue; + if (!trimmed.content) { + continue; + } if (trimmed.truncated) { opts?.warn?.( `workspace bootstrap file ${file.name} is ${trimmed.originalLength} chars (limit ${trimmed.maxChars}); truncating in injected context`, @@ -188,7 +204,9 @@ export function sanitizeGoogleTurnOrdering(messages: AgentMessage[]): AgentMessa ) { return messages; } - if (role !== "assistant") return messages; + if (role !== "assistant") { + return messages; + } // Cloud Code Assist rejects histories that begin with a model turn (tool call or text). // Prepend a tiny synthetic user turn so the rest of the transcript can be used. diff --git a/src/agents/pi-embedded-helpers/errors.ts b/src/agents/pi-embedded-helpers/errors.ts index 674edb6d15..473628202c 100644 --- a/src/agents/pi-embedded-helpers/errors.ts +++ b/src/agents/pi-embedded-helpers/errors.ts @@ -5,7 +5,9 @@ import { formatSandboxToolPolicyBlockedMessage } from "../sandbox.js"; import type { FailoverReason } from "./types.js"; export function isContextOverflowError(errorMessage?: string): boolean { - if (!errorMessage) return false; + if (!errorMessage) { + return false; + } const lower = errorMessage.toLowerCase(); const hasRequestSizeExceeds = lower.includes("request size exceeds"); const hasContextWindow = @@ -30,15 +32,25 @@ const CONTEXT_OVERFLOW_HINT_RE = /context.*overflow|context window.*(too (?:large|long)|exceed|over|limit|max(?:imum)?|requested|sent|tokens)|(?:prompt|request|input).*(too (?:large|long)|exceed|over|limit|max(?:imum)?)/i; export function isLikelyContextOverflowError(errorMessage?: string): boolean { - if (!errorMessage) return false; - if (CONTEXT_WINDOW_TOO_SMALL_RE.test(errorMessage)) return false; - if (isContextOverflowError(errorMessage)) return true; + if (!errorMessage) { + return false; + } + if (CONTEXT_WINDOW_TOO_SMALL_RE.test(errorMessage)) { + return false; + } + if (isContextOverflowError(errorMessage)) { + return true; + } return CONTEXT_OVERFLOW_HINT_RE.test(errorMessage); } export function isCompactionFailureError(errorMessage?: string): boolean { - if (!errorMessage) return false; - if (!isContextOverflowError(errorMessage)) return false; + if (!errorMessage) { + return false; + } + if (!isContextOverflowError(errorMessage)) { + return false; + } const lower = errorMessage.toLowerCase(); return ( lower.includes("summarization failed") || @@ -73,15 +85,21 @@ const HTTP_ERROR_HINTS = [ ]; function stripFinalTagsFromText(text: string): string { - if (!text) return text; + if (!text) { + return text; + } return text.replace(FINAL_TAG_RE, ""); } function collapseConsecutiveDuplicateBlocks(text: string): string { const trimmed = text.trim(); - if (!trimmed) return text; + if (!trimmed) { + return text; + } const blocks = trimmed.split(/\n{2,}/); - if (blocks.length < 2) return text; + if (blocks.length < 2) { + return text; + } const normalizeBlock = (value: string) => value.trim().replace(/\s+/g, " "); const result: string[] = []; @@ -96,15 +114,21 @@ function collapseConsecutiveDuplicateBlocks(text: string): string { lastNormalized = normalized; } - if (result.length === blocks.length) return text; + if (result.length === blocks.length) { + return text; + } return result.join("\n\n"); } function isLikelyHttpErrorText(raw: string): boolean { const match = raw.match(HTTP_STATUS_PREFIX_RE); - if (!match) return false; + if (!match) { + return false; + } const code = Number(match[1]); - if (!Number.isFinite(code) || code < 400) return false; + if (!Number.isFinite(code) || code < 400) { + return false; + } const message = match[2].toLowerCase(); return HTTP_ERROR_HINTS.some((hint) => message.includes(hint)); } @@ -112,10 +136,16 @@ function isLikelyHttpErrorText(raw: string): boolean { type ErrorPayload = Record; function isErrorPayloadObject(payload: unknown): payload is ErrorPayload { - if (!payload || typeof payload !== "object" || Array.isArray(payload)) return false; + if (!payload || typeof payload !== "object" || Array.isArray(payload)) { + return false; + } const record = payload as ErrorPayload; - if (record.type === "error") return true; - if (typeof record.request_id === "string" || typeof record.requestId === "string") return true; + if (record.type === "error") { + return true; + } + if (typeof record.request_id === "string" || typeof record.requestId === "string") { + return true; + } if ("error" in record) { const err = record.error; if (err && typeof err === "object" && !Array.isArray(err)) { @@ -133,18 +163,26 @@ function isErrorPayloadObject(payload: unknown): payload is ErrorPayload { } function parseApiErrorPayload(raw: string): ErrorPayload | null { - if (!raw) return null; + if (!raw) { + return null; + } const trimmed = raw.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const candidates = [trimmed]; if (ERROR_PAYLOAD_PREFIX_RE.test(trimmed)) { candidates.push(trimmed.replace(ERROR_PAYLOAD_PREFIX_RE, "").trim()); } for (const candidate of candidates) { - if (!candidate.startsWith("{") || !candidate.endsWith("}")) continue; + if (!candidate.startsWith("{") || !candidate.endsWith("}")) { + continue; + } try { const parsed = JSON.parse(candidate) as unknown; - if (isErrorPayloadObject(parsed)) return parsed; + if (isErrorPayloadObject(parsed)) { + return parsed; + } } catch { // ignore parse errors } @@ -166,9 +204,13 @@ function stableStringify(value: unknown): string { } export function getApiErrorPayloadFingerprint(raw?: string): string | null { - if (!raw) return null; + if (!raw) { + return null; + } const payload = parseApiErrorPayload(raw); - if (!payload) return null; + if (!payload) { + return null; + } return stableStringify(payload); } @@ -184,9 +226,13 @@ export type ApiErrorInfo = { }; export function parseApiErrorInfo(raw?: string): ApiErrorInfo | null { - if (!raw) return null; + if (!raw) { + return null; + } const trimmed = raw.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } let httpCode: string | undefined; let candidate = trimmed; @@ -198,7 +244,9 @@ export function parseApiErrorInfo(raw?: string): ApiErrorInfo | null { } const payload = parseApiErrorPayload(candidate); - if (!payload) return null; + if (!payload) { + return null; + } const requestId = typeof payload.request_id === "string" @@ -214,9 +262,15 @@ export function parseApiErrorInfo(raw?: string): ApiErrorInfo | null { let errMessage: string | undefined; if (payload.error && typeof payload.error === "object" && !Array.isArray(payload.error)) { const err = payload.error as Record; - if (typeof err.type === "string") errType = err.type; - if (typeof err.code === "string" && !errType) errType = err.code; - if (typeof err.message === "string") errMessage = err.message; + if (typeof err.type === "string") { + errType = err.type; + } + if (typeof err.code === "string" && !errType) { + errType = err.code; + } + if (typeof err.message === "string") { + errMessage = err.message; + } } return { @@ -229,7 +283,9 @@ export function parseApiErrorInfo(raw?: string): ApiErrorInfo | null { export function formatRawAssistantErrorForUi(raw?: string): string { const trimmed = (raw ?? "").trim(); - if (!trimmed) return "LLM request failed with an unknown error."; + if (!trimmed) { + return "LLM request failed with an unknown error."; + } const httpMatch = trimmed.match(HTTP_STATUS_PREFIX_RE); if (httpMatch) { @@ -256,8 +312,12 @@ export function formatAssistantErrorText( ): string | undefined { // Also format errors if errorMessage is present, even if stopReason isn't "error" const raw = (msg.errorMessage ?? "").trim(); - if (msg.stopReason !== "error" && !raw) return undefined; - if (!raw) return "LLM request failed with an unknown error."; + if (msg.stopReason !== "error" && !raw) { + return undefined; + } + if (!raw) { + return "LLM request failed with an unknown error."; + } const unknownTool = raw.match(/unknown tool[:\s]+["']?([a-z0-9_-]+)["']?/i) ?? @@ -268,7 +328,9 @@ export function formatAssistantErrorText( sessionKey: opts?.sessionKey, toolName: unknownTool[1], }); - if (rewritten) return rewritten; + if (rewritten) { + return rewritten; + } } if (isContextOverflowError(raw)) { @@ -311,10 +373,14 @@ export function formatAssistantErrorText( } export function sanitizeUserFacingText(text: string): string { - if (!text) return text; + if (!text) { + return text; + } const stripped = stripFinalTagsFromText(text); const trimmed = stripped.trim(); - if (!trimmed) return stripped; + if (!trimmed) { + return stripped; + } if (/incorrect role information|roles must alternate/i.test(trimmed)) { return ( @@ -348,7 +414,9 @@ export function sanitizeUserFacingText(text: string): string { } export function isRateLimitAssistantError(msg: AssistantMessage | undefined): boolean { - if (!msg || msg.stopReason !== "error") return false; + if (!msg || msg.stopReason !== "error") { + return false; + } return isRateLimitErrorMessage(msg.errorMessage ?? ""); } @@ -404,7 +472,9 @@ const IMAGE_DIMENSION_PATH_RE = /messages\.(\d+)\.content\.(\d+)\.image/i; const IMAGE_SIZE_ERROR_RE = /image exceeds\s*(\d+(?:\.\d+)?)\s*mb/i; function matchesErrorPatterns(raw: string, patterns: readonly ErrorPattern[]): boolean { - if (!raw) return false; + if (!raw) { + return false; + } const value = raw.toLowerCase(); return patterns.some((pattern) => pattern instanceof RegExp ? pattern.test(value) : value.includes(pattern), @@ -421,8 +491,12 @@ export function isTimeoutErrorMessage(raw: string): boolean { export function isBillingErrorMessage(raw: string): boolean { const value = raw.toLowerCase(); - if (!value) return false; - if (matchesErrorPatterns(value, ERROR_PATTERNS.billing)) return true; + if (!value) { + return false; + } + if (matchesErrorPatterns(value, ERROR_PATTERNS.billing)) { + return true; + } return ( value.includes("billing") && (value.includes("upgrade") || @@ -433,7 +507,9 @@ export function isBillingErrorMessage(raw: string): boolean { } export function isBillingAssistantError(msg: AssistantMessage | undefined): boolean { - if (!msg || msg.stopReason !== "error") return false; + if (!msg || msg.stopReason !== "error") { + return false; + } return isBillingErrorMessage(msg.errorMessage ?? ""); } @@ -451,9 +527,13 @@ export function parseImageDimensionError(raw: string): { contentIndex?: number; raw: string; } | null { - if (!raw) return null; + if (!raw) { + return null; + } const lower = raw.toLowerCase(); - if (!lower.includes("image dimensions exceed max allowed size")) return null; + if (!lower.includes("image dimensions exceed max allowed size")) { + return null; + } const limitMatch = raw.match(IMAGE_DIMENSION_ERROR_RE); const pathMatch = raw.match(IMAGE_DIMENSION_PATH_RE); return { @@ -472,9 +552,13 @@ export function parseImageSizeError(raw: string): { maxMb?: number; raw: string; } | null { - if (!raw) return null; + if (!raw) { + return null; + } const lower = raw.toLowerCase(); - if (!lower.includes("image exceeds") || !lower.includes("mb")) return null; + if (!lower.includes("image exceeds") || !lower.includes("mb")) { + return null; + } const match = raw.match(IMAGE_SIZE_ERROR_RE); return { maxMb: match?.[1] ? Number.parseFloat(match[1]) : undefined, @@ -483,7 +567,9 @@ export function parseImageSizeError(raw: string): { } export function isImageSizeError(errorMessage?: string): boolean { - if (!errorMessage) return false; + if (!errorMessage) { + return false; + } return Boolean(parseImageSizeError(errorMessage)); } @@ -492,19 +578,37 @@ export function isCloudCodeAssistFormatError(raw: string): boolean { } export function isAuthAssistantError(msg: AssistantMessage | undefined): boolean { - if (!msg || msg.stopReason !== "error") return false; + if (!msg || msg.stopReason !== "error") { + return false; + } return isAuthErrorMessage(msg.errorMessage ?? ""); } export function classifyFailoverReason(raw: string): FailoverReason | null { - if (isImageDimensionErrorMessage(raw)) return null; - if (isImageSizeError(raw)) return null; - if (isRateLimitErrorMessage(raw)) return "rate_limit"; - if (isOverloadedErrorMessage(raw)) return "rate_limit"; - if (isCloudCodeAssistFormatError(raw)) return "format"; - if (isBillingErrorMessage(raw)) return "billing"; - if (isTimeoutErrorMessage(raw)) return "timeout"; - if (isAuthErrorMessage(raw)) return "auth"; + if (isImageDimensionErrorMessage(raw)) { + return null; + } + if (isImageSizeError(raw)) { + return null; + } + if (isRateLimitErrorMessage(raw)) { + return "rate_limit"; + } + if (isOverloadedErrorMessage(raw)) { + return "rate_limit"; + } + if (isCloudCodeAssistFormatError(raw)) { + return "format"; + } + if (isBillingErrorMessage(raw)) { + return "billing"; + } + if (isTimeoutErrorMessage(raw)) { + return "timeout"; + } + if (isAuthErrorMessage(raw)) { + return "auth"; + } return null; } @@ -513,6 +617,8 @@ export function isFailoverErrorMessage(raw: string): boolean { } export function isFailoverAssistantError(msg: AssistantMessage | undefined): boolean { - if (!msg || msg.stopReason !== "error") return false; + if (!msg || msg.stopReason !== "error") { + return false; + } return isFailoverErrorMessage(msg.errorMessage ?? ""); } diff --git a/src/agents/pi-embedded-helpers/google.ts b/src/agents/pi-embedded-helpers/google.ts index 8d53901d67..f62095f0bc 100644 --- a/src/agents/pi-embedded-helpers/google.ts +++ b/src/agents/pi-embedded-helpers/google.ts @@ -13,7 +13,9 @@ export function isAntigravityClaude(params: { }): boolean { const provider = params.provider?.toLowerCase(); const api = params.api?.toLowerCase(); - if (provider !== "google-antigravity" && api !== "google-antigravity") return false; + if (provider !== "google-antigravity" && api !== "google-antigravity") { + return false; + } return params.modelId?.toLowerCase().includes("claude") ?? false; } diff --git a/src/agents/pi-embedded-helpers/images.ts b/src/agents/pi-embedded-helpers/images.ts index 912500f744..1e4af24b0d 100644 --- a/src/agents/pi-embedded-helpers/images.ts +++ b/src/agents/pi-embedded-helpers/images.ts @@ -11,12 +11,20 @@ export function isEmptyAssistantMessageContent( message: Extract, ): boolean { const content = message.content; - if (content == null) return true; - if (!Array.isArray(content)) return false; + if (content == null) { + return true; + } + if (!Array.isArray(content)) { + return false; + } return content.every((block) => { - if (!block || typeof block !== "object") return true; + if (!block || typeof block !== "object") { + return true; + } const rec = block as { type?: unknown; text?: unknown }; - if (rec.type !== "text") return false; + if (rec.type !== "text") { + return false; + } return typeof rec.text !== "string" || rec.text.trim().length === 0; }); } @@ -110,9 +118,13 @@ export async function sanitizeSessionMessagesImages( : stripThoughtSignatures(content, options?.sanitizeThoughtSignatures); // Strip for Gemini const filteredContent = strippedContent.filter((block) => { - if (!block || typeof block !== "object") return true; + if (!block || typeof block !== "object") { + return true; + } const rec = block as { type?: unknown; text?: unknown }; - if (rec.type !== "text" || typeof rec.text !== "string") return true; + if (rec.type !== "text" || typeof rec.text !== "string") { + return true; + } return rec.text.trim().length > 0; }); const finalContent = (await sanitizeContentBlocksImages( diff --git a/src/agents/pi-embedded-helpers/messaging-dedupe.ts b/src/agents/pi-embedded-helpers/messaging-dedupe.ts index fe8acca321..71e1c3aa43 100644 --- a/src/agents/pi-embedded-helpers/messaging-dedupe.ts +++ b/src/agents/pi-embedded-helpers/messaging-dedupe.ts @@ -20,17 +20,27 @@ export function isMessagingToolDuplicateNormalized( normalized: string, normalizedSentTexts: string[], ): boolean { - if (normalizedSentTexts.length === 0) return false; - if (!normalized || normalized.length < MIN_DUPLICATE_TEXT_LENGTH) return false; + if (normalizedSentTexts.length === 0) { + return false; + } + if (!normalized || normalized.length < MIN_DUPLICATE_TEXT_LENGTH) { + return false; + } return normalizedSentTexts.some((normalizedSent) => { - if (!normalizedSent || normalizedSent.length < MIN_DUPLICATE_TEXT_LENGTH) return false; + if (!normalizedSent || normalizedSent.length < MIN_DUPLICATE_TEXT_LENGTH) { + return false; + } return normalized.includes(normalizedSent) || normalizedSent.includes(normalized); }); } export function isMessagingToolDuplicate(text: string, sentTexts: string[]): boolean { - if (sentTexts.length === 0) return false; + if (sentTexts.length === 0) { + return false; + } const normalized = normalizeTextForComparison(text); - if (!normalized || normalized.length < MIN_DUPLICATE_TEXT_LENGTH) return false; + if (!normalized || normalized.length < MIN_DUPLICATE_TEXT_LENGTH) { + return false; + } return isMessagingToolDuplicateNormalized(normalized, sentTexts.map(normalizeTextForComparison)); } diff --git a/src/agents/pi-embedded-helpers/openai.ts b/src/agents/pi-embedded-helpers/openai.ts index f1350c087c..8564e0d2d7 100644 --- a/src/agents/pi-embedded-helpers/openai.ts +++ b/src/agents/pi-embedded-helpers/openai.ts @@ -12,11 +12,15 @@ type OpenAIReasoningSignature = { }; function parseOpenAIReasoningSignature(value: unknown): OpenAIReasoningSignature | null { - if (!value) return null; + if (!value) { + return null; + } let candidate: { id?: unknown; type?: unknown } | null = null; if (typeof value === "string") { const trimmed = value.trim(); - if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return null; + if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) { + return null; + } try { candidate = JSON.parse(trimmed) as { id?: unknown; type?: unknown }; } catch { @@ -25,10 +29,14 @@ function parseOpenAIReasoningSignature(value: unknown): OpenAIReasoningSignature } else if (typeof value === "object") { candidate = value as { id?: unknown; type?: unknown }; } - if (!candidate) return null; + if (!candidate) { + return null; + } const id = typeof candidate.id === "string" ? candidate.id : ""; const type = typeof candidate.type === "string" ? candidate.type : ""; - if (!id.startsWith("rs_")) return null; + if (!id.startsWith("rs_")) { + return null; + } if (type === "reasoning" || type.startsWith("reasoning.")) { return { id, type }; } @@ -41,8 +49,12 @@ function hasFollowingNonThinkingBlock( ): boolean { for (let i = index + 1; i < content.length; i++) { const block = content[i]; - if (!block || typeof block !== "object") return true; - if ((block as { type?: unknown }).type !== "thinking") return true; + if (!block || typeof block !== "object") { + return true; + } + if ((block as { type?: unknown }).type !== "thinking") { + return true; + } } return false; } diff --git a/src/agents/pi-embedded-helpers/thinking.ts b/src/agents/pi-embedded-helpers/thinking.ts index 474c16d855..d8ae2c8378 100644 --- a/src/agents/pi-embedded-helpers/thinking.ts +++ b/src/agents/pi-embedded-helpers/thinking.ts @@ -3,7 +3,9 @@ import { normalizeThinkLevel, type ThinkLevel } from "../../auto-reply/thinking. function extractSupportedValues(raw: string): string[] { const match = raw.match(/supported values are:\s*([^\n.]+)/i) ?? raw.match(/supported values:\s*([^\n.]+)/i); - if (!match?.[1]) return []; + if (!match?.[1]) { + return []; + } const fragment = match[1]; const quoted = Array.from(fragment.matchAll(/['"]([^'"]+)['"]/g)).map((entry) => entry[1]?.trim(), @@ -22,13 +24,21 @@ export function pickFallbackThinkingLevel(params: { attempted: Set; }): ThinkLevel | undefined { const raw = params.message?.trim(); - if (!raw) return undefined; + if (!raw) { + return undefined; + } const supported = extractSupportedValues(raw); - if (supported.length === 0) return undefined; + if (supported.length === 0) { + return undefined; + } for (const entry of supported) { const normalized = normalizeThinkLevel(entry); - if (!normalized) continue; - if (params.attempted.has(normalized)) continue; + if (!normalized) { + continue; + } + if (params.attempted.has(normalized)) { + continue; + } return normalized; } return undefined; diff --git a/src/agents/pi-embedded-messaging.ts b/src/agents/pi-embedded-messaging.ts index 5aae66fd4d..bdd8cd54bc 100644 --- a/src/agents/pi-embedded-messaging.ts +++ b/src/agents/pi-embedded-messaging.ts @@ -11,7 +11,9 @@ const CORE_MESSAGING_TOOLS = new Set(["sessions_send", "message"]); // Provider docking: any plugin with `actions` opts into messaging tool handling. export function isMessagingTool(toolName: string): boolean { - if (CORE_MESSAGING_TOOLS.has(toolName)) return true; + if (CORE_MESSAGING_TOOLS.has(toolName)) { + return true; + } const providerId = normalizeChannelId(toolName); return Boolean(providerId && getChannelPlugin(providerId)?.actions); } @@ -21,13 +23,19 @@ export function isMessagingToolSendAction( args: Record, ): boolean { const action = typeof args.action === "string" ? args.action.trim() : ""; - if (toolName === "sessions_send") return true; + if (toolName === "sessions_send") { + return true; + } if (toolName === "message") { return action === "send" || action === "thread-reply"; } const providerId = normalizeChannelId(toolName); - if (!providerId) return false; + if (!providerId) { + return false; + } const plugin = getChannelPlugin(providerId); - if (!plugin?.actions?.extractToolSend) return false; + if (!plugin?.actions?.extractToolSend) { + return false; + } return Boolean(plugin.actions.extractToolSend({ args })?.to); } diff --git a/src/agents/pi-embedded-runner.applygoogleturnorderingfix.test.ts b/src/agents/pi-embedded-runner.applygoogleturnorderingfix.test.ts index 328d3e0384..f74ce5a325 100644 --- a/src/agents/pi-embedded-runner.applygoogleturnorderingfix.test.ts +++ b/src/agents/pi-embedded-runner.applygoogleturnorderingfix.test.ts @@ -75,7 +75,9 @@ const _ensureModels = (cfg: OpenClawConfig, agentDir: string) => ensureOpenClawModelsJson(cfg, agentDir) as unknown; const _textFromContent = (content: unknown) => { - if (typeof content === "string") return content; + if (typeof content === "string") { + return content; + } if (Array.isArray(content) && content[0]?.type === "text") { return (content[0] as { text?: string }).text; } diff --git a/src/agents/pi-embedded-runner.buildembeddedsandboxinfo.test.ts b/src/agents/pi-embedded-runner.buildembeddedsandboxinfo.test.ts index 77bdd735b5..8c94d1a9aa 100644 --- a/src/agents/pi-embedded-runner.buildembeddedsandboxinfo.test.ts +++ b/src/agents/pi-embedded-runner.buildembeddedsandboxinfo.test.ts @@ -74,7 +74,9 @@ const _ensureModels = (cfg: OpenClawConfig, agentDir: string) => ensureOpenClawModelsJson(cfg, agentDir) as unknown; const _textFromContent = (content: unknown) => { - if (typeof content === "string") return content; + if (typeof content === "string") { + return content; + } if (Array.isArray(content) && content[0]?.type === "text") { return (content[0] as { text?: string }).text; } diff --git a/src/agents/pi-embedded-runner.createsystempromptoverride.test.ts b/src/agents/pi-embedded-runner.createsystempromptoverride.test.ts index c204198369..cd2a13fe4f 100644 --- a/src/agents/pi-embedded-runner.createsystempromptoverride.test.ts +++ b/src/agents/pi-embedded-runner.createsystempromptoverride.test.ts @@ -73,7 +73,9 @@ const _ensureModels = (cfg: OpenClawConfig, agentDir: string) => ensureOpenClawModelsJson(cfg, agentDir) as unknown; const _textFromContent = (content: unknown) => { - if (typeof content === "string") return content; + if (typeof content === "string") { + return content; + } if (Array.isArray(content) && content[0]?.type === "text") { return (content[0] as { text?: string }).text; } diff --git a/src/agents/pi-embedded-runner.get-dm-history-limit-from-session-key.falls-back-provider-default-per-dm-not.test.ts b/src/agents/pi-embedded-runner.get-dm-history-limit-from-session-key.falls-back-provider-default-per-dm-not.test.ts index 4cb6c9f1f1..f2f74cdd05 100644 --- a/src/agents/pi-embedded-runner.get-dm-history-limit-from-session-key.falls-back-provider-default-per-dm-not.test.ts +++ b/src/agents/pi-embedded-runner.get-dm-history-limit-from-session-key.falls-back-provider-default-per-dm-not.test.ts @@ -73,7 +73,9 @@ const _ensureModels = (cfg: OpenClawConfig, agentDir: string) => ensureOpenClawModelsJson(cfg, agentDir); const _textFromContent = (content: unknown) => { - if (typeof content === "string") return content; + if (typeof content === "string") { + return content; + } if (Array.isArray(content) && content[0]?.type === "text") { return (content[0] as { text?: string }).text; } diff --git a/src/agents/pi-embedded-runner.get-dm-history-limit-from-session-key.returns-undefined-sessionkey-is-undefined.test.ts b/src/agents/pi-embedded-runner.get-dm-history-limit-from-session-key.returns-undefined-sessionkey-is-undefined.test.ts index 3e790994be..5abd8ccfba 100644 --- a/src/agents/pi-embedded-runner.get-dm-history-limit-from-session-key.returns-undefined-sessionkey-is-undefined.test.ts +++ b/src/agents/pi-embedded-runner.get-dm-history-limit-from-session-key.returns-undefined-sessionkey-is-undefined.test.ts @@ -73,7 +73,9 @@ const _ensureModels = (cfg: OpenClawConfig, agentDir: string) => ensureOpenClawModelsJson(cfg, agentDir) as unknown; const _textFromContent = (content: unknown) => { - if (typeof content === "string") return content; + if (typeof content === "string") { + return content; + } if (Array.isArray(content) && content[0]?.type === "text") { return (content[0] as { text?: string }).text; } diff --git a/src/agents/pi-embedded-runner.limithistoryturns.test.ts b/src/agents/pi-embedded-runner.limithistoryturns.test.ts index 9ffb5289d2..abff9de20a 100644 --- a/src/agents/pi-embedded-runner.limithistoryturns.test.ts +++ b/src/agents/pi-embedded-runner.limithistoryturns.test.ts @@ -74,7 +74,9 @@ const _ensureModels = (cfg: OpenClawConfig, agentDir: string) => ensureOpenClawModelsJson(cfg, agentDir) as unknown; const _textFromContent = (content: unknown) => { - if (typeof content === "string") return content; + if (typeof content === "string") { + return content; + } if (Array.isArray(content) && content[0]?.type === "text") { return (content[0] as { text?: string }).text; } diff --git a/src/agents/pi-embedded-runner.resolvesessionagentids.test.ts b/src/agents/pi-embedded-runner.resolvesessionagentids.test.ts index 81d43cbf6b..8151e08675 100644 --- a/src/agents/pi-embedded-runner.resolvesessionagentids.test.ts +++ b/src/agents/pi-embedded-runner.resolvesessionagentids.test.ts @@ -73,7 +73,9 @@ const _ensureModels = (cfg: OpenClawConfig, agentDir: string) => ensureOpenClawModelsJson(cfg, agentDir) as unknown; const _textFromContent = (content: unknown) => { - if (typeof content === "string") return content; + if (typeof content === "string") { + return content; + } if (Array.isArray(content) && content[0]?.type === "text") { return (content[0] as { text?: string }).text; } diff --git a/src/agents/pi-embedded-runner.splitsdktools.test.ts b/src/agents/pi-embedded-runner.splitsdktools.test.ts index 26dccb3472..3eb1b9ef23 100644 --- a/src/agents/pi-embedded-runner.splitsdktools.test.ts +++ b/src/agents/pi-embedded-runner.splitsdktools.test.ts @@ -74,7 +74,9 @@ const _ensureModels = (cfg: OpenClawConfig, agentDir: string) => ensureOpenClawModelsJson(cfg, agentDir) as unknown; const _textFromContent = (content: unknown) => { - if (typeof content === "string") return content; + if (typeof content === "string") { + return content; + } if (Array.isArray(content) && content[0]?.type === "text") { return (content[0] as { text?: string }).text; } diff --git a/src/agents/pi-embedded-runner.test.ts b/src/agents/pi-embedded-runner.test.ts index 4d4300d3a1..3e86deae12 100644 --- a/src/agents/pi-embedded-runner.test.ts +++ b/src/agents/pi-embedded-runner.test.ts @@ -62,11 +62,15 @@ vi.mock("@mariozechner/pi-ai", async () => { return { ...actual, complete: async (model: { api: string; provider: string; id: string }) => { - if (model.id === "mock-error") return buildAssistantErrorMessage(model); + if (model.id === "mock-error") { + return buildAssistantErrorMessage(model); + } return buildAssistantMessage(model); }, completeSimple: async (model: { api: string; provider: string; id: string }) => { - if (model.id === "mock-error") return buildAssistantErrorMessage(model); + if (model.id === "mock-error") { + return buildAssistantErrorMessage(model); + } return buildAssistantMessage(model); }, streamSimple: (model: { api: string; provider: string; id: string }) => { @@ -104,7 +108,9 @@ beforeAll(async () => { }, 20_000); afterAll(async () => { - if (!tempRoot) return; + if (!tempRoot) { + return; + } await fs.rm(tempRoot, { recursive: true, force: true }); tempRoot = undefined; }); @@ -142,7 +148,9 @@ const testSessionKey = "agent:test:embedded"; const immediateEnqueue = async (task: () => Promise) => task(); const textFromContent = (content: unknown) => { - if (typeof content === "string") return content; + if (typeof content === "string") { + return content; + } if (Array.isArray(content) && content[0]?.type === "text") { return (content[0] as { text?: string }).text; } diff --git a/src/agents/pi-embedded-runner/abort.ts b/src/agents/pi-embedded-runner/abort.ts index 5eedc1adf6..43d27fc036 100644 --- a/src/agents/pi-embedded-runner/abort.ts +++ b/src/agents/pi-embedded-runner/abort.ts @@ -1,7 +1,11 @@ export function isAbortError(err: unknown): boolean { - if (!err || typeof err !== "object") return false; + if (!err || typeof err !== "object") { + return false; + } const name = "name" in err ? String(err.name) : ""; - if (name === "AbortError") return true; + if (name === "AbortError") { + return true; + } const message = "message" in err && typeof err.message === "string" ? err.message.toLowerCase() : ""; return message.includes("aborted"); diff --git a/src/agents/pi-embedded-runner/cache-ttl.ts b/src/agents/pi-embedded-runner/cache-ttl.ts index c727bf4399..5a28c2caeb 100644 --- a/src/agents/pi-embedded-runner/cache-ttl.ts +++ b/src/agents/pi-embedded-runner/cache-ttl.ts @@ -11,21 +11,28 @@ export type CacheTtlEntryData = { export function isCacheTtlEligibleProvider(provider: string, modelId: string): boolean { const normalizedProvider = provider.toLowerCase(); const normalizedModelId = modelId.toLowerCase(); - if (normalizedProvider === "anthropic") return true; - if (normalizedProvider === "openrouter" && normalizedModelId.startsWith("anthropic/")) + if (normalizedProvider === "anthropic") { return true; + } + if (normalizedProvider === "openrouter" && normalizedModelId.startsWith("anthropic/")) { + return true; + } return false; } export function readLastCacheTtlTimestamp(sessionManager: unknown): number | null { const sm = sessionManager as { getEntries?: () => CustomEntryLike[] }; - if (!sm?.getEntries) return null; + if (!sm?.getEntries) { + return null; + } try { const entries = sm.getEntries(); let last: number | null = null; for (let i = entries.length - 1; i >= 0; i--) { const entry = entries[i]; - if (entry?.type !== "custom" || entry?.customType !== CACHE_TTL_CUSTOM_TYPE) continue; + if (entry?.type !== "custom" || entry?.customType !== CACHE_TTL_CUSTOM_TYPE) { + continue; + } const data = entry?.data as Partial | undefined; const ts = typeof data?.timestamp === "number" ? data.timestamp : null; if (ts && Number.isFinite(ts)) { @@ -43,7 +50,9 @@ export function appendCacheTtlTimestamp(sessionManager: unknown, data: CacheTtlE const sm = sessionManager as { appendCustomEntry?: (customType: string, data: unknown) => void; }; - if (!sm?.appendCustomEntry) return; + if (!sm?.appendCustomEntry) { + return; + } try { sm.appendCustomEntry(CACHE_TTL_CUSTOM_TYPE, data); } catch { diff --git a/src/agents/pi-embedded-runner/compact.ts b/src/agents/pi-embedded-runner/compact.ts index a49f94a50f..ac1fa1e671 100644 --- a/src/agents/pi-embedded-runner/compact.ts +++ b/src/agents/pi-embedded-runner/compact.ts @@ -250,7 +250,9 @@ export async function compactEmbeddedPiSessionDirect( accountId: params.agentAccountId ?? undefined, }); if (inlineButtonsScope !== "off") { - if (!runtimeCapabilities) runtimeCapabilities = []; + if (!runtimeCapabilities) { + runtimeCapabilities = []; + } if ( !runtimeCapabilities.some((cap) => String(cap).trim().toLowerCase() === "inlinebuttons") ) { diff --git a/src/agents/pi-embedded-runner/extensions.ts b/src/agents/pi-embedded-runner/extensions.ts index 331359e847..26a40b2c58 100644 --- a/src/agents/pi-embedded-runner/extensions.ts +++ b/src/agents/pi-embedded-runner/extensions.ts @@ -45,11 +45,17 @@ function buildContextPruningExtension(params: { model: Model | undefined; }): { additionalExtensionPaths?: string[] } { const raw = params.cfg?.agents?.defaults?.contextPruning; - if (raw?.mode !== "cache-ttl") return {}; - if (!isCacheTtlEligibleProvider(params.provider, params.modelId)) return {}; + if (raw?.mode !== "cache-ttl") { + return {}; + } + if (!isCacheTtlEligibleProvider(params.provider, params.modelId)) { + return {}; + } const settings = computeEffectiveSettings(raw); - if (!settings) return {}; + if (!settings) { + return {}; + } setContextPruningRuntime(params.sessionManager, { settings, diff --git a/src/agents/pi-embedded-runner/extra-params.ts b/src/agents/pi-embedded-runner/extra-params.ts index 8109ff8367..911c67565c 100644 --- a/src/agents/pi-embedded-runner/extra-params.ts +++ b/src/agents/pi-embedded-runner/extra-params.ts @@ -29,9 +29,15 @@ function resolveCacheControlTtl( modelId: string, ): CacheControlTtl | undefined { const raw = extraParams?.cacheControlTtl; - if (raw !== "5m" && raw !== "1h") return undefined; - if (provider === "anthropic") return raw; - if (provider === "openrouter" && modelId.startsWith("anthropic/")) return raw; + if (raw !== "5m" && raw !== "1h") { + return undefined; + } + if (provider === "anthropic") { + return raw; + } + if (provider === "openrouter" && modelId.startsWith("anthropic/")) { + return raw; + } return undefined; } diff --git a/src/agents/pi-embedded-runner/google.ts b/src/agents/pi-embedded-runner/google.ts index 5ea6b84322..764772fe3c 100644 --- a/src/agents/pi-embedded-runner/google.ts +++ b/src/agents/pi-embedded-runner/google.ts @@ -45,10 +45,16 @@ const GOOGLE_SCHEMA_UNSUPPORTED_KEYWORDS = new Set([ const ANTIGRAVITY_SIGNATURE_RE = /^[A-Za-z0-9+/]+={0,2}$/; function isValidAntigravitySignature(value: unknown): value is string { - if (typeof value !== "string") return false; + if (typeof value !== "string") { + return false; + } const trimmed = value.trim(); - if (!trimmed) return false; - if (trimmed.length % 4 !== 0) return false; + if (!trimmed) { + return false; + } + if (trimmed.length % 4 !== 0) { + return false; + } return ANTIGRAVITY_SIGNATURE_RE.test(trimmed); } @@ -113,7 +119,9 @@ function sanitizeAntigravityThinkingBlocks(messages: AgentMessage[]): AgentMessa } function findUnsupportedSchemaKeywords(schema: unknown, path: string): string[] { - if (!schema || typeof schema !== "object") return []; + if (!schema || typeof schema !== "object") { + return []; + } if (Array.isArray(schema)) { return schema.flatMap((item, index) => findUnsupportedSchemaKeywords(item, `${path}[${index}]`), @@ -131,7 +139,9 @@ function findUnsupportedSchemaKeywords(schema: unknown, path: string): string[] } } for (const [key, value] of Object.entries(record)) { - if (key === "properties") continue; + if (key === "properties") { + continue; + } if (GOOGLE_SCHEMA_UNSUPPORTED_KEYWORDS.has(key)) { violations.push(`${path}.${key}`); } @@ -153,7 +163,9 @@ export function sanitizeToolsForGoogle< return params.tools; } return params.tools.map((tool) => { - if (!tool.parameters || typeof tool.parameters !== "object") return tool; + if (!tool.parameters || typeof tool.parameters !== "object") { + return tool; + } return { ...tool, parameters: cleanToolSchemaForGemini( @@ -206,7 +218,9 @@ export function onUnhandledCompactionFailure(cb: CompactionFailureListener): () registerUnhandledRejectionHandler((reason) => { const message = describeUnknownError(reason); - if (!isCompactionFailureError(message)) return false; + if (!isCompactionFailureError(message)) { + return false; + } log.error(`Auto-compaction failed (unhandled): ${message}`); compactionFailureEmitter.emit("failure", message); return true; @@ -228,7 +242,9 @@ function readLastModelSnapshot(sessionManager: SessionManager): ModelSnapshotEnt const entries = sessionManager.getEntries(); for (let i = entries.length - 1; i >= 0; i--) { const entry = entries[i] as CustomEntryLike; - if (entry?.type !== "custom" || entry?.customType !== MODEL_SNAPSHOT_CUSTOM_TYPE) continue; + if (entry?.type !== "custom" || entry?.customType !== MODEL_SNAPSHOT_CUSTOM_TYPE) { + continue; + } const data = entry?.data as ModelSnapshotEntry | undefined; if (data && typeof data === "object") { return data; diff --git a/src/agents/pi-embedded-runner/history.ts b/src/agents/pi-embedded-runner/history.ts index d22a91ae8a..5ece1a8f2f 100644 --- a/src/agents/pi-embedded-runner/history.ts +++ b/src/agents/pi-embedded-runner/history.ts @@ -17,7 +17,9 @@ export function limitHistoryTurns( messages: AgentMessage[], limit: number | undefined, ): AgentMessage[] { - if (!limit || limit <= 0 || messages.length === 0) return messages; + if (!limit || limit <= 0 || messages.length === 0) { + return messages; + } let userCount = 0; let lastUserIndex = messages.length; @@ -42,18 +44,24 @@ export function getDmHistoryLimitFromSessionKey( sessionKey: string | undefined, config: OpenClawConfig | undefined, ): number | undefined { - if (!sessionKey || !config) return undefined; + if (!sessionKey || !config) { + return undefined; + } const parts = sessionKey.split(":").filter(Boolean); const providerParts = parts.length >= 3 && parts[0] === "agent" ? parts.slice(2) : parts; const provider = providerParts[0]?.toLowerCase(); - if (!provider) return undefined; + if (!provider) { + return undefined; + } const kind = providerParts[1]?.toLowerCase(); const userIdRaw = providerParts.slice(2).join(":"); const userId = stripThreadSuffix(userIdRaw); - if (kind !== "dm") return undefined; + if (kind !== "dm") { + return undefined; + } const getLimit = ( providerConfig: @@ -63,7 +71,9 @@ export function getDmHistoryLimitFromSessionKey( } | undefined, ): number | undefined => { - if (!providerConfig) return undefined; + if (!providerConfig) { + return undefined; + } if (userId && providerConfig.dms?.[userId]?.historyLimit !== undefined) { return providerConfig.dms[userId].historyLimit; } @@ -75,9 +85,13 @@ export function getDmHistoryLimitFromSessionKey( providerId: string, ): { dmHistoryLimit?: number; dms?: Record } | undefined => { const channels = cfg?.channels; - if (!channels || typeof channels !== "object") return undefined; + if (!channels || typeof channels !== "object") { + return undefined; + } const entry = (channels as Record)[providerId]; - if (!entry || typeof entry !== "object" || Array.isArray(entry)) return undefined; + if (!entry || typeof entry !== "object" || Array.isArray(entry)) { + return undefined; + } return entry as { dmHistoryLimit?: number; dms?: Record }; }; diff --git a/src/agents/pi-embedded-runner/model.ts b/src/agents/pi-embedded-runner/model.ts index db8b459715..315cd73529 100644 --- a/src/agents/pi-embedded-runner/model.ts +++ b/src/agents/pi-embedded-runner/model.ts @@ -25,7 +25,9 @@ export function buildInlineProviderModels( ): InlineModelEntry[] { return Object.entries(providers).flatMap(([providerId, entry]) => { const trimmed = providerId.trim(); - if (!trimmed) return []; + if (!trimmed) { + return []; + } return (entry?.models ?? []).map((model) => ({ ...model, provider: trimmed, @@ -40,9 +42,13 @@ export function buildModelAliasLines(cfg?: OpenClawConfig) { const entries: Array<{ alias: string; model: string }> = []; for (const [keyRaw, entryRaw] of Object.entries(models)) { const model = String(keyRaw ?? "").trim(); - if (!model) continue; + if (!model) { + continue; + } const alias = String((entryRaw as { alias?: string } | undefined)?.alias ?? "").trim(); - if (!alias) continue; + if (!alias) { + continue; + } entries.push({ alias, model }); } return entries diff --git a/src/agents/pi-embedded-runner/run.overflow-compaction.test.ts b/src/agents/pi-embedded-runner/run.overflow-compaction.test.ts index 13707d22cd..8865eb2ded 100644 --- a/src/agents/pi-embedded-runner/run.overflow-compaction.test.ts +++ b/src/agents/pi-embedded-runner/run.overflow-compaction.test.ts @@ -110,7 +110,9 @@ vi.mock("./run/payloads.js", () => ({ vi.mock("./utils.js", () => ({ describeUnknownError: vi.fn((err: unknown) => { - if (err instanceof Error) return err.message; + if (err instanceof Error) { + return err.message; + } return String(err); }), })); @@ -118,12 +120,16 @@ vi.mock("./utils.js", () => ({ vi.mock("../pi-embedded-helpers.js", async () => { return { isCompactionFailureError: (msg?: string) => { - if (!msg) return false; + if (!msg) { + return false; + } const lower = msg.toLowerCase(); return lower.includes("request_too_large") && lower.includes("summarization failed"); }, isContextOverflowError: (msg?: string) => { - if (!msg) return false; + if (!msg) { + return false; + } const lower = msg.toLowerCase(); return lower.includes("request_too_large") || lower.includes("request size exceeds"); }, diff --git a/src/agents/pi-embedded-runner/run.ts b/src/agents/pi-embedded-runner/run.ts index 1d50106794..7f571e4a32 100644 --- a/src/agents/pi-embedded-runner/run.ts +++ b/src/agents/pi-embedded-runner/run.ts @@ -60,7 +60,9 @@ const ANTHROPIC_MAGIC_STRING_TRIGGER_REFUSAL = "ANTHROPIC_MAGIC_STRING_TRIGGER_R const ANTHROPIC_MAGIC_STRING_REPLACEMENT = "ANTHROPIC MAGIC STRING TRIGGER REFUSAL (redacted)"; function scrubAnthropicRefusalMagic(prompt: string): string { - if (!prompt.includes(ANTHROPIC_MAGIC_STRING_TRIGGER_REFUSAL)) return prompt; + if (!prompt.includes(ANTHROPIC_MAGIC_STRING_TRIGGER_REFUSAL)) { + return prompt; + } return prompt.replaceAll( ANTHROPIC_MAGIC_STRING_TRIGGER_REFUSAL, ANTHROPIC_MAGIC_STRING_REPLACEMENT, @@ -174,7 +176,9 @@ export async function runEmbeddedPiAgent( allInCooldown: boolean; message: string; }): FailoverReason => { - if (params.allInCooldown) return "rate_limit"; + if (params.allInCooldown) { + return "rate_limit"; + } const classified = classifyFailoverReason(params.message); return classified ?? "auth"; }; @@ -202,7 +206,9 @@ export async function runEmbeddedPiAgent( cause: params.error, }); } - if (params.error instanceof Error) throw params.error; + if (params.error instanceof Error) { + throw params.error; + } throw new Error(message); }; @@ -242,7 +248,9 @@ export async function runEmbeddedPiAgent( }; const advanceAuthProfile = async (): Promise => { - if (lockedProfileId) return false; + if (lockedProfileId) { + return false; + } let nextIndex = profileIndex + 1; while (nextIndex < profileCandidates.length) { const candidate = profileCandidates[nextIndex]; @@ -257,7 +265,9 @@ export async function runEmbeddedPiAgent( attemptedThinking.clear(); return true; } catch (err) { - if (candidate && candidate === lockedProfileId) throw err; + if (candidate && candidate === lockedProfileId) { + throw err; + } nextIndex += 1; } } @@ -282,7 +292,9 @@ export async function runEmbeddedPiAgent( throwAuthProfileFailover({ allInCooldown: true }); } } catch (err) { - if (err instanceof FailoverError) throw err; + if (err instanceof FailoverError) { + throw err; + } if (profileCandidates[profileIndex] === lockedProfileId) { throwAuthProfileFailover({ allInCooldown: false, error: err }); } @@ -578,7 +590,9 @@ export async function runEmbeddedPiAgent( } const rotated = await advanceAuthProfile(); - if (rotated) continue; + if (rotated) { + continue; + } if (fallbackConfigured) { // Prefer formatted error message (user-friendly) over raw errorMessage diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index c4b5eff981..f1efbd97e2 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -95,12 +95,16 @@ export function injectHistoryImagesIntoMessages( messages: AgentMessage[], historyImagesByIndex: Map, ): boolean { - if (historyImagesByIndex.size === 0) return false; + if (historyImagesByIndex.size === 0) { + return false; + } let didMutate = false; for (const [msgIndex, images] of historyImagesByIndex) { // Bounds check: ensure index is valid before accessing - if (msgIndex < 0 || msgIndex >= messages.length) continue; + if (msgIndex < 0 || msgIndex >= messages.length) { + continue; + } const msg = messages[msgIndex]; if (msg && msg.role === "user") { // Convert string content to array format if needed @@ -256,7 +260,9 @@ export async function runEmbeddedAttempt( accountId: params.agentAccountId ?? undefined, }); if (inlineButtonsScope !== "off") { - if (!runtimeCapabilities) runtimeCapabilities = []; + if (!runtimeCapabilities) { + runtimeCapabilities = []; + } if ( !runtimeCapabilities.some((cap) => String(cap).trim().toLowerCase() === "inlinebuttons") ) { @@ -575,7 +581,9 @@ export async function runEmbeddedAttempt( }; const abortRun = (isTimeout = false, reason?: unknown) => { aborted = true; - if (isTimeout) timedOut = true; + if (isTimeout) { + timedOut = true; + } if (isTimeout) { runAbortController.abort(reason ?? makeTimeoutAbortReason()); } else { @@ -660,7 +668,9 @@ export async function runEmbeddedAttempt( abortRun(true); if (!abortWarnTimer) { abortWarnTimer = setTimeout(() => { - if (!activeSession.isStreaming) return; + if (!activeSession.isStreaming) { + return; + } if (!isProbeSession) { log.warn( `embedded run abort still streaming: runId=${params.runId} sessionId=${params.sessionId}`, @@ -808,7 +818,9 @@ export async function runEmbeddedAttempt( await waitForCompactionRetry(); } catch (err) { if (isAbortError(err)) { - if (!promptError) promptError = err; + if (!promptError) { + promptError = err; + } } else { throw err; } @@ -846,7 +858,9 @@ export async function runEmbeddedAttempt( } } finally { clearTimeout(abortTimer); - if (abortWarnTimer) clearTimeout(abortWarnTimer); + if (abortWarnTimer) { + clearTimeout(abortWarnTimer); + } unsubscribe(); clearActiveEmbeddedRun(params.sessionId, queueHandle); params.abortSignal?.removeEventListener?.("abort", onAbort); diff --git a/src/agents/pi-embedded-runner/run/images.ts b/src/agents/pi-embedded-runner/run/images.ts index 6bb7bef9b0..9cd32940da 100644 --- a/src/agents/pi-embedded-runner/run/images.ts +++ b/src/agents/pi-embedded-runner/run/images.ts @@ -80,9 +80,15 @@ export function detectImageReferences(prompt: string): DetectedImageRef[] { // Helper to add a path ref const addPathRef = (raw: string) => { const trimmed = raw.trim(); - if (!trimmed || seen.has(trimmed.toLowerCase())) return; - if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return; - if (!isImageExtension(trimmed)) return; + if (!trimmed || seen.has(trimmed.toLowerCase())) { + return; + } + if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) { + return; + } + if (!isImageExtension(trimmed)) { + return; + } seen.add(trimmed.toLowerCase()); const resolved = trimmed.startsWith("~") ? resolveUserPath(trimmed) : trimmed; refs.push({ raw: trimmed, type: "path", resolved }); @@ -118,7 +124,9 @@ export function detectImageReferences(prompt: string): DetectedImageRef[] { /\[Image:\s*source:\s*([^\]]+\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif))\]/gi; while ((match = messageImagePattern.exec(prompt)) !== null) { const raw = match[1]?.trim(); - if (raw) addPathRef(raw); + if (raw) { + addPathRef(raw); + } } // Remote HTTP(S) URLs are intentionally ignored. Native image injection is local-only. @@ -127,7 +135,9 @@ export function detectImageReferences(prompt: string): DetectedImageRef[] { const fileUrlPattern = /file:\/\/[^\s<>"'`\]]+\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif)/gi; while ((match = fileUrlPattern.exec(prompt)) !== null) { const raw = match[0]; - if (seen.has(raw.toLowerCase())) continue; + if (seen.has(raw.toLowerCase())) { + continue; + } seen.add(raw.toLowerCase()); // Use fileURLToPath for proper handling (e.g., file://localhost/path) try { @@ -148,7 +158,9 @@ export function detectImageReferences(prompt: string): DetectedImageRef[] { /(?:^|\s|["'`(])((\.\.?\/|[~/])[^\s"'`()[\]]*\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif))/gi; while ((match = pathPattern.exec(prompt)) !== null) { // Use capture group 1 (the path without delimiter prefix); skip if undefined - if (match[1]) addPathRef(match[1]); + if (match[1]) { + addPathRef(match[1]); + } } return refs; @@ -267,9 +279,13 @@ function detectImagesFromHistory(messages: unknown[]): DetectedImageRef[] { const seen = new Set(); const messageHasImageContent = (msg: unknown): boolean => { - if (!msg || typeof msg !== "object") return false; + if (!msg || typeof msg !== "object") { + return false; + } const content = (msg as { content?: unknown }).content; - if (!Array.isArray(content)) return false; + if (!Array.isArray(content)) { + return false; + } return content.some( (part) => part != null && typeof part === "object" && (part as { type?: string }).type === "image", @@ -278,20 +294,30 @@ function detectImagesFromHistory(messages: unknown[]): DetectedImageRef[] { for (let i = 0; i < messages.length; i++) { const msg = messages[i]; - if (!msg || typeof msg !== "object") continue; + if (!msg || typeof msg !== "object") { + continue; + } const message = msg as { role?: string }; // Only scan user messages for image references - if (message.role !== "user") continue; + if (message.role !== "user") { + continue; + } // Skip if message already has image content (prevents reloading each turn) - if (messageHasImageContent(msg)) continue; + if (messageHasImageContent(msg)) { + continue; + } const text = extractTextFromMessage(msg); - if (!text) continue; + if (!text) { + continue; + } const refs = detectImageReferences(text); for (const ref of refs) { const key = ref.resolved.toLowerCase(); - if (seen.has(key)) continue; + if (seen.has(key)) { + continue; + } seen.add(key); allRefs.push({ ...ref, messageIndex: i }); } diff --git a/src/agents/pi-embedded-runner/run/payloads.ts b/src/agents/pi-embedded-runner/run/payloads.ts index 567314f9f7..bc4263d51c 100644 --- a/src/agents/pi-embedded-runner/run/payloads.ts +++ b/src/agents/pi-embedded-runner/run/payloads.ts @@ -76,7 +76,9 @@ export function buildEmbeddedRunPayloads(params: { : null; const normalizedErrorText = errorText ? normalizeTextForComparison(errorText) : null; const genericErrorText = "The AI service returned an error. Please try again."; - if (errorText) replyItems.push({ text: errorText, isError: true }); + if (errorText) { + replyItems.push({ text: errorText, isError: true }); + } const inlineToolResults = params.inlineToolResultsAllowed && params.verboseLevel !== "off" && params.toolMetas.length > 0; @@ -110,31 +112,51 @@ export function buildEmbeddedRunPayloads(params: { params.lastAssistant && params.reasoningLevel === "on" ? formatReasoningMessage(extractAssistantThinking(params.lastAssistant)) : ""; - if (reasoningText) replyItems.push({ text: reasoningText }); + if (reasoningText) { + replyItems.push({ text: reasoningText }); + } const fallbackAnswerText = params.lastAssistant ? extractAssistantText(params.lastAssistant) : ""; const shouldSuppressRawErrorText = (text: string) => { - if (!lastAssistantErrored) return false; + if (!lastAssistantErrored) { + return false; + } const trimmed = text.trim(); - if (!trimmed) return false; + if (!trimmed) { + return false; + } if (errorText) { const normalized = normalizeTextForComparison(trimmed); - if (normalized && normalizedErrorText && normalized === normalizedErrorText) return true; - if (trimmed === genericErrorText) return true; + if (normalized && normalizedErrorText && normalized === normalizedErrorText) { + return true; + } + if (trimmed === genericErrorText) { + return true; + } + } + if (rawErrorMessage && trimmed === rawErrorMessage) { + return true; + } + if (formattedRawErrorMessage && trimmed === formattedRawErrorMessage) { + return true; } - if (rawErrorMessage && trimmed === rawErrorMessage) return true; - if (formattedRawErrorMessage && trimmed === formattedRawErrorMessage) return true; if (normalizedRawErrorText) { const normalized = normalizeTextForComparison(trimmed); - if (normalized && normalized === normalizedRawErrorText) return true; + if (normalized && normalized === normalizedRawErrorText) { + return true; + } } if (normalizedFormattedRawErrorMessage) { const normalized = normalizeTextForComparison(trimmed); - if (normalized && normalized === normalizedFormattedRawErrorMessage) return true; + if (normalized && normalized === normalizedFormattedRawErrorMessage) { + return true; + } } if (rawErrorFingerprint) { const fingerprint = getApiErrorPayloadFingerprint(trimmed); - if (fingerprint && fingerprint === rawErrorFingerprint) return true; + if (fingerprint && fingerprint === rawErrorFingerprint) { + return true; + } } return isRawApiErrorPayload(trimmed); }; @@ -222,8 +244,12 @@ export function buildEmbeddedRunPayloads(params: { audioAsVoice: item.audioAsVoice || Boolean(hasAudioAsVoiceTag && item.media?.length), })) .filter((p) => { - if (!p.text && !p.mediaUrl && (!p.mediaUrls || p.mediaUrls.length === 0)) return false; - if (p.text && isSilentReplyText(p.text, SILENT_REPLY_TOKEN)) return false; + if (!p.text && !p.mediaUrl && (!p.mediaUrls || p.mediaUrls.length === 0)) { + return false; + } + if (p.text && isSilentReplyText(p.text, SILENT_REPLY_TOKEN)) { + return false; + } return true; }); } diff --git a/src/agents/pi-embedded-runner/runs.ts b/src/agents/pi-embedded-runner/runs.ts index 042c12f061..f5ca972108 100644 --- a/src/agents/pi-embedded-runner/runs.ts +++ b/src/agents/pi-embedded-runner/runs.ts @@ -58,12 +58,16 @@ export function isEmbeddedPiRunActive(sessionId: string): boolean { export function isEmbeddedPiRunStreaming(sessionId: string): boolean { const handle = ACTIVE_EMBEDDED_RUNS.get(sessionId); - if (!handle) return false; + if (!handle) { + return false; + } return handle.isStreaming(); } export function waitForEmbeddedPiRunEnd(sessionId: string, timeoutMs = 15_000): Promise { - if (!sessionId || !ACTIVE_EMBEDDED_RUNS.has(sessionId)) return Promise.resolve(true); + if (!sessionId || !ACTIVE_EMBEDDED_RUNS.has(sessionId)) { + return Promise.resolve(true); + } diag.debug(`waiting for run end: sessionId=${sessionId} timeoutMs=${timeoutMs}`); return new Promise((resolve) => { const waiters = EMBEDDED_RUN_WAITERS.get(sessionId) ?? new Set(); @@ -72,7 +76,9 @@ export function waitForEmbeddedPiRunEnd(sessionId: string, timeoutMs = 15_000): timer: setTimeout( () => { waiters.delete(waiter); - if (waiters.size === 0) EMBEDDED_RUN_WAITERS.delete(sessionId); + if (waiters.size === 0) { + EMBEDDED_RUN_WAITERS.delete(sessionId); + } diag.warn(`wait timeout: sessionId=${sessionId} timeoutMs=${timeoutMs}`); resolve(false); }, @@ -83,7 +89,9 @@ export function waitForEmbeddedPiRunEnd(sessionId: string, timeoutMs = 15_000): EMBEDDED_RUN_WAITERS.set(sessionId, waiters); if (!ACTIVE_EMBEDDED_RUNS.has(sessionId)) { waiters.delete(waiter); - if (waiters.size === 0) EMBEDDED_RUN_WAITERS.delete(sessionId); + if (waiters.size === 0) { + EMBEDDED_RUN_WAITERS.delete(sessionId); + } clearTimeout(waiter.timer); resolve(true); } @@ -92,7 +100,9 @@ export function waitForEmbeddedPiRunEnd(sessionId: string, timeoutMs = 15_000): function notifyEmbeddedRunEnded(sessionId: string) { const waiters = EMBEDDED_RUN_WAITERS.get(sessionId); - if (!waiters || waiters.size === 0) return; + if (!waiters || waiters.size === 0) { + return; + } EMBEDDED_RUN_WAITERS.delete(sessionId); diag.debug(`notifying waiters: sessionId=${sessionId} waiterCount=${waiters.size}`); for (const waiter of waiters) { diff --git a/src/agents/pi-embedded-runner/sandbox-info.ts b/src/agents/pi-embedded-runner/sandbox-info.ts index a72797c9c4..a81ae114c7 100644 --- a/src/agents/pi-embedded-runner/sandbox-info.ts +++ b/src/agents/pi-embedded-runner/sandbox-info.ts @@ -6,7 +6,9 @@ export function buildEmbeddedSandboxInfo( sandbox?: Awaited>, execElevated?: ExecElevatedDefaults, ): EmbeddedSandboxInfo | undefined { - if (!sandbox?.enabled) return undefined; + if (!sandbox?.enabled) { + return undefined; + } const elevatedAllowed = Boolean(execElevated?.enabled && execElevated.allowed); return { enabled: true, diff --git a/src/agents/pi-embedded-runner/session-manager-cache.ts b/src/agents/pi-embedded-runner/session-manager-cache.ts index a429420751..4d81086c0f 100644 --- a/src/agents/pi-embedded-runner/session-manager-cache.ts +++ b/src/agents/pi-embedded-runner/session-manager-cache.ts @@ -23,7 +23,9 @@ function isSessionManagerCacheEnabled(): boolean { } export function trackSessionManagerAccess(sessionFile: string): void { - if (!isSessionManagerCacheEnabled()) return; + if (!isSessionManagerCacheEnabled()) { + return; + } const now = Date.now(); SESSION_MANAGER_CACHE.set(sessionFile, { sessionFile, @@ -32,17 +34,25 @@ export function trackSessionManagerAccess(sessionFile: string): void { } function isSessionManagerCached(sessionFile: string): boolean { - if (!isSessionManagerCacheEnabled()) return false; + if (!isSessionManagerCacheEnabled()) { + return false; + } const entry = SESSION_MANAGER_CACHE.get(sessionFile); - if (!entry) return false; + if (!entry) { + return false; + } const now = Date.now(); const ttl = getSessionManagerTtl(); return now - entry.loadedAt <= ttl; } export async function prewarmSessionFile(sessionFile: string): Promise { - if (!isSessionManagerCacheEnabled()) return; - if (isSessionManagerCached(sessionFile)) return; + if (!isSessionManagerCacheEnabled()) { + return; + } + if (isSessionManagerCached(sessionFile)) { + return; + } try { // Read a small chunk to encourage OS page cache warmup. diff --git a/src/agents/pi-embedded-runner/utils.ts b/src/agents/pi-embedded-runner/utils.ts index a95c18330e..02daedec87 100644 --- a/src/agents/pi-embedded-runner/utils.ts +++ b/src/agents/pi-embedded-runner/utils.ts @@ -5,19 +5,27 @@ import type { ExecToolDefaults } from "../bash-tools.js"; export function mapThinkingLevel(level?: ThinkLevel): ThinkingLevel { // pi-agent-core supports "xhigh"; OpenClaw enables it for specific models. - if (!level) return "off"; + if (!level) { + return "off"; + } return level; } export function resolveExecToolDefaults(config?: OpenClawConfig): ExecToolDefaults | undefined { const tools = config?.tools; - if (!tools?.exec) return undefined; + if (!tools?.exec) { + return undefined; + } return tools.exec; } export function describeUnknownError(error: unknown): string { - if (error instanceof Error) return error.message; - if (typeof error === "string") return error; + if (error instanceof Error) { + return error.message; + } + if (typeof error === "string") { + return error; + } try { const serialized = JSON.stringify(error); return serialized ?? "Unknown error"; diff --git a/src/agents/pi-embedded-subscribe.handlers.messages.ts b/src/agents/pi-embedded-subscribe.handlers.messages.ts index be5a3d58c9..e89d254c67 100644 --- a/src/agents/pi-embedded-subscribe.handlers.messages.ts +++ b/src/agents/pi-embedded-subscribe.handlers.messages.ts @@ -23,7 +23,9 @@ export function handleMessageStart( evt: AgentEvent & { message: AgentMessage }, ) { const msg = evt.message; - if (msg?.role !== "assistant") return; + if (msg?.role !== "assistant") { + return; + } // KNOWN: Resetting at `text_end` is unsafe (late/duplicate end events). // ASSUME: `message_start` is the only reliable boundary for “new assistant message begins”. @@ -40,7 +42,9 @@ export function handleMessageUpdate( evt: AgentEvent & { message: AgentMessage; assistantMessageEvent?: unknown }, ) { const msg = evt.message; - if (msg?.role !== "assistant") return; + if (msg?.role !== "assistant") { + return; + } const assistantEvent = evt.assistantMessageEvent; const assistantRecord = @@ -159,7 +163,9 @@ export function handleMessageEnd( evt: AgentEvent & { message: AgentMessage }, ) { const msg = evt.message; - if (msg?.role !== "assistant") return; + if (msg?.role !== "assistant") { + return; + } const assistantMessage = msg; promoteThinkingTagsToBlocks(assistantMessage); @@ -195,12 +201,16 @@ export function handleMessageEnd( const shouldEmitReasoningBeforeAnswer = shouldEmitReasoning && ctx.state.blockReplyBreak === "message_end" && !addedDuringMessage; const maybeEmitReasoning = () => { - if (!shouldEmitReasoning || !formattedReasoning) return; + if (!shouldEmitReasoning || !formattedReasoning) { + return; + } ctx.state.lastReasoningSent = formattedReasoning; void onBlockReply?.({ text: formattedReasoning }); }; - if (shouldEmitReasoningBeforeAnswer) maybeEmitReasoning(); + if (shouldEmitReasoningBeforeAnswer) { + maybeEmitReasoning(); + } if ( (ctx.state.blockReplyBreak === "message_end" || @@ -251,7 +261,9 @@ export function handleMessageEnd( } } - if (!shouldEmitReasoningBeforeAnswer) maybeEmitReasoning(); + if (!shouldEmitReasoningBeforeAnswer) { + maybeEmitReasoning(); + } if (ctx.state.streamReasoning && rawThinking) { ctx.emitReasoningStream(rawThinking); } diff --git a/src/agents/pi-embedded-subscribe.handlers.tools.ts b/src/agents/pi-embedded-subscribe.handlers.tools.ts index 129042273e..c8017c18d0 100644 --- a/src/agents/pi-embedded-subscribe.handlers.tools.ts +++ b/src/agents/pi-embedded-subscribe.handlers.tools.ts @@ -16,13 +16,23 @@ import { normalizeToolName } from "./tool-policy.js"; function extendExecMeta(toolName: string, args: unknown, meta?: string): string | undefined { const normalized = toolName.trim().toLowerCase(); - if (normalized !== "exec" && normalized !== "bash") return meta; - if (!args || typeof args !== "object") return meta; + if (normalized !== "exec" && normalized !== "bash") { + return meta; + } + if (!args || typeof args !== "object") { + return meta; + } const record = args as Record; const flags: string[] = []; - if (record.pty === true) flags.push("pty"); - if (record.elevated === true) flags.push("elevated"); - if (flags.length === 0) return meta; + if (record.pty === true) { + flags.push("pty"); + } + if (record.elevated === true) { + flags.push("elevated"); + } + if (flags.length === 0) { + return meta; + } const suffix = flags.join(" · "); return meta ? `${meta} · ${suffix}` : suffix; } diff --git a/src/agents/pi-embedded-subscribe.raw-stream.ts b/src/agents/pi-embedded-subscribe.raw-stream.ts index 8fc2743b38..308d488999 100644 --- a/src/agents/pi-embedded-subscribe.raw-stream.ts +++ b/src/agents/pi-embedded-subscribe.raw-stream.ts @@ -12,7 +12,9 @@ const RAW_STREAM_PATH = let rawStreamReady = false; export function appendRawStream(payload: Record) { - if (!RAW_STREAM_ENABLED) return; + if (!RAW_STREAM_ENABLED) { + return; + } if (!rawStreamReady) { rawStreamReady = true; try { diff --git a/src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.splits-long-single-line-fenced-blocks-reopen.test.ts b/src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.splits-long-single-line-fenced-blocks-reopen.test.ts index aef772deb0..19cbeaa2a4 100644 --- a/src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.splits-long-single-line-fenced-blocks-reopen.test.ts +++ b/src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.splits-long-single-line-fenced-blocks-reopen.test.ts @@ -70,7 +70,9 @@ describe("subscribeEmbeddedPiSession", () => { listeners.push(listener); return () => { const index = listeners.indexOf(listener); - if (index !== -1) listeners.splice(index, 1); + if (index !== -1) { + listeners.splice(index, 1); + } }; }, } as unknown as Parameters[0]["session"]; diff --git a/src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.waits-multiple-compaction-retries-before-resolving.test.ts b/src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.waits-multiple-compaction-retries-before-resolving.test.ts index 6d31dae660..c9ca1eeca6 100644 --- a/src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.waits-multiple-compaction-retries-before-resolving.test.ts +++ b/src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.waits-multiple-compaction-retries-before-resolving.test.ts @@ -67,8 +67,12 @@ describe("subscribeEmbeddedPiSession", () => { const events: Array<{ phase: string; willRetry?: boolean }> = []; const stop = onAgentEvent((evt) => { - if (evt.runId !== "run-compaction") return; - if (evt.stream !== "compaction") return; + if (evt.runId !== "run-compaction") { + return; + } + if (evt.stream !== "compaction") { + return; + } const phase = typeof evt.data?.phase === "string" ? evt.data.phase : ""; events.push({ phase, diff --git a/src/agents/pi-embedded-subscribe.tools.ts b/src/agents/pi-embedded-subscribe.tools.ts index 195a70a645..a979d8723a 100644 --- a/src/agents/pi-embedded-subscribe.tools.ts +++ b/src/agents/pi-embedded-subscribe.tools.ts @@ -7,48 +7,72 @@ const TOOL_RESULT_MAX_CHARS = 8000; const TOOL_ERROR_MAX_CHARS = 400; function truncateToolText(text: string): string { - if (text.length <= TOOL_RESULT_MAX_CHARS) return text; + if (text.length <= TOOL_RESULT_MAX_CHARS) { + return text; + } return `${truncateUtf16Safe(text, TOOL_RESULT_MAX_CHARS)}\n…(truncated)…`; } function normalizeToolErrorText(text: string): string | undefined { const trimmed = text.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } const firstLine = trimmed.split(/\r?\n/)[0]?.trim() ?? ""; - if (!firstLine) return undefined; + if (!firstLine) { + return undefined; + } return firstLine.length > TOOL_ERROR_MAX_CHARS ? `${truncateUtf16Safe(firstLine, TOOL_ERROR_MAX_CHARS)}…` : firstLine; } function readErrorCandidate(value: unknown): string | undefined { - if (typeof value === "string") return normalizeToolErrorText(value); - if (!value || typeof value !== "object") return undefined; + if (typeof value === "string") { + return normalizeToolErrorText(value); + } + if (!value || typeof value !== "object") { + return undefined; + } const record = value as Record; - if (typeof record.message === "string") return normalizeToolErrorText(record.message); - if (typeof record.error === "string") return normalizeToolErrorText(record.error); + if (typeof record.message === "string") { + return normalizeToolErrorText(record.message); + } + if (typeof record.error === "string") { + return normalizeToolErrorText(record.error); + } return undefined; } function extractErrorField(value: unknown): string | undefined { - if (!value || typeof value !== "object") return undefined; + if (!value || typeof value !== "object") { + return undefined; + } const record = value as Record; const direct = readErrorCandidate(record.error) ?? readErrorCandidate(record.message) ?? readErrorCandidate(record.reason); - if (direct) return direct; + if (direct) { + return direct; + } const status = typeof record.status === "string" ? record.status.trim() : ""; return status ? normalizeToolErrorText(status) : undefined; } export function sanitizeToolResult(result: unknown): unknown { - if (!result || typeof result !== "object") return result; + if (!result || typeof result !== "object") { + return result; + } const record = result as Record; const content = Array.isArray(record.content) ? record.content : null; - if (!content) return record; + if (!content) { + return record; + } const sanitized = content.map((item) => { - if (!item || typeof item !== "object") return item; + if (!item || typeof item !== "object") { + return item; + } const entry = item as Record; const type = typeof entry.type === "string" ? entry.type : undefined; if (type === "text" && typeof entry.text === "string") { @@ -67,47 +91,73 @@ export function sanitizeToolResult(result: unknown): unknown { } export function extractToolResultText(result: unknown): string | undefined { - if (!result || typeof result !== "object") return undefined; + if (!result || typeof result !== "object") { + return undefined; + } const record = result as Record; const content = Array.isArray(record.content) ? record.content : null; - if (!content) return undefined; + if (!content) { + return undefined; + } const texts = content .map((item) => { - if (!item || typeof item !== "object") return undefined; + if (!item || typeof item !== "object") { + return undefined; + } const entry = item as Record; - if (entry.type !== "text" || typeof entry.text !== "string") return undefined; + if (entry.type !== "text" || typeof entry.text !== "string") { + return undefined; + } const trimmed = entry.text.trim(); return trimmed ? trimmed : undefined; }) .filter((value): value is string => Boolean(value)); - if (texts.length === 0) return undefined; + if (texts.length === 0) { + return undefined; + } return texts.join("\n"); } export function isToolResultError(result: unknown): boolean { - if (!result || typeof result !== "object") return false; + if (!result || typeof result !== "object") { + return false; + } const record = result as { details?: unknown }; const details = record.details; - if (!details || typeof details !== "object") return false; + if (!details || typeof details !== "object") { + return false; + } const status = (details as { status?: unknown }).status; - if (typeof status !== "string") return false; + if (typeof status !== "string") { + return false; + } const normalized = status.trim().toLowerCase(); return normalized === "error" || normalized === "timeout"; } export function extractToolErrorMessage(result: unknown): string | undefined { - if (!result || typeof result !== "object") return undefined; + if (!result || typeof result !== "object") { + return undefined; + } const record = result as Record; const fromDetails = extractErrorField(record.details); - if (fromDetails) return fromDetails; + if (fromDetails) { + return fromDetails; + } const fromRoot = extractErrorField(record); - if (fromRoot) return fromRoot; + if (fromRoot) { + return fromRoot; + } const text = extractToolResultText(result); - if (!text) return undefined; + if (!text) { + return undefined; + } try { const parsed = JSON.parse(text) as unknown; const fromJson = extractErrorField(parsed); - if (fromJson) return fromJson; + if (fromJson) { + return fromJson; + } } catch { // Fall through to first-line text fallback. } @@ -123,9 +173,13 @@ export function extractMessagingToolSend( const accountIdRaw = typeof args.accountId === "string" ? args.accountId.trim() : undefined; const accountId = accountIdRaw ? accountIdRaw : undefined; if (toolName === "message") { - if (action !== "send" && action !== "thread-reply") return undefined; + if (action !== "send" && action !== "thread-reply") { + return undefined; + } const toRaw = typeof args.to === "string" ? args.to : undefined; - if (!toRaw) return undefined; + if (!toRaw) { + return undefined; + } const providerRaw = typeof args.provider === "string" ? args.provider.trim() : ""; const channelRaw = typeof args.channel === "string" ? args.channel.trim() : ""; const providerHint = providerRaw || channelRaw; @@ -135,10 +189,14 @@ export function extractMessagingToolSend( return to ? { tool: toolName, provider, accountId, to } : undefined; } const providerId = normalizeChannelId(toolName); - if (!providerId) return undefined; + if (!providerId) { + return undefined; + } const plugin = getChannelPlugin(providerId); const extracted = plugin?.actions?.extractToolSend?.({ args }); - if (!extracted?.to) return undefined; + if (!extracted?.to) { + return undefined; + } const to = normalizeTargetForProvider(providerId, extracted.to); return to ? { diff --git a/src/agents/pi-embedded-subscribe.ts b/src/agents/pi-embedded-subscribe.ts index a4a4b906a3..2e58b4af30 100644 --- a/src/agents/pi-embedded-subscribe.ts +++ b/src/agents/pi-embedded-subscribe.ts @@ -106,17 +106,27 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar }; const shouldSkipAssistantText = (text: string) => { - if (state.lastAssistantTextMessageIndex !== state.assistantMessageIndex) return false; + if (state.lastAssistantTextMessageIndex !== state.assistantMessageIndex) { + return false; + } const trimmed = text.trimEnd(); - if (trimmed && trimmed === state.lastAssistantTextTrimmed) return true; + if (trimmed && trimmed === state.lastAssistantTextTrimmed) { + return true; + } const normalized = normalizeTextForComparison(text); - if (normalized.length > 0 && normalized === state.lastAssistantTextNormalized) return true; + if (normalized.length > 0 && normalized === state.lastAssistantTextNormalized) { + return true; + } return false; }; const pushAssistantText = (text: string) => { - if (!text) return; - if (shouldSkipAssistantText(text)) return; + if (!text) { + return; + } + if (shouldSkipAssistantText(text)) { + return; + } assistantTexts.push(text); rememberAssistantText(text); }; @@ -184,7 +194,9 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar }; const resolveCompactionRetry = () => { - if (state.pendingCompactionRetry <= 0) return; + if (state.pendingCompactionRetry <= 0) { + return; + } state.pendingCompactionRetry -= 1; if (state.pendingCompactionRetry === 0 && !state.compactionInFlight) { state.compactionRetryResolve?.(); @@ -216,17 +228,25 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar : params.verboseLevel === "full"; const formatToolOutputBlock = (text: string) => { const trimmed = text.trim(); - if (!trimmed) return "(no output)"; - if (!useMarkdown) return trimmed; + if (!trimmed) { + return "(no output)"; + } + if (!useMarkdown) { + return trimmed; + } return `\`\`\`txt\n${trimmed}\n\`\`\``; }; const emitToolSummary = (toolName?: string, meta?: string) => { - if (!params.onToolResult) return; + if (!params.onToolResult) { + return; + } const agg = formatToolAggregate(toolName, meta ? [meta] : undefined, { markdown: useMarkdown, }); const { text: cleanedText, mediaUrls } = parseReplyDirectives(agg); - if (!cleanedText && (!mediaUrls || mediaUrls.length === 0)) return; + if (!cleanedText && (!mediaUrls || mediaUrls.length === 0)) { + return; + } try { void params.onToolResult({ text: cleanedText, @@ -237,13 +257,17 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar } }; const emitToolOutput = (toolName?: string, meta?: string, output?: string) => { - if (!params.onToolResult || !output) return; + if (!params.onToolResult || !output) { + return; + } const agg = formatToolAggregate(toolName, meta ? [meta] : undefined, { markdown: useMarkdown, }); const message = `${agg}\n${formatToolOutputBlock(output)}`; const { text: cleanedText, mediaUrls } = parseReplyDirectives(message); - if (!cleanedText && (!mediaUrls || mediaUrls.length === 0)) return; + if (!cleanedText && (!mediaUrls || mediaUrls.length === 0)) { + return; + } try { void params.onToolResult({ text: cleanedText, @@ -258,7 +282,9 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar text: string, state: { thinking: boolean; final: boolean; inlineCode?: InlineCodeState }, ): string => { - if (!text) return text; + if (!text) { + return text; + } const inlineStateStart = state.inlineCode ?? createInlineCodeState(); const codeSpans = buildCodeSpanIndex(text, inlineStateStart); @@ -270,7 +296,9 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar let inThinking = state.thinking; for (const match of text.matchAll(THINKING_TAG_SCAN_RE)) { const idx = match.index ?? 0; - if (codeSpans.isInside(idx)) continue; + if (codeSpans.isInside(idx)) { + continue; + } if (!inThinking) { processed += text.slice(lastIndex, idx); } @@ -303,7 +331,9 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar for (const match of processed.matchAll(FINAL_TAG_SCAN_RE)) { const idx = match.index ?? 0; - if (finalCodeSpans.isInside(idx)) continue; + if (finalCodeSpans.isInside(idx)) { + continue; + } const isClose = match[1] === "/"; if (!inFinal && !isClose) { @@ -348,7 +378,9 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar pattern.lastIndex = 0; for (const match of text.matchAll(pattern)) { const idx = match.index ?? 0; - if (isInside(idx)) continue; + if (isInside(idx)) { + continue; + } output += text.slice(lastIndex, idx); lastIndex = idx + match[0].length; } @@ -357,11 +389,17 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar }; const emitBlockChunk = (text: string) => { - if (state.suppressBlockChunks) return; + if (state.suppressBlockChunks) { + return; + } // Strip and blocks across chunk boundaries to avoid leaking reasoning. const chunk = stripBlockTags(text, state.blockState).trimEnd(); - if (!chunk) return; - if (chunk === state.lastBlockReplyText) return; + if (!chunk) { + return; + } + if (chunk === state.lastBlockReplyText) { + return; + } // Only check committed (successful) messaging tool texts - checking pending texts // is risky because if the tool fails after suppression, the user gets no response @@ -371,14 +409,20 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar return; } - if (shouldSkipAssistantText(chunk)) return; + if (shouldSkipAssistantText(chunk)) { + return; + } state.lastBlockReplyText = chunk; assistantTexts.push(chunk); rememberAssistantText(chunk); - if (!params.onBlockReply) return; + if (!params.onBlockReply) { + return; + } const splitResult = replyDirectiveAccumulator.consume(chunk); - if (!splitResult) return; + if (!splitResult) { + return; + } const { text: cleanedText, mediaUrls, @@ -388,7 +432,9 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar replyToCurrent, } = splitResult; // Skip empty payloads, but always emit if audioAsVoice is set (to propagate the flag) - if (!cleanedText && (!mediaUrls || mediaUrls.length === 0) && !audioAsVoice) return; + if (!cleanedText && (!mediaUrls || mediaUrls.length === 0) && !audioAsVoice) { + return; + } void params.onBlockReply({ text: cleanedText, mediaUrls: mediaUrls?.length ? mediaUrls : undefined, @@ -403,7 +449,9 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar replyDirectiveAccumulator.consume(text, options); const flushBlockReplyBuffer = () => { - if (!params.onBlockReply) return; + if (!params.onBlockReply) { + return; + } if (blockChunker?.hasBuffered()) { blockChunker.drain({ force: true, emit: emitBlockChunk }); blockChunker.reset(); @@ -416,10 +464,16 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar }; const emitReasoningStream = (text: string) => { - if (!state.streamReasoning || !params.onReasoningStream) return; + if (!state.streamReasoning || !params.onReasoningStream) { + return; + } const formatted = formatReasoningMessage(text); - if (!formatted) return; - if (formatted === state.lastStreamedReasoning) return; + if (!formatted) { + return; + } + if (formatted === state.lastStreamedReasoning) { + return; + } state.lastStreamedReasoning = formatted; void params.onReasoningStream({ text: formatted, diff --git a/src/agents/pi-embedded-utils.ts b/src/agents/pi-embedded-utils.ts index 969b0a3162..d95b90707f 100644 --- a/src/agents/pi-embedded-utils.ts +++ b/src/agents/pi-embedded-utils.ts @@ -11,8 +11,12 @@ import { formatToolDetail, resolveToolDisplay } from "./tool-display.js"; * - closing tags */ export function stripMinimaxToolCallXml(text: string): string { - if (!text) return text; - if (!/minimax:tool_call/i.test(text)) return text; + if (!text) { + return text; + } + if (!/minimax:tool_call/i.test(text)) { + return text; + } // Remove ... blocks (non-greedy to handle multiple). let cleaned = text.replace(/]*>[\s\S]*?<\/invoke>/gi, ""); @@ -30,8 +34,12 @@ export function stripMinimaxToolCallXml(text: string): string { * not be shown to users. */ export function stripDowngradedToolCallText(text: string): string { - if (!text) return text; - if (!/\[Tool (?:Call|Result)/i.test(text)) return text; + if (!text) { + return text; + } + if (!/\[Tool (?:Call|Result)/i.test(text)) { + return text; + } const consumeJsonish = ( input: string, @@ -52,7 +60,9 @@ export function stripDowngradedToolCallText(text: string): string { } break; } - if (index >= input.length) return null; + if (index >= input.length) { + return null; + } const startChar = input[index]; if (startChar === "{" || startChar === "[") { @@ -81,7 +91,9 @@ export function stripDowngradedToolCallText(text: string): string { } if (ch === "}" || ch === "]") { depth -= 1; - if (depth === 0) return i + 1; + if (depth === 0) { + return i + 1; + } } } return null; @@ -99,7 +111,9 @@ export function stripDowngradedToolCallText(text: string): string { escape = true; continue; } - if (ch === '"') return i + 1; + if (ch === '"') { + return i + 1; + } } return null; } @@ -117,7 +131,9 @@ export function stripDowngradedToolCallText(text: string): string { let cursor = 0; for (const match of input.matchAll(markerRe)) { const start = match.index ?? 0; - if (start < cursor) continue; + if (start < cursor) { + continue; + } result += input.slice(cursor, start); let index = start + match[0].length; while (index < input.length && (input[index] === " " || input[index] === "\t")) { @@ -125,7 +141,9 @@ export function stripDowngradedToolCallText(text: string): string { } if (input[index] === "\r") { index += 1; - if (input[index] === "\n") index += 1; + if (input[index] === "\n") { + index += 1; + } } else if (input[index] === "\n") { index += 1; } @@ -134,17 +152,27 @@ export function stripDowngradedToolCallText(text: string): string { } if (input.slice(index, index + 9).toLowerCase() === "arguments") { index += 9; - if (input[index] === ":") index += 1; - if (input[index] === " ") index += 1; + if (input[index] === ":") { + index += 1; + } + if (input[index] === " ") { + index += 1; + } const end = consumeJsonish(input, index, { allowLeadingNewlines: true }); - if (end !== null) index = end; + if (end !== null) { + index = end; + } } if ( (input[index] === "\n" || input[index] === "\r") && (result.endsWith("\n") || result.endsWith("\r") || result.length === 0) ) { - if (input[index] === "\r") index += 1; - if (input[index] === "\n") index += 1; + if (input[index] === "\r") { + index += 1; + } + if (input[index] === "\n") { + index += 1; + } } cursor = index; } @@ -172,7 +200,9 @@ export function stripThinkingTagsFromText(text: string): string { export function extractAssistantText(msg: AssistantMessage): string { const isTextBlock = (block: unknown): block is { type: "text"; text: string } => { - if (!block || typeof block !== "object") return false; + if (!block || typeof block !== "object") { + return false; + } const rec = block as Record; return rec.type === "text" && typeof rec.text === "string"; }; @@ -192,10 +222,14 @@ export function extractAssistantText(msg: AssistantMessage): string { } export function extractAssistantThinking(msg: AssistantMessage): string { - if (!Array.isArray(msg.content)) return ""; + if (!Array.isArray(msg.content)) { + return ""; + } const blocks = msg.content .map((block) => { - if (!block || typeof block !== "object") return ""; + if (!block || typeof block !== "object") { + return ""; + } const record = block as unknown as Record; if (record.type === "thinking" && typeof record.thinking === "string") { return record.thinking.trim(); @@ -208,7 +242,9 @@ export function extractAssistantThinking(msg: AssistantMessage): string { export function formatReasoningMessage(text: string): string { const trimmed = text.trim(); - if (!trimmed) return ""; + if (!trimmed) { + return ""; + } // Show reasoning in italics (cursive) for markdown-friendly surfaces (Discord, etc.). // Keep the plain "Reasoning:" prefix so existing parsing/detection keeps working. // Note: Underscore markdown cannot span multiple lines on Telegram, so we wrap @@ -229,11 +265,17 @@ export function splitThinkingTaggedText(text: string): ThinkTaggedSplitBlock[] | // Avoid false positives: only treat it as structured thinking when it begins // with a think tag (common for local/OpenAI-compat providers that emulate // reasoning blocks via tags). - if (!trimmedStart.startsWith("<")) return null; + if (!trimmedStart.startsWith("<")) { + return null; + } const openRe = /<\s*(?:think(?:ing)?|thought|antthinking)\s*>/i; const closeRe = /<\s*\/\s*(?:think(?:ing)?|thought|antthinking)\s*>/i; - if (!openRe.test(trimmedStart)) return null; - if (!closeRe.test(text)) return null; + if (!openRe.test(trimmedStart)) { + return null; + } + if (!closeRe.test(text)) { + return null; + } const scanRe = /<\s*(\/?)\s*(?:think(?:ing)?|thought|antthinking)\s*>/gi; let inThinking = false; @@ -242,12 +284,16 @@ export function splitThinkingTaggedText(text: string): ThinkTaggedSplitBlock[] | const blocks: ThinkTaggedSplitBlock[] = []; const pushText = (value: string) => { - if (!value) return; + if (!value) { + return; + } blocks.push({ type: "text", text: value }); }; const pushThinking = (value: string) => { const cleaned = value.trim(); - if (!cleaned) return; + if (!cleaned) { + return; + } blocks.push({ type: "thinking", thinking: cleaned }); }; @@ -269,18 +315,26 @@ export function splitThinkingTaggedText(text: string): ThinkTaggedSplitBlock[] | } } - if (inThinking) return null; + if (inThinking) { + return null; + } pushText(text.slice(cursor)); const hasThinking = blocks.some((b) => b.type === "thinking"); - if (!hasThinking) return null; + if (!hasThinking) { + return null; + } return blocks; } export function promoteThinkingTagsToBlocks(message: AssistantMessage): void { - if (!Array.isArray(message.content)) return; + if (!Array.isArray(message.content)) { + return; + } const hasThinkingBlock = message.content.some((block) => block.type === "thinking"); - if (hasThinkingBlock) return; + if (hasThinkingBlock) { + return; + } const next: AssistantMessage["content"] = []; let changed = false; @@ -301,17 +355,23 @@ export function promoteThinkingTagsToBlocks(message: AssistantMessage): void { next.push({ type: "thinking", thinking: part.thinking }); } else if (part.type === "text") { const cleaned = part.text.trimStart(); - if (cleaned) next.push({ type: "text", text: cleaned }); + if (cleaned) { + next.push({ type: "text", text: cleaned }); + } } } } - if (!changed) return; + if (!changed) { + return; + } message.content = next; } export function extractThinkingFromTaggedText(text: string): string { - if (!text) return ""; + if (!text) { + return ""; + } const scanRe = /<\s*(\/?)\s*(?:think(?:ing)?|thought|antthinking)\s*>/gi; let result = ""; let lastIndex = 0; @@ -329,14 +389,20 @@ export function extractThinkingFromTaggedText(text: string): string { } export function extractThinkingFromTaggedStream(text: string): string { - if (!text) return ""; + if (!text) { + return ""; + } const closed = extractThinkingFromTaggedText(text); - if (closed) return closed; + if (closed) { + return closed; + } const openRe = /<\s*(?:think(?:ing)?|thought|antthinking)\s*>/gi; const closeRe = /<\s*\/\s*(?:think(?:ing)?|thought|antthinking)\s*>/gi; const openMatches = [...text.matchAll(openRe)]; - if (openMatches.length === 0) return ""; + if (openMatches.length === 0) { + return ""; + } const closeMatches = [...text.matchAll(closeRe)]; const lastOpen = openMatches[openMatches.length - 1]; const lastClose = closeMatches[closeMatches.length - 1]; diff --git a/src/agents/pi-extensions/compaction-safeguard.ts b/src/agents/pi-extensions/compaction-safeguard.ts index cf76e402eb..c824c47355 100644 --- a/src/agents/pi-extensions/compaction-safeguard.ts +++ b/src/agents/pi-extensions/compaction-safeguard.ts @@ -32,12 +32,16 @@ function normalizeFailureText(text: string): string { } function truncateFailureText(text: string, maxChars: number): string { - if (text.length <= maxChars) return text; + if (text.length <= maxChars) { + return text; + } return `${text.slice(0, Math.max(0, maxChars - 3))}...`; } function formatToolFailureMeta(details: unknown): string | undefined { - if (!details || typeof details !== "object") return undefined; + if (!details || typeof details !== "object") { + return undefined; + } const record = details as Record; const status = typeof record.status === "string" ? record.status : undefined; const exitCode = @@ -45,16 +49,24 @@ function formatToolFailureMeta(details: unknown): string | undefined { ? record.exitCode : undefined; const parts: string[] = []; - if (status) parts.push(`status=${status}`); - if (exitCode !== undefined) parts.push(`exitCode=${exitCode}`); + if (status) { + parts.push(`status=${status}`); + } + if (exitCode !== undefined) { + parts.push(`exitCode=${exitCode}`); + } return parts.length > 0 ? parts.join(" ") : undefined; } function extractToolResultText(content: unknown): string { - if (!Array.isArray(content)) return ""; + if (!Array.isArray(content)) { + return ""; + } const parts: string[] = []; for (const block of content) { - if (!block || typeof block !== "object") continue; + if (!block || typeof block !== "object") { + continue; + } const rec = block as { type?: unknown; text?: unknown }; if (rec.type === "text" && typeof rec.text === "string") { parts.push(rec.text); @@ -68,9 +80,13 @@ function collectToolFailures(messages: AgentMessage[]): ToolFailure[] { const seen = new Set(); for (const message of messages) { - if (!message || typeof message !== "object") continue; + if (!message || typeof message !== "object") { + continue; + } const role = (message as { role?: unknown }).role; - if (role !== "toolResult") continue; + if (role !== "toolResult") { + continue; + } const toolResult = message as { toolCallId?: unknown; toolName?: unknown; @@ -78,9 +94,13 @@ function collectToolFailures(messages: AgentMessage[]): ToolFailure[] { details?: unknown; isError?: unknown; }; - if (toolResult.isError !== true) continue; + if (toolResult.isError !== true) { + continue; + } const toolCallId = typeof toolResult.toolCallId === "string" ? toolResult.toolCallId : ""; - if (!toolCallId || seen.has(toolCallId)) continue; + if (!toolCallId || seen.has(toolCallId)) { + continue; + } seen.add(toolCallId); const toolName = @@ -101,7 +121,9 @@ function collectToolFailures(messages: AgentMessage[]): ToolFailure[] { } function formatToolFailuresSection(failures: ToolFailure[]): string { - if (failures.length === 0) return ""; + if (failures.length === 0) { + return ""; + } const lines = failures.slice(0, MAX_TOOL_FAILURES).map((failure) => { const meta = failure.meta ? ` (${failure.meta})` : ""; return `- ${failure.toolName}${meta}: ${failure.summary}`; @@ -130,7 +152,9 @@ function formatFileOperations(readFiles: string[], modifiedFiles: string[]): str if (modifiedFiles.length > 0) { sections.push(`\n${modifiedFiles.join("\n")}\n`); } - if (sections.length === 0) return ""; + if (sections.length === 0) { + return ""; + } return `\n\n${sections.join("\n\n")}`; } diff --git a/src/agents/pi-extensions/context-pruning.test.ts b/src/agents/pi-extensions/context-pruning.test.ts index 9e92e320eb..bf0bad5fd1 100644 --- a/src/agents/pi-extensions/context-pruning.test.ts +++ b/src/agents/pi-extensions/context-pruning.test.ts @@ -12,15 +12,21 @@ import { } from "./context-pruning.js"; function toolText(msg: AgentMessage): string { - if (msg.role !== "toolResult") throw new Error("expected toolResult"); + if (msg.role !== "toolResult") { + throw new Error("expected toolResult"); + } const first = msg.content.find((b) => b.type === "text"); - if (!first || first.type !== "text") return ""; + if (!first || first.type !== "text") { + return ""; + } return first.text; } function findToolResult(messages: AgentMessage[], toolCallId: string): AgentMessage { const msg = messages.find((m) => m.role === "toolResult" && m.toolCallId === toolCallId); - if (!msg) throw new Error(`missing toolResult: ${toolCallId}`); + if (!msg) { + throw new Error(`missing toolResult: ${toolCallId}`); + } return msg; } @@ -295,14 +301,18 @@ describe("context-pruning", () => { contextPruningExtension(api); - if (!handler) throw new Error("missing context handler"); + if (!handler) { + throw new Error("missing context handler"); + } const result = handler({ messages }, { model: undefined, sessionManager, } as unknown as ExtensionContext); - if (!result) throw new Error("expected handler to return messages"); + if (!result) { + throw new Error("expected handler to return messages"); + } expect(toolText(findToolResult(result.messages, "t1"))).toBe("[cleared]"); }); @@ -352,17 +362,23 @@ describe("context-pruning", () => { } as unknown as ExtensionAPI; contextPruningExtension(api); - if (!handler) throw new Error("missing context handler"); + if (!handler) { + throw new Error("missing context handler"); + } const first = handler({ messages }, { model: undefined, sessionManager, } as unknown as ExtensionContext); - if (!first) throw new Error("expected first prune"); + if (!first) { + throw new Error("expected first prune"); + } expect(toolText(findToolResult(first.messages, "t1"))).toBe("[cleared]"); const runtime = getContextPruningRuntime(sessionManager); - if (!runtime?.lastCacheTouchAt) throw new Error("expected lastCacheTouchAt"); + if (!runtime?.lastCacheTouchAt) { + throw new Error("expected lastCacheTouchAt"); + } expect(runtime.lastCacheTouchAt).toBeGreaterThan(lastTouch); const second = handler({ messages }, { diff --git a/src/agents/pi-extensions/context-pruning/extension.ts b/src/agents/pi-extensions/context-pruning/extension.ts index d544a4811d..8f68c3f238 100644 --- a/src/agents/pi-extensions/context-pruning/extension.ts +++ b/src/agents/pi-extensions/context-pruning/extension.ts @@ -6,7 +6,9 @@ import { getContextPruningRuntime } from "./runtime.js"; export default function contextPruningExtension(api: ExtensionAPI): void { api.on("context", (event: ContextEvent, ctx: ExtensionContext) => { const runtime = getContextPruningRuntime(ctx.sessionManager); - if (!runtime) return undefined; + if (!runtime) { + return undefined; + } if (runtime.settings.mode === "cache-ttl") { const ttlMs = runtime.settings.ttlMs; @@ -27,7 +29,9 @@ export default function contextPruningExtension(api: ExtensionAPI): void { contextWindowTokensOverride: runtime.contextWindowTokens ?? undefined, }); - if (next === event.messages) return undefined; + if (next === event.messages) { + return undefined; + } if (runtime.settings.mode === "cache-ttl") { runtime.lastCacheTouchAt = Date.now(); diff --git a/src/agents/pi-extensions/context-pruning/pruner.ts b/src/agents/pi-extensions/context-pruning/pruner.ts index c13e5c37a1..dd66c454cc 100644 --- a/src/agents/pi-extensions/context-pruning/pruner.ts +++ b/src/agents/pi-extensions/context-pruning/pruner.ts @@ -17,29 +17,39 @@ function asText(text: string): TextContent { function collectTextSegments(content: ReadonlyArray): string[] { const parts: string[] = []; for (const block of content) { - if (block.type === "text") parts.push(block.text); + if (block.type === "text") { + parts.push(block.text); + } } return parts; } function estimateJoinedTextLength(parts: string[]): number { - if (parts.length === 0) return 0; + if (parts.length === 0) { + return 0; + } let len = 0; - for (const p of parts) len += p.length; + for (const p of parts) { + len += p.length; + } // Joined with "\n" separators between blocks. len += Math.max(0, parts.length - 1); return len; } function takeHeadFromJoinedText(parts: string[], maxChars: number): string { - if (maxChars <= 0 || parts.length === 0) return ""; + if (maxChars <= 0 || parts.length === 0) { + return ""; + } let remaining = maxChars; let out = ""; for (let i = 0; i < parts.length && remaining > 0; i++) { if (i > 0) { out += "\n"; remaining -= 1; - if (remaining <= 0) break; + if (remaining <= 0) { + break; + } } const p = parts[i]; if (p.length <= remaining) { @@ -54,7 +64,9 @@ function takeHeadFromJoinedText(parts: string[], maxChars: number): string { } function takeTailFromJoinedText(parts: string[], maxChars: number): string { - if (maxChars <= 0 || parts.length === 0) return ""; + if (maxChars <= 0 || parts.length === 0) { + return ""; + } let remaining = maxChars; const out: string[] = []; for (let i = parts.length - 1; i >= 0 && remaining > 0; i--) { @@ -78,7 +90,9 @@ function takeTailFromJoinedText(parts: string[], maxChars: number): string { function hasImageBlocks(content: ReadonlyArray): boolean { for (const block of content) { - if (block.type === "image") return true; + if (block.type === "image") { + return true; + } } return false; } @@ -86,11 +100,17 @@ function hasImageBlocks(content: ReadonlyArray): boo function estimateMessageChars(message: AgentMessage): number { if (message.role === "user") { const content = message.content; - if (typeof content === "string") return content.length; + if (typeof content === "string") { + return content.length; + } let chars = 0; for (const b of content) { - if (b.type === "text") chars += b.text.length; - if (b.type === "image") chars += IMAGE_CHAR_ESTIMATE; + if (b.type === "text") { + chars += b.text.length; + } + if (b.type === "image") { + chars += IMAGE_CHAR_ESTIMATE; + } } return chars; } @@ -98,8 +118,12 @@ function estimateMessageChars(message: AgentMessage): number { if (message.role === "assistant") { let chars = 0; for (const b of message.content) { - if (b.type === "text") chars += b.text.length; - if (b.type === "thinking") chars += b.thinking.length; + if (b.type === "text") { + chars += b.text.length; + } + if (b.type === "thinking") { + chars += b.thinking.length; + } if (b.type === "toolCall") { try { chars += JSON.stringify(b.arguments ?? {}).length; @@ -114,8 +138,12 @@ function estimateMessageChars(message: AgentMessage): number { if (message.role === "toolResult") { let chars = 0; for (const b of message.content) { - if (b.type === "text") chars += b.text.length; - if (b.type === "image") chars += IMAGE_CHAR_ESTIMATE; + if (b.type === "text") { + chars += b.text.length; + } + if (b.type === "image") { + chars += IMAGE_CHAR_ESTIMATE; + } } return chars; } @@ -132,13 +160,19 @@ function findAssistantCutoffIndex( keepLastAssistants: number, ): number | null { // keepLastAssistants <= 0 => everything is potentially prunable. - if (keepLastAssistants <= 0) return messages.length; + if (keepLastAssistants <= 0) { + return messages.length; + } let remaining = keepLastAssistants; for (let i = messages.length - 1; i >= 0; i--) { - if (messages[i]?.role !== "assistant") continue; + if (messages[i]?.role !== "assistant") { + continue; + } remaining--; - if (remaining === 0) return i; + if (remaining === 0) { + return i; + } } // Not enough assistant messages to establish a protected tail. @@ -147,7 +181,9 @@ function findAssistantCutoffIndex( function findFirstUserIndex(messages: AgentMessage[]): number | null { for (let i = 0; i < messages.length; i++) { - if (messages[i]?.role === "user") return i; + if (messages[i]?.role === "user") { + return i; + } } return null; } @@ -158,15 +194,21 @@ function softTrimToolResultMessage(params: { }): ToolResultMessage | null { const { msg, settings } = params; // Ignore image tool results for now: these are often directly relevant and hard to partially prune safely. - if (hasImageBlocks(msg.content)) return null; + if (hasImageBlocks(msg.content)) { + return null; + } const parts = collectTextSegments(msg.content); const rawLen = estimateJoinedTextLength(parts); - if (rawLen <= settings.softTrim.maxChars) return null; + if (rawLen <= settings.softTrim.maxChars) { + return null; + } const headChars = Math.max(0, settings.softTrim.headChars); const tailChars = Math.max(0, settings.softTrim.tailChars); - if (headChars + tailChars >= rawLen) return null; + if (headChars + tailChars >= rawLen) { + return null; + } const head = takeHeadFromJoinedText(parts, headChars); const tail = takeTailFromJoinedText(parts, tailChars); @@ -195,13 +237,19 @@ export function pruneContextMessages(params: { params.contextWindowTokensOverride > 0 ? params.contextWindowTokensOverride : ctx.model?.contextWindow; - if (!contextWindowTokens || contextWindowTokens <= 0) return messages; + if (!contextWindowTokens || contextWindowTokens <= 0) { + return messages; + } const charWindow = contextWindowTokens * CHARS_PER_TOKEN_ESTIMATE; - if (charWindow <= 0) return messages; + if (charWindow <= 0) { + return messages; + } const cutoffIndex = findAssistantCutoffIndex(messages, settings.keepLastAssistants); - if (cutoffIndex === null) return messages; + if (cutoffIndex === null) { + return messages; + } // Bootstrap safety: never prune anything before the first user message. This protects initial // "identity" reads (SOUL.md, USER.md, etc.) which typically happen before the first inbound user @@ -223,8 +271,12 @@ export function pruneContextMessages(params: { for (let i = pruneStartIndex; i < cutoffIndex; i++) { const msg = messages[i]; - if (!msg || msg.role !== "toolResult") continue; - if (!isToolPrunable(msg.toolName)) continue; + if (!msg || msg.role !== "toolResult") { + continue; + } + if (!isToolPrunable(msg.toolName)) { + continue; + } if (hasImageBlocks(msg.content)) { continue; } @@ -234,12 +286,16 @@ export function pruneContextMessages(params: { msg: msg as unknown as ToolResultMessage, settings, }); - if (!updated) continue; + if (!updated) { + continue; + } const beforeChars = estimateMessageChars(msg); const afterChars = estimateMessageChars(updated as unknown as AgentMessage); totalChars += afterChars - beforeChars; - if (!next) next = messages.slice(); + if (!next) { + next = messages.slice(); + } next[i] = updated as unknown as AgentMessage; } @@ -255,7 +311,9 @@ export function pruneContextMessages(params: { let prunableToolChars = 0; for (const i of prunableToolIndexes) { const msg = outputAfterSoftTrim[i]; - if (!msg || msg.role !== "toolResult") continue; + if (!msg || msg.role !== "toolResult") { + continue; + } prunableToolChars += estimateMessageChars(msg); } if (prunableToolChars < settings.minPrunableToolChars) { @@ -263,16 +321,22 @@ export function pruneContextMessages(params: { } for (const i of prunableToolIndexes) { - if (ratio < settings.hardClearRatio) break; + if (ratio < settings.hardClearRatio) { + break; + } const msg = (next ?? messages)[i]; - if (!msg || msg.role !== "toolResult") continue; + if (!msg || msg.role !== "toolResult") { + continue; + } const beforeChars = estimateMessageChars(msg); const cleared: ToolResultMessage = { ...msg, content: [asText(settings.hardClear.placeholder)], }; - if (!next) next = messages.slice(); + if (!next) { + next = messages.slice(); + } next[i] = cleared as unknown as AgentMessage; const afterChars = estimateMessageChars(cleared as unknown as AgentMessage); totalChars += afterChars - beforeChars; diff --git a/src/agents/pi-extensions/context-pruning/settings.ts b/src/agents/pi-extensions/context-pruning/settings.ts index 8d1497083c..817954cc69 100644 --- a/src/agents/pi-extensions/context-pruning/settings.ts +++ b/src/agents/pi-extensions/context-pruning/settings.ts @@ -65,9 +65,13 @@ export const DEFAULT_CONTEXT_PRUNING_SETTINGS: EffectiveContextPruningSettings = }; export function computeEffectiveSettings(raw: unknown): EffectiveContextPruningSettings | null { - if (!raw || typeof raw !== "object") return null; + if (!raw || typeof raw !== "object") { + return null; + } const cfg = raw as ContextPruningConfig; - if (cfg.mode !== "cache-ttl") return null; + if (cfg.mode !== "cache-ttl") { + return null; + } const s: EffectiveContextPruningSettings = structuredClone(DEFAULT_CONTEXT_PRUNING_SETTINGS); s.mode = cfg.mode; diff --git a/src/agents/pi-extensions/context-pruning/tools.ts b/src/agents/pi-extensions/context-pruning/tools.ts index aaebc8f4aa..1fbca70657 100644 --- a/src/agents/pi-extensions/context-pruning/tools.ts +++ b/src/agents/pi-extensions/context-pruning/tools.ts @@ -1,7 +1,9 @@ import type { ContextPruningToolMatch } from "./settings.js"; function normalizePatterns(patterns?: string[]): string[] { - if (!Array.isArray(patterns)) return []; + if (!Array.isArray(patterns)) { + return []; + } return patterns .map((p) => String(p ?? "") @@ -17,8 +19,12 @@ type CompiledPattern = | { kind: "regex"; value: RegExp }; function compilePattern(pattern: string): CompiledPattern { - if (pattern === "*") return { kind: "all" }; - if (!pattern.includes("*")) return { kind: "exact", value: pattern }; + if (pattern === "*") { + return { kind: "all" }; + } + if (!pattern.includes("*")) { + return { kind: "exact", value: pattern }; + } const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const re = new RegExp(`^${escaped.replaceAll("\\*", ".*")}$`); @@ -31,9 +37,15 @@ function compilePatterns(patterns?: string[]): CompiledPattern[] { function matchesAny(toolName: string, patterns: CompiledPattern[]): boolean { for (const p of patterns) { - if (p.kind === "all") return true; - if (p.kind === "exact" && toolName === p.value) return true; - if (p.kind === "regex" && p.value.test(toolName)) return true; + if (p.kind === "all") { + return true; + } + if (p.kind === "exact" && toolName === p.value) { + return true; + } + if (p.kind === "regex" && p.value.test(toolName)) { + return true; + } } return false; } @@ -46,8 +58,12 @@ export function makeToolPrunablePredicate( return (toolName: string) => { const normalized = toolName.trim().toLowerCase(); - if (matchesAny(normalized, deny)) return false; - if (allow.length === 0) return true; + if (matchesAny(normalized, deny)) { + return false; + } + if (allow.length === 0) { + return true; + } return matchesAny(normalized, allow); }; } diff --git a/src/agents/pi-tool-definition-adapter.ts b/src/agents/pi-tool-definition-adapter.ts index 63c65a077b..a0a9bea458 100644 --- a/src/agents/pi-tool-definition-adapter.ts +++ b/src/agents/pi-tool-definition-adapter.ts @@ -45,12 +45,16 @@ export function toToolDefinitions(tools: AnyAgentTool[]): ToolDefinition[] { try { return await tool.execute(toolCallId, params, signal, onUpdate); } catch (err) { - if (signal?.aborted) throw err; + if (signal?.aborted) { + throw err; + } const name = err && typeof err === "object" && "name" in err ? String((err as { name?: unknown }).name) : ""; - if (name === "AbortError") throw err; + if (name === "AbortError") { + throw err; + } const described = describeToolExecutionError(err); if (described.stack && described.stack !== described.message) { logDebug(`tools: ${normalizedName} failed stack:\n${described.stack}`); diff --git a/src/agents/pi-tools.abort.ts b/src/agents/pi-tools.abort.ts index 8a7502609c..a14629782c 100644 --- a/src/agents/pi-tools.abort.ts +++ b/src/agents/pi-tools.abort.ts @@ -7,11 +7,21 @@ function throwAbortError(): never { } function combineAbortSignals(a?: AbortSignal, b?: AbortSignal): AbortSignal | undefined { - if (!a && !b) return undefined; - if (a && !b) return a; - if (b && !a) return b; - if (a?.aborted) return a; - if (b?.aborted) return b; + if (!a && !b) { + return undefined; + } + if (a && !b) { + return a; + } + if (b && !a) { + return b; + } + if (a?.aborted) { + return a; + } + if (b?.aborted) { + return b; + } if (typeof AbortSignal.any === "function") { return AbortSignal.any([a as AbortSignal, b as AbortSignal]); } @@ -26,14 +36,20 @@ export function wrapToolWithAbortSignal( tool: AnyAgentTool, abortSignal?: AbortSignal, ): AnyAgentTool { - if (!abortSignal) return tool; + if (!abortSignal) { + return tool; + } const execute = tool.execute; - if (!execute) return tool; + if (!execute) { + return tool; + } return { ...tool, execute: async (toolCallId, params, signal, onUpdate) => { const combined = combineAbortSignals(signal, abortSignal); - if (combined?.aborted) throwAbortError(); + if (combined?.aborted) { + throwAbortError(); + } return await execute(toolCallId, params, combined, onUpdate); }, }; diff --git a/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping-b.test.ts b/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping-b.test.ts index b5f1d1470d..09f5ce4929 100644 --- a/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping-b.test.ts +++ b/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping-b.test.ts @@ -10,12 +10,18 @@ describe("createOpenClawCodingTools", () => { const toolNames = ["browser", "canvas", "nodes", "cron", "gateway", "message"]; const collectActionValues = (schema: unknown, values: Set): void => { - if (!schema || typeof schema !== "object") return; + if (!schema || typeof schema !== "object") { + return; + } const record = schema as Record; - if (typeof record.const === "string") values.add(record.const); + if (typeof record.const === "string") { + values.add(record.const); + } if (Array.isArray(record.enum)) { for (const value of record.enum) { - if (typeof value === "string") values.add(value); + if (typeof value === "string") { + values.add(value); + } } } if (Array.isArray(record.anyOf)) { diff --git a/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping.test.ts b/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping.test.ts index d3ff7a4af1..cab315ec52 100644 --- a/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping.test.ts +++ b/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping.test.ts @@ -220,14 +220,18 @@ describe("createOpenClawCodingTools", () => { const keywords = new Set(["anyOf", "oneOf", "allOf"]); const walk = (value: unknown, path: string, name: string): void => { - if (!value) return; + if (!value) { + return; + } if (Array.isArray(value)) { for (const [index, entry] of value.entries()) { walk(entry, `${path}[${index}]`, name); } return; } - if (typeof value !== "object") return; + if (typeof value !== "object") { + return; + } const record = value as Record; for (const [key, entry] of Object.entries(record)) { @@ -270,14 +274,18 @@ describe("createOpenClawCodingTools", () => { const keywords = new Set(["anyOf", "oneOf", "allOf"]); const walk = (value: unknown, path: string, name: string): void => { - if (!value) return; + if (!value) { + return; + } if (Array.isArray(value)) { for (const [index, entry] of value.entries()) { walk(entry, `${path}[${index}]`, name); } return; } - if (typeof value !== "object") return; + if (typeof value !== "object") { + return; + } const record = value as Record; for (const [key, entry] of Object.entries(record)) { const nextPath = path ? `${path}.${key}` : key; @@ -289,7 +297,9 @@ describe("createOpenClawCodingTools", () => { }; for (const tool of tools) { - if (!coreTools.has(tool.name)) continue; + if (!coreTools.has(tool.name)) { + continue; + } walk(tool.parameters, "", tool.name); } @@ -410,7 +420,9 @@ describe("createOpenClawCodingTools", () => { const findUnsupportedKeywords = (schema: unknown, path: string): string[] => { const found: string[] = []; - if (!schema || typeof schema !== "object") return found; + if (!schema || typeof schema !== "object") { + return found; + } if (Array.isArray(schema)) { schema.forEach((item, i) => { found.push(...findUnsupportedKeywords(item, `${path}[${i}]`)); @@ -432,7 +444,9 @@ describe("createOpenClawCodingTools", () => { } for (const [key, value] of Object.entries(record)) { - if (key === "properties") continue; + if (key === "properties") { + continue; + } if (unsupportedKeywords.has(key)) { found.push(`${path}.${key}`); } diff --git a/src/agents/pi-tools.policy.ts b/src/agents/pi-tools.policy.ts index 541fbdfcc8..bbc8ebc406 100644 --- a/src/agents/pi-tools.policy.ts +++ b/src/agents/pi-tools.policy.ts @@ -15,9 +15,15 @@ type CompiledPattern = function compilePattern(pattern: string): CompiledPattern { const normalized = normalizeToolName(pattern); - if (!normalized) return { kind: "exact", value: "" }; - if (normalized === "*") return { kind: "all" }; - if (!normalized.includes("*")) return { kind: "exact", value: normalized }; + if (!normalized) { + return { kind: "exact", value: "" }; + } + if (normalized === "*") { + return { kind: "all" }; + } + if (!normalized.includes("*")) { + return { kind: "exact", value: normalized }; + } const escaped = normalized.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); return { kind: "regex", @@ -26,7 +32,9 @@ function compilePattern(pattern: string): CompiledPattern { } function compilePatterns(patterns?: string[]): CompiledPattern[] { - if (!Array.isArray(patterns)) return []; + if (!Array.isArray(patterns)) { + return []; + } return expandToolGroups(patterns) .map(compilePattern) .filter((pattern) => pattern.kind !== "exact" || pattern.value); @@ -34,9 +42,15 @@ function compilePatterns(patterns?: string[]): CompiledPattern[] { function matchesAny(name: string, patterns: CompiledPattern[]): boolean { for (const pattern of patterns) { - if (pattern.kind === "all") return true; - if (pattern.kind === "exact" && name === pattern.value) return true; - if (pattern.kind === "regex" && pattern.value.test(name)) return true; + if (pattern.kind === "all") { + return true; + } + if (pattern.kind === "exact" && name === pattern.value) { + return true; + } + if (pattern.kind === "regex" && pattern.value.test(name)) { + return true; + } } return false; } @@ -46,10 +60,18 @@ function makeToolPolicyMatcher(policy: SandboxToolPolicy) { const allow = compilePatterns(policy.allow); return (name: string) => { const normalized = normalizeToolName(name); - if (matchesAny(normalized, deny)) return false; - if (allow.length === 0) return true; - if (matchesAny(normalized, allow)) return true; - if (normalized === "apply_patch" && matchesAny("exec", allow)) return true; + if (matchesAny(normalized, deny)) { + return false; + } + if (allow.length === 0) { + return true; + } + if (matchesAny(normalized, allow)) { + return true; + } + if (normalized === "apply_patch" && matchesAny("exec", allow)) { + return true; + } return false; }; } @@ -84,12 +106,16 @@ export function resolveSubagentToolPolicy(cfg?: OpenClawConfig): SandboxToolPoli } export function isToolAllowedByPolicyName(name: string, policy?: SandboxToolPolicy): boolean { - if (!policy) return true; + if (!policy) { + return true; + } return makeToolPolicyMatcher(policy)(name); } export function filterToolsByPolicy(tools: AnyAgentTool[], policy?: SandboxToolPolicy) { - if (!policy) return tools; + if (!policy) { + return tools; + } const matcher = makeToolPolicyMatcher(policy); return tools.filter((tool) => matcher(tool.name)); } @@ -102,7 +128,9 @@ type ToolPolicyConfig = { }; function unionAllow(base?: string[], extra?: string[]) { - if (!Array.isArray(extra) || extra.length === 0) return base; + if (!Array.isArray(extra) || extra.length === 0) { + return base; + } // If the user is using alsoAllow without an allowlist, treat it as additive on top of // an implicit allow-all policy. if (!Array.isArray(base) || base.length === 0) { @@ -112,14 +140,18 @@ function unionAllow(base?: string[], extra?: string[]) { } function pickToolPolicy(config?: ToolPolicyConfig): SandboxToolPolicy | undefined { - if (!config) return undefined; + if (!config) { + return undefined; + } const allow = Array.isArray(config.allow) ? unionAllow(config.allow, config.alsoAllow) : Array.isArray(config.alsoAllow) && config.alsoAllow.length > 0 ? unionAllow(undefined, config.alsoAllow) : undefined; const deny = Array.isArray(config.deny) ? config.deny : undefined; - if (!allow && !deny) return undefined; + if (!allow && !deny) { + return undefined; + } return { allow, deny }; } @@ -132,18 +164,26 @@ function resolveGroupContextFromSessionKey(sessionKey?: string | null): { groupId?: string; } { const raw = (sessionKey ?? "").trim(); - if (!raw) return {}; + if (!raw) { + return {}; + } const base = resolveThreadParentSessionKey(raw) ?? raw; const parts = base.split(":").filter(Boolean); let body = parts[0] === "agent" ? parts.slice(2) : parts; if (body[0] === "subagent") { body = body.slice(1); } - if (body.length < 3) return {}; + if (body.length < 3) { + return {}; + } const [channel, kind, ...rest] = body; - if (kind !== "group" && kind !== "channel") return {}; + if (kind !== "group" && kind !== "channel") { + return {}; + } const groupId = rest.join(":").trim(); - if (!groupId) return {}; + if (!groupId) { + return {}; + } return { channel: channel.trim().toLowerCase(), groupId }; } @@ -153,15 +193,21 @@ function resolveProviderToolPolicy(params: { modelId?: string; }): ToolPolicyConfig | undefined { const provider = params.modelProvider?.trim(); - if (!provider || !params.byProvider) return undefined; + if (!provider || !params.byProvider) { + return undefined; + } const entries = Object.entries(params.byProvider); - if (entries.length === 0) return undefined; + if (entries.length === 0) { + return undefined; + } const lookup = new Map(); for (const [key, value] of entries) { const normalized = normalizeProviderKey(key); - if (!normalized) continue; + if (!normalized) { + continue; + } lookup.set(normalized, value); } @@ -174,7 +220,9 @@ function resolveProviderToolPolicy(params: { for (const key of candidates) { const match = lookup.get(key); - if (match) return match; + if (match) { + return match; + } } return undefined; } @@ -238,14 +286,20 @@ export function resolveGroupToolPolicy(params: { senderUsername?: string | null; senderE164?: string | null; }): SandboxToolPolicy | undefined { - if (!params.config) return undefined; + if (!params.config) { + return undefined; + } const sessionContext = resolveGroupContextFromSessionKey(params.sessionKey); const spawnedContext = resolveGroupContextFromSessionKey(params.spawnedBy); const groupId = params.groupId ?? sessionContext.groupId ?? spawnedContext.groupId; - if (!groupId) return undefined; + if (!groupId) { + return undefined; + } const channelRaw = params.messageProvider ?? sessionContext.channel ?? spawnedContext.channel; const channel = normalizeMessageChannel(channelRaw); - if (!channel) return undefined; + if (!channel) { + return undefined; + } let dock; try { dock = getChannelDock(channel); diff --git a/src/agents/pi-tools.read.ts b/src/agents/pi-tools.read.ts index f48f50a8a9..0d27702032 100644 --- a/src/agents/pi-tools.read.ts +++ b/src/agents/pi-tools.read.ts @@ -14,11 +14,15 @@ type TextContentBlock = Extract; async function sniffMimeFromBase64(base64: string): Promise { const trimmed = base64.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } const take = Math.min(256, trimmed.length); const sliceLen = take - (take % 4); - if (sliceLen < 8) return undefined; + if (sliceLen < 8) { + return undefined; + } try { const head = Buffer.from(trimmed.slice(0, sliceLen), "base64"); @@ -50,14 +54,18 @@ async function normalizeReadImageResult( typeof (b as { data?: unknown }).data === "string" && typeof (b as { mimeType?: unknown }).mimeType === "string", ); - if (!image) return result; + if (!image) { + return result; + } if (!image.data.trim()) { throw new Error(`read: image payload is empty (${filePath})`); } const sniffed = await sniffMimeFromBase64(image.data); - if (!sniffed) return result; + if (!sniffed) { + return result; + } if (!sniffed.startsWith("image/")) { throw new Error( @@ -65,7 +73,9 @@ async function normalizeReadImageResult( ); } - if (sniffed === image.mimeType) return result; + if (sniffed === image.mimeType) { + return result; + } const nextContent = content.map((block) => { if (block && typeof block === "object" && (block as { type?: unknown }).type === "image") { @@ -116,7 +126,9 @@ export const CLAUDE_PARAM_GROUPS = { // Claude Code uses file_path/old_string/new_string while pi-coding-agent uses path/oldText/newText. // This prevents models trained on Claude Code from getting stuck in tool-call loops. export function normalizeToolParams(params: unknown): Record | undefined { - if (!params || typeof params !== "object") return undefined; + if (!params || typeof params !== "object") { + return undefined; + } const record = params as Record; const normalized = { ...record }; // file_path → path (read, write, edit) @@ -160,7 +172,9 @@ export function patchToolSchemaForClaudeCompatibility(tool: AnyAgentTool): AnyAg ]; for (const { original, alias } of aliasPairs) { - if (!(original in properties)) continue; + if (!(original in properties)) { + continue; + } if (!(alias in properties)) { properties[alias] = properties[original]; changed = true; @@ -172,7 +186,9 @@ export function patchToolSchemaForClaudeCompatibility(tool: AnyAgentTool): AnyAg } } - if (!changed) return tool; + if (!changed) { + return tool; + } return { ...tool, @@ -195,10 +211,16 @@ export function assertRequiredParams( for (const group of groups) { const satisfied = group.keys.some((key) => { - if (!(key in record)) return false; + if (!(key in record)) { + return false; + } const value = record[key]; - if (typeof value !== "string") return false; - if (group.allowEmpty) return true; + if (typeof value !== "string") { + return false; + } + if (group.allowEmpty) { + return true; + } return value.trim().length > 0; }); diff --git a/src/agents/pi-tools.safe-bins.test.ts b/src/agents/pi-tools.safe-bins.test.ts index 9e9c257fa7..ecf976ef4f 100644 --- a/src/agents/pi-tools.safe-bins.test.ts +++ b/src/agents/pi-tools.safe-bins.test.ts @@ -42,7 +42,9 @@ vi.mock("../infra/exec-approvals.js", async (importOriginal) => { describe("createOpenClawCodingTools safeBins", () => { it("threads tools.exec.safeBins into exec allowlist checks", async () => { - if (process.platform === "win32") return; + if (process.platform === "win32") { + return; + } const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-safe-bins-")); const cfg: OpenClawConfig = { diff --git a/src/agents/pi-tools.schema.ts b/src/agents/pi-tools.schema.ts index 1910f3eef4..ca8e64e08c 100644 --- a/src/agents/pi-tools.schema.ts +++ b/src/agents/pi-tools.schema.ts @@ -2,10 +2,16 @@ import type { AnyAgentTool } from "./pi-tools.types.js"; import { cleanSchemaForGemini } from "./schema/clean-for-gemini.js"; function extractEnumValues(schema: unknown): unknown[] | undefined { - if (!schema || typeof schema !== "object") return undefined; + if (!schema || typeof schema !== "object") { + return undefined; + } const record = schema as Record; - if (Array.isArray(record.enum)) return record.enum; - if ("const" in record) return [record.const]; + if (Array.isArray(record.enum)) { + return record.enum; + } + if ("const" in record) { + return [record.const]; + } const variants = Array.isArray(record.anyOf) ? record.anyOf : Array.isArray(record.oneOf) @@ -22,8 +28,12 @@ function extractEnumValues(schema: unknown): unknown[] | undefined { } function mergePropertySchemas(existing: unknown, incoming: unknown): unknown { - if (!existing) return incoming; - if (!incoming) return existing; + if (!existing) { + return incoming; + } + if (!incoming) { + return existing; + } const existingEnum = extractEnumValues(existing); const incomingEnum = extractEnumValues(incoming); @@ -31,14 +41,20 @@ function mergePropertySchemas(existing: unknown, incoming: unknown): unknown { const values = Array.from(new Set([...(existingEnum ?? []), ...(incomingEnum ?? [])])); const merged: Record = {}; for (const source of [existing, incoming]) { - if (!source || typeof source !== "object") continue; + if (!source || typeof source !== "object") { + continue; + } const record = source as Record; for (const key of ["title", "description", "default"]) { - if (!(key in merged) && key in record) merged[key] = record[key]; + if (!(key in merged) && key in record) { + merged[key] = record[key]; + } } } const types = new Set(values.map((value) => typeof value)); - if (types.size === 1) merged.type = Array.from(types)[0]; + if (types.size === 1) { + merged.type = Array.from(types)[0]; + } merged.enum = values; return merged; } @@ -51,7 +67,9 @@ export function normalizeToolParameters(tool: AnyAgentTool): AnyAgentTool { tool.parameters && typeof tool.parameters === "object" ? (tool.parameters as Record) : undefined; - if (!schema) return tool; + if (!schema) { + return tool; + } // Provider quirks: // - Gemini rejects several JSON Schema keywords, so we scrub those. @@ -88,16 +106,22 @@ export function normalizeToolParameters(tool: AnyAgentTool): AnyAgentTool { : Array.isArray(schema.oneOf) ? "oneOf" : null; - if (!variantKey) return tool; + if (!variantKey) { + return tool; + } const variants = schema[variantKey] as unknown[]; const mergedProperties: Record = {}; const requiredCounts = new Map(); let objectVariants = 0; for (const entry of variants) { - if (!entry || typeof entry !== "object") continue; + if (!entry || typeof entry !== "object") { + continue; + } const props = (entry as { properties?: unknown }).properties; - if (!props || typeof props !== "object") continue; + if (!props || typeof props !== "object") { + continue; + } objectVariants += 1; for (const [key, value] of Object.entries(props as Record)) { if (!(key in mergedProperties)) { @@ -110,7 +134,9 @@ export function normalizeToolParameters(tool: AnyAgentTool): AnyAgentTool { ? (entry as { required: unknown[] }).required : []; for (const key of required) { - if (typeof key !== "string") continue; + if (typeof key !== "string") { + continue; + } requiredCounts.set(key, (requiredCounts.get(key) ?? 0) + 1); } } diff --git a/src/agents/pi-tools.ts b/src/agents/pi-tools.ts index 39b9d58186..371868ee14 100644 --- a/src/agents/pi-tools.ts +++ b/src/agents/pi-tools.ts @@ -62,9 +62,13 @@ function isApplyPatchAllowedForModel(params: { allowModels?: string[]; }) { const allowModels = Array.isArray(params.allowModels) ? params.allowModels : []; - if (allowModels.length === 0) return true; + if (allowModels.length === 0) { + return true; + } const modelId = params.modelId?.trim(); - if (!modelId) return false; + if (!modelId) { + return false; + } const normalizedModelId = modelId.toLowerCase(); const provider = params.modelProvider?.trim().toLowerCase(); const normalizedFull = @@ -73,7 +77,9 @@ function isApplyPatchAllowedForModel(params: { : normalizedModelId; return allowModels.some((entry) => { const normalized = entry.trim().toLowerCase(); - if (!normalized) return false; + if (!normalized) { + return false; + } return normalized === normalizedModelId || normalized === normalizedFull; }); } @@ -187,7 +193,9 @@ export function createOpenClawCodingTools(options?: { const providerProfilePolicy = resolveToolProfilePolicy(providerProfile); const mergeAlsoAllow = (policy: typeof profilePolicy, alsoAllow?: string[]) => { - if (!policy?.allow || !Array.isArray(alsoAllow) || alsoAllow.length === 0) return policy; + if (!policy?.allow || !Array.isArray(alsoAllow) || alsoAllow.length === 0) { + return policy; + } return { ...policy, allow: Array.from(new Set([...policy.allow, ...alsoAllow])) }; }; @@ -234,16 +242,22 @@ export function createOpenClawCodingTools(options?: { const freshReadTool = createReadTool(workspaceRoot); return [createOpenClawReadTool(freshReadTool)]; } - if (tool.name === "bash" || tool.name === execToolName) return []; + if (tool.name === "bash" || tool.name === execToolName) { + return []; + } if (tool.name === "write") { - if (sandboxRoot) return []; + if (sandboxRoot) { + return []; + } // Wrap with param normalization for Claude Code compatibility return [ wrapToolParamNormalization(createWriteTool(workspaceRoot), CLAUDE_PARAM_GROUPS.write), ]; } if (tool.name === "edit") { - if (sandboxRoot) return []; + if (sandboxRoot) { + return []; + } // Wrap with param normalization for Claude Code compatibility return [wrapToolParamNormalization(createEditTool(workspaceRoot), CLAUDE_PARAM_GROUPS.edit)]; } diff --git a/src/agents/pty-keys.ts b/src/agents/pty-keys.ts index 445b500761..0c6df8ca3e 100644 --- a/src/agents/pty-keys.ts +++ b/src/agents/pty-keys.ts @@ -133,17 +133,23 @@ export function encodeKeySequence(request: KeyEncodingRequest): KeyEncodingResul } export function encodePaste(text: string, bracketed = true): string { - if (!bracketed) return text; + if (!bracketed) { + return text; + } return `${BRACKETED_PASTE_START}${text}${BRACKETED_PASTE_END}`; } function encodeKeyToken(raw: string, warnings: string[]): string { const token = raw.trim(); - if (!token) return ""; + if (!token) { + return ""; + } if (token.length === 2 && token.startsWith("^")) { const ctrl = toCtrlChar(token[1]); - if (ctrl) return ctrl; + if (ctrl) { + return ctrl; + } } const parsed = parseModifiers(token); @@ -190,10 +196,15 @@ function parseModifiers(token: string) { while (rest.length > 2 && rest[1] === "-") { const mod = rest[0].toLowerCase(); - if (mod === "c") mods.ctrl = true; - else if (mod === "m") mods.alt = true; - else if (mod === "s") mods.shift = true; - else break; + if (mod === "c") { + mods.ctrl = true; + } else if (mod === "m") { + mods.alt = true; + } else if (mod === "s") { + mods.shift = true; + } else { + break; + } sawModifiers = true; rest = rest.slice(2); } @@ -208,7 +219,9 @@ function applyCharModifiers(char: string, mods: Modifiers): string { } if (mods.ctrl) { const ctrl = toCtrlChar(value); - if (ctrl) value = ctrl; + if (ctrl) { + value = ctrl; + } } if (mods.alt) { value = `${ESC}${value}`; @@ -217,8 +230,12 @@ function applyCharModifiers(char: string, mods: Modifiers): string { } function toCtrlChar(char: string): string | null { - if (char.length !== 1) return null; - if (char === "?") return "\x7f"; + if (char.length !== 1) { + return null; + } + if (char === "?") { + return "\x7f"; + } const code = char.toUpperCase().charCodeAt(0); if (code >= 64 && code <= 95) { return String.fromCharCode(code & 0x1f); @@ -228,9 +245,15 @@ function toCtrlChar(char: string): string | null { function xtermModifier(mods: Modifiers): number { let mod = 1; - if (mods.shift) mod += 1; - if (mods.alt) mod += 2; - if (mods.ctrl) mod += 4; + if (mods.shift) { + mod += 1; + } + if (mods.alt) { + mod += 2; + } + if (mods.ctrl) { + mod += 4; + } return mod; } @@ -259,8 +282,12 @@ function hasAnyModifier(mods: Modifiers): boolean { function parseHexByte(raw: string): number | null { const trimmed = raw.trim().toLowerCase(); const normalized = trimmed.startsWith("0x") ? trimmed.slice(2) : trimmed; - if (!/^[0-9a-f]{1,2}$/.test(normalized)) return null; + if (!/^[0-9a-f]{1,2}$/.test(normalized)) { + return null; + } const value = Number.parseInt(normalized, 16); - if (Number.isNaN(value) || value < 0 || value > 0xff) return null; + if (Number.isNaN(value) || value < 0 || value > 0xff) { + return null; + } return value; } diff --git a/src/agents/sandbox-create-args.test.ts b/src/agents/sandbox-create-args.test.ts index 32794b9ad8..c005d29cf1 100644 --- a/src/agents/sandbox-create-args.test.ts +++ b/src/agents/sandbox-create-args.test.ts @@ -84,7 +84,9 @@ describe("buildSandboxCreateArgs", () => { for (let i = 0; i < args.length; i += 1) { if (args[i] === "--ulimit") { const value = args[i + 1]; - if (value) ulimitValues.push(value); + if (value) { + ulimitValues.push(value); + } } } expect(ulimitValues).toEqual( @@ -116,7 +118,9 @@ describe("buildSandboxCreateArgs", () => { for (let i = 0; i < args.length; i++) { if (args[i] === "-v") { const value = args[i + 1]; - if (value) vFlags.push(value); + if (value) { + vFlags.push(value); + } } } expect(vFlags).toContain("/home/user/source:/source:rw"); diff --git a/src/agents/sandbox-paths.ts b/src/agents/sandbox-paths.ts index f3dc787b79..054a16a689 100644 --- a/src/agents/sandbox-paths.ts +++ b/src/agents/sandbox-paths.ts @@ -21,7 +21,9 @@ function expandPath(filePath: string): string { function resolveToCwd(filePath: string, cwd: string): string { const expanded = expandPath(filePath); - if (path.isAbsolute(expanded)) return expanded; + if (path.isAbsolute(expanded)) { + return expanded; + } return path.resolve(cwd, expanded); } @@ -48,7 +50,9 @@ export async function assertSandboxPath(params: { filePath: string; cwd: string; } async function assertNoSymlink(relative: string, root: string) { - if (!relative) return; + if (!relative) { + return; + } const parts = relative.split(path.sep).filter(Boolean); let current = root; for (const part of parts) { diff --git a/src/agents/sandbox-skills.test.ts b/src/agents/sandbox-skills.test.ts index 034175dad4..80fe6ce650 100644 --- a/src/agents/sandbox-skills.test.ts +++ b/src/agents/sandbox-skills.test.ts @@ -57,7 +57,9 @@ async function writeSkill(params: { dir: string; name: string; description: stri function restoreEnv(snapshot: Record) { for (const key of Object.keys(process.env)) { - if (!(key in snapshot)) delete process.env[key]; + if (!(key in snapshot)) { + delete process.env[key]; + } } for (const [key, value] of Object.entries(snapshot)) { if (value === undefined) { diff --git a/src/agents/sandbox/browser.ts b/src/agents/sandbox/browser.ts index ed8d4f6690..0a26a1c68e 100644 --- a/src/agents/sandbox/browser.ts +++ b/src/agents/sandbox/browser.ts @@ -27,7 +27,9 @@ async function waitForSandboxCdp(params: { cdpPort: number; timeoutMs: number }) const t = setTimeout(() => ctrl.abort(), 1000); try { const res = await fetch(url, { signal: ctrl.signal }); - if (res.ok) return true; + if (res.ok) { + return true; + } } finally { clearTimeout(t); } @@ -74,7 +76,9 @@ async function ensureSandboxBrowserImage(image: string) { const result = await execDocker(["image", "inspect", image], { allowFailure: true, }); - if (result.code === 0) return; + if (result.code === 0) { + return; + } throw new Error( `Sandbox browser image not found: ${image}. Build it with scripts/sandbox-browser-setup.sh.`, ); @@ -87,8 +91,12 @@ export async function ensureSandboxBrowser(params: { cfg: SandboxConfig; evaluateEnabled?: boolean; }): Promise { - if (!params.cfg.browser.enabled) return null; - if (!isToolAllowed(params.cfg.tools, "browser")) return null; + if (!params.cfg.browser.enabled) { + return null; + } + if (!isToolAllowed(params.cfg.tools, "browser")) { + return null; + } const slug = params.cfg.scope === "shared" ? "shared" : slugifySessionKey(params.scopeKey); const name = `${params.cfg.browser.containerPrefix}${slug}`; @@ -152,12 +160,16 @@ export async function ensureSandboxBrowser(params: { } const bridge = (() => { - if (shouldReuse && existing) return existing.bridge; + if (shouldReuse && existing) { + return existing.bridge; + } return null; })(); const ensureBridge = async () => { - if (bridge) return bridge; + if (bridge) { + return bridge; + } const onEnsureAttachTarget = params.cfg.browser.autoStart ? async () => { diff --git a/src/agents/sandbox/config-hash.ts b/src/agents/sandbox/config-hash.ts index 6253a9f1f9..604168d79b 100644 --- a/src/agents/sandbox/config-hash.ts +++ b/src/agents/sandbox/config-hash.ts @@ -13,7 +13,9 @@ function isPrimitive(value: unknown): value is string | number | boolean | bigin return value === null || (typeof value !== "object" && typeof value !== "function"); } function normalizeForHash(value: unknown): unknown { - if (value === undefined) return undefined; + if (value === undefined) { + return undefined; + } if (Array.isArray(value)) { const normalized = value .map(normalizeForHash) @@ -31,7 +33,9 @@ function normalizeForHash(value: unknown): unknown { const normalized: Record = {}; for (const [key, entryValue] of entries) { const next = normalizeForHash(entryValue); - if (next !== undefined) normalized[key] = next; + if (next !== undefined) { + normalized[key] = next; + } } return normalized; } @@ -39,10 +43,18 @@ function normalizeForHash(value: unknown): unknown { } function primitiveToString(value: unknown): string { - if (value === null) return "null"; - if (typeof value === "string") return value; - if (typeof value === "number") return String(value); - if (typeof value === "boolean") return value ? "true" : "false"; + if (value === null) { + return "null"; + } + if (typeof value === "string") { + return value; + } + if (typeof value === "number") { + return String(value); + } + if (typeof value === "boolean") { + return value ? "true" : "false"; + } return JSON.stringify(value); } diff --git a/src/agents/sandbox/config.ts b/src/agents/sandbox/config.ts index de3596b096..b07ec715eb 100644 --- a/src/agents/sandbox/config.ts +++ b/src/agents/sandbox/config.ts @@ -27,7 +27,9 @@ export function resolveSandboxScope(params: { scope?: SandboxScope; perSession?: boolean; }): SandboxScope { - if (params.scope) return params.scope; + if (params.scope) { + return params.scope; + } if (typeof params.perSession === "boolean") { return params.perSession ? "session" : "shared"; } diff --git a/src/agents/sandbox/context.ts b/src/agents/sandbox/context.ts index f420282c0a..c6f3ffa089 100644 --- a/src/agents/sandbox/context.ts +++ b/src/agents/sandbox/context.ts @@ -21,13 +21,17 @@ export async function resolveSandboxContext(params: { workspaceDir?: string; }): Promise { const rawSessionKey = params.sessionKey?.trim(); - if (!rawSessionKey) return null; + if (!rawSessionKey) { + return null; + } const runtime = resolveSandboxRuntimeStatus({ cfg: params.config, sessionKey: rawSessionKey, }); - if (!runtime.sandboxed) return null; + if (!runtime.sandboxed) { + return null; + } const cfg = resolveSandboxConfigForAgent(params.config, runtime.agentId); @@ -101,13 +105,17 @@ export async function ensureSandboxWorkspaceForSession(params: { workspaceDir?: string; }): Promise { const rawSessionKey = params.sessionKey?.trim(); - if (!rawSessionKey) return null; + if (!rawSessionKey) { + return null; + } const runtime = resolveSandboxRuntimeStatus({ cfg: params.config, sessionKey: rawSessionKey, }); - if (!runtime.sandboxed) return null; + if (!runtime.sandboxed) { + return null; + } const cfg = resolveSandboxConfigForAgent(params.config, runtime.agentId); diff --git a/src/agents/sandbox/docker.ts b/src/agents/sandbox/docker.ts index 2f904420da..a0031b5505 100644 --- a/src/agents/sandbox/docker.ts +++ b/src/agents/sandbox/docker.ts @@ -38,10 +38,14 @@ export async function readDockerPort(containerName: string, port: number) { const result = await execDocker(["port", containerName, `${port}/tcp`], { allowFailure: true, }); - if (result.code !== 0) return null; + if (result.code !== 0) { + return null; + } const line = result.stdout.trim().split(/\r?\n/)[0] ?? ""; const match = line.match(/:(\d+)\s*$/); - if (!match) return null; + if (!match) { + return null; + } const mapped = Number.parseInt(match[1] ?? "", 10); return Number.isFinite(mapped) ? mapped : null; } @@ -50,7 +54,9 @@ async function dockerImageExists(image: string) { const result = await execDocker(["image", "inspect", image], { allowFailure: true, }); - if (result.code === 0) return true; + if (result.code === 0) { + return true; + } const stderr = result.stderr.trim(); if (stderr.includes("No such image")) { return false; @@ -60,7 +66,9 @@ async function dockerImageExists(image: string) { export async function ensureDockerImage(image: string) { const exists = await dockerImageExists(image); - if (exists) return; + if (exists) { + return; + } if (image === DEFAULT_SANDBOX_IMAGE) { await execDocker(["pull", "debian:bookworm-slim"]); await execDocker(["tag", "debian:bookworm-slim", DEFAULT_SANDBOX_IMAGE]); @@ -73,12 +81,16 @@ export async function dockerContainerState(name: string) { const result = await execDocker(["inspect", "-f", "{{.State.Running}}", name], { allowFailure: true, }); - if (result.code !== 0) return { exists: false, running: false }; + if (result.code !== 0) { + return { exists: false, running: false }; + } return { exists: true, running: result.stdout.trim() === "true" }; } function normalizeDockerLimit(value?: string | number) { - if (value === undefined || value === null) return undefined; + if (value === undefined || value === null) { + return undefined; + } if (typeof value === "number") { return Number.isFinite(value) ? String(value) : undefined; } @@ -90,16 +102,24 @@ function formatUlimitValue( name: string, value: string | number | { soft?: number; hard?: number }, ) { - if (!name.trim()) return null; + if (!name.trim()) { + return null; + } if (typeof value === "number" || typeof value === "string") { const raw = String(value).trim(); return raw ? `${name}=${raw}` : null; } const soft = typeof value.soft === "number" ? Math.max(0, value.soft) : undefined; const hard = typeof value.hard === "number" ? Math.max(0, value.hard) : undefined; - if (soft === undefined && hard === undefined) return null; - if (soft === undefined) return `${name}=${hard}`; - if (hard === undefined) return `${name}=${soft}`; + if (soft === undefined && hard === undefined) { + return null; + } + if (soft === undefined) { + return `${name}=${hard}`; + } + if (hard === undefined) { + return `${name}=${soft}`; + } return `${name}=${soft}:${hard}`; } @@ -120,14 +140,22 @@ export function buildSandboxCreateArgs(params: { args.push("--label", `openclaw.configHash=${params.configHash}`); } for (const [key, value] of Object.entries(params.labels ?? {})) { - if (key && value) args.push("--label", `${key}=${value}`); + if (key && value) { + args.push("--label", `${key}=${value}`); + } + } + if (params.cfg.readOnlyRoot) { + args.push("--read-only"); } - if (params.cfg.readOnlyRoot) args.push("--read-only"); for (const entry of params.cfg.tmpfs) { args.push("--tmpfs", entry); } - if (params.cfg.network) args.push("--network", params.cfg.network); - if (params.cfg.user) args.push("--user", params.cfg.user); + if (params.cfg.network) { + args.push("--network", params.cfg.network); + } + if (params.cfg.user) { + args.push("--user", params.cfg.user); + } for (const cap of params.cfg.capDrop) { args.push("--cap-drop", cap); } @@ -139,18 +167,26 @@ export function buildSandboxCreateArgs(params: { args.push("--security-opt", `apparmor=${params.cfg.apparmorProfile}`); } for (const entry of params.cfg.dns ?? []) { - if (entry.trim()) args.push("--dns", entry); + if (entry.trim()) { + args.push("--dns", entry); + } } for (const entry of params.cfg.extraHosts ?? []) { - if (entry.trim()) args.push("--add-host", entry); + if (entry.trim()) { + args.push("--add-host", entry); + } } if (typeof params.cfg.pidsLimit === "number" && params.cfg.pidsLimit > 0) { args.push("--pids-limit", String(params.cfg.pidsLimit)); } const memory = normalizeDockerLimit(params.cfg.memory); - if (memory) args.push("--memory", memory); + if (memory) { + args.push("--memory", memory); + } const memorySwap = normalizeDockerLimit(params.cfg.memorySwap); - if (memorySwap) args.push("--memory-swap", memorySwap); + if (memorySwap) { + args.push("--memory-swap", memorySwap); + } if (typeof params.cfg.cpus === "number" && params.cfg.cpus > 0) { args.push("--cpus", String(params.cfg.cpus)); } @@ -158,7 +194,9 @@ export function buildSandboxCreateArgs(params: { [string, string | number | { soft?: number; hard?: number }] >) { const formatted = formatUlimitValue(name, value); - if (formatted) args.push("--ulimit", formatted); + if (formatted) { + args.push("--ulimit", formatted); + } } if (params.cfg.binds?.length) { for (const bind of params.cfg.binds) { @@ -213,9 +251,13 @@ async function readContainerConfigHash(containerName: string): Promise") return null; + if (!raw || raw === "") { + return null; + } return raw; }; return await readLabel("openclaw.configHash"); diff --git a/src/agents/sandbox/prune.ts b/src/agents/sandbox/prune.ts index 5c23e58e6d..a106df2aa5 100644 --- a/src/agents/sandbox/prune.ts +++ b/src/agents/sandbox/prune.ts @@ -16,7 +16,9 @@ async function pruneSandboxContainers(cfg: SandboxConfig) { const now = Date.now(); const idleHours = cfg.prune.idleHours; const maxAgeDays = cfg.prune.maxAgeDays; - if (idleHours === 0 && maxAgeDays === 0) return; + if (idleHours === 0 && maxAgeDays === 0) { + return; + } const registry = await readRegistry(); for (const entry of registry.entries) { const idleMs = now - entry.lastUsedAtMs; @@ -42,7 +44,9 @@ async function pruneSandboxBrowsers(cfg: SandboxConfig) { const now = Date.now(); const idleHours = cfg.prune.idleHours; const maxAgeDays = cfg.prune.maxAgeDays; - if (idleHours === 0 && maxAgeDays === 0) return; + if (idleHours === 0 && maxAgeDays === 0) { + return; + } const registry = await readBrowserRegistry(); for (const entry of registry.entries) { const idleMs = now - entry.lastUsedAtMs; @@ -71,7 +75,9 @@ async function pruneSandboxBrowsers(cfg: SandboxConfig) { export async function maybePruneSandboxes(cfg: SandboxConfig) { const now = Date.now(); - if (now - lastPruneAtMs < 5 * 60 * 1000) return; + if (now - lastPruneAtMs < 5 * 60 * 1000) { + return; + } lastPruneAtMs = now; try { await pruneSandboxContainers(cfg); diff --git a/src/agents/sandbox/registry.ts b/src/agents/sandbox/registry.ts index c8b7c46ec5..c0b2d4b9ae 100644 --- a/src/agents/sandbox/registry.ts +++ b/src/agents/sandbox/registry.ts @@ -37,7 +37,9 @@ export async function readRegistry(): Promise { try { const raw = await fs.readFile(SANDBOX_REGISTRY_PATH, "utf-8"); const parsed = JSON.parse(raw) as SandboxRegistry; - if (parsed && Array.isArray(parsed.entries)) return parsed; + if (parsed && Array.isArray(parsed.entries)) { + return parsed; + } } catch { // ignore } @@ -65,7 +67,9 @@ export async function updateRegistry(entry: SandboxRegistryEntry) { export async function removeRegistryEntry(containerName: string) { const registry = await readRegistry(); const next = registry.entries.filter((item) => item.containerName !== containerName); - if (next.length === registry.entries.length) return; + if (next.length === registry.entries.length) { + return; + } await writeRegistry({ entries: next }); } @@ -73,7 +77,9 @@ export async function readBrowserRegistry(): Promise { try { const raw = await fs.readFile(SANDBOX_BROWSER_REGISTRY_PATH, "utf-8"); const parsed = JSON.parse(raw) as SandboxBrowserRegistry; - if (parsed && Array.isArray(parsed.entries)) return parsed; + if (parsed && Array.isArray(parsed.entries)) { + return parsed; + } } catch { // ignore } @@ -104,6 +110,8 @@ export async function updateBrowserRegistry(entry: SandboxBrowserRegistryEntry) export async function removeBrowserRegistryEntry(containerName: string) { const registry = await readBrowserRegistry(); const next = registry.entries.filter((item) => item.containerName !== containerName); - if (next.length === registry.entries.length) return; + if (next.length === registry.entries.length) { + return; + } await writeBrowserRegistry({ entries: next }); } diff --git a/src/agents/sandbox/runtime-status.ts b/src/agents/sandbox/runtime-status.ts index 9d3083bc67..4489d3c3d6 100644 --- a/src/agents/sandbox/runtime-status.ts +++ b/src/agents/sandbox/runtime-status.ts @@ -8,8 +8,12 @@ import { resolveSandboxToolPolicyForAgent } from "./tool-policy.js"; import type { SandboxConfig, SandboxToolPolicyResolved } from "./types.js"; function shouldSandboxSession(cfg: SandboxConfig, sessionKey: string, mainSessionKey: string) { - if (cfg.mode === "off") return false; - if (cfg.mode === "all") return true; + if (cfg.mode === "off") { + return false; + } + if (cfg.mode === "all") { + return true; + } return sessionKey.trim() !== mainSessionKey.trim(); } @@ -17,7 +21,9 @@ function resolveMainSessionKeyForSandbox(params: { cfg?: OpenClawConfig; agentId: string; }): string { - if (params.cfg?.session?.scope === "global") return "global"; + if (params.cfg?.session?.scope === "global") { + return "global"; + } return resolveAgentMainSessionKey({ cfg: params.cfg, agentId: params.agentId, @@ -78,20 +84,26 @@ export function formatSandboxToolPolicyBlockedMessage(params: { toolName: string; }): string | undefined { const tool = params.toolName.trim().toLowerCase(); - if (!tool) return undefined; + if (!tool) { + return undefined; + } const runtime = resolveSandboxRuntimeStatus({ cfg: params.cfg, sessionKey: params.sessionKey, }); - if (!runtime.sandboxed) return undefined; + if (!runtime.sandboxed) { + return undefined; + } const deny = new Set(expandToolGroups(runtime.toolPolicy.deny)); const allow = expandToolGroups(runtime.toolPolicy.allow); const allowSet = allow.length > 0 ? new Set(allow) : null; const blockedByDeny = deny.has(tool); const blockedByAllow = allowSet ? !allowSet.has(tool) : false; - if (!blockedByDeny && !blockedByAllow) return undefined; + if (!blockedByDeny && !blockedByAllow) { + return undefined; + } const reasons: string[] = []; const fixes: string[] = []; @@ -112,7 +124,9 @@ export function formatSandboxToolPolicyBlockedMessage(params: { lines.push(`Reason: ${reasons.join(" + ")}`); lines.push("Fix:"); lines.push(`- agents.defaults.sandbox.mode=off (disable sandbox)`); - for (const fix of fixes) lines.push(`- ${fix}`); + for (const fix of fixes) { + lines.push(`- ${fix}`); + } if (runtime.mode === "non-main") { lines.push(`- Use main session key (direct): ${runtime.mainSessionKey}`); } diff --git a/src/agents/sandbox/shared.ts b/src/agents/sandbox/shared.ts index fe9a80bcd5..1cff3525e9 100644 --- a/src/agents/sandbox/shared.ts +++ b/src/agents/sandbox/shared.ts @@ -24,16 +24,24 @@ export function resolveSandboxWorkspaceDir(root: string, sessionKey: string) { export function resolveSandboxScopeKey(scope: "session" | "agent" | "shared", sessionKey: string) { const trimmed = sessionKey.trim() || "main"; - if (scope === "shared") return "shared"; - if (scope === "session") return trimmed; + if (scope === "shared") { + return "shared"; + } + if (scope === "session") { + return trimmed; + } const agentId = resolveAgentIdFromSessionKey(trimmed); return `agent:${agentId}`; } export function resolveSandboxAgentId(scopeKey: string): string | undefined { const trimmed = scopeKey.trim(); - if (!trimmed || trimmed === "shared") return undefined; + if (!trimmed || trimmed === "shared") { + return undefined; + } const parts = trimmed.split(":").filter(Boolean); - if (parts[0] === "agent" && parts[1]) return normalizeAgentId(parts[1]); + if (parts[0] === "agent" && parts[1]) { + return normalizeAgentId(parts[1]); + } return resolveAgentIdFromSessionKey(trimmed); } diff --git a/src/agents/sandbox/tool-policy.ts b/src/agents/sandbox/tool-policy.ts index aa33940246..a853e2ff01 100644 --- a/src/agents/sandbox/tool-policy.ts +++ b/src/agents/sandbox/tool-policy.ts @@ -15,9 +15,15 @@ type CompiledPattern = function compilePattern(pattern: string): CompiledPattern { const normalized = pattern.trim().toLowerCase(); - if (!normalized) return { kind: "exact", value: "" }; - if (normalized === "*") return { kind: "all" }; - if (!normalized.includes("*")) return { kind: "exact", value: normalized }; + if (!normalized) { + return { kind: "exact", value: "" }; + } + if (normalized === "*") { + return { kind: "all" }; + } + if (!normalized.includes("*")) { + return { kind: "exact", value: normalized }; + } const escaped = normalized.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); return { kind: "regex", @@ -26,7 +32,9 @@ function compilePattern(pattern: string): CompiledPattern { } function compilePatterns(patterns?: string[]): CompiledPattern[] { - if (!Array.isArray(patterns)) return []; + if (!Array.isArray(patterns)) { + return []; + } return expandToolGroups(patterns) .map(compilePattern) .filter((pattern) => pattern.kind !== "exact" || pattern.value); @@ -34,9 +42,15 @@ function compilePatterns(patterns?: string[]): CompiledPattern[] { function matchesAny(name: string, patterns: CompiledPattern[]): boolean { for (const pattern of patterns) { - if (pattern.kind === "all") return true; - if (pattern.kind === "exact" && name === pattern.value) return true; - if (pattern.kind === "regex" && pattern.value.test(name)) return true; + if (pattern.kind === "all") { + return true; + } + if (pattern.kind === "exact" && name === pattern.value) { + return true; + } + if (pattern.kind === "regex" && pattern.value.test(name)) { + return true; + } } return false; } @@ -44,9 +58,13 @@ function matchesAny(name: string, patterns: CompiledPattern[]): boolean { export function isToolAllowed(policy: SandboxToolPolicy, name: string) { const normalized = name.trim().toLowerCase(); const deny = compilePatterns(policy.deny); - if (matchesAny(normalized, deny)) return false; + if (matchesAny(normalized, deny)) { + return false; + } const allow = compilePatterns(policy.allow); - if (allow.length === 0) return true; + if (allow.length === 0) { + return true; + } return matchesAny(normalized, allow); } diff --git a/src/agents/schema/clean-for-gemini.ts b/src/agents/schema/clean-for-gemini.ts index 3c5712a1dd..d87bcdcbbc 100644 --- a/src/agents/schema/clean-for-gemini.ts +++ b/src/agents/schema/clean-for-gemini.ts @@ -34,13 +34,17 @@ export const GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS = new Set([ // Some schemas may use { enum: ["value"], type: "string" }. // Both patterns are flattened to { type: "string", enum: ["a", "b", ...] }. function tryFlattenLiteralAnyOf(variants: unknown[]): { type: string; enum: unknown[] } | null { - if (variants.length === 0) return null; + if (variants.length === 0) { + return null; + } const allValues: unknown[] = []; let commonType: string | null = null; for (const variant of variants) { - if (!variant || typeof variant !== "object") return null; + if (!variant || typeof variant !== "object") { + return null; + } const v = variant as Record; let literalValue: unknown; @@ -53,14 +57,21 @@ function tryFlattenLiteralAnyOf(variants: unknown[]): { type: string; enum: unkn } const variantType = typeof v.type === "string" ? v.type : null; - if (!variantType) return null; - if (commonType === null) commonType = variantType; - else if (commonType !== variantType) return null; + if (!variantType) { + return null; + } + if (commonType === null) { + commonType = variantType; + } else if (commonType !== variantType) { + return null; + } allValues.push(literalValue); } - if (commonType && allValues.length > 0) return { type: commonType, enum: allValues }; + if (commonType && allValues.length > 0) { + return { type: commonType, enum: allValues }; + } return null; } @@ -69,12 +80,16 @@ function isNullSchema(variant: unknown): boolean { return false; } const record = variant as Record; - if ("const" in record && record.const === null) return true; + if ("const" in record && record.const === null) { + return true; + } if (Array.isArray(record.enum) && record.enum.length === 1) { return record.enum[0] === null; } const typeValue = record.type; - if (typeValue === "null") return true; + if (typeValue === "null") { + return true; + } if (Array.isArray(typeValue) && typeValue.length === 1 && typeValue[0] === "null") { return true; } @@ -85,7 +100,9 @@ function stripNullVariants(variants: unknown[]): { variants: unknown[]; stripped: boolean; } { - if (variants.length === 0) return { variants, stripped: false }; + if (variants.length === 0) { + return { variants, stripped: false }; + } const nonNull = variants.filter((variant) => !isNullSchema(variant)); return { variants: nonNull, @@ -110,14 +127,20 @@ function extendSchemaDefs( ? (schema.definitions as Record) : undefined; - if (!defsEntry && !legacyDefsEntry) return defs; + if (!defsEntry && !legacyDefsEntry) { + return defs; + } const next = defs ? new Map(defs) : new Map(); if (defsEntry) { - for (const [key, value] of Object.entries(defsEntry)) next.set(key, value); + for (const [key, value] of Object.entries(defsEntry)) { + next.set(key, value); + } } if (legacyDefsEntry) { - for (const [key, value] of Object.entries(legacyDefsEntry)) next.set(key, value); + for (const [key, value] of Object.entries(legacyDefsEntry)) { + next.set(key, value); + } } return next; } @@ -127,11 +150,17 @@ function decodeJsonPointerSegment(segment: string): string { } function tryResolveLocalRef(ref: string, defs: SchemaDefs | undefined): unknown { - if (!defs) return undefined; + if (!defs) { + return undefined; + } const match = ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/); - if (!match) return undefined; + if (!match) { + return undefined; + } const name = decodeJsonPointerSegment(match[1] ?? ""); - if (!name) return undefined; + if (!name) { + return undefined; + } return defs.get(name); } @@ -140,7 +169,9 @@ function cleanSchemaForGeminiWithDefs( defs: SchemaDefs | undefined, refStack: Set | undefined, ): unknown { - if (!schema || typeof schema !== "object") return schema; + if (!schema || typeof schema !== "object") { + return schema; + } if (Array.isArray(schema)) { return schema.map((item) => cleanSchemaForGeminiWithDefs(item, defs, refStack)); } @@ -150,7 +181,9 @@ function cleanSchemaForGeminiWithDefs( const refValue = typeof obj.$ref === "string" ? obj.$ref : undefined; if (refValue) { - if (refStack?.has(refValue)) return {}; + if (refStack?.has(refValue)) { + return {}; + } const resolved = tryResolveLocalRef(refValue, nextDefs); if (resolved) { @@ -166,14 +199,18 @@ function cleanSchemaForGeminiWithDefs( ...(cleaned as Record), }; for (const key of ["description", "title", "default"]) { - if (key in obj && obj[key] !== undefined) result[key] = obj[key]; + if (key in obj && obj[key] !== undefined) { + result[key] = obj[key]; + } } return result; } const result: Record = {}; for (const key of ["description", "title", "default"]) { - if (key in obj && obj[key] !== undefined) result[key] = obj[key]; + if (key in obj && obj[key] !== undefined) { + result[key] = obj[key]; + } } return result; } @@ -193,7 +230,9 @@ function cleanSchemaForGeminiWithDefs( if (hasAnyOf) { const { variants: nonNullVariants, stripped } = stripNullVariants(cleanedAnyOf ?? []); - if (stripped) cleanedAnyOf = nonNullVariants; + if (stripped) { + cleanedAnyOf = nonNullVariants; + } const flattened = tryFlattenLiteralAnyOf(nonNullVariants); if (flattened) { @@ -202,7 +241,9 @@ function cleanSchemaForGeminiWithDefs( enum: flattened.enum, }; for (const key of ["description", "title", "default"]) { - if (key in obj && obj[key] !== undefined) result[key] = obj[key]; + if (key in obj && obj[key] !== undefined) { + result[key] = obj[key]; + } } return result; } @@ -213,7 +254,9 @@ function cleanSchemaForGeminiWithDefs( ...(lone as Record), }; for (const key of ["description", "title", "default"]) { - if (key in obj && obj[key] !== undefined) result[key] = obj[key]; + if (key in obj && obj[key] !== undefined) { + result[key] = obj[key]; + } } return result; } @@ -223,7 +266,9 @@ function cleanSchemaForGeminiWithDefs( if (hasOneOf) { const { variants: nonNullVariants, stripped } = stripNullVariants(cleanedOneOf ?? []); - if (stripped) cleanedOneOf = nonNullVariants; + if (stripped) { + cleanedOneOf = nonNullVariants; + } const flattened = tryFlattenLiteralAnyOf(nonNullVariants); if (flattened) { @@ -232,7 +277,9 @@ function cleanSchemaForGeminiWithDefs( enum: flattened.enum, }; for (const key of ["description", "title", "default"]) { - if (key in obj && obj[key] !== undefined) result[key] = obj[key]; + if (key in obj && obj[key] !== undefined) { + result[key] = obj[key]; + } } return result; } @@ -243,7 +290,9 @@ function cleanSchemaForGeminiWithDefs( ...(lone as Record), }; for (const key of ["description", "title", "default"]) { - if (key in obj && obj[key] !== undefined) result[key] = obj[key]; + if (key in obj && obj[key] !== undefined) { + result[key] = obj[key]; + } } return result; } @@ -254,14 +303,18 @@ function cleanSchemaForGeminiWithDefs( const cleaned: Record = {}; for (const [key, value] of Object.entries(obj)) { - if (GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS.has(key)) continue; + if (GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS.has(key)) { + continue; + } if (key === "const") { cleaned.enum = [value]; continue; } - if (key === "type" && (hasAnyOf || hasOneOf)) continue; + if (key === "type" && (hasAnyOf || hasOneOf)) { + continue; + } if ( key === "type" && Array.isArray(value) && @@ -311,8 +364,12 @@ function cleanSchemaForGeminiWithDefs( } export function cleanSchemaForGemini(schema: unknown): unknown { - if (!schema || typeof schema !== "object") return schema; - if (Array.isArray(schema)) return schema.map(cleanSchemaForGemini); + if (!schema || typeof schema !== "object") { + return schema; + } + if (Array.isArray(schema)) { + return schema.map(cleanSchemaForGemini); + } const defs = extendSchemaDefs(undefined, schema as Record); return cleanSchemaForGeminiWithDefs(schema, defs, undefined); diff --git a/src/agents/session-slug.ts b/src/agents/session-slug.ts index c3ae926a1b..c15c9746e7 100644 --- a/src/agents/session-slug.ts +++ b/src/agents/session-slug.ts @@ -106,7 +106,9 @@ function randomChoice(values: string[], fallback: string) { function createSlugBase(words = 2) { const parts = [randomChoice(SLUG_ADJECTIVES, "steady"), randomChoice(SLUG_NOUNS, "harbor")]; - if (words > 2) parts.push(randomChoice(SLUG_NOUNS, "reef")); + if (words > 2) { + parts.push(randomChoice(SLUG_NOUNS, "reef")); + } return parts.join("-"); } @@ -114,18 +116,26 @@ export function createSessionSlug(isTaken?: (id: string) => boolean): string { const isIdTaken = isTaken ?? (() => false); for (let attempt = 0; attempt < 12; attempt += 1) { const base = createSlugBase(2); - if (!isIdTaken(base)) return base; + if (!isIdTaken(base)) { + return base; + } for (let i = 2; i <= 12; i += 1) { const candidate = `${base}-${i}`; - if (!isIdTaken(candidate)) return candidate; + if (!isIdTaken(candidate)) { + return candidate; + } } } for (let attempt = 0; attempt < 12; attempt += 1) { const base = createSlugBase(3); - if (!isIdTaken(base)) return base; + if (!isIdTaken(base)) { + return base; + } for (let i = 2; i <= 12; i += 1) { const candidate = `${base}-${i}`; - if (!isIdTaken(candidate)) return candidate; + if (!isIdTaken(candidate)) { + return candidate; + } } } const fallback = `${createSlugBase(3)}-${Math.random().toString(36).slice(2, 5)}`; diff --git a/src/agents/session-tool-result-guard.ts b/src/agents/session-tool-result-guard.ts index feb6b854c3..2a8fb2f27d 100644 --- a/src/agents/session-tool-result-guard.ts +++ b/src/agents/session-tool-result-guard.ts @@ -8,13 +8,19 @@ type ToolCall = { id: string; name?: string }; function extractAssistantToolCalls(msg: Extract): ToolCall[] { const content = msg.content; - if (!Array.isArray(content)) return []; + if (!Array.isArray(content)) { + return []; + } const toolCalls: ToolCall[] = []; for (const block of content) { - if (!block || typeof block !== "object") continue; + if (!block || typeof block !== "object") { + continue; + } const rec = block as { type?: unknown; id?: unknown; name?: unknown }; - if (typeof rec.id !== "string" || !rec.id) continue; + if (typeof rec.id !== "string" || !rec.id) { + continue; + } if (rec.type === "toolCall" || rec.type === "toolUse" || rec.type === "functionCall") { toolCalls.push({ id: rec.id, @@ -27,9 +33,13 @@ function extractAssistantToolCalls(msg: Extract): string | null { const toolCallId = (msg as { toolCallId?: unknown }).toolCallId; - if (typeof toolCallId === "string" && toolCallId) return toolCallId; + if (typeof toolCallId === "string" && toolCallId) { + return toolCallId; + } const toolUseId = (msg as { toolUseId?: unknown }).toolUseId; - if (typeof toolUseId === "string" && toolUseId) return toolUseId; + if (typeof toolUseId === "string" && toolUseId) { + return toolUseId; + } return null; } @@ -68,7 +78,9 @@ export function installSessionToolResultGuard( const allowSyntheticToolResults = opts?.allowSyntheticToolResults ?? true; const flushPendingToolResults = () => { - if (pending.size === 0) return; + if (pending.size === 0) { + return; + } if (allowSyntheticToolResults) { for (const [id, name] of pending.entries()) { const synthetic = makeMissingToolResult({ toolCallId: id, toolName: name }); @@ -90,7 +102,9 @@ export function installSessionToolResultGuard( if (role === "toolResult") { const id = extractToolResultId(message as Extract); const toolName = id ? pending.get(id) : undefined; - if (id) pending.delete(id); + if (id) { + pending.delete(id); + } return originalAppend( persistToolResult(message, { toolCallId: id ?? undefined, diff --git a/src/agents/session-transcript-repair.ts b/src/agents/session-transcript-repair.ts index 68e5a2bf29..af93b1e5ce 100644 --- a/src/agents/session-transcript-repair.ts +++ b/src/agents/session-transcript-repair.ts @@ -9,13 +9,19 @@ function extractToolCallsFromAssistant( msg: Extract, ): ToolCallLike[] { const content = msg.content; - if (!Array.isArray(content)) return []; + if (!Array.isArray(content)) { + return []; + } const toolCalls: ToolCallLike[] = []; for (const block of content) { - if (!block || typeof block !== "object") continue; + if (!block || typeof block !== "object") { + continue; + } const rec = block as { type?: unknown; id?: unknown; name?: unknown }; - if (typeof rec.id !== "string" || !rec.id) continue; + if (typeof rec.id !== "string" || !rec.id) { + continue; + } if (rec.type === "toolCall" || rec.type === "toolUse" || rec.type === "functionCall") { toolCalls.push({ @@ -29,9 +35,13 @@ function extractToolCallsFromAssistant( function extractToolResultId(msg: Extract): string | null { const toolCallId = (msg as { toolCallId?: unknown }).toolCallId; - if (typeof toolCallId === "string" && toolCallId) return toolCallId; + if (typeof toolCallId === "string" && toolCallId) { + return toolCallId; + } const toolUseId = (msg as { toolUseId?: unknown }).toolUseId; - if (typeof toolUseId === "string" && toolUseId) return toolUseId; + if (typeof toolUseId === "string" && toolUseId) { + return toolUseId; + } return null; } @@ -90,7 +100,9 @@ export function repairToolUseResultPairing(messages: AgentMessage[]): ToolUseRep changed = true; return; } - if (id) seenToolResultIds.add(id); + if (id) { + seenToolResultIds.add(id); + } out.push(msg); }; @@ -136,7 +148,9 @@ export function repairToolUseResultPairing(messages: AgentMessage[]): ToolUseRep } const nextRole = (next as { role?: unknown }).role; - if (nextRole === "assistant") break; + if (nextRole === "assistant") { + break; + } if (nextRole === "toolResult") { const toolResult = next as Extract; diff --git a/src/agents/session-write-lock.ts b/src/agents/session-write-lock.ts index 82a2428da8..7335abaf0b 100644 --- a/src/agents/session-write-lock.ts +++ b/src/agents/session-write-lock.ts @@ -19,7 +19,9 @@ type CleanupSignal = (typeof CLEANUP_SIGNALS)[number]; const cleanupHandlers = new Map void>(); function isAlive(pid: number): boolean { - if (!Number.isFinite(pid) || pid <= 0) return false; + if (!Number.isFinite(pid) || pid <= 0) { + return false; + } try { process.kill(pid, 0); return true; @@ -57,7 +59,9 @@ function handleTerminationSignal(signal: CleanupSignal): void { const shouldReraise = process.listenerCount(signal) === 1; if (shouldReraise) { const handler = cleanupHandlers.get(signal); - if (handler) process.off(signal, handler); + if (handler) { + process.off(signal, handler); + } try { process.kill(process.pid, signal); } catch { @@ -67,7 +71,9 @@ function handleTerminationSignal(signal: CleanupSignal): void { } function registerCleanupHandlers(): void { - if (cleanupRegistered) return; + if (cleanupRegistered) { + return; + } cleanupRegistered = true; // Cleanup on normal exit and process.exit() calls @@ -91,8 +97,12 @@ async function readLockPayload(lockPath: string): Promise; - if (typeof parsed.pid !== "number") return null; - if (typeof parsed.createdAt !== "string") return null; + if (typeof parsed.pid !== "number") { + return null; + } + if (typeof parsed.createdAt !== "string") { + return null; + } return { pid: parsed.pid, createdAt: parsed.createdAt }; } catch { return null; @@ -127,9 +137,13 @@ export async function acquireSessionWriteLock(params: { return { release: async () => { const current = HELD_LOCKS.get(normalizedSessionFile); - if (!current) return; + if (!current) { + return; + } current.count -= 1; - if (current.count > 0) return; + if (current.count > 0) { + return; + } HELD_LOCKS.delete(normalizedSessionFile); await current.handle.close(); await fs.rm(current.lockPath, { force: true }); @@ -151,9 +165,13 @@ export async function acquireSessionWriteLock(params: { return { release: async () => { const current = HELD_LOCKS.get(normalizedSessionFile); - if (!current) return; + if (!current) { + return; + } current.count -= 1; - if (current.count > 0) return; + if (current.count > 0) { + return; + } HELD_LOCKS.delete(normalizedSessionFile); await current.handle.close(); await fs.rm(current.lockPath, { force: true }); @@ -161,7 +179,9 @@ export async function acquireSessionWriteLock(params: { }; } catch (err) { const code = (err as { code?: unknown }).code; - if (code !== "EEXIST") throw err; + if (code !== "EEXIST") { + throw err; + } const payload = await readLockPayload(lockPath); const createdAt = payload?.createdAt ? Date.parse(payload.createdAt) : NaN; const stale = !Number.isFinite(createdAt) || Date.now() - createdAt > staleMs; diff --git a/src/agents/shell-utils.ts b/src/agents/shell-utils.ts index 6d4efac599..1a392ba09e 100644 --- a/src/agents/shell-utils.ts +++ b/src/agents/shell-utils.ts @@ -12,7 +12,9 @@ function resolvePowerShellPath(): string { "v1.0", "powershell.exe", ); - if (fs.existsSync(candidate)) return candidate; + if (fs.existsSync(candidate)) { + return candidate; + } } return "powershell.exe"; } @@ -35,9 +37,13 @@ export function getShellConfig(): { shell: string; args: string[] } { // Fish rejects common bashisms used by tools, so prefer bash when detected. if (shellName === "fish") { const bash = resolveShellFromPath("bash"); - if (bash) return { shell: bash, args: ["-c"] }; + if (bash) { + return { shell: bash, args: ["-c"] }; + } const sh = resolveShellFromPath("sh"); - if (sh) return { shell: sh, args: ["-c"] }; + if (sh) { + return { shell: sh, args: ["-c"] }; + } } const shell = envShell && envShell.length > 0 ? envShell : "sh"; return { shell, args: ["-c"] }; @@ -45,7 +51,9 @@ export function getShellConfig(): { shell: string; args: string[] } { function resolveShellFromPath(name: string): string | undefined { const envPath = process.env.PATH ?? ""; - if (!envPath) return undefined; + if (!envPath) { + return undefined; + } const entries = envPath.split(path.delimiter).filter(Boolean); for (const entry of entries) { const candidate = path.join(entry, name); @@ -61,16 +69,22 @@ function resolveShellFromPath(name: string): string | undefined { export function sanitizeBinaryOutput(text: string): string { const scrubbed = text.replace(/[\p{Format}\p{Surrogate}]/gu, ""); - if (!scrubbed) return scrubbed; + if (!scrubbed) { + return scrubbed; + } const chunks: string[] = []; for (const char of scrubbed) { const code = char.codePointAt(0); - if (code == null) continue; + if (code == null) { + continue; + } if (code === 0x09 || code === 0x0a || code === 0x0d) { chunks.push(char); continue; } - if (code < 0x20) continue; + if (code < 0x20) { + continue; + } chunks.push(char); } return chunks.join(""); diff --git a/src/agents/skills-install.ts b/src/agents/skills-install.ts index 508a52d8b3..f0dcea620d 100644 --- a/src/agents/skills-install.ts +++ b/src/agents/skills-install.ts @@ -40,19 +40,25 @@ function isNodeReadableStream(value: unknown): value is NodeJS.ReadableStream { function summarizeInstallOutput(text: string): string | undefined { const raw = text.trim(); - if (!raw) return undefined; + if (!raw) { + return undefined; + } const lines = raw .split("\n") .map((line) => line.trim()) .filter(Boolean); - if (lines.length === 0) return undefined; + if (lines.length === 0) { + return undefined; + } const preferred = lines.find((line) => /^error\b/i.test(line)) ?? lines.find((line) => /\b(err!|error:|failed)\b/i.test(line)) ?? lines.at(-1); - if (!preferred) return undefined; + if (!preferred) { + return undefined; + } const normalized = preferred.replace(/\s+/g, " ").trim(); const maxLen = 200; return normalized.length > maxLen ? `${normalized.slice(0, maxLen - 1)}…` : normalized; @@ -65,7 +71,9 @@ function formatInstallFailureMessage(result: { }): string { const code = typeof result.code === "number" ? `exit ${result.code}` : "unknown exit"; const summary = summarizeInstallOutput(result.stderr) ?? summarizeInstallOutput(result.stdout); - if (!summary) return `Install failed (${code})`; + if (!summary) { + return `Install failed (${code})`; + } return `Install failed (${code}): ${summary}`; } @@ -76,7 +84,9 @@ function resolveInstallId(spec: SkillInstallSpec, index: number): string { function findInstallSpec(entry: SkillEntry, installId: string): SkillInstallSpec | undefined { const specs = entry.metadata?.install ?? []; for (const [index, spec] of specs.entries()) { - if (resolveInstallId(spec, index) === installId) return spec; + if (resolveInstallId(spec, index) === installId) { + return spec; + } } return undefined; } @@ -103,21 +113,29 @@ function buildInstallCommand( } { switch (spec.kind) { case "brew": { - if (!spec.formula) return { argv: null, error: "missing brew formula" }; + if (!spec.formula) { + return { argv: null, error: "missing brew formula" }; + } return { argv: ["brew", "install", spec.formula] }; } case "node": { - if (!spec.package) return { argv: null, error: "missing node package" }; + if (!spec.package) { + return { argv: null, error: "missing node package" }; + } return { argv: buildNodeInstallCommand(spec.package, prefs), }; } case "go": { - if (!spec.module) return { argv: null, error: "missing go module" }; + if (!spec.module) { + return { argv: null, error: "missing go module" }; + } return { argv: ["go", "install", spec.module] }; } case "uv": { - if (!spec.package) return { argv: null, error: "missing uv package" }; + if (!spec.package) { + return { argv: null, error: "missing uv package" }; + } return { argv: ["uv", "tool", "install", spec.package] }; } case "download": { @@ -129,18 +147,28 @@ function buildInstallCommand( } function resolveDownloadTargetDir(entry: SkillEntry, spec: SkillInstallSpec): string { - if (spec.targetDir?.trim()) return resolveUserPath(spec.targetDir); + if (spec.targetDir?.trim()) { + return resolveUserPath(spec.targetDir); + } const key = resolveSkillKey(entry.skill, entry); return path.join(CONFIG_DIR, "tools", key); } function resolveArchiveType(spec: SkillInstallSpec, filename: string): string | undefined { const explicit = spec.archive?.trim().toLowerCase(); - if (explicit) return explicit; + if (explicit) { + return explicit; + } const lower = filename.toLowerCase(); - if (lower.endsWith(".tar.gz") || lower.endsWith(".tgz")) return "tar.gz"; - if (lower.endsWith(".tar.bz2") || lower.endsWith(".tbz2")) return "tar.bz2"; - if (lower.endsWith(".zip")) return "zip"; + if (lower.endsWith(".tar.gz") || lower.endsWith(".tgz")) { + return "tar.gz"; + } + if (lower.endsWith(".tar.bz2") || lower.endsWith(".tbz2")) { + return "tar.bz2"; + } + if (lower.endsWith(".zip")) { + return "zip"; + } return undefined; } @@ -220,7 +248,9 @@ async function installDownloadSpec(params: { } catch { filename = path.basename(url); } - if (!filename) filename = "download"; + if (!filename) { + filename = "download"; + } const targetDir = resolveDownloadTargetDir(entry, spec); await ensureDir(targetDir); @@ -278,22 +308,30 @@ async function installDownloadSpec(params: { async function resolveBrewBinDir(timeoutMs: number, brewExe?: string): Promise { const exe = brewExe ?? (hasBinary("brew") ? "brew" : resolveBrewExecutable()); - if (!exe) return undefined; + if (!exe) { + return undefined; + } const prefixResult = await runCommandWithTimeout([exe, "--prefix"], { timeoutMs: Math.min(timeoutMs, 30_000), }); if (prefixResult.code === 0) { const prefix = prefixResult.stdout.trim(); - if (prefix) return path.join(prefix, "bin"); + if (prefix) { + return path.join(prefix, "bin"); + } } const envPrefix = process.env.HOMEBREW_PREFIX?.trim(); - if (envPrefix) return path.join(envPrefix, "bin"); + if (envPrefix) { + return path.join(envPrefix, "bin"); + } for (const candidate of ["/opt/homebrew/bin", "/usr/local/bin"]) { try { - if (fs.existsSync(candidate)) return candidate; + if (fs.existsSync(candidate)) { + return candidate; + } } catch { // ignore } @@ -418,7 +456,9 @@ export async function installSkill(params: SkillInstallRequest): Promise { diff --git a/src/agents/skills-status.ts b/src/agents/skills-status.ts index be949b757b..db7e4ff2f7 100644 --- a/src/agents/skills-status.ts +++ b/src/agents/skills-status.ts @@ -76,7 +76,9 @@ function selectPreferredInstallSpec( install: SkillInstallSpec[], prefs: SkillsInstallPreferences, ): { spec: SkillInstallSpec; index: number } | undefined { - if (install.length === 0) return undefined; + if (install.length === 0) { + return undefined; + } const indexed = install.map((spec, index) => ({ spec, index })); const findKind = (kind: SkillInstallSpec["kind"]) => indexed.find((item) => item.spec.kind === kind); @@ -86,11 +88,21 @@ function selectPreferredInstallSpec( const goSpec = findKind("go"); const uvSpec = findKind("uv"); - if (prefs.preferBrew && hasBinary("brew") && brewSpec) return brewSpec; - if (uvSpec) return uvSpec; - if (nodeSpec) return nodeSpec; - if (brewSpec) return brewSpec; - if (goSpec) return goSpec; + if (prefs.preferBrew && hasBinary("brew") && brewSpec) { + return brewSpec; + } + if (uvSpec) { + return uvSpec; + } + if (nodeSpec) { + return nodeSpec; + } + if (brewSpec) { + return brewSpec; + } + if (goSpec) { + return goSpec; + } return indexed[0]; } @@ -99,14 +111,18 @@ function normalizeInstallOptions( prefs: SkillsInstallPreferences, ): SkillInstallOption[] { const install = entry.metadata?.install ?? []; - if (install.length === 0) return []; + if (install.length === 0) { + return []; + } const platform = process.platform; const filtered = install.filter((spec) => { const osList = spec.os ?? []; return osList.length === 0 || osList.includes(platform); }); - if (filtered.length === 0) return []; + if (filtered.length === 0) { + return []; + } const toOption = (spec: SkillInstallSpec, index: number): SkillInstallOption => { const id = (spec.id ?? `${spec.kind}-${index}`).trim(); @@ -141,7 +157,9 @@ function normalizeInstallOptions( } const preferred = selectPreferredInstallSpec(filtered, prefs); - if (!preferred) return []; + if (!preferred) { + return []; + } return [toOption(preferred.spec, preferred.index)]; } @@ -172,8 +190,12 @@ function buildSkillStatus( const requiredOs = entry.metadata?.os ?? []; const missingBins = requiredBins.filter((bin) => { - if (hasBinary(bin)) return false; - if (eligibility?.remote?.hasBin?.(bin)) return false; + if (hasBinary(bin)) { + return false; + } + if (eligibility?.remote?.hasBin?.(bin)) { + return false; + } return true; }); const missingAnyBins = @@ -193,8 +215,12 @@ function buildSkillStatus( const missingEnv: string[] = []; for (const envName of requiredEnv) { - if (process.env[envName]) continue; - if (skillConfig?.env?.[envName]) continue; + if (process.env[envName]) { + continue; + } + if (skillConfig?.env?.[envName]) { + continue; + } if (skillConfig?.apiKey && entry.metadata?.primaryEnv === envName) { continue; } diff --git a/src/agents/skills.build-workspace-skills-prompt.syncs-merged-skills-into-target-workspace.test.ts b/src/agents/skills.build-workspace-skills-prompt.syncs-merged-skills-into-target-workspace.test.ts index 81ab263e3f..72cade4aee 100644 --- a/src/agents/skills.build-workspace-skills-prompt.syncs-merged-skills-into-target-workspace.test.ts +++ b/src/agents/skills.build-workspace-skills-prompt.syncs-merged-skills-into-target-workspace.test.ts @@ -104,8 +104,11 @@ describe("buildWorkspaceSkillsPrompt", () => { }); expect(enabledPrompt).toContain("nano-banana-pro"); } finally { - if (originalEnv === undefined) delete process.env.GEMINI_API_KEY; - else process.env.GEMINI_API_KEY = originalEnv; + if (originalEnv === undefined) { + delete process.env.GEMINI_API_KEY; + } else { + process.env.GEMINI_API_KEY = originalEnv; + } } }); it("applies skill filters, including empty lists", async () => { diff --git a/src/agents/skills/bundled-dir.ts b/src/agents/skills/bundled-dir.ts index 0a9471e3e0..826d9665bd 100644 --- a/src/agents/skills/bundled-dir.ts +++ b/src/agents/skills/bundled-dir.ts @@ -4,13 +4,17 @@ import { fileURLToPath } from "node:url"; export function resolveBundledSkillsDir(): string | undefined { const override = process.env.OPENCLAW_BUNDLED_SKILLS_DIR?.trim(); - if (override) return override; + if (override) { + return override; + } // bun --compile: ship a sibling `skills/` next to the executable. try { const execDir = path.dirname(process.execPath); const sibling = path.join(execDir, "skills"); - if (fs.existsSync(sibling)) return sibling; + if (fs.existsSync(sibling)) { + return sibling; + } } catch { // ignore } @@ -20,7 +24,9 @@ export function resolveBundledSkillsDir(): string | undefined { const moduleDir = path.dirname(fileURLToPath(import.meta.url)); const root = path.resolve(moduleDir, "..", "..", ".."); const candidate = path.join(root, "skills"); - if (fs.existsSync(candidate)) return candidate; + if (fs.existsSync(candidate)) { + return candidate; + } } catch { // ignore } diff --git a/src/agents/skills/config.ts b/src/agents/skills/config.ts index f0a59869ad..9e7a74c05e 100644 --- a/src/agents/skills/config.ts +++ b/src/agents/skills/config.ts @@ -10,10 +10,18 @@ const DEFAULT_CONFIG_VALUES: Record = { }; function isTruthy(value: unknown): boolean { - if (value === undefined || value === null) return false; - if (typeof value === "boolean") return value; - if (typeof value === "number") return value !== 0; - if (typeof value === "string") return value.trim().length > 0; + if (value === undefined || value === null) { + return false; + } + if (typeof value === "boolean") { + return value; + } + if (typeof value === "number") { + return value !== 0; + } + if (typeof value === "string") { + return value.trim().length > 0; + } return true; } @@ -21,7 +29,9 @@ export function resolveConfigPath(config: OpenClawConfig | undefined, pathStr: s const parts = pathStr.split(".").filter(Boolean); let current: unknown = config; for (const part of parts) { - if (typeof current !== "object" || current === null) return undefined; + if (typeof current !== "object" || current === null) { + return undefined; + } current = (current as Record)[part]; } return current; @@ -40,9 +50,13 @@ export function resolveSkillConfig( skillKey: string, ): SkillConfig | undefined { const skills = config?.skills?.entries; - if (!skills || typeof skills !== "object") return undefined; + if (!skills || typeof skills !== "object") { + return undefined; + } const entry = (skills as Record)[skillKey]; - if (!entry || typeof entry !== "object") return undefined; + if (!entry || typeof entry !== "object") { + return undefined; + } return entry; } @@ -51,8 +65,12 @@ export function resolveRuntimePlatform(): string { } function normalizeAllowlist(input: unknown): string[] | undefined { - if (!input) return undefined; - if (!Array.isArray(input)) return undefined; + if (!input) { + return undefined; + } + if (!Array.isArray(input)) { + return undefined; + } const normalized = input.map((entry) => String(entry).trim()).filter(Boolean); return normalized.length > 0 ? normalized : undefined; } @@ -68,8 +86,12 @@ export function resolveBundledAllowlist(config?: OpenClawConfig): string[] | und } export function isBundledSkillAllowed(entry: SkillEntry, allowlist?: string[]): boolean { - if (!allowlist || allowlist.length === 0) return true; - if (!isBundledSkill(entry)) return true; + if (!allowlist || allowlist.length === 0) { + return true; + } + if (!isBundledSkill(entry)) { + return true; + } const key = resolveSkillKey(entry.skill, entry); return allowlist.includes(key) || allowlist.includes(entry.skill.name); } @@ -101,8 +123,12 @@ export function shouldIncludeSkill(params: { const osList = entry.metadata?.os ?? []; const remotePlatforms = eligibility?.remote?.platforms ?? []; - if (skillConfig?.enabled === false) return false; - if (!isBundledSkillAllowed(entry, allowBundled)) return false; + if (skillConfig?.enabled === false) { + return false; + } + if (!isBundledSkillAllowed(entry, allowBundled)) { + return false; + } if ( osList.length > 0 && !osList.includes(resolveRuntimePlatform()) && @@ -117,8 +143,12 @@ export function shouldIncludeSkill(params: { const requiredBins = entry.metadata?.requires?.bins ?? []; if (requiredBins.length > 0) { for (const bin of requiredBins) { - if (hasBinary(bin)) continue; - if (eligibility?.remote?.hasBin?.(bin)) continue; + if (hasBinary(bin)) { + continue; + } + if (eligibility?.remote?.hasBin?.(bin)) { + continue; + } return false; } } @@ -127,14 +157,20 @@ export function shouldIncludeSkill(params: { const anyFound = requiredAnyBins.some((bin) => hasBinary(bin)) || eligibility?.remote?.hasAnyBin?.(requiredAnyBins); - if (!anyFound) return false; + if (!anyFound) { + return false; + } } const requiredEnv = entry.metadata?.requires?.env ?? []; if (requiredEnv.length > 0) { for (const envName of requiredEnv) { - if (process.env[envName]) continue; - if (skillConfig?.env?.[envName]) continue; + if (process.env[envName]) { + continue; + } + if (skillConfig?.env?.[envName]) { + continue; + } if (skillConfig?.apiKey && entry.metadata?.primaryEnv === envName) { continue; } @@ -145,7 +181,9 @@ export function shouldIncludeSkill(params: { const requiredConfig = entry.metadata?.requires?.config ?? []; if (requiredConfig.length > 0) { for (const configPath of requiredConfig) { - if (!isConfigPathTruthy(config, configPath)) return false; + if (!isConfigPathTruthy(config, configPath)) { + return false; + } } } diff --git a/src/agents/skills/env-overrides.ts b/src/agents/skills/env-overrides.ts index 7e949c8704..5acebaf3da 100644 --- a/src/agents/skills/env-overrides.ts +++ b/src/agents/skills/env-overrides.ts @@ -10,11 +10,15 @@ export function applySkillEnvOverrides(params: { skills: SkillEntry[]; config?: for (const entry of skills) { const skillKey = resolveSkillKey(entry.skill, entry); const skillConfig = resolveSkillConfig(config, skillKey); - if (!skillConfig) continue; + if (!skillConfig) { + continue; + } if (skillConfig.env) { for (const [envKey, envValue] of Object.entries(skillConfig.env)) { - if (!envValue || process.env[envKey]) continue; + if (!envValue || process.env[envKey]) { + continue; + } updates.push({ key: envKey, prev: process.env[envKey] }); process.env[envKey] = envValue; } @@ -29,8 +33,11 @@ export function applySkillEnvOverrides(params: { skills: SkillEntry[]; config?: return () => { for (const update of updates) { - if (update.prev === undefined) delete process.env[update.key]; - else process.env[update.key] = update.prev; + if (update.prev === undefined) { + delete process.env[update.key]; + } else { + process.env[update.key] = update.prev; + } } }; } @@ -40,16 +47,22 @@ export function applySkillEnvOverridesFromSnapshot(params: { config?: OpenClawConfig; }) { const { snapshot, config } = params; - if (!snapshot) return () => {}; + if (!snapshot) { + return () => {}; + } const updates: Array<{ key: string; prev: string | undefined }> = []; for (const skill of snapshot.skills) { const skillConfig = resolveSkillConfig(config, skill.name); - if (!skillConfig) continue; + if (!skillConfig) { + continue; + } if (skillConfig.env) { for (const [envKey, envValue] of Object.entries(skillConfig.env)) { - if (!envValue || process.env[envKey]) continue; + if (!envValue || process.env[envKey]) { + continue; + } updates.push({ key: envKey, prev: process.env[envKey] }); process.env[envKey] = envValue; } @@ -66,8 +79,11 @@ export function applySkillEnvOverridesFromSnapshot(params: { return () => { for (const update of updates) { - if (update.prev === undefined) delete process.env[update.key]; - else process.env[update.key] = update.prev; + if (update.prev === undefined) { + delete process.env[update.key]; + } else { + process.env[update.key] = update.prev; + } } }; } diff --git a/src/agents/skills/frontmatter.ts b/src/agents/skills/frontmatter.ts index a3c3d13e8d..c82f9eb39c 100644 --- a/src/agents/skills/frontmatter.ts +++ b/src/agents/skills/frontmatter.ts @@ -17,7 +17,9 @@ export function parseFrontmatter(content: string): ParsedSkillFrontmatter { } function normalizeStringList(input: unknown): string[] { - if (!input) return []; + if (!input) { + return []; + } if (Array.isArray(input)) { return input.map((value) => String(value).trim()).filter(Boolean); } @@ -31,7 +33,9 @@ function normalizeStringList(input: unknown): string[] { } function parseInstallSpec(input: unknown): SkillInstallSpec | undefined { - if (!input || typeof input !== "object") return undefined; + if (!input || typeof input !== "object") { + return undefined; + } const raw = input as Record; const kindRaw = typeof raw.kind === "string" ? raw.kind : typeof raw.type === "string" ? raw.type : ""; @@ -44,20 +48,44 @@ function parseInstallSpec(input: unknown): SkillInstallSpec | undefined { kind: kind, }; - if (typeof raw.id === "string") spec.id = raw.id; - if (typeof raw.label === "string") spec.label = raw.label; + if (typeof raw.id === "string") { + spec.id = raw.id; + } + if (typeof raw.label === "string") { + spec.label = raw.label; + } const bins = normalizeStringList(raw.bins); - if (bins.length > 0) spec.bins = bins; + if (bins.length > 0) { + spec.bins = bins; + } const osList = normalizeStringList(raw.os); - if (osList.length > 0) spec.os = osList; - if (typeof raw.formula === "string") spec.formula = raw.formula; - if (typeof raw.package === "string") spec.package = raw.package; - if (typeof raw.module === "string") spec.module = raw.module; - if (typeof raw.url === "string") spec.url = raw.url; - if (typeof raw.archive === "string") spec.archive = raw.archive; - if (typeof raw.extract === "boolean") spec.extract = raw.extract; - if (typeof raw.stripComponents === "number") spec.stripComponents = raw.stripComponents; - if (typeof raw.targetDir === "string") spec.targetDir = raw.targetDir; + if (osList.length > 0) { + spec.os = osList; + } + if (typeof raw.formula === "string") { + spec.formula = raw.formula; + } + if (typeof raw.package === "string") { + spec.package = raw.package; + } + if (typeof raw.module === "string") { + spec.module = raw.module; + } + if (typeof raw.url === "string") { + spec.url = raw.url; + } + if (typeof raw.archive === "string") { + spec.archive = raw.archive; + } + if (typeof raw.extract === "boolean") { + spec.extract = raw.extract; + } + if (typeof raw.stripComponents === "number") { + spec.stripComponents = raw.stripComponents; + } + if (typeof raw.targetDir === "string") { + spec.targetDir = raw.targetDir; + } return spec; } @@ -76,10 +104,14 @@ export function resolveOpenClawMetadata( frontmatter: ParsedSkillFrontmatter, ): OpenClawSkillMetadata | undefined { const raw = getFrontmatterValue(frontmatter, "metadata"); - if (!raw) return undefined; + if (!raw) { + return undefined; + } try { 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]; let metadataRaw: unknown; for (const key of metadataRawCandidates) { @@ -89,7 +121,9 @@ export function resolveOpenClawMetadata( break; } } - if (!metadataRaw || typeof metadataRaw !== "object") return undefined; + if (!metadataRaw || typeof metadataRaw !== "object") { + return undefined; + } const metadataObj = metadataRaw as Record; const requiresRaw = typeof metadataObj.requires === "object" && metadataObj.requires !== null diff --git a/src/agents/skills/plugin-skills.ts b/src/agents/skills/plugin-skills.ts index dfbb1e2738..14e2adc67d 100644 --- a/src/agents/skills/plugin-skills.ts +++ b/src/agents/skills/plugin-skills.ts @@ -17,12 +17,16 @@ export function resolvePluginSkillDirs(params: { config?: OpenClawConfig; }): string[] { const workspaceDir = params.workspaceDir.trim(); - if (!workspaceDir) return []; + if (!workspaceDir) { + return []; + } const registry = loadPluginManifestRegistry({ workspaceDir, config: params.config, }); - if (registry.plugins.length === 0) return []; + if (registry.plugins.length === 0) { + return []; + } const normalizedPlugins = normalizePluginsConfig(params.config?.plugins); const memorySlot = normalizedPlugins.slots.memory; let selectedMemoryPluginId: string | null = null; @@ -30,28 +34,38 @@ export function resolvePluginSkillDirs(params: { const resolved: string[] = []; for (const record of registry.plugins) { - if (!record.skills || record.skills.length === 0) continue; + if (!record.skills || record.skills.length === 0) { + continue; + } const enableState = resolveEnableState(record.id, record.origin, normalizedPlugins); - if (!enableState.enabled) continue; + if (!enableState.enabled) { + continue; + } const memoryDecision = resolveMemorySlotDecision({ id: record.id, kind: record.kind, slot: memorySlot, selectedId: selectedMemoryPluginId, }); - if (!memoryDecision.enabled) continue; + if (!memoryDecision.enabled) { + continue; + } if (memoryDecision.selected && record.kind === "memory") { selectedMemoryPluginId = record.id; } for (const raw of record.skills) { const trimmed = raw.trim(); - if (!trimmed) continue; + if (!trimmed) { + continue; + } const candidate = path.resolve(record.rootDir, trimmed); if (!fs.existsSync(candidate)) { log.warn(`plugin skill path not found (${record.id}): ${candidate}`); continue; } - if (seen.has(candidate)) continue; + if (seen.has(candidate)) { + continue; + } seen.add(candidate); resolved.push(candidate); } diff --git a/src/agents/skills/refresh.ts b/src/agents/skills/refresh.ts index 8f78e0d2bc..cf114686ff 100644 --- a/src/agents/skills/refresh.ts +++ b/src/agents/skills/refresh.ts @@ -92,14 +92,18 @@ export function bumpSkillsSnapshotVersion(params?: { } export function getSkillsSnapshotVersion(workspaceDir?: string): number { - if (!workspaceDir) return globalVersion; + if (!workspaceDir) { + return globalVersion; + } const local = workspaceVersions.get(workspaceDir) ?? 0; return Math.max(globalVersion, local); } export function ensureSkillsWatcher(params: { workspaceDir: string; config?: OpenClawConfig }) { const workspaceDir = params.workspaceDir.trim(); - if (!workspaceDir) return; + if (!workspaceDir) { + return; + } const watchEnabled = params.config?.skills?.load?.watch !== false; const debounceMsRaw = params.config?.skills?.load?.watchDebounceMs; const debounceMs = @@ -111,7 +115,9 @@ export function ensureSkillsWatcher(params: { workspaceDir: string; config?: Ope if (!watchEnabled) { if (existing) { watchers.delete(workspaceDir); - if (existing.timer) clearTimeout(existing.timer); + if (existing.timer) { + clearTimeout(existing.timer); + } void existing.watcher.close().catch(() => {}); } return; @@ -124,7 +130,9 @@ export function ensureSkillsWatcher(params: { workspaceDir: string; config?: Ope } if (existing) { watchers.delete(workspaceDir); - if (existing.timer) clearTimeout(existing.timer); + if (existing.timer) { + clearTimeout(existing.timer); + } void existing.watcher.close().catch(() => {}); } @@ -143,7 +151,9 @@ export function ensureSkillsWatcher(params: { workspaceDir: string; config?: Ope const schedule = (changedPath?: string) => { state.pendingPath = changedPath ?? state.pendingPath; - if (state.timer) clearTimeout(state.timer); + if (state.timer) { + clearTimeout(state.timer); + } state.timer = setTimeout(() => { const pendingPath = state.pendingPath; state.pendingPath = undefined; diff --git a/src/agents/skills/serialize.ts b/src/agents/skills/serialize.ts index 8cbf1f43ff..89c6f8cbb0 100644 --- a/src/agents/skills/serialize.ts +++ b/src/agents/skills/serialize.ts @@ -7,6 +7,8 @@ export async function serializeByKey(key: string, task: () => Promise) { try { return await next; } finally { - if (SKILLS_SYNC_QUEUE.get(key) === next) SKILLS_SYNC_QUEUE.delete(key); + if (SKILLS_SYNC_QUEUE.get(key) === next) { + SKILLS_SYNC_QUEUE.delete(key); + } } } diff --git a/src/agents/skills/workspace.ts b/src/agents/skills/workspace.ts index 7c5bf466dd..51ef238f6c 100644 --- a/src/agents/skills/workspace.ts +++ b/src/agents/skills/workspace.ts @@ -36,7 +36,9 @@ function debugSkillCommandOnce( message: string, meta?: Record, ) { - if (skillCommandDebugOnce.has(messageKey)) return; + if (skillCommandDebugOnce.has(messageKey)) { + return; + } skillCommandDebugOnce.add(messageKey); skillsLogger.debug(message, meta); } @@ -79,14 +81,18 @@ function sanitizeSkillCommandName(raw: string): string { function resolveUniqueSkillCommandName(base: string, used: Set): string { const normalizedBase = base.toLowerCase(); - if (!used.has(normalizedBase)) return base; + if (!used.has(normalizedBase)) { + return base; + } for (let index = 2; index < 1000; index += 1) { const suffix = `_${index}`; const maxBaseLength = Math.max(1, SKILL_COMMAND_MAX_LENGTH - suffix.length); const trimmedBase = base.slice(0, maxBaseLength); const candidate = `${trimmedBase}${suffix}`; const candidateKey = candidate.toLowerCase(); - if (!used.has(candidateKey)) return candidate; + if (!used.has(candidateKey)) { + return candidate; + } } const fallback = `${base.slice(0, Math.max(1, SKILL_COMMAND_MAX_LENGTH - 2))}_x`; return fallback; @@ -102,7 +108,9 @@ function loadSkillEntries( ): SkillEntry[] { const loadSkills = (params: { dir: string; source: string }): Skill[] => { const loaded = loadSkillsFromDir(params); - if (Array.isArray(loaded)) return loaded; + if (Array.isArray(loaded)) { + return loaded; + } if ( loaded && typeof loaded === "object" && @@ -151,10 +159,18 @@ function loadSkillEntries( const merged = new Map(); // Precedence: extra < bundled < managed < workspace - for (const skill of extraSkills) merged.set(skill.name, skill); - for (const skill of bundledSkills) merged.set(skill.name, skill); - for (const skill of managedSkills) merged.set(skill.name, skill); - for (const skill of workspaceSkills) merged.set(skill.name, skill); + for (const skill of extraSkills) { + merged.set(skill.name, skill); + } + for (const skill of bundledSkills) { + merged.set(skill.name, skill); + } + for (const skill of managedSkills) { + merged.set(skill.name, skill); + } + for (const skill of workspaceSkills) { + merged.set(skill.name, skill); + } const skillEntries: SkillEntry[] = Array.from(merged.values()).map((skill) => { let frontmatter: ParsedSkillFrontmatter = {}; @@ -246,7 +262,9 @@ export function resolveSkillsPromptForRun(params: { workspaceDir: string; }): string { const snapshotPrompt = params.skillsSnapshot?.prompt?.trim(); - if (snapshotPrompt) return snapshotPrompt; + if (snapshotPrompt) { + return snapshotPrompt; + } if (params.entries && params.entries.length > 0) { const prompt = buildWorkspaceSkillsPrompt(params.workspaceDir, { entries: params.entries, @@ -277,7 +295,9 @@ export async function syncSkillsToWorkspace(params: { }) { const sourceDir = resolveUserPath(params.sourceWorkspaceDir); const targetDir = resolveUserPath(params.targetWorkspaceDir); - if (sourceDir === targetDir) return; + if (sourceDir === targetDir) { + return; + } await serializeByKey(`syncSkills:${targetDir}`, async () => { const targetSkillsDir = path.join(targetDir, "skills"); @@ -371,8 +391,12 @@ export function buildWorkspaceSkillCommandSpecs( ) .trim() .toLowerCase(); - if (!kindRaw) return undefined; - if (kindRaw !== "tool") return undefined; + if (!kindRaw) { + return undefined; + } + if (kindRaw !== "tool") { + return undefined; + } const toolName = ( entry.frontmatter?.["command-tool"] ?? diff --git a/src/agents/subagent-announce-queue.ts b/src/agents/subagent-announce-queue.ts index d7510b3f55..2c3062d804 100644 --- a/src/agents/subagent-announce-queue.ts +++ b/src/agents/subagent-announce-queue.ts @@ -82,7 +82,9 @@ function getAnnounceQueue( function scheduleAnnounceDrain(key: string) { const queue = ANNOUNCE_QUEUES.get(key); - if (!queue || queue.draining) return; + if (!queue || queue.draining) { + return; + } queue.draining = true; void (async () => { try { @@ -92,19 +94,27 @@ function scheduleAnnounceDrain(key: string) { if (queue.mode === "collect") { if (forceIndividualCollect) { const next = queue.items.shift(); - if (!next) break; + if (!next) { + break; + } await queue.send(next); continue; } const isCrossChannel = hasCrossChannelItems(queue.items, (item) => { - if (!item.origin) return {}; - if (!item.originKey) return { cross: true }; + if (!item.origin) { + return {}; + } + if (!item.originKey) { + return { cross: true }; + } return { key: item.originKey }; }); if (isCrossChannel) { forceIndividualCollect = true; const next = queue.items.shift(); - if (!next) break; + if (!next) { + break; + } await queue.send(next); continue; } @@ -117,7 +127,9 @@ function scheduleAnnounceDrain(key: string) { renderItem: (item, idx) => `---\nQueued #${idx + 1}\n${item.prompt}`.trim(), }); const last = items.at(-1); - if (!last) break; + if (!last) { + break; + } await queue.send({ ...last, prompt }); continue; } @@ -125,13 +137,17 @@ function scheduleAnnounceDrain(key: string) { const summaryPrompt = buildQueueSummaryPrompt({ state: queue, noun: "announce" }); if (summaryPrompt) { const next = queue.items.shift(); - if (!next) break; + if (!next) { + break; + } await queue.send({ ...next, prompt: summaryPrompt }); continue; } const next = queue.items.shift(); - if (!next) break; + if (!next) { + break; + } await queue.send(next); } } catch (err) { diff --git a/src/agents/subagent-announce.format.test.ts b/src/agents/subagent-announce.format.test.ts index 0d119b599a..a75e03df60 100644 --- a/src/agents/subagent-announce.format.test.ts +++ b/src/agents/subagent-announce.format.test.ts @@ -24,8 +24,12 @@ vi.mock("../gateway/call.js", () => ({ if (typed.method === "agent.wait") { return { status: "error", startedAt: 10, endedAt: 20, error: "boom" }; } - if (typed.method === "sessions.patch") return {}; - if (typed.method === "sessions.delete") return {}; + if (typed.method === "sessions.patch") { + return {}; + } + if (typed.method === "sessions.delete") { + return {}; + } return {}; }), })); diff --git a/src/agents/subagent-announce.ts b/src/agents/subagent-announce.ts index f5feda3bcf..0aa90b2c9a 100644 --- a/src/agents/subagent-announce.ts +++ b/src/agents/subagent-announce.ts @@ -23,27 +23,45 @@ import { type AnnounceQueueItem, enqueueAnnounce } from "./subagent-announce-que import { readLatestAssistantReply } from "./tools/agent-step.js"; function formatDurationShort(valueMs?: number) { - if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) return undefined; + if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) { + return undefined; + } const totalSeconds = Math.round(valueMs / 1000); const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; - if (hours > 0) return `${hours}h${minutes}m`; - if (minutes > 0) return `${minutes}m${seconds}s`; + if (hours > 0) { + return `${hours}h${minutes}m`; + } + if (minutes > 0) { + return `${minutes}m${seconds}s`; + } return `${seconds}s`; } function formatTokenCount(value?: number) { - if (!value || !Number.isFinite(value)) return "0"; - if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}m`; - if (value >= 1_000) return `${(value / 1_000).toFixed(1)}k`; + if (!value || !Number.isFinite(value)) { + return "0"; + } + if (value >= 1_000_000) { + return `${(value / 1_000_000).toFixed(1)}m`; + } + if (value >= 1_000) { + return `${(value / 1_000).toFixed(1)}k`; + } return String(Math.round(value)); } function formatUsd(value?: number) { - if (value === undefined || !Number.isFinite(value)) return undefined; - if (value >= 1) return `$${value.toFixed(2)}`; - if (value >= 0.01) return `$${value.toFixed(2)}`; + if (value === undefined || !Number.isFinite(value)) { + return undefined; + } + if (value >= 1) { + return `$${value.toFixed(2)}`; + } + if (value >= 0.01) { + return `$${value.toFixed(2)}`; + } return `$${value.toFixed(4)}`; } @@ -61,7 +79,9 @@ function resolveModelCost(params: { | undefined { const provider = params.provider?.trim(); const model = params.model?.trim(); - if (!provider || !model) return undefined; + if (!provider || !model) { + return undefined; + } const models = params.config.models?.providers?.[provider]?.models ?? []; const entry = models.find((candidate) => candidate.id === model); return entry?.cost; @@ -72,17 +92,23 @@ async function waitForSessionUsage(params: { sessionKey: string }) { const agentId = resolveAgentIdFromSessionKey(params.sessionKey); const storePath = resolveStorePath(cfg.session?.store, { agentId }); let entry = loadSessionStore(storePath)[params.sessionKey]; - if (!entry) return { entry, storePath }; + if (!entry) { + return { entry, storePath }; + } const hasTokens = () => entry && (typeof entry.totalTokens === "number" || typeof entry.inputTokens === "number" || typeof entry.outputTokens === "number"); - if (hasTokens()) return { entry, storePath }; + if (hasTokens()) { + return { entry, storePath }; + } for (let attempt = 0; attempt < 4; attempt += 1) { await new Promise((resolve) => setTimeout(resolve, 200)); entry = loadSessionStore(storePath)[params.sessionKey]; - if (hasTokens()) break; + if (hasTokens()) { + break; + } } return { entry, storePath }; } @@ -125,9 +151,15 @@ function resolveRequesterStoreKey( requesterSessionKey: string, ): string { const raw = requesterSessionKey.trim(); - if (!raw) return raw; - if (raw === "global" || raw === "unknown") return raw; - if (raw.startsWith("agent:")) return raw; + if (!raw) { + return raw; + } + if (raw === "global" || raw === "unknown") { + return raw; + } + if (raw.startsWith("agent:")) { + return raw; + } const mainKey = normalizeMainKey(cfg.session?.mainKey); if (raw === "main" || raw === mainKey) { return resolveMainSessionKey(cfg); @@ -155,7 +187,9 @@ async function maybeQueueSubagentAnnounce(params: { const { cfg, entry } = loadRequesterSessionEntry(params.requesterSessionKey); const canonicalKey = resolveRequesterStoreKey(cfg, params.requesterSessionKey); const sessionId = entry?.sessionId; - if (!sessionId) return "none"; + if (!sessionId) { + return "none"; + } const queueSettings = resolveQueueSettings({ cfg, @@ -167,7 +201,9 @@ async function maybeQueueSubagentAnnounce(params: { const shouldSteer = queueSettings.mode === "steer" || queueSettings.mode === "steer-backlog"; if (shouldSteer) { const steered = queueEmbeddedPiMessage(sessionId, params.triggerMessage); - if (steered) return "steered"; + if (steered) { + return "steered"; + } } const shouldFollowup = @@ -239,10 +275,16 @@ async function buildSubagentStatsLine(params: { parts.push("tokens n/a"); } const costText = formatUsd(cost); - if (costText) parts.push(`est ${costText}`); + if (costText) { + parts.push(`est ${costText}`); + } parts.push(`sessionKey ${params.sessionKey}`); - if (sessionId) parts.push(`sessionId ${sessionId}`); - if (transcriptPath) parts.push(`transcript ${transcriptPath}`); + if (sessionId) { + parts.push(`sessionId ${sessionId}`); + } + if (transcriptPath) { + parts.push(`transcript ${transcriptPath}`); + } return `Stats: ${parts.join(" \u2022 ")}`; } @@ -349,7 +391,9 @@ export async function runSubagentAnnounceFlow(params: { params.endedAt = wait.endedAt; } if (wait?.status === "timeout") { - if (!outcome) outcome = { status: "timeout" }; + if (!outcome) { + outcome = { status: "timeout" }; + } } reply = await readLatestAssistantReply({ sessionKey: params.childSessionKey, @@ -362,7 +406,9 @@ export async function runSubagentAnnounceFlow(params: { }); } - if (!outcome) outcome = { status: "unknown" }; + if (!outcome) { + outcome = { status: "unknown" }; + } // Build stats const statsLine = await buildSubagentStatsLine({ diff --git a/src/agents/subagent-registry.store.ts b/src/agents/subagent-registry.store.ts index 902e268502..a72b223223 100644 --- a/src/agents/subagent-registry.store.ts +++ b/src/agents/subagent-registry.store.ts @@ -37,18 +37,28 @@ export function resolveSubagentRegistryPath(): string { export function loadSubagentRegistryFromDisk(): Map { const pathname = resolveSubagentRegistryPath(); const raw = loadJsonFile(pathname); - if (!raw || typeof raw !== "object") return new Map(); + if (!raw || typeof raw !== "object") { + return new Map(); + } const record = raw as Partial; - if (record.version !== 1 && record.version !== 2) return new Map(); + if (record.version !== 1 && record.version !== 2) { + return new Map(); + } const runsRaw = record.runs; - if (!runsRaw || typeof runsRaw !== "object") return new Map(); + if (!runsRaw || typeof runsRaw !== "object") { + return new Map(); + } const out = new Map(); const isLegacy = record.version === 1; let migrated = false; for (const [runId, entry] of Object.entries(runsRaw)) { - if (!entry || typeof entry !== "object") continue; + if (!entry || typeof entry !== "object") { + continue; + } const typed = entry as LegacySubagentRunRecord; - if (!typed.runId || typeof typed.runId !== "string") continue; + if (!typed.runId || typeof typed.runId !== "string") { + continue; + } const legacyCompletedAt = isLegacy && typeof typed.announceCompletedAt === "number" ? typed.announceCompletedAt @@ -81,7 +91,9 @@ export function loadSubagentRegistryFromDisk(): Map { cleanupCompletedAt, cleanupHandled, }); - if (isLegacy) migrated = true; + if (isLegacy) { + migrated = true; + } } if (migrated) { try { diff --git a/src/agents/subagent-registry.ts b/src/agents/subagent-registry.ts index 930209d52f..7d931219b6 100644 --- a/src/agents/subagent-registry.ts +++ b/src/agents/subagent-registry.ts @@ -45,13 +45,21 @@ function persistSubagentRuns() { const resumedRuns = new Set(); function resumeSubagentRun(runId: string) { - if (!runId || resumedRuns.has(runId)) return; + if (!runId || resumedRuns.has(runId)) { + return; + } const entry = subagentRuns.get(runId); - if (!entry) return; - if (entry.cleanupCompletedAt) return; + if (!entry) { + return; + } + if (entry.cleanupCompletedAt) { + return; + } if (typeof entry.endedAt === "number" && entry.endedAt > 0) { - if (!beginSubagentCleanup(runId)) return; + if (!beginSubagentCleanup(runId)) { + return; + } const requesterOrigin = normalizeDeliveryContext(entry.requesterOrigin); void runSubagentAnnounceFlow({ childSessionKey: entry.childSessionKey, @@ -82,13 +90,19 @@ function resumeSubagentRun(runId: string) { } function restoreSubagentRunsOnce() { - if (restoreAttempted) return; + if (restoreAttempted) { + return; + } restoreAttempted = true; try { const restored = loadSubagentRegistryFromDisk(); - if (restored.size === 0) return; + if (restored.size === 0) { + return; + } for (const [runId, entry] of restored.entries()) { - if (!runId || !entry) continue; + if (!runId || !entry) { + continue; + } // Keep any newer in-memory entries. if (!subagentRuns.has(runId)) { subagentRuns.set(runId, entry); @@ -111,7 +125,9 @@ function restoreSubagentRunsOnce() { function resolveArchiveAfterMs(cfg?: ReturnType) { const config = cfg ?? loadConfig(); const minutes = config.agents?.defaults?.subagents?.archiveAfterMinutes ?? 60; - if (!Number.isFinite(minutes) || minutes <= 0) return undefined; + if (!Number.isFinite(minutes) || minutes <= 0) { + return undefined; + } return Math.max(1, Math.floor(minutes)) * 60_000; } @@ -123,7 +139,9 @@ function resolveSubagentWaitTimeoutMs( } function startSweeper() { - if (sweeper) return; + if (sweeper) { + return; + } sweeper = setInterval(() => { void sweepSubagentRuns(); }, 60_000); @@ -131,7 +149,9 @@ function startSweeper() { } function stopSweeper() { - if (!sweeper) return; + if (!sweeper) { + return; + } clearInterval(sweeper); sweeper = null; } @@ -140,7 +160,9 @@ async function sweepSubagentRuns() { const now = Date.now(); let mutated = false; for (const [runId, entry] of subagentRuns.entries()) { - if (!entry.archiveAtMs || entry.archiveAtMs > now) continue; + if (!entry.archiveAtMs || entry.archiveAtMs > now) { + continue; + } subagentRuns.delete(runId); mutated = true; try { @@ -153,8 +175,12 @@ async function sweepSubagentRuns() { // ignore } } - if (mutated) persistSubagentRuns(); - if (subagentRuns.size === 0) stopSweeper(); + if (mutated) { + persistSubagentRuns(); + } + if (subagentRuns.size === 0) { + stopSweeper(); + } } function ensureListener() { @@ -163,7 +189,9 @@ function ensureListener() { } listenerStarted = true; listenerStop = onAgentEvent((evt) => { - if (!evt || evt.stream !== "lifecycle") return; + if (!evt || evt.stream !== "lifecycle") { + return; + } const entry = subagentRuns.get(evt.runId); if (!entry) { return; @@ -177,7 +205,9 @@ function ensureListener() { } return; } - if (phase !== "end" && phase !== "error") return; + if (phase !== "end" && phase !== "error") { + return; + } const endedAt = typeof evt.data?.endedAt === "number" ? evt.data.endedAt : Date.now(); entry.endedAt = endedAt; if (phase === "error") { @@ -214,7 +244,9 @@ function ensureListener() { function finalizeSubagentCleanup(runId: string, cleanup: "delete" | "keep", didAnnounce: boolean) { const entry = subagentRuns.get(runId); - if (!entry) return; + if (!entry) { + return; + } if (cleanup === "delete") { subagentRuns.delete(runId); persistSubagentRuns(); @@ -232,9 +264,15 @@ function finalizeSubagentCleanup(runId: string, cleanup: "delete" | "keep", didA function beginSubagentCleanup(runId: string) { const entry = subagentRuns.get(runId); - if (!entry) return false; - if (entry.cleanupCompletedAt) return false; - if (entry.cleanupHandled) return false; + if (!entry) { + return false; + } + if (entry.cleanupCompletedAt) { + return false; + } + if (entry.cleanupHandled) { + return false; + } entry.cleanupHandled = true; persistSubagentRuns(); return true; @@ -273,7 +311,9 @@ export function registerSubagentRun(params: { }); ensureListener(); persistSubagentRuns(); - if (archiveAfterMs) startSweeper(); + if (archiveAfterMs) { + startSweeper(); + } // Wait for subagent completion via gateway RPC (cross-process). // The in-process lifecycle listener is a fallback for embedded runs. void waitForSubagentCompletion(params.runId, waitTimeoutMs); @@ -290,9 +330,13 @@ async function waitForSubagentCompletion(runId: string, waitTimeoutMs: number) { }, timeoutMs: timeoutMs + 10_000, }); - if (wait?.status !== "ok" && wait?.status !== "error") return; + if (wait?.status !== "ok" && wait?.status !== "error") { + return; + } const entry = subagentRuns.get(runId); - if (!entry) return; + if (!entry) { + return; + } let mutated = false; if (typeof wait.startedAt === "number") { entry.startedAt = wait.startedAt; @@ -309,8 +353,12 @@ async function waitForSubagentCompletion(runId: string, waitTimeoutMs: number) { entry.outcome = wait.status === "error" ? { status: "error", error: wait.error } : { status: "ok" }; mutated = true; - if (mutated) persistSubagentRuns(); - if (!beginSubagentCleanup(runId)) return; + if (mutated) { + persistSubagentRuns(); + } + if (!beginSubagentCleanup(runId)) { + return; + } const requesterOrigin = normalizeDeliveryContext(entry.requesterOrigin); void runSubagentAnnounceFlow({ childSessionKey: entry.childSessionKey, @@ -354,13 +402,19 @@ export function addSubagentRunForTests(entry: SubagentRunRecord) { export function releaseSubagentRun(runId: string) { const didDelete = subagentRuns.delete(runId); - if (didDelete) persistSubagentRuns(); - if (subagentRuns.size === 0) stopSweeper(); + if (didDelete) { + persistSubagentRuns(); + } + if (subagentRuns.size === 0) { + stopSweeper(); + } } export function listSubagentRunsForRequester(requesterSessionKey: string): SubagentRunRecord[] { const key = requesterSessionKey.trim(); - if (!key) return []; + if (!key) { + return []; + } return [...subagentRuns.values()].filter((entry) => entry.requesterSessionKey === key); } diff --git a/src/agents/system-prompt-params.ts b/src/agents/system-prompt-params.ts index 405a202d27..f450768d09 100644 --- a/src/agents/system-prompt-params.ts +++ b/src/agents/system-prompt-params.ts @@ -68,7 +68,9 @@ function resolveRepoRoot(params: { try { const resolved = path.resolve(configured); const stat = fs.statSync(resolved); - if (stat.isDirectory()) return resolved; + if (stat.isDirectory()) { + return resolved; + } } catch { // ignore invalid config path } @@ -79,10 +81,14 @@ function resolveRepoRoot(params: { const seen = new Set(); for (const candidate of candidates) { const resolved = path.resolve(candidate); - if (seen.has(resolved)) continue; + if (seen.has(resolved)) { + continue; + } seen.add(resolved); const root = findGitRoot(resolved); - if (root) return root; + if (root) { + return root; + } } return undefined; } @@ -93,12 +99,16 @@ function findGitRoot(startDir: string): string | null { const gitPath = path.join(current, ".git"); try { const stat = fs.statSync(gitPath); - if (stat.isDirectory() || stat.isFile()) return current; + if (stat.isDirectory() || stat.isFile()) { + return current; + } } catch { // ignore missing .git at this level } const parent = path.dirname(current); - if (parent === current) break; + if (parent === current) { + break; + } current = parent; } return null; diff --git a/src/agents/system-prompt-report.ts b/src/agents/system-prompt-report.ts index e32b1254f9..4ce7ece563 100644 --- a/src/agents/system-prompt-report.ts +++ b/src/agents/system-prompt-report.ts @@ -10,15 +10,21 @@ function extractBetween( endMarker: string, ): { text: string; found: boolean } { const start = input.indexOf(startMarker); - if (start === -1) return { text: "", found: false }; + if (start === -1) { + return { text: "", found: false }; + } const end = input.indexOf(endMarker, start + startMarker.length); - if (end === -1) return { text: input.slice(start), found: true }; + if (end === -1) { + return { text: input.slice(start), found: true }; + } return { text: input.slice(start, end), found: true }; } function parseSkillBlocks(skillsPrompt: string): Array<{ name: string; blockChars: number }> { const prompt = skillsPrompt.trim(); - if (!prompt) return []; + if (!prompt) { + return []; + } const blocks = Array.from(prompt.matchAll(/[\s\S]*?<\/skill>/gi)).map( (match) => match[0] ?? "", ); @@ -58,7 +64,9 @@ function buildToolsEntries(tools: AgentTool[]): SessionSystemPromptReport["tools const summary = tool.description?.trim() || tool.label?.trim() || ""; const summaryChars = summary.length; const schemaChars = (() => { - if (!tool.parameters || typeof tool.parameters !== "object") return 0; + if (!tool.parameters || typeof tool.parameters !== "object") { + return 0; + } try { return JSON.stringify(tool.parameters).length; } catch { @@ -71,7 +79,9 @@ function buildToolsEntries(tools: AgentTool[]): SessionSystemPromptReport["tools ? (tool.parameters as Record) : null; const props = schema && typeof schema.properties === "object" ? schema.properties : null; - if (!props || typeof props !== "object") return null; + if (!props || typeof props !== "object") { + return null; + } return Object.keys(props as Record).length; })(); return { name, summaryChars, schemaChars, propertiesCount }; @@ -83,7 +93,9 @@ function extractToolListText(systemPrompt: string): string { const markerB = "\nTOOLS.md does not control tool availability; it is user guidance for how to use external tools."; const extracted = extractBetween(systemPrompt, markerA, markerB); - if (!extracted.found) return ""; + if (!extracted.found) { + return ""; + } return extracted.text.replace(markerA, "").trim(); } diff --git a/src/agents/system-prompt.ts b/src/agents/system-prompt.ts index 6ea8e3ad94..320c3a6bea 100644 --- a/src/agents/system-prompt.ts +++ b/src/agents/system-prompt.ts @@ -17,9 +17,13 @@ function buildSkillsSection(params: { isMinimal: boolean; readToolName: string; }) { - if (params.isMinimal) return []; + if (params.isMinimal) { + return []; + } const trimmed = params.skillsPrompt?.trim(); - if (!trimmed) return []; + if (!trimmed) { + return []; + } return [ "## Skills (mandatory)", "Before replying: scan entries.", @@ -33,7 +37,9 @@ function buildSkillsSection(params: { } function buildMemorySection(params: { isMinimal: boolean; availableTools: Set }) { - if (params.isMinimal) return []; + if (params.isMinimal) { + return []; + } if (!params.availableTools.has("memory_search") && !params.availableTools.has("memory_get")) { return []; } @@ -45,17 +51,23 @@ function buildMemorySection(params: { isMinimal: boolean; availableTools: Set(); for (const [key, value] of Object.entries(params.toolSummaries ?? {})) { const normalized = key.trim().toLowerCase(); - if (!normalized || !value?.trim()) continue; + if (!normalized || !value?.trim()) { + continue; + } externalToolSummaries.set(normalized, value.trim()); } const extraTools = Array.from( diff --git a/src/agents/timeout.ts b/src/agents/timeout.ts index 9abd10fc4c..9d33f96b63 100644 --- a/src/agents/timeout.ts +++ b/src/agents/timeout.ts @@ -21,12 +21,16 @@ export function resolveAgentTimeoutMs(opts: { const defaultMs = resolveAgentTimeoutSeconds(opts.cfg) * 1000; const overrideMs = normalizeNumber(opts.overrideMs); if (overrideMs !== undefined) { - if (overrideMs <= 0) return defaultMs; + if (overrideMs <= 0) { + return defaultMs; + } return Math.max(overrideMs, minMs); } const overrideSeconds = normalizeNumber(opts.overrideSeconds); if (overrideSeconds !== undefined) { - if (overrideSeconds <= 0) return defaultMs; + if (overrideSeconds <= 0) { + return defaultMs; + } return Math.max(overrideSeconds * 1000, minMs); } return Math.max(defaultMs, minMs); diff --git a/src/agents/tool-call-id.ts b/src/agents/tool-call-id.ts index 8b7e054179..380c93c18a 100644 --- a/src/agents/tool-call-id.ts +++ b/src/agents/tool-call-id.ts @@ -14,14 +14,20 @@ const STRICT9_LEN = 9; */ export function sanitizeToolCallId(id: string, mode: ToolCallIdMode = "strict"): string { if (!id || typeof id !== "string") { - if (mode === "strict9") return "defaultid"; + if (mode === "strict9") { + return "defaultid"; + } return "defaulttoolid"; } if (mode === "strict9") { const alphanumericOnly = id.replace(/[^a-zA-Z0-9]/g, ""); - if (alphanumericOnly.length >= STRICT9_LEN) return alphanumericOnly.slice(0, STRICT9_LEN); - if (alphanumericOnly.length > 0) return shortHash(alphanumericOnly, STRICT9_LEN); + if (alphanumericOnly.length >= STRICT9_LEN) { + return alphanumericOnly.slice(0, STRICT9_LEN); + } + if (alphanumericOnly.length > 0) { + return shortHash(alphanumericOnly, STRICT9_LEN); + } return shortHash("sanitized", STRICT9_LEN); } @@ -31,7 +37,9 @@ export function sanitizeToolCallId(id: string, mode: ToolCallIdMode = "strict"): } export function isValidCloudCodeAssistToolId(id: string, mode: ToolCallIdMode = "strict"): boolean { - if (!id || typeof id !== "string") return false; + if (!id || typeof id !== "string") { + return false; + } if (mode === "strict9") { return /^[a-zA-Z0-9]{9}$/.test(id); } @@ -47,11 +55,15 @@ function makeUniqueToolId(params: { id: string; used: Set; mode: ToolCal if (params.mode === "strict9") { const base = sanitizeToolCallId(params.id, params.mode); const candidate = base.length >= STRICT9_LEN ? base.slice(0, STRICT9_LEN) : ""; - if (candidate && !params.used.has(candidate)) return candidate; + if (candidate && !params.used.has(candidate)) { + return candidate; + } for (let i = 0; i < 1000; i += 1) { const hashed = shortHash(`${params.id}:${i}`, STRICT9_LEN); - if (!params.used.has(hashed)) return hashed; + if (!params.used.has(hashed)) { + return hashed; + } } return shortHash(`${params.id}:${Date.now()}`, STRICT9_LEN); @@ -60,7 +72,9 @@ function makeUniqueToolId(params: { id: string; used: Set; mode: ToolCal const MAX_LEN = 40; const base = sanitizeToolCallId(params.id, params.mode).slice(0, MAX_LEN); - if (!params.used.has(base)) return base; + if (!params.used.has(base)) { + return base; + } const hash = shortHash(params.id); // Use separator based on mode: none for strict, underscore for non-strict variants @@ -68,12 +82,16 @@ function makeUniqueToolId(params: { id: string; used: Set; mode: ToolCal const maxBaseLen = MAX_LEN - separator.length - hash.length; const clippedBase = base.length > maxBaseLen ? base.slice(0, maxBaseLen) : base; const candidate = `${clippedBase}${separator}${hash}`; - if (!params.used.has(candidate)) return candidate; + if (!params.used.has(candidate)) { + return candidate; + } for (let i = 2; i < 1000; i += 1) { const suffix = params.mode === "strict" ? `x${i}` : `_${i}`; const next = `${candidate.slice(0, MAX_LEN - suffix.length)}${suffix}`; - if (!params.used.has(next)) return next; + if (!params.used.has(next)) { + return next; + } } const ts = params.mode === "strict" ? `t${Date.now()}` : `_${Date.now()}`; @@ -85,11 +103,15 @@ function rewriteAssistantToolCallIds(params: { resolve: (id: string) => string; }): Extract { const content = params.message.content; - if (!Array.isArray(content)) return params.message; + if (!Array.isArray(content)) { + return params.message; + } let changed = false; const next = content.map((block) => { - if (!block || typeof block !== "object") return block; + if (!block || typeof block !== "object") { + return block; + } const rec = block as { type?: unknown; id?: unknown }; const type = rec.type; const id = rec.id; @@ -101,12 +123,16 @@ function rewriteAssistantToolCallIds(params: { return block; } const nextId = params.resolve(id); - if (nextId === id) return block; + if (nextId === id) { + return block; + } changed = true; return { ...(block as unknown as Record), id: nextId }; }); - if (!changed) return params.message; + if (!changed) { + return params.message; + } return { ...params.message, content: next as typeof params.message.content }; } @@ -154,7 +180,9 @@ export function sanitizeToolCallIdsForCloudCodeAssist( const resolve = (id: string) => { const existing = map.get(id); - if (existing) return existing; + if (existing) { + return existing; + } const next = makeUniqueToolId({ id, used, mode }); map.set(id, next); used.add(next); @@ -163,14 +191,18 @@ export function sanitizeToolCallIdsForCloudCodeAssist( let changed = false; const out = messages.map((msg) => { - if (!msg || typeof msg !== "object") return msg; + if (!msg || typeof msg !== "object") { + return msg; + } const role = (msg as { role?: unknown }).role; if (role === "assistant") { const next = rewriteAssistantToolCallIds({ message: msg as Extract, resolve, }); - if (next !== msg) changed = true; + if (next !== msg) { + changed = true; + } return next; } if (role === "toolResult") { @@ -178,7 +210,9 @@ export function sanitizeToolCallIdsForCloudCodeAssist( message: msg as Extract, resolve, }); - if (next !== msg) changed = true; + if (next !== msg) { + changed = true; + } return next; } return msg; diff --git a/src/agents/tool-display.ts b/src/agents/tool-display.ts index da6eab476d..f3b1fae4fc 100644 --- a/src/agents/tool-display.ts +++ b/src/agents/tool-display.ts @@ -59,7 +59,9 @@ function normalizeToolName(name?: string): string { function defaultTitle(name: string): string { const cleaned = name.replace(/_/g, " ").trim(); - if (!cleaned) return "Tool"; + if (!cleaned) { + return "Tool"; + } return cleaned .split(/\s+/) .map((part) => @@ -72,31 +74,43 @@ function defaultTitle(name: string): string { function normalizeVerb(value?: string): string | undefined { const trimmed = value?.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } return trimmed.replace(/_/g, " "); } function coerceDisplayValue(value: unknown): string | undefined { - if (value === null || value === undefined) return undefined; + if (value === null || value === undefined) { + return undefined; + } if (typeof value === "string") { const trimmed = value.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } const firstLine = trimmed.split(/\r?\n/)[0]?.trim() ?? ""; - if (!firstLine) return undefined; + if (!firstLine) { + return undefined; + } return firstLine.length > 160 ? `${firstLine.slice(0, 157)}…` : firstLine; } if (typeof value === "boolean") { return value ? "true" : undefined; } if (typeof value === "number") { - if (!Number.isFinite(value) || value === 0) return undefined; + if (!Number.isFinite(value) || value === 0) { + return undefined; + } return String(value); } if (Array.isArray(value)) { const values = value .map((item) => coerceDisplayValue(item)) .filter((item): item is string => Boolean(item)); - if (values.length === 0) return undefined; + if (values.length === 0) { + return undefined; + } const preview = values.slice(0, 3).join(", "); return values.length > 3 ? `${preview}…` : preview; } @@ -104,11 +118,17 @@ function coerceDisplayValue(value: unknown): string | undefined { } function lookupValueByPath(args: unknown, path: string): unknown { - if (!args || typeof args !== "object") return undefined; + if (!args || typeof args !== "object") { + return undefined; + } let current: unknown = args; for (const segment of path.split(".")) { - if (!segment) return undefined; - if (!current || typeof current !== "object") return undefined; + if (!segment) { + return undefined; + } + if (!current || typeof current !== "object") { + return undefined; + } const record = current as Record; current = record[segment]; } @@ -119,7 +139,9 @@ function formatDetailKey(raw: string): string { const segments = raw.split(".").filter(Boolean); const last = segments.at(-1) ?? raw; const override = DETAIL_LABEL_OVERRIDES[last]; - if (override) return override; + if (override) { + return override; + } const cleaned = last.replace(/_/g, " ").replace(/-/g, " "); const spaced = cleaned.replace(/([a-z0-9])([A-Z])/g, "$1 $2"); return spaced.trim().toLowerCase() || last.toLowerCase(); @@ -130,21 +152,31 @@ function resolveDetailFromKeys(args: unknown, keys: string[]): string | undefine for (const key of keys) { const value = lookupValueByPath(args, key); const display = coerceDisplayValue(value); - if (!display) continue; + if (!display) { + continue; + } entries.push({ label: formatDetailKey(key), value: display }); } - if (entries.length === 0) return undefined; - if (entries.length === 1) return entries[0].value; + if (entries.length === 0) { + return undefined; + } + if (entries.length === 1) { + return entries[0].value; + } const seen = new Set(); const unique: Array<{ label: string; value: string }> = []; for (const entry of entries) { const token = `${entry.label}:${entry.value}`; - if (seen.has(token)) continue; + if (seen.has(token)) { + continue; + } seen.add(token); unique.push(entry); } - if (unique.length === 0) return undefined; + if (unique.length === 0) { + return undefined; + } return unique .slice(0, MAX_DETAIL_ENTRIES) .map((entry) => `${entry.label} ${entry.value}`) @@ -152,10 +184,14 @@ function resolveDetailFromKeys(args: unknown, keys: string[]): string | undefine } function resolveReadDetail(args: unknown): string | undefined { - if (!args || typeof args !== "object") return undefined; + if (!args || typeof args !== "object") { + return undefined; + } const record = args as Record; const path = typeof record.path === "string" ? record.path : undefined; - if (!path) return undefined; + if (!path) { + return undefined; + } const offset = typeof record.offset === "number" ? record.offset : undefined; const limit = typeof record.limit === "number" ? record.limit : undefined; if (offset !== undefined && limit !== undefined) { @@ -165,7 +201,9 @@ function resolveReadDetail(args: unknown): string | undefined { } function resolveWriteDetail(args: unknown): string | undefined { - if (!args || typeof args !== "object") return undefined; + if (!args || typeof args !== "object") { + return undefined; + } const record = args as Record; const path = typeof record.path === "string" ? record.path : undefined; return path; @@ -175,7 +213,9 @@ function resolveActionSpec( spec: ToolDisplaySpec | undefined, action: string | undefined, ): ToolDisplayActionSpec | undefined { - if (!spec || !action) return undefined; + if (!spec || !action) { + return undefined; + } return spec.actions?.[action] ?? undefined; } @@ -199,7 +239,9 @@ export function resolveToolDisplay(params: { const verb = normalizeVerb(actionSpec?.label ?? action); let detail: string | undefined; - if (key === "read") detail = resolveReadDetail(params.args); + if (key === "read") { + detail = resolveReadDetail(params.args); + } if (!detail && (key === "write" || key === "edit" || key === "attach")) { detail = resolveWriteDetail(params.args); } @@ -229,9 +271,15 @@ export function resolveToolDisplay(params: { export function formatToolDetail(display: ToolDisplay): string | undefined { const parts: string[] = []; - if (display.verb) parts.push(display.verb); - if (display.detail) parts.push(redactToolDetail(display.detail)); - if (parts.length === 0) return undefined; + if (display.verb) { + parts.push(display.verb); + } + if (display.detail) { + parts.push(redactToolDetail(display.detail)); + } + if (parts.length === 0) { + return undefined; + } return parts.join(" · "); } diff --git a/src/agents/tool-images.ts b/src/agents/tool-images.ts index 3e169561ef..8b8ec4e3d3 100644 --- a/src/agents/tool-images.ts +++ b/src/agents/tool-images.ts @@ -19,23 +19,35 @@ const MAX_IMAGE_BYTES = 5 * 1024 * 1024; const log = createSubsystemLogger("agents/tool-images"); function isImageBlock(block: unknown): block is ImageContentBlock { - if (!block || typeof block !== "object") return false; + if (!block || typeof block !== "object") { + return false; + } const rec = block as Record; return rec.type === "image" && typeof rec.data === "string" && typeof rec.mimeType === "string"; } function isTextBlock(block: unknown): block is TextContentBlock { - if (!block || typeof block !== "object") return false; + if (!block || typeof block !== "object") { + return false; + } const rec = block as Record; return rec.type === "text" && typeof rec.text === "string"; } function inferMimeTypeFromBase64(base64: string): string | undefined { const trimmed = base64.trim(); - if (!trimmed) return undefined; - if (trimmed.startsWith("/9j/")) return "image/jpeg"; - if (trimmed.startsWith("iVBOR")) return "image/png"; - if (trimmed.startsWith("R0lGOD")) return "image/gif"; + if (!trimmed) { + return undefined; + } + if (trimmed.startsWith("/9j/")) { + return "image/jpeg"; + } + if (trimmed.startsWith("iVBOR")) { + return "image/png"; + } + if (trimmed.startsWith("R0lGOD")) { + return "image/gif"; + } return undefined; } @@ -189,7 +201,9 @@ export async function sanitizeImageBlocks( label: string, opts: { maxDimensionPx?: number; maxBytes?: 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 next = sanitized.filter(isImageBlock); return { images: next, dropped: Math.max(0, images.length - next.length) }; @@ -201,7 +215,9 @@ export async function sanitizeToolResultImages( opts: { maxDimensionPx?: number; maxBytes?: number } = {}, ): Promise> { const content = Array.isArray(result.content) ? result.content : []; - if (!content.some((b) => isImageBlock(b) || isTextBlock(b))) return result; + if (!content.some((b) => isImageBlock(b) || isTextBlock(b))) { + return result; + } const next = await sanitizeContentBlocksImages(content, label, opts); return { ...result, content: next }; diff --git a/src/agents/tool-policy.ts b/src/agents/tool-policy.ts index 4df740b808..293e6d5e9d 100644 --- a/src/agents/tool-policy.ts +++ b/src/agents/tool-policy.ts @@ -81,7 +81,9 @@ export function normalizeToolName(name: string) { } export function normalizeToolList(list?: string[]) { - if (!list) return []; + if (!list) { + return []; + } return list.map(normalizeToolName).filter(Boolean); } @@ -118,11 +120,17 @@ export function expandToolGroups(list?: string[]) { export function collectExplicitAllowlist(policies: Array): string[] { const entries: string[] = []; for (const policy of policies) { - if (!policy?.allow) continue; + if (!policy?.allow) { + continue; + } for (const value of policy.allow) { - if (typeof value !== "string") continue; + if (typeof value !== "string") { + continue; + } const trimmed = value.trim(); - if (trimmed) entries.push(trimmed); + if (trimmed) { + entries.push(trimmed); + } } } return entries; @@ -136,7 +144,9 @@ export function buildPluginToolGroups(params: { const byPlugin = new Map(); for (const tool of params.tools) { const meta = params.toolMeta(tool); - if (!meta) continue; + if (!meta) { + continue; + } const name = normalizeToolName(tool.name); all.push(name); const pluginId = meta.pluginId.toLowerCase(); @@ -151,7 +161,9 @@ export function expandPluginGroups( list: string[] | undefined, groups: PluginToolGroups, ): string[] | undefined { - if (!list || list.length === 0) return list; + if (!list || list.length === 0) { + return list; + } const expanded: string[] = []; for (const entry of list) { const normalized = normalizeToolName(entry); @@ -177,7 +189,9 @@ export function expandPolicyWithPluginGroups( policy: ToolPolicyLike | undefined, groups: PluginToolGroups, ): ToolPolicyLike | undefined { - if (!policy) return undefined; + if (!policy) { + return undefined; + } return { allow: expandPluginGroups(policy.allow, groups), deny: expandPluginGroups(policy.deny, groups), @@ -205,8 +219,12 @@ export function stripPluginOnlyAllowlist( entry === "group:plugins" || pluginIds.has(entry) || pluginTools.has(entry); const expanded = expandToolGroups([entry]); const isCoreEntry = expanded.some((tool) => coreTools.has(tool)); - if (isCoreEntry) hasCoreEntry = true; - if (!isCoreEntry && !isPluginEntry) unknownAllowlist.push(entry); + if (isCoreEntry) { + hasCoreEntry = true; + } + if (!isCoreEntry && !isPluginEntry) { + unknownAllowlist.push(entry); + } } const strippedAllowlist = !hasCoreEntry; // When an allowlist contains only plugin tools, we strip it to avoid accidentally @@ -223,10 +241,16 @@ export function stripPluginOnlyAllowlist( } export function resolveToolProfilePolicy(profile?: string): ToolProfilePolicy | undefined { - if (!profile) return undefined; + if (!profile) { + return undefined; + } const resolved = TOOL_PROFILES[profile as ToolProfileId]; - if (!resolved) return undefined; - if (!resolved.allow && !resolved.deny) return undefined; + if (!resolved) { + return undefined; + } + if (!resolved.allow && !resolved.deny) { + return undefined; + } return { allow: resolved.allow ? [...resolved.allow] : undefined, deny: resolved.deny ? [...resolved.deny] : undefined, diff --git a/src/agents/tool-summaries.ts b/src/agents/tool-summaries.ts index 09671efbf6..bca325e4b8 100644 --- a/src/agents/tool-summaries.ts +++ b/src/agents/tool-summaries.ts @@ -4,7 +4,9 @@ export function buildToolSummaryMap(tools: AgentTool[]): Record const summaries: Record = {}; for (const tool of tools) { const summary = tool.description?.trim() || tool.label?.trim(); - if (!summary) continue; + if (!summary) { + continue; + } summaries[tool.name.toLowerCase()] = summary; } return summaries; diff --git a/src/agents/tools/agent-step.ts b/src/agents/tools/agent-step.ts index eb826f3065..b11ec259ae 100644 --- a/src/agents/tools/agent-step.ts +++ b/src/agents/tools/agent-step.ts @@ -52,6 +52,8 @@ export async function runAgentStep(params: { }, timeoutMs: stepWaitMs + 2000, }); - if (wait?.status !== "ok") return undefined; + if (wait?.status !== "ok") { + return undefined; + } return await readLatestAssistantReply({ sessionKey: params.sessionKey }); } diff --git a/src/agents/tools/agents-list-tool.ts b/src/agents/tools/agents-list-tool.ts index fb8d6ff624..ee4c5d964f 100644 --- a/src/agents/tools/agents-list-tool.ts +++ b/src/agents/tools/agents-list-tool.ts @@ -59,16 +59,22 @@ export function createAgentsListTool(opts?: { const configuredNameMap = new Map(); for (const entry of configuredAgents) { const name = entry?.name?.trim() ?? ""; - if (!name) continue; + if (!name) { + continue; + } configuredNameMap.set(normalizeAgentId(entry.id), name); } const allowed = new Set(); allowed.add(requesterAgentId); if (allowAny) { - for (const id of configuredIds) allowed.add(id); + for (const id of configuredIds) { + allowed.add(id); + } } else { - for (const id of allowSet) allowed.add(id); + for (const id of allowSet) { + allowed.add(id); + } } const all = Array.from(allowed); diff --git a/src/agents/tools/browser-tool.ts b/src/agents/tools/browser-tool.ts index 77a233166d..293aedcaf4 100644 --- a/src/agents/tools/browser-tool.ts +++ b/src/agents/tools/browser-tool.ts @@ -70,7 +70,9 @@ async function resolveBrowserNodeTarget(params: { if (params.sandboxBridgeUrl?.trim() && params.target !== "node" && !params.requestedNode) { return null; } - if (params.target && params.target !== "node") return null; + if (params.target && params.target !== "node") { + return null; + } if (mode === "manual" && params.target !== "node" && !params.requestedNode) { return null; } @@ -101,7 +103,9 @@ async function resolveBrowserNodeTarget(params: { ); } - if (mode === "manual") return null; + if (mode === "manual") { + return null; + } if (browserNodes.length === 1) { const node = browserNodes[0]; @@ -152,7 +156,9 @@ async function callBrowserProxy(params: { } async function persistProxyFiles(files: BrowserProxyFile[] | undefined) { - if (!files || files.length === 0) return new Map(); + if (!files || files.length === 0) { + return new Map(); + } const mapping = new Map(); for (const file of files) { const buffer = Buffer.from(file.base64, "base64"); @@ -163,7 +169,9 @@ async function persistProxyFiles(files: BrowserProxyFile[] | undefined) { } function applyProxyPaths(result: unknown, mapping: Map) { - if (!result || typeof result !== "object") return; + if (!result || typeof result !== "object") { + return; + } const obj = result as Record; if (typeof obj.path === "string" && mapping.has(obj.path)) { obj.path = mapping.get(obj.path); @@ -402,8 +410,11 @@ export function createBrowserTool(opts?: { }); return jsonResult(result); } - if (targetId) await browserCloseTab(baseUrl, targetId, { profile }); - else await browserAct(baseUrl, { kind: "close" }, { profile }); + if (targetId) { + await browserCloseTab(baseUrl, targetId, { profile }); + } else { + await browserAct(baseUrl, { kind: "close" }, { profile }); + } return jsonResult({ ok: true }); } case "snapshot": { @@ -592,7 +603,9 @@ export function createBrowserTool(opts?: { } case "upload": { const paths = Array.isArray(params.paths) ? params.paths.map((p) => String(p)) : []; - if (paths.length === 0) throw new Error("paths required"); + if (paths.length === 0) { + throw new Error("paths required"); + } const ref = readStringParam(params, "ref"); const inputRef = readStringParam(params, "inputRef"); const element = readStringParam(params, "element"); diff --git a/src/agents/tools/canvas-tool.ts b/src/agents/tools/canvas-tool.ts index 5914b4546c..d112fe1917 100644 --- a/src/agents/tools/canvas-tool.ts +++ b/src/agents/tools/canvas-tool.ts @@ -164,7 +164,9 @@ export function createCanvasTool(): AnyAgentTool { : typeof params.jsonlPath === "string" && params.jsonlPath.trim() ? await fs.readFile(params.jsonlPath.trim(), "utf8") : ""; - if (!jsonl.trim()) throw new Error("jsonl or jsonlPath required"); + if (!jsonl.trim()) { + throw new Error("jsonl or jsonlPath required"); + } await invoke("canvas.a2ui.pushJSONL", { jsonl }); return jsonResult({ ok: true }); } diff --git a/src/agents/tools/common.ts b/src/agents/tools/common.ts index 7e5446d064..732144163d 100644 --- a/src/agents/tools/common.ts +++ b/src/agents/tools/common.ts @@ -25,7 +25,9 @@ export function createActionGate>( ): ActionGate { return (key, defaultValue = true) => { const value = actions?.[key]; - if (value === undefined) return defaultValue; + if (value === undefined) { + return defaultValue; + } return value !== false; }; } @@ -48,12 +50,16 @@ export function readStringParam( const { required = false, trim = true, label = key, allowEmpty = false } = options; const raw = params[key]; if (typeof raw !== "string") { - if (required) throw new Error(`${label} required`); + if (required) { + throw new Error(`${label} required`); + } return undefined; } const value = trim ? raw.trim() : raw; if (!value && !allowEmpty) { - if (required) throw new Error(`${label} required`); + if (required) { + throw new Error(`${label} required`); + } return undefined; } return value; @@ -71,9 +77,13 @@ export function readStringOrNumberParam( } if (typeof raw === "string") { const value = raw.trim(); - if (value) return value; + if (value) { + return value; + } + } + if (required) { + throw new Error(`${label} required`); } - if (required) throw new Error(`${label} required`); return undefined; } @@ -91,11 +101,15 @@ export function readNumberParam( const trimmed = raw.trim(); if (trimmed) { const parsed = Number.parseFloat(trimmed); - if (Number.isFinite(parsed)) value = parsed; + if (Number.isFinite(parsed)) { + value = parsed; + } } } if (value === undefined) { - if (required) throw new Error(`${label} required`); + if (required) { + throw new Error(`${label} required`); + } return undefined; } return integer ? Math.trunc(value) : value; @@ -124,7 +138,9 @@ export function readStringArrayParam( .map((entry) => entry.trim()) .filter(Boolean); if (values.length === 0) { - if (required) throw new Error(`${label} required`); + if (required) { + throw new Error(`${label} required`); + } return undefined; } return values; @@ -132,12 +148,16 @@ export function readStringArrayParam( if (typeof raw === "string") { const value = raw.trim(); if (!value) { - if (required) throw new Error(`${label} required`); + if (required) { + throw new Error(`${label} required`); + } return undefined; } return [value]; } - if (required) throw new Error(`${label} required`); + if (required) { + throw new Error(`${label} required`); + } return undefined; } diff --git a/src/agents/tools/cron-tool.ts b/src/agents/tools/cron-tool.ts index ca3e69a5fc..a22f53d8c7 100644 --- a/src/agents/tools/cron-tool.ts +++ b/src/agents/tools/cron-tool.ts @@ -51,12 +51,16 @@ type ChatMessage = { function stripExistingContext(text: string) { const index = text.indexOf(REMINDER_CONTEXT_MARKER); - if (index === -1) return text; + if (index === -1) { + return text; + } return text.slice(0, index).trim(); } function truncateText(input: string, maxLen: number) { - if (input.length <= maxLen) return input; + if (input.length <= maxLen) { + return input; + } const truncated = truncateUtf16Safe(input, Math.max(0, maxLen - 3)).trimEnd(); return `${truncated}...`; } @@ -67,17 +71,25 @@ function normalizeContextText(raw: string) { function extractMessageText(message: ChatMessage): { role: string; text: string } | null { const role = typeof message.role === "string" ? message.role : ""; - if (role !== "user" && role !== "assistant") return null; + if (role !== "user" && role !== "assistant") { + return null; + } const content = message.content; if (typeof content === "string") { const normalized = normalizeContextText(content); return normalized ? { role, text: normalized } : null; } - if (!Array.isArray(content)) return null; + if (!Array.isArray(content)) { + return null; + } const chunks: string[] = []; for (const block of content) { - if (!block || typeof block !== "object") continue; - if ((block as { type?: unknown }).type !== "text") continue; + if (!block || typeof block !== "object") { + continue; + } + if ((block as { type?: unknown }).type !== "text") { + continue; + } const text = (block as { text?: unknown }).text; if (typeof text === "string" && text.trim()) { chunks.push(text); @@ -96,9 +108,13 @@ async function buildReminderContextLines(params: { REMINDER_CONTEXT_MESSAGES_MAX, Math.max(0, Math.floor(params.contextMessages)), ); - if (maxMessages <= 0) return []; + if (maxMessages <= 0) { + return []; + } const sessionKey = params.agentSessionKey?.trim(); - if (!sessionKey) return []; + if (!sessionKey) { + return []; + } const cfg = loadConfig(); const { mainKey, alias } = resolveMainSessionAlias(cfg); const resolvedKey = resolveInternalSessionKey({ key: sessionKey, alias, mainKey }); @@ -112,7 +128,9 @@ async function buildReminderContextLines(params: { .map((msg) => extractMessageText(msg as ChatMessage)) .filter((msg): msg is { role: string; text: string } => Boolean(msg)); const recent = parsed.slice(-maxMessages); - if (recent.length === 0) return []; + if (recent.length === 0) { + return []; + } const lines: string[] = []; let total = 0; for (const entry of recent) { @@ -120,7 +138,9 @@ async function buildReminderContextLines(params: { const text = truncateText(entry.text, REMINDER_CONTEXT_PER_MESSAGE_MAX); const line = `- ${label}: ${text}`; total += line.length; - if (total > REMINDER_CONTEXT_TOTAL_MAX) break; + if (total > REMINDER_CONTEXT_TOTAL_MAX) { + break; + } lines.push(line); } return lines; diff --git a/src/agents/tools/discord-actions-guild.ts b/src/agents/tools/discord-actions-guild.ts index 26e21c82e9..c6f2312ee5 100644 --- a/src/agents/tools/discord-actions-guild.ts +++ b/src/agents/tools/discord-actions-guild.ts @@ -30,8 +30,12 @@ import { } from "./common.js"; function readParentIdParam(params: Record): string | null | undefined { - if (params.clearParent === true) return null; - if (params.parentId === null) return null; + if (params.clearParent === true) { + return null; + } + if (params.parentId === null) { + return null; + } return readStringParam(params, "parentId"); } diff --git a/src/agents/tools/discord-actions-messaging.ts b/src/agents/tools/discord-actions-messaging.ts index f90fb60de9..9d2029ca9f 100644 --- a/src/agents/tools/discord-actions-messaging.ts +++ b/src/agents/tools/discord-actions-messaging.ts @@ -60,7 +60,9 @@ export async function handleDiscordMessagingAction( ); const accountId = readStringParam(params, "accountId"); const normalizeMessage = (message: unknown) => { - if (!message || typeof message !== "object") return message; + if (!message || typeof message !== "object") { + return message; + } return withNormalizedTimestamp( message as Record, (message as { timestamp?: unknown }).timestamp, diff --git a/src/agents/tools/gateway-tool.ts b/src/agents/tools/gateway-tool.ts index af0c3fc91f..4c2037bf0d 100644 --- a/src/agents/tools/gateway-tool.ts +++ b/src/agents/tools/gateway-tool.ts @@ -14,7 +14,9 @@ import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js"; import { callGatewayTool } from "./gateway.js"; function resolveBaseHashFromSnapshot(snapshot: unknown): string | undefined { - if (!snapshot || typeof snapshot !== "object") return undefined; + if (!snapshot || typeof snapshot !== "object") { + return undefined; + } const hashValue = (snapshot as { hash?: unknown }).hash; const rawValue = (snapshot as { raw?: unknown }).raw; const hash = resolveConfigSnapshotHash({ diff --git a/src/agents/tools/image-tool.helpers.ts b/src/agents/tools/image-tool.helpers.ts index d7e6767264..9fc366f90d 100644 --- a/src/agents/tools/image-tool.helpers.ts +++ b/src/agents/tools/image-tool.helpers.ts @@ -12,7 +12,9 @@ export function decodeDataUrl(dataUrl: string): { } { const trimmed = dataUrl.trim(); const match = /^data:([^;,]+);base64,([a-z0-9+/=\r\n]+)$/i.exec(trimmed); - if (!match) throw new Error("Invalid data URL (expected base64 data: URL)."); + if (!match) { + throw new Error("Invalid data URL (expected base64 data: URL)."); + } const mimeType = (match[1] ?? "").trim().toLowerCase(); if (!mimeType.startsWith("image/")) { throw new Error(`Unsupported data URL type: ${mimeType || "unknown"}`); @@ -43,7 +45,9 @@ export function coerceImageAssistantText(params: { throw new Error(`Image model failed (${params.provider}/${params.model}): ${errorMessage}`); } const text = extractAssistantText(params.message); - if (text.trim()) return text.trim(); + if (text.trim()) { + return text.trim(); + } throw new Error(`Image model returned no text (${params.provider}/${params.model}).`); } diff --git a/src/agents/tools/image-tool.test.ts b/src/agents/tools/image-tool.test.ts index dff4e377b1..39c21ab3cf 100644 --- a/src/agents/tools/image-tool.test.ts +++ b/src/agents/tools/image-tool.test.ts @@ -148,7 +148,9 @@ describe("image tool implicit imageModel config", () => { }; const tool = createImageTool({ config: cfg, agentDir, sandboxRoot }); expect(tool).not.toBeNull(); - if (!tool) throw new Error("expected image tool"); + if (!tool) { + throw new Error("expected image tool"); + } await expect(tool.execute("t1", { image: "https://example.com/a.png" })).rejects.toThrow( /Sandboxed image tool does not allow remote URLs/i, @@ -198,7 +200,9 @@ describe("image tool implicit imageModel config", () => { }; const tool = createImageTool({ config: cfg, agentDir, sandboxRoot }); expect(tool).not.toBeNull(); - if (!tool) throw new Error("expected image tool"); + if (!tool) { + throw new Error("expected image tool"); + } const res = await tool.execute("t1", { prompt: "Describe the image.", @@ -266,7 +270,9 @@ describe("image tool MiniMax VLM routing", () => { }; const tool = createImageTool({ config: cfg, agentDir }); expect(tool).not.toBeNull(); - if (!tool) throw new Error("expected image tool"); + if (!tool) { + throw new Error("expected image tool"); + } const res = await tool.execute("t1", { prompt: "Describe the image.", @@ -308,7 +314,9 @@ describe("image tool MiniMax VLM routing", () => { }; const tool = createImageTool({ config: cfg, agentDir }); expect(tool).not.toBeNull(); - if (!tool) throw new Error("expected image tool"); + if (!tool) { + throw new Error("expected image tool"); + } await expect( tool.execute("t1", { diff --git a/src/agents/tools/image-tool.ts b/src/agents/tools/image-tool.ts index 1906b0f469..d6968fc735 100644 --- a/src/agents/tools/image-tool.ts +++ b/src/agents/tools/image-tool.ts @@ -48,7 +48,9 @@ function resolveDefaultModelRef(cfg?: OpenClawConfig): { } function hasAuthForProvider(params: { provider: string; agentDir: string }): boolean { - if (resolveEnvApiKey(params.provider)?.apiKey) return true; + if (resolveEnvApiKey(params.provider)?.apiKey) { + return true; + } const store = ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false, }); @@ -89,8 +91,12 @@ export function resolveImageModelConfigForTool(params: { const fallbacks: string[] = []; const addFallback = (modelRef: string | null) => { const ref = (modelRef ?? "").trim(); - if (!ref) return; - if (fallbacks.includes(ref)) return; + if (!ref) { + return; + } + if (fallbacks.includes(ref)) { + return; + } fallbacks.push(ref); }; @@ -117,8 +123,12 @@ export function resolveImageModelConfigForTool(params: { } if (preferred?.trim()) { - if (openaiOk) addFallback("openai/gpt-5-mini"); - if (anthropicOk) addFallback("anthropic/claude-opus-4-5"); + if (openaiOk) { + addFallback("openai/gpt-5-mini"); + } + if (anthropicOk) { + addFallback("anthropic/claude-opus-4-5"); + } // Don't duplicate primary in fallbacks. const pruned = fallbacks.filter((ref) => ref !== preferred); return { @@ -129,7 +139,9 @@ export function resolveImageModelConfigForTool(params: { // Cross-provider fallback when we can't pair with the primary provider. if (openaiOk) { - if (anthropicOk) addFallback("anthropic/claude-opus-4-5"); + if (anthropicOk) { + addFallback("anthropic/claude-opus-4-5"); + } return { primary: "openai/gpt-5-mini", ...(fallbacks.length ? { fallbacks } : {}), @@ -305,7 +317,9 @@ export function createImageTool(options?: { cfg: options?.config, agentDir, }); - if (!imageModelConfig) return null; + if (!imageModelConfig) { + return null; + } // If model has native vision, images in the prompt are auto-injected // so this tool is only needed when image wasn't provided in the prompt @@ -329,7 +343,9 @@ export function createImageTool(options?: { const imageRaw = imageRawInput.startsWith("@") ? imageRawInput.slice(1).trim() : imageRawInput; - if (!imageRaw) throw new Error("image required"); + if (!imageRaw) { + throw new Error("image required"); + } // The tool accepts file paths, file/data URLs, or http(s) URLs. In some // agent/model contexts, images can be referenced as pseudo-URIs like @@ -371,8 +387,12 @@ export function createImageTool(options?: { } const resolvedImage = (() => { - if (sandboxRoot) return imageRaw; - if (imageRaw.startsWith("~")) return resolveUserPath(imageRaw); + if (sandboxRoot) { + return imageRaw; + } + if (imageRaw.startsWith("~")) { + return resolveUserPath(imageRaw); + } return imageRaw; })(); const resolvedPathInfo: { resolved: string; rewrittenFrom?: string } = isDataUrl diff --git a/src/agents/tools/memory-tool.does-not-crash-on-errors.test.ts b/src/agents/tools/memory-tool.does-not-crash-on-errors.test.ts index da362f6abd..85535cedfe 100644 --- a/src/agents/tools/memory-tool.does-not-crash-on-errors.test.ts +++ b/src/agents/tools/memory-tool.does-not-crash-on-errors.test.ts @@ -34,7 +34,9 @@ describe("memory tools", () => { const cfg = { agents: { list: [{ id: "main", default: true }] } }; const tool = createMemorySearchTool({ config: cfg }); expect(tool).not.toBeNull(); - if (!tool) throw new Error("tool missing"); + if (!tool) { + throw new Error("tool missing"); + } const result = await tool.execute("call_1", { query: "hello" }); expect(result.details).toEqual({ @@ -48,7 +50,9 @@ describe("memory tools", () => { const cfg = { agents: { list: [{ id: "main", default: true }] } }; const tool = createMemoryGetTool({ config: cfg }); expect(tool).not.toBeNull(); - if (!tool) throw new Error("tool missing"); + if (!tool) { + throw new Error("tool missing"); + } const result = await tool.execute("call_2", { path: "memory/NOPE.md" }); expect(result.details).toEqual({ diff --git a/src/agents/tools/memory-tool.ts b/src/agents/tools/memory-tool.ts index a28f7037ae..172ce75e17 100644 --- a/src/agents/tools/memory-tool.ts +++ b/src/agents/tools/memory-tool.ts @@ -24,12 +24,16 @@ export function createMemorySearchTool(options: { agentSessionKey?: string; }): AnyAgentTool | null { const cfg = options.config; - if (!cfg) return null; + if (!cfg) { + return null; + } const agentId = resolveSessionAgentId({ sessionKey: options.agentSessionKey, config: cfg, }); - if (!resolveMemorySearchConfig(cfg, agentId)) return null; + if (!resolveMemorySearchConfig(cfg, agentId)) { + return null; + } return { label: "Memory Search", name: "memory_search", @@ -73,12 +77,16 @@ export function createMemoryGetTool(options: { agentSessionKey?: string; }): AnyAgentTool | null { const cfg = options.config; - if (!cfg) return null; + if (!cfg) { + return null; + } const agentId = resolveSessionAgentId({ sessionKey: options.agentSessionKey, config: cfg, }); - if (!resolveMemorySearchConfig(cfg, agentId)) return null; + if (!resolveMemorySearchConfig(cfg, agentId)) { + return null; + } return { label: "Memory Get", name: "memory_get", diff --git a/src/agents/tools/message-tool.ts b/src/agents/tools/message-tool.ts index 93e7420cf2..3896590524 100644 --- a/src/agents/tools/message-tool.ts +++ b/src/agents/tools/message-tool.ts @@ -88,8 +88,12 @@ function buildSendSchema(options: { includeButtons: boolean; includeCards: boole ), ), }; - if (!options.includeButtons) delete props.buttons; - if (!options.includeCards) delete props.card; + if (!options.includeButtons) { + delete props.buttons; + } + if (!options.includeCards) { + delete props.card; + } return props; } @@ -262,7 +266,9 @@ function buildMessageToolSchema(cfg: OpenClawConfig) { function resolveAgentAccountId(value?: string): string | undefined { const trimmed = value?.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } return normalizeAccountId(trimmed); } @@ -272,9 +278,13 @@ function filterActionsForContext(params: { currentChannelId?: string; }): ChannelMessageActionName[] { const channel = normalizeMessageChannel(params.channel); - if (!channel || channel !== "bluebubbles") return params.actions; + if (!channel || channel !== "bluebubbles") { + return params.actions; + } const currentChannelId = params.currentChannelId?.trim(); - if (!currentChannelId) return params.actions; + if (!currentChannelId) { + return params.actions; + } const normalizedTarget = normalizeTargetForProvider(channel, currentChannelId) ?? currentChannelId; const lowered = normalizedTarget.trim().toLowerCase(); @@ -283,7 +293,9 @@ function filterActionsForContext(params: { lowered.startsWith("chat_id:") || lowered.startsWith("chat_identifier:") || lowered.startsWith("group:"); - if (isGroupTarget) return params.actions; + if (isGroupTarget) { + return params.actions; + } return params.actions.filter((action) => !BLUEBUBBLES_GROUP_ACTIONS.has(action)); } @@ -396,7 +408,9 @@ export function createMessageTool(options?: MessageToolOptions): AnyAgentTool { }); const toolResult = getToolResult(result); - if (toolResult) return toolResult; + if (toolResult) { + return toolResult; + } return jsonResult(result.payload); }, }; diff --git a/src/agents/tools/nodes-utils.ts b/src/agents/tools/nodes-utils.ts index 0d7b8fb5e4..da1d9116ab 100644 --- a/src/agents/tools/nodes-utils.ts +++ b/src/agents/tools/nodes-utils.ts @@ -89,11 +89,15 @@ function pickDefaultNode(nodes: NodeListNode[]): NodeListNode | null { const withCanvas = nodes.filter((n) => Array.isArray(n.caps) ? n.caps.includes("canvas") : true, ); - if (withCanvas.length === 0) return null; + if (withCanvas.length === 0) { + return null; + } const connected = withCanvas.filter((n) => n.connected); const candidates = connected.length > 0 ? connected : withCanvas; - if (candidates.length === 1) return candidates[0]; + if (candidates.length === 1) { + return candidates[0]; + } const local = candidates.filter( (n) => @@ -101,7 +105,9 @@ function pickDefaultNode(nodes: NodeListNode[]): NodeListNode | null { typeof n.nodeId === "string" && n.nodeId.startsWith("mac-"), ); - if (local.length === 1) return local[0]; + if (local.length === 1) { + return local[0]; + } return null; } @@ -119,22 +125,34 @@ export function resolveNodeIdFromList( if (!q) { if (allowDefault) { const picked = pickDefaultNode(nodes); - if (picked) return picked.nodeId; + if (picked) { + return picked.nodeId; + } } throw new Error("node required"); } const qNorm = normalizeNodeKey(q); const matches = nodes.filter((n) => { - if (n.nodeId === q) return true; - if (typeof n.remoteIp === "string" && n.remoteIp === q) return true; + if (n.nodeId === q) { + return true; + } + if (typeof n.remoteIp === "string" && n.remoteIp === q) { + return true; + } const name = typeof n.displayName === "string" ? n.displayName : ""; - if (name && normalizeNodeKey(name) === qNorm) return true; - if (q.length >= 6 && n.nodeId.startsWith(q)) return true; + if (name && normalizeNodeKey(name) === qNorm) { + return true; + } + if (q.length >= 6 && n.nodeId.startsWith(q)) { + return true; + } return false; }); - if (matches.length === 1) return matches[0].nodeId; + if (matches.length === 1) { + return matches[0].nodeId; + } if (matches.length === 0) { const known = nodes .map((n) => n.displayName || n.remoteIp || n.nodeId) diff --git a/src/agents/tools/session-status-tool.ts b/src/agents/tools/session-status-tool.ts index 3cdb94d1b4..d1cf333ce2 100644 --- a/src/agents/tools/session-status-tool.ts +++ b/src/agents/tools/session-status-tool.ts @@ -55,7 +55,9 @@ const SessionStatusToolSchema = Type.Object({ function formatApiKeySnippet(apiKey: string): string { const compact = apiKey.replace(/\s+/g, ""); - if (!compact) return "unknown"; + if (!compact) { + return "unknown"; + } const edge = compact.length >= 12 ? 6 : 4; const head = compact.slice(0, edge); const tail = compact.slice(-edge); @@ -69,7 +71,9 @@ function resolveModelAuthLabel(params: { agentDir?: string; }): string | undefined { const resolvedProvider = params.provider?.trim(); - if (!resolvedProvider) return undefined; + if (!resolvedProvider) { + return undefined; + } const providerKey = normalizeProviderId(resolvedProvider); const store = ensureAuthProfileStore(params.agentDir, { @@ -126,7 +130,9 @@ function resolveSessionEntry(params: { mainKey: string; }): { key: string; entry: SessionEntry } | null { const keyRaw = params.keyRaw.trim(); - if (!keyRaw) return null; + if (!keyRaw) { + return null; + } const internal = resolveInternalSessionKey({ key: keyRaw, alias: params.alias, @@ -149,7 +155,9 @@ function resolveSessionEntry(params: { for (const key of candidates) { const entry = params.store[key]; - if (entry) return { key, entry }; + if (entry) { + return { key, entry }; + } } return null; @@ -161,11 +169,17 @@ function resolveSessionKeyFromSessionId(params: { agentId?: string; }): string | null { const trimmed = params.sessionId.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const { store } = loadCombinedSessionStoreForGateway(params.cfg); const match = Object.entries(store).find(([key, entry]) => { - if (entry?.sessionId !== trimmed) return false; - if (!params.agentId) return true; + if (entry?.sessionId !== trimmed) { + return false; + } + if (!params.agentId) { + return true; + } return resolveAgentIdFromSessionKey(key) === params.agentId; }); return match?.[0] ?? null; @@ -186,8 +200,12 @@ async function resolveModelOverride(params: { } > { const raw = params.raw.trim(); - if (!raw) return { kind: "reset" }; - if (raw.toLowerCase() === "default") return { kind: "reset" }; + if (!raw) { + return { kind: "reset" }; + } + if (raw.toLowerCase() === "default") { + return { kind: "reset" }; + } const configDefault = resolveDefaultModelForAgent({ cfg: params.cfg, @@ -256,7 +274,9 @@ export function createSessionStatusTool(opts?: { opts?.agentSessionKey ?? requestedKeyRaw, ); const ensureAgentAccess = (targetAgentId: string) => { - if (targetAgentId === requesterAgentId) return; + if (targetAgentId === requesterAgentId) { + return; + } // Gate cross-agent access behind tools.agentToAgent settings. if (!a2aPolicy.enabled) { throw new Error( diff --git a/src/agents/tools/sessions-announce-target.ts b/src/agents/tools/sessions-announce-target.ts index 7b9fd1daf3..d0e5061270 100644 --- a/src/agents/tools/sessions-announce-target.ts +++ b/src/agents/tools/sessions-announce-target.ts @@ -46,7 +46,9 @@ export async function resolveAnnounceTarget(params: { const accountId = (typeof deliveryContext?.accountId === "string" ? deliveryContext.accountId : undefined) ?? (typeof match?.lastAccountId === "string" ? match.lastAccountId : undefined); - if (channel && to) return { channel, to, accountId }; + if (channel && to) { + return { channel, to, accountId }; + } } catch { // ignore } diff --git a/src/agents/tools/sessions-helpers.ts b/src/agents/tools/sessions-helpers.ts index c8d3459c79..920cfb1fcd 100644 --- a/src/agents/tools/sessions-helpers.ts +++ b/src/agents/tools/sessions-helpers.ts @@ -53,13 +53,19 @@ export function resolveMainSessionAlias(cfg: OpenClawConfig) { } export function resolveDisplaySessionKey(params: { key: string; alias: string; mainKey: string }) { - if (params.key === params.alias) return "main"; - if (params.key === params.mainKey) return "main"; + if (params.key === params.alias) { + return "main"; + } + if (params.key === params.mainKey) { + return "main"; + } return params.key; } export function resolveInternalSessionKey(params: { key: string; alias: string; mainKey: string }) { - if (params.key === "main") return params.alias; + if (params.key === "main") { + return params.alias; + } return params.key; } @@ -74,20 +80,32 @@ export function createAgentToAgentPolicy(cfg: OpenClawConfig): AgentToAgentPolic const enabled = routingA2A?.enabled === true; const allowPatterns = Array.isArray(routingA2A?.allow) ? routingA2A.allow : []; const matchesAllow = (agentId: string) => { - if (allowPatterns.length === 0) return true; + if (allowPatterns.length === 0) { + return true; + } return allowPatterns.some((pattern) => { const raw = String(pattern ?? "").trim(); - if (!raw) return false; - if (raw === "*") return true; - if (!raw.includes("*")) return raw === agentId; + if (!raw) { + return false; + } + if (raw === "*") { + return true; + } + if (!raw.includes("*")) { + return raw === agentId; + } const escaped = raw.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const re = new RegExp(`^${escaped.replaceAll("\\*", ".*")}$`, "i"); return re.test(agentId); }); }; const isAllowed = (requesterAgentId: string, targetAgentId: string) => { - if (requesterAgentId === targetAgentId) return true; - if (!enabled) return false; + if (requesterAgentId === targetAgentId) { + return true; + } + if (!enabled) { + return false; + } return matchesAllow(requesterAgentId) && matchesAllow(targetAgentId); }; return { enabled, matchesAllow, isAllowed }; @@ -101,14 +119,28 @@ export function looksLikeSessionId(value: string): boolean { export function looksLikeSessionKey(value: string): boolean { const raw = value.trim(); - if (!raw) return false; + if (!raw) { + return false; + } // These are canonical key shapes that should never be treated as sessionIds. - if (raw === "main" || raw === "global" || raw === "unknown") return true; - if (isAcpSessionKey(raw)) return true; - if (raw.startsWith("agent:")) return true; - if (raw.startsWith("cron:") || raw.startsWith("hook:")) return true; - if (raw.startsWith("node-") || raw.startsWith("node:")) return true; - if (raw.includes(":group:") || raw.includes(":channel:")) return true; + if (raw === "main" || raw === "global" || raw === "unknown") { + return true; + } + if (isAcpSessionKey(raw)) { + return true; + } + if (raw.startsWith("agent:")) { + return true; + } + if (raw.startsWith("cron:") || raw.startsWith("hook:")) { + return true; + } + if (raw.startsWith("node-") || raw.startsWith("node:")) { + return true; + } + if (raw.includes(":group:") || raw.includes(":channel:")) { + return true; + } return false; } @@ -196,7 +228,9 @@ async function resolveSessionKeyFromKey(params: { }, }); const key = typeof result?.key === "string" ? result.key.trim() : ""; - if (!key) return null; + if (!key) { + return null; + } return { ok: true, key, @@ -229,7 +263,9 @@ export async function resolveSessionReference(params: { requesterInternalKey: params.requesterInternalKey, restrictToSpawned: params.restrictToSpawned, }); - if (resolvedByKey) return resolvedByKey; + if (resolvedByKey) { + return resolvedByKey; + } return await resolveSessionKeyFromSessionId({ sessionId: raw, alias: params.alias, @@ -259,11 +295,21 @@ export function classifySessionKind(params: { mainKey: string; }): SessionKind { const key = params.key; - if (key === params.alias || key === params.mainKey) return "main"; - if (key.startsWith("cron:")) return "cron"; - if (key.startsWith("hook:")) return "hook"; - if (key.startsWith("node-") || key.startsWith("node:")) return "node"; - if (params.gatewayKind === "group") return "group"; + if (key === params.alias || key === params.mainKey) { + return "main"; + } + if (key.startsWith("cron:")) { + return "cron"; + } + if (key.startsWith("hook:")) { + return "hook"; + } + if (key.startsWith("node-") || key.startsWith("node:")) { + return "node"; + } + if (params.gatewayKind === "group") { + return "group"; + } if (key.includes(":group:") || key.includes(":channel:")) { return "group"; } @@ -276,11 +322,17 @@ export function deriveChannel(params: { channel?: string | null; lastChannel?: string | null; }): string { - if (params.kind === "cron" || params.kind === "hook" || params.kind === "node") return "internal"; + if (params.kind === "cron" || params.kind === "hook" || params.kind === "node") { + return "internal"; + } const channel = normalizeKey(params.channel ?? undefined); - if (channel) return channel; + if (channel) { + return channel; + } const lastChannel = normalizeKey(params.lastChannel ?? undefined); - if (lastChannel) return lastChannel; + if (lastChannel) { + return lastChannel; + } const parts = params.key.split(":").filter(Boolean); if (parts.length >= 3 && (parts[1] === "group" || parts[1] === "channel")) { return parts[0]; @@ -290,7 +342,9 @@ export function deriveChannel(params: { export function stripToolMessages(messages: unknown[]): unknown[] { return messages.filter((msg) => { - if (!msg || typeof msg !== "object") return true; + if (!msg || typeof msg !== "object") { + return true; + } const role = (msg as { role?: unknown }).role; return role !== "toolResult"; }); @@ -301,19 +355,31 @@ export function stripToolMessages(messages: unknown[]): unknown[] { * This ensures user-facing text doesn't leak internal tool representations. */ export function sanitizeTextContent(text: string): string { - if (!text) return text; + if (!text) { + return text; + } return stripThinkingTagsFromText(stripDowngradedToolCallText(stripMinimaxToolCallXml(text))); } export function extractAssistantText(message: unknown): string | undefined { - if (!message || typeof message !== "object") return undefined; - if ((message as { role?: unknown }).role !== "assistant") return undefined; + if (!message || typeof message !== "object") { + return undefined; + } + if ((message as { role?: unknown }).role !== "assistant") { + return undefined; + } const content = (message as { content?: unknown }).content; - if (!Array.isArray(content)) return undefined; + if (!Array.isArray(content)) { + return undefined; + } const chunks: string[] = []; for (const block of content) { - if (!block || typeof block !== "object") continue; - if ((block as { type?: unknown }).type !== "text") continue; + if (!block || typeof block !== "object") { + continue; + } + if ((block as { type?: unknown }).type !== "text") { + continue; + } const text = (block as { text?: unknown }).text; if (typeof text === "string") { const sanitized = sanitizeTextContent(text); diff --git a/src/agents/tools/sessions-list-tool.ts b/src/agents/tools/sessions-list-tool.ts index 035be9abc6..85c3d95b65 100644 --- a/src/agents/tools/sessions-list-tool.ts +++ b/src/agents/tools/sessions-list-tool.ts @@ -97,20 +97,32 @@ export function createSessionsListTool(opts?: { const rows: SessionListRow[] = []; for (const entry of sessions) { - if (!entry || typeof entry !== "object") continue; + if (!entry || typeof entry !== "object") { + continue; + } const key = typeof entry.key === "string" ? entry.key : ""; - if (!key) continue; + if (!key) { + continue; + } const entryAgentId = resolveAgentIdFromSessionKey(key); const crossAgent = entryAgentId !== requesterAgentId; - if (crossAgent && !a2aPolicy.isAllowed(requesterAgentId, entryAgentId)) continue; + if (crossAgent && !a2aPolicy.isAllowed(requesterAgentId, entryAgentId)) { + continue; + } - if (key === "unknown") continue; - if (key === "global" && alias !== "global") continue; + if (key === "unknown") { + continue; + } + if (key === "global" && alias !== "global") { + continue; + } const gatewayKind = typeof entry.kind === "string" ? entry.kind : undefined; const kind = classifySessionKind({ key, gatewayKind, alias, mainKey }); - if (allowedKinds && !allowedKinds.has(kind)) continue; + if (allowedKinds && !allowedKinds.has(kind)) { + continue; + } const displayKey = resolveDisplaySessionKey({ key, diff --git a/src/agents/tools/sessions-send-helpers.ts b/src/agents/tools/sessions-send-helpers.ts index 4e517e918c..94dc3fe0c6 100644 --- a/src/agents/tools/sessions-send-helpers.ts +++ b/src/agents/tools/sessions-send-helpers.ts @@ -20,9 +20,13 @@ export type AnnounceTarget = { export function resolveAnnounceTargetFromKey(sessionKey: string): AnnounceTarget | null { const rawParts = sessionKey.split(":").filter(Boolean); const parts = rawParts.length >= 3 && rawParts[0] === "agent" ? rawParts.slice(2) : rawParts; - if (parts.length < 3) return null; + if (parts.length < 3) { + return null; + } const [channelRaw, kind, ...rest] = parts; - if (kind !== "group" && kind !== "channel") return null; + if (kind !== "group" && kind !== "channel") { + return null; + } // Extract topic/thread ID from rest (supports both :topic: and :thread:) // Telegram uses :topic:, other platforms use :thread: @@ -39,12 +43,18 @@ export function resolveAnnounceTargetFromKey(sessionKey: string): AnnounceTarget // Remove :topic:N or :thread:N suffix from ID for target const id = match ? restJoined.replace(/:(topic|thread):\d+$/, "") : restJoined.trim(); - if (!id) return null; - if (!channelRaw) return null; + if (!id) { + return null; + } + if (!channelRaw) { + return null; + } const normalizedChannel = normalizeAnyChannelId(channelRaw) ?? normalizeChatChannelId(channelRaw); const channel = normalizedChannel ?? channelRaw.toLowerCase(); const kindTarget = (() => { - if (!normalizedChannel) return id; + if (!normalizedChannel) { + return id; + } if (normalizedChannel === "discord" || normalizedChannel === "slack") { return `channel:${id}`; } @@ -148,7 +158,9 @@ export function isReplySkip(text?: string) { export function resolvePingPongTurns(cfg?: OpenClawConfig) { const raw = cfg?.session?.agentToAgent?.maxPingPongTurns; const fallback = DEFAULT_PING_PONG_TURNS; - if (typeof raw !== "number" || !Number.isFinite(raw)) return fallback; + if (typeof raw !== "number" || !Number.isFinite(raw)) { + return fallback; + } const rounded = Math.floor(raw); return Math.max(0, Math.min(MAX_PING_PONG_TURNS, rounded)); } diff --git a/src/agents/tools/sessions-send-tool.a2a.ts b/src/agents/tools/sessions-send-tool.a2a.ts index 6a0de3094a..468647cee7 100644 --- a/src/agents/tools/sessions-send-tool.a2a.ts +++ b/src/agents/tools/sessions-send-tool.a2a.ts @@ -48,7 +48,9 @@ export async function runSessionsSendA2AFlow(params: { latestReply = primaryReply; } } - if (!latestReply) return; + if (!latestReply) { + return; + } const announceTarget = await resolveAnnounceTarget({ sessionKey: params.targetSessionKey, diff --git a/src/agents/tools/sessions-spawn-tool.ts b/src/agents/tools/sessions-spawn-tool.ts index e00730f2a2..dfc1ee1b19 100644 --- a/src/agents/tools/sessions-spawn-tool.ts +++ b/src/agents/tools/sessions-spawn-tool.ts @@ -38,11 +38,17 @@ const SessionsSpawnToolSchema = Type.Object({ }); function splitModelRef(ref?: string) { - if (!ref) return { provider: undefined, model: undefined }; + if (!ref) { + return { provider: undefined, model: undefined }; + } const trimmed = ref.trim(); - if (!trimmed) return { provider: undefined, model: undefined }; + if (!trimmed) { + return { provider: undefined, model: undefined }; + } const [provider, model] = trimmed.split("/", 2); - if (model) return { provider, model }; + if (model) { + return { provider, model }; + } return { provider: undefined, model: trimmed }; } @@ -51,9 +57,13 @@ function normalizeModelSelection(value: unknown): string | undefined { const trimmed = value.trim(); return trimmed || undefined; } - if (!value || typeof value !== "object") return undefined; + if (!value || typeof value !== "object") { + return undefined; + } const primary = (value as { primary?: unknown }).primary; - if (typeof primary === "string" && primary.trim()) return primary.trim(); + if (typeof primary === "string" && primary.trim()) { + return primary.trim(); + } return undefined; } @@ -96,7 +106,9 @@ export function createSessionsSpawnTool(opts?: { typeof params.runTimeoutSeconds === "number" && Number.isFinite(params.runTimeoutSeconds) ? Math.max(0, Math.floor(params.runTimeoutSeconds)) : undefined; - if (explicit !== undefined) return explicit; + if (explicit !== undefined) { + return explicit; + } const legacy = typeof params.timeoutSeconds === "number" && Number.isFinite(params.timeoutSeconds) ? Math.max(0, Math.floor(params.timeoutSeconds)) diff --git a/src/agents/tools/slack-actions.ts b/src/agents/tools/slack-actions.ts index 3bb8ea717d..88834c3eeb 100644 --- a/src/agents/tools/slack-actions.ts +++ b/src/agents/tools/slack-actions.ts @@ -49,16 +49,24 @@ function resolveThreadTsFromContext( context: SlackActionContext | undefined, ): string | undefined { // Agent explicitly provided threadTs - use it - if (explicitThreadTs) return explicitThreadTs; + if (explicitThreadTs) { + return explicitThreadTs; + } // No context or missing required fields - if (!context?.currentThreadTs || !context?.currentChannelId) return undefined; + if (!context?.currentThreadTs || !context?.currentChannelId) { + return undefined; + } const parsedTarget = parseSlackTarget(targetChannel, { defaultKind: "channel" }); - if (!parsedTarget || parsedTarget.kind !== "channel") return undefined; + if (!parsedTarget || parsedTarget.kind !== "channel") { + return undefined; + } const normalizedTarget = parsedTarget.id; // Different channel - don't inject - if (normalizedTarget !== context.currentChannelId) return undefined; + if (normalizedTarget !== context.currentChannelId) { + return undefined; + } // Check replyToMode if (context.replyToMode === "all") { @@ -93,15 +101,21 @@ export async function handleSlackAction( // Choose the most appropriate token for Slack read/write operations. const getTokenForOperation = (operation: "read" | "write") => { - if (operation === "read") return userToken ?? botToken; - if (!allowUserWrites) return botToken; + if (operation === "read") { + return userToken ?? botToken; + } + if (!allowUserWrites) { + return botToken; + } return botToken ?? userToken; }; const buildActionOpts = (operation: "read" | "write") => { const token = getTokenForOperation(operation); const tokenOverride = token && token !== botToken ? token : undefined; - if (!accountId && !tokenOverride) return undefined; + if (!accountId && !tokenOverride) { + return undefined; + } return { ...(accountId ? { accountId } : {}), ...(tokenOverride ? { token: tokenOverride } : {}), diff --git a/src/agents/tools/telegram-actions.ts b/src/agents/tools/telegram-actions.ts index bf8e74db49..8853cfa8d0 100644 --- a/src/agents/tools/telegram-actions.ts +++ b/src/agents/tools/telegram-actions.ts @@ -32,7 +32,9 @@ export function readTelegramButtons( params: Record, ): TelegramButton[][] | undefined { const raw = params.buttons; - if (raw == null) return undefined; + if (raw == null) { + return undefined; + } if (!Array.isArray(raw)) { throw new Error("buttons must be an array of button rows"); } diff --git a/src/agents/tools/tts-tool.ts b/src/agents/tools/tts-tool.ts index 16a55767d3..fed7bd90cb 100644 --- a/src/agents/tools/tts-tool.ts +++ b/src/agents/tools/tts-tool.ts @@ -38,7 +38,9 @@ export function createTtsTool(opts?: { if (result.success && result.audioPath) { const lines: string[] = []; // Tag Telegram Opus output as a voice bubble instead of a file attachment. - if (result.voiceCompatible) lines.push("[[audio_as_voice]]"); + if (result.voiceCompatible) { + lines.push("[[audio_as_voice]]"); + } lines.push(`MEDIA:${result.audioPath}`); return { content: [{ type: "text", text: lines.join("\n") }], diff --git a/src/agents/tools/web-fetch-utils.ts b/src/agents/tools/web-fetch-utils.ts index cf40d8b6d9..5e0a248df9 100644 --- a/src/agents/tools/web-fetch-utils.ts +++ b/src/agents/tools/web-fetch-utils.ts @@ -34,7 +34,9 @@ export function htmlToMarkdown(html: string): { text: string; title?: string } { .replace(//gi, ""); text = text.replace(/]*href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi, (_, href, body) => { const label = normalizeWhitespace(stripTags(body)); - if (!label) return href; + if (!label) { + return href; + } return `[${label}](${href})`; }); text = text.replace(/]*>([\s\S]*?)<\/h\1>/gi, (_, level, body) => { @@ -72,7 +74,9 @@ export function truncateText( value: string, maxChars: number, ): { text: string; truncated: boolean } { - if (value.length <= maxChars) return { text: value, truncated: false }; + if (value.length <= maxChars) { + return { text: value, truncated: false }; + } return { text: value.slice(0, maxChars), truncated: true }; } @@ -102,7 +106,9 @@ export async function extractReadableContent(params: { } const reader = new Readability(document, { charThreshold: 0 }); const parsed = reader.parse(); - if (!parsed?.content) return fallback(); + if (!parsed?.content) { + return fallback(); + } const title = parsed.title || undefined; if (params.extractMode === "text") { const text = normalizeWhitespace(parsed.textContent ?? ""); diff --git a/src/agents/tools/web-fetch.ts b/src/agents/tools/web-fetch.ts index b127cd49f9..b40f33c2bc 100644 --- a/src/agents/tools/web-fetch.ts +++ b/src/agents/tools/web-fetch.ts @@ -80,24 +80,34 @@ type FirecrawlFetchConfig = function resolveFetchConfig(cfg?: OpenClawConfig): WebFetchConfig { const fetch = cfg?.tools?.web?.fetch; - if (!fetch || typeof fetch !== "object") return undefined; + if (!fetch || typeof fetch !== "object") { + return undefined; + } return fetch as WebFetchConfig; } function resolveFetchEnabled(params: { fetch?: WebFetchConfig; sandboxed?: boolean }): boolean { - if (typeof params.fetch?.enabled === "boolean") return params.fetch.enabled; + if (typeof params.fetch?.enabled === "boolean") { + return params.fetch.enabled; + } return true; } function resolveFetchReadabilityEnabled(fetch?: WebFetchConfig): boolean { - if (typeof fetch?.readability === "boolean") return fetch.readability; + if (typeof fetch?.readability === "boolean") { + return fetch.readability; + } return true; } function resolveFirecrawlConfig(fetch?: WebFetchConfig): FirecrawlFetchConfig { - if (!fetch || typeof fetch !== "object") return undefined; + if (!fetch || typeof fetch !== "object") { + return undefined; + } const firecrawl = "firecrawl" in fetch ? fetch.firecrawl : undefined; - if (!firecrawl || typeof firecrawl !== "object") return undefined; + if (!firecrawl || typeof firecrawl !== "object") { + return undefined; + } return firecrawl as FirecrawlFetchConfig; } @@ -114,7 +124,9 @@ function resolveFirecrawlEnabled(params: { firecrawl?: FirecrawlFetchConfig; apiKey?: string; }): boolean { - if (typeof params.firecrawl?.enabled === "boolean") return params.firecrawl.enabled; + if (typeof params.firecrawl?.enabled === "boolean") { + return params.firecrawl.enabled; + } return Boolean(params.apiKey); } @@ -127,7 +139,9 @@ function resolveFirecrawlBaseUrl(firecrawl?: FirecrawlFetchConfig): string { } function resolveFirecrawlOnlyMainContent(firecrawl?: FirecrawlFetchConfig): boolean { - if (typeof firecrawl?.onlyMainContent === "boolean") return firecrawl.onlyMainContent; + if (typeof firecrawl?.onlyMainContent === "boolean") { + return firecrawl.onlyMainContent; + } return true; } @@ -136,14 +150,18 @@ function resolveFirecrawlMaxAgeMs(firecrawl?: FirecrawlFetchConfig): number | un firecrawl && "maxAgeMs" in firecrawl && typeof firecrawl.maxAgeMs === "number" ? firecrawl.maxAgeMs : undefined; - if (typeof raw !== "number" || !Number.isFinite(raw)) return undefined; + if (typeof raw !== "number" || !Number.isFinite(raw)) { + return undefined; + } const parsed = Math.max(0, Math.floor(raw)); return parsed > 0 ? parsed : undefined; } function resolveFirecrawlMaxAgeMsOrDefault(firecrawl?: FirecrawlFetchConfig): number { const resolved = resolveFirecrawlMaxAgeMs(firecrawl); - if (typeof resolved === "number") return resolved; + if (typeof resolved === "number") { + return resolved; + } return DEFAULT_FIRECRAWL_MAX_AGE_MS; } @@ -159,7 +177,9 @@ function resolveMaxRedirects(value: unknown, fallback: number): number { function looksLikeHtml(value: string): boolean { const trimmed = value.trimStart(); - if (!trimmed) return false; + if (!trimmed) { + return false; + } const head = trimmed.slice(0, 256).toLowerCase(); return head.startsWith(" { - if (!params.firecrawlEnabled || !params.firecrawlApiKey) return null; + if (!params.firecrawlEnabled || !params.firecrawlApiKey) { + return null; + } try { const firecrawl = await fetchFirecrawlContent({ url: params.url, @@ -556,7 +582,9 @@ async function tryFirecrawlFallback(params: { function resolveFirecrawlEndpoint(baseUrl: string): string { const trimmed = baseUrl.trim(); - if (!trimmed) return `${DEFAULT_FIRECRAWL_BASE_URL}/v2/scrape`; + if (!trimmed) { + return `${DEFAULT_FIRECRAWL_BASE_URL}/v2/scrape`; + } try { const url = new URL(trimmed); if (url.pathname && url.pathname !== "/") { @@ -574,7 +602,9 @@ export function createWebFetchTool(options?: { sandboxed?: boolean; }): AnyAgentTool | null { const fetch = resolveFetchConfig(options?.config); - if (!resolveFetchEnabled({ fetch, sandboxed: options?.sandboxed })) return null; + if (!resolveFetchEnabled({ fetch, sandboxed: options?.sandboxed })) { + return null; + } const readabilityEnabled = resolveFetchReadabilityEnabled(fetch); const firecrawl = resolveFirecrawlConfig(fetch); const firecrawlApiKey = resolveFirecrawlApiKey(firecrawl); diff --git a/src/agents/tools/web-search.ts b/src/agents/tools/web-search.ts index bf57414901..4e0cc9f0ea 100644 --- a/src/agents/tools/web-search.ts +++ b/src/agents/tools/web-search.ts @@ -105,13 +105,19 @@ type PerplexityBaseUrlHint = "direct" | "openrouter"; function resolveSearchConfig(cfg?: OpenClawConfig): WebSearchConfig { const search = cfg?.tools?.web?.search; - if (!search || typeof search !== "object") return undefined; + if (!search || typeof search !== "object") { + return undefined; + } return search as WebSearchConfig; } function resolveSearchEnabled(params: { search?: WebSearchConfig; sandboxed?: boolean }): boolean { - if (typeof params.search?.enabled === "boolean") return params.search.enabled; - if (params.sandboxed) return true; + if (typeof params.search?.enabled === "boolean") { + return params.search.enabled; + } + if (params.sandboxed) { + return true; + } return true; } @@ -143,15 +149,23 @@ function resolveSearchProvider(search?: WebSearchConfig): (typeof SEARCH_PROVIDE search && "provider" in search && typeof search.provider === "string" ? search.provider.trim().toLowerCase() : ""; - if (raw === "perplexity") return "perplexity"; - if (raw === "brave") return "brave"; + if (raw === "perplexity") { + return "perplexity"; + } + if (raw === "brave") { + return "brave"; + } return "brave"; } function resolvePerplexityConfig(search?: WebSearchConfig): PerplexityConfig { - if (!search || typeof search !== "object") return {}; + if (!search || typeof search !== "object") { + return {}; + } const perplexity = "perplexity" in search ? search.perplexity : undefined; - if (!perplexity || typeof perplexity !== "object") return {}; + if (!perplexity || typeof perplexity !== "object") { + return {}; + } return perplexity as PerplexityConfig; } @@ -182,7 +196,9 @@ function normalizeApiKey(key: unknown): string { } function inferPerplexityBaseUrlFromApiKey(apiKey?: string): PerplexityBaseUrlHint | undefined { - if (!apiKey) return undefined; + if (!apiKey) { + return undefined; + } const normalized = apiKey.toLowerCase(); if (PERPLEXITY_KEY_PREFIXES.some((prefix) => normalized.startsWith(prefix))) { return "direct"; @@ -202,13 +218,23 @@ function resolvePerplexityBaseUrl( perplexity && "baseUrl" in perplexity && typeof perplexity.baseUrl === "string" ? perplexity.baseUrl.trim() : ""; - if (fromConfig) return fromConfig; - if (apiKeySource === "perplexity_env") return PERPLEXITY_DIRECT_BASE_URL; - if (apiKeySource === "openrouter_env") return DEFAULT_PERPLEXITY_BASE_URL; + if (fromConfig) { + return fromConfig; + } + if (apiKeySource === "perplexity_env") { + return PERPLEXITY_DIRECT_BASE_URL; + } + if (apiKeySource === "openrouter_env") { + return DEFAULT_PERPLEXITY_BASE_URL; + } if (apiKeySource === "config") { const inferred = inferPerplexityBaseUrlFromApiKey(apiKey); - if (inferred === "direct") return PERPLEXITY_DIRECT_BASE_URL; - if (inferred === "openrouter") return DEFAULT_PERPLEXITY_BASE_URL; + if (inferred === "direct") { + return PERPLEXITY_DIRECT_BASE_URL; + } + if (inferred === "openrouter") { + return DEFAULT_PERPLEXITY_BASE_URL; + } } return DEFAULT_PERPLEXITY_BASE_URL; } @@ -228,27 +254,43 @@ function resolveSearchCount(value: unknown, fallback: number): number { } function normalizeFreshness(value: string | undefined): string | undefined { - if (!value) return undefined; + if (!value) { + return undefined; + } const trimmed = value.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } const lower = trimmed.toLowerCase(); - if (BRAVE_FRESHNESS_SHORTCUTS.has(lower)) return lower; + if (BRAVE_FRESHNESS_SHORTCUTS.has(lower)) { + return lower; + } const match = trimmed.match(BRAVE_FRESHNESS_RANGE); - if (!match) return undefined; + if (!match) { + return undefined; + } const [, start, end] = match; - if (!isValidIsoDate(start) || !isValidIsoDate(end)) return undefined; - if (start > end) return undefined; + if (!isValidIsoDate(start) || !isValidIsoDate(end)) { + return undefined; + } + if (start > end) { + return undefined; + } return `${start}to${end}`; } function isValidIsoDate(value: string): boolean { - if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false; + if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) { + return false; + } const [year, month, day] = value.split("-").map((part) => Number.parseInt(part, 10)); - if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) return false; + if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) { + return false; + } const date = new Date(Date.UTC(year, month - 1, day)); return ( @@ -257,7 +299,9 @@ function isValidIsoDate(value: string): boolean { } function resolveSiteName(url: string | undefined): string | undefined { - if (!url) return undefined; + if (!url) { + return undefined; + } try { return new URL(url).hostname; } catch { @@ -326,7 +370,9 @@ async function runWebSearch(params: { : `${params.provider}:${params.query}:${params.count}:${params.country || "default"}:${params.search_lang || "default"}:${params.ui_lang || "default"}`, ); const cached = readCache(SEARCH_CACHE, cacheKey); - if (cached) return { ...cached.value, cached: true }; + if (cached) { + return { ...cached.value, cached: true }; + } const start = Date.now(); @@ -411,7 +457,9 @@ export function createWebSearchTool(options?: { sandboxed?: boolean; }): AnyAgentTool | null { const search = resolveSearchConfig(options?.config); - if (!resolveSearchEnabled({ search, sandboxed: options?.sandboxed })) return null; + if (!resolveSearchEnabled({ search, sandboxed: options?.sandboxed })) { + return null; + } const provider = resolveSearchProvider(search); const perplexityConfig = resolvePerplexityConfig(search); diff --git a/src/agents/tools/web-shared.ts b/src/agents/tools/web-shared.ts index d56800067d..d172a06341 100644 --- a/src/agents/tools/web-shared.ts +++ b/src/agents/tools/web-shared.ts @@ -28,7 +28,9 @@ export function readCache( key: string, ): { value: T; cached: boolean } | null { const entry = cache.get(key); - if (!entry) return null; + if (!entry) { + return null; + } if (Date.now() > entry.expiresAt) { cache.delete(key); return null; @@ -42,10 +44,14 @@ export function writeCache( value: T, ttlMs: number, ) { - if (ttlMs <= 0) return; + if (ttlMs <= 0) { + return; + } if (cache.size >= DEFAULT_CACHE_MAX_ENTRIES) { const oldest = cache.keys().next(); - if (!oldest.done) cache.delete(oldest.value); + if (!oldest.done) { + cache.delete(oldest.value); + } } cache.set(key, { value, @@ -55,7 +61,9 @@ export function writeCache( } export function withTimeout(signal: AbortSignal | undefined, timeoutMs: number): AbortSignal { - if (timeoutMs <= 0) return signal ?? new AbortController().signal; + if (timeoutMs <= 0) { + return signal ?? new AbortController().signal; + } const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeoutMs); if (signal) { diff --git a/src/agents/tools/web-tools.fetch.test.ts b/src/agents/tools/web-tools.fetch.test.ts index 86bdeb7a27..15b9bd2ee9 100644 --- a/src/agents/tools/web-tools.fetch.test.ts +++ b/src/agents/tools/web-tools.fetch.test.ts @@ -65,9 +65,15 @@ function errorHtmlResponse( }; } function requestUrl(input: RequestInfo): string { - if (typeof input === "string") return input; - if (input instanceof URL) return input.toString(); - if ("url" in input && typeof input.url === "string") return input.url; + if (typeof input === "string") { + return input; + } + if (input instanceof URL) { + return input.toString(); + } + if ("url" in input && typeof input.url === "string") { + return input.url; + } return ""; } diff --git a/src/agents/transcript-policy.ts b/src/agents/transcript-policy.ts index 9ae14d38fc..6f1b167c7f 100644 --- a/src/agents/transcript-policy.ts +++ b/src/agents/transcript-policy.ts @@ -39,17 +39,23 @@ const OPENAI_MODEL_APIS = new Set([ const OPENAI_PROVIDERS = new Set(["openai", "openai-codex"]); function isOpenAiApi(modelApi?: string | null): boolean { - if (!modelApi) return false; + if (!modelApi) { + return false; + } return OPENAI_MODEL_APIS.has(modelApi); } function isOpenAiProvider(provider?: string | null): boolean { - if (!provider) return false; + if (!provider) { + return false; + } return OPENAI_PROVIDERS.has(normalizeProviderId(provider)); } function isAnthropicApi(modelApi?: string | null, provider?: string | null): boolean { - if (modelApi === "anthropic-messages") return true; + if (modelApi === "anthropic-messages") { + return true; + } const normalized = normalizeProviderId(provider ?? ""); // MiniMax now uses openai-completions API, not anthropic-messages return normalized === "anthropic"; @@ -57,9 +63,13 @@ function isAnthropicApi(modelApi?: string | null, provider?: string | null): boo function isMistralModel(params: { provider?: string | null; modelId?: string | null }): boolean { const provider = normalizeProviderId(params.provider ?? ""); - if (provider === "mistral") return true; + if (provider === "mistral") { + return true; + } const modelId = (params.modelId ?? "").toLowerCase(); - if (!modelId) return false; + if (!modelId) { + return false; + } return MISTRAL_MODEL_HINTS.some((hint) => modelId.includes(hint)); } diff --git a/src/agents/usage.ts b/src/agents/usage.ts index aa7dce767b..ec0610b8d3 100644 --- a/src/agents/usage.ts +++ b/src/agents/usage.ts @@ -31,20 +31,28 @@ export type NormalizedUsage = { }; const asFiniteNumber = (value: unknown): number | undefined => { - if (typeof value !== "number") return undefined; - if (!Number.isFinite(value)) return undefined; + if (typeof value !== "number") { + return undefined; + } + if (!Number.isFinite(value)) { + return undefined; + } return value; }; export function hasNonzeroUsage(usage?: NormalizedUsage | null): usage is NormalizedUsage { - if (!usage) return false; + if (!usage) { + return false; + } return [usage.input, usage.output, usage.cacheRead, usage.cacheWrite, usage.total].some( (v) => typeof v === "number" && Number.isFinite(v) && v > 0, ); } export function normalizeUsage(raw?: UsageLike | null): NormalizedUsage | undefined { - if (!raw) return undefined; + if (!raw) { + return undefined; + } const input = asFiniteNumber( raw.input ?? raw.inputTokens ?? raw.input_tokens ?? raw.promptTokens ?? raw.prompt_tokens, @@ -86,7 +94,9 @@ export function derivePromptTokens(usage?: { cacheRead?: number; cacheWrite?: number; }): number | undefined { - if (!usage) return undefined; + if (!usage) { + return undefined; + } const input = usage.input ?? 0; const cacheRead = usage.cacheRead ?? 0; const cacheWrite = usage.cacheWrite ?? 0; diff --git a/src/agents/workspace.ts b/src/agents/workspace.ts index 0c59e2c815..1d2c647468 100644 --- a/src/agents/workspace.ts +++ b/src/agents/workspace.ts @@ -35,9 +35,13 @@ const TEMPLATE_DIR = path.resolve( ); function stripFrontMatter(content: string): string { - if (!content.startsWith("---")) return content; + if (!content.startsWith("---")) { + return content; + } const endIndex = content.indexOf("\n---", 3); - if (endIndex === -1) return content; + if (endIndex === -1) { + return content; + } const start = endIndex + "\n---".length; let trimmed = content.slice(start); trimmed = trimmed.replace(/^\s+/, ""); @@ -82,7 +86,9 @@ async function writeFileIfMissing(filePath: string, content: string) { }); } catch (err) { const anyErr = err as { code?: string }; - if (anyErr.code !== "EEXIST") throw err; + if (anyErr.code !== "EEXIST") { + throw err; + } } } @@ -105,9 +111,15 @@ async function isGitAvailable(): Promise { } async function ensureGitRepo(dir: string, isBrandNewWorkspace: boolean) { - if (!isBrandNewWorkspace) return; - if (await hasGitRepo(dir)) return; - if (!(await isGitAvailable())) return; + if (!isBrandNewWorkspace) { + return; + } + if (await hasGitRepo(dir)) { + return; + } + if (!(await isGitAvailable())) { + return; + } try { await runCommandWithTimeout(["git", "init"], { cwd: dir, timeoutMs: 10_000 }); } catch { @@ -132,7 +144,9 @@ export async function ensureAgentWorkspace(params?: { const dir = resolveUserPath(rawDir); await fs.mkdir(dir, { recursive: true }); - if (!params?.ensureBootstrapFiles) return { dir }; + if (!params?.ensureBootstrapFiles) { + return { dir }; + } const agentsPath = path.join(dir, DEFAULT_AGENTS_FILENAME); const soulPath = path.join(dir, DEFAULT_SOUL_FILENAME); @@ -205,7 +219,9 @@ async function resolveMemoryBootstrapEntries( // optional } } - if (entries.length <= 1) return entries; + if (entries.length <= 1) { + return entries; + } const seen = new Set(); const deduped: Array<{ name: WorkspaceBootstrapFileName; filePath: string }> = []; @@ -214,7 +230,9 @@ async function resolveMemoryBootstrapEntries( try { key = await fs.realpath(entry.filePath); } catch {} - if (seen.has(key)) continue; + if (seen.has(key)) { + continue; + } seen.add(key); deduped.push(entry); } @@ -283,6 +301,8 @@ export function filterBootstrapFilesForSession( files: WorkspaceBootstrapFile[], sessionKey?: string, ): WorkspaceBootstrapFile[] { - if (!sessionKey || !isSubagentSessionKey(sessionKey)) return files; + if (!sessionKey || !isSubagentSessionKey(sessionKey)) { + return files; + } return files.filter((file) => SUBAGENT_BOOTSTRAP_ALLOWLIST.has(file.name)); } diff --git a/src/auto-reply/chunk.test.ts b/src/auto-reply/chunk.test.ts index 5458998435..e1417d18c0 100644 --- a/src/auto-reply/chunk.test.ts +++ b/src/auto-reply/chunk.test.ts @@ -15,7 +15,9 @@ function expectFencesBalanced(chunks: string[]) { let open: { markerChar: string; markerLen: number } | null = null; for (const line of chunk.split("\n")) { const match = line.match(/^( {0,3})(`{3,}|~{3,})(.*)$/); - if (!match) continue; + if (!match) { + continue; + } const marker = match[2]; if (!open) { open = { markerChar: marker[0], markerLen: marker.length }; diff --git a/src/auto-reply/chunk.ts b/src/auto-reply/chunk.ts index e72b06ddc9..204f88ad39 100644 --- a/src/auto-reply/chunk.ts +++ b/src/auto-reply/chunk.ts @@ -32,7 +32,9 @@ function resolveChunkLimitForProvider( cfgSection: ProviderChunkConfig | undefined, accountId?: string | null, ): number | undefined { - if (!cfgSection) return undefined; + if (!cfgSection) { + return undefined; + } const normalizedAccountId = normalizeAccountId(accountId); const accounts = cfgSection.accounts; if (accounts && typeof accounts === "object") { @@ -62,7 +64,9 @@ export function resolveTextChunkLimit( ? opts.fallbackLimit : DEFAULT_CHUNK_LIMIT; const providerOverride = (() => { - if (!provider || provider === INTERNAL_MESSAGE_CHANNEL) return undefined; + if (!provider || provider === INTERNAL_MESSAGE_CHANNEL) { + return undefined; + } const channelsConfig = cfg?.channels as Record | undefined; const providerConfig = (channelsConfig?.[provider] ?? (cfg as Record | undefined)?.[provider]) as ProviderChunkConfig | undefined; @@ -78,7 +82,9 @@ function resolveChunkModeForProvider( cfgSection: ProviderChunkConfig | undefined, accountId?: string | null, ): ChunkMode | undefined { - if (!cfgSection) return undefined; + if (!cfgSection) { + return undefined; + } const normalizedAccountId = normalizeAccountId(accountId); const accounts = cfgSection.accounts; if (accounts && typeof accounts === "object") { @@ -102,7 +108,9 @@ export function resolveChunkMode( provider?: TextChunkProvider, accountId?: string | null, ): ChunkMode { - if (!provider || provider === INTERNAL_MESSAGE_CHANNEL) return DEFAULT_CHUNK_MODE; + if (!provider || provider === INTERNAL_MESSAGE_CHANNEL) { + return DEFAULT_CHUNK_MODE; + } const channelsConfig = cfg?.channels as Record | undefined; const providerConfig = (channelsConfig?.[provider] ?? (cfg as Record | undefined)?.[provider]) as ProviderChunkConfig | undefined; @@ -124,8 +132,12 @@ export function chunkByNewline( isSafeBreak?: (index: number) => boolean; }, ): string[] { - if (!text) return []; - if (maxLineLength <= 0) return text.trim() ? [text] : []; + if (!text) { + return []; + } + if (maxLineLength <= 0) { + return text.trim() ? [text] : []; + } const splitLongLines = opts?.splitLongLines !== false; const trimLines = opts?.trimLines !== false; const lines = splitByNewline(text, opts?.isSafeBreak); @@ -180,8 +192,12 @@ export function chunkByParagraph( limit: number, opts?: { splitLongParagraphs?: boolean }, ): string[] { - if (!text) return []; - if (limit <= 0) return [text]; + if (!text) { + return []; + } + if (limit <= 0) { + return [text]; + } const splitLongParagraphs = opts?.splitLongParagraphs !== false; // Normalize to \n so blank line detection is consistent. @@ -192,8 +208,12 @@ export function chunkByParagraph( // boundaries, not only exceeding a length limit.) const paragraphRe = /\n[\t ]*\n+/; if (!paragraphRe.test(normalized)) { - if (normalized.length <= limit) return [normalized]; - if (!splitLongParagraphs) return [normalized]; + if (normalized.length <= limit) { + return [normalized]; + } + if (!splitLongParagraphs) { + return [normalized]; + } return chunkText(normalized, limit); } @@ -218,7 +238,9 @@ export function chunkByParagraph( const chunks: string[] = []; for (const part of parts) { const paragraph = part.replace(/\s+$/g, ""); - if (!paragraph.trim()) continue; + if (!paragraph.trim()) { + continue; + } if (paragraph.length <= limit) { chunks.push(paragraph); } else if (!splitLongParagraphs) { @@ -249,8 +271,11 @@ export function chunkMarkdownTextWithMode(text: string, limit: number, mode: Chu const out: string[] = []; for (const chunk of paragraphChunks) { const nested = chunkMarkdownText(chunk, limit); - if (!nested.length && chunk) out.push(chunk); - else out.push(...nested); + if (!nested.length && chunk) { + out.push(chunk); + } else { + out.push(...nested); + } } return out; } @@ -274,9 +299,15 @@ function splitByNewline( } export function chunkText(text: string, limit: number): string[] { - if (!text) return []; - if (limit <= 0) return [text]; - if (text.length <= limit) return [text]; + if (!text) { + return []; + } + if (limit <= 0) { + return [text]; + } + if (text.length <= limit) { + return [text]; + } const chunks: string[] = []; let remaining = text; @@ -291,7 +322,9 @@ export function chunkText(text: string, limit: number): string[] { let breakIdx = lastNewline > 0 ? lastNewline : lastWhitespace; // 3) Fallback: hard break exactly at the limit. - if (breakIdx <= 0) breakIdx = limit; + if (breakIdx <= 0) { + breakIdx = limit; + } const rawChunk = remaining.slice(0, breakIdx); const chunk = rawChunk.trimEnd(); @@ -305,15 +338,23 @@ export function chunkText(text: string, limit: number): string[] { remaining = remaining.slice(nextStart).trimStart(); } - if (remaining.length) chunks.push(remaining); + if (remaining.length) { + chunks.push(remaining); + } return chunks; } export function chunkMarkdownText(text: string, limit: number): string[] { - if (!text) return []; - if (limit <= 0) return [text]; - if (text.length <= limit) return [text]; + if (!text) { + return []; + } + if (limit <= 0) { + return [text]; + } + if (text.length <= limit) { + return [text]; + } const chunks: string[] = []; let remaining = text; @@ -348,7 +389,9 @@ export function chunkMarkdownText(text: string, limit: number): string[] { let lastNewline = remaining.lastIndexOf("\n", Math.max(0, maxIdxIfAlreadyNewline - 1)); while (lastNewline !== -1) { const candidateBreak = lastNewline + 1; - if (candidateBreak < minProgressIdx) break; + if (candidateBreak < minProgressIdx) { + break; + } const candidateFence = findFenceSpanAt(spans, candidateBreak); if (candidateFence && candidateFence.start === initialFence.start) { breakIdx = Math.max(1, candidateBreak); @@ -374,7 +417,9 @@ export function chunkMarkdownText(text: string, limit: number): string[] { } let rawChunk = remaining.slice(0, breakIdx); - if (!rawChunk) break; + if (!rawChunk) { + break; + } const brokeOnSeparator = breakIdx < remaining.length && /\s/.test(remaining[breakIdx]); const nextStart = Math.min(remaining.length, breakIdx + (brokeOnSeparator ? 1 : 0)); @@ -392,13 +437,17 @@ export function chunkMarkdownText(text: string, limit: number): string[] { remaining = next; } - if (remaining.length) chunks.push(remaining); + if (remaining.length) { + chunks.push(remaining); + } return chunks; } function stripLeadingNewlines(value: string): string { let i = 0; - while (i < value.length && value[i] === "\n") i++; + while (i < value.length && value[i] === "\n") { + i++; + } return i > 0 ? value.slice(i) : value; } @@ -407,8 +456,12 @@ function pickSafeBreakIndex(window: string, spans: ReturnType 0) return lastNewline; - if (lastWhitespace > 0) return lastWhitespace; + if (lastNewline > 0) { + return lastNewline; + } + if (lastWhitespace > 0) { + return lastWhitespace; + } return -1; } @@ -421,7 +474,9 @@ function scanParenAwareBreakpoints( let depth = 0; for (let i = 0; i < window.length; i++) { - if (!isAllowed(i)) continue; + if (!isAllowed(i)) { + continue; + } const char = window[i]; if (char === "(") { depth += 1; @@ -431,9 +486,14 @@ function scanParenAwareBreakpoints( depth -= 1; continue; } - if (depth !== 0) continue; - if (char === "\n") lastNewline = i; - else if (/\s/.test(char)) lastWhitespace = i; + if (depth !== 0) { + continue; + } + if (char === "\n") { + lastNewline = i; + } else if (/\s/.test(char)) { + lastWhitespace = i; + } } return { lastNewline, lastWhitespace }; diff --git a/src/auto-reply/command-auth.ts b/src/auto-reply/command-auth.ts index 290fed46da..aad616424e 100644 --- a/src/auto-reply/command-auth.ts +++ b/src/auto-reply/command-auth.ts @@ -19,26 +19,36 @@ function resolveProviderFromContext(ctx: MsgContext, cfg: OpenClawConfig): Chann normalizeAnyChannelId(ctx.Provider) ?? normalizeAnyChannelId(ctx.Surface) ?? normalizeAnyChannelId(ctx.OriginatingChannel); - if (direct) return direct; + if (direct) { + return direct; + } const candidates = [ctx.From, ctx.To] .filter((value): value is string => Boolean(value?.trim())) .flatMap((value) => value.split(":").map((part) => part.trim())); for (const candidate of candidates) { const normalized = normalizeAnyChannelId(candidate); - if (normalized) return normalized; + if (normalized) { + return normalized; + } } const configured = listChannelDocks() .map((dock) => { - if (!dock.config?.resolveAllowFrom) return null; + if (!dock.config?.resolveAllowFrom) { + return null; + } const allowFrom = dock.config.resolveAllowFrom({ cfg, accountId: ctx.AccountId, }); - if (!Array.isArray(allowFrom) || allowFrom.length === 0) return null; + if (!Array.isArray(allowFrom) || allowFrom.length === 0) { + return null; + } return dock.id; }) .filter((value): value is ChannelId => Boolean(value)); - if (configured.length === 1) return configured[0]; + if (configured.length === 1) { + return configured[0]; + } return undefined; } @@ -49,7 +59,9 @@ function formatAllowFromList(params: { allowFrom: Array; }): string[] { const { dock, cfg, accountId, allowFrom } = params; - if (!allowFrom || allowFrom.length === 0) return []; + if (!allowFrom || allowFrom.length === 0) { + return []; + } if (dock?.config?.formatAllowFrom) { return dock.config.formatAllowFrom({ cfg, accountId, allowFrom }); } @@ -84,7 +96,9 @@ function resolveSenderCandidates(params: { const candidates: string[] = []; const pushCandidate = (value?: string | null) => { const trimmed = (value ?? "").trim(); - if (!trimmed) return; + if (!trimmed) { + return; + } candidates.push(trimmed); }; if (params.providerId === "whatsapp") { @@ -100,7 +114,9 @@ function resolveSenderCandidates(params: { for (const sender of candidates) { const entries = normalizeAllowFromEntry({ dock, cfg, accountId, value: sender }); for (const entry of entries) { - if (!normalized.includes(entry)) normalized.push(entry); + if (!normalized.includes(entry)) { + normalized.push(entry); + } } } return normalized; @@ -136,7 +152,9 @@ export function resolveCommandAuthorization(params: { accountId: ctx.AccountId, value: to, }); - if (normalizedTo.length > 0) ownerCandidates.push(...normalizedTo); + if (normalizedTo.length > 0) { + ownerCandidates.push(...normalizedTo); + } } const ownerList = Array.from(new Set(ownerCandidates)); diff --git a/src/auto-reply/command-detection.ts b/src/auto-reply/command-detection.ts index b2b5d2ca15..5a295a8298 100644 --- a/src/auto-reply/command-detection.ts +++ b/src/auto-reply/command-detection.ts @@ -12,21 +12,33 @@ export function hasControlCommand( cfg?: OpenClawConfig, options?: CommandNormalizeOptions, ): boolean { - if (!text) return false; + if (!text) { + return false; + } const trimmed = text.trim(); - if (!trimmed) return false; + if (!trimmed) { + return false; + } const normalizedBody = normalizeCommandBody(trimmed, options); - if (!normalizedBody) return false; + if (!normalizedBody) { + return false; + } const lowered = normalizedBody.toLowerCase(); const commands = cfg ? listChatCommandsForConfig(cfg) : listChatCommands(); for (const command of commands) { for (const alias of command.textAliases) { const normalized = alias.trim().toLowerCase(); - if (!normalized) continue; - if (lowered === normalized) return true; + if (!normalized) { + continue; + } + if (lowered === normalized) { + return true; + } if (command.acceptsArgs && lowered.startsWith(normalized)) { const nextChar = normalizedBody.charAt(normalized.length); - if (nextChar && /\s/.test(nextChar)) return true; + if (nextChar && /\s/.test(nextChar)) { + return true; + } } } } @@ -38,10 +50,16 @@ export function isControlCommandMessage( cfg?: OpenClawConfig, options?: CommandNormalizeOptions, ): boolean { - if (!text) return false; + if (!text) { + return false; + } const trimmed = text.trim(); - if (!trimmed) return false; - if (hasControlCommand(trimmed, cfg, options)) return true; + if (!trimmed) { + return false; + } + if (hasControlCommand(trimmed, cfg, options)) { + return true; + } const normalized = normalizeCommandBody(trimmed, options).trim().toLowerCase(); return isAbortTrigger(normalized); } @@ -55,7 +73,9 @@ export function isControlCommandMessage( */ export function hasInlineCommandTokens(text?: string): boolean { const body = text ?? ""; - if (!body.trim()) return false; + if (!body.trim()) { + return false; + } return /(?:^|\s)[/!][a-z]/i.test(body); } diff --git a/src/auto-reply/commands-args.ts b/src/auto-reply/commands-args.ts index 7122d54570..cd617071b6 100644 --- a/src/auto-reply/commands-args.ts +++ b/src/auto-reply/commands-args.ts @@ -3,7 +3,9 @@ import type { CommandArgValues } from "./commands-registry.types.js"; export type CommandArgsFormatter = (values: CommandArgValues) => string | undefined; function normalizeArgValue(value: unknown): string | undefined { - if (value == null) return undefined; + if (value == null) { + return undefined; + } let text: string; if (typeof value === "string") { text = value.trim(); @@ -24,7 +26,9 @@ const formatConfigArgs: CommandArgsFormatter = (values) => { const action = normalizeArgValue(values.action)?.toLowerCase(); const path = normalizeArgValue(values.path); const value = normalizeArgValue(values.value); - if (!action) return undefined; + if (!action) { + return undefined; + } if (action === "show" || action === "get") { return path ? `${action} ${path}` : action; } @@ -32,8 +36,12 @@ const formatConfigArgs: CommandArgsFormatter = (values) => { return path ? `${action} ${path}` : action; } if (action === "set") { - if (!path) return action; - if (!value) return `${action} ${path}`; + if (!path) { + return action; + } + if (!value) { + return `${action} ${path}`; + } return `${action} ${path}=${value}`; } return action; @@ -43,7 +51,9 @@ const formatDebugArgs: CommandArgsFormatter = (values) => { const action = normalizeArgValue(values.action)?.toLowerCase(); const path = normalizeArgValue(values.path); const value = normalizeArgValue(values.value); - if (!action) return undefined; + if (!action) { + return undefined; + } if (action === "show" || action === "reset") { return action; } @@ -51,8 +61,12 @@ const formatDebugArgs: CommandArgsFormatter = (values) => { return path ? `${action} ${path}` : action; } if (action === "set") { - if (!path) return action; - if (!value) return `${action} ${path}`; + if (!path) { + return action; + } + if (!value) { + return `${action} ${path}`; + } return `${action} ${path}=${value}`; } return action; @@ -64,10 +78,18 @@ const formatQueueArgs: CommandArgsFormatter = (values) => { const cap = normalizeArgValue(values.cap); const drop = normalizeArgValue(values.drop); const parts: string[] = []; - if (mode) parts.push(mode); - if (debounce) parts.push(`debounce:${debounce}`); - if (cap) parts.push(`cap:${cap}`); - if (drop) parts.push(`drop:${drop}`); + if (mode) { + parts.push(mode); + } + if (debounce) { + parts.push(`debounce:${debounce}`); + } + if (cap) { + parts.push(`cap:${cap}`); + } + if (drop) { + parts.push(`drop:${drop}`); + } return parts.length > 0 ? parts.join(" ") : undefined; }; diff --git a/src/auto-reply/commands-registry.data.ts b/src/auto-reply/commands-registry.data.ts index f5547bc3c8..e9395347ed 100644 --- a/src/auto-reply/commands-registry.data.ts +++ b/src/auto-reply/commands-registry.data.ts @@ -66,9 +66,13 @@ function registerAlias(commands: ChatCommandDefinition[], key: string, ...aliase const existing = new Set(command.textAliases.map((alias) => alias.trim().toLowerCase())); for (const alias of aliases) { const trimmed = alias.trim(); - if (!trimmed) continue; + if (!trimmed) { + continue; + } const lowered = trimmed.toLowerCase(); - if (existing.has(lowered)) continue; + if (existing.has(lowered)) { + continue; + } existing.add(lowered); command.textAliases.push(trimmed); } @@ -585,7 +589,9 @@ function buildChatCommands(): ChatCommandDefinition[] { export function getChatCommands(): ChatCommandDefinition[] { const registry = getActivePluginRegistry(); - if (cachedCommands && registry === cachedRegistry) return cachedCommands; + if (cachedCommands && registry === cachedRegistry) { + return cachedCommands; + } const commands = buildChatCommands(); cachedCommands = commands; cachedRegistry = registry; diff --git a/src/auto-reply/commands-registry.ts b/src/auto-reply/commands-registry.ts index b0112d8535..7c0b099eb9 100644 --- a/src/auto-reply/commands-registry.ts +++ b/src/auto-reply/commands-registry.ts @@ -43,7 +43,9 @@ let cachedDetectionCommands: ChatCommandDefinition[] | null = null; function getTextAliasMap(): Map { const commands = getChatCommands(); - if (cachedTextAliasMap && cachedTextAliasCommands === commands) return cachedTextAliasMap; + if (cachedTextAliasMap && cachedTextAliasCommands === commands) { + return cachedTextAliasMap; + } const map = new Map(); for (const command of commands) { // Canonicalize to the *primary* text alias, not `/${key}`. Some command keys are @@ -53,7 +55,9 @@ function getTextAliasMap(): Map { const acceptsArgs = Boolean(command.acceptsArgs); for (const alias of command.textAliases) { const normalized = alias.trim().toLowerCase(); - if (!normalized) continue; + if (!normalized) { + continue; + } if (!map.has(normalized)) { map.set(normalized, { key: command.key, canonical, acceptsArgs }); } @@ -69,7 +73,9 @@ function escapeRegExp(value: string) { } function buildSkillCommandDefinitions(skillCommands?: SkillCommandSpec[]): ChatCommandDefinition[] { - if (!skillCommands || skillCommands.length === 0) return []; + if (!skillCommands || skillCommands.length === 0) { + return []; + } return skillCommands.map((spec) => ({ key: `skill:${spec.skillName}`, nativeName: spec.name, @@ -85,14 +91,22 @@ export function listChatCommands(params?: { skillCommands?: SkillCommandSpec[]; }): ChatCommandDefinition[] { const commands = getChatCommands(); - if (!params?.skillCommands?.length) return [...commands]; + if (!params?.skillCommands?.length) { + return [...commands]; + } return [...commands, ...buildSkillCommandDefinitions(params.skillCommands)]; } export function isCommandEnabled(cfg: OpenClawConfig, commandKey: string): boolean { - if (commandKey === "config") return cfg.commands?.config === true; - if (commandKey === "debug") return cfg.commands?.debug === true; - if (commandKey === "bash") return cfg.commands?.bash === true; + if (commandKey === "config") { + return cfg.commands?.config === true; + } + if (commandKey === "debug") { + return cfg.commands?.debug === true; + } + if (commandKey === "bash") { + return cfg.commands?.bash === true; + } return true; } @@ -101,7 +115,9 @@ export function listChatCommandsForConfig( params?: { skillCommands?: SkillCommandSpec[] }, ): ChatCommandDefinition[] { const base = getChatCommands().filter((command) => isCommandEnabled(cfg, command.key)); - if (!params?.skillCommands?.length) return base; + if (!params?.skillCommands?.length) { + return base; + } return [...base, ...buildSkillCommandDefinitions(params.skillCommands)]; } @@ -112,10 +128,14 @@ const NATIVE_NAME_OVERRIDES: Record> = { }; function resolveNativeName(command: ChatCommandDefinition, provider?: string): string | undefined { - if (!command.nativeName) return undefined; + if (!command.nativeName) { + return undefined; + } if (provider) { const override = NATIVE_NAME_OVERRIDES[provider]?.[command.key]; - if (override) return override; + if (override) { + return override; + } } return command.nativeName; } @@ -168,11 +188,15 @@ export function buildCommandText(commandName: string, args?: string): string { function parsePositionalArgs(definitions: CommandArgDefinition[], raw: string): CommandArgValues { const values: CommandArgValues = {}; const trimmed = raw.trim(); - if (!trimmed) return values; + if (!trimmed) { + return values; + } const tokens = trimmed.split(/\s+/).filter(Boolean); let index = 0; for (const definition of definitions) { - if (index >= tokens.length) break; + if (index >= tokens.length) { + break; + } if (definition.captureRemaining) { values[definition.name] = tokens.slice(index).join(" "); index = tokens.length; @@ -191,16 +215,22 @@ function formatPositionalArgs( const parts: string[] = []; for (const definition of definitions) { const value = values[definition.name]; - if (value == null) continue; + if (value == null) { + continue; + } let rendered: string; if (typeof value === "string") { rendered = value.trim(); } else { rendered = String(value); } - if (!rendered) continue; + if (!rendered) { + continue; + } parts.push(rendered); - if (definition.captureRemaining) break; + if (definition.captureRemaining) { + break; + } } return parts.length > 0 ? parts.join(" ") : undefined; } @@ -210,7 +240,9 @@ export function parseCommandArgs( raw?: string, ): CommandArgs | undefined { const trimmed = raw?.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } if (!command.args || command.argsParsing === "none") { return { raw: trimmed }; } @@ -224,11 +256,19 @@ export function serializeCommandArgs( command: ChatCommandDefinition, args?: CommandArgs, ): string | undefined { - if (!args) return undefined; + if (!args) { + return undefined; + } const raw = args.raw?.trim(); - if (raw) return raw; - if (!args.values || !command.args) return undefined; - if (command.formatArgs) return command.formatArgs(args.values); + if (raw) { + return raw; + } + if (!args.values || !command.args) { + return undefined; + } + if (command.formatArgs) { + return command.formatArgs(args.values); + } return formatPositionalArgs(command.args, args.values); } @@ -265,7 +305,9 @@ export function resolveCommandArgChoices(params: { model?: string; }): ResolvedCommandArgChoice[] { const { command, arg, cfg } = params; - if (!arg.choices) return []; + if (!arg.choices) { + return []; + } const provided = arg.choices; const raw = Array.isArray(provided) ? provided @@ -291,27 +333,43 @@ export function resolveCommandArgMenu(params: { cfg?: OpenClawConfig; }): { arg: CommandArgDefinition; choices: ResolvedCommandArgChoice[]; title?: string } | null { const { command, args, cfg } = params; - if (!command.args || !command.argsMenu) return null; - if (command.argsParsing === "none") return null; + if (!command.args || !command.argsMenu) { + return null; + } + if (command.argsParsing === "none") { + return null; + } const argSpec = command.argsMenu; const argName = argSpec === "auto" ? command.args.find((arg) => resolveCommandArgChoices({ command, arg, cfg }).length > 0)?.name : argSpec.arg; - if (!argName) return null; - if (args?.values && args.values[argName] != null) return null; - if (args?.raw && !args.values) return null; + if (!argName) { + return null; + } + if (args?.values && args.values[argName] != null) { + return null; + } + if (args?.raw && !args.values) { + return null; + } const arg = command.args.find((entry) => entry.name === argName); - if (!arg) return null; + if (!arg) { + return null; + } const choices = resolveCommandArgChoices({ command, arg, cfg }); - if (choices.length === 0) return null; + if (choices.length === 0) { + return null; + } const title = argSpec !== "auto" ? argSpec.title : undefined; return { arg, choices, title }; } export function normalizeCommandBody(raw: string, options?: CommandNormalizeOptions): string { const trimmed = raw.trim(); - if (!trimmed.startsWith("/")) return trimmed; + if (!trimmed.startsWith("/")) { + return trimmed; + } const newline = trimmed.indexOf("\n"); const singleLine = newline === -1 ? trimmed : trimmed.slice(0, newline).trim(); @@ -337,15 +395,23 @@ export function normalizeCommandBody(raw: string, options?: CommandNormalizeOpti const lowered = commandBody.toLowerCase(); const textAliasMap = getTextAliasMap(); const exact = textAliasMap.get(lowered); - if (exact) return exact.canonical; + if (exact) { + return exact.canonical; + } const tokenMatch = commandBody.match(/^\/([^\s]+)(?:\s+([\s\S]+))?$/); - if (!tokenMatch) return commandBody; + if (!tokenMatch) { + return commandBody; + } const [, token, rest] = tokenMatch; const tokenKey = `/${token.toLowerCase()}`; const tokenSpec = textAliasMap.get(tokenKey); - if (!tokenSpec) return commandBody; - if (rest && !tokenSpec.acceptsArgs) return commandBody; + if (!tokenSpec) { + return commandBody; + } + if (rest && !tokenSpec.acceptsArgs) { + return commandBody; + } const normalizedRest = rest?.trimStart(); return normalizedRest ? `${tokenSpec.canonical} ${normalizedRest}` : tokenSpec.canonical; } @@ -357,16 +423,22 @@ export function isCommandMessage(raw: string): boolean { export function getCommandDetection(_cfg?: OpenClawConfig): CommandDetection { const commands = getChatCommands(); - if (cachedDetection && cachedDetectionCommands === commands) return cachedDetection; + if (cachedDetection && cachedDetectionCommands === commands) { + return cachedDetection; + } const exact = new Set(); const patterns: string[] = []; for (const cmd of commands) { for (const alias of cmd.textAliases) { const normalized = alias.trim().toLowerCase(); - if (!normalized) continue; + if (!normalized) { + continue; + } exact.add(normalized); const escaped = escapeRegExp(normalized); - if (!escaped) continue; + if (!escaped) { + continue; + } if (cmd.acceptsArgs) { patterns.push(`${escaped}(?:\\s+.+|\\s*:\\s*.*)?`); } else { @@ -384,13 +456,21 @@ export function getCommandDetection(_cfg?: OpenClawConfig): CommandDetection { export function maybeResolveTextAlias(raw: string, cfg?: OpenClawConfig) { const trimmed = normalizeCommandBody(raw).trim(); - if (!trimmed.startsWith("/")) return null; + if (!trimmed.startsWith("/")) { + return null; + } const detection = getCommandDetection(cfg); const normalized = trimmed.toLowerCase(); - if (detection.exact.has(normalized)) return normalized; - if (!detection.regex.test(normalized)) return null; + if (detection.exact.has(normalized)) { + return normalized; + } + if (!detection.regex.test(normalized)) { + return null; + } const tokenMatch = normalized.match(/^\/([^\s:]+)(?:\s|$)/); - if (!tokenMatch) return null; + if (!tokenMatch) { + return null; + } const tokenKey = `/${tokenMatch[1]}`; return getTextAliasMap().has(tokenKey) ? tokenKey : null; } @@ -404,23 +484,37 @@ export function resolveTextCommand( } | null { const trimmed = normalizeCommandBody(raw).trim(); const alias = maybeResolveTextAlias(trimmed, cfg); - if (!alias) return null; + if (!alias) { + return null; + } const spec = getTextAliasMap().get(alias); - if (!spec) return null; + if (!spec) { + return null; + } const command = getChatCommands().find((entry) => entry.key === spec.key); - if (!command) return null; - if (!spec.acceptsArgs) return { command }; + if (!command) { + return null; + } + if (!spec.acceptsArgs) { + return { command }; + } const args = trimmed.slice(alias.length).trim(); return { command, args: args || undefined }; } export function isNativeCommandSurface(surface?: string): boolean { - if (!surface) return false; + if (!surface) { + return false; + } return getNativeCommandSurfaces().has(surface.toLowerCase()); } export function shouldHandleTextCommands(params: ShouldHandleTextCommandsParams): boolean { - if (params.commandSource === "native") return true; - if (params.cfg.commands?.text !== false) return true; + if (params.commandSource === "native") { + return true; + } + if (params.cfg.commands?.text !== false) { + return true; + } return !isNativeCommandSurface(params.surface); } diff --git a/src/auto-reply/envelope.ts b/src/auto-reply/envelope.ts index 1d3f73fcf0..360b544926 100644 --- a/src/auto-reply/envelope.ts +++ b/src/auto-reply/envelope.ts @@ -77,10 +77,16 @@ function resolveExplicitTimezone(value: string): string | undefined { function resolveEnvelopeTimezone(options: NormalizedEnvelopeOptions): ResolvedEnvelopeTimezone { const trimmed = options.timezone?.trim(); - if (!trimmed) return { mode: "local" }; + if (!trimmed) { + return { mode: "local" }; + } const lowered = trimmed.toLowerCase(); - if (lowered === "utc" || lowered === "gmt") return { mode: "utc" }; - if (lowered === "local" || lowered === "host") return { mode: "local" }; + if (lowered === "utc" || lowered === "gmt") { + return { mode: "utc" }; + } + if (lowered === "local" || lowered === "host") { + return { mode: "local" }; + } if (lowered === "user") { return { mode: "iana", timeZone: resolveUserTimezone(options.userTimezone) }; } @@ -118,7 +124,9 @@ function formatZonedTimestamp(date: Date, timeZone?: string): string | undefined .toReversed() .find((part) => part.type === "timeZoneName") ?.value?.trim(); - if (!yyyy || !mm || !dd || !hh || !min) return undefined; + if (!yyyy || !mm || !dd || !hh || !min) { + return undefined; + } return `${yyyy}-${mm}-${dd} ${hh}:${min}${tz ? ` ${tz}` : ""}`; } @@ -126,29 +134,47 @@ function formatTimestamp( ts: number | Date | undefined, options?: EnvelopeFormatOptions, ): string | undefined { - if (!ts) return undefined; + if (!ts) { + return undefined; + } const resolved = normalizeEnvelopeOptions(options); - if (!resolved.includeTimestamp) return undefined; + if (!resolved.includeTimestamp) { + return undefined; + } const date = ts instanceof Date ? ts : new Date(ts); - if (Number.isNaN(date.getTime())) return undefined; + if (Number.isNaN(date.getTime())) { + return undefined; + } const zone = resolveEnvelopeTimezone(resolved); - if (zone.mode === "utc") return formatUtcTimestamp(date); - if (zone.mode === "local") return formatZonedTimestamp(date); + if (zone.mode === "utc") { + return formatUtcTimestamp(date); + } + if (zone.mode === "local") { + return formatZonedTimestamp(date); + } return formatZonedTimestamp(date, zone.timeZone); } function formatElapsedTime(currentMs: number, previousMs: number): string | undefined { const elapsedMs = currentMs - previousMs; - if (!Number.isFinite(elapsedMs) || elapsedMs < 0) return undefined; + if (!Number.isFinite(elapsedMs) || elapsedMs < 0) { + return undefined; + } const seconds = Math.floor(elapsedMs / 1000); - if (seconds < 60) return `${seconds}s`; + if (seconds < 60) { + return `${seconds}s`; + } const minutes = Math.floor(seconds / 60); - if (minutes < 60) return `${minutes}m`; + if (minutes < 60) { + return `${minutes}m`; + } const hours = Math.floor(minutes / 60); - if (hours < 24) return `${hours}h`; + if (hours < 24) { + return `${hours}h`; + } const days = Math.floor(hours / 24); return `${days}d`; @@ -173,10 +199,16 @@ export function formatAgentEnvelope(params: AgentEnvelopeParams): string { } else if (elapsed) { parts.push(`+${elapsed}`); } - if (params.host?.trim()) parts.push(params.host.trim()); - if (params.ip?.trim()) parts.push(params.ip.trim()); + if (params.host?.trim()) { + parts.push(params.host.trim()); + } + if (params.ip?.trim()) { + parts.push(params.ip.trim()); + } const ts = formatTimestamp(params.timestamp, resolved); - if (ts) parts.push(ts); + if (ts) { + parts.push(ts); + } const header = `[${parts.join(" ")}]`; return `${header} ${params.body}`; } @@ -223,7 +255,9 @@ export function formatInboundFromLabel(params: { const directLabel = params.directLabel.trim(); const directId = params.directId?.trim(); - if (!directId || directId === directLabel) return directLabel; + if (!directId || directId === directLabel) { + return directLabel; + } return `${directLabel} id:${directId}`; } diff --git a/src/auto-reply/group-activation.ts b/src/auto-reply/group-activation.ts index 7dcd2e6967..6da7c04623 100644 --- a/src/auto-reply/group-activation.ts +++ b/src/auto-reply/group-activation.ts @@ -4,8 +4,12 @@ export type GroupActivationMode = "mention" | "always"; export function normalizeGroupActivation(raw?: string | null): GroupActivationMode | undefined { const value = raw?.trim().toLowerCase(); - if (value === "mention") return "mention"; - if (value === "always") return "always"; + if (value === "mention") { + return "mention"; + } + if (value === "always") { + return "always"; + } return undefined; } @@ -13,12 +17,18 @@ export function parseActivationCommand(raw?: string): { hasCommand: boolean; mode?: GroupActivationMode; } { - if (!raw) return { hasCommand: false }; + if (!raw) { + return { hasCommand: false }; + } const trimmed = raw.trim(); - if (!trimmed) return { hasCommand: false }; + if (!trimmed) { + return { hasCommand: false }; + } const normalized = normalizeCommandBody(trimmed); const match = normalized.match(/^\/activation(?:\s+([a-zA-Z]+))?\s*$/i); - if (!match) return { hasCommand: false }; + if (!match) { + return { hasCommand: false }; + } const mode = normalizeGroupActivation(match[1]); return { hasCommand: true, mode }; } diff --git a/src/auto-reply/heartbeat.ts b/src/auto-reply/heartbeat.ts index cca6c6ad10..4f4ef22aa7 100644 --- a/src/auto-reply/heartbeat.ts +++ b/src/auto-reply/heartbeat.ts @@ -20,20 +20,30 @@ export const DEFAULT_HEARTBEAT_ACK_MAX_CHARS = 300; * decide what to do. This function is only for when the file exists but has no content. */ export function isHeartbeatContentEffectivelyEmpty(content: string | undefined | null): boolean { - if (content === undefined || content === null) return false; - if (typeof content !== "string") return false; + if (content === undefined || content === null) { + return false; + } + if (typeof content !== "string") { + return false; + } const lines = content.split("\n"); for (const line of lines) { const trimmed = line.trim(); // Skip empty lines - if (!trimmed) continue; + if (!trimmed) { + continue; + } // Skip markdown header lines (# followed by space or EOL, ## etc) // This intentionally does NOT skip lines like "#TODO" or "#hashtag" which might be content // (Those aren't valid markdown headers - ATX headers require space after #) - if (/^#+(\s|$)/.test(trimmed)) continue; + if (/^#+(\s|$)/.test(trimmed)) { + continue; + } // Skip empty markdown list items like "- [ ]" or "* [ ]" or just "- " - if (/^[-*+]\s*(\[[\sXx]?\]\s*)?$/.test(trimmed)) continue; + if (/^[-*+]\s*(\[[\sXx]?\]\s*)?$/.test(trimmed)) { + continue; + } // Found a non-empty, non-comment line - there's actionable content return false; } @@ -50,10 +60,14 @@ export type StripHeartbeatMode = "heartbeat" | "message"; function stripTokenAtEdges(raw: string): { text: string; didStrip: boolean } { let text = raw.trim(); - if (!text) return { text: "", didStrip: false }; + if (!text) { + return { text: "", didStrip: false }; + } const token = HEARTBEAT_TOKEN; - if (!text.includes(token)) return { text, didStrip: false }; + if (!text.includes(token)) { + return { text, didStrip: false }; + } let didStrip = false; let changed = true; @@ -83,9 +97,13 @@ export function stripHeartbeatToken( raw?: string, opts: { mode?: StripHeartbeatMode; maxAckChars?: number } = {}, ) { - if (!raw) return { shouldSkip: true, text: "", didStrip: false }; + if (!raw) { + return { shouldSkip: true, text: "", didStrip: false }; + } const trimmed = raw.trim(); - if (!trimmed) return { shouldSkip: true, text: "", didStrip: false }; + if (!trimmed) { + return { shouldSkip: true, text: "", didStrip: false }; + } const mode: StripHeartbeatMode = opts.mode ?? "message"; const maxAckCharsRaw = opts.maxAckChars; diff --git a/src/auto-reply/inbound-debounce.ts b/src/auto-reply/inbound-debounce.ts index 3ef9c44faf..bb63b2a9d0 100644 --- a/src/auto-reply/inbound-debounce.ts +++ b/src/auto-reply/inbound-debounce.ts @@ -2,7 +2,9 @@ import type { OpenClawConfig } from "../config/config.js"; import type { InboundDebounceByProvider } from "../config/types.messages.js"; const resolveMs = (value: unknown): number | undefined => { - if (typeof value !== "number" || !Number.isFinite(value)) return undefined; + if (typeof value !== "number" || !Number.isFinite(value)) { + return undefined; + } return Math.max(0, Math.trunc(value)); }; @@ -10,7 +12,9 @@ const resolveChannelOverride = (params: { byChannel?: InboundDebounceByProvider; channel: string; }): number | undefined => { - if (!params.byChannel) return undefined; + if (!params.byChannel) { + return undefined; + } return resolveMs(params.byChannel[params.channel]); }; @@ -50,7 +54,9 @@ export function createInboundDebouncer(params: { clearTimeout(buffer.timeout); buffer.timeout = null; } - if (buffer.items.length === 0) return; + if (buffer.items.length === 0) { + return; + } try { await params.onFlush(buffer.items); } catch (err) { @@ -60,12 +66,16 @@ export function createInboundDebouncer(params: { const flushKey = async (key: string) => { const buffer = buffers.get(key); - if (!buffer) return; + if (!buffer) { + return; + } await flushBuffer(key, buffer); }; const scheduleFlush = (key: string, buffer: DebounceBuffer) => { - if (buffer.timeout) clearTimeout(buffer.timeout); + if (buffer.timeout) { + clearTimeout(buffer.timeout); + } buffer.timeout = setTimeout(() => { void flushBuffer(key, buffer); }, debounceMs); diff --git a/src/auto-reply/media-note.ts b/src/auto-reply/media-note.ts index cd8617c309..a34139fee0 100644 --- a/src/auto-reply/media-note.ts +++ b/src/auto-reply/media-note.ts @@ -27,7 +27,9 @@ export function buildInboundMediaNote(ctx: MsgContext): string | undefined { } if (Array.isArray(ctx.MediaUnderstandingDecisions)) { for (const decision of ctx.MediaUnderstandingDecisions) { - if (decision.outcome !== "success") continue; + if (decision.outcome !== "success") { + continue; + } for (const attachment of decision.attachments) { if (attachment.chosen?.outcome === "success") { suppressed.add(attachment.attachmentIndex); @@ -42,7 +44,9 @@ export function buildInboundMediaNote(ctx: MsgContext): string | undefined { : ctx.MediaPath?.trim() ? [ctx.MediaPath.trim()] : []; - if (paths.length === 0) return undefined; + if (paths.length === 0) { + return undefined; + } const urls = Array.isArray(ctx.MediaUrls) && ctx.MediaUrls.length === paths.length @@ -61,7 +65,9 @@ export function buildInboundMediaNote(ctx: MsgContext): string | undefined { index, })) .filter((entry) => !suppressed.has(entry.index)); - if (entries.length === 0) return undefined; + if (entries.length === 0) { + return undefined; + } if (entries.length === 1) { return formatMediaAttachedLine({ path: entries[0]?.path ?? "", diff --git a/src/auto-reply/model.ts b/src/auto-reply/model.ts index 450a016b69..b330d0a9fb 100644 --- a/src/auto-reply/model.ts +++ b/src/auto-reply/model.ts @@ -11,7 +11,9 @@ export function extractModelDirective( rawProfile?: string; hasDirective: boolean; } { - if (!body) return { cleaned: "", hasDirective: false }; + if (!body) { + return { cleaned: "", hasDirective: false }; + } const modelMatch = body.match( /(?:^|\s)\/model(?=$|\s|:)\s*:?\s*([A-Za-z0-9_.:@-]+(?:\/[A-Za-z0-9_.:@-]+)*)?/i, diff --git a/src/auto-reply/reply.directive.directive-behavior.applies-inline-reasoning-mixed-messages-acks-immediately.e2e.test.ts b/src/auto-reply/reply.directive.directive-behavior.applies-inline-reasoning-mixed-messages-acks-immediately.e2e.test.ts index 9e7e062eb7..165d67a931 100644 --- a/src/auto-reply/reply.directive.directive-behavior.applies-inline-reasoning-mixed-messages-acks-immediately.e2e.test.ts +++ b/src/auto-reply/reply.directive.directive-behavior.applies-inline-reasoning-mixed-messages-acks-immediately.e2e.test.ts @@ -82,7 +82,9 @@ describe("directive behavior", () => { }, { onBlockReply: (payload) => { - if (payload.text) blockReplies.push(payload.text); + if (payload.text) { + blockReplies.push(payload.text); + } }, }, { @@ -125,7 +127,9 @@ describe("directive behavior", () => { }, { onBlockReply: (payload) => { - if (payload.text) blockReplies.push(payload.text); + if (payload.text) { + blockReplies.push(payload.text); + } }, }, { @@ -149,7 +153,9 @@ describe("directive behavior", () => { }, { onBlockReply: (payload) => { - if (payload.text) blockReplies.push(payload.text); + if (payload.text) { + blockReplies.push(payload.text); + } }, }, { diff --git a/src/auto-reply/reply/abort.ts b/src/auto-reply/reply/abort.ts index 050b29e9f9..053f5890b0 100644 --- a/src/auto-reply/reply/abort.ts +++ b/src/auto-reply/reply/abort.ts @@ -24,7 +24,9 @@ const ABORT_TRIGGERS = new Set(["stop", "esc", "abort", "wait", "exit", "interru const ABORT_MEMORY = new Map(); export function isAbortTrigger(text?: string): boolean { - if (!text) return false; + if (!text) { + return false; + } const normalized = text.trim().toLowerCase(); return ABORT_TRIGGERS.has(normalized); } @@ -49,15 +51,21 @@ function resolveSessionEntryForKey( store: Record | undefined, sessionKey: string | undefined, ) { - if (!store || !sessionKey) return {}; + if (!store || !sessionKey) { + return {}; + } const direct = store[sessionKey]; - if (direct) return { entry: direct, key: sessionKey }; + if (direct) { + return { entry: direct, key: sessionKey }; + } return {}; } function resolveAbortTargetKey(ctx: MsgContext): string | undefined { const target = ctx.CommandTargetSessionKey?.trim(); - if (target) return target; + if (target) { + return target; + } const sessionKey = ctx.SessionKey?.trim(); return sessionKey || undefined; } @@ -67,7 +75,9 @@ function normalizeRequesterSessionKey( key: string | undefined, ): string | undefined { const cleaned = key?.trim(); - if (!cleaned) return undefined; + if (!cleaned) { + return undefined; + } const { mainKey, alias } = resolveMainSessionAlias(cfg); return resolveInternalSessionKey({ key: cleaned, alias, mainKey }); } @@ -77,18 +87,26 @@ export function stopSubagentsForRequester(params: { requesterSessionKey?: string; }): { stopped: number } { const requesterKey = normalizeRequesterSessionKey(params.cfg, params.requesterSessionKey); - if (!requesterKey) return { stopped: 0 }; + if (!requesterKey) { + return { stopped: 0 }; + } const runs = listSubagentRunsForRequester(requesterKey); - if (runs.length === 0) return { stopped: 0 }; + if (runs.length === 0) { + return { stopped: 0 }; + } const storeCache = new Map>(); const seenChildKeys = new Set(); let stopped = 0; for (const run of runs) { - if (run.endedAt) continue; + if (run.endedAt) { + continue; + } const childKey = run.childSessionKey?.trim(); - if (!childKey || seenChildKeys.has(childKey)) continue; + if (!childKey || seenChildKeys.has(childKey)) { + continue; + } seenChildKeys.add(childKey); const cleared = clearSessionQueues([childKey]); @@ -130,7 +148,9 @@ export async function tryFastAbortFromMessage(params: { const stripped = isGroup ? stripMentions(raw, ctx, cfg, agentId) : raw; const normalized = normalizeCommandBody(stripped); const abortRequested = normalized === "/stop" || isAbortTrigger(stripped); - if (!abortRequested) return { handled: false, aborted: false }; + if (!abortRequested) { + return { handled: false, aborted: false }; + } const commandAuthorized = ctx.CommandAuthorized; const auth = resolveCommandAuthorization({ @@ -138,7 +158,9 @@ export async function tryFastAbortFromMessage(params: { cfg, commandAuthorized, }); - if (!auth.isAuthorizedSender) return { handled: false, aborted: false }; + if (!auth.isAuthorizedSender) { + return { handled: false, aborted: false }; + } const abortKey = targetKey ?? auth.from ?? auth.to; const requesterSessionKey = targetKey ?? ctx.SessionKey ?? abortKey; @@ -161,7 +183,9 @@ export async function tryFastAbortFromMessage(params: { store[key] = entry; await updateSessionStore(storePath, (nextStore) => { const nextEntry = nextStore[key] ?? entry; - if (!nextEntry) return; + if (!nextEntry) { + return; + } nextEntry.abortedLastRun = true; nextEntry.updatedAt = Date.now(); nextStore[key] = nextEntry; diff --git a/src/auto-reply/reply/agent-runner-execution.ts b/src/auto-reply/reply/agent-runner-execution.ts index 21732f49f8..6db986835f 100644 --- a/src/auto-reply/reply/agent-runner-execution.ts +++ b/src/auto-reply/reply/agent-runner-execution.ts @@ -103,7 +103,9 @@ export async function runAgentTurnWithFallback(params: { params.followupRun.run.reasoningLevel === "stream" && params.opts?.onReasoningStream ); const normalizeStreamingText = (payload: ReplyPayload): { text?: string; skip: boolean } => { - if (!allowPartialStream) return { skip: true }; + if (!allowPartialStream) { + return { skip: true }; + } let text = payload.text; if (!params.isHeartbeat && text?.includes("HEARTBEAT_OK")) { const stripped = stripHeartbeatToken(text, { @@ -121,14 +123,20 @@ export async function runAgentTurnWithFallback(params: { if (isSilentReplyText(text, SILENT_REPLY_TOKEN)) { return { skip: true }; } - if (!text) return { skip: true }; + if (!text) { + return { skip: true }; + } const sanitized = sanitizeUserFacingText(text); - if (!sanitized.trim()) return { skip: true }; + if (!sanitized.trim()) { + return { skip: true }; + } return { text: sanitized, skip: false }; }; const handlePartialForTyping = async (payload: ReplyPayload): Promise => { const { text, skip } = normalizeStreamingText(payload); - if (skip || !text) return undefined; + if (skip || !text) { + return undefined; + } await params.typingSignals.signalTextDelta(text); return text; }; @@ -266,7 +274,9 @@ export async function runAgentTurnWithFallback(params: { params.sessionCtx.Surface, params.sessionCtx.Provider, ); - if (!channel) return "markdown"; + if (!channel) { + return "markdown"; + } return isMarkdownCapableMessageChannel(channel) ? "markdown" : "plain"; })(), bashElevated: params.followupRun.run.bashElevated, @@ -279,7 +289,9 @@ export async function runAgentTurnWithFallback(params: { onPartialReply: allowPartialStream ? async (payload) => { const textForTyping = await handlePartialForTyping(payload); - if (!params.opts?.onPartialReply || textForTyping === undefined) return; + if (!params.opts?.onPartialReply || textForTyping === undefined) { + return; + } await params.opts.onPartialReply({ text: textForTyping, mediaUrls: payload.mediaUrls, @@ -324,7 +336,9 @@ export async function runAgentTurnWithFallback(params: { ? async (payload) => { const { text, skip } = normalizeStreamingText(payload); const hasPayloadMedia = (payload.mediaUrls?.length ?? 0) > 0; - if (skip && !hasPayloadMedia) return; + if (skip && !hasPayloadMedia) { + return; + } const currentMessageId = params.sessionCtx.MessageSidFull ?? params.sessionCtx.MessageSid; const taggedPayload = applyReplyTagsToPayload( @@ -339,7 +353,9 @@ export async function runAgentTurnWithFallback(params: { currentMessageId, ); // Let through payloads with audioAsVoice flag even if empty (need to track it) - if (!isRenderablePayload(taggedPayload) && !payload.audioAsVoice) return; + if (!isRenderablePayload(taggedPayload) && !payload.audioAsVoice) { + return; + } const parsed = parseReplyDirectives(taggedPayload.text ?? "", { currentMessageId, silentToken: SILENT_REPLY_TOKEN, @@ -353,9 +369,12 @@ export async function runAgentTurnWithFallback(params: { !hasRenderableMedia && !payload.audioAsVoice && !parsed.audioAsVoice - ) + ) { return; - if (parsed.isSilent && !hasRenderableMedia) return; + } + if (parsed.isSilent && !hasRenderableMedia) { + return; + } const blockPayload: ReplyPayload = params.applyReplyToMode({ ...taggedPayload, @@ -399,7 +418,9 @@ export async function runAgentTurnWithFallback(params: { // a typing loop that never sees a matching markRunComplete(). Track and drain. const task = (async () => { const { text, skip } = normalizeStreamingText(payload); - if (skip) return; + if (skip) { + return; + } await params.typingSignals.signalTextDelta(text); await onToolResult({ text, diff --git a/src/auto-reply/reply/agent-runner-helpers.ts b/src/auto-reply/reply/agent-runner-helpers.ts index e19d233484..6f3658b743 100644 --- a/src/auto-reply/reply/agent-runner-helpers.ts +++ b/src/auto-reply/reply/agent-runner-helpers.ts @@ -26,7 +26,9 @@ export const createShouldEmitToolResult = (params: { const store = loadSessionStore(params.storePath); const entry = store[params.sessionKey]; const current = normalizeVerboseLevel(String(entry?.verboseLevel ?? "")); - if (current) return current !== "off"; + if (current) { + return current !== "off"; + } } catch { // ignore store read failures } @@ -49,7 +51,9 @@ export const createShouldEmitToolOutput = (params: { const store = loadSessionStore(params.storePath); const entry = store[params.sessionKey]; const current = normalizeVerboseLevel(String(entry?.verboseLevel ?? "")); - if (current) return current === "full"; + if (current) { + return current === "full"; + } } catch { // ignore store read failures } @@ -72,9 +76,15 @@ export const signalTypingIfNeeded = async ( ): Promise => { const shouldSignalTyping = payloads.some((payload) => { const trimmed = payload.text?.trim(); - if (trimmed) return true; - if (payload.mediaUrl) return true; - if (payload.mediaUrls && payload.mediaUrls.length > 0) return true; + if (trimmed) { + return true; + } + if (payload.mediaUrl) { + return true; + } + if (payload.mediaUrls && payload.mediaUrls.length > 0) { + return true; + } return false; }); if (shouldSignalTyping) { diff --git a/src/auto-reply/reply/agent-runner-memory.ts b/src/auto-reply/reply/agent-runner-memory.ts index c139448932..1c2bb26005 100644 --- a/src/auto-reply/reply/agent-runner-memory.ts +++ b/src/auto-reply/reply/agent-runner-memory.ts @@ -39,15 +39,21 @@ export async function runMemoryFlushIfNeeded(params: { isHeartbeat: boolean; }): Promise { const memoryFlushSettings = resolveMemoryFlushSettings(params.cfg); - if (!memoryFlushSettings) return params.sessionEntry; + if (!memoryFlushSettings) { + return params.sessionEntry; + } const memoryFlushWritable = (() => { - if (!params.sessionKey) return true; + if (!params.sessionKey) { + return true; + } const runtime = resolveSandboxRuntimeStatus({ cfg: params.cfg, sessionKey: params.sessionKey, }); - if (!runtime.sandboxed) return true; + if (!runtime.sandboxed) { + return true; + } const sandboxCfg = resolveSandboxConfigForAgent(params.cfg, runtime.agentId); return sandboxCfg.workspaceAccess === "rw"; })(); @@ -69,7 +75,9 @@ export async function runMemoryFlushIfNeeded(params: { softThresholdTokens: memoryFlushSettings.softThresholdTokens, }); - if (!shouldFlushMemory) return params.sessionEntry; + if (!shouldFlushMemory) { + return params.sessionEntry; + } let activeSessionEntry = params.sessionEntry; const activeSessionStore = params.sessionStore; diff --git a/src/auto-reply/reply/agent-runner-payloads.ts b/src/auto-reply/reply/agent-runner-payloads.ts index 60d6fa763a..614c1d7d04 100644 --- a/src/auto-reply/reply/agent-runner-payloads.ts +++ b/src/auto-reply/reply/agent-runner-payloads.ts @@ -52,7 +52,9 @@ export function buildReplyPayloads(params: { logVerbose("Stripped stray HEARTBEAT_OK token from reply"); } const hasMedia = Boolean(payload.mediaUrl) || (payload.mediaUrls?.length ?? 0) > 0; - if (stripped.shouldSkip && !hasMedia) return []; + if (stripped.shouldSkip && !hasMedia) { + return []; + } return [{ ...payload, text: stripped.text }]; }); diff --git a/src/auto-reply/reply/agent-runner-utils.ts b/src/auto-reply/reply/agent-runner-utils.ts index 143e54e28f..164e6dfb8e 100644 --- a/src/auto-reply/reply/agent-runner-utils.ts +++ b/src/auto-reply/reply/agent-runner-utils.ts @@ -20,9 +20,13 @@ export function buildThreadingToolContext(params: { hasRepliedRef: { value: boolean } | undefined; }): ChannelThreadingToolContext { const { sessionCtx, config, hasRepliedRef } = params; - if (!config) return {}; + if (!config) { + return {}; + } const rawProvider = sessionCtx.Provider?.trim().toLowerCase(); - if (!rawProvider) return {}; + if (!rawProvider) { + return {}; + } const provider = normalizeChannelId(rawProvider) ?? normalizeAnyChannelId(rawProvider); // Fallback for unrecognized/plugin channels (e.g., BlueBubbles before plugin registry init) const dock = provider ? getChannelDock(provider) : undefined; @@ -78,10 +82,14 @@ export const formatResponseUsageLine = (params: { }; }): string | null => { const usage = params.usage; - if (!usage) return null; + if (!usage) { + return null; + } const input = usage.input; const output = usage.output; - if (typeof input !== "number" && typeof output !== "number") return null; + if (typeof input !== "number" && typeof output !== "number") { + return null; + } const inputLabel = typeof input === "number" ? formatTokenCount(input) : "?"; const outputLabel = typeof output === "number" ? formatTokenCount(output) : "?"; const cost = @@ -109,7 +117,9 @@ export const appendUsageLine = (payloads: ReplyPayload[], line: string): ReplyPa break; } } - if (index === -1) return [...payloads, { text: line }]; + if (index === -1) { + return [...payloads, { text: line }]; + } const existing = payloads[index]; const existingText = existing.text ?? ""; const separator = existingText.endsWith("\n") ? "" : "\n"; diff --git a/src/auto-reply/reply/agent-runner.claude-cli.test.ts b/src/auto-reply/reply/agent-runner.claude-cli.test.ts index 546af159f4..8b624f947a 100644 --- a/src/auto-reply/reply/agent-runner.claude-cli.test.ts +++ b/src/auto-reply/reply/agent-runner.claude-cli.test.ts @@ -106,10 +106,16 @@ describe("runReplyAgent claude-cli routing", () => { const randomSpy = vi.spyOn(crypto, "randomUUID").mockReturnValue("run-1"); const lifecyclePhases: string[] = []; const unsubscribe = onAgentEvent((evt) => { - if (evt.runId !== "run-1") return; - if (evt.stream !== "lifecycle") return; + if (evt.runId !== "run-1") { + return; + } + if (evt.stream !== "lifecycle") { + return; + } const phase = evt.data?.phase; - if (typeof phase === "string") lifecyclePhases.push(phase); + if (typeof phase === "string") { + lifecyclePhases.push(phase); + } }); runCliAgentMock.mockResolvedValueOnce({ payloads: [{ text: "ok" }], diff --git a/src/auto-reply/reply/agent-runner.ts b/src/auto-reply/reply/agent-runner.ts index 227e6f17ea..c6ead849c2 100644 --- a/src/auto-reply/reply/agent-runner.ts +++ b/src/auto-reply/reply/agent-runner.ts @@ -236,9 +236,13 @@ export async function runReplyAgent(params: { buildLogMessage, cleanupTranscripts, }: SessionResetOptions): Promise => { - if (!sessionKey || !activeSessionStore || !storePath) return false; + if (!sessionKey || !activeSessionStore || !storePath) { + return false; + } const prevEntry = activeSessionStore[sessionKey] ?? activeSessionEntry; - if (!prevEntry) return false; + if (!prevEntry) { + return false; + } const prevSessionId = cleanupTranscripts ? prevEntry.sessionId : undefined; const nextSessionId = crypto.randomUUID(); const nextEntry: SessionEntry = { @@ -273,7 +277,9 @@ export async function runReplyAgent(params: { if (cleanupTranscripts && prevSessionId) { const transcriptCandidates = new Set(); const resolved = resolveSessionFilePath(prevSessionId, prevEntry, { agentId }); - if (resolved) transcriptCandidates.add(resolved); + if (resolved) { + transcriptCandidates.add(resolved); + } transcriptCandidates.add(resolveSessionTranscriptPath(prevSessionId, agentId)); for (const candidate of transcriptCandidates) { try { @@ -391,8 +397,9 @@ export async function runReplyAgent(params: { // Drain any late tool/block deliveries before deciding there's "nothing to send". // Otherwise, a late typing trigger (e.g. from a tool callback) can outlive the run and // keep the typing indicator stuck. - if (payloadArray.length === 0) + if (payloadArray.length === 0) { return finalizeWithFollowup(undefined, queueKey, runFollowupTurn); + } const payloadResult = buildReplyPayloads({ payloads: payloadArray, @@ -413,8 +420,9 @@ export async function runReplyAgent(params: { const { replyPayloads } = payloadResult; didLogHeartbeatStrip = payloadResult.didLogHeartbeatStrip; - if (replyPayloads.length === 0) + if (replyPayloads.length === 0) { return finalizeWithFollowup(undefined, queueKey, runFollowupTurn); + } await signalTypingIfNeeded(replyPayloads, typingSignals); @@ -477,7 +485,9 @@ export async function runReplyAgent(params: { if (formatted && responseUsageMode === "full" && sessionKey) { formatted = `${formatted} · session ${sessionKey}`; } - if (formatted) responseUsageLine = formatted; + if (formatted) { + responseUsageLine = formatted; + } } // If verbose is enabled and this is a new session, prepend a session hint. diff --git a/src/auto-reply/reply/bash-command.ts b/src/auto-reply/reply/bash-command.ts index fb2e0c8f26..f57afeb616 100644 --- a/src/auto-reply/reply/bash-command.ts +++ b/src/auto-reply/reply/bash-command.ts @@ -35,19 +35,25 @@ let activeJob: ActiveBashJob | null = null; function resolveForegroundMs(cfg: OpenClawConfig): number { const raw = cfg.commands?.bashForegroundMs; - if (typeof raw !== "number" || Number.isNaN(raw)) return DEFAULT_FOREGROUND_MS; + if (typeof raw !== "number" || Number.isNaN(raw)) { + return DEFAULT_FOREGROUND_MS; + } return clampInt(raw, 0, MAX_FOREGROUND_MS); } function formatSessionSnippet(sessionId: string) { const trimmed = sessionId.trim(); - if (trimmed.length <= 12) return trimmed; + if (trimmed.length <= 12) { + return trimmed; + } return `${trimmed.slice(0, 8)}…`; } function formatOutputBlock(text: string) { const trimmed = text.trim(); - if (!trimmed) return "(no output)"; + if (!trimmed) { + return "(no output)"; + } return `\`\`\`txt\n${trimmed}\n\`\`\``; } @@ -56,7 +62,9 @@ function parseBashRequest(raw: string): BashRequest | null { let restSource = ""; if (trimmed.toLowerCase().startsWith("/bash")) { const match = trimmed.match(/^\/bash(?:\s*:\s*|\s+|$)([\s\S]*)$/i); - if (!match) return null; + if (!match) { + return null; + } restSource = match[1] ?? ""; } else if (trimmed.startsWith("!")) { restSource = trimmed.slice(1); @@ -68,7 +76,9 @@ function parseBashRequest(raw: string): BashRequest | null { } const rest = restSource.trimStart(); - if (!rest) return { action: "help" }; + if (!rest) { + return { action: "help" }; + } const tokenMatch = rest.match(/^(\S+)(?:\s+([\s\S]+))?$/); const token = tokenMatch?.[1]?.trim() ?? ""; const remainder = tokenMatch?.[2]?.trim() ?? ""; @@ -100,17 +110,27 @@ function resolveRawCommandBody(params: { function getScopedSession(sessionId: string) { const running = getSession(sessionId); - if (running && running.scopeKey === CHAT_BASH_SCOPE_KEY) return { running }; + if (running && running.scopeKey === CHAT_BASH_SCOPE_KEY) { + return { running }; + } const finished = getFinishedSession(sessionId); - if (finished && finished.scopeKey === CHAT_BASH_SCOPE_KEY) return { finished }; + if (finished && finished.scopeKey === CHAT_BASH_SCOPE_KEY) { + return { finished }; + } return {}; } function ensureActiveJobState() { - if (!activeJob) return null; - if (activeJob.state === "starting") return activeJob; + if (!activeJob) { + return null; + } + if (activeJob.state === "starting") { + return activeJob; + } const { running, finished } = getScopedSession(activeJob.sessionId); - if (running) return activeJob; + if (running) { + return activeJob; + } if (finished) { activeJob = null; return null; @@ -120,12 +140,20 @@ function ensureActiveJobState() { } function attachActiveWatcher(sessionId: string) { - if (!activeJob || activeJob.state !== "running") return; - if (activeJob.sessionId !== sessionId) return; - if (activeJob.watcherAttached) return; + if (!activeJob || activeJob.state !== "running") { + return; + } + if (activeJob.sessionId !== sessionId) { + return; + } + if (activeJob.watcherAttached) { + return; + } const { running } = getScopedSession(sessionId); const child = running?.child; - if (!child) return; + if (!child) { + return; + } activeJob.watcherAttached = true; child.once("close", () => { if (activeJob?.state === "running" && activeJob.sessionId === sessionId) { @@ -317,7 +345,9 @@ export async function handleBashChatCommand(params: { } const commandText = request.command.trim(); - if (!commandText) return buildUsageReply(); + if (!commandText) { + return buildUsageReply(); + } activeJob = { state: "starting", diff --git a/src/auto-reply/reply/block-reply-coalescer.ts b/src/auto-reply/reply/block-reply-coalescer.ts index d18a85881c..89e700f91f 100644 --- a/src/auto-reply/reply/block-reply-coalescer.ts +++ b/src/auto-reply/reply/block-reply-coalescer.ts @@ -25,7 +25,9 @@ export function createBlockReplyCoalescer(params: { let idleTimer: NodeJS.Timeout | undefined; const clearIdleTimer = () => { - if (!idleTimer) return; + if (!idleTimer) { + return; + } clearTimeout(idleTimer); idleTimer = undefined; }; @@ -37,7 +39,9 @@ export function createBlockReplyCoalescer(params: { }; const scheduleIdleFlush = () => { - if (idleMs <= 0) return; + if (idleMs <= 0) { + return; + } clearIdleTimer(); idleTimer = setTimeout(() => { void flush({ force: false }); @@ -50,7 +54,9 @@ export function createBlockReplyCoalescer(params: { resetBuffer(); return; } - if (!bufferText) return; + if (!bufferText) { + return; + } if (!options?.force && bufferText.length < minChars) { scheduleIdleFlush(); return; @@ -65,7 +71,9 @@ export function createBlockReplyCoalescer(params: { }; const enqueue = (payload: ReplyPayload) => { - if (shouldAbort()) return; + if (shouldAbort()) { + return; + } const hasMedia = Boolean(payload.mediaUrl) || (payload.mediaUrls?.length ?? 0) > 0; const text = payload.text ?? ""; const hasText = text.trim().length > 0; @@ -74,7 +82,9 @@ export function createBlockReplyCoalescer(params: { void onFlush(payload); return; } - if (!hasText) return; + if (!hasText) { + return; + } if ( bufferText && diff --git a/src/auto-reply/reply/block-reply-pipeline.ts b/src/auto-reply/reply/block-reply-pipeline.ts index 00e08acba4..e6ed2a056f 100644 --- a/src/auto-reply/reply/block-reply-pipeline.ts +++ b/src/auto-reply/reply/block-reply-pipeline.ts @@ -53,7 +53,9 @@ const withTimeout = async ( timeoutMs: number, timeoutError: Error, ): Promise => { - if (!timeoutMs || timeoutMs <= 0) return promise; + if (!timeoutMs || timeoutMs <= 0) { + return promise; + } let timer: NodeJS.Timeout | undefined; const timeoutPromise = new Promise((_, reject) => { timer = setTimeout(() => reject(timeoutError), timeoutMs); @@ -61,7 +63,9 @@ const withTimeout = async ( try { return await Promise.race([promise, timeoutPromise]); } finally { - if (timer) clearTimeout(timer); + if (timer) { + clearTimeout(timer); + } } }; @@ -87,20 +91,28 @@ export function createBlockReplyPipeline(params: { let didLogTimeout = false; const sendPayload = (payload: ReplyPayload, skipSeen?: boolean) => { - if (aborted) return; + if (aborted) { + return; + } const payloadKey = createBlockReplyPayloadKey(payload); if (!skipSeen) { - if (seenKeys.has(payloadKey)) return; + if (seenKeys.has(payloadKey)) { + return; + } seenKeys.add(payloadKey); } - if (sentKeys.has(payloadKey) || pendingKeys.has(payloadKey)) return; + if (sentKeys.has(payloadKey) || pendingKeys.has(payloadKey)) { + return; + } pendingKeys.add(payloadKey); const timeoutError = new Error(`block reply delivery timed out after ${timeoutMs}ms`); const abortController = new AbortController(); sendChain = sendChain .then(async () => { - if (aborted) return false; + if (aborted) { + return false; + } await withTimeout( onBlockReply(payload, { abortSignal: abortController.signal, @@ -112,7 +124,9 @@ export function createBlockReplyPipeline(params: { return true; }) .then((didSend) => { - if (!didSend) return; + if (!didSend) { + return; + } sentKeys.add(payloadKey); didStream = true; }) @@ -148,7 +162,9 @@ export function createBlockReplyPipeline(params: { const bufferPayload = (payload: ReplyPayload) => { buffer?.onEnqueue?.(payload); - if (!buffer?.shouldBuffer(payload)) return false; + if (!buffer?.shouldBuffer(payload)) { + return false; + } const payloadKey = createBlockReplyPayloadKey(payload); if ( seenKeys.has(payloadKey) || @@ -165,7 +181,9 @@ export function createBlockReplyPipeline(params: { }; const flushBuffered = () => { - if (!bufferedPayloads.length) return; + if (!bufferedPayloads.length) { + return; + } for (const payload of bufferedPayloads) { const finalPayload = buffer?.finalize?.(payload) ?? payload; sendPayload(finalPayload, true); @@ -175,8 +193,12 @@ export function createBlockReplyPipeline(params: { }; const enqueue = (payload: ReplyPayload) => { - if (aborted) return; - if (bufferPayload(payload)) return; + if (aborted) { + return; + } + if (bufferPayload(payload)) { + return; + } const hasMedia = Boolean(payload.mediaUrl) || (payload.mediaUrls?.length ?? 0) > 0; if (hasMedia) { void coalescer?.flush({ force: true }); diff --git a/src/auto-reply/reply/block-streaming.ts b/src/auto-reply/reply/block-streaming.ts index 74e6c51cca..b9e9392716 100644 --- a/src/auto-reply/reply/block-streaming.ts +++ b/src/auto-reply/reply/block-streaming.ts @@ -16,7 +16,9 @@ const getBlockChunkProviders = () => new Set([...listDeliverableMessageChannels(), INTERNAL_MESSAGE_CHANNEL]); function normalizeChunkProvider(provider?: string): TextChunkProvider | undefined { - if (!provider) return undefined; + if (!provider) { + return undefined; + } const cleaned = provider.trim().toLowerCase(); return getBlockChunkProviders().has(cleaned as TextChunkProvider) ? (cleaned as TextChunkProvider) @@ -34,9 +36,13 @@ function resolveProviderBlockStreamingCoalesce(params: { accountId?: string | null; }): BlockStreamingCoalesceConfig | undefined { const { cfg, providerKey, accountId } = params; - if (!cfg || !providerKey) return undefined; + if (!cfg || !providerKey) { + return undefined; + } const providerCfg = (cfg as Record)[providerKey]; - if (!providerCfg || typeof providerCfg !== "object") return undefined; + if (!providerCfg || typeof providerCfg !== "object") { + return undefined; + } const normalizedAccountId = normalizeAccountId(accountId); const typed = providerCfg as ProviderBlockStreamingConfig; const accountCfg = typed.accounts?.[normalizedAccountId]; diff --git a/src/auto-reply/reply/body.ts b/src/auto-reply/reply/body.ts index ffac41987e..dcc958eb05 100644 --- a/src/auto-reply/reply/body.ts +++ b/src/auto-reply/reply/body.ts @@ -26,7 +26,9 @@ export async function applySessionHints(params: { const sessionKey = params.sessionKey; await updateSessionStore(params.storePath, (store) => { const entry = store[sessionKey] ?? params.sessionEntry; - if (!entry) return; + if (!entry) { + return; + } store[sessionKey] = { ...entry, abortedLastRun: false, diff --git a/src/auto-reply/reply/commands-allowlist.ts b/src/auto-reply/reply/commands-allowlist.ts index 1ff4de9604..fd4f371319 100644 --- a/src/auto-reply/reply/commands-allowlist.ts +++ b/src/auto-reply/reply/commands-allowlist.ts @@ -54,9 +54,13 @@ const SCOPES = new Set(["dm", "group", "all"]); function parseAllowlistCommand(raw: string): AllowlistCommand | null { const trimmed = raw.trim(); - if (!trimmed.toLowerCase().startsWith("/allowlist")) return null; + if (!trimmed.toLowerCase().startsWith("/allowlist")) { + return null; + } const rest = trimmed.slice("/allowlist".length).trim(); - if (!rest) return { action: "list", scope: "dm" }; + if (!rest) { + return { action: "list", scope: "dm" }; + } const tokens = rest.split(/\s+/); let action: AllowlistAction = "list"; @@ -107,11 +111,15 @@ function parseAllowlistCommand(raw: string): AllowlistCommand | null { const key = kv[0]?.trim().toLowerCase(); const value = kv[1]?.trim(); if (key === "channel") { - if (value) channel = value; + if (value) { + channel = value; + } continue; } if (key === "account") { - if (value) account = value; + if (value) { + account = value; + } continue; } if (key === "scope" && value && SCOPES.has(value.toLowerCase() as AllowlistScope)) { @@ -151,7 +159,9 @@ function normalizeAllowFrom(params: { } function formatEntryList(entries: string[], resolved?: Map): string { - if (entries.length === 0) return "(none)"; + if (entries.length === 0) { + return "(none)"; + } return entries .map((entry) => { const name = resolved?.get(entry); @@ -185,7 +195,9 @@ function resolveAccountTarget( function getNestedValue(root: Record, path: string[]): unknown { let current: unknown = root; for (const key of path) { - if (!current || typeof current !== "object") return undefined; + if (!current || typeof current !== "object") { + return undefined; + } current = (current as Record)[key]; } return current; @@ -207,7 +219,9 @@ function ensureNestedObject( } function setNestedValue(root: Record, path: string[], value: unknown) { - if (path.length === 0) return; + if (path.length === 0) { + return; + } if (path.length === 1) { root[path[0]] = value; return; @@ -217,13 +231,17 @@ function setNestedValue(root: Record, path: string[], value: un } function deleteNestedValue(root: Record, path: string[]) { - if (path.length === 0) return; + if (path.length === 0) { + return; + } if (path.length === 1) { delete root[path[0]]; return; } const parent = getNestedValue(root, path.slice(0, -1)); - if (!parent || typeof parent !== "object") return; + if (!parent || typeof parent !== "object") { + return; + } delete (parent as Record)[path[path.length - 1]]; } @@ -231,9 +249,13 @@ function resolveChannelAllowFromPaths( channelId: ChannelId, scope: AllowlistScope, ): string[] | null { - if (scope === "all") return null; + if (scope === "all") { + return null; + } if (scope === "dm") { - if (channelId === "slack" || channelId === "discord") return ["dm", "allowFrom"]; + if (channelId === "slack" || channelId === "discord") { + return ["dm", "allowFrom"]; + } if ( channelId === "telegram" || channelId === "whatsapp" || @@ -265,11 +287,15 @@ async function resolveSlackNames(params: { }) { const account = resolveSlackAccount({ cfg: params.cfg, accountId: params.accountId }); const token = account.config.userToken?.trim() || account.botToken?.trim(); - if (!token) return new Map(); + if (!token) { + return new Map(); + } const resolved = await resolveSlackUserAllowlist({ token, entries: params.entries }); const map = new Map(); for (const entry of resolved) { - if (entry.resolved && entry.name) map.set(entry.input, entry.name); + if (entry.resolved && entry.name) { + map.set(entry.input, entry.name); + } } return map; } @@ -281,19 +307,27 @@ async function resolveDiscordNames(params: { }) { const account = resolveDiscordAccount({ cfg: params.cfg, accountId: params.accountId }); const token = account.token?.trim(); - if (!token) return new Map(); + if (!token) { + return new Map(); + } const resolved = await resolveDiscordUserAllowlist({ token, entries: params.entries }); const map = new Map(); for (const entry of resolved) { - if (entry.resolved && entry.name) map.set(entry.input, entry.name); + if (entry.resolved && entry.name) { + map.set(entry.input, entry.name); + } } return map; } export const handleAllowlistCommand: CommandHandler = async (params, allowTextCommands) => { - if (!allowTextCommands) return null; + if (!allowTextCommands) { + return null; + } const parsed = parseAllowlistCommand(params.command.commandBodyNormalized); - if (!parsed) return null; + if (!parsed) { + return null; + } if (parsed.action === "error") { return { shouldContinue: false, reply: { text: `⚠️ ${parsed.message}` } }; } @@ -444,8 +478,12 @@ export const handleAllowlistCommand: CommandHandler = async (params, allowTextCo const lines: string[] = ["🧾 Allowlist"]; lines.push(`Channel: ${channelId}${accountId ? ` (account ${accountId})` : ""}`); - if (dmPolicy) lines.push(`DM policy: ${dmPolicy}`); - if (groupPolicy) lines.push(`Group policy: ${groupPolicy}`); + if (dmPolicy) { + lines.push(`DM policy: ${dmPolicy}`); + } + if (groupPolicy) { + lines.push(`Group policy: ${groupPolicy}`); + } const showDm = scope === "dm" || scope === "all"; const showGroup = scope === "group" || scope === "all"; diff --git a/src/auto-reply/reply/commands-approve.ts b/src/auto-reply/reply/commands-approve.ts index a34e4b31cf..35f3400c75 100644 --- a/src/auto-reply/reply/commands-approve.ts +++ b/src/auto-reply/reply/commands-approve.ts @@ -24,7 +24,9 @@ type ParsedApproveCommand = function parseApproveCommand(raw: string): ParsedApproveCommand | null { const trimmed = raw.trim(); - if (!trimmed.toLowerCase().startsWith(COMMAND)) return null; + if (!trimmed.toLowerCase().startsWith(COMMAND)) { + return null; + } const rest = trimmed.slice(COMMAND.length).trim(); if (!rest) { return { ok: false, error: "Usage: /approve allow-once|allow-always|deny" }; @@ -61,10 +63,14 @@ function buildResolvedByLabel(params: Parameters[0]): string { } export const handleApproveCommand: CommandHandler = async (params, allowTextCommands) => { - if (!allowTextCommands) return null; + if (!allowTextCommands) { + return null; + } const normalized = params.command.commandBodyNormalized; const parsed = parseApproveCommand(normalized); - if (!parsed) return null; + if (!parsed) { + return null; + } if (!params.command.isAuthorizedSender) { logVerbose( `Ignoring /approve from unauthorized sender: ${params.command.senderId || ""}`, diff --git a/src/auto-reply/reply/commands-bash.ts b/src/auto-reply/reply/commands-bash.ts index 4ef36b7210..de884241e6 100644 --- a/src/auto-reply/reply/commands-bash.ts +++ b/src/auto-reply/reply/commands-bash.ts @@ -3,7 +3,9 @@ import { handleBashChatCommand } from "./bash-command.js"; import type { CommandHandler } from "./commands-types.js"; export const handleBashCommand: CommandHandler = async (params, allowTextCommands) => { - if (!allowTextCommands) return null; + if (!allowTextCommands) { + return null; + } const { command } = params; const bashSlashRequested = command.commandBodyNormalized === "/bash" || command.commandBodyNormalized.startsWith("/bash "); diff --git a/src/auto-reply/reply/commands-compact.ts b/src/auto-reply/reply/commands-compact.ts index fe0814e6fa..d9b63d4fbd 100644 --- a/src/auto-reply/reply/commands-compact.ts +++ b/src/auto-reply/reply/commands-compact.ts @@ -25,12 +25,18 @@ function extractCompactInstructions(params: { ? stripMentions(raw, params.ctx, params.cfg, params.agentId) : raw; const trimmed = stripped.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } const lowered = trimmed.toLowerCase(); const prefix = lowered.startsWith("/compact") ? "/compact" : null; - if (!prefix) return undefined; + if (!prefix) { + return undefined; + } let rest = trimmed.slice(prefix.length).trimStart(); - if (rest.startsWith(":")) rest = rest.slice(1).trimStart(); + if (rest.startsWith(":")) { + rest = rest.slice(1).trimStart(); + } return rest.length ? rest : undefined; } @@ -38,7 +44,9 @@ export const handleCompactCommand: CommandHandler = async (params) => { const compactRequested = params.command.commandBodyNormalized === "/compact" || params.command.commandBodyNormalized.startsWith("/compact "); - if (!compactRequested) return null; + if (!compactRequested) { + return null; + } if (!params.command.isAuthorizedSender) { logVerbose( `Ignoring /compact from unauthorized sender: ${params.command.senderId || ""}`, diff --git a/src/auto-reply/reply/commands-config.ts b/src/auto-reply/reply/commands-config.ts index 4090f12812..c81be681cf 100644 --- a/src/auto-reply/reply/commands-config.ts +++ b/src/auto-reply/reply/commands-config.ts @@ -23,9 +23,13 @@ import { parseConfigCommand } from "./config-commands.js"; import { parseDebugCommand } from "./debug-commands.js"; export const handleConfigCommand: CommandHandler = async (params, allowTextCommands) => { - if (!allowTextCommands) return null; + if (!allowTextCommands) { + return null; + } const configCommand = parseConfigCommand(params.command.commandBodyNormalized); - if (!configCommand) return null; + if (!configCommand) { + return null; + } if (!params.command.isAuthorizedSender) { logVerbose( `Ignoring /config from unauthorized sender: ${params.command.senderId || ""}`, @@ -173,9 +177,13 @@ export const handleConfigCommand: CommandHandler = async (params, allowTextComma }; export const handleDebugCommand: CommandHandler = async (params, allowTextCommands) => { - if (!allowTextCommands) return null; + if (!allowTextCommands) { + return null; + } const debugCommand = parseDebugCommand(params.command.commandBodyNormalized); - if (!debugCommand) return null; + if (!debugCommand) { + return null; + } if (!params.command.isAuthorizedSender) { logVerbose( `Ignoring /debug from unauthorized sender: ${params.command.senderId || ""}`, diff --git a/src/auto-reply/reply/commands-context-report.ts b/src/auto-reply/reply/commands-context-report.ts index 5319ec3ea5..20968f0941 100644 --- a/src/auto-reply/reply/commands-context-report.ts +++ b/src/auto-reply/reply/commands-context-report.ts @@ -29,8 +29,12 @@ function formatCharsAndTokens(chars: number): string { } function parseContextArgs(commandBodyNormalized: string): string { - if (commandBodyNormalized === "/context") return ""; - if (commandBodyNormalized.startsWith("/context ")) return commandBodyNormalized.slice(8).trim(); + if (commandBodyNormalized === "/context") { + return ""; + } + if (commandBodyNormalized.startsWith("/context ")) { + return commandBodyNormalized.slice(8).trim(); + } return ""; } @@ -49,7 +53,9 @@ async function resolveContextReport( params: HandleCommandsParams, ): Promise { const existing = params.sessionEntry?.systemPromptReport; - if (existing && existing.source === "run") return existing; + if (existing && existing.source === "run") { + return existing; + } const workspaceDir = params.workspaceDir; const bootstrapMaxChars = resolveBootstrapMaxChars(params.cfg); diff --git a/src/auto-reply/reply/commands-core.ts b/src/auto-reply/reply/commands-core.ts index a54f90b2be..27fdad40b4 100644 --- a/src/auto-reply/reply/commands-core.ts +++ b/src/auto-reply/reply/commands-core.ts @@ -110,7 +110,9 @@ export async function handleCommands(params: HandleCommandsParams): Promise { - if (!allowTextCommands) return null; - if (params.command.commandBodyNormalized !== "/help") return null; + if (!allowTextCommands) { + return null; + } + if (params.command.commandBodyNormalized !== "/help") { + return null; + } if (!params.command.isAuthorizedSender) { logVerbose( `Ignoring /help from unauthorized sender: ${params.command.senderId || ""}`, @@ -25,8 +29,12 @@ export const handleHelpCommand: CommandHandler = async (params, allowTextCommand }; export const handleCommandsListCommand: CommandHandler = async (params, allowTextCommands) => { - if (!allowTextCommands) return null; - if (params.command.commandBodyNormalized !== "/commands") return null; + if (!allowTextCommands) { + return null; + } + if (params.command.commandBodyNormalized !== "/commands") { + return null; + } if (!params.command.isAuthorizedSender) { logVerbose( `Ignoring /commands from unauthorized sender: ${params.command.senderId || ""}`, @@ -108,10 +116,14 @@ export function buildCommandsPaginationKeyboard( } export const handleStatusCommand: CommandHandler = async (params, allowTextCommands) => { - if (!allowTextCommands) return null; + if (!allowTextCommands) { + return null; + } const statusRequested = params.directives.hasStatusDirective || params.command.commandBodyNormalized === "/status"; - if (!statusRequested) return null; + if (!statusRequested) { + return null; + } if (!params.command.isAuthorizedSender) { logVerbose( `Ignoring /status from unauthorized sender: ${params.command.senderId || ""}`, @@ -140,9 +152,13 @@ export const handleStatusCommand: CommandHandler = async (params, allowTextComma }; export const handleContextCommand: CommandHandler = async (params, allowTextCommands) => { - if (!allowTextCommands) return null; + if (!allowTextCommands) { + return null; + } const normalized = params.command.commandBodyNormalized; - if (normalized !== "/context" && !normalized.startsWith("/context ")) return null; + if (normalized !== "/context" && !normalized.startsWith("/context ")) { + return null; + } if (!params.command.isAuthorizedSender) { logVerbose( `Ignoring /context from unauthorized sender: ${params.command.senderId || ""}`, @@ -153,8 +169,12 @@ export const handleContextCommand: CommandHandler = async (params, allowTextComm }; export const handleWhoamiCommand: CommandHandler = async (params, allowTextCommands) => { - if (!allowTextCommands) return null; - if (params.command.commandBodyNormalized !== "/whoami") return null; + if (!allowTextCommands) { + return null; + } + if (params.command.commandBodyNormalized !== "/whoami") { + return null; + } if (!params.command.isAuthorizedSender) { logVerbose( `Ignoring /whoami from unauthorized sender: ${params.command.senderId || ""}`, @@ -164,7 +184,9 @@ export const handleWhoamiCommand: CommandHandler = async (params, allowTextComma const senderId = params.ctx.SenderId ?? ""; const senderUsername = params.ctx.SenderUsername ?? ""; const lines = ["🧭 Identity", `Channel: ${params.command.channel}`]; - if (senderId) lines.push(`User id: ${senderId}`); + if (senderId) { + lines.push(`User id: ${senderId}`); + } if (senderUsername) { const handle = senderUsername.startsWith("@") ? senderUsername : `@${senderUsername}`; lines.push(`Username: ${handle}`); diff --git a/src/auto-reply/reply/commands-models.ts b/src/auto-reply/reply/commands-models.ts index 87d0bfd68f..0759c881f3 100644 --- a/src/auto-reply/reply/commands-models.ts +++ b/src/auto-reply/reply/commands-models.ts @@ -42,12 +42,16 @@ function parseModelsArgs(raw: string): { } if (lower.startsWith("page=")) { const value = Number.parseInt(lower.slice("page=".length), 10); - if (Number.isFinite(value) && value > 0) page = value; + if (Number.isFinite(value) && value > 0) { + page = value; + } continue; } if (/^[0-9]+$/.test(lower)) { const value = Number.parseInt(lower, 10); - if (Number.isFinite(value) && value > 0) page = value; + if (Number.isFinite(value) && value > 0) { + page = value; + } } } @@ -57,7 +61,9 @@ function parseModelsArgs(raw: string): { if (lower.startsWith("limit=") || lower.startsWith("size=")) { const rawValue = lower.slice(lower.indexOf("=") + 1); const value = Number.parseInt(rawValue, 10); - if (Number.isFinite(value) && value > 0) pageSize = Math.min(PAGE_SIZE_MAX, value); + if (Number.isFinite(value) && value > 0) { + pageSize = Math.min(PAGE_SIZE_MAX, value); + } } } @@ -74,7 +80,9 @@ export async function resolveModelsCommandReply(params: { commandBodyNormalized: string; }): Promise { const body = params.commandBodyNormalized.trim(); - if (!body.startsWith("/models")) return null; + if (!body.startsWith("/models")) { + return null; + } const argText = body.replace(/^\/models\b/i, "").trim(); const { provider, page, pageSize, all } = parseModelsArgs(argText); @@ -108,13 +116,17 @@ export async function resolveModelsCommandReply(params: { const addRawModelRef = (raw?: string) => { const trimmed = raw?.trim(); - if (!trimmed) return; + if (!trimmed) { + return; + } const resolved = resolveModelRefFromString({ raw: trimmed, defaultProvider: resolvedDefault.provider, aliasIndex, }); - if (!resolved) return; + if (!resolved) { + return; + } add(resolved.ref.provider, resolved.ref.model); }; @@ -232,12 +244,16 @@ export async function resolveModelsCommandReply(params: { } export const handleModelsCommand: CommandHandler = async (params, allowTextCommands) => { - if (!allowTextCommands) return null; + if (!allowTextCommands) { + return null; + } const reply = await resolveModelsCommandReply({ cfg: params.cfg, commandBodyNormalized: params.command.commandBodyNormalized, }); - if (!reply) return null; + if (!reply) { + return null; + } return { reply, shouldContinue: false }; }; diff --git a/src/auto-reply/reply/commands-plugin.ts b/src/auto-reply/reply/commands-plugin.ts index 3b21a6aa00..933576aad2 100644 --- a/src/auto-reply/reply/commands-plugin.ts +++ b/src/auto-reply/reply/commands-plugin.ts @@ -19,11 +19,15 @@ export const handlePluginCommand: CommandHandler = async ( ): Promise => { const { command, cfg } = params; - if (!allowTextCommands) return null; + if (!allowTextCommands) { + return null; + } // Try to match a plugin command const match = matchPluginCommand(command.commandBodyNormalized); - if (!match) return null; + if (!match) { + return null; + } // Execute the plugin command (always returns a result) const result = await executePluginCommand({ diff --git a/src/auto-reply/reply/commands-session.ts b/src/auto-reply/reply/commands-session.ts index 15d762f4f8..ff361a6a41 100644 --- a/src/auto-reply/reply/commands-session.ts +++ b/src/auto-reply/reply/commands-session.ts @@ -22,9 +22,13 @@ function resolveSessionEntryForKey( store: Record | undefined, sessionKey: string | undefined, ) { - if (!store || !sessionKey) return {}; + if (!store || !sessionKey) { + return {}; + } const direct = store[sessionKey]; - if (direct) return { entry: direct, key: sessionKey }; + if (direct) { + return { entry: direct, key: sessionKey }; + } return {}; } @@ -36,7 +40,9 @@ function resolveAbortTarget(params: { }) { const targetSessionKey = params.ctx.CommandTargetSessionKey?.trim() || params.sessionKey; const { entry, key } = resolveSessionEntryForKey(params.sessionStore, targetSessionKey); - if (entry && key) return { entry, key, sessionId: entry.sessionId }; + if (entry && key) { + return { entry, key, sessionId: entry.sessionId }; + } if (params.sessionEntry && params.sessionKey) { return { entry: params.sessionEntry, @@ -48,9 +54,13 @@ function resolveAbortTarget(params: { } export const handleActivationCommand: CommandHandler = async (params, allowTextCommands) => { - if (!allowTextCommands) return null; + if (!allowTextCommands) { + return null; + } const activationCommand = parseActivationCommand(params.command.commandBodyNormalized); - if (!activationCommand.hasCommand) return null; + if (!activationCommand.hasCommand) { + return null; + } if (!params.isGroup) { return { shouldContinue: false, @@ -89,9 +99,13 @@ export const handleActivationCommand: CommandHandler = async (params, allowTextC }; export const handleSendPolicyCommand: CommandHandler = async (params, allowTextCommands) => { - if (!allowTextCommands) return null; + if (!allowTextCommands) { + return null; + } const sendPolicyCommand = parseSendPolicyCommand(params.command.commandBodyNormalized); - if (!sendPolicyCommand.hasCommand) return null; + if (!sendPolicyCommand.hasCommand) { + return null; + } if (!params.command.isAuthorizedSender) { logVerbose( `Ignoring /send from unauthorized sender: ${params.command.senderId || ""}`, @@ -131,9 +145,13 @@ export const handleSendPolicyCommand: CommandHandler = async (params, allowTextC }; export const handleUsageCommand: CommandHandler = async (params, allowTextCommands) => { - if (!allowTextCommands) return null; + if (!allowTextCommands) { + return null; + } const normalized = params.command.commandBodyNormalized; - if (normalized !== "/usage" && !normalized.startsWith("/usage ")) return null; + if (normalized !== "/usage" && !normalized.startsWith("/usage ")) { + return null; + } if (!params.command.isAuthorizedSender) { logVerbose( `Ignoring /usage from unauthorized sender: ${params.command.senderId || ""}`, @@ -195,8 +213,11 @@ export const handleUsageCommand: CommandHandler = async (params, allowTextComman const next = requested ?? (current === "off" ? "tokens" : current === "tokens" ? "full" : "off"); if (params.sessionEntry && params.sessionStore && params.sessionKey) { - if (next === "off") delete params.sessionEntry.responseUsage; - else params.sessionEntry.responseUsage = next; + if (next === "off") { + delete params.sessionEntry.responseUsage; + } else { + params.sessionEntry.responseUsage = next; + } params.sessionEntry.updatedAt = Date.now(); params.sessionStore[params.sessionKey] = params.sessionEntry; if (params.storePath) { @@ -215,8 +236,12 @@ export const handleUsageCommand: CommandHandler = async (params, allowTextComman }; export const handleRestartCommand: CommandHandler = async (params, allowTextCommands) => { - if (!allowTextCommands) return null; - if (params.command.commandBodyNormalized !== "/restart") return null; + if (!allowTextCommands) { + return null; + } + if (params.command.commandBodyNormalized !== "/restart") { + return null; + } if (!params.command.isAuthorizedSender) { logVerbose( `Ignoring /restart from unauthorized sender: ${params.command.senderId || ""}`, @@ -260,8 +285,12 @@ export const handleRestartCommand: CommandHandler = async (params, allowTextComm }; export const handleStopCommand: CommandHandler = async (params, allowTextCommands) => { - if (!allowTextCommands) return null; - if (params.command.commandBodyNormalized !== "/stop") return null; + if (!allowTextCommands) { + return null; + } + if (params.command.commandBodyNormalized !== "/stop") { + return null; + } if (!params.command.isAuthorizedSender) { logVerbose( `Ignoring /stop from unauthorized sender: ${params.command.senderId || ""}`, @@ -319,8 +348,12 @@ export const handleStopCommand: CommandHandler = async (params, allowTextCommand }; export const handleAbortTrigger: CommandHandler = async (params, allowTextCommands) => { - if (!allowTextCommands) return null; - if (!isAbortTrigger(params.command.rawBodyNormalized)) return null; + if (!allowTextCommands) { + return null; + } + if (!isAbortTrigger(params.command.rawBodyNormalized)) { + return null; + } const abortTarget = resolveAbortTarget({ ctx: params.ctx, sessionKey: params.sessionKey, diff --git a/src/auto-reply/reply/commands-status.ts b/src/auto-reply/reply/commands-status.ts index 0c4b5182d3..256512f0df 100644 --- a/src/auto-reply/reply/commands-status.ts +++ b/src/auto-reply/reply/commands-status.ts @@ -34,7 +34,9 @@ import { resolveSubagentLabel } from "./subagents-utils.js"; function formatApiKeySnippet(apiKey: string): string { const compact = apiKey.replace(/\s+/g, ""); - if (!compact) return "unknown"; + if (!compact) { + return "unknown"; + } const edge = compact.length >= 12 ? 6 : 4; const head = compact.slice(0, edge); const tail = compact.slice(-edge); @@ -48,7 +50,9 @@ function resolveModelAuthLabel( agentDir?: string, ): string | undefined { const resolved = provider?.trim(); - if (!resolved) return undefined; + if (!resolved) { + return undefined; + } const providerKey = normalizeProviderId(resolved); const store = ensureAuthProfileStore(agentDir, { @@ -161,7 +165,9 @@ export async function buildStatusReply(params: { maxWindows: 2, includeResets: true, }); - if (summaryLine) usageLine = `📊 Usage: ${summaryLine}`; + if (summaryLine) { + usageLine = `📊 Usage: ${summaryLine}`; + } } } catch { usageLine = null; diff --git a/src/auto-reply/reply/commands-subagents.ts b/src/auto-reply/reply/commands-subagents.ts index e4ff7c27dc..082fc876d8 100644 --- a/src/auto-reply/reply/commands-subagents.ts +++ b/src/auto-reply/reply/commands-subagents.ts @@ -36,18 +36,24 @@ const COMMAND = "/subagents"; const ACTIONS = new Set(["list", "stop", "log", "send", "info", "help"]); function formatTimestamp(valueMs?: number) { - if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) return "n/a"; + if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) { + return "n/a"; + } return new Date(valueMs).toISOString(); } function formatTimestampWithAge(valueMs?: number) { - if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) return "n/a"; + if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) { + return "n/a"; + } return `${formatTimestamp(valueMs)} (${formatAgeShort(Date.now() - valueMs)})`; } function resolveRequesterSessionKey(params: Parameters[0]): string | undefined { const raw = params.sessionKey?.trim() || params.ctx.CommandTargetSessionKey?.trim(); - if (!raw) return undefined; + if (!raw) { + return undefined; + } const { mainKey, alias } = resolveMainSessionAlias(params.cfg); return resolveInternalSessionKey({ key: raw, alias, mainKey }); } @@ -57,7 +63,9 @@ function resolveSubagentTarget( token: string | undefined, ): SubagentTargetResolution { const trimmed = token?.trim(); - if (!trimmed) return { error: "Missing subagent id." }; + if (!trimmed) { + return { error: "Missing subagent id." }; + } if (trimmed === "last") { const sorted = sortSubagentRuns(runs); return { entry: sorted[0] }; @@ -75,7 +83,9 @@ function resolveSubagentTarget( return match ? { entry: match } : { error: `Unknown subagent session: ${trimmed}` }; } const byRunId = runs.filter((entry) => entry.runId.startsWith(trimmed)); - if (byRunId.length === 1) return { entry: byRunId[0] }; + if (byRunId.length === 1) { + return { entry: byRunId[0] }; + } if (byRunId.length > 1) { return { error: `Ambiguous run id prefix: ${trimmed}` }; } @@ -117,11 +127,17 @@ export function extractMessageText(message: ChatMessage): { role: string; text: ); return normalized ? { role, text: normalized } : null; } - if (!Array.isArray(content)) return null; + if (!Array.isArray(content)) { + return null; + } const chunks: string[] = []; for (const block of content) { - if (!block || typeof block !== "object") continue; - if ((block as { type?: unknown }).type !== "text") continue; + if (!block || typeof block !== "object") { + continue; + } + if ((block as { type?: unknown }).type !== "text") { + continue; + } const text = (block as { text?: unknown }).text; if (typeof text === "string") { const value = shouldSanitize ? sanitizeTextContent(text) : text; @@ -138,7 +154,9 @@ function formatLogLines(messages: ChatMessage[]) { const lines: string[] = []; for (const msg of messages) { const extracted = extractMessageText(msg); - if (!extracted) continue; + if (!extracted) { + continue; + } const label = extracted.role === "assistant" ? "Assistant" : "User"; lines.push(`${label}: ${extracted.text}`); } @@ -153,9 +171,13 @@ function loadSubagentSessionEntry(params: Parameters[0], childKe } export const handleSubagentsCommand: CommandHandler = async (params, allowTextCommands) => { - if (!allowTextCommands) return null; + if (!allowTextCommands) { + return null; + } const normalized = params.command.commandBodyNormalized; - if (!normalized.startsWith(COMMAND)) return null; + if (!normalized.startsWith(COMMAND)) { + return null; + } if (!params.command.isAuthorizedSender) { logVerbose( `Ignoring /subagents from unauthorized sender: ${params.command.senderId || ""}`, @@ -361,7 +383,9 @@ export const handleSubagentsCommand: CommandHandler = async (params, allowTextCo }, timeoutMs: 10_000, }); - if (response?.runId) runId = response.runId; + if (response?.runId) { + runId = response.runId; + } } catch (err) { const messageText = err instanceof Error ? err.message : typeof err === "string" ? err : "error"; diff --git a/src/auto-reply/reply/commands-tts.ts b/src/auto-reply/reply/commands-tts.ts index 04b60a4e9b..7ecee9b7e5 100644 --- a/src/auto-reply/reply/commands-tts.ts +++ b/src/auto-reply/reply/commands-tts.ts @@ -26,10 +26,16 @@ type ParsedTtsCommand = { function parseTtsCommand(normalized: string): ParsedTtsCommand | null { // Accept `/tts` and `/tts [args]` as a single control surface. - if (normalized === "/tts") return { action: "status", args: "" }; - if (!normalized.startsWith("/tts ")) return null; + if (normalized === "/tts") { + return { action: "status", args: "" }; + } + if (!normalized.startsWith("/tts ")) { + return null; + } const rest = normalized.slice(5).trim(); - if (!rest) return { action: "status", args: "" }; + if (!rest) { + return { action: "status", args: "" }; + } const [action, ...tail] = rest.split(/\s+/); return { action: action.toLowerCase(), args: tail.join(" ").trim() }; } @@ -63,9 +69,13 @@ function ttsUsage(): ReplyPayload { } export const handleTtsCommands: CommandHandler = async (params, allowTextCommands) => { - if (!allowTextCommands) return null; + if (!allowTextCommands) { + return null; + } const parsed = parseTtsCommand(params.command.commandBodyNormalized); - if (!parsed) return null; + if (!parsed) { + return null; + } if (!params.command.isAuthorizedSender) { logVerbose( diff --git a/src/auto-reply/reply/config-commands.ts b/src/auto-reply/reply/config-commands.ts index cd504f9bf0..b78baa4590 100644 --- a/src/auto-reply/reply/config-commands.ts +++ b/src/auto-reply/reply/config-commands.ts @@ -8,12 +8,18 @@ export type ConfigCommand = export function parseConfigCommand(raw: string): ConfigCommand | null { const trimmed = raw.trim(); - if (!trimmed.toLowerCase().startsWith("/config")) return null; + if (!trimmed.toLowerCase().startsWith("/config")) { + return null; + } const rest = trimmed.slice("/config".length).trim(); - if (!rest) return { action: "show" }; + if (!rest) { + return { action: "show" }; + } const match = rest.match(/^(\S+)(?:\s+([\s\S]+))?$/); - if (!match) return { action: "error", message: "Invalid /config syntax." }; + if (!match) { + return { action: "error", message: "Invalid /config syntax." }; + } const action = match[1].toLowerCase(); const args = (match[2] ?? "").trim(); @@ -23,7 +29,9 @@ export function parseConfigCommand(raw: string): ConfigCommand | null { case "get": return { action: "show", path: args || undefined }; case "unset": { - if (!args) return { action: "error", message: "Usage: /config unset path" }; + if (!args) { + return { action: "error", message: "Usage: /config unset path" }; + } return { action: "unset", path: args }; } case "set": { diff --git a/src/auto-reply/reply/config-value.ts b/src/auto-reply/reply/config-value.ts index f57a2820a5..7c329e3fb0 100644 --- a/src/auto-reply/reply/config-value.ts +++ b/src/auto-reply/reply/config-value.ts @@ -3,7 +3,9 @@ export function parseConfigValue(raw: string): { error?: string; } { const trimmed = raw.trim(); - if (!trimmed) return { error: "Missing value." }; + if (!trimmed) { + return { error: "Missing value." }; + } if (trimmed.startsWith("{") || trimmed.startsWith("[")) { try { @@ -13,13 +15,21 @@ export function parseConfigValue(raw: string): { } } - if (trimmed === "true") return { value: true }; - if (trimmed === "false") return { value: false }; - if (trimmed === "null") return { value: null }; + if (trimmed === "true") { + return { value: true }; + } + if (trimmed === "false") { + return { value: false }; + } + if (trimmed === "null") { + return { value: null }; + } if (/^-?\d+(\.\d+)?$/.test(trimmed)) { const num = Number(trimmed); - if (Number.isFinite(num)) return { value: num }; + if (Number.isFinite(num)) { + return { value: num }; + } } if ( diff --git a/src/auto-reply/reply/debug-commands.ts b/src/auto-reply/reply/debug-commands.ts index 09f7d2e169..5f9f8c9fd0 100644 --- a/src/auto-reply/reply/debug-commands.ts +++ b/src/auto-reply/reply/debug-commands.ts @@ -9,12 +9,18 @@ export type DebugCommand = export function parseDebugCommand(raw: string): DebugCommand | null { const trimmed = raw.trim(); - if (!trimmed.toLowerCase().startsWith("/debug")) return null; + if (!trimmed.toLowerCase().startsWith("/debug")) { + return null; + } const rest = trimmed.slice("/debug".length).trim(); - if (!rest) return { action: "show" }; + if (!rest) { + return { action: "show" }; + } const match = rest.match(/^(\S+)(?:\s+([\s\S]+))?$/); - if (!match) return { action: "error", message: "Invalid /debug syntax." }; + if (!match) { + return { action: "error", message: "Invalid /debug syntax." }; + } const action = match[1].toLowerCase(); const args = (match[2] ?? "").trim(); @@ -24,7 +30,9 @@ export function parseDebugCommand(raw: string): DebugCommand | null { case "reset": return { action: "reset" }; case "unset": { - if (!args) return { action: "error", message: "Usage: /debug unset path" }; + if (!args) { + return { action: "error", message: "Usage: /debug unset path" }; + } return { action: "unset", path: args }; } case "set": { diff --git a/src/auto-reply/reply/directive-handling.auth.ts b/src/auto-reply/reply/directive-handling.auth.ts index 2ddc059813..a0eb43f134 100644 --- a/src/auto-reply/reply/directive-handling.auth.ts +++ b/src/auto-reply/reply/directive-handling.auth.ts @@ -17,8 +17,12 @@ export type ModelAuthDetailMode = "compact" | "verbose"; const maskApiKey = (value: string): string => { const trimmed = value.trim(); - if (!trimmed) return "missing"; - if (trimmed.length <= 16) return trimmed; + if (!trimmed) { + return "missing"; + } + if (trimmed.length <= 16) { + return trimmed; + } return `${trimmed.slice(0, 8)}...${trimmed.slice(-8)}`; }; @@ -37,9 +41,13 @@ export const resolveAuthLabel = async ( const providerKey = normalizeProviderId(provider); const lastGood = (() => { const map = store.lastGood; - if (!map) return undefined; + if (!map) { + return undefined; + } for (const [key, value] of Object.entries(map)) { - if (normalizeProviderId(key) === providerKey) return value; + if (normalizeProviderId(key) === providerKey) { + return value; + } } return undefined; })(); @@ -49,10 +57,16 @@ export const resolveAuthLabel = async ( const formatUntil = (timestampMs: number) => { const remainingMs = Math.max(0, timestampMs - now); const minutes = Math.round(remainingMs / 60_000); - if (minutes < 1) return "soon"; - if (minutes < 60) return `${minutes}m`; + if (minutes < 1) { + return "soon"; + } + if (minutes < 60) { + return `${minutes}m`; + } const hours = Math.round(minutes / 60); - if (hours < 48) return `${hours}h`; + if (hours < 48) { + return `${hours}h`; + } const days = Math.round(hours / 24); return `${days}d`; }; @@ -60,7 +74,9 @@ export const resolveAuthLabel = async ( if (order.length > 0) { if (mode === "compact") { const profileId = nextProfileId; - if (!profileId) return { label: "missing", source: "missing" }; + if (!profileId) { + return { label: "missing", source: "missing" }; + } const profile = store.profiles[profileId]; const configProfile = cfg.auth?.profiles?.[profileId]; const missing = @@ -71,7 +87,9 @@ export const resolveAuthLabel = async ( !(configProfile.mode === "oauth" && profile.type === "token")); const more = order.length > 1 ? ` (+${order.length - 1})` : ""; - if (missing) return { label: `${profileId} missing${more}`, source: "" }; + if (missing) { + return { label: `${profileId} missing${more}`, source: "" }; + } if (profile.type === "api_key") { return { @@ -110,8 +128,12 @@ export const resolveAuthLabel = async ( const profile = store.profiles[profileId]; const configProfile = cfg.auth?.profiles?.[profileId]; const flags: string[] = []; - if (profileId === nextProfileId) flags.push("next"); - if (lastGood && profileId === lastGood) flags.push("lastGood"); + if (profileId === nextProfileId) { + flags.push("next"); + } + if (lastGood && profileId === lastGood) { + flags.push("lastGood"); + } if (isProfileInCooldown(store, profileId)) { const until = store.usageStats?.[profileId]?.cooldownUntil; if (typeof until === "number" && Number.isFinite(until) && until > now) { @@ -205,7 +227,9 @@ export const resolveProfileOverride = (params: { agentDir?: string; }): { profileId?: string; error?: string } => { const raw = params.rawProfile?.trim(); - if (!raw) return {}; + if (!raw) { + return {}; + } const store = ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false, }); diff --git a/src/auto-reply/reply/directive-handling.impl.ts b/src/auto-reply/reply/directive-handling.impl.ts index f95fd333e8..76eda2d9bf 100644 --- a/src/auto-reply/reply/directive-handling.impl.ts +++ b/src/auto-reply/reply/directive-handling.impl.ts @@ -133,7 +133,9 @@ export async function handleDirectiveOnly(params: { allowedModelCatalog, resetModelOverride, }); - if (modelInfo) return modelInfo; + if (modelInfo) { + return modelInfo; + } const modelResolution = resolveModelSelectionFromDirective({ directives, @@ -146,7 +148,9 @@ export async function handleDirectiveOnly(params: { allowedModelCatalog, provider, }); - if (modelResolution.errorText) return { text: modelResolution.errorText }; + if (modelResolution.errorText) { + return { text: modelResolution.errorText }; + } const modelSelection = modelResolution.modelSelection; const profileOverride = modelResolution.profileOverride; @@ -267,7 +271,9 @@ export async function handleDirectiveOnly(params: { channel: provider, sessionEntry, }); - if (queueAck) return queueAck; + if (queueAck) { + return queueAck; + } if ( directives.hasThinkDirective && @@ -301,8 +307,11 @@ export async function handleDirectiveOnly(params: { let reasoningChanged = directives.hasReasoningDirective && directives.reasoningLevel !== undefined; if (directives.hasThinkDirective && directives.thinkLevel) { - if (directives.thinkLevel === "off") delete sessionEntry.thinkingLevel; - else sessionEntry.thinkingLevel = directives.thinkLevel; + if (directives.thinkLevel === "off") { + delete sessionEntry.thinkingLevel; + } else { + sessionEntry.thinkingLevel = directives.thinkLevel; + } } if (shouldDowngradeXHigh) { sessionEntry.thinkingLevel = "high"; @@ -311,8 +320,11 @@ export async function handleDirectiveOnly(params: { applyVerboseOverride(sessionEntry, directives.verboseLevel); } if (directives.hasReasoningDirective && directives.reasoningLevel) { - if (directives.reasoningLevel === "off") delete sessionEntry.reasoningLevel; - else sessionEntry.reasoningLevel = directives.reasoningLevel; + if (directives.reasoningLevel === "off") { + delete sessionEntry.reasoningLevel; + } else { + sessionEntry.reasoningLevel = directives.reasoningLevel; + } reasoningChanged = directives.reasoningLevel !== prevReasoningLevel && directives.reasoningLevel !== undefined; } @@ -351,7 +363,9 @@ export async function handleDirectiveOnly(params: { delete sessionEntry.queueCap; delete sessionEntry.queueDrop; } else if (directives.hasQueueDirective) { - if (directives.queueMode) sessionEntry.queueMode = directives.queueMode; + if (directives.queueMode) { + sessionEntry.queueMode = directives.queueMode; + } if (typeof directives.debounceMs === "number") { sessionEntry.queueDebounceMs = directives.debounceMs; } @@ -427,14 +441,24 @@ export async function handleDirectiveOnly(params: { ? formatDirectiveAck("Elevated mode set to full (auto-approve).") : formatDirectiveAck("Elevated mode set to ask (approvals may still apply)."), ); - if (shouldHintDirectRuntime) parts.push(formatElevatedRuntimeHint()); + if (shouldHintDirectRuntime) { + parts.push(formatElevatedRuntimeHint()); + } } if (directives.hasExecDirective && directives.hasExecOptions) { const execParts: string[] = []; - if (directives.execHost) execParts.push(`host=${directives.execHost}`); - if (directives.execSecurity) execParts.push(`security=${directives.execSecurity}`); - if (directives.execAsk) execParts.push(`ask=${directives.execAsk}`); - if (directives.execNode) execParts.push(`node=${directives.execNode}`); + if (directives.execHost) { + execParts.push(`host=${directives.execHost}`); + } + if (directives.execSecurity) { + execParts.push(`security=${directives.execSecurity}`); + } + if (directives.execAsk) { + execParts.push(`ask=${directives.execAsk}`); + } + if (directives.execNode) { + execParts.push(`node=${directives.execNode}`); + } if (execParts.length > 0) { parts.push(formatDirectiveAck(`Exec defaults set (${execParts.join(", ")}).`)); } @@ -471,6 +495,8 @@ export async function handleDirectiveOnly(params: { parts.push(formatDirectiveAck(`Queue drop set to ${directives.dropPolicy}.`)); } const ack = parts.join(" ").trim(); - if (!ack && directives.hasStatusDirective) return undefined; + if (!ack && directives.hasStatusDirective) { + return undefined; + } return { text: ack || "OK." }; } diff --git a/src/auto-reply/reply/directive-handling.model-picker.ts b/src/auto-reply/reply/directive-handling.model-picker.ts index 1bc50f7c12..0c2bcaf61e 100644 --- a/src/auto-reply/reply/directive-handling.model-picker.ts +++ b/src/auto-reply/reply/directive-handling.model-picker.ts @@ -34,9 +34,15 @@ const PROVIDER_RANK = new Map( function compareProvidersForPicker(a: string, b: string): number { const pa = PROVIDER_RANK.get(a); const pb = PROVIDER_RANK.get(b); - if (pa !== undefined && pb !== undefined) return pa - pb; - if (pa !== undefined) return -1; - if (pb !== undefined) return 1; + if (pa !== undefined && pb !== undefined) { + return pa - pb; + } + if (pa !== undefined) { + return -1; + } + if (pb !== undefined) { + return 1; + } return a.localeCompare(b); } @@ -47,10 +53,14 @@ export function buildModelPickerItems(catalog: ModelPickerCatalogEntry[]): Model for (const entry of catalog) { const provider = normalizeProviderId(entry.provider); const model = entry.id?.trim(); - if (!provider || !model) continue; + if (!provider || !model) { + continue; + } const key = `${provider}/${model}`; - if (seen.has(key)) continue; + if (seen.has(key)) { + continue; + } seen.add(key); out.push({ model, provider }); @@ -59,7 +69,9 @@ export function buildModelPickerItems(catalog: ModelPickerCatalogEntry[]): Model // Sort by provider preference first, then by model name out.sort((a, b) => { const providerOrder = compareProvidersForPicker(a.provider, b.provider); - if (providerOrder !== 0) return providerOrder; + if (providerOrder !== 0) { + return providerOrder; + } return a.model.toLowerCase().localeCompare(b.model.toLowerCase()); }); diff --git a/src/auto-reply/reply/directive-handling.model.ts b/src/auto-reply/reply/directive-handling.model.ts index 634e289c94..eca6ae0ee8 100644 --- a/src/auto-reply/reply/directive-handling.model.ts +++ b/src/auto-reply/reply/directive-handling.model.ts @@ -43,22 +43,30 @@ function buildModelPickerCatalog(params: { const pushRef = (ref: { provider: string; model: string }, name?: string) => { const provider = normalizeProviderId(ref.provider); const id = String(ref.model ?? "").trim(); - if (!provider || !id) return; + if (!provider || !id) { + return; + } const key = modelKey(provider, id); - if (keys.has(key)) return; + if (keys.has(key)) { + return; + } keys.add(key); out.push({ provider, id, name: name ?? id }); }; const pushRaw = (raw?: string) => { const value = String(raw ?? "").trim(); - if (!value) return; + if (!value) { + return; + } const resolved = resolveModelRefFromString({ raw: value, defaultProvider: params.defaultProvider, aliasIndex: params.aliasIndex, }); - if (!resolved) return; + if (!resolved) { + return; + } pushRef(resolved.ref); }; @@ -92,9 +100,13 @@ function buildModelPickerCatalog(params: { const push = (entry: ModelPickerCatalogEntry) => { const provider = normalizeProviderId(entry.provider); const id = String(entry.id ?? "").trim(); - if (!provider || !id) return; + if (!provider || !id) { + return; + } const key = modelKey(provider, id); - if (keys.has(key)) return; + if (keys.has(key)) { + return; + } keys.add(key); out.push({ provider, id, name: entry.name }); }; @@ -131,7 +143,9 @@ function buildModelPickerCatalog(params: { defaultProvider: params.defaultProvider, aliasIndex: params.aliasIndex, }); - if (!resolved) continue; + if (!resolved) { + continue; + } push({ provider: resolved.ref.provider, id: resolved.ref.model, @@ -164,14 +178,18 @@ export async function maybeHandleModelDirectiveInfo(params: { allowedModelCatalog: Array<{ provider: string; id?: string; name?: string }>; resetModelOverride: boolean; }): Promise { - if (!params.directives.hasModelDirective) return undefined; + if (!params.directives.hasModelDirective) { + return undefined; + } const rawDirective = params.directives.rawModelDirective?.trim(); const directive = rawDirective?.toLowerCase(); const wantsStatus = directive === "status"; const wantsSummary = !rawDirective; const wantsLegacyList = directive === "list"; - if (!wantsSummary && !wantsStatus && !wantsLegacyList) return undefined; + if (!wantsSummary && !wantsStatus && !wantsLegacyList) { + return undefined; + } if (params.directives.rawModelProfile) { return { text: "Auth profile override requires a model selection." }; @@ -209,12 +227,16 @@ export async function maybeHandleModelDirectiveInfo(params: { const modelsPath = `${params.agentDir}/models.json`; const formatPath = (value: string) => shortenHomePath(value); const authMode: ModelAuthDetailMode = "verbose"; - if (pickerCatalog.length === 0) return { text: "No models available." }; + if (pickerCatalog.length === 0) { + return { text: "No models available." }; + } const authByProvider = new Map(); for (const entry of pickerCatalog) { const provider = normalizeProviderId(entry.provider); - if (authByProvider.has(provider)) continue; + if (authByProvider.has(provider)) { + continue; + } const auth = await resolveAuthLabel( provider, params.cfg, @@ -250,7 +272,9 @@ export async function maybeHandleModelDirectiveInfo(params: { for (const provider of byProvider.keys()) { const models = byProvider.get(provider); - if (!models) continue; + if (!models) { + continue; + } const authLabel = authByProvider.get(provider) ?? "missing"; const endpoint = resolveProviderEndpointLabel(provider, params.cfg); const endpointSuffix = endpoint.endpoint diff --git a/src/auto-reply/reply/directive-handling.parse.ts b/src/auto-reply/reply/directive-handling.parse.ts index c2f9621874..b09d5c553b 100644 --- a/src/auto-reply/reply/directive-handling.parse.ts +++ b/src/auto-reply/reply/directive-handling.parse.ts @@ -206,8 +206,9 @@ export function isDirectiveOnly(params: { !directives.hasExecDirective && !directives.hasModelDirective && !directives.hasQueueDirective - ) + ) { return false; + } const stripped = stripStructuralPrefixes(cleanedBody ?? ""); const noMentions = isGroup ? stripMentions(stripped, ctx, cfg, agentId) : stripped; return noMentions.length === 0; diff --git a/src/auto-reply/reply/directive-handling.queue-validation.ts b/src/auto-reply/reply/directive-handling.queue-validation.ts index 160bd8d79e..aecdd1c97a 100644 --- a/src/auto-reply/reply/directive-handling.queue-validation.ts +++ b/src/auto-reply/reply/directive-handling.queue-validation.ts @@ -12,7 +12,9 @@ export function maybeHandleQueueDirective(params: { sessionEntry?: SessionEntry; }): ReplyPayload | undefined { const { directives } = params; - if (!directives.hasQueueDirective) return undefined; + if (!directives.hasQueueDirective) { + return undefined; + } const wantsStatus = !directives.queueMode && diff --git a/src/auto-reply/reply/directive-handling.shared.ts b/src/auto-reply/reply/directive-handling.shared.ts index c6fe025a44..f380d3a753 100644 --- a/src/auto-reply/reply/directive-handling.shared.ts +++ b/src/auto-reply/reply/directive-handling.shared.ts @@ -4,8 +4,12 @@ import type { ElevatedLevel, ReasoningLevel } from "./directives.js"; export const SYSTEM_MARK = "⚙️"; export const formatDirectiveAck = (text: string): string => { - if (!text) return text; - if (text.startsWith(SYSTEM_MARK)) return text; + if (!text) { + return text; + } + if (text.startsWith(SYSTEM_MARK)) { + return text; + } return `${SYSTEM_MARK} ${text}`; }; @@ -27,8 +31,12 @@ export const formatElevatedEvent = (level: ElevatedLevel) => { }; export const formatReasoningEvent = (level: ReasoningLevel) => { - if (level === "stream") return "Reasoning STREAM — emit live ."; - if (level === "on") return "Reasoning ON — include ."; + if (level === "stream") { + return "Reasoning STREAM — emit live ."; + } + if (level === "on") { + return "Reasoning ON — include ."; + } return "Reasoning OFF — hide ."; }; diff --git a/src/auto-reply/reply/directives.ts b/src/auto-reply/reply/directives.ts index 7fc6592668..4313979156 100644 --- a/src/auto-reply/reply/directives.ts +++ b/src/auto-reply/reply/directives.ts @@ -25,17 +25,25 @@ const matchLevelDirective = ( ): { start: number; end: number; rawLevel?: string } | null => { const namePattern = names.map(escapeRegExp).join("|"); const match = body.match(new RegExp(`(?:^|\\s)\\/(?:${namePattern})(?=$|\\s|:)`, "i")); - if (!match || match.index === undefined) return null; + if (!match || match.index === undefined) { + return null; + } const start = match.index; let end = match.index + match[0].length; let i = end; - while (i < body.length && /\s/.test(body[i])) i += 1; + while (i < body.length && /\s/.test(body[i])) { + i += 1; + } if (body[i] === ":") { i += 1; - while (i < body.length && /\s/.test(body[i])) i += 1; + while (i < body.length && /\s/.test(body[i])) { + i += 1; + } } const argStart = i; - while (i < body.length && /[A-Za-z-]/.test(body[i])) i += 1; + while (i < body.length && /[A-Za-z-]/.test(body[i])) { + i += 1; + } const rawLevel = i > argStart ? body.slice(argStart, i) : undefined; end = i; return { start, end, rawLevel }; @@ -87,7 +95,9 @@ export function extractThinkDirective(body?: string): { rawLevel?: string; hasDirective: boolean; } { - if (!body) return { cleaned: "", hasDirective: false }; + if (!body) { + return { cleaned: "", hasDirective: false }; + } const extracted = extractLevelDirective(body, ["thinking", "think", "t"], normalizeThinkLevel); return { cleaned: extracted.cleaned, @@ -103,7 +113,9 @@ export function extractVerboseDirective(body?: string): { rawLevel?: string; hasDirective: boolean; } { - if (!body) return { cleaned: "", hasDirective: false }; + if (!body) { + return { cleaned: "", hasDirective: false }; + } const extracted = extractLevelDirective(body, ["verbose", "v"], normalizeVerboseLevel); return { cleaned: extracted.cleaned, @@ -119,7 +131,9 @@ export function extractNoticeDirective(body?: string): { rawLevel?: string; hasDirective: boolean; } { - if (!body) return { cleaned: "", hasDirective: false }; + if (!body) { + return { cleaned: "", hasDirective: false }; + } const extracted = extractLevelDirective(body, ["notice", "notices"], normalizeNoticeLevel); return { cleaned: extracted.cleaned, @@ -135,7 +149,9 @@ export function extractElevatedDirective(body?: string): { rawLevel?: string; hasDirective: boolean; } { - if (!body) return { cleaned: "", hasDirective: false }; + if (!body) { + return { cleaned: "", hasDirective: false }; + } const extracted = extractLevelDirective(body, ["elevated", "elev"], normalizeElevatedLevel); return { cleaned: extracted.cleaned, @@ -151,7 +167,9 @@ export function extractReasoningDirective(body?: string): { rawLevel?: string; hasDirective: boolean; } { - if (!body) return { cleaned: "", hasDirective: false }; + if (!body) { + return { cleaned: "", hasDirective: false }; + } const extracted = extractLevelDirective(body, ["reasoning", "reason"], normalizeReasoningLevel); return { cleaned: extracted.cleaned, @@ -165,7 +183,9 @@ export function extractStatusDirective(body?: string): { cleaned: string; hasDirective: boolean; } { - if (!body) return { cleaned: "", hasDirective: false }; + if (!body) { + return { cleaned: "", hasDirective: false }; + } return extractSimpleDirective(body, ["status"]); } diff --git a/src/auto-reply/reply/dispatch-from-config.ts b/src/auto-reply/reply/dispatch-from-config.ts index 1dac8e4d29..3192f4d516 100644 --- a/src/auto-reply/reply/dispatch-from-config.ts +++ b/src/auto-reply/reply/dispatch-from-config.ts @@ -29,7 +29,9 @@ const isInboundAudioContext = (ctx: FinalizedMsgContext): boolean => { ...(Array.isArray(ctx.MediaTypes) ? ctx.MediaTypes : []), ].filter(Boolean) as string[]; const types = rawTypes.map((type) => normalizeMediaType(type)); - if (types.some((type) => type === "audio" || type.startsWith("audio/"))) return true; + if (types.some((type) => type === "audio" || type.startsWith("audio/"))) { + return true; + } const body = typeof ctx.BodyForCommands === "string" @@ -42,8 +44,12 @@ const isInboundAudioContext = (ctx: FinalizedMsgContext): boolean => { ? ctx.Body : ""; const trimmed = body.trim(); - if (!trimmed) return false; - if (AUDIO_PLACEHOLDER_RE.test(trimmed)) return true; + if (!trimmed) { + return false; + } + if (AUDIO_PLACEHOLDER_RE.test(trimmed)) { + return true; + } return AUDIO_HEADER_RE.test(trimmed); }; @@ -54,7 +60,9 @@ const resolveSessionTtsAuto = ( const targetSessionKey = ctx.CommandSource === "native" ? ctx.CommandTargetSessionKey?.trim() : undefined; const sessionKey = (targetSessionKey ?? ctx.SessionKey)?.trim(); - if (!sessionKey) return undefined; + if (!sessionKey) { + return undefined; + } const agentId = resolveSessionAgentId({ sessionKey, config: cfg }); const storePath = resolveStorePath(cfg.session?.store, { agentId }); try { @@ -94,7 +102,9 @@ export async function dispatchReplyFromConfig(params: { error?: string; }, ) => { - if (!diagnosticsEnabled) return; + if (!diagnosticsEnabled) { + return; + } logMessageProcessed({ channel, chatId, @@ -108,7 +118,9 @@ export async function dispatchReplyFromConfig(params: { }; const markProcessing = () => { - if (!canTrackSession || !sessionKey) return; + if (!canTrackSession || !sessionKey) { + return; + } logMessageQueued({ sessionKey, channel, source: "dispatch" }); logSessionStateChange({ sessionKey, @@ -118,7 +130,9 @@ export async function dispatchReplyFromConfig(params: { }; const markIdle = (reason: string) => { - if (!canTrackSession || !sessionKey) return; + if (!canTrackSession || !sessionKey) { + return; + } logSessionStateChange({ sessionKey, state: "idle", @@ -210,8 +224,12 @@ export async function dispatchReplyFromConfig(params: { ): Promise => { // TypeScript doesn't narrow these from the shouldRouteToOriginating check, // but they're guaranteed non-null when this function is called. - if (!originatingChannel || !originatingTo) return; - if (abortSignal?.aborted) return; + if (!originatingChannel || !originatingTo) { + return; + } + if (abortSignal?.aborted) { + return; + } const result = await routeReply({ payload, channel: originatingChannel, @@ -249,7 +267,9 @@ export async function dispatchReplyFromConfig(params: { cfg, }); queuedFinal = result.ok; - if (result.ok) routedFinalCount += 1; + if (result.ok) { + routedFinalCount += 1; + } if (!result.ok) { logVerbose( `dispatch-from-config: route-reply (abort) failed: ${result.error ?? "unknown error"}`, @@ -357,7 +377,9 @@ export async function dispatchReplyFromConfig(params: { ); } queuedFinal = result.ok || queuedFinal; - if (result.ok) routedFinalCount += 1; + if (result.ok) { + routedFinalCount += 1; + } } else { queuedFinal = dispatcher.sendFinalReply(ttsReply) || queuedFinal; } @@ -400,7 +422,9 @@ export async function dispatchReplyFromConfig(params: { cfg, }); queuedFinal = result.ok || queuedFinal; - if (result.ok) routedFinalCount += 1; + if (result.ok) { + routedFinalCount += 1; + } if (!result.ok) { logVerbose( `dispatch-from-config: route-reply (tts-only) failed: ${result.error ?? "unknown error"}`, diff --git a/src/auto-reply/reply/exec/directive.ts b/src/auto-reply/reply/exec/directive.ts index 0356530db7..44fdfeda8f 100644 --- a/src/auto-reply/reply/exec/directive.ts +++ b/src/auto-reply/reply/exec/directive.ts @@ -20,15 +20,17 @@ type ExecDirectiveParse = { function normalizeExecHost(value?: string): ExecHost | undefined { const normalized = value?.trim().toLowerCase(); - if (normalized === "sandbox" || normalized === "gateway" || normalized === "node") + if (normalized === "sandbox" || normalized === "gateway" || normalized === "node") { return normalized; + } return undefined; } function normalizeExecSecurity(value?: string): ExecSecurity | undefined { const normalized = value?.trim().toLowerCase(); - if (normalized === "deny" || normalized === "allowlist" || normalized === "full") + if (normalized === "deny" || normalized === "allowlist" || normalized === "full") { return normalized; + } return undefined; } @@ -48,10 +50,14 @@ function parseExecDirectiveArgs(raw: string): Omit< } { let i = 0; const len = raw.length; - while (i < len && /\s/.test(raw[i])) i += 1; + while (i < len && /\s/.test(raw[i])) { + i += 1; + } if (raw[i] === ":") { i += 1; - while (i < len && /\s/.test(raw[i])) i += 1; + while (i < len && /\s/.test(raw[i])) { + i += 1; + } } let consumed = i; let execHost: ExecHost | undefined; @@ -69,12 +75,20 @@ function parseExecDirectiveArgs(raw: string): Omit< let invalidNode = false; const takeToken = (): string | null => { - if (i >= len) return null; + if (i >= len) { + return null; + } const start = i; - while (i < len && !/\s/.test(raw[i])) i += 1; - if (start === i) return null; + while (i < len && !/\s/.test(raw[i])) { + i += 1; + } + if (start === i) { + return null; + } const token = raw.slice(start, i); - while (i < len && /\s/.test(raw[i])) i += 1; + while (i < len && /\s/.test(raw[i])) { + i += 1; + } return token; }; @@ -82,23 +96,33 @@ function parseExecDirectiveArgs(raw: string): Omit< const eq = token.indexOf("="); const colon = token.indexOf(":"); const idx = eq === -1 ? colon : colon === -1 ? eq : Math.min(eq, colon); - if (idx === -1) return null; + if (idx === -1) { + return null; + } const key = token.slice(0, idx).trim().toLowerCase(); const value = token.slice(idx + 1).trim(); - if (!key) return null; + if (!key) { + return null; + } return { key, value }; }; while (i < len) { const token = takeToken(); - if (!token) break; + if (!token) { + break; + } const parsed = splitToken(token); - if (!parsed) break; + if (!parsed) { + break; + } const { key, value } = parsed; if (key === "host") { rawExecHost = value; execHost = normalizeExecHost(value); - if (!execHost) invalidHost = true; + if (!execHost) { + invalidHost = true; + } hasExecOptions = true; consumed = i; continue; @@ -106,7 +130,9 @@ function parseExecDirectiveArgs(raw: string): Omit< if (key === "security") { rawExecSecurity = value; execSecurity = normalizeExecSecurity(value); - if (!execSecurity) invalidSecurity = true; + if (!execSecurity) { + invalidSecurity = true; + } hasExecOptions = true; consumed = i; continue; @@ -114,7 +140,9 @@ function parseExecDirectiveArgs(raw: string): Omit< if (key === "ask") { rawExecAsk = value; execAsk = normalizeExecAsk(value); - if (!execAsk) invalidAsk = true; + if (!execAsk) { + invalidAsk = true; + } hasExecOptions = true; consumed = i; continue; diff --git a/src/auto-reply/reply/followup-runner.ts b/src/auto-reply/reply/followup-runner.ts index 77edf66e5a..8204c8184d 100644 --- a/src/auto-reply/reply/followup-runner.ts +++ b/src/auto-reply/reply/followup-runner.ts @@ -172,7 +172,9 @@ export function createFollowupRunner(params: { runId, blockReplyBreak: queued.run.blockReplyBreak, onAgentEvent: (evt) => { - if (evt.stream !== "compaction") return; + if (evt.stream !== "compaction") { + return; + } const phase = typeof evt.data.phase === "string" ? evt.data.phase : ""; const willRetry = Boolean(evt.data.willRetry); if (phase === "end" && !willRetry) { @@ -212,13 +214,19 @@ export function createFollowupRunner(params: { } const payloadArray = runResult.payloads ?? []; - if (payloadArray.length === 0) return; + if (payloadArray.length === 0) { + return; + } const sanitizedPayloads = payloadArray.flatMap((payload) => { const text = payload.text; - if (!text || !text.includes("HEARTBEAT_OK")) return [payload]; + if (!text || !text.includes("HEARTBEAT_OK")) { + return [payload]; + } const stripped = stripHeartbeatToken(text, { mode: "message" }); const hasMedia = Boolean(payload.mediaUrl) || (payload.mediaUrls?.length ?? 0) > 0; - if (stripped.shouldSkip && !hasMedia) return []; + if (stripped.shouldSkip && !hasMedia) { + return []; + } return [{ ...payload, text: stripped.text }]; }); const replyToChannel = @@ -249,7 +257,9 @@ export function createFollowupRunner(params: { }); const finalPayloads = suppressMessagingToolReplies ? [] : dedupedPayloads; - if (finalPayloads.length === 0) return; + if (finalPayloads.length === 0) { + return; + } if (autoCompactionCompleted) { const count = await incrementCompactionCount({ diff --git a/src/auto-reply/reply/get-reply-directives.ts b/src/auto-reply/reply/get-reply-directives.ts index 8444ddb131..67641efa2f 100644 --- a/src/auto-reply/reply/get-reply-directives.ts +++ b/src/auto-reply/reply/get-reply-directives.ts @@ -73,7 +73,9 @@ function resolveExecOverrides(params: { (params.sessionEntry?.execSecurity as ExecOverrides["security"]); const ask = params.directives.execAsk ?? (params.sessionEntry?.execAsk as ExecOverrides["ask"]); const node = params.directives.execNode ?? params.sessionEntry?.execNode; - if (!host && !security && !ask && !node) return undefined; + if (!host && !security && !ask && !node) { + return undefined; + } return { host, security, ask, node }; } @@ -270,7 +272,9 @@ export async function resolveReplyDirectives(params: { }; const existingBody = sessionCtx.BodyStripped ?? sessionCtx.Body ?? ""; let cleanedBody = (() => { - if (!existingBody) return parsedDirectives.cleaned; + if (!existingBody) { + return parsedDirectives.cleaned; + } if (!sessionCtx.CommandBody && !sessionCtx.RawBody) { return parseInlineDirectives(existingBody, { modelAliases: configuredAliases, diff --git a/src/auto-reply/reply/get-reply-inline-actions.ts b/src/auto-reply/reply/get-reply-inline-actions.ts index 1ecfb26305..92bb25d0f7 100644 --- a/src/auto-reply/reply/get-reply-inline-actions.ts +++ b/src/auto-reply/reply/get-reply-inline-actions.ts @@ -26,17 +26,23 @@ export type InlineActionResult = }; function extractTextFromToolResult(result: any): string | null { - if (!result || typeof result !== "object") return null; + if (!result || typeof result !== "object") { + return null; + } const content = (result as { content?: unknown }).content; if (typeof content === "string") { const trimmed = content.trim(); return trimmed ? trimmed : null; } - if (!Array.isArray(content)) return null; + if (!Array.isArray(content)) { + return null; + } const parts: string[] = []; for (const block of content) { - if (!block || typeof block !== "object") continue; + if (!block || typeof block !== "object") { + continue; + } const rec = block as { type?: unknown; text?: unknown }; if (rec.type === "text" && typeof rec.text === "string") { parts.push(rec.text); @@ -212,8 +218,12 @@ export async function handleInlineActions(params: { } const sendInlineReply = async (reply?: ReplyPayload) => { - if (!reply) return; - if (!opts?.onBlockReply) return; + if (!reply) { + return; + } + if (!opts?.onBlockReply) { + return; + } await opts.onBlockReply(reply); }; diff --git a/src/auto-reply/reply/groups.ts b/src/auto-reply/reply/groups.ts index 67b506e33d..b947be7da1 100644 --- a/src/auto-reply/reply/groups.ts +++ b/src/auto-reply/reply/groups.ts @@ -8,7 +8,9 @@ import type { TemplateContext } from "../templating.js"; function extractGroupId(raw: string | undefined | null): string | undefined { const trimmed = (raw ?? "").trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } const parts = trimmed.split(":").filter(Boolean); if (parts.length >= 3 && (parts[1] === "group" || parts[1] === "channel")) { return parts.slice(2).join(":") || undefined; @@ -34,7 +36,9 @@ export function resolveGroupRequireMention(params: { const { cfg, ctx, groupResolution } = params; const rawChannel = groupResolution?.channel ?? ctx.Provider?.trim(); const channel = normalizeChannelId(rawChannel); - if (!channel) return true; + if (!channel) { + return true; + } const groupId = groupResolution?.id ?? extractGroupId(ctx.From); const groupChannel = ctx.GroupChannel?.trim() ?? ctx.GroupSubject?.trim(); const groupSpace = ctx.GroupSpace?.trim(); @@ -45,7 +49,9 @@ export function resolveGroupRequireMention(params: { groupSpace, accountId: ctx.AccountId, }); - if (typeof requireMention === "boolean") return requireMention; + if (typeof requireMention === "boolean") { + return requireMention; + } return true; } @@ -68,9 +74,15 @@ export function buildGroupIntro(params: { const providerKey = rawProvider?.toLowerCase() ?? ""; const providerId = normalizeChannelId(rawProvider); const providerLabel = (() => { - if (!providerKey) return "chat"; - if (isInternalMessageChannel(providerKey)) return "WebChat"; - if (providerId) return getChannelPlugin(providerId)?.meta.label ?? providerId; + if (!providerKey) { + return "chat"; + } + if (isInternalMessageChannel(providerKey)) { + return "WebChat"; + } + if (providerId) { + return getChannelPlugin(providerId)?.meta.label ?? providerId; + } return `${providerKey.at(0)?.toUpperCase() ?? ""}${providerKey.slice(1)}`; })(); const subjectLine = subject diff --git a/src/auto-reply/reply/history.ts b/src/auto-reply/reply/history.ts index 45ad44d5a1..ab75f86534 100644 --- a/src/auto-reply/reply/history.ts +++ b/src/auto-reply/reply/history.ts @@ -14,12 +14,16 @@ export function evictOldHistoryKeys( historyMap: Map, maxKeys: number = MAX_HISTORY_KEYS, ): void { - if (historyMap.size <= maxKeys) return; + if (historyMap.size <= maxKeys) { + return; + } const keysToDelete = historyMap.size - maxKeys; const iterator = historyMap.keys(); for (let i = 0; i < keysToDelete; i++) { const key = iterator.next().value; - if (key !== undefined) historyMap.delete(key); + if (key !== undefined) { + historyMap.delete(key); + } } } @@ -37,7 +41,9 @@ export function buildHistoryContext(params: { }): string { const { historyText, currentMessage } = params; const lineBreak = params.lineBreak ?? "\n"; - if (!historyText.trim()) return currentMessage; + if (!historyText.trim()) { + return currentMessage; + } return [HISTORY_CONTEXT_MARKER, historyText, "", CURRENT_MESSAGE_MARKER, currentMessage].join( lineBreak, ); @@ -50,10 +56,14 @@ export function appendHistoryEntry(params: { limit: number; }): T[] { const { historyMap, historyKey, entry } = params; - if (params.limit <= 0) return []; + if (params.limit <= 0) { + return []; + } const history = historyMap.get(historyKey) ?? []; history.push(entry); - while (history.length > params.limit) history.shift(); + while (history.length > params.limit) { + history.shift(); + } if (historyMap.has(historyKey)) { // Refresh insertion order so eviction keeps recently used histories. historyMap.delete(historyKey); @@ -79,8 +89,12 @@ export function recordPendingHistoryEntryIfEnabled(param entry?: T | null; limit: number; }): T[] { - if (!params.entry) return []; - if (params.limit <= 0) return []; + if (!params.entry) { + return []; + } + if (params.limit <= 0) { + return []; + } return recordPendingHistoryEntry({ historyMap: params.historyMap, historyKey: params.historyKey, @@ -97,7 +111,9 @@ export function buildPendingHistoryContextFromMap(params: { formatEntry: (entry: HistoryEntry) => string; lineBreak?: string; }): string { - if (params.limit <= 0) return params.currentMessage; + if (params.limit <= 0) { + return params.currentMessage; + } const entries = params.historyMap.get(params.historyKey) ?? []; return buildHistoryContextFromEntries({ entries, @@ -118,7 +134,9 @@ export function buildHistoryContextFromMap(params: { lineBreak?: string; excludeLast?: boolean; }): string { - if (params.limit <= 0) return params.currentMessage; + if (params.limit <= 0) { + return params.currentMessage; + } const entries = params.entry ? appendHistoryEntry({ historyMap: params.historyMap, @@ -148,7 +166,9 @@ export function clearHistoryEntriesIfEnabled(params: { historyKey: string; limit: number; }): void { - if (params.limit <= 0) return; + if (params.limit <= 0) { + return; + } clearHistoryEntries({ historyMap: params.historyMap, historyKey: params.historyKey }); } @@ -161,7 +181,9 @@ export function buildHistoryContextFromEntries(params: { }): string { const lineBreak = params.lineBreak ?? "\n"; const entries = params.excludeLast === false ? params.entries : params.entries.slice(0, -1); - if (entries.length === 0) return params.currentMessage; + if (entries.length === 0) { + return params.currentMessage; + } const historyText = entries.map(params.formatEntry).join(lineBreak); return buildHistoryContext({ historyText, diff --git a/src/auto-reply/reply/inbound-context.ts b/src/auto-reply/reply/inbound-context.ts index 1af20f7e96..353e2b41d1 100644 --- a/src/auto-reply/reply/inbound-context.ts +++ b/src/auto-reply/reply/inbound-context.ts @@ -12,7 +12,9 @@ export type FinalizeInboundContextOptions = { }; function normalizeTextField(value: unknown): string | undefined { - if (typeof value !== "string") return undefined; + if (typeof value !== "string") { + return undefined; + } return normalizeInboundTextNewlines(value); } @@ -51,7 +53,9 @@ export function finalizeInboundContext>( const explicitLabel = normalized.ConversationLabel?.trim(); if (opts.forceConversationLabel || !explicitLabel) { const resolved = resolveConversationLabel(normalized)?.trim(); - if (resolved) normalized.ConversationLabel = resolved; + if (resolved) { + normalized.ConversationLabel = resolved; + } } else { normalized.ConversationLabel = explicitLabel; } diff --git a/src/auto-reply/reply/inbound-dedupe.ts b/src/auto-reply/reply/inbound-dedupe.ts index fdfba0a035..191e4c4f47 100644 --- a/src/auto-reply/reply/inbound-dedupe.ts +++ b/src/auto-reply/reply/inbound-dedupe.ts @@ -18,9 +18,13 @@ const resolveInboundPeerId = (ctx: MsgContext) => export function buildInboundDedupeKey(ctx: MsgContext): string | null { const provider = normalizeProvider(ctx.OriginatingChannel ?? ctx.Provider ?? ctx.Surface); const messageId = ctx.MessageSid?.trim(); - if (!provider || !messageId) return null; + if (!provider || !messageId) { + return null; + } const peerId = resolveInboundPeerId(ctx); - if (!peerId) return null; + if (!peerId) { + return null; + } const sessionKey = ctx.SessionKey?.trim() ?? ""; const accountId = ctx.AccountId?.trim() ?? ""; const threadId = @@ -35,7 +39,9 @@ export function shouldSkipDuplicateInbound( opts?: { cache?: DedupeCache; now?: number }, ): boolean { const key = buildInboundDedupeKey(ctx); - if (!key) return false; + if (!key) { + return false; + } const cache = opts?.cache ?? inboundDedupeCache; const skipped = cache.check(key, opts?.now); if (skipped && shouldLogVerbose()) { diff --git a/src/auto-reply/reply/inbound-sender-meta.ts b/src/auto-reply/reply/inbound-sender-meta.ts index d60b61e75c..5e8ce704ff 100644 --- a/src/auto-reply/reply/inbound-sender-meta.ts +++ b/src/auto-reply/reply/inbound-sender-meta.ts @@ -4,10 +4,16 @@ import { listSenderLabelCandidates, resolveSenderLabel } from "../../channels/se export function formatInboundBodyWithSenderMeta(params: { body: string; ctx: MsgContext }): string { const body = params.body; - if (!body.trim()) return body; + if (!body.trim()) { + return body; + } const chatType = normalizeChatType(params.ctx.ChatType); - if (!chatType || chatType === "direct") return body; - if (hasSenderMetaLine(body, params.ctx)) return body; + if (!chatType || chatType === "direct") { + return body; + } + if (hasSenderMetaLine(body, params.ctx)) { + return body; + } const senderLabel = resolveSenderLabel({ name: params.ctx.SenderName, @@ -16,13 +22,17 @@ export function formatInboundBodyWithSenderMeta(params: { body: string; ctx: Msg e164: params.ctx.SenderE164, id: params.ctx.SenderId, }); - if (!senderLabel) return body; + if (!senderLabel) { + return body; + } return `${body}\n[from: ${senderLabel}]`; } function hasSenderMetaLine(body: string, ctx: MsgContext): boolean { - if (/(^|\n)\[from:/i.test(body)) return true; + if (/(^|\n)\[from:/i.test(body)) { + return true; + } const candidates = listSenderLabelCandidates({ name: ctx.SenderName, username: ctx.SenderUsername, @@ -30,7 +40,9 @@ function hasSenderMetaLine(body: string, ctx: MsgContext): boolean { e164: ctx.SenderE164, id: ctx.SenderId, }); - if (candidates.length === 0) return false; + if (candidates.length === 0) { + return false; + } return candidates.some((candidate) => { const escaped = escapeRegExp(candidate); // Envelope bodies look like "[Signal ...] Alice: hi". diff --git a/src/auto-reply/reply/line-directives.ts b/src/auto-reply/reply/line-directives.ts index a28faeef7c..6c6cc41b9e 100644 --- a/src/auto-reply/reply/line-directives.ts +++ b/src/auto-reply/reply/line-directives.ts @@ -26,7 +26,9 @@ import { */ export function parseLineDirectives(payload: ReplyPayload): ReplyPayload { let text = payload.text; - if (!text) return payload; + if (!text) { + return payload; + } const result: ReplyPayload = { ...payload }; const lineData: LineChannelData = { @@ -121,9 +123,13 @@ export function parseLineDirectives(payload: ReplyPayload): ReplyPayload { // Find first colon delimiter, ignoring URLs without a label. const colonIndex = (() => { const index = trimmed.indexOf(":"); - if (index === -1) return -1; + if (index === -1) { + return -1; + } const lower = trimmed.toLowerCase(); - if (lower.startsWith("http://") || lower.startsWith("https://")) return -1; + if (lower.startsWith("http://") || lower.startsWith("https://")) { + return -1; + } return index; })(); diff --git a/src/auto-reply/reply/memory-flush.ts b/src/auto-reply/reply/memory-flush.ts index b05153eb7e..ad271f64c2 100644 --- a/src/auto-reply/reply/memory-flush.ts +++ b/src/auto-reply/reply/memory-flush.ts @@ -28,7 +28,9 @@ export type MemoryFlushSettings = { }; const normalizeNonNegativeInt = (value: unknown): number | null => { - if (typeof value !== "number" || !Number.isFinite(value)) return null; + if (typeof value !== "number" || !Number.isFinite(value)) { + return null; + } const int = Math.floor(value); return int >= 0 ? int : null; }; @@ -36,7 +38,9 @@ const normalizeNonNegativeInt = (value: unknown): number | null => { export function resolveMemoryFlushSettings(cfg?: OpenClawConfig): MemoryFlushSettings | null { const defaults = cfg?.agents?.defaults?.compaction?.memoryFlush; const enabled = defaults?.enabled ?? true; - if (!enabled) return null; + if (!enabled) { + return null; + } const softThresholdTokens = normalizeNonNegativeInt(defaults?.softThresholdTokens) ?? DEFAULT_MEMORY_FLUSH_SOFT_TOKENS; const prompt = defaults?.prompt?.trim() || DEFAULT_MEMORY_FLUSH_PROMPT; @@ -55,7 +59,9 @@ export function resolveMemoryFlushSettings(cfg?: OpenClawConfig): MemoryFlushSet } function ensureNoReplyHint(text: string): string { - if (text.includes(SILENT_REPLY_TOKEN)) return text; + if (text.includes(SILENT_REPLY_TOKEN)) { + return text; + } return `${text}\n\nIf no user-visible reply is needed, start with ${SILENT_REPLY_TOKEN}.`; } @@ -75,13 +81,19 @@ export function shouldRunMemoryFlush(params: { softThresholdTokens: number; }): boolean { const totalTokens = params.entry?.totalTokens; - if (!totalTokens || totalTokens <= 0) return false; + if (!totalTokens || totalTokens <= 0) { + return false; + } const contextWindow = Math.max(1, Math.floor(params.contextWindowTokens)); const reserveTokens = Math.max(0, Math.floor(params.reserveTokensFloor)); const softThreshold = Math.max(0, Math.floor(params.softThresholdTokens)); const threshold = Math.max(0, contextWindow - reserveTokens - softThreshold); - if (threshold <= 0) return false; - if (totalTokens < threshold) return false; + if (threshold <= 0) { + return false; + } + if (totalTokens < threshold) { + return false; + } const compactionCount = params.entry?.compactionCount ?? 0; const lastFlushAt = params.entry?.memoryFlushCompactionCount; diff --git a/src/auto-reply/reply/mentions.ts b/src/auto-reply/reply/mentions.ts index cf51ea27a4..f22b22d83d 100644 --- a/src/auto-reply/reply/mentions.ts +++ b/src/auto-reply/reply/mentions.ts @@ -28,7 +28,9 @@ const BACKSPACE_CHAR = "\u0008"; export const CURRENT_MESSAGE_MARKER = "[Current message - respond to this]"; function normalizeMentionPattern(pattern: string): string { - if (!pattern.includes(BACKSPACE_CHAR)) return pattern; + if (!pattern.includes(BACKSPACE_CHAR)) { + return pattern; + } return pattern.split(BACKSPACE_CHAR).join("\\b"); } @@ -37,7 +39,9 @@ function normalizeMentionPatterns(patterns: string[]): string[] { } function resolveMentionPatterns(cfg: OpenClawConfig | undefined, agentId?: string): string[] { - if (!cfg) return []; + if (!cfg) { + return []; + } const agentConfig = agentId ? resolveAgentConfig(cfg, agentId) : undefined; const agentGroupChat = agentConfig?.groupChat; if (agentGroupChat && Object.hasOwn(agentGroupChat, "mentionPatterns")) { @@ -69,9 +73,13 @@ export function normalizeMentionText(text: string): string { } export function matchesMentionPatterns(text: string, mentionRegexes: RegExp[]): boolean { - if (mentionRegexes.length === 0) return false; + if (mentionRegexes.length === 0) { + return false; + } const cleaned = normalizeMentionText(text ?? ""); - if (!cleaned) return false; + if (!cleaned) { + return false; + } return mentionRegexes.some((re) => re.test(cleaned)); } @@ -93,7 +101,9 @@ export function matchesMentionWithExplicit(params: { if (hasAnyMention && explicitAvailable) { return explicit || params.mentionRegexes.some((re) => re.test(cleaned)); } - if (!cleaned) return explicit; + if (!cleaned) { + return explicit; + } return explicit || params.mentionRegexes.some((re) => re.test(cleaned)); } diff --git a/src/auto-reply/reply/model-selection.ts b/src/auto-reply/reply/model-selection.ts index c106356d66..027791546c 100644 --- a/src/auto-reply/reply/model-selection.ts +++ b/src/auto-reply/reply/model-selection.ts @@ -48,11 +48,17 @@ const FUZZY_VARIANT_TOKENS = [ ]; function boundedLevenshteinDistance(a: string, b: string, maxDistance: number): number | null { - if (a === b) return 0; - if (!a || !b) return null; + if (a === b) { + return 0; + } + if (!a || !b) { + return null; + } const aLen = a.length; const bLen = b.length; - if (Math.abs(aLen - bLen) > maxDistance) return null; + if (Math.abs(aLen - bLen) > maxDistance) { + return null; + } // Standard DP with early exit. O(maxDistance * minLen) in common cases. const prev = Array.from({ length: bLen + 1 }, (_, idx) => idx); @@ -66,16 +72,24 @@ function boundedLevenshteinDistance(a: string, b: string, maxDistance: number): for (let j = 1; j <= bLen; j++) { const cost = aChar === b.charCodeAt(j - 1) ? 0 : 1; curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost); - if (curr[j] < rowMin) rowMin = curr[j]; + if (curr[j] < rowMin) { + rowMin = curr[j]; + } } - if (rowMin > maxDistance) return null; + if (rowMin > maxDistance) { + return null; + } - for (let j = 0; j <= bLen; j++) prev[j] = curr[j] ?? 0; + for (let j = 0; j <= bLen; j++) { + prev[j] = curr[j] ?? 0; + } } const dist = prev[bLen] ?? null; - if (dist == null || dist > maxDistance) return null; + if (dist == null || dist > maxDistance) { + return null; + } return dist; } @@ -90,7 +104,9 @@ function resolveModelOverrideFromEntry(entry?: SessionEntry): { model: string; } | null { const model = entry?.modelOverride?.trim(); - if (!model) return null; + if (!model) { + return null; + } const provider = entry?.providerOverride?.trim() || undefined; return { provider, model }; } @@ -100,9 +116,13 @@ function resolveParentSessionKeyCandidate(params: { parentSessionKey?: string; }): string | null { const explicit = params.parentSessionKey?.trim(); - if (explicit && explicit !== params.sessionKey) return explicit; + if (explicit && explicit !== params.sessionKey) { + return explicit; + } const derived = resolveThreadParentSessionKey(params.sessionKey); - if (derived && derived !== params.sessionKey) return derived; + if (derived && derived !== params.sessionKey) { + return derived; + } return null; } @@ -113,15 +133,21 @@ function resolveStoredModelOverride(params: { parentSessionKey?: string; }): StoredModelOverride | null { const direct = resolveModelOverrideFromEntry(params.sessionEntry); - if (direct) return { ...direct, source: "session" }; + if (direct) { + return { ...direct, source: "session" }; + } const parentKey = resolveParentSessionKeyCandidate({ sessionKey: params.sessionKey, parentSessionKey: params.parentSessionKey, }); - if (!parentKey || !params.sessionStore) return null; + if (!parentKey || !params.sessionStore) { + return null; + } const parentEntry = params.sessionStore[parentKey]; const parentOverride = resolveModelOverrideFromEntry(parentEntry); - if (!parentOverride) return null; + if (!parentOverride) { + return null; + } return { ...parentOverride, source: "parent" }; } @@ -152,11 +178,19 @@ function scoreFuzzyMatch(params: { value: string, weights: { exact: number; starts: number; includes: number }, ) => { - if (!fragment) return 0; + if (!fragment) { + return 0; + } let score = 0; - if (value === fragment) score = Math.max(score, weights.exact); - if (value.startsWith(fragment)) score = Math.max(score, weights.starts); - if (value.includes(fragment)) score = Math.max(score, weights.includes); + if (value === fragment) { + score = Math.max(score, weights.exact); + } + if (value.startsWith(fragment)) { + score = Math.max(score, weights.starts); + } + if (value.includes(fragment)) { + score = Math.max(score, weights.includes); + } return score; }; @@ -200,13 +234,19 @@ function scoreFuzzyMatch(params: { if (fragmentVariants.length === 0 && variantCount > 0) { score -= variantCount * 30; } else if (fragmentVariants.length > 0) { - if (variantMatchCount > 0) score += variantMatchCount * 40; - if (variantMatchCount === 0) score -= 20; + if (variantMatchCount > 0) { + score += variantMatchCount * 40; + } + if (variantMatchCount === 0) { + score -= 20; + } } const defaultProvider = normalizeProviderId(params.defaultProvider); const isDefault = provider === defaultProvider && model === params.defaultModel; - if (isDefault) score += 20; + if (isDefault) { + score += 20; + } return { score, @@ -331,7 +371,9 @@ export async function createModelSelectionState(params: { let defaultThinkingLevel: ThinkLevel | undefined; const resolveDefaultThinkingLevel = async () => { - if (defaultThinkingLevel) return defaultThinkingLevel; + if (defaultThinkingLevel) { + return defaultThinkingLevel; + } let catalogForThinking = modelCatalog ?? allowedModelCatalog; if (!catalogForThinking || catalogForThinking.length === 0) { modelCatalog = await loadModelCatalog({ config: cfg }); @@ -389,17 +431,23 @@ export function resolveModelDirectiveSelection(params: { fragment: string; }): { selection?: ModelDirectiveSelection; error?: string } => { const fragment = params.fragment.trim().toLowerCase(); - if (!fragment) return {}; + if (!fragment) { + return {}; + } const providerFilter = params.provider ? normalizeProviderId(params.provider) : undefined; const candidates: Array<{ provider: string; model: string }> = []; for (const key of allowedModelKeys) { const slash = key.indexOf("/"); - if (slash <= 0) continue; + if (slash <= 0) { + continue; + } const provider = normalizeProviderId(key.slice(0, slash)); const model = key.slice(slash + 1); - if (providerFilter && provider !== providerFilter) continue; + if (providerFilter && provider !== providerFilter) { + continue; + } candidates.push({ provider, model }); } @@ -407,7 +455,9 @@ export function resolveModelDirectiveSelection(params: { if (!params.provider) { const aliasMatches: Array<{ provider: string; model: string }> = []; for (const [aliasKey, entry] of aliasIndex.byAlias.entries()) { - if (!aliasKey.includes(fragment)) continue; + if (!aliasKey.includes(fragment)) { + continue; + } aliasMatches.push({ provider: entry.ref.provider, model: entry.ref.model, @@ -415,14 +465,18 @@ export function resolveModelDirectiveSelection(params: { } for (const match of aliasMatches) { const key = modelKey(match.provider, match.model); - if (!allowedModelKeys.has(key)) continue; + if (!allowedModelKeys.has(key)) { + continue; + } if (!candidates.some((c) => c.provider === match.provider && c.model === match.model)) { candidates.push(match); } } } - if (candidates.length === 0) return {}; + if (candidates.length === 0) { + return {}; + } const scored = candidates .map((candidate) => { @@ -437,21 +491,34 @@ export function resolveModelDirectiveSelection(params: { return Object.assign({ candidate }, details); }) .toSorted((a, b) => { - if (b.score !== a.score) return b.score - a.score; - if (a.isDefault !== b.isDefault) return a.isDefault ? -1 : 1; - if (a.variantMatchCount !== b.variantMatchCount) + if (b.score !== a.score) { + return b.score - a.score; + } + if (a.isDefault !== b.isDefault) { + return a.isDefault ? -1 : 1; + } + if (a.variantMatchCount !== b.variantMatchCount) { return b.variantMatchCount - a.variantMatchCount; - if (a.variantCount !== b.variantCount) return a.variantCount - b.variantCount; - if (a.modelLength !== b.modelLength) return a.modelLength - b.modelLength; + } + if (a.variantCount !== b.variantCount) { + return a.variantCount - b.variantCount; + } + if (a.modelLength !== b.modelLength) { + return a.modelLength - b.modelLength; + } return a.key.localeCompare(b.key); }); const bestScored = scored[0]; const best = bestScored?.candidate; - if (!best || !bestScored) return {}; + if (!best || !bestScored) { + return {}; + } const minScore = providerFilter ? 90 : 120; - if (bestScored.score < minScore) return {}; + if (bestScored.score < minScore) { + return {}; + } return { selection: buildSelection(best.provider, best.model) }; }; @@ -464,7 +531,9 @@ export function resolveModelDirectiveSelection(params: { if (!resolved) { const fuzzy = resolveFuzzy({ fragment: rawTrimmed }); - if (fuzzy.selection || fuzzy.error) return fuzzy; + if (fuzzy.selection || fuzzy.error) { + return fuzzy; + } return { error: `Unrecognized model "${rawTrimmed}". Use /models to list providers, or /models to list models.`, }; @@ -489,12 +558,16 @@ export function resolveModelDirectiveSelection(params: { const provider = normalizeProviderId(rawTrimmed.slice(0, slash).trim()); const fragment = rawTrimmed.slice(slash + 1).trim(); const fuzzy = resolveFuzzy({ provider, fragment }); - if (fuzzy.selection || fuzzy.error) return fuzzy; + if (fuzzy.selection || fuzzy.error) { + return fuzzy; + } } // Otherwise, try fuzzy matching across allowlisted models. const fuzzy = resolveFuzzy({ fragment: rawTrimmed }); - if (fuzzy.selection || fuzzy.error) return fuzzy; + if (fuzzy.selection || fuzzy.error) { + return fuzzy; + } return { error: `Model "${resolved.ref.provider}/${resolved.ref.model}" is not allowed. Use /models to list providers, or /models to list models.`, diff --git a/src/auto-reply/reply/normalize-reply.ts b/src/auto-reply/reply/normalize-reply.ts index 9a58bebde0..9b1d24eb95 100644 --- a/src/auto-reply/reply/normalize-reply.ts +++ b/src/auto-reply/reply/normalize-reply.ts @@ -51,7 +51,9 @@ export function normalizeReplyPayload( const shouldStripHeartbeat = opts.stripHeartbeat ?? true; if (shouldStripHeartbeat && text?.includes(HEARTBEAT_TOKEN)) { const stripped = stripHeartbeatToken(text, { mode: "message" }); - if (stripped.didStrip) opts.onHeartbeatStrip?.(); + if (stripped.didStrip) { + opts.onHeartbeatStrip?.(); + } if (stripped.shouldSkip && !hasMedia && !hasChannelData) { opts.onSkip?.("heartbeat"); return null; diff --git a/src/auto-reply/reply/queue/cleanup.ts b/src/auto-reply/reply/queue/cleanup.ts index 6f53c0185a..996f9ed476 100644 --- a/src/auto-reply/reply/queue/cleanup.ts +++ b/src/auto-reply/reply/queue/cleanup.ts @@ -16,7 +16,9 @@ export function clearSessionQueues(keys: Array): ClearSessio for (const key of keys) { const cleaned = key?.trim(); - if (!cleaned || seen.has(cleaned)) continue; + if (!cleaned || seen.has(cleaned)) { + continue; + } seen.add(cleaned); clearedKeys.push(cleaned); followupCleared += clearFollowupQueue(cleaned); diff --git a/src/auto-reply/reply/queue/directive.ts b/src/auto-reply/reply/queue/directive.ts index 426e7bc6f9..c906d82615 100644 --- a/src/auto-reply/reply/queue/directive.ts +++ b/src/auto-reply/reply/queue/directive.ts @@ -3,10 +3,14 @@ import { normalizeQueueDropPolicy, normalizeQueueMode } from "./normalize.js"; import type { QueueDropPolicy, QueueMode } from "./types.js"; function parseQueueDebounce(raw?: string): number | undefined { - if (!raw) return undefined; + if (!raw) { + return undefined; + } try { const parsed = parseDurationMs(raw.trim(), { defaultUnit: "ms" }); - if (!parsed || parsed < 0) return undefined; + if (!parsed || parsed < 0) { + return undefined; + } return Math.round(parsed); } catch { return undefined; @@ -14,11 +18,17 @@ function parseQueueDebounce(raw?: string): number | undefined { } function parseQueueCap(raw?: string): number | undefined { - if (!raw) return undefined; + if (!raw) { + return undefined; + } const num = Number(raw); - if (!Number.isFinite(num)) return undefined; + if (!Number.isFinite(num)) { + return undefined; + } const cap = Math.floor(num); - if (cap < 1) return undefined; + if (cap < 1) { + return undefined; + } return cap; } @@ -37,10 +47,14 @@ function parseQueueDirectiveArgs(raw: string): { } { let i = 0; const len = raw.length; - while (i < len && /\s/.test(raw[i])) i += 1; + while (i < len && /\s/.test(raw[i])) { + i += 1; + } if (raw[i] === ":") { i += 1; - while (i < len && /\s/.test(raw[i])) i += 1; + while (i < len && /\s/.test(raw[i])) { + i += 1; + } } let consumed = i; let queueMode: QueueMode | undefined; @@ -54,17 +68,27 @@ function parseQueueDirectiveArgs(raw: string): { let rawDrop: string | undefined; let hasOptions = false; const takeToken = (): string | null => { - if (i >= len) return null; + if (i >= len) { + return null; + } const start = i; - while (i < len && !/\s/.test(raw[i])) i += 1; - if (start === i) return null; + while (i < len && !/\s/.test(raw[i])) { + i += 1; + } + if (start === i) { + return null; + } const token = raw.slice(start, i); - while (i < len && /\s/.test(raw[i])) i += 1; + while (i < len && /\s/.test(raw[i])) { + i += 1; + } return token; }; while (i < len) { const token = takeToken(); - if (!token) break; + if (!token) { + break; + } const lowered = token.trim().toLowerCase(); if (lowered === "default" || lowered === "reset" || lowered === "clear") { queueReset = true; diff --git a/src/auto-reply/reply/queue/drain.ts b/src/auto-reply/reply/queue/drain.ts index 01361a1ecc..addc91b4a6 100644 --- a/src/auto-reply/reply/queue/drain.ts +++ b/src/auto-reply/reply/queue/drain.ts @@ -14,7 +14,9 @@ export function scheduleFollowupDrain( runFollowup: (run: FollowupRun) => Promise, ): void { const queue = FOLLOWUP_QUEUES.get(key); - if (!queue || queue.draining) return; + if (!queue || queue.draining) { + return; + } queue.draining = true; void (async () => { try { @@ -28,7 +30,9 @@ export function scheduleFollowupDrain( // Debug: `pnpm test src/auto-reply/reply/queue.collect-routing.test.ts` if (forceIndividualCollect) { const next = queue.items.shift(); - if (!next) break; + if (!next) { + break; + } await runFollowup(next); continue; } @@ -55,7 +59,9 @@ export function scheduleFollowupDrain( if (isCrossChannel) { forceIndividualCollect = true; const next = queue.items.shift(); - if (!next) break; + if (!next) { + break; + } await runFollowup(next); continue; } @@ -63,7 +69,9 @@ export function scheduleFollowupDrain( const items = queue.items.splice(0, queue.items.length); const summary = buildQueueSummaryPrompt({ state: queue, noun: "message" }); const run = items.at(-1)?.run ?? queue.lastRun; - if (!run) break; + if (!run) { + break; + } // Preserve originating channel from items when collecting same-channel. const originatingChannel = items.find((i) => i.originatingChannel)?.originatingChannel; @@ -96,7 +104,9 @@ export function scheduleFollowupDrain( const summaryPrompt = buildQueueSummaryPrompt({ state: queue, noun: "message" }); if (summaryPrompt) { const run = queue.lastRun; - if (!run) break; + if (!run) { + break; + } await runFollowup({ prompt: summaryPrompt, run, @@ -106,7 +116,9 @@ export function scheduleFollowupDrain( } const next = queue.items.shift(); - if (!next) break; + if (!next) { + break; + } await runFollowup(next); } } catch (err) { diff --git a/src/auto-reply/reply/queue/enqueue.ts b/src/auto-reply/reply/queue/enqueue.ts index 7b242adbcd..f5444c0a96 100644 --- a/src/auto-reply/reply/queue/enqueue.ts +++ b/src/auto-reply/reply/queue/enqueue.ts @@ -17,7 +17,9 @@ function isRunAlreadyQueued( if (messageId) { return items.some((item) => item.messageId?.trim() === messageId && hasSameRouting(item)); } - if (!allowPromptFallback) return false; + if (!allowPromptFallback) { + return false; + } return items.some((item) => item.prompt === run.prompt && hasSameRouting(item)); } @@ -35,7 +37,9 @@ export function enqueueFollowupRun( isRunAlreadyQueued(item, items, dedupeMode === "prompt"); // Deduplicate: skip if the same message is already queued. - if (shouldSkipQueueItem({ item: run, items: queue.items, dedupe })) return false; + if (shouldSkipQueueItem({ item: run, items: queue.items, dedupe })) { + return false; + } queue.lastEnqueuedAt = Date.now(); queue.lastRun = run.run; @@ -44,7 +48,9 @@ export function enqueueFollowupRun( queue, summarize: (item) => item.summaryLine?.trim() || item.prompt.trim(), }); - if (!shouldEnqueue) return false; + if (!shouldEnqueue) { + return false; + } queue.items.push(run); return true; @@ -52,8 +58,12 @@ export function enqueueFollowupRun( export function getFollowupQueueDepth(key: string): number { const cleaned = key.trim(); - if (!cleaned) return 0; + if (!cleaned) { + return 0; + } const queue = FOLLOWUP_QUEUES.get(cleaned); - if (!queue) return 0; + if (!queue) { + return 0; + } return queue.items.length; } diff --git a/src/auto-reply/reply/queue/normalize.ts b/src/auto-reply/reply/queue/normalize.ts index eac9b3bd1e..33545bb997 100644 --- a/src/auto-reply/reply/queue/normalize.ts +++ b/src/auto-reply/reply/queue/normalize.ts @@ -1,25 +1,44 @@ import type { QueueDropPolicy, QueueMode } from "./types.js"; export function normalizeQueueMode(raw?: string): QueueMode | undefined { - if (!raw) return undefined; + if (!raw) { + return undefined; + } const cleaned = raw.trim().toLowerCase(); - if (cleaned === "queue" || cleaned === "queued") return "steer"; - if (cleaned === "interrupt" || cleaned === "interrupts" || cleaned === "abort") + if (cleaned === "queue" || cleaned === "queued") { + return "steer"; + } + if (cleaned === "interrupt" || cleaned === "interrupts" || cleaned === "abort") { return "interrupt"; - if (cleaned === "steer" || cleaned === "steering") return "steer"; - if (cleaned === "followup" || cleaned === "follow-ups" || cleaned === "followups") + } + if (cleaned === "steer" || cleaned === "steering") { + return "steer"; + } + if (cleaned === "followup" || cleaned === "follow-ups" || cleaned === "followups") { return "followup"; - if (cleaned === "collect" || cleaned === "coalesce") return "collect"; - if (cleaned === "steer+backlog" || cleaned === "steer-backlog" || cleaned === "steer_backlog") + } + if (cleaned === "collect" || cleaned === "coalesce") { + return "collect"; + } + if (cleaned === "steer+backlog" || cleaned === "steer-backlog" || cleaned === "steer_backlog") { return "steer-backlog"; + } return undefined; } export function normalizeQueueDropPolicy(raw?: string): QueueDropPolicy | undefined { - if (!raw) return undefined; + if (!raw) { + return undefined; + } const cleaned = raw.trim().toLowerCase(); - if (cleaned === "old" || cleaned === "oldest") return "old"; - if (cleaned === "new" || cleaned === "newest") return "new"; - if (cleaned === "summarize" || cleaned === "summary") return "summarize"; + if (cleaned === "old" || cleaned === "oldest") { + return "old"; + } + if (cleaned === "new" || cleaned === "newest") { + return "new"; + } + if (cleaned === "summarize" || cleaned === "summary") { + return "summarize"; + } return undefined; } diff --git a/src/auto-reply/reply/queue/settings.ts b/src/auto-reply/reply/queue/settings.ts index 9591a8bc4e..4aec6d2375 100644 --- a/src/auto-reply/reply/queue/settings.ts +++ b/src/auto-reply/reply/queue/settings.ts @@ -13,13 +13,17 @@ function resolveChannelDebounce( byChannel: InboundDebounceByProvider | undefined, channelKey: string | undefined, ): number | undefined { - if (!channelKey || !byChannel) return undefined; + if (!channelKey || !byChannel) { + return undefined; + } const value = byChannel[channelKey]; return typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : undefined; } function resolvePluginDebounce(channelKey: string | undefined): number | undefined { - if (!channelKey) return undefined; + if (!channelKey) { + return undefined; + } const plugin = getChannelPlugin(channelKey); const value = plugin?.defaults?.queue?.debounceMs; return typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : undefined; diff --git a/src/auto-reply/reply/queue/state.ts b/src/auto-reply/reply/queue/state.ts index 0357ff8c86..816926622d 100644 --- a/src/auto-reply/reply/queue/state.ts +++ b/src/auto-reply/reply/queue/state.ts @@ -58,9 +58,13 @@ export function getFollowupQueue(key: string, settings: QueueSettings): Followup export function clearFollowupQueue(key: string): number { const cleaned = key.trim(); - if (!cleaned) return 0; + if (!cleaned) { + return 0; + } const queue = FOLLOWUP_QUEUES.get(cleaned); - if (!queue) return 0; + if (!queue) { + return 0; + } const cleared = queue.items.length + queue.droppedCount; queue.items.length = 0; queue.droppedCount = 0; diff --git a/src/auto-reply/reply/reply-dispatcher.ts b/src/auto-reply/reply/reply-dispatcher.ts index fd7fb5493e..090571a2e9 100644 --- a/src/auto-reply/reply/reply-dispatcher.ts +++ b/src/auto-reply/reply/reply-dispatcher.ts @@ -24,12 +24,16 @@ const DEFAULT_HUMAN_DELAY_MAX_MS = 2500; /** Generate a random delay within the configured range. */ function getHumanDelay(config: HumanDelayConfig | undefined): number { const mode = config?.mode ?? "off"; - if (mode === "off") return 0; + if (mode === "off") { + return 0; + } const min = mode === "custom" ? (config?.minMs ?? DEFAULT_HUMAN_DELAY_MIN_MS) : DEFAULT_HUMAN_DELAY_MIN_MS; const max = mode === "custom" ? (config?.maxMs ?? DEFAULT_HUMAN_DELAY_MAX_MS) : DEFAULT_HUMAN_DELAY_MAX_MS; - if (max <= min) return min; + if (max <= min) { + return min; + } return Math.floor(Math.random() * (max - min + 1)) + min; } @@ -115,20 +119,26 @@ export function createReplyDispatcher(options: ReplyDispatcherOptions): ReplyDis onHeartbeatStrip: options.onHeartbeatStrip, onSkip: (reason) => options.onSkip?.(payload, { kind, reason }), }); - if (!normalized) return false; + if (!normalized) { + return false; + } queuedCounts[kind] += 1; pending += 1; // Determine if we should add human-like delay (only for block replies after the first). const shouldDelay = kind === "block" && sentFirstBlock; - if (kind === "block") sentFirstBlock = true; + if (kind === "block") { + sentFirstBlock = true; + } sendChain = sendChain .then(async () => { // Add human-like delay between block replies for natural rhythm. if (shouldDelay) { const delayMs = getHumanDelay(options.humanDelay); - if (delayMs > 0) await sleep(delayMs); + if (delayMs > 0) { + await sleep(delayMs); + } } await options.deliver(normalized, { kind }); }) diff --git a/src/auto-reply/reply/reply-elevated.ts b/src/auto-reply/reply/reply-elevated.ts index 7a657bdb89..04372f0a04 100644 --- a/src/auto-reply/reply/reply-elevated.ts +++ b/src/auto-reply/reply/reply-elevated.ts @@ -8,14 +8,20 @@ import { formatCliCommand } from "../../cli/command-format.js"; import type { MsgContext } from "../templating.js"; function normalizeAllowToken(value?: string) { - if (!value) return ""; + if (!value) { + return ""; + } return value.trim().toLowerCase(); } function slugAllowToken(value?: string) { - if (!value) return ""; + if (!value) { + return ""; + } let text = value.trim().toLowerCase(); - if (!text) return ""; + if (!text) { + return ""; + } text = text.replace(/^[@#]+/, ""); text = text.replace(/[\s_]+/g, "-"); text = text.replace(/[^a-z0-9-]+/g, "-"); @@ -32,7 +38,9 @@ const SENDER_PREFIXES = [ const SENDER_PREFIX_RE = new RegExp(`^(${SENDER_PREFIXES.join("|")}):`, "i"); function stripSenderPrefix(value?: string) { - if (!value) return ""; + if (!value) { + return ""; + } const trimmed = value.trim(); return trimmed.replace(SENDER_PREFIX_RE, ""); } @@ -42,7 +50,9 @@ function resolveElevatedAllowList( provider: string, fallbackAllowFrom?: Array, ): Array | undefined { - if (!allowFrom) return fallbackAllowFrom; + if (!allowFrom) { + return fallbackAllowFrom; + } const value = allowFrom[provider]; return Array.isArray(value) ? value : fallbackAllowFrom; } @@ -58,22 +68,36 @@ function isApprovedElevatedSender(params: { params.provider, params.fallbackAllowFrom, ); - if (!rawAllow || rawAllow.length === 0) return false; + if (!rawAllow || rawAllow.length === 0) { + return false; + } const allowTokens = rawAllow.map((entry) => String(entry).trim()).filter(Boolean); - if (allowTokens.length === 0) return false; - if (allowTokens.some((entry) => entry === "*")) return true; + if (allowTokens.length === 0) { + return false; + } + if (allowTokens.some((entry) => entry === "*")) { + return true; + } const tokens = new Set(); const addToken = (value?: string) => { - if (!value) return; + if (!value) { + return; + } const trimmed = value.trim(); - if (!trimmed) return; + if (!trimmed) { + return; + } tokens.add(trimmed); const normalized = normalizeAllowToken(trimmed); - if (normalized) tokens.add(normalized); + if (normalized) { + tokens.add(normalized); + } const slugged = slugAllowToken(trimmed); - if (slugged) tokens.add(slugged); + if (slugged) { + tokens.add(slugged); + } }; addToken(params.ctx.SenderName); @@ -87,13 +111,21 @@ function isApprovedElevatedSender(params: { for (const rawEntry of allowTokens) { const entry = rawEntry.trim(); - if (!entry) continue; + if (!entry) { + continue; + } const stripped = stripSenderPrefix(entry); - if (tokens.has(entry) || tokens.has(stripped)) return true; + if (tokens.has(entry) || tokens.has(stripped)) { + return true; + } const normalized = normalizeAllowToken(stripped); - if (normalized && tokens.has(normalized)) return true; + if (normalized && tokens.has(normalized)) { + return true; + } const slugged = slugAllowToken(stripped); - if (slugged && tokens.has(slugged)) return true; + if (slugged && tokens.has(slugged)) { + return true; + } } return false; @@ -115,13 +147,18 @@ export function resolveElevatedPermissions(params: { const agentEnabled = agentConfig?.enabled !== false; const enabled = globalEnabled && agentEnabled; const failures: Array<{ gate: string; key: string }> = []; - if (!globalEnabled) failures.push({ gate: "enabled", key: "tools.elevated.enabled" }); - if (!agentEnabled) + if (!globalEnabled) { + failures.push({ gate: "enabled", key: "tools.elevated.enabled" }); + } + if (!agentEnabled) { failures.push({ gate: "enabled", key: "agents.list[].tools.elevated.enabled", }); - if (!enabled) return { enabled, allowed: false, failures }; + } + if (!enabled) { + return { enabled, allowed: false, failures }; + } if (!params.provider) { failures.push({ gate: "provider", key: "ctx.Provider" }); return { enabled, allowed: false, failures }; diff --git a/src/auto-reply/reply/reply-inline.ts b/src/auto-reply/reply/reply-inline.ts index 37fb4fb8b8..dc3c4e9742 100644 --- a/src/auto-reply/reply/reply-inline.ts +++ b/src/auto-reply/reply/reply-inline.ts @@ -12,12 +12,18 @@ export function extractInlineSimpleCommand(body?: string): { command: string; cleaned: string; } | null { - if (!body) return null; + if (!body) { + return null; + } const match = body.match(INLINE_SIMPLE_COMMAND_RE); - if (!match || match.index === undefined) return null; + if (!match || match.index === undefined) { + return null; + } const alias = `/${match[1].toLowerCase()}`; const command = INLINE_SIMPLE_COMMAND_ALIASES.get(alias); - if (!command) return null; + if (!command) { + return null; + } const cleaned = body.replace(match[0], " ").replace(/\s+/g, " ").trim(); return { command, cleaned }; } @@ -27,7 +33,9 @@ export function stripInlineStatus(body: string): { didStrip: boolean; } { const trimmed = body.trim(); - if (!trimmed) return { cleaned: "", didStrip: false }; + if (!trimmed) { + return { cleaned: "", didStrip: false }; + } const cleaned = trimmed.replace(INLINE_STATUS_RE, " ").replace(/\s+/g, " ").trim(); return { cleaned, didStrip: cleaned !== trimmed }; } diff --git a/src/auto-reply/reply/reply-payloads.ts b/src/auto-reply/reply/reply-payloads.ts index ecb28cf00e..0bc7e63a08 100644 --- a/src/auto-reply/reply/reply-payloads.ts +++ b/src/auto-reply/reply/reply-payloads.ts @@ -12,7 +12,9 @@ export function applyReplyTagsToPayload( currentMessageId?: string, ): ReplyPayload { if (typeof payload.text !== "string") { - if (!payload.replyToCurrent || payload.replyToId) return payload; + if (!payload.replyToCurrent || payload.replyToId) { + return payload; + } return { ...payload, replyToId: currentMessageId?.trim() || undefined, @@ -20,7 +22,9 @@ export function applyReplyTagsToPayload( } const shouldParseTags = payload.text.includes("[["); if (!shouldParseTags) { - if (!payload.replyToCurrent || payload.replyToId) return payload; + if (!payload.replyToCurrent || payload.replyToId) { + return payload; + } return { ...payload, replyToId: currentMessageId?.trim() || undefined, @@ -69,7 +73,9 @@ export function filterMessagingToolDuplicates(params: { sentTexts: string[]; }): ReplyPayload[] { const { payloads, sentTexts } = params; - if (sentTexts.length === 0) return payloads; + if (sentTexts.length === 0) { + return payloads; + } return payloads.filter((payload) => !isMessagingToolDuplicate(payload.text ?? "", sentTexts)); } @@ -85,17 +91,29 @@ export function shouldSuppressMessagingToolReplies(params: { accountId?: string; }): boolean { const provider = params.messageProvider?.trim().toLowerCase(); - if (!provider) return false; + if (!provider) { + return false; + } const originTarget = normalizeTargetForProvider(provider, params.originatingTo); - if (!originTarget) return false; + if (!originTarget) { + return false; + } const originAccount = normalizeAccountId(params.accountId); const sentTargets = params.messagingToolSentTargets ?? []; - if (sentTargets.length === 0) return false; + if (sentTargets.length === 0) { + return false; + } return sentTargets.some((target) => { - if (!target?.provider) return false; - if (target.provider.trim().toLowerCase() !== provider) return false; + if (!target?.provider) { + return false; + } + if (target.provider.trim().toLowerCase() !== provider) { + return false; + } const targetKey = normalizeTargetForProvider(provider, target.to); - if (!targetKey) return false; + if (!targetKey) { + return false; + } const targetAccount = normalizeAccountId(target.accountId); if (originAccount && targetAccount && originAccount !== targetAccount) { return false; diff --git a/src/auto-reply/reply/reply-reference.ts b/src/auto-reply/reply/reply-reference.ts index 33dda2098b..aba099afd8 100644 --- a/src/auto-reply/reply/reply-reference.ts +++ b/src/auto-reply/reply/reply-reference.ts @@ -26,13 +26,19 @@ export function createReplyReferencePlanner(options: { const startId = options.startId?.trim(); const use = (): string | undefined => { - if (!allowReference) return undefined; + if (!allowReference) { + return undefined; + } if (existingId) { hasReplied = true; return existingId; } - if (!startId) return undefined; - if (options.replyToMode === "off") return undefined; + if (!startId) { + return undefined; + } + if (options.replyToMode === "off") { + return undefined; + } if (options.replyToMode === "all") { hasReplied = true; return startId; diff --git a/src/auto-reply/reply/reply-threading.ts b/src/auto-reply/reply/reply-threading.ts index 9526812917..140e2837dc 100644 --- a/src/auto-reply/reply/reply-threading.ts +++ b/src/auto-reply/reply/reply-threading.ts @@ -12,7 +12,9 @@ export function resolveReplyToMode( chatType?: string | null, ): ReplyToMode { const provider = normalizeChannelId(channel); - if (!provider) return "all"; + if (!provider) { + return "all"; + } const resolved = getChannelDock(provider)?.threading?.resolveReplyToMode?.({ cfg, accountId, @@ -27,12 +29,18 @@ export function createReplyToModeFilter( ) { let hasThreaded = false; return (payload: ReplyPayload): ReplyPayload => { - if (!payload.replyToId) return payload; + if (!payload.replyToId) { + return payload; + } if (mode === "off") { - if (opts.allowTagsWhenOff && payload.replyToTag) return payload; + if (opts.allowTagsWhenOff && payload.replyToTag) { + return payload; + } return { ...payload, replyToId: undefined }; } - if (mode === "all") return payload; + if (mode === "all") { + return payload; + } if (hasThreaded) { return { ...payload, replyToId: undefined }; } diff --git a/src/auto-reply/reply/response-prefix-template.ts b/src/auto-reply/reply/response-prefix-template.ts index 788531ca53..6558d9fbf3 100644 --- a/src/auto-reply/reply/response-prefix-template.ts +++ b/src/auto-reply/reply/response-prefix-template.ts @@ -39,7 +39,9 @@ export function resolveResponsePrefixTemplate( template: string | undefined, context: ResponsePrefixContext, ): string | undefined { - if (!template) return undefined; + if (!template) { + return undefined; + } return template.replace(TEMPLATE_VAR_PATTERN, (match, varName: string) => { const normalizedVar = varName.toLowerCase(); @@ -90,7 +92,9 @@ export function extractShortModelName(fullModel: string): string { * Check if a template string contains any template variables. */ export function hasTemplateVariables(template: string | undefined): boolean { - if (!template) return false; + if (!template) { + return false; + } // Reset lastIndex since we're using a global regex TEMPLATE_VAR_PATTERN.lastIndex = 0; return TEMPLATE_VAR_PATTERN.test(template); diff --git a/src/auto-reply/reply/route-reply.ts b/src/auto-reply/reply/route-reply.ts index 700d28a343..9500a294a3 100644 --- a/src/auto-reply/reply/route-reply.ts +++ b/src/auto-reply/reply/route-reply.ts @@ -72,7 +72,9 @@ export async function routeReply(params: RouteReplyParams): Promise { - if (!channel || channel === INTERNAL_MESSAGE_CHANNEL) return false; + if (!channel || channel === INTERNAL_MESSAGE_CHANNEL) { + return false; + } return normalizeChannelId(channel) !== null; } diff --git a/src/auto-reply/reply/session-reset-model.ts b/src/auto-reply/reply/session-reset-model.ts index 6b80ecf414..34364d6bbe 100644 --- a/src/auto-reply/reply/session-reset-model.ts +++ b/src/auto-reply/reply/session-reset-model.ts @@ -41,9 +41,13 @@ function buildSelectionFromExplicit(params: { defaultProvider: params.defaultProvider, aliasIndex: params.aliasIndex, }); - if (!resolved) return undefined; + if (!resolved) { + return undefined; + } const key = modelKey(resolved.ref.provider, resolved.ref.model); - if (params.allowedModelKeys.size > 0 && !params.allowedModelKeys.has(key)) return undefined; + if (params.allowedModelKeys.size > 0 && !params.allowedModelKeys.has(key)) { + return undefined; + } const isDefault = resolved.ref.provider === params.defaultProvider && resolved.ref.model === params.defaultModel; return { @@ -62,12 +66,16 @@ function applySelectionToSession(params: { storePath?: string; }) { const { selection, sessionEntry, sessionStore, sessionKey, storePath } = params; - if (!sessionEntry || !sessionStore || !sessionKey) return; + if (!sessionEntry || !sessionStore || !sessionKey) { + return; + } const { updated } = applyModelOverrideToSessionEntry({ entry: sessionEntry, selection, }); - if (!updated) return; + if (!updated) { + return; + } sessionStore[sessionKey] = sessionEntry; if (storePath) { updateSessionStore(storePath, (store) => { @@ -92,12 +100,18 @@ export async function applyResetModelOverride(params: { defaultModel: string; aliasIndex: ModelAliasIndex; }): Promise { - if (!params.resetTriggered) return {}; + if (!params.resetTriggered) { + return {}; + } const rawBody = params.bodyStripped?.trim(); - if (!rawBody) return {}; + if (!rawBody) { + return {}; + } const { tokens, first, second } = splitBody(rawBody); - if (!first) return {}; + if (!first) { + return {}; + } const catalog = await loadModelCatalog({ config: params.cfg }); const allowed = buildAllowedModelSet({ @@ -107,12 +121,16 @@ export async function applyResetModelOverride(params: { defaultModel: params.defaultModel, }); const allowedModelKeys = allowed.allowedKeys; - if (allowedModelKeys.size === 0) return {}; + if (allowedModelKeys.size === 0) { + return {}; + } const providers = new Set(); for (const key of allowedModelKeys) { const slash = key.indexOf("/"); - if (slash <= 0) continue; + if (slash <= 0) { + continue; + } providers.add(normalizeProviderId(key.slice(0, slash))); } @@ -145,7 +163,9 @@ export async function applyResetModelOverride(params: { aliasIndex: params.aliasIndex, allowedModelKeys, }); - if (selection) consumed = 1; + if (selection) { + consumed = 1; + } } if (!selection) { @@ -153,11 +173,15 @@ export async function applyResetModelOverride(params: { const allowFuzzy = providers.has(normalizeProviderId(first)) || first.trim().length >= 6; if (allowFuzzy) { selection = resolved.selection; - if (selection) consumed = 1; + if (selection) { + consumed = 1; + } } } - if (!selection) return {}; + if (!selection) { + return {}; + } const cleanedBody = tokens.slice(consumed).join(" ").trim(); params.sessionCtx.BodyStripped = formatInboundBodyWithSenderMeta({ diff --git a/src/auto-reply/reply/session-updates.ts b/src/auto-reply/reply/session-updates.ts index 2eebf4b20c..7787603a6c 100644 --- a/src/auto-reply/reply/session-updates.ts +++ b/src/auto-reply/reply/session-updates.ts @@ -18,14 +18,22 @@ export async function prependSystemEvents(params: { }): Promise { const compactSystemEvent = (line: string): string | null => { const trimmed = line.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const lower = trimmed.toLowerCase(); - if (lower.includes("reason periodic")) return null; + if (lower.includes("reason periodic")) { + return null; + } // Filter out the actual heartbeat prompt, but not cron jobs that mention "heartbeat" // The heartbeat prompt starts with "Read HEARTBEAT.md" - cron payloads won't match this - if (lower.startsWith("read heartbeat.md")) return null; + if (lower.startsWith("read heartbeat.md")) { + return null; + } // Also filter heartbeat poll/wake noise - if (lower.includes("heartbeat poll") || lower.includes("heartbeat wake")) return null; + if (lower.includes("heartbeat poll") || lower.includes("heartbeat wake")) { + return null; + } if (trimmed.startsWith("Node:")) { return trimmed.replace(/ · last input [^·]+/i, "").trim(); } @@ -43,10 +51,16 @@ export async function prependSystemEvents(params: { const resolveSystemEventTimezone = (cfg: OpenClawConfig) => { const raw = cfg.agents?.defaults?.envelopeTimezone?.trim(); - if (!raw) return { mode: "local" as const }; + if (!raw) { + return { mode: "local" as const }; + } const lowered = raw.toLowerCase(); - if (lowered === "utc" || lowered === "gmt") return { mode: "utc" as const }; - if (lowered === "local" || lowered === "host") return { mode: "local" as const }; + if (lowered === "utc" || lowered === "gmt") { + return { mode: "utc" as const }; + } + if (lowered === "local" || lowered === "host") { + return { mode: "local" as const }; + } if (lowered === "user") { return { mode: "iana" as const, @@ -90,16 +104,24 @@ export async function prependSystemEvents(params: { .toReversed() .find((part) => part.type === "timeZoneName") ?.value?.trim(); - if (!yyyy || !mm || !dd || !hh || !min || !sec) return undefined; + if (!yyyy || !mm || !dd || !hh || !min || !sec) { + return undefined; + } return `${yyyy}-${mm}-${dd} ${hh}:${min}:${sec}${tz ? ` ${tz}` : ""}`; }; const formatSystemEventTimestamp = (ts: number, cfg: OpenClawConfig) => { const date = new Date(ts); - if (Number.isNaN(date.getTime())) return "unknown-time"; + if (Number.isNaN(date.getTime())) { + return "unknown-time"; + } const zone = resolveSystemEventTimezone(cfg); - if (zone.mode === "utc") return formatUtcTimestamp(date); - if (zone.mode === "local") return formatZonedTimestamp(date) ?? "unknown-time"; + if (zone.mode === "utc") { + return formatUtcTimestamp(date); + } + if (zone.mode === "local") { + return formatZonedTimestamp(date) ?? "unknown-time"; + } return formatZonedTimestamp(date, zone.timeZone) ?? "unknown-time"; }; @@ -109,16 +131,22 @@ export async function prependSystemEvents(params: { ...queued .map((event) => { const compacted = compactSystemEvent(event.text); - if (!compacted) return null; + if (!compacted) { + return null; + } return `[${formatSystemEventTimestamp(event.ts, params.cfg)}] ${compacted}`; }) .filter((v): v is string => Boolean(v)), ); if (params.isMainSession && params.isNewSession) { const summary = await buildChannelSummary(params.cfg); - if (summary.length > 0) systemLines.unshift(...summary); + if (summary.length > 0) { + systemLines.unshift(...summary); + } + } + if (systemLines.length === 0) { + return params.prefixedBodyBase; } - if (systemLines.length === 0) return params.prefixedBodyBase; const block = systemLines.map((l) => `System: ${l}`).join("\n"); return `${block}\n\n${params.prefixedBodyBase}`; @@ -252,9 +280,13 @@ export async function incrementCompactionCount(params: { now = Date.now(), tokensAfter, } = params; - if (!sessionStore || !sessionKey) return undefined; + if (!sessionStore || !sessionKey) { + return undefined; + } const entry = sessionStore[sessionKey] ?? sessionEntry; - if (!entry) return undefined; + if (!entry) { + return undefined; + } const nextCount = (entry.compactionCount ?? 0) + 1; // Build update payload with compaction count and optionally updated token counts const updates: Partial = { diff --git a/src/auto-reply/reply/session-usage.ts b/src/auto-reply/reply/session-usage.ts index 3d5226258c..8ef885d1a1 100644 --- a/src/auto-reply/reply/session-usage.ts +++ b/src/auto-reply/reply/session-usage.ts @@ -19,7 +19,9 @@ export async function persistSessionUsageUpdate(params: { logLabel?: string; }): Promise { const { storePath, sessionKey } = params; - if (!storePath || !sessionKey) return; + if (!storePath || !sessionKey) { + return; + } const label = params.logLabel ? `${params.logLabel} ` : ""; if (hasNonzeroUsage(params.usage)) { diff --git a/src/auto-reply/reply/session.ts b/src/auto-reply/reply/session.ts index cbf7e2cf2f..fff056516f 100644 --- a/src/auto-reply/reply/session.ts +++ b/src/auto-reply/reply/session.ts @@ -60,14 +60,18 @@ function forkSessionFromParent(params: { params.parentEntry.sessionId, params.parentEntry, ); - if (!parentSessionFile || !fs.existsSync(parentSessionFile)) return null; + if (!parentSessionFile || !fs.existsSync(parentSessionFile)) { + return null; + } try { const manager = SessionManager.open(parentSessionFile); const leafId = manager.getLeafId(); if (leafId) { const sessionFile = manager.createBranchedSession(leafId) ?? manager.getSessionFile(); const sessionId = manager.getSessionId(); - if (sessionFile && sessionId) return { sessionId, sessionFile }; + if (sessionFile && sessionId) { + return { sessionId, sessionFile }; + } } const sessionId = crypto.randomUUID(); const timestamp = new Date().toISOString(); @@ -165,8 +169,12 @@ export async function initSessionState(params: { const strippedForResetLower = strippedForReset.toLowerCase(); for (const trigger of resetTriggers) { - if (!trigger) continue; - if (!resetAuthorized) break; + if (!trigger) { + continue; + } + if (!resetAuthorized) { + break; + } const triggerLower = trigger.toLowerCase(); if (trimmedBodyLower === triggerLower || strippedForResetLower === triggerLower) { isNewSession = true; diff --git a/src/auto-reply/reply/stage-sandbox-media.ts b/src/auto-reply/reply/stage-sandbox-media.ts index 55fb9bd78d..d2a910249e 100644 --- a/src/auto-reply/reply/stage-sandbox-media.ts +++ b/src/auto-reply/reply/stage-sandbox-media.ts @@ -24,7 +24,9 @@ export async function stageSandboxMedia(params: { : ctx.MediaPath?.trim() ? [ctx.MediaPath.trim()] : []; - if (rawPaths.length === 0 || !sessionKey) return; + if (rawPaths.length === 0 || !sessionKey) { + return; + } const sandbox = await ensureSandboxWorkspaceForSession({ config: cfg, @@ -37,11 +39,15 @@ export async function stageSandboxMedia(params: { ? path.join(CONFIG_DIR, "media", "remote-cache", sessionKey) : null; const effectiveWorkspaceDir = sandbox?.workspaceDir ?? remoteMediaCacheDir; - if (!effectiveWorkspaceDir) return; + if (!effectiveWorkspaceDir) { + return; + } const resolveAbsolutePath = (value: string): string | null => { let resolved = value.trim(); - if (!resolved) return null; + if (!resolved) { + return null; + } if (resolved.startsWith("file://")) { try { resolved = fileURLToPath(resolved); @@ -49,7 +55,9 @@ export async function stageSandboxMedia(params: { return null; } } - if (!path.isAbsolute(resolved)) return null; + if (!path.isAbsolute(resolved)) { + return null; + } return resolved; }; @@ -65,11 +73,17 @@ export async function stageSandboxMedia(params: { for (const raw of rawPaths) { const source = resolveAbsolutePath(raw); - if (!source) continue; - if (staged.has(source)) continue; + if (!source) { + continue; + } + if (staged.has(source)) { + continue; + } const baseName = path.basename(source); - if (!baseName) continue; + if (!baseName) { + continue; + } const parsed = path.parse(baseName); let fileName = baseName; let suffix = 1; @@ -93,9 +107,13 @@ export async function stageSandboxMedia(params: { const rewriteIfStaged = (value: string | undefined): string | undefined => { const raw = value?.trim(); - if (!raw) return value; + if (!raw) { + return value; + } const abs = resolveAbsolutePath(raw); - if (!abs) return value; + if (!abs) { + return value; + } const mapped = staged.get(abs); return mapped ?? value; }; @@ -152,8 +170,11 @@ async function scpFile(remoteHost: string, remotePath: string, localPath: string child.once("error", reject); child.once("exit", (code) => { - if (code === 0) resolve(); - else reject(new Error(`scp failed (${code}): ${stderr.trim()}`)); + if (code === 0) { + resolve(); + } else { + reject(new Error(`scp failed (${code}): ${stderr.trim()}`)); + } }); }); } diff --git a/src/auto-reply/reply/streaming-directives.ts b/src/auto-reply/reply/streaming-directives.ts index a79e640a1f..c3a0cec758 100644 --- a/src/auto-reply/reply/streaming-directives.ts +++ b/src/auto-reply/reply/streaming-directives.ts @@ -20,9 +20,13 @@ type ConsumeOptions = { const splitTrailingDirective = (text: string): { text: string; tail: string } => { const openIndex = text.lastIndexOf("[["); - if (openIndex < 0) return { text, tail: "" }; + if (openIndex < 0) { + return { text, tail: "" }; + } const closeIndex = text.indexOf("]]", openIndex + 2); - if (closeIndex >= 0) return { text, tail: "" }; + if (closeIndex >= 0) { + return { text, tail: "" }; + } return { text: text.slice(0, openIndex), tail: text.slice(openIndex), diff --git a/src/auto-reply/reply/subagents-utils.ts b/src/auto-reply/reply/subagents-utils.ts index c6d8a4250c..092ac6465d 100644 --- a/src/auto-reply/reply/subagents-utils.ts +++ b/src/auto-reply/reply/subagents-utils.ts @@ -2,23 +2,37 @@ import type { SubagentRunRecord } from "../../agents/subagent-registry.js"; import { truncateUtf16Safe } from "../../utils.js"; export function formatDurationShort(valueMs?: number) { - if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) return "n/a"; + if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) { + return "n/a"; + } const totalSeconds = Math.round(valueMs / 1000); const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; - if (hours > 0) return `${hours}h${minutes}m`; - if (minutes > 0) return `${minutes}m${seconds}s`; + if (hours > 0) { + return `${hours}h${minutes}m`; + } + if (minutes > 0) { + return `${minutes}m${seconds}s`; + } return `${seconds}s`; } export function formatAgeShort(valueMs?: number) { - if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) return "n/a"; + if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) { + return "n/a"; + } const minutes = Math.round(valueMs / 60_000); - if (minutes < 1) return "just now"; - if (minutes < 60) return `${minutes}m ago`; + if (minutes < 1) { + return "just now"; + } + if (minutes < 60) { + return `${minutes}m ago`; + } const hours = Math.round(minutes / 60); - if (hours < 48) return `${hours}h ago`; + if (hours < 48) { + return `${hours}h ago`; + } const days = Math.round(hours / 24); return `${days}d ago`; } @@ -31,12 +45,16 @@ export function resolveSubagentLabel(entry: SubagentRunRecord, fallback = "subag export function formatRunLabel(entry: SubagentRunRecord, options?: { maxLength?: number }) { const raw = resolveSubagentLabel(entry); const maxLength = options?.maxLength ?? 72; - if (!Number.isFinite(maxLength) || maxLength <= 0) return raw; + if (!Number.isFinite(maxLength) || maxLength <= 0) { + return raw; + } return raw.length > maxLength ? `${truncateUtf16Safe(raw, maxLength).trimEnd()}…` : raw; } export function formatRunStatus(entry: SubagentRunRecord) { - if (!entry.endedAt) return "running"; + if (!entry.endedAt) { + return "running"; + } const status = entry.outcome?.status ?? "done"; return status === "ok" ? "done" : status; } diff --git a/src/auto-reply/reply/typing-mode.ts b/src/auto-reply/reply/typing-mode.ts index 0d73a37948..37805ef3be 100644 --- a/src/auto-reply/reply/typing-mode.ts +++ b/src/auto-reply/reply/typing-mode.ts @@ -17,9 +17,15 @@ export function resolveTypingMode({ wasMentioned, isHeartbeat, }: TypingModeContext): TypingMode { - if (isHeartbeat) return "never"; - if (configured) return configured; - if (!isGroupChat || wasMentioned) return "instant"; + if (isHeartbeat) { + return "never"; + } + if (configured) { + return configured; + } + if (!isGroupChat || wasMentioned) { + return "instant"; + } return DEFAULT_GROUP_TYPING_MODE; } @@ -51,23 +57,33 @@ export function createTypingSignaler(params: { const isRenderableText = (text?: string): boolean => { const trimmed = text?.trim(); - if (!trimmed) return false; + if (!trimmed) { + return false; + } return !isSilentReplyText(trimmed, SILENT_REPLY_TOKEN); }; const signalRunStart = async () => { - if (disabled || !shouldStartImmediately) return; + if (disabled || !shouldStartImmediately) { + return; + } await typing.startTypingLoop(); }; const signalMessageStart = async () => { - if (disabled || !shouldStartOnMessageStart) return; - if (!hasRenderableText) return; + if (disabled || !shouldStartOnMessageStart) { + return; + } + if (!hasRenderableText) { + return; + } await typing.startTypingLoop(); }; const signalTextDelta = async (text?: string) => { - if (disabled) return; + if (disabled) { + return; + } const renderable = isRenderableText(text); if (renderable) { hasRenderableText = true; @@ -87,14 +103,20 @@ export function createTypingSignaler(params: { }; const signalReasoningDelta = async () => { - if (disabled || !shouldStartOnReasoning) return; - if (!hasRenderableText) return; + if (disabled || !shouldStartOnReasoning) { + return; + } + if (!hasRenderableText) { + return; + } await typing.startTypingLoop(); typing.refreshTypingTtl(); }; const signalToolStart = async () => { - if (disabled) return; + if (disabled) { + return; + } // Start typing as soon as tools begin executing, even before the first text delta. if (!typing.isActive()) { await typing.startTypingLoop(); diff --git a/src/auto-reply/reply/typing.ts b/src/auto-reply/reply/typing.ts index 8271ebc78f..fbfab5b479 100644 --- a/src/auto-reply/reply/typing.ts +++ b/src/auto-reply/reply/typing.ts @@ -38,7 +38,9 @@ export function createTypingController(params: { const typingIntervalMs = typingIntervalSeconds * 1000; const formatTypingTtl = (ms: number) => { - if (ms % 60_000 === 0) return `${ms / 60_000}m`; + if (ms % 60_000 === 0) { + return `${ms / 60_000}m`; + } return `${Math.round(ms / 1000)}s`; }; @@ -50,7 +52,9 @@ export function createTypingController(params: { }; const cleanup = () => { - if (sealed) return; + if (sealed) { + return; + } if (typingTtlTimer) { clearTimeout(typingTtlTimer); typingTtlTimer = undefined; @@ -64,14 +68,22 @@ export function createTypingController(params: { }; const refreshTypingTtl = () => { - if (sealed) return; - if (!typingIntervalMs || typingIntervalMs <= 0) return; - if (typingTtlMs <= 0) return; + if (sealed) { + return; + } + if (!typingIntervalMs || typingIntervalMs <= 0) { + return; + } + if (typingTtlMs <= 0) { + return; + } if (typingTtlTimer) { clearTimeout(typingTtlTimer); } typingTtlTimer = setTimeout(() => { - if (!typingTimer) return; + if (!typingTimer) { + return; + } log?.(`typing TTL reached (${formatTypingTtl(typingTtlMs)}); stopping typing indicator`); cleanup(); }, typingTtlMs); @@ -80,37 +92,59 @@ export function createTypingController(params: { const isActive = () => active && !sealed; const triggerTyping = async () => { - if (sealed) return; + if (sealed) { + return; + } await onReplyStart?.(); }; const ensureStart = async () => { - if (sealed) return; + if (sealed) { + return; + } // Late callbacks after a run completed should never restart typing. - if (runComplete) return; + if (runComplete) { + return; + } if (!active) { active = true; } - if (started) return; + if (started) { + return; + } started = true; await triggerTyping(); }; const maybeStopOnIdle = () => { - if (!active) return; + if (!active) { + return; + } // Stop only when the model run is done and the dispatcher queue is empty. - if (runComplete && dispatchIdle) cleanup(); + if (runComplete && dispatchIdle) { + cleanup(); + } }; const startTypingLoop = async () => { - if (sealed) return; - if (runComplete) return; + if (sealed) { + return; + } + if (runComplete) { + return; + } // Always refresh TTL when called, even if loop already running. // This keeps typing alive during long tool executions. refreshTypingTtl(); - if (!onReplyStart) return; - if (typingIntervalMs <= 0) return; - if (typingTimer) return; + if (!onReplyStart) { + return; + } + if (typingIntervalMs <= 0) { + return; + } + if (typingTimer) { + return; + } await ensureStart(); typingTimer = setInterval(() => { void triggerTyping(); @@ -118,10 +152,16 @@ export function createTypingController(params: { }; const startTypingOnText = async (text?: string) => { - if (sealed) return; + if (sealed) { + return; + } const trimmed = text?.trim(); - if (!trimmed) return; - if (silentToken && isSilentReplyText(trimmed, silentToken)) return; + if (!trimmed) { + return; + } + if (silentToken && isSilentReplyText(trimmed, silentToken)) { + return; + } refreshTypingTtl(); await startTypingLoop(); }; diff --git a/src/auto-reply/send-policy.ts b/src/auto-reply/send-policy.ts index 101869573a..84dd8f8334 100644 --- a/src/auto-reply/send-policy.ts +++ b/src/auto-reply/send-policy.ts @@ -4,9 +4,15 @@ export type SendPolicyOverride = "allow" | "deny"; export function normalizeSendPolicyOverride(raw?: string | null): SendPolicyOverride | undefined { const value = raw?.trim().toLowerCase(); - if (!value) return undefined; - if (value === "allow" || value === "on") return "allow"; - if (value === "deny" || value === "off") return "deny"; + if (!value) { + return undefined; + } + if (value === "allow" || value === "on") { + return "allow"; + } + if (value === "deny" || value === "off") { + return "deny"; + } return undefined; } @@ -14,14 +20,22 @@ export function parseSendPolicyCommand(raw?: string): { hasCommand: boolean; mode?: SendPolicyOverride | "inherit"; } { - if (!raw) return { hasCommand: false }; + if (!raw) { + return { hasCommand: false }; + } const trimmed = raw.trim(); - if (!trimmed) return { hasCommand: false }; + if (!trimmed) { + return { hasCommand: false }; + } const normalized = normalizeCommandBody(trimmed); const match = normalized.match(/^\/send(?:\s+([a-zA-Z]+))?\s*$/i); - if (!match) return { hasCommand: false }; + if (!match) { + return { hasCommand: false }; + } const token = match[1]?.trim().toLowerCase(); - if (!token) return { hasCommand: true }; + if (!token) { + return { hasCommand: true }; + } if (token === "inherit" || token === "default" || token === "reset") { return { hasCommand: true, mode: "inherit" }; } diff --git a/src/auto-reply/skill-commands.ts b/src/auto-reply/skill-commands.ts index db3175aeb8..1b00d2077f 100644 --- a/src/auto-reply/skill-commands.ts +++ b/src/auto-reply/skill-commands.ts @@ -9,10 +9,14 @@ import { listChatCommands } from "./commands-registry.js"; function resolveReservedCommandNames(): Set { const reserved = new Set(); for (const command of listChatCommands()) { - if (command.nativeName) reserved.add(command.nativeName.toLowerCase()); + if (command.nativeName) { + reserved.add(command.nativeName.toLowerCase()); + } for (const alias of command.textAliases) { const trimmed = alias.trim(); - if (!trimmed.startsWith("/")) continue; + if (!trimmed.startsWith("/")) { + continue; + } reserved.add(trimmed.slice(1).toLowerCase()); } } @@ -41,7 +45,9 @@ export function listSkillCommandsForAgents(params: { const agentIds = params.agentIds ?? listAgentIds(params.cfg); for (const agentId of agentIds) { const workspaceDir = resolveAgentWorkspaceDir(params.cfg, agentId); - if (!fs.existsSync(workspaceDir)) continue; + if (!fs.existsSync(workspaceDir)) { + continue; + } const commands = buildWorkspaceSkillCommandSpecs(workspaceDir, { config: params.cfg, eligibility: { remote: getRemoteSkillEligibility() }, @@ -67,12 +73,18 @@ function findSkillCommand( rawName: string, ): SkillCommandSpec | undefined { const trimmed = rawName.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } const lowered = trimmed.toLowerCase(); const normalized = normalizeSkillCommandLookup(trimmed); return skillCommands.find((entry) => { - if (entry.name.toLowerCase() === lowered) return true; - if (entry.skillName.toLowerCase() === lowered) return true; + if (entry.name.toLowerCase() === lowered) { + return true; + } + if (entry.skillName.toLowerCase() === lowered) { + return true; + } return ( normalizeSkillCommandLookup(entry.name) === normalized || normalizeSkillCommandLookup(entry.skillName) === normalized @@ -85,23 +97,37 @@ export function resolveSkillCommandInvocation(params: { skillCommands: SkillCommandSpec[]; }): { command: SkillCommandSpec; args?: string } | null { const trimmed = params.commandBodyNormalized.trim(); - if (!trimmed.startsWith("/")) return null; + if (!trimmed.startsWith("/")) { + return null; + } const match = trimmed.match(/^\/([^\s]+)(?:\s+([\s\S]+))?$/); - if (!match) return null; + if (!match) { + return null; + } const commandName = match[1]?.trim().toLowerCase(); - if (!commandName) return null; + if (!commandName) { + return null; + } if (commandName === "skill") { const remainder = match[2]?.trim(); - if (!remainder) return null; + if (!remainder) { + return null; + } const skillMatch = remainder.match(/^([^\s]+)(?:\s+([\s\S]+))?$/); - if (!skillMatch) return null; + if (!skillMatch) { + return null; + } const skillCommand = findSkillCommand(params.skillCommands, skillMatch[1] ?? ""); - if (!skillCommand) return null; + if (!skillCommand) { + return null; + } const args = skillMatch[2]?.trim(); return { command: skillCommand, args: args || undefined }; } const command = params.skillCommands.find((entry) => entry.name.toLowerCase() === commandName); - if (!command) return null; + if (!command) { + return null; + } const args = match[2]?.trim(); return { command, args: args || undefined }; } diff --git a/src/auto-reply/status.ts b/src/auto-reply/status.ts index 0587343bea..dc74c192bf 100644 --- a/src/auto-reply/status.ts +++ b/src/auto-reply/status.ts @@ -84,16 +84,24 @@ function resolveRuntimeLabel( sessionKey, }); const sandboxMode = runtimeStatus.mode ?? "off"; - if (sandboxMode === "off") return "direct"; + if (sandboxMode === "off") { + return "direct"; + } const runtime = runtimeStatus.sandboxed ? "docker" : sessionKey ? "direct" : "unknown"; return `${runtime}/${sandboxMode}`; } const sandboxMode = args.agent?.sandbox?.mode ?? "off"; - if (sandboxMode === "off") return "direct"; + if (sandboxMode === "off") { + return "direct"; + } const sandboxed = (() => { - if (!sessionKey) return false; - if (sandboxMode === "all") return true; + if (!sessionKey) { + return false; + } + if (sandboxMode === "all") { + return true; + } if (args.config) { return resolveSandboxRuntimeStatus({ cfg: args.config, @@ -128,32 +136,48 @@ export const formatContextUsageShort = ( ) => `Context ${formatTokens(total, contextTokens ?? null)}`; const formatAge = (ms?: number | null) => { - if (!ms || ms < 0) return "unknown"; + if (!ms || ms < 0) { + return "unknown"; + } const minutes = Math.round(ms / 60_000); - if (minutes < 1) return "just now"; - if (minutes < 60) return `${minutes}m ago`; + if (minutes < 1) { + return "just now"; + } + if (minutes < 60) { + return `${minutes}m ago`; + } const hours = Math.round(minutes / 60); - if (hours < 48) return `${hours}h ago`; + if (hours < 48) { + return `${hours}h ago`; + } const days = Math.round(hours / 24); return `${days}d ago`; }; const formatQueueDetails = (queue?: QueueStatus) => { - if (!queue) return ""; + if (!queue) { + return ""; + } const depth = typeof queue.depth === "number" ? `depth ${queue.depth}` : null; if (!queue.showDetails) { return depth ? ` (${depth})` : ""; } const detailParts: string[] = []; - if (depth) detailParts.push(depth); + if (depth) { + detailParts.push(depth); + } if (typeof queue.debounceMs === "number") { const ms = Math.max(0, Math.round(queue.debounceMs)); const label = ms >= 1000 ? `${ms % 1000 === 0 ? ms / 1000 : (ms / 1000).toFixed(1)}s` : `${ms}ms`; detailParts.push(`debounce ${label}`); } - if (typeof queue.cap === "number") detailParts.push(`cap ${queue.cap}`); - if (queue.dropPolicy) detailParts.push(`drop ${queue.dropPolicy}`); + if (typeof queue.cap === "number") { + detailParts.push(`cap ${queue.cap}`); + } + if (queue.dropPolicy) { + detailParts.push(`drop ${queue.dropPolicy}`); + } return detailParts.length ? ` (${detailParts.join(" · ")})` : ""; }; @@ -170,9 +194,13 @@ const readUsageFromSessionLog = ( } | undefined => { // Transcripts are stored at the session file path (fallback: ~/.openclaw/sessions/.jsonl) - if (!sessionId) return undefined; + if (!sessionId) { + return undefined; + } const logPath = resolveSessionFilePath(sessionId, sessionEntry); - if (!fs.existsSync(logPath)) return undefined; + if (!fs.existsSync(logPath)) { + return undefined; + } try { const lines = fs.readFileSync(logPath, "utf-8").split(/\n+/); @@ -183,7 +211,9 @@ const readUsageFromSessionLog = ( let lastUsage: ReturnType | undefined; for (const line of lines) { - if (!line.trim()) continue; + if (!line.trim()) { + continue; + } try { const parsed = JSON.parse(line) as { message?: { @@ -195,19 +225,25 @@ const readUsageFromSessionLog = ( }; const usageRaw = parsed.message?.usage ?? parsed.usage; const usage = normalizeUsage(usageRaw); - if (usage) lastUsage = usage; + if (usage) { + lastUsage = usage; + } model = parsed.message?.model ?? parsed.model ?? model; } catch { // ignore bad lines } } - if (!lastUsage) return undefined; + if (!lastUsage) { + return undefined; + } input = lastUsage.input ?? 0; output = lastUsage.output ?? 0; promptTokens = derivePromptTokens(lastUsage) ?? lastUsage.total ?? input + output; const total = lastUsage.total ?? promptTokens + output; - if (promptTokens === 0 && total === 0) return undefined; + if (promptTokens === 0 && total === 0) { + return undefined; + } return { input, output, promptTokens, total, model }; } catch { return undefined; @@ -215,14 +251,18 @@ const readUsageFromSessionLog = ( }; const formatUsagePair = (input?: number | null, output?: number | null) => { - if (input == null && output == null) return null; + if (input == null && output == null) { + return null; + } const inputLabel = typeof input === "number" ? formatTokenCount(input) : "?"; const outputLabel = typeof output === "number" ? formatTokenCount(output) : "?"; return `🧮 Tokens: ${inputLabel} in / ${outputLabel} out`; }; const formatMediaUnderstandingLine = (decisions?: MediaUnderstandingDecision[]) => { - if (!decisions || decisions.length === 0) return null; + if (!decisions || decisions.length === 0) { + return null; + } const parts = decisions .map((decision) => { const count = decision.attachments.length; @@ -253,8 +293,12 @@ const formatMediaUnderstandingLine = (decisions?: MediaUnderstandingDecision[]) return null; }) .filter((part): part is string => part != null); - if (parts.length === 0) return null; - if (parts.every((part) => part.endsWith(" none"))) return null; + if (parts.length === 0) { + return null; + } + if (parts.every((part) => part.endsWith(" none"))) { + return null; + } return `📎 Media: ${parts.join(" · ")}`; }; @@ -262,7 +306,9 @@ const formatVoiceModeLine = ( config?: OpenClawConfig, sessionEntry?: SessionEntry, ): string | null => { - if (!config) return null; + if (!config) { + return null; + } const ttsConfig = resolveTtsConfig(config); const prefsPath = resolveTtsPrefsPath(ttsConfig); const autoMode = resolveTtsAutoMode({ @@ -270,7 +316,9 @@ const formatVoiceModeLine = ( prefsPath, sessionAuto: sessionEntry?.ttsAuto, }); - if (autoMode === "off") return null; + if (autoMode === "off") { + return null; + } const provider = getTtsProvider(ttsConfig, prefsPath); const maxLength = getTtsMaxLength(prefsPath); const summarize = isSummarizationEnabled(prefsPath) ? "on" : "off"; @@ -310,12 +358,18 @@ export function buildStatusMessage(args: StatusArgs): string { if (!totalTokens || totalTokens === 0 || candidate > totalTokens) { totalTokens = candidate; } - if (!model) model = logUsage.model ?? model; + if (!model) { + model = logUsage.model ?? model; + } if (!contextTokens && logUsage.model) { contextTokens = lookupContextTokens(logUsage.model) ?? contextTokens; } - if (!inputTokens || inputTokens === 0) inputTokens = logUsage.input; - if (!outputTokens || outputTokens === 0) outputTokens = logUsage.output; + if (!inputTokens || inputTokens === 0) { + inputTokens = logUsage.input; + } + if (!outputTokens || outputTokens === 0) { + outputTokens = logUsage.output; + } } } @@ -476,8 +530,12 @@ export function buildHelpMessage(cfg?: OpenClawConfig): string { lines.push(""); const optionParts = ["/think ", "/model ", "/verbose on|off"]; - if (cfg?.commands?.config === true) optionParts.push("/config"); - if (cfg?.commands?.debug === true) optionParts.push("/debug"); + if (cfg?.commands?.config === true) { + optionParts.push("/config"); + } + if (cfg?.commands?.debug === true) { + optionParts.push("/debug"); + } lines.push("Options"); lines.push(` ${optionParts.join(" | ")}`); lines.push(""); @@ -521,7 +579,9 @@ function formatCommandEntry(command: ChatCommandDefinition): string { .filter((alias) => alias.toLowerCase() !== primary.toLowerCase()) .filter((alias) => { const key = alias.toLowerCase(); - if (seen.has(key)) return false; + if (seen.has(key)) { + return false; + } seen.add(key); return true; }); @@ -544,7 +604,9 @@ function buildCommandItems( for (const category of CATEGORY_ORDER) { const categoryCommands = grouped.get(category) ?? []; - if (categoryCommands.length === 0) continue; + if (categoryCommands.length === 0) { + continue; + } const label = CATEGORY_LABELS[category]; for (const command of categoryCommands) { items.push({ label, text: formatCommandEntry(command) }); @@ -568,7 +630,9 @@ function formatCommandList(items: CommandsListItem[]): string { for (const item of items) { if (item.label !== currentLabel) { - if (lines.length > 0) lines.push(""); + if (lines.length > 0) { + lines.push(""); + } lines.push(item.label); currentLabel = item.label; } diff --git a/src/auto-reply/templating.ts b/src/auto-reply/templating.ts index 1e07f6a32f..517e249203 100644 --- a/src/auto-reply/templating.ts +++ b/src/auto-reply/templating.ts @@ -138,8 +138,12 @@ export type TemplateContext = MsgContext & { }; function formatTemplateValue(value: unknown): string { - if (value == null) return ""; - if (typeof value === "string") return value; + if (value == null) { + return ""; + } + if (typeof value === "string") { + return value; + } if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") { return String(value); } @@ -149,8 +153,12 @@ function formatTemplateValue(value: unknown): string { if (Array.isArray(value)) { return value .flatMap((entry) => { - if (entry == null) return []; - if (typeof entry === "string") return [entry]; + if (entry == null) { + return []; + } + if (typeof entry === "string") { + return [entry]; + } if (typeof entry === "number" || typeof entry === "boolean" || typeof entry === "bigint") { return [String(entry)]; } @@ -166,7 +174,9 @@ function formatTemplateValue(value: unknown): string { // Simple {{Placeholder}} interpolation using inbound message context. export function applyTemplate(str: string | undefined, ctx: TemplateContext) { - if (!str) return ""; + if (!str) { + return ""; + } return str.replace(/{{\s*(\w+)\s*}}/g, (_, key) => { const value = ctx[key as keyof TemplateContext]; return formatTemplateValue(value); diff --git a/src/auto-reply/thinking.ts b/src/auto-reply/thinking.ts index a0f712199e..15c94545ac 100644 --- a/src/auto-reply/thinking.ts +++ b/src/auto-reply/thinking.ts @@ -7,9 +7,13 @@ export type ReasoningLevel = "off" | "on" | "stream"; export type UsageDisplayLevel = "off" | "tokens" | "full"; function normalizeProviderId(provider?: string | null): string { - if (!provider) return ""; + if (!provider) { + return ""; + } const normalized = provider.trim().toLowerCase(); - if (normalized === "z.ai" || normalized === "z-ai") return "zai"; + if (normalized === "z.ai" || normalized === "z-ai") { + return "zai"; + } return normalized; } @@ -32,24 +36,44 @@ const XHIGH_MODEL_IDS = new Set( // Normalize user-provided thinking level strings to the canonical enum. export function normalizeThinkLevel(raw?: string | null): ThinkLevel | undefined { - if (!raw) return undefined; + if (!raw) { + return undefined; + } const key = raw.toLowerCase(); - if (["off"].includes(key)) return "off"; - if (["on", "enable", "enabled"].includes(key)) return "low"; - if (["min", "minimal"].includes(key)) return "minimal"; - if (["low", "thinkhard", "think-hard", "think_hard"].includes(key)) return "low"; - if (["mid", "med", "medium", "thinkharder", "think-harder", "harder"].includes(key)) + if (["off"].includes(key)) { + return "off"; + } + if (["on", "enable", "enabled"].includes(key)) { + return "low"; + } + if (["min", "minimal"].includes(key)) { + return "minimal"; + } + if (["low", "thinkhard", "think-hard", "think_hard"].includes(key)) { + return "low"; + } + if (["mid", "med", "medium", "thinkharder", "think-harder", "harder"].includes(key)) { return "medium"; - if (["high", "ultra", "ultrathink", "think-hard", "thinkhardest", "highest", "max"].includes(key)) + } + if ( + ["high", "ultra", "ultrathink", "think-hard", "thinkhardest", "highest", "max"].includes(key) + ) { return "high"; - if (["xhigh", "x-high", "x_high"].includes(key)) return "xhigh"; - if (["think"].includes(key)) return "minimal"; + } + if (["xhigh", "x-high", "x_high"].includes(key)) { + return "xhigh"; + } + if (["think"].includes(key)) { + return "minimal"; + } return undefined; } export function supportsXHighThinking(provider?: string | null, model?: string | null): boolean { const modelKey = model?.trim().toLowerCase(); - if (!modelKey) return false; + if (!modelKey) { + return false; + } const providerKey = provider?.trim().toLowerCase(); if (providerKey) { return XHIGH_MODEL_SET.has(`${providerKey}/${modelKey}`); @@ -59,12 +83,16 @@ export function supportsXHighThinking(provider?: string | null, model?: string | export function listThinkingLevels(provider?: string | null, model?: string | null): ThinkLevel[] { const levels: ThinkLevel[] = ["off", "minimal", "low", "medium", "high"]; - if (supportsXHighThinking(provider, model)) levels.push("xhigh"); + if (supportsXHighThinking(provider, model)) { + levels.push("xhigh"); + } return levels; } export function listThinkingLevelLabels(provider?: string | null, model?: string | null): string[] { - if (isBinaryThinkingProvider(provider)) return ["off", "on"]; + if (isBinaryThinkingProvider(provider)) { + return ["off", "on"]; + } return listThinkingLevels(provider, model); } @@ -78,40 +106,72 @@ export function formatThinkingLevels( export function formatXHighModelHint(): string { const refs = [...XHIGH_MODEL_REFS] as string[]; - if (refs.length === 0) return "unknown model"; - if (refs.length === 1) return refs[0]; - if (refs.length === 2) return `${refs[0]} or ${refs[1]}`; + if (refs.length === 0) { + return "unknown model"; + } + if (refs.length === 1) { + return refs[0]; + } + if (refs.length === 2) { + return `${refs[0]} or ${refs[1]}`; + } return `${refs.slice(0, -1).join(", ")} or ${refs[refs.length - 1]}`; } // Normalize verbose flags used to toggle agent verbosity. export function normalizeVerboseLevel(raw?: string | null): VerboseLevel | undefined { - if (!raw) return undefined; + if (!raw) { + return undefined; + } const key = raw.toLowerCase(); - if (["off", "false", "no", "0"].includes(key)) return "off"; - if (["full", "all", "everything"].includes(key)) return "full"; - if (["on", "minimal", "true", "yes", "1"].includes(key)) return "on"; + if (["off", "false", "no", "0"].includes(key)) { + return "off"; + } + if (["full", "all", "everything"].includes(key)) { + return "full"; + } + if (["on", "minimal", "true", "yes", "1"].includes(key)) { + return "on"; + } return undefined; } // Normalize system notice flags used to toggle system notifications. export function normalizeNoticeLevel(raw?: string | null): NoticeLevel | undefined { - if (!raw) return undefined; + if (!raw) { + return undefined; + } const key = raw.toLowerCase(); - if (["off", "false", "no", "0"].includes(key)) return "off"; - if (["full", "all", "everything"].includes(key)) return "full"; - if (["on", "minimal", "true", "yes", "1"].includes(key)) return "on"; + if (["off", "false", "no", "0"].includes(key)) { + return "off"; + } + if (["full", "all", "everything"].includes(key)) { + return "full"; + } + if (["on", "minimal", "true", "yes", "1"].includes(key)) { + return "on"; + } return undefined; } // Normalize response-usage display modes used to toggle per-response usage footers. export function normalizeUsageDisplay(raw?: string | null): UsageDisplayLevel | undefined { - if (!raw) return undefined; + if (!raw) { + return undefined; + } const key = raw.toLowerCase(); - if (["off", "false", "no", "0", "disable", "disabled"].includes(key)) return "off"; - if (["on", "true", "yes", "1", "enable", "enabled"].includes(key)) return "tokens"; - if (["tokens", "token", "tok", "minimal", "min"].includes(key)) return "tokens"; - if (["full", "session"].includes(key)) return "full"; + if (["off", "false", "no", "0", "disable", "disabled"].includes(key)) { + return "off"; + } + if (["on", "true", "yes", "1", "enable", "enabled"].includes(key)) { + return "tokens"; + } + if (["tokens", "token", "tok", "minimal", "min"].includes(key)) { + return "tokens"; + } + if (["full", "session"].includes(key)) { + return "full"; + } return undefined; } @@ -121,28 +181,49 @@ export function resolveResponseUsageMode(raw?: string | null): UsageDisplayLevel // Normalize elevated flags used to toggle elevated bash permissions. export function normalizeElevatedLevel(raw?: string | null): ElevatedLevel | undefined { - if (!raw) return undefined; + if (!raw) { + return undefined; + } const key = raw.toLowerCase(); - if (["off", "false", "no", "0"].includes(key)) return "off"; - if (["full", "auto", "auto-approve", "autoapprove"].includes(key)) return "full"; - if (["ask", "prompt", "approval", "approve"].includes(key)) return "ask"; - if (["on", "true", "yes", "1"].includes(key)) return "on"; + if (["off", "false", "no", "0"].includes(key)) { + return "off"; + } + if (["full", "auto", "auto-approve", "autoapprove"].includes(key)) { + return "full"; + } + if (["ask", "prompt", "approval", "approve"].includes(key)) { + return "ask"; + } + if (["on", "true", "yes", "1"].includes(key)) { + return "on"; + } return undefined; } export function resolveElevatedMode(level?: ElevatedLevel | null): ElevatedMode { - if (!level || level === "off") return "off"; - if (level === "full") return "full"; + if (!level || level === "off") { + return "off"; + } + if (level === "full") { + return "full"; + } return "ask"; } // Normalize reasoning visibility flags used to toggle reasoning exposure. export function normalizeReasoningLevel(raw?: string | null): ReasoningLevel | undefined { - if (!raw) return undefined; + if (!raw) { + return undefined; + } const key = raw.toLowerCase(); - if (["off", "false", "no", "0", "hide", "hidden", "disable", "disabled"].includes(key)) + if (["off", "false", "no", "0", "hide", "hidden", "disable", "disabled"].includes(key)) { return "off"; - if (["on", "true", "yes", "1", "show", "visible", "enable", "enabled"].includes(key)) return "on"; - if (["stream", "streaming", "draft", "live"].includes(key)) return "stream"; + } + if (["on", "true", "yes", "1", "show", "visible", "enable", "enabled"].includes(key)) { + return "on"; + } + if (["stream", "streaming", "draft", "live"].includes(key)) { + return "stream"; + } return undefined; } diff --git a/src/auto-reply/tokens.ts b/src/auto-reply/tokens.ts index b503eff693..62b4f09140 100644 --- a/src/auto-reply/tokens.ts +++ b/src/auto-reply/tokens.ts @@ -9,10 +9,14 @@ export function isSilentReplyText( text: string | undefined, token: string = SILENT_REPLY_TOKEN, ): boolean { - if (!text) return false; + if (!text) { + return false; + } const escaped = escapeRegExp(token); const prefix = new RegExp(`^\\s*${escaped}(?=$|\\W)`); - if (prefix.test(text)) return true; + if (prefix.test(text)) { + return true; + } const suffix = new RegExp(`\\b${escaped}\\b\\W*$`); return suffix.test(text); } diff --git a/src/auto-reply/tool-meta.ts b/src/auto-reply/tool-meta.ts index aaf527ada7..4297741f81 100644 --- a/src/auto-reply/tool-meta.ts +++ b/src/auto-reply/tool-meta.ts @@ -10,9 +10,13 @@ export function shortenPath(p: string): string { } export function shortenMeta(meta: string): string { - if (!meta) return meta; + if (!meta) { + return meta; + } const colonIdx = meta.indexOf(":"); - if (colonIdx === -1) return shortenHomeInString(meta); + if (colonIdx === -1) { + return shortenHomeInString(meta); + } const base = meta.slice(0, colonIdx); const rest = meta.slice(colonIdx); return `${shortenHomeInString(base)}${rest}`; @@ -26,7 +30,9 @@ export function formatToolAggregate( const filtered = (metas ?? []).filter(Boolean).map(shortenMeta); const display = resolveToolDisplay({ name: toolName }); const prefix = `${display.emoji} ${display.label}`; - if (!filtered.length) return prefix; + if (!filtered.length) { + return prefix; + } const rawSegments: string[] = []; // Group by directory and brace-collapse filenames @@ -44,17 +50,23 @@ export function formatToolAggregate( if (parts.length > 1) { const dir = parts.slice(0, -1).join("/"); const base = parts.at(-1) ?? m; - if (!grouped[dir]) grouped[dir] = []; + if (!grouped[dir]) { + grouped[dir] = []; + } grouped[dir].push(base); } else { - if (!grouped["."]) grouped["."] = []; + if (!grouped["."]) { + grouped["."] = []; + } grouped["."].push(m); } } const segments = Object.entries(grouped).map(([dir, files]) => { const brace = files.length > 1 ? `{${files.join(", ")}}` : files[0]; - if (dir === ".") return brace; + if (dir === ".") { + return brace; + } return `${dir}/${brace}`; }); @@ -78,7 +90,9 @@ function formatMetaForDisplay( if (normalized === "exec" || normalized === "bash") { const { flags, body } = splitExecFlags(meta); if (flags.length > 0) { - if (!body) return flags.join(" · "); + if (!body) { + return flags.join(" · "); + } return `${flags.join(" · ")} · ${maybeWrapMarkdown(body, markdown)}`; } } @@ -90,7 +104,9 @@ function splitExecFlags(meta: string): { flags: string[]; body: string } { .split(" · ") .map((part) => part.trim()) .filter(Boolean); - if (parts.length === 0) return { flags: [], body: "" }; + if (parts.length === 0) { + return { flags: [], body: "" }; + } const flags: string[] = []; const bodyParts: string[] = []; for (const part of parts) { @@ -104,16 +120,30 @@ function splitExecFlags(meta: string): { flags: string[]; body: string } { } function isPathLike(value: string): boolean { - if (!value) return false; - if (value.includes(" ")) return false; - if (value.includes("://")) return false; - if (value.includes("·")) return false; - if (value.includes("&&") || value.includes("||")) return false; + if (!value) { + return false; + } + if (value.includes(" ")) { + return false; + } + if (value.includes("://")) { + return false; + } + if (value.includes("·")) { + return false; + } + if (value.includes("&&") || value.includes("||")) { + return false; + } return /^~?(\/[^\s]+)+$/.test(value); } function maybeWrapMarkdown(value: string, markdown?: boolean): string { - if (!markdown) return value; - if (value.includes("`")) return value; + if (!markdown) { + return value; + } + if (value.includes("`")) { + return value; + } return `\`${value}\``; } diff --git a/src/browser/bridge-server.ts b/src/browser/bridge-server.ts index eaab062cc4..66373c5b0f 100644 --- a/src/browser/bridge-server.ts +++ b/src/browser/bridge-server.ts @@ -35,7 +35,9 @@ export async function startBrowserBridgeServer(params: { if (authToken) { app.use((req, res, next) => { const auth = String(req.headers.authorization ?? "").trim(); - if (auth === `Bearer ${authToken}`) return next(); + if (auth === `Bearer ${authToken}`) { + return next(); + } res.status(401).send("Unauthorized"); }); } diff --git a/src/browser/cdp.helpers.ts b/src/browser/cdp.helpers.ts index 7e1e7222e9..f7cc4bab50 100644 --- a/src/browser/cdp.helpers.ts +++ b/src/browser/cdp.helpers.ts @@ -32,7 +32,9 @@ export function getHeadersWithAuth(url: string, headers: Record try { const parsed = new URL(url); const hasAuthHeader = Object.keys(headers).some((key) => key.toLowerCase() === "authorization"); - if (hasAuthHeader) return headers; + if (hasAuthHeader) { + return headers; + } if (parsed.username || parsed.password) { const auth = Buffer.from(`${parsed.username}:${parsed.password}`).toString("base64"); return { ...headers, Authorization: `Basic ${auth}` }; @@ -65,7 +67,9 @@ function createCdpSender(ws: WebSocket) { }; const closeWithError = (err: Error) => { - for (const [, p] of pending) p.reject(err); + for (const [, p] of pending) { + p.reject(err); + } pending.clear(); try { ws.close(); @@ -77,9 +81,13 @@ function createCdpSender(ws: WebSocket) { ws.on("message", (data) => { try { const parsed = JSON.parse(rawDataToString(data)) as CdpResponse; - if (typeof parsed.id !== "number") return; + if (typeof parsed.id !== "number") { + return; + } const p = pending.get(parsed.id); - if (!p) return; + if (!p) { + return; + } pending.delete(parsed.id); if (parsed.error?.message) { p.reject(new Error(parsed.error.message)); @@ -104,7 +112,9 @@ export async function fetchJson(url: string, timeoutMs = 1500, init?: Request try { const headers = getHeadersWithAuth(url, (init?.headers as Record) || {}); const res = await fetch(url, { ...init, headers, signal: ctrl.signal }); - if (!res.ok) throw new Error(`HTTP ${res.status}`); + if (!res.ok) { + throw new Error(`HTTP ${res.status}`); + } return (await res.json()) as T; } finally { clearTimeout(t); @@ -117,7 +127,9 @@ export async function fetchOk(url: string, timeoutMs = 1500, init?: RequestInit) try { const headers = getHeadersWithAuth(url, (init?.headers as Record) || {}); const res = await fetch(url, { ...init, headers, signal: ctrl.signal }); - if (!res.ok) throw new Error(`HTTP ${res.status}`); + if (!res.ok) { + throw new Error(`HTTP ${res.status}`); + } } finally { clearTimeout(t); } diff --git a/src/browser/cdp.test.ts b/src/browser/cdp.test.ts index 24e9f7c58c..46faaa7945 100644 --- a/src/browser/cdp.test.ts +++ b/src/browser/cdp.test.ts @@ -11,12 +11,16 @@ describe("cdp", () => { afterEach(async () => { await new Promise((resolve) => { - if (!httpServer) return resolve(); + if (!httpServer) { + return resolve(); + } httpServer.close(() => resolve()); httpServer = null; }); await new Promise((resolve) => { - if (!wsServer) return resolve(); + if (!wsServer) { + return resolve(); + } wsServer.close(() => resolve()); wsServer = null; }); @@ -34,7 +38,9 @@ describe("cdp", () => { method?: string; params?: { url?: string }; }; - if (msg.method !== "Target.createTarget") return; + if (msg.method !== "Target.createTarget") { + return; + } socket.send( JSON.stringify({ id: msg.id, diff --git a/src/browser/cdp.ts b/src/browser/cdp.ts index 9e33855940..28fbb872c1 100644 --- a/src/browser/cdp.ts +++ b/src/browser/cdp.ts @@ -8,7 +8,9 @@ export function normalizeCdpWsUrl(wsUrl: string, cdpUrl: string): string { if (isLoopbackHost(ws.hostname) && !isLoopbackHost(cdp.hostname)) { ws.hostname = cdp.hostname; const cdpPort = cdp.port || (cdp.protocol === "https:" ? "443" : "80"); - if (cdpPort) ws.port = cdpPort; + if (cdpPort) { + ws.port = cdpPort; + } ws.protocol = cdp.protocol === "https:" ? "wss:" : "ws:"; } if (cdp.protocol === "https:" && ws.protocol === "ws:") { @@ -19,7 +21,9 @@ export function normalizeCdpWsUrl(wsUrl: string, cdpUrl: string): string { ws.password = cdp.password; } for (const [key, value] of cdp.searchParams.entries()) { - if (!ws.searchParams.has(key)) ws.searchParams.append(key, value); + if (!ws.searchParams.has(key)) { + ws.searchParams.append(key, value); + } } return ws.toString(); } @@ -71,7 +75,9 @@ export async function captureScreenshot(opts: { })) as { data?: string }; const base64 = result?.data; - if (!base64) throw new Error("Screenshot failed: missing data"); + if (!base64) { + throw new Error("Screenshot failed: missing data"); + } return Buffer.from(base64, "base64"); }); } @@ -86,14 +92,18 @@ export async function createTargetViaCdp(opts: { ); const wsUrlRaw = String(version?.webSocketDebuggerUrl ?? "").trim(); const wsUrl = wsUrlRaw ? normalizeCdpWsUrl(wsUrlRaw, opts.cdpUrl) : ""; - if (!wsUrl) throw new Error("CDP /json/version missing webSocketDebuggerUrl"); + if (!wsUrl) { + throw new Error("CDP /json/version missing webSocketDebuggerUrl"); + } return await withCdpSocket(wsUrl, async (send) => { const created = (await send("Target.createTarget", { url: opts.url })) as { targetId?: string; }; const targetId = String(created?.targetId ?? "").trim(); - if (!targetId) throw new Error("CDP Target.createTarget returned no targetId"); + if (!targetId) { + throw new Error("CDP Target.createTarget returned no targetId"); + } return { targetId }; }); } @@ -138,7 +148,9 @@ export async function evaluateJavaScript(opts: { }; const result = evaluated?.result; - if (!result) throw new Error("CDP Runtime.evaluate returned no result"); + if (!result) { + throw new Error("CDP Runtime.evaluate returned no result"); + } return { result, exceptionDetails: evaluated.exceptionDetails }; }); } @@ -164,9 +176,13 @@ export type RawAXNode = { }; function axValue(v: unknown): string { - if (!v || typeof v !== "object") return ""; + if (!v || typeof v !== "object") { + return ""; + } const value = (v as { value?: unknown }).value; - if (typeof value === "string") return value; + if (typeof value === "string") { + return value; + } if (typeof value === "number" || typeof value === "boolean") { return String(value); } @@ -176,25 +192,35 @@ function axValue(v: unknown): string { export function formatAriaSnapshot(nodes: RawAXNode[], limit: number): AriaSnapshotNode[] { const byId = new Map(); for (const n of nodes) { - if (n.nodeId) byId.set(n.nodeId, n); + if (n.nodeId) { + byId.set(n.nodeId, n); + } } // Heuristic: pick a root-ish node (one that is not referenced as a child), else first. const referenced = new Set(); for (const n of nodes) { - for (const c of n.childIds ?? []) referenced.add(c); + for (const c of n.childIds ?? []) { + referenced.add(c); + } } const root = nodes.find((n) => n.nodeId && !referenced.has(n.nodeId)) ?? nodes[0]; - if (!root?.nodeId) return []; + if (!root?.nodeId) { + return []; + } const out: AriaSnapshotNode[] = []; const stack: Array<{ id: string; depth: number }> = [{ id: root.nodeId, depth: 0 }]; while (stack.length && out.length < limit) { const popped = stack.pop(); - if (!popped) break; + if (!popped) { + break; + } const { id, depth } = popped; const n = byId.get(id); - if (!n) continue; + if (!n) { + continue; + } const role = axValue(n.role); const name = axValue(n.name); const value = axValue(n.value); @@ -213,7 +239,9 @@ export function formatAriaSnapshot(nodes: RawAXNode[], limit: number): AriaSnaps const children = (n.childIds ?? []).filter((c) => byId.has(c)); for (let i = children.length - 1; i >= 0; i--) { const child = children[i]; - if (child) stack.push({ id: child, depth: depth + 1 }); + if (child) { + stack.push({ id: child, depth: depth + 1 }); + } } } @@ -297,7 +325,9 @@ export async function snapshotDom(opts: { returnByValue: true, }); 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; return { nodes: Array.isArray(nodes) ? (nodes as DomSnapshotNode[]) : [] }; } diff --git a/src/browser/chrome.default-browser.test.ts b/src/browser/chrome.default-browser.test.ts index ab473f536e..8f681e3ce7 100644 --- a/src/browser/chrome.default-browser.test.ts +++ b/src/browser/chrome.default-browser.test.ts @@ -39,7 +39,9 @@ describe("browser default executable detection", () => { }); vi.mocked(fs.existsSync).mockImplementation((p) => { const value = String(p); - if (value.includes("com.apple.launchservices.secure.plist")) return true; + if (value.includes("com.apple.launchservices.secure.plist")) { + return true; + } return value.includes("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"); }); @@ -65,7 +67,9 @@ describe("browser default executable detection", () => { }); vi.mocked(fs.existsSync).mockImplementation((p) => { const value = String(p); - if (value.includes("com.apple.launchservices.secure.plist")) return true; + if (value.includes("com.apple.launchservices.secure.plist")) { + return true; + } return value.includes("Google Chrome.app/Contents/MacOS/Google Chrome"); }); diff --git a/src/browser/chrome.executables.ts b/src/browser/chrome.executables.ts index 4c0d504274..9abfb4368f 100644 --- a/src/browser/chrome.executables.ts +++ b/src/browser/chrome.executables.ts @@ -115,10 +115,18 @@ function execText( function inferKindFromIdentifier(identifier: string): BrowserExecutable["kind"] { const id = identifier.toLowerCase(); - if (id.includes("brave")) return "brave"; - if (id.includes("edge")) return "edge"; - if (id.includes("chromium")) return "chromium"; - if (id.includes("canary")) return "canary"; + if (id.includes("brave")) { + return "brave"; + } + if (id.includes("edge")) { + return "edge"; + } + if (id.includes("chromium")) { + return "chromium"; + } + if (id.includes("canary")) { + return "canary"; + } if ( id.includes("opera") || id.includes("vivaldi") || @@ -132,40 +140,63 @@ function inferKindFromIdentifier(identifier: string): BrowserExecutable["kind"] function inferKindFromExecutableName(name: string): BrowserExecutable["kind"] { const lower = name.toLowerCase(); - if (lower.includes("brave")) return "brave"; - if (lower.includes("edge") || lower.includes("msedge")) return "edge"; - if (lower.includes("chromium")) return "chromium"; - if (lower.includes("canary") || lower.includes("sxs")) return "canary"; - if (lower.includes("opera") || lower.includes("vivaldi") || lower.includes("yandex")) + if (lower.includes("brave")) { + return "brave"; + } + if (lower.includes("edge") || lower.includes("msedge")) { + return "edge"; + } + if (lower.includes("chromium")) { return "chromium"; + } + if (lower.includes("canary") || lower.includes("sxs")) { + return "canary"; + } + if (lower.includes("opera") || lower.includes("vivaldi") || lower.includes("yandex")) { + return "chromium"; + } return "chrome"; } function detectDefaultChromiumExecutable(platform: NodeJS.Platform): BrowserExecutable | null { - if (platform === "darwin") return detectDefaultChromiumExecutableMac(); - if (platform === "linux") return detectDefaultChromiumExecutableLinux(); - if (platform === "win32") return detectDefaultChromiumExecutableWindows(); + if (platform === "darwin") { + return detectDefaultChromiumExecutableMac(); + } + if (platform === "linux") { + return detectDefaultChromiumExecutableLinux(); + } + if (platform === "win32") { + return detectDefaultChromiumExecutableWindows(); + } return null; } function detectDefaultChromiumExecutableMac(): BrowserExecutable | null { const bundleId = detectDefaultBrowserBundleIdMac(); - if (!bundleId || !CHROMIUM_BUNDLE_IDS.has(bundleId)) return null; + if (!bundleId || !CHROMIUM_BUNDLE_IDS.has(bundleId)) { + return null; + } const appPathRaw = execText("/usr/bin/osascript", [ "-e", `POSIX path of (path to application id "${bundleId}")`, ]); - if (!appPathRaw) return null; + if (!appPathRaw) { + return null; + } const appPath = appPathRaw.trim().replace(/\/$/, ""); const exeName = execText("/usr/bin/defaults", [ "read", path.join(appPath, "Contents", "Info"), "CFBundleExecutable", ]); - if (!exeName) return null; + if (!exeName) { + return null; + } const exePath = path.join(appPath, "Contents", "MacOS", exeName.trim()); - if (!exists(exePath)) return null; + if (!exists(exePath)) { + return null; + } return { kind: inferKindFromIdentifier(bundleId), path: exePath }; } @@ -174,33 +205,45 @@ function detectDefaultBrowserBundleIdMac(): string | null { os.homedir(), "Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist", ); - if (!exists(plistPath)) return null; + if (!exists(plistPath)) { + return null; + } const handlersRaw = execText( "/usr/bin/plutil", ["-extract", "LSHandlers", "json", "-o", "-", "--", plistPath], 2000, 5 * 1024 * 1024, ); - if (!handlersRaw) return null; + if (!handlersRaw) { + return null; + } let handlers: unknown; try { handlers = JSON.parse(handlersRaw); } catch { return null; } - if (!Array.isArray(handlers)) return null; + if (!Array.isArray(handlers)) { + return null; + } const resolveScheme = (scheme: string) => { let candidate: string | null = null; for (const entry of handlers) { - if (!entry || typeof entry !== "object") continue; + if (!entry || typeof entry !== "object") { + continue; + } const record = entry as Record; - if (record.LSHandlerURLScheme !== scheme) continue; + if (record.LSHandlerURLScheme !== scheme) { + continue; + } const role = (typeof record.LSHandlerRoleAll === "string" && record.LSHandlerRoleAll) || (typeof record.LSHandlerRoleViewer === "string" && record.LSHandlerRoleViewer) || null; - if (role) candidate = role; + if (role) { + candidate = role; + } } return candidate; }; @@ -212,19 +255,33 @@ function detectDefaultChromiumExecutableLinux(): BrowserExecutable | null { const desktopId = execText("xdg-settings", ["get", "default-web-browser"]) || execText("xdg-mime", ["query", "default", "x-scheme-handler/http"]); - if (!desktopId) return null; + if (!desktopId) { + return null; + } const trimmed = desktopId.trim(); - if (!CHROMIUM_DESKTOP_IDS.has(trimmed)) return null; + if (!CHROMIUM_DESKTOP_IDS.has(trimmed)) { + return null; + } const desktopPath = findDesktopFilePath(trimmed); - if (!desktopPath) return null; + if (!desktopPath) { + return null; + } const execLine = readDesktopExecLine(desktopPath); - if (!execLine) return null; + if (!execLine) { + return null; + } const command = extractExecutableFromExecLine(execLine); - if (!command) return null; + if (!command) { + return null; + } const resolved = resolveLinuxExecutablePath(command); - if (!resolved) return null; + if (!resolved) { + return null; + } const exeName = path.posix.basename(resolved).toLowerCase(); - if (!CHROMIUM_EXE_NAMES.has(exeName)) return null; + if (!CHROMIUM_EXE_NAMES.has(exeName)) { + return null; + } return { kind: inferKindFromExecutableName(exeName), path: resolved }; } @@ -232,13 +289,21 @@ function detectDefaultChromiumExecutableWindows(): BrowserExecutable | null { const progId = readWindowsProgId(); const command = (progId ? readWindowsCommandForProgId(progId) : null) || readWindowsCommandForProgId("http"); - if (!command) return null; + if (!command) { + return null; + } const expanded = expandWindowsEnvVars(command); const exePath = extractWindowsExecutablePath(expanded); - if (!exePath) return null; - if (!exists(exePath)) return null; + if (!exePath) { + return null; + } + if (!exists(exePath)) { + return null; + } const exeName = path.win32.basename(exePath).toLowerCase(); - if (!CHROMIUM_EXE_NAMES.has(exeName)) return null; + if (!CHROMIUM_EXE_NAMES.has(exeName)) { + return null; + } return { kind: inferKindFromExecutableName(exeName), path: exePath }; } @@ -250,7 +315,9 @@ function findDesktopFilePath(desktopId: string): string | null { path.join("/var/lib/snapd/desktop/applications", desktopId), ]; for (const candidate of candidates) { - if (exists(candidate)) return candidate; + if (exists(candidate)) { + return candidate; + } } return null; } @@ -273,9 +340,15 @@ function readDesktopExecLine(desktopPath: string): string | null { function extractExecutableFromExecLine(execLine: string): string | null { const tokens = splitExecLine(execLine); for (const token of tokens) { - if (!token) continue; - if (token === "env") continue; - if (token.includes("=") && !token.startsWith("/") && !token.includes("\\")) continue; + if (!token) { + continue; + } + if (token === "env") { + continue; + } + if (token.includes("=") && !token.startsWith("/") && !token.includes("\\")) { + continue; + } return token.replace(/^["']|["']$/g, ""); } return null; @@ -307,14 +380,20 @@ function splitExecLine(line: string): string[] { } current += ch; } - if (current) tokens.push(current); + if (current) { + tokens.push(current); + } return tokens; } function resolveLinuxExecutablePath(command: string): string | null { const cleaned = command.trim().replace(/%[a-zA-Z]/g, ""); - if (!cleaned) return null; - if (cleaned.startsWith("/")) return cleaned; + if (!cleaned) { + return null; + } + if (cleaned.startsWith("/")) { + return cleaned; + } const resolved = execText("which", [cleaned], 800); return resolved ? resolved.trim() : null; } @@ -326,7 +405,9 @@ function readWindowsProgId(): string | null { "/v", "ProgId", ]); - if (!output) return null; + if (!output) { + return null; + } const match = output.match(/ProgId\s+REG_\w+\s+(.+)$/im); return match?.[1]?.trim() || null; } @@ -337,7 +418,9 @@ function readWindowsCommandForProgId(progId: string): string | null { ? "HKCR\\http\\shell\\open\\command" : `HKCR\\${progId}\\shell\\open\\command`; const output = execText("reg", ["query", key, "/ve"]); - if (!output) return null; + if (!output) { + return null; + } const match = output.match(/REG_\w+\s+(.+)$/im); return match?.[1]?.trim() || null; } @@ -351,15 +434,21 @@ function expandWindowsEnvVars(value: string): string { function extractWindowsExecutablePath(command: string): string | null { const quoted = command.match(/"([^"]+\\.exe)"/i); - if (quoted?.[1]) return quoted[1]; + if (quoted?.[1]) { + return quoted[1]; + } const unquoted = command.match(/([^\\s]+\\.exe)/i); - if (unquoted?.[1]) return unquoted[1]; + if (unquoted?.[1]) { + return unquoted[1]; + } return null; } function findFirstExecutable(candidates: Array): BrowserExecutable | null { for (const candidate of candidates) { - if (exists(candidate.path)) return candidate; + if (exists(candidate.path)) { + return candidate; + } } return null; @@ -520,10 +609,18 @@ export function resolveBrowserExecutableForPlatform( } const detected = detectDefaultChromiumExecutable(platform); - if (detected) return detected; + if (detected) { + return detected; + } - if (platform === "darwin") return findChromeExecutableMac(); - if (platform === "linux") return findChromeExecutableLinux(); - if (platform === "win32") return findChromeExecutableWindows(); + if (platform === "darwin") { + return findChromeExecutableMac(); + } + if (platform === "linux") { + return findChromeExecutableLinux(); + } + if (platform === "win32") { + return findChromeExecutableWindows(); + } return null; } diff --git a/src/browser/chrome.profile-decoration.ts b/src/browser/chrome.profile-decoration.ts index 99235a9fc2..fe6fece723 100644 --- a/src/browser/chrome.profile-decoration.ts +++ b/src/browser/chrome.profile-decoration.ts @@ -12,10 +12,14 @@ function decoratedMarkerPath(userDataDir: string) { function safeReadJson(filePath: string): Record | null { try { - if (!fs.existsSync(filePath)) return null; + if (!fs.existsSync(filePath)) { + return null; + } const raw = fs.readFileSync(filePath, "utf-8"); const parsed = JSON.parse(raw) as unknown; - if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null; + if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) { + return null; + } return parsed as Record; } catch { return null; @@ -41,7 +45,9 @@ function setDeep(obj: Record, keys: string[], value: unknown) { function parseHexRgbToSignedArgbInt(hex: string): number | null { const cleaned = hex.trim().replace(/^#/, ""); - if (!/^[0-9a-fA-F]{6}$/.test(cleaned)) return null; + if (!/^[0-9a-fA-F]{6}$/.test(cleaned)) { + return null; + } const rgb = Number.parseInt(cleaned, 16); const argbUnsigned = (0xff << 24) | rgb; // Chrome stores colors as signed 32-bit ints (SkColor). diff --git a/src/browser/chrome.ts b/src/browser/chrome.ts index f0b7140458..8a9b0b76f4 100644 --- a/src/browser/chrome.ts +++ b/src/browser/chrome.ts @@ -88,9 +88,13 @@ async function fetchChromeVersion(cdpUrl: string, timeoutMs = 500): Promise { const version = await fetchChromeVersion(cdpUrl, timeoutMs); const wsUrl = String(version?.webSocketDebuggerUrl ?? "").trim(); - if (!wsUrl) return null; + if (!wsUrl) { + return null; + } return normalizeCdpWsUrl(wsUrl, cdpUrl); } @@ -149,7 +155,9 @@ export async function isChromeCdpReady( handshakeTimeoutMs = 800, ): Promise { const wsUrl = await getChromeWebSocketUrl(cdpUrl, timeoutMs); - if (!wsUrl) return false; + if (!wsUrl) { + return false; + } return await canOpenWebSocket(wsUrl, handshakeTimeoutMs); } @@ -232,7 +240,9 @@ export async function launchOpenClawChrome( const bootstrap = spawnOnce(); const deadline = Date.now() + 10_000; while (Date.now() < deadline) { - if (exists(localStatePath) && exists(preferencesPath)) break; + if (exists(localStatePath) && exists(preferencesPath)) { + break; + } await new Promise((r) => setTimeout(r, 100)); } try { @@ -242,7 +252,9 @@ export async function launchOpenClawChrome( } const exitDeadline = Date.now() + 5000; while (Date.now() < exitDeadline) { - if (bootstrap.exitCode != null) break; + if (bootstrap.exitCode != null) { + break; + } await new Promise((r) => setTimeout(r, 50)); } } @@ -269,7 +281,9 @@ export async function launchOpenClawChrome( // Wait for CDP to come up. const readyDeadline = Date.now() + 15_000; while (Date.now() < readyDeadline) { - if (await isChromeReachable(profile.cdpUrl, 500)) break; + if (await isChromeReachable(profile.cdpUrl, 500)) { + break; + } await new Promise((r) => setTimeout(r, 200)); } @@ -301,7 +315,9 @@ export async function launchOpenClawChrome( export async function stopOpenClawChrome(running: RunningChrome, timeoutMs = 2500) { const proc = running.proc; - if (proc.killed) return; + if (proc.killed) { + return; + } try { proc.kill("SIGTERM"); } catch { @@ -310,8 +326,12 @@ export async function stopOpenClawChrome(running: RunningChrome, timeoutMs = 250 const start = Date.now(); while (Date.now() - start < timeoutMs) { - if (!proc.exitCode && proc.killed) break; - if (!(await isChromeReachable(cdpUrlForPort(running.cdpPort), 200))) return; + if (!proc.exitCode && proc.killed) { + break; + } + if (!(await isChromeReachable(cdpUrlForPort(running.cdpPort), 200))) { + return; + } await new Promise((r) => setTimeout(r, 100)); } diff --git a/src/browser/client-actions-core.ts b/src/browser/client-actions-core.ts index 96688f7014..7c90d2a7c5 100644 --- a/src/browser/client-actions-core.ts +++ b/src/browser/client-actions-core.ts @@ -11,7 +11,9 @@ function buildProfileQuery(profile?: string): string { function withBaseUrl(baseUrl: string | undefined, path: string): string { const trimmed = baseUrl?.trim(); - if (!trimmed) return path; + if (!trimmed) { + return path; + } return `${trimmed.replace(/\/$/, "")}${path}`; } diff --git a/src/browser/client-actions-observe.ts b/src/browser/client-actions-observe.ts index 4b042d9b05..50d0948683 100644 --- a/src/browser/client-actions-observe.ts +++ b/src/browser/client-actions-observe.ts @@ -12,7 +12,9 @@ function buildProfileQuery(profile?: string): string { function withBaseUrl(baseUrl: string | undefined, path: string): string { const trimmed = baseUrl?.trim(); - if (!trimmed) return path; + if (!trimmed) { + return path; + } return `${trimmed.replace(/\/$/, "")}${path}`; } @@ -21,9 +23,15 @@ export async function browserConsoleMessages( opts: { level?: string; targetId?: string; profile?: string } = {}, ): Promise<{ ok: true; messages: BrowserConsoleMessage[]; targetId: string }> { const q = new URLSearchParams(); - if (opts.level) q.set("level", opts.level); - if (opts.targetId) q.set("targetId", opts.targetId); - if (opts.profile) q.set("profile", opts.profile); + if (opts.level) { + q.set("level", opts.level); + } + if (opts.targetId) { + q.set("targetId", opts.targetId); + } + if (opts.profile) { + q.set("profile", opts.profile); + } const suffix = q.toString() ? `?${q.toString()}` : ""; return await fetchBrowserJson<{ ok: true; @@ -50,9 +58,15 @@ export async function browserPageErrors( opts: { targetId?: string; clear?: boolean; profile?: string } = {}, ): Promise<{ ok: true; targetId: string; errors: BrowserPageError[] }> { const q = new URLSearchParams(); - if (opts.targetId) q.set("targetId", opts.targetId); - if (typeof opts.clear === "boolean") q.set("clear", String(opts.clear)); - if (opts.profile) q.set("profile", opts.profile); + if (opts.targetId) { + q.set("targetId", opts.targetId); + } + if (typeof opts.clear === "boolean") { + q.set("clear", String(opts.clear)); + } + if (opts.profile) { + q.set("profile", opts.profile); + } const suffix = q.toString() ? `?${q.toString()}` : ""; return await fetchBrowserJson<{ ok: true; @@ -71,10 +85,18 @@ export async function browserRequests( } = {}, ): Promise<{ ok: true; targetId: string; requests: BrowserNetworkRequest[] }> { const q = new URLSearchParams(); - if (opts.targetId) q.set("targetId", opts.targetId); - if (opts.filter) q.set("filter", opts.filter); - if (typeof opts.clear === "boolean") q.set("clear", String(opts.clear)); - if (opts.profile) q.set("profile", opts.profile); + if (opts.targetId) { + q.set("targetId", opts.targetId); + } + if (opts.filter) { + q.set("filter", opts.filter); + } + if (typeof opts.clear === "boolean") { + q.set("clear", String(opts.clear)); + } + if (opts.profile) { + q.set("profile", opts.profile); + } const suffix = q.toString() ? `?${q.toString()}` : ""; return await fetchBrowserJson<{ ok: true; diff --git a/src/browser/client-actions-state.ts b/src/browser/client-actions-state.ts index 751634a9e0..b2f351b33d 100644 --- a/src/browser/client-actions-state.ts +++ b/src/browser/client-actions-state.ts @@ -7,7 +7,9 @@ function buildProfileQuery(profile?: string): string { function withBaseUrl(baseUrl: string | undefined, path: string): string { const trimmed = baseUrl?.trim(); - if (!trimmed) return path; + if (!trimmed) { + return path; + } return `${trimmed.replace(/\/$/, "")}${path}`; } @@ -16,8 +18,12 @@ export async function browserCookies( opts: { targetId?: string; profile?: string } = {}, ): Promise<{ ok: true; targetId: string; cookies: unknown[] }> { const q = new URLSearchParams(); - if (opts.targetId) q.set("targetId", opts.targetId); - if (opts.profile) q.set("profile", opts.profile); + if (opts.targetId) { + q.set("targetId", opts.targetId); + } + if (opts.profile) { + q.set("profile", opts.profile); + } const suffix = q.toString() ? `?${q.toString()}` : ""; return await fetchBrowserJson<{ ok: true; @@ -66,9 +72,15 @@ export async function browserStorageGet( }, ): Promise<{ ok: true; targetId: string; values: Record }> { const q = new URLSearchParams(); - if (opts.targetId) q.set("targetId", opts.targetId); - if (opts.key) q.set("key", opts.key); - if (opts.profile) q.set("profile", opts.profile); + if (opts.targetId) { + q.set("targetId", opts.targetId); + } + if (opts.key) { + q.set("key", opts.key); + } + if (opts.profile) { + q.set("profile", opts.profile); + } const suffix = q.toString() ? `?${q.toString()}` : ""; return await fetchBrowserJson<{ ok: true; diff --git a/src/browser/client.ts b/src/browser/client.ts index b7eabc0d09..5085825cb6 100644 --- a/src/browser/client.ts +++ b/src/browser/client.ts @@ -92,7 +92,9 @@ function buildProfileQuery(profile?: string): string { function withBaseUrl(baseUrl: string | undefined, path: string): string { const trimmed = baseUrl?.trim(); - if (!trimmed) return path; + if (!trimmed) { + return path; + } return `${trimmed.replace(/\/$/, "")}${path}`; } @@ -291,21 +293,42 @@ export async function browserSnapshot( ): Promise { const q = new URLSearchParams(); q.set("format", opts.format); - if (opts.targetId) q.set("targetId", opts.targetId); - if (typeof opts.limit === "number") q.set("limit", String(opts.limit)); + if (opts.targetId) { + q.set("targetId", opts.targetId); + } + if (typeof opts.limit === "number") { + q.set("limit", String(opts.limit)); + } if (typeof opts.maxChars === "number" && Number.isFinite(opts.maxChars)) { q.set("maxChars", String(opts.maxChars)); } - if (opts.refs === "aria" || opts.refs === "role") q.set("refs", opts.refs); - if (typeof opts.interactive === "boolean") q.set("interactive", String(opts.interactive)); - if (typeof opts.compact === "boolean") q.set("compact", String(opts.compact)); - if (typeof opts.depth === "number" && Number.isFinite(opts.depth)) + if (opts.refs === "aria" || opts.refs === "role") { + q.set("refs", opts.refs); + } + if (typeof opts.interactive === "boolean") { + q.set("interactive", String(opts.interactive)); + } + if (typeof opts.compact === "boolean") { + q.set("compact", String(opts.compact)); + } + if (typeof opts.depth === "number" && Number.isFinite(opts.depth)) { q.set("depth", String(opts.depth)); - if (opts.selector?.trim()) q.set("selector", opts.selector.trim()); - if (opts.frame?.trim()) q.set("frame", opts.frame.trim()); - if (opts.labels === true) q.set("labels", "1"); - if (opts.mode) q.set("mode", opts.mode); - if (opts.profile) q.set("profile", opts.profile); + } + if (opts.selector?.trim()) { + q.set("selector", opts.selector.trim()); + } + if (opts.frame?.trim()) { + q.set("frame", opts.frame.trim()); + } + if (opts.labels === true) { + q.set("labels", "1"); + } + if (opts.mode) { + q.set("mode", opts.mode); + } + if (opts.profile) { + q.set("profile", opts.profile); + } return await fetchBrowserJson(withBaseUrl(baseUrl, `/snapshot?${q.toString()}`), { timeoutMs: 20000, }); diff --git a/src/browser/config.ts b/src/browser/config.ts index ec03402fdd..5c384e59a7 100644 --- a/src/browser/config.ts +++ b/src/browser/config.ts @@ -57,9 +57,13 @@ function isLoopbackHost(host: string) { function normalizeHexColor(raw: string | undefined) { const value = (raw ?? "").trim(); - if (!value) return DEFAULT_OPENCLAW_BROWSER_COLOR; + if (!value) { + return DEFAULT_OPENCLAW_BROWSER_COLOR; + } const normalized = value.startsWith("#") ? value : `#${value}`; - if (!/^#[0-9a-fA-F]{6}$/.test(normalized)) return DEFAULT_OPENCLAW_BROWSER_COLOR; + if (!/^#[0-9a-fA-F]{6}$/.test(normalized)) { + return DEFAULT_OPENCLAW_BROWSER_COLOR; + } return normalized.toUpperCase(); } @@ -124,12 +128,18 @@ function ensureDefaultChromeExtensionProfile( controlPort: number, ): Record { const result = { ...profiles }; - if (result.chrome) return result; + if (result.chrome) { + return result; + } const relayPort = controlPort + 1; - if (!Number.isFinite(relayPort) || relayPort <= 0 || relayPort > 65535) return result; + if (!Number.isFinite(relayPort) || relayPort <= 0 || relayPort > 65535) { + return result; + } // Avoid adding the built-in profile if the derived relay port is already used by another profile // (legacy single-profile configs may use controlPort+1 for openclaw/openclaw CDP). - if (getUsedPorts(result).has(relayPort)) return result; + if (getUsedPorts(result).has(relayPort)) { + return result; + } result.chrome = { driver: "extension", cdpUrl: `http://127.0.0.1:${relayPort}`, @@ -226,7 +236,9 @@ export function resolveProfile( profileName: string, ): ResolvedBrowserProfile | null { const profile = resolved.profiles[profileName]; - if (!profile) return null; + if (!profile) { + return null; + } const rawProfileUrl = profile.cdpUrl?.trim() ?? ""; let cdpHost = resolved.cdpHost; diff --git a/src/browser/control-service.ts b/src/browser/control-service.ts index f8914098b0..30a7447117 100644 --- a/src/browser/control-service.ts +++ b/src/browser/control-service.ts @@ -19,11 +19,15 @@ export function createBrowserControlContext() { } export async function startBrowserControlServiceFromConfig(): Promise { - if (state) return state; + if (state) { + return state; + } const cfg = loadConfig(); const resolved = resolveBrowserConfig(cfg.browser, cfg); - if (!resolved.enabled) return null; + if (!resolved.enabled) { + return null; + } state = { server: null, @@ -36,7 +40,9 @@ export async function startBrowserControlServiceFromConfig(): Promise { logService.warn(`Chrome extension relay init failed for profile "${name}": ${String(err)}`); }); @@ -50,7 +56,9 @@ export async function startBrowserControlServiceFromConfig(): Promise { const current = state; - if (!current) return; + if (!current) { + return; + } const ctx = createBrowserRouteContext({ getState: () => state, diff --git a/src/browser/extension-relay.test.ts b/src/browser/extension-relay.test.ts index e1cd67eed1..2abdb1847e 100644 --- a/src/browser/extension-relay.test.ts +++ b/src/browser/extension-relay.test.ts @@ -18,7 +18,9 @@ async function getFreePort(): Promise { s.close((err) => (err ? reject(err) : resolve(assigned))); }); }); - if (port < 65535) return port; + if (port < 65535) { + return port; + } } } @@ -36,12 +38,16 @@ function createMessageQueue(ws: WebSocket) { let waiterTimer: NodeJS.Timeout | null = null; const flushWaiter = (value: string) => { - if (!waiter) return false; + if (!waiter) { + return false; + } const resolve = waiter; waiter = null; const reject = waiterReject; waiterReject = null; - if (waiterTimer) clearTimeout(waiterTimer); + if (waiterTimer) { + clearTimeout(waiterTimer); + } waiterTimer = null; if (reject) { // no-op (kept for symmetry) @@ -59,16 +65,22 @@ function createMessageQueue(ws: WebSocket) { : Array.isArray(data) ? Buffer.concat(data).toString("utf8") : Buffer.from(data).toString("utf8"); - if (flushWaiter(text)) return; + if (flushWaiter(text)) { + return; + } queue.push(text); }); ws.on("error", (err) => { - if (!waiterReject) return; + if (!waiterReject) { + return; + } const reject = waiterReject; waiterReject = null; waiter = null; - if (waiterTimer) clearTimeout(waiterTimer); + if (waiterTimer) { + clearTimeout(waiterTimer); + } waiterTimer = null; reject(err instanceof Error ? err : new Error(String(err))); }); @@ -76,7 +88,9 @@ function createMessageQueue(ws: WebSocket) { const next = (timeoutMs = 5000) => new Promise((resolve, reject) => { const existing = queue.shift(); - if (existing !== undefined) return resolve(existing); + if (existing !== undefined) { + return resolve(existing); + } waiter = resolve; waiterReject = reject; waiterTimer = setTimeout(() => { @@ -99,7 +113,9 @@ async function waitForListMatch( const deadline = Date.now() + timeoutMs; while (true) { const value = await fetchList(); - if (predicate(value)) return value; + if (predicate(value)) { + return value; + } if (Date.now() >= deadline) { throw new Error("timeout waiting for list update"); } diff --git a/src/browser/extension-relay.ts b/src/browser/extension-relay.ts index ea3d10d2ae..8ca3061f8c 100644 --- a/src/browser/extension-relay.ts +++ b/src/browser/extension-relay.ts @@ -99,11 +99,21 @@ function isLoopbackHost(host: string) { } function isLoopbackAddress(ip: string | undefined): boolean { - if (!ip) return false; - if (ip === "127.0.0.1") return true; - if (ip.startsWith("127.")) return true; - if (ip === "::1") return true; - if (ip.startsWith("::ffff:127.")) return true; + if (!ip) { + return false; + } + if (ip === "127.0.0.1") { + return true; + } + if (ip.startsWith("127.")) { + return true; + } + if (ip === "::1") { + return true; + } + if (ip.startsWith("::ffff:127.")) { + return true; + } return false; } @@ -158,7 +168,9 @@ export async function ensureChromeExtensionRelayServer(opts: { } const existing = serversByPort.get(info.port); - if (existing) return existing; + if (existing) { + return existing; + } let extensionWs: WebSocket | null = null; const cdpClients = new Set(); @@ -192,13 +204,17 @@ export async function ensureChromeExtensionRelayServer(opts: { const broadcastToCdpClients = (evt: CdpEvent) => { const msg = JSON.stringify(evt); for (const ws of cdpClients) { - if (ws.readyState !== WebSocket.OPEN) continue; + if (ws.readyState !== WebSocket.OPEN) { + continue; + } ws.send(msg); } }; const sendResponseToCdp = (ws: WebSocket, res: CdpResponse) => { - if (ws.readyState !== WebSocket.OPEN) return; + if (ws.readyState !== WebSocket.OPEN) { + return; + } ws.send(JSON.stringify(res)); }; @@ -253,12 +269,16 @@ export async function ensureChromeExtensionRelayServer(opts: { const targetId = typeof params.targetId === "string" ? params.targetId : undefined; if (targetId) { for (const t of connectedTargets.values()) { - if (t.targetId === targetId) return { targetInfo: t.targetInfo }; + if (t.targetId === targetId) { + return { targetInfo: t.targetInfo }; + } } } if (cmd.sessionId && connectedTargets.has(cmd.sessionId)) { const t = connectedTargets.get(cmd.sessionId); - if (t) return { targetInfo: t.targetInfo }; + if (t) { + return { targetInfo: t.targetInfo }; + } } const first = Array.from(connectedTargets.values())[0]; return { targetInfo: first?.targetInfo }; @@ -266,9 +286,13 @@ export async function ensureChromeExtensionRelayServer(opts: { case "Target.attachToTarget": { const params = (cmd.params ?? {}) as { targetId?: string }; const targetId = typeof params.targetId === "string" ? params.targetId : undefined; - if (!targetId) throw new Error("targetId required"); + if (!targetId) { + throw new Error("targetId required"); + } for (const t of connectedTargets.values()) { - if (t.targetId === targetId) return { sessionId: t.sessionId }; + if (t.targetId === targetId) { + return { sessionId: t.sessionId }; + } } throw new Error("target not found"); } @@ -322,7 +346,9 @@ export async function ensureChromeExtensionRelayServer(opts: { "Protocol-Version": "1.3", }; // Only advertise the WS URL if a real extension is connected. - if (extensionWs) payload.webSocketDebuggerUrl = cdpWsUrl; + if (extensionWs) { + payload.webSocketDebuggerUrl = cdpWsUrl; + } res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(payload)); return; @@ -438,7 +464,9 @@ export async function ensureChromeExtensionRelayServer(opts: { extensionWs = ws; const ping = setInterval(() => { - if (ws.readyState !== WebSocket.OPEN) return; + if (ws.readyState !== WebSocket.OPEN) { + return; + } ws.send(JSON.stringify({ method: "ping" } satisfies ExtensionPingMessage)); }, 5000); @@ -452,7 +480,9 @@ export async function ensureChromeExtensionRelayServer(opts: { if (parsed && typeof parsed === "object" && "id" in parsed && typeof parsed.id === "number") { const pending = pendingExtension.get(parsed.id); - if (!pending) return; + if (!pending) { + return; + } pendingExtension.delete(parsed.id); clearTimeout(pending.timer); if ("error" in parsed && typeof parsed.error === "string" && parsed.error.trim()) { @@ -464,18 +494,26 @@ export async function ensureChromeExtensionRelayServer(opts: { } if (parsed && typeof parsed === "object" && "method" in parsed) { - if ((parsed as ExtensionPongMessage).method === "pong") return; - if ((parsed as ExtensionForwardEventMessage).method !== "forwardCDPEvent") return; + if ((parsed as ExtensionPongMessage).method === "pong") { + return; + } + if ((parsed as ExtensionForwardEventMessage).method !== "forwardCDPEvent") { + return; + } const evt = parsed as ExtensionForwardEventMessage; const method = evt.params?.method; const params = evt.params?.params; const sessionId = evt.params?.sessionId; - if (!method || typeof method !== "string") return; + if (!method || typeof method !== "string") { + return; + } if (method === "Target.attachedToTarget") { const attached = (params ?? {}) as AttachedToTargetEvent; const targetType = attached?.targetInfo?.type ?? "page"; - if (targetType !== "page") return; + if (targetType !== "page") { + return; + } if (attached?.sessionId && attached?.targetInfo?.targetId) { const prev = connectedTargets.get(attached.sessionId); const nextTargetId = attached.targetInfo.targetId; @@ -502,7 +540,9 @@ export async function ensureChromeExtensionRelayServer(opts: { if (method === "Target.detachedFromTarget") { const detached = (params ?? {}) as DetachedFromTargetEvent; - if (detached?.sessionId) connectedTargets.delete(detached.sessionId); + if (detached?.sessionId) { + connectedTargets.delete(detached.sessionId); + } broadcastToCdpClients({ method, params, sessionId }); return; } @@ -515,7 +555,9 @@ export async function ensureChromeExtensionRelayServer(opts: { const targetId = targetInfo?.targetId; if (targetId && (targetInfo?.type ?? "page") === "page") { for (const [sid, target] of connectedTargets) { - if (target.targetId !== targetId) continue; + if (target.targetId !== targetId) { + continue; + } connectedTargets.set(sid, { ...target, targetInfo: { ...target.targetInfo, ...(targetInfo as object) }, @@ -559,8 +601,12 @@ export async function ensureChromeExtensionRelayServer(opts: { } catch { return; } - if (!cmd || typeof cmd !== "object") return; - if (typeof cmd.id !== "number" || typeof cmd.method !== "string") return; + if (!cmd || typeof cmd !== "object") { + return; + } + if (typeof cmd.id !== "number" || typeof cmd.method !== "string") { + return; + } if (!extensionWs) { sendResponseToCdp(ws, { @@ -665,7 +711,9 @@ export async function ensureChromeExtensionRelayServer(opts: { export async function stopChromeExtensionRelayServer(opts: { cdpUrl: string }): Promise { const info = parseBaseUrl(opts.cdpUrl); const existing = serversByPort.get(info.port); - if (!existing) return false; + if (!existing) { + return false; + } await existing.stop(); return true; } diff --git a/src/browser/profiles-service.ts b/src/browser/profiles-service.ts index 36f2c4523a..d39c67b986 100644 --- a/src/browser/profiles-service.ts +++ b/src/browser/profiles-service.ts @@ -124,7 +124,9 @@ export function createBrowserProfilesService(ctx: BrowserRouteContext) { const deleteProfile = async (nameRaw: string): Promise => { const name = nameRaw.trim(); - if (!name) throw new Error("profile name is required"); + if (!name) { + throw new Error("profile name is required"); + } if (!isValidProfileName(name)) { throw new Error("invalid profile name"); } diff --git a/src/browser/profiles.ts b/src/browser/profiles.ts index b45f5dfd8f..43a3d14ae2 100644 --- a/src/browser/profiles.ts +++ b/src/browser/profiles.ts @@ -18,7 +18,9 @@ export const CDP_PORT_RANGE_END = 18899; export const PROFILE_NAME_REGEX = /^[a-z0-9][a-z0-9-]*$/; export function isValidProfileName(name: string): boolean { - if (!name || name.length > 64) return false; + if (!name || name.length > 64) { + return false; + } return PROFILE_NAME_REGEX.test(name); } @@ -31,9 +33,13 @@ export function allocateCdpPort( if (!Number.isFinite(start) || !Number.isFinite(end) || start <= 0 || end <= 0) { return null; } - if (start > end) return null; + if (start > end) { + return null; + } for (let port = start; port <= end; port++) { - if (!usedPorts.has(port)) return port; + if (!usedPorts.has(port)) { + return port; + } } return null; } @@ -41,7 +47,9 @@ export function allocateCdpPort( export function getUsedPorts( profiles: Record | undefined, ): Set { - if (!profiles) return new Set(); + if (!profiles) { + return new Set(); + } const used = new Set(); for (const profile of Object.values(profiles)) { if (typeof profile.cdpPort === "number") { @@ -49,7 +57,9 @@ export function getUsedPorts( continue; } const rawUrl = profile.cdpUrl?.trim(); - if (!rawUrl) continue; + if (!rawUrl) { + continue; + } try { const parsed = new URL(rawUrl); const port = @@ -97,6 +107,8 @@ export function allocateColor(usedColors: Set): string { export function getUsedColors( profiles: Record | undefined, ): Set { - if (!profiles) return new Set(); + if (!profiles) { + return new Set(); + } return new Set(Object.values(profiles).map((p) => p.color.toUpperCase())); } diff --git a/src/browser/pw-ai-module.ts b/src/browser/pw-ai-module.ts index 2fd88ad59e..e5062b8fe7 100644 --- a/src/browser/pw-ai-module.ts +++ b/src/browser/pw-ai-module.ts @@ -9,7 +9,9 @@ let pwAiModuleStrict: Promise | null = null; function isModuleNotFoundError(err: unknown): boolean { const code = extractErrorCode(err); - if (code === "ERR_MODULE_NOT_FOUND") return true; + if (code === "ERR_MODULE_NOT_FOUND") { + return true; + } const msg = formatErrorMessage(err); return ( msg.includes("Cannot find module") || @@ -24,8 +26,12 @@ async function loadPwAiModule(mode: PwAiLoadMode): Promise { try { return await import("./pw-ai.js"); } catch (err) { - if (mode === "soft") return null; - if (isModuleNotFoundError(err)) return null; + if (mode === "soft") { + return null; + } + if (isModuleNotFoundError(err)) { + return null; + } throw err; } } @@ -33,9 +39,13 @@ async function loadPwAiModule(mode: PwAiLoadMode): Promise { export async function getPwAiModule(opts?: { mode?: PwAiLoadMode }): Promise { const mode: PwAiLoadMode = opts?.mode ?? "soft"; if (mode === "soft") { - if (!pwAiModuleSoft) pwAiModuleSoft = loadPwAiModule("soft"); + if (!pwAiModuleSoft) { + pwAiModuleSoft = loadPwAiModule("soft"); + } return await pwAiModuleSoft; } - if (!pwAiModuleStrict) pwAiModuleStrict = loadPwAiModule("strict"); + if (!pwAiModuleStrict) { + pwAiModuleStrict = loadPwAiModule("strict"); + } return await pwAiModuleStrict; } diff --git a/src/browser/pw-role-snapshot.ts b/src/browser/pw-role-snapshot.ts index 0f9a800bbd..bac62859a7 100644 --- a/src/browser/pw-role-snapshot.ts +++ b/src/browser/pw-role-snapshot.ts @@ -125,7 +125,9 @@ function createRoleNameTracker(): RoleNameTracker { getDuplicateKeys() { const out = new Set(); for (const [key, refs] of refsByKey) { - if (refs.length > 1) out.add(key); + if (refs.length > 1) { + out.add(key); + } } return out; }, @@ -136,7 +138,9 @@ function removeNthFromNonDuplicates(refs: RoleRefMap, tracker: RoleNameTracker) const duplicates = tracker.getDuplicateKeys(); for (const [ref, data] of Object.entries(refs)) { const key = tracker.getKey(data.role, data.name); - if (!duplicates.has(key)) delete refs[ref]?.nth; + if (!duplicates.has(key)) { + delete refs[ref]?.nth; + } } } @@ -159,13 +163,17 @@ function compactTree(tree: string) { let hasRelevantChildren = false; for (let j = i + 1; j < lines.length; j += 1) { const childIndent = getIndentLevel(lines[j]); - if (childIndent <= currentIndent) break; + if (childIndent <= currentIndent) { + break; + } if (lines[j]?.includes("[ref=")) { hasRelevantChildren = true; break; } } - if (hasRelevantChildren) result.push(line); + if (hasRelevantChildren) { + result.push(line); + } } return result.join("\n"); @@ -179,24 +187,36 @@ function processLine( nextRef: () => string, ): string | null { const depth = getIndentLevel(line); - if (options.maxDepth !== undefined && depth > options.maxDepth) return null; + if (options.maxDepth !== undefined && depth > options.maxDepth) { + return null; + } const match = line.match(/^(\s*-\s*)(\w+)(?:\s+"([^"]*)")?(.*)$/); - if (!match) return options.interactive ? null : line; + if (!match) { + return options.interactive ? null : line; + } const [, prefix, roleRaw, name, suffix] = match; - if (roleRaw.startsWith("/")) return options.interactive ? null : line; + if (roleRaw.startsWith("/")) { + return options.interactive ? null : line; + } const role = roleRaw.toLowerCase(); const isInteractive = INTERACTIVE_ROLES.has(role); const isContent = CONTENT_ROLES.has(role); const isStructural = STRUCTURAL_ROLES.has(role); - if (options.interactive && !isInteractive) return null; - if (options.compact && isStructural && !name) return null; + if (options.interactive && !isInteractive) { + return null; + } + if (options.compact && isStructural && !name) { + return null; + } const shouldHaveRef = isInteractive || (isContent && name); - if (!shouldHaveRef) return line; + if (!shouldHaveRef) { + return line; + } const ref = nextRef(); const nth = tracker.getNextIndex(role, name); @@ -208,16 +228,24 @@ function processLine( }; let enhanced = `${prefix}${roleRaw}`; - if (name) enhanced += ` "${name}"`; + if (name) { + enhanced += ` "${name}"`; + } enhanced += ` [ref=${ref}]`; - if (nth > 0) enhanced += ` [nth=${nth}]`; - if (suffix) enhanced += suffix; + if (nth > 0) { + enhanced += ` [nth=${nth}]`; + } + if (suffix) { + enhanced += suffix; + } return enhanced; } export function parseRoleRef(raw: string): string | null { const trimmed = raw.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const normalized = trimmed.startsWith("@") ? trimmed.slice(1) : trimmed.startsWith("ref=") @@ -244,15 +272,23 @@ export function buildRoleSnapshotFromAriaSnapshot( const result: string[] = []; for (const line of lines) { const depth = getIndentLevel(line); - if (options.maxDepth !== undefined && depth > options.maxDepth) continue; + if (options.maxDepth !== undefined && depth > options.maxDepth) { + continue; + } const match = line.match(/^(\s*-\s*)(\w+)(?:\s+"([^"]*)")?(.*)$/); - if (!match) continue; + if (!match) { + continue; + } const [, , roleRaw, name, suffix] = match; - if (roleRaw.startsWith("/")) continue; + if (roleRaw.startsWith("/")) { + continue; + } const role = roleRaw.toLowerCase(); - if (!INTERACTIVE_ROLES.has(role)) continue; + if (!INTERACTIVE_ROLES.has(role)) { + continue; + } const ref = nextRef(); const nth = tracker.getNextIndex(role, name); @@ -264,10 +300,16 @@ export function buildRoleSnapshotFromAriaSnapshot( }; let enhanced = `- ${roleRaw}`; - if (name) enhanced += ` "${name}"`; + if (name) { + enhanced += ` "${name}"`; + } enhanced += ` [ref=${ref}]`; - if (nth > 0) enhanced += ` [nth=${nth}]`; - if (suffix.includes("[")) enhanced += suffix; + if (nth > 0) { + enhanced += ` [nth=${nth}]`; + } + if (suffix.includes("[")) { + enhanced += suffix; + } result.push(enhanced); } @@ -282,7 +324,9 @@ export function buildRoleSnapshotFromAriaSnapshot( const result: string[] = []; for (const line of lines) { const processed = processLine(line, refs, options, tracker, nextRef); - if (processed !== null) result.push(processed); + if (processed !== null) { + result.push(processed); + } } removeNthFromNonDuplicates(refs, tracker); @@ -314,15 +358,25 @@ export function buildRoleSnapshotFromAiSnapshot( const out: string[] = []; for (const line of lines) { const depth = getIndentLevel(line); - if (options.maxDepth !== undefined && depth > options.maxDepth) continue; + if (options.maxDepth !== undefined && depth > options.maxDepth) { + continue; + } const match = line.match(/^(\s*-\s*)(\w+)(?:\s+"([^"]*)")?(.*)$/); - if (!match) continue; + if (!match) { + continue; + } const [, , roleRaw, name, suffix] = match; - if (roleRaw.startsWith("/")) continue; + if (roleRaw.startsWith("/")) { + continue; + } const role = roleRaw.toLowerCase(); - if (!INTERACTIVE_ROLES.has(role)) continue; + if (!INTERACTIVE_ROLES.has(role)) { + continue; + } const ref = parseAiSnapshotRef(suffix); - if (!ref) continue; + if (!ref) { + continue; + } refs[ref] = { role, ...(name ? { name } : {}) }; out.push(`- ${roleRaw}${name ? ` "${name}"` : ""}${suffix}`); } @@ -335,7 +389,9 @@ export function buildRoleSnapshotFromAiSnapshot( const out: string[] = []; for (const line of lines) { const depth = getIndentLevel(line); - if (options.maxDepth !== undefined && depth > options.maxDepth) continue; + if (options.maxDepth !== undefined && depth > options.maxDepth) { + continue; + } const match = line.match(/^(\s*-\s*)(\w+)(?:\s+"([^"]*)")?(.*)$/); if (!match) { @@ -351,10 +407,14 @@ export function buildRoleSnapshotFromAiSnapshot( const role = roleRaw.toLowerCase(); const isStructural = STRUCTURAL_ROLES.has(role); - if (options.compact && isStructural && !name) continue; + if (options.compact && isStructural && !name) { + continue; + } const ref = parseAiSnapshotRef(suffix); - if (ref) refs[ref] = { role, ...(name ? { name } : {}) }; + if (ref) { + refs[ref] = { role, ...(name ? { name } : {}) }; + } out.push(line); } diff --git a/src/browser/pw-session.browserless.live.test.ts b/src/browser/pw-session.browserless.live.test.ts index b26f19184f..250598cb9a 100644 --- a/src/browser/pw-session.browserless.live.test.ts +++ b/src/browser/pw-session.browserless.live.test.ts @@ -11,7 +11,9 @@ async function waitFor( ): Promise { const deadline = Date.now() + opts.timeoutMs; while (Date.now() < deadline) { - if (await fn()) return; + if (await fn()) { + return; + } await new Promise((r) => setTimeout(r, opts.intervalMs)); } throw new Error("timed out"); diff --git a/src/browser/pw-session.ts b/src/browser/pw-session.ts index e1dbcf7a12..8b726f1524 100644 --- a/src/browser/pw-session.ts +++ b/src/browser/pw-session.ts @@ -117,7 +117,9 @@ export function rememberRoleRefsForTarget(opts: { mode?: NonNullable; }): void { const targetId = opts.targetId.trim(); - if (!targetId) return; + if (!targetId) { + return; + } roleRefsByTarget.set(roleRefsKey(opts.cdpUrl, targetId), { refs: opts.refs, ...(opts.frameSelector ? { frameSelector: opts.frameSelector } : {}), @@ -125,7 +127,9 @@ export function rememberRoleRefsForTarget(opts: { }); while (roleRefsByTarget.size > MAX_ROLE_REFS_CACHE) { const first = roleRefsByTarget.keys().next(); - if (first.done) break; + if (first.done) { + break; + } roleRefsByTarget.delete(first.value); } } @@ -142,7 +146,9 @@ export function storeRoleRefsForTarget(opts: { state.roleRefs = opts.refs; state.roleRefsFrameSelector = opts.frameSelector; state.roleRefsMode = opts.mode; - if (!opts.targetId?.trim()) return; + if (!opts.targetId?.trim()) { + return; + } rememberRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, @@ -158,11 +164,17 @@ export function restoreRoleRefsForTarget(opts: { page: Page; }): void { const targetId = opts.targetId?.trim() || ""; - if (!targetId) return; + if (!targetId) { + return; + } const cached = roleRefsByTarget.get(roleRefsKey(opts.cdpUrl, targetId)); - if (!cached) return; + if (!cached) { + return; + } const state = ensurePageState(opts.page); - if (state.roleRefs) return; + if (state.roleRefs) { + return; + } state.roleRefs = cached.refs; state.roleRefsFrameSelector = cached.frameSelector; state.roleRefsMode = cached.mode; @@ -170,7 +182,9 @@ export function restoreRoleRefsForTarget(opts: { export function ensurePageState(page: Page): PageState { const existing = pageStates.get(page); - if (existing) return existing; + if (existing) { + return existing; + } const state: PageState = { console: [], @@ -194,7 +208,9 @@ export function ensurePageState(page: Page): PageState { location: msg.location(), }; state.console.push(entry); - if (state.console.length > MAX_CONSOLE_MESSAGES) state.console.shift(); + if (state.console.length > MAX_CONSOLE_MESSAGES) { + state.console.shift(); + } }); page.on("pageerror", (err: Error) => { state.errors.push({ @@ -203,7 +219,9 @@ export function ensurePageState(page: Page): PageState { stack: err?.stack ? String(err.stack) : undefined, timestamp: new Date().toISOString(), }); - if (state.errors.length > MAX_PAGE_ERRORS) state.errors.shift(); + if (state.errors.length > MAX_PAGE_ERRORS) { + state.errors.shift(); + } }); page.on("request", (req: Request) => { state.nextRequestId += 1; @@ -216,12 +234,16 @@ export function ensurePageState(page: Page): PageState { url: req.url(), resourceType: req.resourceType(), }); - if (state.requests.length > MAX_NETWORK_REQUESTS) state.requests.shift(); + if (state.requests.length > MAX_NETWORK_REQUESTS) { + state.requests.shift(); + } }); page.on("response", (resp: Response) => { const req = resp.request(); const id = state.requestIds.get(req); - if (!id) return; + if (!id) { + return; + } let rec: BrowserNetworkRequest | undefined; for (let i = state.requests.length - 1; i >= 0; i -= 1) { const candidate = state.requests[i]; @@ -230,13 +252,17 @@ export function ensurePageState(page: Page): PageState { break; } } - if (!rec) return; + if (!rec) { + return; + } rec.status = resp.status(); rec.ok = resp.ok(); }); page.on("requestfailed", (req: Request) => { const id = state.requestIds.get(req); - if (!id) return; + if (!id) { + return; + } let rec: BrowserNetworkRequest | undefined; for (let i = state.requests.length - 1; i >= 0; i -= 1) { const candidate = state.requests[i]; @@ -245,7 +271,9 @@ export function ensurePageState(page: Page): PageState { break; } } - if (!rec) return; + if (!rec) { + return; + } rec.failureText = req.failure()?.errorText; rec.ok = false; }); @@ -259,30 +287,42 @@ export function ensurePageState(page: Page): PageState { } function observeContext(context: BrowserContext) { - if (observedContexts.has(context)) return; + if (observedContexts.has(context)) { + return; + } observedContexts.add(context); ensureContextState(context); - for (const page of context.pages()) ensurePageState(page); + for (const page of context.pages()) { + ensurePageState(page); + } context.on("page", (page) => ensurePageState(page)); } export function ensureContextState(context: BrowserContext): ContextState { const existing = contextStates.get(context); - if (existing) return existing; + if (existing) { + return existing; + } const state: ContextState = { traceActive: false }; contextStates.set(context, state); return state; } function observeBrowser(browser: Browser) { - for (const context of browser.contexts()) observeContext(context); + for (const context of browser.contexts()) { + observeContext(context); + } } async function connectBrowser(cdpUrl: string): Promise { const normalized = normalizeCdpUrl(cdpUrl); - if (cached?.cdpUrl === normalized) return cached; - if (connecting) return await connecting; + if (cached?.cdpUrl === normalized) { + return cached; + } + if (connecting) { + return await connecting; + } const connectWithRetry = async (): Promise => { let lastErr: unknown; @@ -297,7 +337,9 @@ async function connectBrowser(cdpUrl: string): Promise { cached = connected; observeBrowser(browser); browser.on("disconnected", () => { - if (cached?.browser === browser) cached = null; + if (cached?.browser === browser) { + cached = null; + } }); return connected; } catch (err) { @@ -346,7 +388,9 @@ async function findPageByTargetId( // First, try the standard CDP session approach for (const page of pages) { const tid = await pageTargetId(page).catch(() => null); - if (tid && tid === targetId) return page; + if (tid && tid === targetId) { + return page; + } } // If CDP sessions fail (e.g., extension relay blocks Target.attachToBrowserTarget), // fall back to URL-based matching using the /json/list endpoint @@ -396,15 +440,21 @@ export async function getPageForTargetId(opts: { }): Promise { const { browser } = await connectBrowser(opts.cdpUrl); const pages = await getAllPages(browser); - if (!pages.length) throw new Error("No pages available in the connected browser."); + if (!pages.length) { + throw new Error("No pages available in the connected browser."); + } const first = pages[0]; - if (!opts.targetId) return first; + if (!opts.targetId) { + return first; + } const found = await findPageByTargetId(browser, opts.targetId, opts.cdpUrl); if (!found) { // Extension relays can block CDP attachment APIs (e.g. Target.attachToBrowserTarget), // which prevents us from resolving a page's targetId via newCDPSession(). If Playwright // only exposes a single Page, use it as a best-effort fallback. - if (pages.length === 1) return first; + if (pages.length === 1) { + return first; + } throw new Error("tab not found"); } return found; @@ -452,7 +502,9 @@ export function refLocator(page: Page, ref: string) { export async function closePlaywrightBrowserConnection(): Promise { const cur = cached; cached = null; - if (!cur) return; + if (!cur) { + return; + } await cur.browser.close().catch(() => {}); } diff --git a/src/browser/pw-tools-core.activity.ts b/src/browser/pw-tools-core.activity.ts index fb24849720..3329506002 100644 --- a/src/browser/pw-tools-core.activity.ts +++ b/src/browser/pw-tools-core.activity.ts @@ -13,7 +13,9 @@ export async function getPageErrorsViaPlaywright(opts: { const page = await getPageForTargetId(opts); const state = ensurePageState(page); const errors = [...state.errors]; - if (opts.clear) state.errors = []; + if (opts.clear) { + state.errors = []; + } return { errors }; } @@ -58,7 +60,9 @@ export async function getConsoleMessagesViaPlaywright(opts: { }): Promise { const page = await getPageForTargetId(opts); const state = ensurePageState(page); - if (!opts.level) return [...state.console]; + if (!opts.level) { + return [...state.console]; + } const min = consolePriority(opts.level); return state.console.filter((msg) => consolePriority(msg.type) >= min); } diff --git a/src/browser/pw-tools-core.clamps-timeoutms-scrollintoview.test.ts b/src/browser/pw-tools-core.clamps-timeoutms-scrollintoview.test.ts index 68586c2b6b..4a98144ed9 100644 --- a/src/browser/pw-tools-core.clamps-timeoutms-scrollintoview.test.ts +++ b/src/browser/pw-tools-core.clamps-timeoutms-scrollintoview.test.ts @@ -11,13 +11,17 @@ let pageState: { const sessionMocks = vi.hoisted(() => ({ getPageForTargetId: vi.fn(async () => { - if (!currentPage) throw new Error("missing page"); + if (!currentPage) { + throw new Error("missing page"); + } return currentPage; }), ensurePageState: vi.fn(() => pageState), restoreRoleRefsForTarget: vi.fn(() => {}), refLocator: vi.fn(() => { - if (!currentRefLocator) throw new Error("missing locator"); + if (!currentRefLocator) { + throw new Error("missing locator"); + } return currentRefLocator; }), rememberRoleRefsForTarget: vi.fn(() => {}), @@ -39,7 +43,9 @@ describe("pw-tools-core", () => { armIdDialog: 0, armIdDownload: 0, }; - for (const fn of Object.values(sessionMocks)) fn.mockClear(); + for (const fn of Object.values(sessionMocks)) { + fn.mockClear(); + } }); it("clamps timeoutMs for scrollIntoView", async () => { diff --git a/src/browser/pw-tools-core.downloads.ts b/src/browser/pw-tools-core.downloads.ts index 561d59c21e..c63b6d9eb8 100644 --- a/src/browser/pw-tools-core.downloads.ts +++ b/src/browser/pw-tools-core.downloads.ts @@ -31,7 +31,9 @@ function createPageDownloadWaiter(page: Page, timeoutMs: number) { let handler: ((download: unknown) => void) | undefined; const cleanup = () => { - if (timer) clearTimeout(timer); + if (timer) { + clearTimeout(timer); + } timer = undefined; if (handler) { page.off("download", handler as never); @@ -41,7 +43,9 @@ function createPageDownloadWaiter(page: Page, timeoutMs: number) { const promise = new Promise((resolve, reject) => { handler = (download: unknown) => { - if (done) return; + if (done) { + return; + } done = true; cleanup(); resolve(download); @@ -49,7 +53,9 @@ function createPageDownloadWaiter(page: Page, timeoutMs: number) { page.on("download", handler as never); timer = setTimeout(() => { - if (done) return; + if (done) { + return; + } done = true; cleanup(); reject(new Error("Timeout waiting for download")); @@ -59,7 +65,9 @@ function createPageDownloadWaiter(page: Page, timeoutMs: number) { return { promise, cancel: () => { - if (done) return; + if (done) { + return; + } done = true; cleanup(); }, @@ -82,7 +90,9 @@ export async function armFileUploadViaPlaywright(opts: { void page .waitForEvent("filechooser", { timeout }) .then(async (fileChooser) => { - if (state.armIdUpload !== armId) return; + if (state.armIdUpload !== armId) { + return; + } if (!opts.paths?.length) { // Playwright removed `FileChooser.cancel()`; best-effort close the chooser instead. try { @@ -130,9 +140,14 @@ export async function armDialogViaPlaywright(opts: { void page .waitForEvent("dialog", { timeout }) .then(async (dialog) => { - if (state.armIdDialog !== armId) return; - if (opts.accept) await dialog.accept(opts.promptText); - else await dialog.dismiss(); + if (state.armIdDialog !== armId) { + return; + } + if (opts.accept) { + await dialog.accept(opts.promptText); + } else { + await dialog.dismiss(); + } }) .catch(() => { // Ignore timeouts; the dialog may never appear. @@ -199,7 +214,9 @@ export async function downloadViaPlaywright(opts: { const ref = requireRef(opts.ref); const outPath = String(opts.path ?? "").trim(); - if (!outPath) throw new Error("path is required"); + if (!outPath) { + throw new Error("path is required"); + } state.armIdDownload = bumpDownloadArmId(); const armId = state.armIdDownload; diff --git a/src/browser/pw-tools-core.interactions.ts b/src/browser/pw-tools-core.interactions.ts index 4b8c1512a7..0c673ec1fa 100644 --- a/src/browser/pw-tools-core.interactions.ts +++ b/src/browser/pw-tools-core.interactions.ts @@ -88,7 +88,9 @@ export async function dragViaPlaywright(opts: { }): Promise { const startRef = requireRef(opts.startRef); const endRef = requireRef(opts.endRef); - if (!startRef || !endRef) throw new Error("startRef and endRef are required"); + if (!startRef || !endRef) { + throw new Error("startRef and endRef are required"); + } const page = await getPageForTargetId(opts); ensurePageState(page); restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page }); @@ -109,7 +111,9 @@ export async function selectOptionViaPlaywright(opts: { timeoutMs?: number; }): Promise { const ref = requireRef(opts.ref); - if (!opts.values?.length) throw new Error("values are required"); + if (!opts.values?.length) { + throw new Error("values are required"); + } const page = await getPageForTargetId(opts); ensurePageState(page); restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page }); @@ -129,7 +133,9 @@ export async function pressKeyViaPlaywright(opts: { delayMs?: number; }): Promise { const key = String(opts.key ?? "").trim(); - if (!key) throw new Error("key is required"); + if (!key) { + throw new Error("key is required"); + } const page = await getPageForTargetId(opts); ensurePageState(page); await page.keyboard.press(key, { @@ -188,7 +194,9 @@ export async function fillFormViaPlaywright(opts: { : typeof rawValue === "number" || typeof rawValue === "boolean" ? String(rawValue) : ""; - if (!ref || !type) continue; + if (!ref || !type) { + continue; + } const locator = refLocator(page, ref); if (type === "checkbox" || type === "radio") { const checked = @@ -215,7 +223,9 @@ export async function evaluateViaPlaywright(opts: { ref?: string; }): Promise { const fnText = String(opts.fn ?? "").trim(); - if (!fnText) throw new Error("function is required"); + if (!fnText) { + throw new Error("function is required"); + } const page = await getPageForTargetId(opts); ensurePageState(page); restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page }); @@ -344,13 +354,17 @@ export async function takeScreenshotViaPlaywright(opts: { restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page }); const type = opts.type ?? "png"; if (opts.ref) { - if (opts.fullPage) throw new Error("fullPage is not supported for element screenshots"); + if (opts.fullPage) { + throw new Error("fullPage is not supported for element screenshots"); + } const locator = refLocator(page, opts.ref); const buffer = await locator.screenshot({ type }); return { buffer }; } if (opts.element) { - if (opts.fullPage) throw new Error("fullPage is not supported for element screenshots"); + if (opts.fullPage) { + throw new Error("fullPage is not supported for element screenshots"); + } const locator = page.locator(opts.element).first(); const buffer = await locator.screenshot({ type }); return { buffer }; @@ -499,7 +513,9 @@ export async function setInputFilesViaPlaywright(opts: { const page = await getPageForTargetId(opts); ensurePageState(page); restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page }); - if (!opts.paths.length) throw new Error("paths are required"); + if (!opts.paths.length) { + throw new Error("paths are required"); + } const inputRef = typeof opts.inputRef === "string" ? opts.inputRef.trim() : ""; const element = typeof opts.element === "string" ? opts.element.trim() : ""; if (inputRef && element) { diff --git a/src/browser/pw-tools-core.last-file-chooser-arm-wins.test.ts b/src/browser/pw-tools-core.last-file-chooser-arm-wins.test.ts index c1cd47650b..a197691ca7 100644 --- a/src/browser/pw-tools-core.last-file-chooser-arm-wins.test.ts +++ b/src/browser/pw-tools-core.last-file-chooser-arm-wins.test.ts @@ -11,13 +11,17 @@ let pageState: { const sessionMocks = vi.hoisted(() => ({ getPageForTargetId: vi.fn(async () => { - if (!currentPage) throw new Error("missing page"); + if (!currentPage) { + throw new Error("missing page"); + } return currentPage; }), ensurePageState: vi.fn(() => pageState), restoreRoleRefsForTarget: vi.fn(() => {}), refLocator: vi.fn(() => { - if (!currentRefLocator) throw new Error("missing locator"); + if (!currentRefLocator) { + throw new Error("missing locator"); + } return currentRefLocator; }), rememberRoleRefsForTarget: vi.fn(() => {}), @@ -39,7 +43,9 @@ describe("pw-tools-core", () => { armIdDialog: 0, armIdDownload: 0, }; - for (const fn of Object.values(sessionMocks)) fn.mockClear(); + for (const fn of Object.values(sessionMocks)) { + fn.mockClear(); + } }); it("last file-chooser arm wins", async () => { diff --git a/src/browser/pw-tools-core.responses.ts b/src/browser/pw-tools-core.responses.ts index 6af5623647..5a6ddc1818 100644 --- a/src/browser/pw-tools-core.responses.ts +++ b/src/browser/pw-tools-core.responses.ts @@ -4,8 +4,12 @@ import { normalizeTimeoutMs } from "./pw-tools-core.shared.js"; function matchUrlPattern(pattern: string, url: string): boolean { const p = pattern.trim(); - if (!p) return false; - if (p === url) return true; + if (!p) { + return false; + } + if (p === url) { + return true; + } if (p.includes("*")) { const escaped = p.replace(/[|\\{}()[\]^$+?.]/g, "\\$&"); const regex = new RegExp(`^${escaped.replace(/\*\*/g, ".*").replace(/\*/g, ".*")}$`); @@ -28,7 +32,9 @@ export async function responseBodyViaPlaywright(opts: { truncated?: boolean; }> { const pattern = String(opts.url ?? "").trim(); - if (!pattern) throw new Error("url is required"); + if (!pattern) { + throw new Error("url is required"); + } const maxChars = typeof opts.maxChars === "number" && Number.isFinite(opts.maxChars) ? Math.max(1, Math.min(5_000_000, Math.floor(opts.maxChars))) @@ -44,16 +50,24 @@ export async function responseBodyViaPlaywright(opts: { let handler: ((resp: unknown) => void) | undefined; const cleanup = () => { - if (timer) clearTimeout(timer); + if (timer) { + clearTimeout(timer); + } timer = undefined; - if (handler) page.off("response", handler as never); + if (handler) { + page.off("response", handler as never); + } }; handler = (resp: unknown) => { - if (done) return; + if (done) { + return; + } const r = resp as { url?: () => string }; const u = r.url?.() || ""; - if (!matchUrlPattern(pattern, u)) return; + if (!matchUrlPattern(pattern, u)) { + return; + } done = true; cleanup(); resolve(resp); @@ -61,7 +75,9 @@ export async function responseBodyViaPlaywright(opts: { page.on("response", handler as never); timer = setTimeout(() => { - if (done) return; + if (done) { + return; + } done = true; cleanup(); reject( diff --git a/src/browser/pw-tools-core.screenshots-element-selector.test.ts b/src/browser/pw-tools-core.screenshots-element-selector.test.ts index a5ff14756c..a297f7d512 100644 --- a/src/browser/pw-tools-core.screenshots-element-selector.test.ts +++ b/src/browser/pw-tools-core.screenshots-element-selector.test.ts @@ -11,13 +11,17 @@ let pageState: { const sessionMocks = vi.hoisted(() => ({ getPageForTargetId: vi.fn(async () => { - if (!currentPage) throw new Error("missing page"); + if (!currentPage) { + throw new Error("missing page"); + } return currentPage; }), ensurePageState: vi.fn(() => pageState), restoreRoleRefsForTarget: vi.fn(() => {}), refLocator: vi.fn(() => { - if (!currentRefLocator) throw new Error("missing locator"); + if (!currentRefLocator) { + throw new Error("missing locator"); + } return currentRefLocator; }), rememberRoleRefsForTarget: vi.fn(() => {}), @@ -39,7 +43,9 @@ describe("pw-tools-core", () => { armIdDialog: 0, armIdDownload: 0, }; - for (const fn of Object.values(sessionMocks)) fn.mockClear(); + for (const fn of Object.values(sessionMocks)) { + fn.mockClear(); + } }); it("screenshots an element selector", async () => { diff --git a/src/browser/pw-tools-core.shared.ts b/src/browser/pw-tools-core.shared.ts index 6cc2521b98..d5ad74477d 100644 --- a/src/browser/pw-tools-core.shared.ts +++ b/src/browser/pw-tools-core.shared.ts @@ -23,7 +23,9 @@ export function requireRef(value: unknown): string { const raw = typeof value === "string" ? value.trim() : ""; const roleRef = raw ? parseRoleRef(raw) : null; const ref = roleRef ?? (raw.startsWith("@") ? raw.slice(1) : raw); - if (!ref) throw new Error("ref is required"); + if (!ref) { + throw new Error("ref is required"); + } return ref; } diff --git a/src/browser/pw-tools-core.snapshot.ts b/src/browser/pw-tools-core.snapshot.ts index 357283624e..158b092a9d 100644 --- a/src/browser/pw-tools-core.snapshot.ts +++ b/src/browser/pw-tools-core.snapshot.ts @@ -160,7 +160,9 @@ export async function navigateViaPlaywright(opts: { timeoutMs?: number; }): Promise<{ url: string }> { const url = String(opts.url ?? "").trim(); - if (!url) throw new Error("url is required"); + if (!url) { + throw new Error("url is required"); + } const page = await getPageForTargetId(opts); ensurePageState(page); await page.goto(url, { diff --git a/src/browser/pw-tools-core.state.ts b/src/browser/pw-tools-core.state.ts index 6841e5f86e..b7b0e146b6 100644 --- a/src/browser/pw-tools-core.state.ts +++ b/src/browser/pw-tools-core.state.ts @@ -47,7 +47,9 @@ export async function setHttpCredentialsViaPlaywright(opts: { } const username = String(opts.username ?? ""); const password = String(opts.password ?? ""); - if (!username) throw new Error("username is required (or set clear=true)"); + if (!username) { + throw new Error("username is required (or set clear=true)"); + } await page.context().setHTTPCredentials({ username, password }); } @@ -108,7 +110,9 @@ export async function setLocaleViaPlaywright(opts: { const page = await getPageForTargetId(opts); ensurePageState(page); const locale = String(opts.locale ?? "").trim(); - if (!locale) throw new Error("locale is required"); + if (!locale) { + throw new Error("locale is required"); + } await withCdpSession(page, async (session) => { try { await session.send("Emulation.setLocaleOverride", { locale }); @@ -129,15 +133,20 @@ export async function setTimezoneViaPlaywright(opts: { const page = await getPageForTargetId(opts); ensurePageState(page); const timezoneId = String(opts.timezoneId ?? "").trim(); - if (!timezoneId) throw new Error("timezoneId is required"); + if (!timezoneId) { + throw new Error("timezoneId is required"); + } await withCdpSession(page, async (session) => { try { await session.send("Emulation.setTimezoneOverride", { timezoneId }); } catch (err) { const msg = String(err); - if (msg.includes("Timezone override is already in effect")) return; - if (msg.includes("Invalid timezone")) + if (msg.includes("Timezone override is already in effect")) { + return; + } + if (msg.includes("Invalid timezone")) { throw new Error(`Invalid timezone ID: ${timezoneId}`, { cause: err }); + } throw err; } }); @@ -151,7 +160,9 @@ export async function setDeviceViaPlaywright(opts: { const page = await getPageForTargetId(opts); ensurePageState(page); const name = String(opts.name ?? "").trim(); - if (!name) throw new Error("device name is required"); + if (!name) { + throw new Error("device name is required"); + } const descriptor = (playwrightDevices as Record)[name] as | { userAgent?: string; diff --git a/src/browser/pw-tools-core.storage.ts b/src/browser/pw-tools-core.storage.ts index caf2a0d2c7..8126d66fa7 100644 --- a/src/browser/pw-tools-core.storage.ts +++ b/src/browser/pw-tools-core.storage.ts @@ -74,9 +74,13 @@ export async function storageGetViaPlaywright(opts: { const out: Record = {}; for (let i = 0; i < store.length; i += 1) { const k = store.key(i); - if (!k) continue; + if (!k) { + continue; + } const v = store.getItem(k); - if (v !== null) out[k] = v; + if (v !== null) { + out[k] = v; + } } return out; }, @@ -95,7 +99,9 @@ export async function storageSetViaPlaywright(opts: { const page = await getPageForTargetId(opts); ensurePageState(page); const key = String(opts.key ?? ""); - if (!key) throw new Error("key is required"); + if (!key) { + throw new Error("key is required"); + } await page.evaluate( ({ kind, key: k, value }) => { const store = kind === "session" ? window.sessionStorage : window.localStorage; diff --git a/src/browser/pw-tools-core.waits-next-download-saves-it.test.ts b/src/browser/pw-tools-core.waits-next-download-saves-it.test.ts index 90151c48f1..e30d3ebfec 100644 --- a/src/browser/pw-tools-core.waits-next-download-saves-it.test.ts +++ b/src/browser/pw-tools-core.waits-next-download-saves-it.test.ts @@ -12,13 +12,17 @@ let pageState: { const sessionMocks = vi.hoisted(() => ({ getPageForTargetId: vi.fn(async () => { - if (!currentPage) throw new Error("missing page"); + if (!currentPage) { + throw new Error("missing page"); + } return currentPage; }), ensurePageState: vi.fn(() => pageState), restoreRoleRefsForTarget: vi.fn(() => {}), refLocator: vi.fn(() => { - if (!currentRefLocator) throw new Error("missing locator"); + if (!currentRefLocator) { + throw new Error("missing locator"); + } return currentRefLocator; }), rememberRoleRefsForTarget: vi.fn(() => {}), @@ -40,13 +44,17 @@ describe("pw-tools-core", () => { armIdDialog: 0, armIdDownload: 0, }; - for (const fn of Object.values(sessionMocks)) fn.mockClear(); + for (const fn of Object.values(sessionMocks)) { + fn.mockClear(); + } }); it("waits for the next download and saves it", async () => { let downloadHandler: ((download: unknown) => void) | undefined; const on = vi.fn((event: string, handler: (download: unknown) => void) => { - if (event === "download") downloadHandler = handler; + if (event === "download") { + downloadHandler = handler; + } }); const off = vi.fn(); @@ -79,7 +87,9 @@ describe("pw-tools-core", () => { it("clicks a ref and saves the resulting download", async () => { let downloadHandler: ((download: unknown) => void) | undefined; const on = vi.fn((event: string, handler: (download: unknown) => void) => { - if (event === "download") downloadHandler = handler; + if (event === "download") { + downloadHandler = handler; + } }); const off = vi.fn(); @@ -118,7 +128,9 @@ describe("pw-tools-core", () => { it("waits for a matching response and returns its body", async () => { let responseHandler: ((resp: unknown) => void) | undefined; const on = vi.fn((event: string, handler: (resp: unknown) => void) => { - if (event === "response") responseHandler = handler; + if (event === "response") { + responseHandler = handler; + } }); const off = vi.fn(); currentPage = { on, off }; diff --git a/src/browser/routes/agent.act.shared.ts b/src/browser/routes/agent.act.shared.ts index 977da9c3c8..81ca8caab7 100644 --- a/src/browser/routes/agent.act.shared.ts +++ b/src/browser/routes/agent.act.shared.ts @@ -16,7 +16,9 @@ export const ACT_KINDS = [ export type ActKind = (typeof ACT_KINDS)[number]; export function isActKind(value: unknown): value is ActKind { - if (typeof value !== "string") return false; + if (typeof value !== "string") { + return false; + } return (ACT_KINDS as readonly string[]).includes(value); } @@ -32,7 +34,9 @@ const ALLOWED_CLICK_MODIFIERS = new Set([ ]); export function parseClickButton(raw: string): ClickButton | undefined { - if (raw === "left" || raw === "right" || raw === "middle") return raw; + if (raw === "left" || raw === "right" || raw === "middle") { + return raw; + } return undefined; } diff --git a/src/browser/routes/agent.act.ts b/src/browser/routes/agent.act.ts index 97e9ec3dd6..1fc40e72d0 100644 --- a/src/browser/routes/agent.act.ts +++ b/src/browser/routes/agent.act.ts @@ -22,7 +22,9 @@ export function registerBrowserAgentActRoutes( ) { app.post("/act", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const body = readBody(req); const kindRaw = toStringOrEmpty(body.kind); if (!isActKind(kindRaw)) { @@ -38,18 +40,24 @@ export function registerBrowserAgentActRoutes( const tab = await profileCtx.ensureTabAvailable(targetId); const cdpUrl = profileCtx.profile.cdpUrl; const pw = await requirePwAi(res, `act:${kind}`); - if (!pw) return; + if (!pw) { + return; + } const evaluateEnabled = ctx.state().resolved.evaluateEnabled; switch (kind) { case "click": { const ref = toStringOrEmpty(body.ref); - if (!ref) return jsonError(res, 400, "ref is required"); + if (!ref) { + return jsonError(res, 400, "ref is required"); + } const doubleClick = toBoolean(body.doubleClick) ?? false; const timeoutMs = toNumber(body.timeoutMs); const buttonRaw = toStringOrEmpty(body.button) || ""; const button = buttonRaw ? parseClickButton(buttonRaw) : undefined; - if (buttonRaw && !button) return jsonError(res, 400, "button must be left|right|middle"); + if (buttonRaw && !button) { + return jsonError(res, 400, "button must be left|right|middle"); + } const modifiersRaw = toStringArray(body.modifiers) ?? []; const parsedModifiers = parseClickModifiers(modifiersRaw); @@ -63,16 +71,26 @@ export function registerBrowserAgentActRoutes( ref, doubleClick, }; - if (button) clickRequest.button = button; - if (modifiers) clickRequest.modifiers = modifiers; - if (timeoutMs) clickRequest.timeoutMs = timeoutMs; + if (button) { + clickRequest.button = button; + } + if (modifiers) { + clickRequest.modifiers = modifiers; + } + if (timeoutMs) { + clickRequest.timeoutMs = timeoutMs; + } await pw.clickViaPlaywright(clickRequest); return res.json({ ok: true, targetId: tab.targetId, url: tab.url }); } case "type": { const ref = toStringOrEmpty(body.ref); - if (!ref) return jsonError(res, 400, "ref is required"); - if (typeof body.text !== "string") return jsonError(res, 400, "text is required"); + if (!ref) { + return jsonError(res, 400, "ref is required"); + } + if (typeof body.text !== "string") { + return jsonError(res, 400, "text is required"); + } const text = body.text; const submit = toBoolean(body.submit) ?? false; const slowly = toBoolean(body.slowly) ?? false; @@ -85,13 +103,17 @@ export function registerBrowserAgentActRoutes( submit, slowly, }; - if (timeoutMs) typeRequest.timeoutMs = timeoutMs; + if (timeoutMs) { + typeRequest.timeoutMs = timeoutMs; + } await pw.typeViaPlaywright(typeRequest); return res.json({ ok: true, targetId: tab.targetId }); } case "press": { const key = toStringOrEmpty(body.key); - if (!key) return jsonError(res, 400, "key is required"); + if (!key) { + return jsonError(res, 400, "key is required"); + } const delayMs = toNumber(body.delayMs); await pw.pressKeyViaPlaywright({ cdpUrl, @@ -103,7 +125,9 @@ export function registerBrowserAgentActRoutes( } case "hover": { const ref = toStringOrEmpty(body.ref); - if (!ref) return jsonError(res, 400, "ref is required"); + if (!ref) { + return jsonError(res, 400, "ref is required"); + } const timeoutMs = toNumber(body.timeoutMs); await pw.hoverViaPlaywright({ cdpUrl, @@ -115,21 +139,27 @@ export function registerBrowserAgentActRoutes( } case "scrollIntoView": { const ref = toStringOrEmpty(body.ref); - if (!ref) return jsonError(res, 400, "ref is required"); + if (!ref) { + return jsonError(res, 400, "ref is required"); + } const timeoutMs = toNumber(body.timeoutMs); const scrollRequest: Parameters[0] = { cdpUrl, targetId: tab.targetId, ref, }; - if (timeoutMs) scrollRequest.timeoutMs = timeoutMs; + if (timeoutMs) { + scrollRequest.timeoutMs = timeoutMs; + } await pw.scrollIntoViewViaPlaywright(scrollRequest); return res.json({ ok: true, targetId: tab.targetId }); } case "drag": { const startRef = toStringOrEmpty(body.startRef); const endRef = toStringOrEmpty(body.endRef); - if (!startRef || !endRef) return jsonError(res, 400, "startRef and endRef are required"); + if (!startRef || !endRef) { + return jsonError(res, 400, "startRef and endRef are required"); + } const timeoutMs = toNumber(body.timeoutMs); await pw.dragViaPlaywright({ cdpUrl, @@ -143,7 +173,9 @@ export function registerBrowserAgentActRoutes( case "select": { const ref = toStringOrEmpty(body.ref); const values = toStringArray(body.values); - if (!ref || !values?.length) return jsonError(res, 400, "ref and values are required"); + if (!ref || !values?.length) { + return jsonError(res, 400, "ref and values are required"); + } const timeoutMs = toNumber(body.timeoutMs); await pw.selectOptionViaPlaywright({ cdpUrl, @@ -158,11 +190,15 @@ export function registerBrowserAgentActRoutes( const rawFields = Array.isArray(body.fields) ? body.fields : []; const fields = rawFields .map((field) => { - if (!field || typeof field !== "object") return null; + if (!field || typeof field !== "object") { + return null; + } const rec = field as Record; const ref = toStringOrEmpty(rec.ref); const type = toStringOrEmpty(rec.type); - if (!ref || !type) return null; + if (!ref || !type) { + return null; + } const value = typeof rec.value === "string" || typeof rec.value === "number" || @@ -174,7 +210,9 @@ export function registerBrowserAgentActRoutes( return parsed; }) .filter((field): field is BrowserFormField => field !== null); - if (!fields.length) return jsonError(res, 400, "fields are required"); + if (!fields.length) { + return jsonError(res, 400, "fields are required"); + } const timeoutMs = toNumber(body.timeoutMs); await pw.fillFormViaPlaywright({ cdpUrl, @@ -187,7 +225,9 @@ export function registerBrowserAgentActRoutes( case "resize": { const width = toNumber(body.width); const height = toNumber(body.height); - if (!width || !height) return jsonError(res, 400, "width and height are required"); + if (!width || !height) { + return jsonError(res, 400, "width and height are required"); + } await pw.resizeViewportViaPlaywright({ cdpUrl, targetId: tab.targetId, @@ -262,7 +302,9 @@ export function registerBrowserAgentActRoutes( ); } const fn = toStringOrEmpty(body.fn); - if (!fn) return jsonError(res, 400, "fn is required"); + if (!fn) { + return jsonError(res, 400, "fn is required"); + } const ref = toStringOrEmpty(body.ref) || undefined; const result = await pw.evaluateViaPlaywright({ cdpUrl, @@ -292,7 +334,9 @@ export function registerBrowserAgentActRoutes( app.post("/hooks/file-chooser", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; const ref = toStringOrEmpty(body.ref) || undefined; @@ -300,11 +344,15 @@ export function registerBrowserAgentActRoutes( const element = toStringOrEmpty(body.element) || undefined; const paths = toStringArray(body.paths) ?? []; const timeoutMs = toNumber(body.timeoutMs); - if (!paths.length) return jsonError(res, 400, "paths are required"); + if (!paths.length) { + return jsonError(res, 400, "paths are required"); + } try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "file chooser hook"); - if (!pw) return; + if (!pw) { + return; + } if (inputRef || element) { if (ref) { return jsonError(res, 400, "ref cannot be combined with inputRef/element"); @@ -339,17 +387,23 @@ export function registerBrowserAgentActRoutes( app.post("/hooks/dialog", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; const accept = toBoolean(body.accept); const promptText = toStringOrEmpty(body.promptText) || undefined; const timeoutMs = toNumber(body.timeoutMs); - if (accept === undefined) return jsonError(res, 400, "accept is required"); + if (accept === undefined) { + return jsonError(res, 400, "accept is required"); + } try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "dialog hook"); - if (!pw) return; + if (!pw) { + return; + } await pw.armDialogViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -365,7 +419,9 @@ export function registerBrowserAgentActRoutes( app.post("/wait/download", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; const out = toStringOrEmpty(body.path) || undefined; @@ -373,7 +429,9 @@ export function registerBrowserAgentActRoutes( try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "wait for download"); - if (!pw) return; + if (!pw) { + return; + } const result = await pw.waitForDownloadViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -388,18 +446,26 @@ export function registerBrowserAgentActRoutes( app.post("/download", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; const ref = toStringOrEmpty(body.ref); const out = toStringOrEmpty(body.path); const timeoutMs = toNumber(body.timeoutMs); - if (!ref) return jsonError(res, 400, "ref is required"); - if (!out) return jsonError(res, 400, "path is required"); + if (!ref) { + return jsonError(res, 400, "ref is required"); + } + if (!out) { + return jsonError(res, 400, "path is required"); + } try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "download"); - if (!pw) return; + if (!pw) { + return; + } const result = await pw.downloadViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -415,17 +481,23 @@ export function registerBrowserAgentActRoutes( app.post("/response/body", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; const url = toStringOrEmpty(body.url); const timeoutMs = toNumber(body.timeoutMs); const maxChars = toNumber(body.maxChars); - if (!url) return jsonError(res, 400, "url is required"); + if (!url) { + return jsonError(res, 400, "url is required"); + } try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "response body"); - if (!pw) return; + if (!pw) { + return; + } const result = await pw.responseBodyViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -441,15 +513,21 @@ export function registerBrowserAgentActRoutes( app.post("/highlight", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; const ref = toStringOrEmpty(body.ref); - if (!ref) return jsonError(res, 400, "ref is required"); + if (!ref) { + return jsonError(res, 400, "ref is required"); + } try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "highlight"); - if (!pw) return; + if (!pw) { + return; + } await pw.highlightViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, diff --git a/src/browser/routes/agent.debug.ts b/src/browser/routes/agent.debug.ts index 86598b88ae..5650cbf838 100644 --- a/src/browser/routes/agent.debug.ts +++ b/src/browser/routes/agent.debug.ts @@ -13,14 +13,18 @@ export function registerBrowserAgentDebugRoutes( ) { app.get("/console", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const targetId = typeof req.query.targetId === "string" ? req.query.targetId.trim() : ""; const level = typeof req.query.level === "string" ? req.query.level : ""; try { const tab = await profileCtx.ensureTabAvailable(targetId || undefined); const pw = await requirePwAi(res, "console messages"); - if (!pw) return; + if (!pw) { + return; + } const messages = await pw.getConsoleMessagesViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -34,14 +38,18 @@ export function registerBrowserAgentDebugRoutes( app.get("/errors", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const targetId = typeof req.query.targetId === "string" ? req.query.targetId.trim() : ""; const clear = toBoolean(req.query.clear) ?? false; try { const tab = await profileCtx.ensureTabAvailable(targetId || undefined); const pw = await requirePwAi(res, "page errors"); - if (!pw) return; + if (!pw) { + return; + } const result = await pw.getPageErrorsViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -55,7 +63,9 @@ export function registerBrowserAgentDebugRoutes( app.get("/requests", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const targetId = typeof req.query.targetId === "string" ? req.query.targetId.trim() : ""; const filter = typeof req.query.filter === "string" ? req.query.filter : ""; const clear = toBoolean(req.query.clear) ?? false; @@ -63,7 +73,9 @@ export function registerBrowserAgentDebugRoutes( try { const tab = await profileCtx.ensureTabAvailable(targetId || undefined); const pw = await requirePwAi(res, "network requests"); - if (!pw) return; + if (!pw) { + return; + } const result = await pw.getNetworkRequestsViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -78,7 +90,9 @@ export function registerBrowserAgentDebugRoutes( app.post("/trace/start", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; const screenshots = toBoolean(body.screenshots) ?? undefined; @@ -87,7 +101,9 @@ export function registerBrowserAgentDebugRoutes( try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "trace start"); - if (!pw) return; + if (!pw) { + return; + } await pw.traceStartViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -103,14 +119,18 @@ export function registerBrowserAgentDebugRoutes( app.post("/trace/stop", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; const out = toStringOrEmpty(body.path) || ""; try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "trace stop"); - if (!pw) return; + if (!pw) { + return; + } const id = crypto.randomUUID(); const dir = "/tmp/openclaw"; await fs.mkdir(dir, { recursive: true }); diff --git a/src/browser/routes/agent.shared.ts b/src/browser/routes/agent.shared.ts index 08b010149c..fbe50f4c44 100644 --- a/src/browser/routes/agent.shared.ts +++ b/src/browser/routes/agent.shared.ts @@ -16,13 +16,17 @@ export const SELECTOR_UNSUPPORTED_MESSAGE = [ export function readBody(req: BrowserRequest): Record { const body = req.body as Record | undefined; - if (!body || typeof body !== "object" || Array.isArray(body)) return {}; + if (!body || typeof body !== "object" || Array.isArray(body)) { + return {}; + } return body; } export function handleRouteError(ctx: BrowserRouteContext, res: BrowserResponse, err: unknown) { const mapped = ctx.mapTabError(err); - if (mapped) return jsonError(res, mapped.status, mapped.message); + if (mapped) { + return jsonError(res, mapped.status, mapped.message); + } jsonError(res, 500, String(err)); } @@ -48,7 +52,9 @@ export async function requirePwAi( feature: string, ): Promise { const mod = await getPwAiModule(); - if (mod) return mod; + if (mod) { + return mod; + } jsonError( res, 501, diff --git a/src/browser/routes/agent.snapshot.ts b/src/browser/routes/agent.snapshot.ts index 41ade72ff2..315b7a83a0 100644 --- a/src/browser/routes/agent.snapshot.ts +++ b/src/browser/routes/agent.snapshot.ts @@ -29,15 +29,21 @@ export function registerBrowserAgentSnapshotRoutes( ) { app.post("/navigate", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const body = readBody(req); const url = toStringOrEmpty(body.url); const targetId = toStringOrEmpty(body.targetId) || undefined; - if (!url) return jsonError(res, 400, "url is required"); + if (!url) { + return jsonError(res, 400, "url is required"); + } try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "navigate"); - if (!pw) return; + if (!pw) { + return; + } const result = await pw.navigateViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -51,13 +57,17 @@ export function registerBrowserAgentSnapshotRoutes( app.post("/pdf", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "pdf"); - if (!pw) return; + if (!pw) { + return; + } const pdf = await pw.pdfViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -82,7 +92,9 @@ export function registerBrowserAgentSnapshotRoutes( app.post("/screenshot", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; const fullPage = toBoolean(body.fullPage) ?? false; @@ -101,7 +113,9 @@ export function registerBrowserAgentSnapshotRoutes( profileCtx.profile.driver === "extension" || !tab.wsUrl || Boolean(ref) || Boolean(element); if (shouldUsePlaywright) { const pw = await requirePwAi(res, "screenshot"); - if (!pw) return; + if (!pw) { + return; + } const snap = await pw.takeScreenshotViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -144,7 +158,9 @@ export function registerBrowserAgentSnapshotRoutes( app.get("/snapshot", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const targetId = typeof req.query.targetId === "string" ? req.query.targetId.trim() : ""; const mode = req.query.mode === "efficient" ? "efficient" : undefined; const labels = toBoolean(req.query.labels) ?? undefined; @@ -187,7 +203,9 @@ export function registerBrowserAgentSnapshotRoutes( } if (format === "ai") { const pw = await requirePwAi(res, "ai snapshot"); - if (!pw) return; + if (!pw) { + return; + } const wantsRoleSnapshot = labels === true || mode === "efficient" || @@ -282,7 +300,9 @@ export function registerBrowserAgentSnapshotRoutes( // Extension relay doesn't expose per-page WS URLs; run AX snapshot via Playwright CDP session. // Also covers cases where wsUrl is missing/unusable. return requirePwAi(res, "aria snapshot").then(async (pw) => { - if (!pw) return null; + if (!pw) { + return null; + } return await pw.snapshotAriaViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -293,7 +313,9 @@ export function registerBrowserAgentSnapshotRoutes( : snapshotAria({ wsUrl: tab.wsUrl ?? "", limit }); const resolved = await Promise.resolve(snap); - if (!resolved) return; + if (!resolved) { + return; + } return res.json({ ok: true, format, diff --git a/src/browser/routes/agent.storage.ts b/src/browser/routes/agent.storage.ts index 384741375e..24f8994e11 100644 --- a/src/browser/routes/agent.storage.ts +++ b/src/browser/routes/agent.storage.ts @@ -9,12 +9,16 @@ export function registerBrowserAgentStorageRoutes( ) { app.get("/cookies", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const targetId = typeof req.query.targetId === "string" ? req.query.targetId.trim() : ""; try { const tab = await profileCtx.ensureTabAvailable(targetId || undefined); const pw = await requirePwAi(res, "cookies"); - if (!pw) return; + if (!pw) { + return; + } const result = await pw.cookiesGetViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -27,18 +31,24 @@ export function registerBrowserAgentStorageRoutes( app.post("/cookies/set", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; const cookie = body.cookie && typeof body.cookie === "object" && !Array.isArray(body.cookie) ? (body.cookie as Record) : null; - if (!cookie) return jsonError(res, 400, "cookie is required"); + if (!cookie) { + return jsonError(res, 400, "cookie is required"); + } try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "cookies set"); - if (!pw) return; + if (!pw) { + return; + } await pw.cookiesSetViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -65,13 +75,17 @@ export function registerBrowserAgentStorageRoutes( app.post("/cookies/clear", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "cookies clear"); - if (!pw) return; + if (!pw) { + return; + } await pw.cookiesClearViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -84,16 +98,21 @@ export function registerBrowserAgentStorageRoutes( app.get("/storage/:kind", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const kind = toStringOrEmpty(req.params.kind); - if (kind !== "local" && kind !== "session") + if (kind !== "local" && kind !== "session") { return jsonError(res, 400, "kind must be local|session"); + } const targetId = typeof req.query.targetId === "string" ? req.query.targetId.trim() : ""; const key = typeof req.query.key === "string" ? req.query.key : ""; try { const tab = await profileCtx.ensureTabAvailable(targetId || undefined); const pw = await requirePwAi(res, "storage get"); - if (!pw) return; + if (!pw) { + return; + } const result = await pw.storageGetViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -108,19 +127,26 @@ export function registerBrowserAgentStorageRoutes( app.post("/storage/:kind/set", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const kind = toStringOrEmpty(req.params.kind); - if (kind !== "local" && kind !== "session") + if (kind !== "local" && kind !== "session") { return jsonError(res, 400, "kind must be local|session"); + } const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; const key = toStringOrEmpty(body.key); - if (!key) return jsonError(res, 400, "key is required"); + if (!key) { + return jsonError(res, 400, "key is required"); + } const value = typeof body.value === "string" ? body.value : ""; try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "storage set"); - if (!pw) return; + if (!pw) { + return; + } await pw.storageSetViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -136,16 +162,21 @@ export function registerBrowserAgentStorageRoutes( app.post("/storage/:kind/clear", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const kind = toStringOrEmpty(req.params.kind); - if (kind !== "local" && kind !== "session") + if (kind !== "local" && kind !== "session") { return jsonError(res, 400, "kind must be local|session"); + } const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "storage clear"); - if (!pw) return; + if (!pw) { + return; + } await pw.storageClearViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -159,15 +190,21 @@ export function registerBrowserAgentStorageRoutes( app.post("/set/offline", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; const offline = toBoolean(body.offline); - if (offline === undefined) return jsonError(res, 400, "offline is required"); + if (offline === undefined) { + return jsonError(res, 400, "offline is required"); + } try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "offline"); - if (!pw) return; + if (!pw) { + return; + } await pw.setOfflineViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -181,22 +218,30 @@ export function registerBrowserAgentStorageRoutes( app.post("/set/headers", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; const headers = body.headers && typeof body.headers === "object" && !Array.isArray(body.headers) ? (body.headers as Record) : null; - if (!headers) return jsonError(res, 400, "headers is required"); + if (!headers) { + return jsonError(res, 400, "headers is required"); + } const parsed: Record = {}; for (const [k, v] of Object.entries(headers)) { - if (typeof v === "string") parsed[k] = v; + if (typeof v === "string") { + parsed[k] = v; + } } try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "headers"); - if (!pw) return; + if (!pw) { + return; + } await pw.setExtraHTTPHeadersViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -210,7 +255,9 @@ export function registerBrowserAgentStorageRoutes( app.post("/set/credentials", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; const clear = toBoolean(body.clear) ?? false; @@ -219,7 +266,9 @@ export function registerBrowserAgentStorageRoutes( try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "http credentials"); - if (!pw) return; + if (!pw) { + return; + } await pw.setHttpCredentialsViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -235,7 +284,9 @@ export function registerBrowserAgentStorageRoutes( app.post("/set/geolocation", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; const clear = toBoolean(body.clear) ?? false; @@ -246,7 +297,9 @@ export function registerBrowserAgentStorageRoutes( try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "geolocation"); - if (!pw) return; + if (!pw) { + return; + } await pw.setGeolocationViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -264,7 +317,9 @@ export function registerBrowserAgentStorageRoutes( app.post("/set/media", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; const schemeRaw = toStringOrEmpty(body.colorScheme); @@ -274,12 +329,15 @@ export function registerBrowserAgentStorageRoutes( : schemeRaw === "none" ? null : undefined; - if (colorScheme === undefined) + if (colorScheme === undefined) { return jsonError(res, 400, "colorScheme must be dark|light|no-preference|none"); + } try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "media emulation"); - if (!pw) return; + if (!pw) { + return; + } await pw.emulateMediaViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -293,15 +351,21 @@ export function registerBrowserAgentStorageRoutes( app.post("/set/timezone", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; const timezoneId = toStringOrEmpty(body.timezoneId); - if (!timezoneId) return jsonError(res, 400, "timezoneId is required"); + if (!timezoneId) { + return jsonError(res, 400, "timezoneId is required"); + } try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "timezone"); - if (!pw) return; + if (!pw) { + return; + } await pw.setTimezoneViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -315,15 +379,21 @@ export function registerBrowserAgentStorageRoutes( app.post("/set/locale", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; const locale = toStringOrEmpty(body.locale); - if (!locale) return jsonError(res, 400, "locale is required"); + if (!locale) { + return jsonError(res, 400, "locale is required"); + } try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "locale"); - if (!pw) return; + if (!pw) { + return; + } await pw.setLocaleViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, @@ -337,15 +407,21 @@ export function registerBrowserAgentStorageRoutes( app.post("/set/device", async (req, res) => { const profileCtx = resolveProfileContext(req, res, ctx); - if (!profileCtx) return; + if (!profileCtx) { + return; + } const body = readBody(req); const targetId = toStringOrEmpty(body.targetId) || undefined; const name = toStringOrEmpty(body.name); - if (!name) return jsonError(res, 400, "name is required"); + if (!name) { + return jsonError(res, 400, "name is required"); + } try { const tab = await profileCtx.ensureTabAvailable(targetId); const pw = await requirePwAi(res, "device emulation"); - if (!pw) return; + if (!pw) { + return; + } await pw.setDeviceViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, diff --git a/src/browser/routes/basic.ts b/src/browser/routes/basic.ts index 34d90e18f4..f677affd4f 100644 --- a/src/browser/routes/basic.ts +++ b/src/browser/routes/basic.ts @@ -131,7 +131,9 @@ export function registerBrowserBasicRoutes(app: BrowserRouteRegistrar, ctx: Brow | "extension" | ""; - if (!name) return jsonError(res, 400, "name is required"); + if (!name) { + return jsonError(res, 400, "name is required"); + } try { const service = createBrowserProfilesService(ctx); @@ -163,7 +165,9 @@ export function registerBrowserBasicRoutes(app: BrowserRouteRegistrar, ctx: Brow // Delete a profile app.delete("/profiles/:name", async (req, res) => { const name = toStringOrEmpty(req.params.name); - if (!name) return jsonError(res, 400, "profile name is required"); + if (!name) { + return jsonError(res, 400, "profile name is required"); + } try { const service = createBrowserProfilesService(ctx); diff --git a/src/browser/routes/dispatcher.ts b/src/browser/routes/dispatcher.ts index d5a26598df..d7dd6a6d65 100644 --- a/src/browser/routes/dispatcher.ts +++ b/src/browser/routes/dispatcher.ts @@ -55,7 +55,9 @@ function createRegistry() { } function normalizePath(path: string) { - if (!path) return "/"; + if (!path) { + return "/"; + } return path.startsWith("/") ? path : `/${path}`; } @@ -71,7 +73,9 @@ export function createBrowserRouteDispatcher(ctx: BrowserRouteContext) { const body = req.body; const match = registry.routes.find((route) => { - if (route.method !== method) return false; + if (route.method !== method) { + return false; + } return route.regex.test(path); }); if (!match) { diff --git a/src/browser/routes/tabs.ts b/src/browser/routes/tabs.ts index e510dd5217..2769ff59f5 100644 --- a/src/browser/routes/tabs.ts +++ b/src/browser/routes/tabs.ts @@ -5,10 +5,14 @@ import type { BrowserRouteRegistrar } from "./types.js"; export function registerBrowserTabRoutes(app: BrowserRouteRegistrar, ctx: BrowserRouteContext) { app.get("/tabs", async (req, res) => { const profileCtx = getProfileContext(req, ctx); - if ("error" in profileCtx) return jsonError(res, profileCtx.status, profileCtx.error); + if ("error" in profileCtx) { + return jsonError(res, profileCtx.status, profileCtx.error); + } try { const reachable = await profileCtx.isReachable(300); - if (!reachable) return res.json({ running: false, tabs: [] as unknown[] }); + if (!reachable) { + return res.json({ running: false, tabs: [] as unknown[] }); + } const tabs = await profileCtx.listTabs(); res.json({ running: true, tabs }); } catch (err) { @@ -18,9 +22,13 @@ export function registerBrowserTabRoutes(app: BrowserRouteRegistrar, ctx: Browse app.post("/tabs/open", async (req, res) => { const profileCtx = getProfileContext(req, ctx); - if ("error" in profileCtx) return jsonError(res, profileCtx.status, profileCtx.error); + if ("error" in profileCtx) { + return jsonError(res, profileCtx.status, profileCtx.error); + } const url = toStringOrEmpty((req.body as { url?: unknown })?.url); - if (!url) return jsonError(res, 400, "url is required"); + if (!url) { + return jsonError(res, 400, "url is required"); + } try { await profileCtx.ensureBrowserAvailable(); const tab = await profileCtx.openTab(url); @@ -32,45 +40,65 @@ export function registerBrowserTabRoutes(app: BrowserRouteRegistrar, ctx: Browse app.post("/tabs/focus", async (req, res) => { const profileCtx = getProfileContext(req, ctx); - if ("error" in profileCtx) return jsonError(res, profileCtx.status, profileCtx.error); + if ("error" in profileCtx) { + return jsonError(res, profileCtx.status, profileCtx.error); + } const targetId = toStringOrEmpty((req.body as { targetId?: unknown })?.targetId); - if (!targetId) return jsonError(res, 400, "targetId is required"); + if (!targetId) { + return jsonError(res, 400, "targetId is required"); + } try { - if (!(await profileCtx.isReachable(300))) return jsonError(res, 409, "browser not running"); + if (!(await profileCtx.isReachable(300))) { + return jsonError(res, 409, "browser not running"); + } await profileCtx.focusTab(targetId); res.json({ ok: true }); } catch (err) { const mapped = ctx.mapTabError(err); - if (mapped) return jsonError(res, mapped.status, mapped.message); + if (mapped) { + return jsonError(res, mapped.status, mapped.message); + } jsonError(res, 500, String(err)); } }); app.delete("/tabs/:targetId", async (req, res) => { const profileCtx = getProfileContext(req, ctx); - if ("error" in profileCtx) return jsonError(res, profileCtx.status, profileCtx.error); + if ("error" in profileCtx) { + return jsonError(res, profileCtx.status, profileCtx.error); + } const targetId = toStringOrEmpty(req.params.targetId); - if (!targetId) return jsonError(res, 400, "targetId is required"); + if (!targetId) { + return jsonError(res, 400, "targetId is required"); + } try { - if (!(await profileCtx.isReachable(300))) return jsonError(res, 409, "browser not running"); + if (!(await profileCtx.isReachable(300))) { + return jsonError(res, 409, "browser not running"); + } await profileCtx.closeTab(targetId); res.json({ ok: true }); } catch (err) { const mapped = ctx.mapTabError(err); - if (mapped) return jsonError(res, mapped.status, mapped.message); + if (mapped) { + return jsonError(res, mapped.status, mapped.message); + } jsonError(res, 500, String(err)); } }); app.post("/tabs/action", async (req, res) => { const profileCtx = getProfileContext(req, ctx); - if ("error" in profileCtx) return jsonError(res, profileCtx.status, profileCtx.error); + if ("error" in profileCtx) { + return jsonError(res, profileCtx.status, profileCtx.error); + } const action = toStringOrEmpty((req.body as { action?: unknown })?.action); const index = toNumber((req.body as { index?: unknown })?.index); try { if (action === "list") { const reachable = await profileCtx.isReachable(300); - if (!reachable) return res.json({ ok: true, tabs: [] as unknown[] }); + if (!reachable) { + return res.json({ ok: true, tabs: [] as unknown[] }); + } const tabs = await profileCtx.listTabs(); return res.json({ ok: true, tabs }); } @@ -84,16 +112,22 @@ export function registerBrowserTabRoutes(app: BrowserRouteRegistrar, ctx: Browse if (action === "close") { const tabs = await profileCtx.listTabs(); const target = typeof index === "number" ? tabs[index] : tabs.at(0); - if (!target) return jsonError(res, 404, "tab not found"); + if (!target) { + return jsonError(res, 404, "tab not found"); + } await profileCtx.closeTab(target.targetId); return res.json({ ok: true, targetId: target.targetId }); } if (action === "select") { - if (typeof index !== "number") return jsonError(res, 400, "index is required"); + if (typeof index !== "number") { + return jsonError(res, 400, "index is required"); + } const tabs = await profileCtx.listTabs(); const target = tabs[index]; - if (!target) return jsonError(res, 404, "tab not found"); + if (!target) { + return jsonError(res, 404, "tab not found"); + } await profileCtx.focusTab(target.targetId); return res.json({ ok: true, targetId: target.targetId }); } @@ -101,7 +135,9 @@ export function registerBrowserTabRoutes(app: BrowserRouteRegistrar, ctx: Browse return jsonError(res, 400, "unknown tab action"); } catch (err) { const mapped = ctx.mapTabError(err); - if (mapped) return jsonError(res, mapped.status, mapped.message); + if (mapped) { + return jsonError(res, mapped.status, mapped.message); + } jsonError(res, 500, String(err)); } }); diff --git a/src/browser/routes/utils.ts b/src/browser/routes/utils.ts index ad510da4b5..23ab7f20ac 100644 --- a/src/browser/routes/utils.ts +++ b/src/browser/routes/utils.ts @@ -37,7 +37,9 @@ export function jsonError(res: BrowserResponse, status: number, message: string) } export function toStringOrEmpty(value: unknown) { - if (typeof value === "string") return value.trim(); + if (typeof value === "string") { + return value.trim(); + } if (typeof value === "number" || typeof value === "boolean") { return String(value).trim(); } @@ -45,7 +47,9 @@ export function toStringOrEmpty(value: unknown) { } export function toNumber(value: unknown) { - if (typeof value === "number" && Number.isFinite(value)) return value; + if (typeof value === "number" && Number.isFinite(value)) { + return value; + } if (typeof value === "string" && value.trim()) { const parsed = Number(value); return Number.isFinite(parsed) ? parsed : undefined; @@ -61,7 +65,9 @@ export function toBoolean(value: unknown) { } export function toStringArray(value: unknown): string[] | undefined { - if (!Array.isArray(value)) return undefined; + if (!Array.isArray(value)) { + return undefined; + } const strings = value.map((v) => toStringOrEmpty(v)).filter(Boolean); return strings.length ? strings : undefined; } diff --git a/src/browser/server-context.ensure-tab-available.prefers-last-target.test.ts b/src/browser/server-context.ensure-tab-available.prefers-last-target.test.ts index 8a007a96cc..4cfe2298a7 100644 --- a/src/browser/server-context.ensure-tab-available.prefers-last-target.test.ts +++ b/src/browser/server-context.ensure-tab-available.prefers-last-target.test.ts @@ -104,9 +104,13 @@ describe("browser server-context ensureTabAvailable", () => { fetchMock.mockImplementation(async (url: unknown) => { const u = String(url); - if (!u.includes("/json/list")) throw new Error(`unexpected fetch: ${u}`); + if (!u.includes("/json/list")) { + throw new Error(`unexpected fetch: ${u}`); + } const next = responses.shift(); - if (!next) throw new Error("no more responses"); + if (!next) { + throw new Error("no more responses"); + } return { ok: true, json: async () => next } as unknown as Response; }); @@ -152,9 +156,13 @@ describe("browser server-context ensureTabAvailable", () => { const responses = [[]]; fetchMock.mockImplementation(async (url: unknown) => { const u = String(url); - if (!u.includes("/json/list")) throw new Error(`unexpected fetch: ${u}`); + if (!u.includes("/json/list")) { + throw new Error(`unexpected fetch: ${u}`); + } const next = responses.shift(); - if (!next) throw new Error("no more responses"); + if (!next) { + throw new Error("no more responses"); + } return { ok: true, json: async () => next } as unknown as Response; }); // @ts-expect-error test override diff --git a/src/browser/server-context.remote-tab-ops.test.ts b/src/browser/server-context.remote-tab-ops.test.ts index 3848c8e87c..d4c7ce7ab7 100644 --- a/src/browser/server-context.remote-tab-ops.test.ts +++ b/src/browser/server-context.remote-tab-ops.test.ts @@ -116,7 +116,9 @@ describe("browser server-context remote profile tab operations", () => { const listPagesViaPlaywright = vi.fn(async () => { const next = responses.shift(); - if (!next) throw new Error("no more responses"); + if (!next) { + throw new Error("no more responses"); + } return next; }); @@ -212,7 +214,9 @@ describe("browser server-context remote profile tab operations", () => { const fetchMock = vi.fn(async (url: unknown) => { const u = String(url); - if (!u.includes("/json/list")) throw new Error(`unexpected fetch: ${u}`); + if (!u.includes("/json/list")) { + throw new Error(`unexpected fetch: ${u}`); + } return { ok: true, json: async () => [ @@ -254,7 +258,9 @@ describe("browser server-context tab selection state", () => { const fetchMock = vi.fn(async (url: unknown) => { const u = String(url); - if (!u.includes("/json/list")) throw new Error(`unexpected fetch: ${u}`); + if (!u.includes("/json/list")) { + throw new Error(`unexpected fetch: ${u}`); + } return { ok: true, json: async () => [ diff --git a/src/browser/server-context.ts b/src/browser/server-context.ts index 36f3856b8d..80a6228588 100644 --- a/src/browser/server-context.ts +++ b/src/browser/server-context.ts @@ -40,7 +40,9 @@ export type { * Normalize a CDP WebSocket URL to use the correct base URL. */ function normalizeWsUrl(raw: string | undefined, cdpBaseUrl: string): string | undefined { - if (!raw) return undefined; + if (!raw) { + return undefined; + } try { return normalizeCdpWsUrl(raw, cdpBaseUrl); } catch { @@ -54,7 +56,9 @@ async function fetchJson(url: string, timeoutMs = 1500, init?: RequestInit): try { const headers = getHeadersWithAuth(url, (init?.headers as Record) || {}); const res = await fetch(url, { ...init, headers, signal: ctrl.signal }); - if (!res.ok) throw new Error(`HTTP ${res.status}`); + if (!res.ok) { + throw new Error(`HTTP ${res.status}`); + } return (await res.json()) as T; } finally { clearTimeout(t); @@ -67,7 +71,9 @@ async function fetchOk(url: string, timeoutMs = 1500, init?: RequestInit): Promi try { const headers = getHeadersWithAuth(url, (init?.headers as Record) || {}); const res = await fetch(url, { ...init, headers, signal: ctrl.signal }); - if (!res.ok) throw new Error(`HTTP ${res.status}`); + if (!res.ok) { + throw new Error(`HTTP ${res.status}`); + } } finally { clearTimeout(t); } @@ -82,7 +88,9 @@ function createProfileContext( ): ProfileContext { const state = () => { const current = opts.getState(); - if (!current) throw new Error("Browser server not started"); + if (!current) { + throw new Error("Browser server not started"); + } return current; }; @@ -170,7 +178,9 @@ function createProfileContext( while (Date.now() < deadline) { const tabs = await listTabs().catch(() => [] as BrowserTab[]); const found = tabs.find((t) => t.targetId === createdViaCdp); - if (found) return found; + if (found) { + return found; + } await new Promise((r) => setTimeout(r, 100)); } return { targetId: createdViaCdp, title: "", url, type: "page" }; @@ -201,7 +211,9 @@ function createProfileContext( throw err; }); - if (!created.id) throw new Error("Failed to open tab (missing id)"); + if (!created.id) { + throw new Error("Failed to open tab (missing id)"); + } const profileState = getProfileState(); profileState.lastTargetId = created.id; return { @@ -214,7 +226,9 @@ function createProfileContext( }; const resolveRemoteHttpTimeout = (timeoutMs: number | undefined) => { - if (profile.cdpIsLoopback) return timeoutMs ?? 300; + if (profile.cdpIsLoopback) { + return timeoutMs ?? 300; + } const resolved = state().resolved; if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs)) { return Math.max(Math.floor(timeoutMs), resolved.remoteCdpTimeoutMs); @@ -249,7 +263,9 @@ function createProfileContext( setProfileRunning(running); running.proc.on("exit", () => { // Guard against server teardown (e.g., SIGUSR1 restart) - if (!opts.getState()) return; + if (!opts.getState()) { + return; + } const profileState = getProfileState(); if (profileState.running?.pid === running.pid) { setProfileRunning(null); @@ -282,7 +298,9 @@ function createProfileContext( } } - if (await isReachable(600)) return; + if (await isReachable(600)) { + return; + } // Relay server is up, but no attached tab yet. Prompt user to attach. throw new Error( `Chrome extension relay is running, but no tab is connected. Click the OpenClaw Chrome extension icon on a tab to attach it (profile "${profile.name}").`, @@ -292,7 +310,9 @@ function createProfileContext( if (!httpReachable) { if ((current.resolved.attachOnly || remoteCdp) && opts.onEnsureAttachTarget) { await opts.onEnsureAttachTarget(profile); - if (await isHttpReachable(1200)) return; + if (await isHttpReachable(1200)) { + return; + } } if (current.resolved.attachOnly || remoteCdp) { throw new Error( @@ -307,7 +327,9 @@ function createProfileContext( } // Port is reachable - check if we own it - if (await isReachable()) return; + if (await isReachable()) { + return; + } // HTTP responds but WebSocket fails - port in use by something else if (!profileState.running) { @@ -321,7 +343,9 @@ function createProfileContext( if (current.resolved.attachOnly || remoteCdp) { if (opts.onEnsureAttachTarget) { await opts.onEnsureAttachTarget(profile); - if (await isReachable(1200)) return; + if (await isReachable(1200)) { + return; + } } throw new Error( remoteCdp @@ -368,7 +392,9 @@ function createProfileContext( const resolveById = (raw: string) => { const resolved = resolveTargetIdFromTabs(raw, candidates); if (!resolved.ok) { - if (resolved.reason === "ambiguous") return "AMBIGUOUS" as const; + if (resolved.reason === "ambiguous") { + return "AMBIGUOUS" as const; + } return null; } return candidates.find((t) => t.targetId === resolved.targetId) ?? null; @@ -377,7 +403,9 @@ function createProfileContext( const pickDefault = () => { const last = profileState.lastTargetId?.trim() || ""; const lastResolved = last ? resolveById(last) : null; - if (lastResolved && lastResolved !== "AMBIGUOUS") return lastResolved; + if (lastResolved && lastResolved !== "AMBIGUOUS") { + return lastResolved; + } // Prefer a real page tab first (avoid service workers/background targets). const page = candidates.find((t) => (t.type ?? "page") === "page"); return page ?? candidates.at(0) ?? null; @@ -393,7 +421,9 @@ function createProfileContext( if (chosen === "AMBIGUOUS") { throw new Error("ambiguous target id prefix"); } - if (!chosen) throw new Error("tab not found"); + if (!chosen) { + throw new Error("tab not found"); + } profileState.lastTargetId = chosen.targetId; return chosen; }; @@ -463,7 +493,9 @@ function createProfileContext( return { stopped }; } const profileState = getProfileState(); - if (!profileState.running) return { stopped: false }; + if (!profileState.running) { + return { stopped: false }; + } await stopOpenClawChrome(profileState.running); setProfileRunning(null); return { stopped: true }; @@ -530,7 +562,9 @@ function createProfileContext( export function createBrowserRouteContext(opts: ContextOptions): BrowserRouteContext { const state = () => { const current = opts.getState(); - if (!current) throw new Error("Browser server not started"); + if (!current) { + throw new Error("Browser server not started"); + } return current; }; @@ -552,7 +586,9 @@ export function createBrowserRouteContext(opts: ContextOptions): BrowserRouteCon for (const name of Object.keys(current.resolved.profiles)) { const profileState = current.profiles.get(name); const profile = resolveProfile(current.resolved, name); - if (!profile) continue; + if (!profile) { + continue; + } let tabCount = 0; let running = false; diff --git a/src/browser/server.agent-contract-form-layout-act-commands.test.ts b/src/browser/server.agent-contract-form-layout-act-commands.test.ts index 45952a163c..8bbcc78a71 100644 --- a/src/browser/server.agent-contract-form-layout-act-commands.test.ts +++ b/src/browser/server.agent-contract-form-layout-act-commands.test.ts @@ -73,7 +73,9 @@ function makeProc(pid = 123) { return undefined; }, emitExit: () => { - for (const cb of handlers.get("exit") ?? []) cb(0); + for (const cb of handlers.get("exit") ?? []) { + cb(0); + } }, kill: () => { return true; @@ -164,7 +166,9 @@ async function getFreePort(): Promise { s.close((err) => (err ? reject(err) : resolve(assigned))); }); }); - if (port < 65535) return port; + if (port < 65535) { + return port; + } } } @@ -191,12 +195,18 @@ describe("browser control server", () => { createTargetId = null; cdpMocks.createTargetViaCdp.mockImplementation(async () => { - if (createTargetId) return { targetId: createTargetId }; + if (createTargetId) { + return { targetId: createTargetId }; + } throw new Error("cdp disabled"); }); - for (const fn of Object.values(pwMocks)) fn.mockClear(); - for (const fn of Object.values(cdpMocks)) fn.mockClear(); + for (const fn of Object.values(pwMocks)) { + fn.mockClear(); + } + for (const fn of Object.values(cdpMocks)) { + fn.mockClear(); + } testPort = await getFreePort(); cdpBaseUrl = `http://127.0.0.1:${testPort + 1}`; @@ -210,7 +220,9 @@ describe("browser control server", () => { vi.fn(async (url: string, init?: RequestInit) => { const u = String(url); if (u.includes("/json/list")) { - if (!reachable) return makeResponse([]); + if (!reachable) { + return makeResponse([]); + } return makeResponse([ { id: "abcd1234", @@ -243,8 +255,12 @@ describe("browser control server", () => { type: "page", }); } - if (u.includes("/json/activate/")) return makeResponse("ok"); - if (u.includes("/json/close/")) return makeResponse("ok"); + if (u.includes("/json/activate/")) { + return makeResponse("ok"); + } + if (u.includes("/json/close/")) { + return makeResponse("ok"); + } return makeResponse({}, { ok: false, status: 500, text: "unexpected" }); }), ); diff --git a/src/browser/server.agent-contract-snapshot-endpoints.test.ts b/src/browser/server.agent-contract-snapshot-endpoints.test.ts index 51c2b837ab..e8892f6597 100644 --- a/src/browser/server.agent-contract-snapshot-endpoints.test.ts +++ b/src/browser/server.agent-contract-snapshot-endpoints.test.ts @@ -73,7 +73,9 @@ function makeProc(pid = 123) { return undefined; }, emitExit: () => { - for (const cb of handlers.get("exit") ?? []) cb(0); + for (const cb of handlers.get("exit") ?? []) { + cb(0); + } }, kill: () => { return true; @@ -163,7 +165,9 @@ async function getFreePort(): Promise { s.close((err) => (err ? reject(err) : resolve(assigned))); }); }); - if (port < 65535) return port; + if (port < 65535) { + return port; + } } } @@ -189,12 +193,18 @@ describe("browser control server", () => { createTargetId = null; cdpMocks.createTargetViaCdp.mockImplementation(async () => { - if (createTargetId) return { targetId: createTargetId }; + if (createTargetId) { + return { targetId: createTargetId }; + } throw new Error("cdp disabled"); }); - for (const fn of Object.values(pwMocks)) fn.mockClear(); - for (const fn of Object.values(cdpMocks)) fn.mockClear(); + for (const fn of Object.values(pwMocks)) { + fn.mockClear(); + } + for (const fn of Object.values(cdpMocks)) { + fn.mockClear(); + } testPort = await getFreePort(); cdpBaseUrl = `http://127.0.0.1:${testPort + 1}`; @@ -208,7 +218,9 @@ describe("browser control server", () => { vi.fn(async (url: string, init?: RequestInit) => { const u = String(url); if (u.includes("/json/list")) { - if (!reachable) return makeResponse([]); + if (!reachable) { + return makeResponse([]); + } return makeResponse([ { id: "abcd1234", @@ -241,8 +253,12 @@ describe("browser control server", () => { type: "page", }); } - if (u.includes("/json/activate/")) return makeResponse("ok"); - if (u.includes("/json/close/")) return makeResponse("ok"); + if (u.includes("/json/activate/")) { + return makeResponse("ok"); + } + if (u.includes("/json/close/")) { + return makeResponse("ok"); + } return makeResponse({}, { ok: false, status: 500, text: "unexpected" }); }), ); diff --git a/src/browser/server.covers-additional-endpoint-branches.test.ts b/src/browser/server.covers-additional-endpoint-branches.test.ts index 8c16b78d7d..1b99f8d8af 100644 --- a/src/browser/server.covers-additional-endpoint-branches.test.ts +++ b/src/browser/server.covers-additional-endpoint-branches.test.ts @@ -72,7 +72,9 @@ function makeProc(pid = 123) { return undefined; }, emitExit: () => { - for (const cb of handlers.get("exit") ?? []) cb(0); + for (const cb of handlers.get("exit") ?? []) { + cb(0); + } }, kill: () => { return true; @@ -162,7 +164,9 @@ async function getFreePort(): Promise { s.close((err) => (err ? reject(err) : resolve(assigned))); }); }); - if (port < 65535) return port; + if (port < 65535) { + return port; + } } } @@ -188,12 +192,18 @@ describe("browser control server", () => { createTargetId = null; cdpMocks.createTargetViaCdp.mockImplementation(async () => { - if (createTargetId) return { targetId: createTargetId }; + if (createTargetId) { + return { targetId: createTargetId }; + } throw new Error("cdp disabled"); }); - for (const fn of Object.values(pwMocks)) fn.mockClear(); - for (const fn of Object.values(cdpMocks)) fn.mockClear(); + for (const fn of Object.values(pwMocks)) { + fn.mockClear(); + } + for (const fn of Object.values(cdpMocks)) { + fn.mockClear(); + } testPort = await getFreePort(); _cdpBaseUrl = `http://127.0.0.1:${testPort + 1}`; @@ -207,7 +217,9 @@ describe("browser control server", () => { vi.fn(async (url: string, init?: RequestInit) => { const u = String(url); if (u.includes("/json/list")) { - if (!reachable) return makeResponse([]); + if (!reachable) { + return makeResponse([]); + } return makeResponse([ { id: "abcd1234", @@ -240,8 +252,12 @@ describe("browser control server", () => { type: "page", }); } - if (u.includes("/json/activate/")) return makeResponse("ok"); - if (u.includes("/json/close/")) return makeResponse("ok"); + if (u.includes("/json/activate/")) { + return makeResponse("ok"); + } + if (u.includes("/json/close/")) { + return makeResponse("ok"); + } return makeResponse({}, { ok: false, status: 500, text: "unexpected" }); }), ); @@ -303,8 +319,12 @@ describe("backward compatibility (profile parameter)", () => { cfgAttachOnly = false; createTargetId = null; - for (const fn of Object.values(pwMocks)) fn.mockClear(); - for (const fn of Object.values(cdpMocks)) fn.mockClear(); + for (const fn of Object.values(pwMocks)) { + fn.mockClear(); + } + for (const fn of Object.values(cdpMocks)) { + fn.mockClear(); + } testPort = await getFreePort(); _cdpBaseUrl = `http://127.0.0.1:${testPort + 1}`; @@ -319,7 +339,9 @@ describe("backward compatibility (profile parameter)", () => { vi.fn(async (url: string) => { const u = String(url); if (u.includes("/json/list")) { - if (!reachable) return makeResponse([]); + if (!reachable) { + return makeResponse([]); + } return makeResponse([ { id: "abcd1234", @@ -339,8 +361,12 @@ describe("backward compatibility (profile parameter)", () => { type: "page", }); } - if (u.includes("/json/activate/")) return makeResponse("ok"); - if (u.includes("/json/close/")) return makeResponse("ok"); + if (u.includes("/json/activate/")) { + return makeResponse("ok"); + } + if (u.includes("/json/close/")) { + return makeResponse("ok"); + } return makeResponse({}, { ok: false, status: 500, text: "unexpected" }); }), ); diff --git a/src/browser/server.post-tabs-open-profile-unknown-returns-404.test.ts b/src/browser/server.post-tabs-open-profile-unknown-returns-404.test.ts index 9acf09ce8a..dbb6a01bc3 100644 --- a/src/browser/server.post-tabs-open-profile-unknown-returns-404.test.ts +++ b/src/browser/server.post-tabs-open-profile-unknown-returns-404.test.ts @@ -72,7 +72,9 @@ function makeProc(pid = 123) { return undefined; }, emitExit: () => { - for (const cb of handlers.get("exit") ?? []) cb(0); + for (const cb of handlers.get("exit") ?? []) { + cb(0); + } }, kill: () => { return true; @@ -162,7 +164,9 @@ async function getFreePort(): Promise { s.close((err) => (err ? reject(err) : resolve(assigned))); }); }); - if (port < 65535) return port; + if (port < 65535) { + return port; + } } } @@ -188,12 +192,18 @@ describe("browser control server", () => { createTargetId = null; cdpMocks.createTargetViaCdp.mockImplementation(async () => { - if (createTargetId) return { targetId: createTargetId }; + if (createTargetId) { + return { targetId: createTargetId }; + } throw new Error("cdp disabled"); }); - for (const fn of Object.values(pwMocks)) fn.mockClear(); - for (const fn of Object.values(cdpMocks)) fn.mockClear(); + for (const fn of Object.values(pwMocks)) { + fn.mockClear(); + } + for (const fn of Object.values(cdpMocks)) { + fn.mockClear(); + } testPort = await getFreePort(); _cdpBaseUrl = `http://127.0.0.1:${testPort + 1}`; @@ -207,7 +217,9 @@ describe("browser control server", () => { vi.fn(async (url: string, init?: RequestInit) => { const u = String(url); if (u.includes("/json/list")) { - if (!reachable) return makeResponse([]); + if (!reachable) { + return makeResponse([]); + } return makeResponse([ { id: "abcd1234", @@ -240,8 +252,12 @@ describe("browser control server", () => { type: "page", }); } - if (u.includes("/json/activate/")) return makeResponse("ok"); - if (u.includes("/json/close/")) return makeResponse("ok"); + if (u.includes("/json/activate/")) { + return makeResponse("ok"); + } + if (u.includes("/json/close/")) { + return makeResponse("ok"); + } return makeResponse({}, { ok: false, status: 500, text: "unexpected" }); }), ); @@ -280,8 +296,12 @@ describe("profile CRUD endpoints", () => { reachable = false; cfgAttachOnly = false; - for (const fn of Object.values(pwMocks)) fn.mockClear(); - for (const fn of Object.values(cdpMocks)) fn.mockClear(); + for (const fn of Object.values(pwMocks)) { + fn.mockClear(); + } + for (const fn of Object.values(cdpMocks)) { + fn.mockClear(); + } testPort = await getFreePort(); _cdpBaseUrl = `http://127.0.0.1:${testPort + 1}`; @@ -295,7 +315,9 @@ describe("profile CRUD endpoints", () => { "fetch", vi.fn(async (url: string) => { const u = String(url); - if (u.includes("/json/list")) return makeResponse([]); + if (u.includes("/json/list")) { + return makeResponse([]); + } return makeResponse({}, { ok: false, status: 500, text: "unexpected" }); }), ); diff --git a/src/browser/server.serves-status-starts-browser-requested.test.ts b/src/browser/server.serves-status-starts-browser-requested.test.ts index 8f1b8911ef..e879b4ce10 100644 --- a/src/browser/server.serves-status-starts-browser-requested.test.ts +++ b/src/browser/server.serves-status-starts-browser-requested.test.ts @@ -72,7 +72,9 @@ function makeProc(pid = 123) { return undefined; }, emitExit: () => { - for (const cb of handlers.get("exit") ?? []) cb(0); + for (const cb of handlers.get("exit") ?? []) { + cb(0); + } }, kill: () => { return true; @@ -162,7 +164,9 @@ async function getFreePort(): Promise { s.close((err) => (err ? reject(err) : resolve(assigned))); }); }); - if (port < 65535) return port; + if (port < 65535) { + return port; + } } } @@ -188,12 +192,18 @@ describe("browser control server", () => { createTargetId = null; cdpMocks.createTargetViaCdp.mockImplementation(async () => { - if (createTargetId) return { targetId: createTargetId }; + if (createTargetId) { + return { targetId: createTargetId }; + } throw new Error("cdp disabled"); }); - for (const fn of Object.values(pwMocks)) fn.mockClear(); - for (const fn of Object.values(cdpMocks)) fn.mockClear(); + for (const fn of Object.values(pwMocks)) { + fn.mockClear(); + } + for (const fn of Object.values(cdpMocks)) { + fn.mockClear(); + } testPort = await getFreePort(); _cdpBaseUrl = `http://127.0.0.1:${testPort + 1}`; @@ -207,7 +217,9 @@ describe("browser control server", () => { vi.fn(async (url: string, init?: RequestInit) => { const u = String(url); if (u.includes("/json/list")) { - if (!reachable) return makeResponse([]); + if (!reachable) { + return makeResponse([]); + } return makeResponse([ { id: "abcd1234", @@ -240,8 +252,12 @@ describe("browser control server", () => { type: "page", }); } - if (u.includes("/json/activate/")) return makeResponse("ok"); - if (u.includes("/json/close/")) return makeResponse("ok"); + if (u.includes("/json/activate/")) { + return makeResponse("ok"); + } + if (u.includes("/json/close/")) { + return makeResponse("ok"); + } return makeResponse({}, { ok: false, status: 500, text: "unexpected" }); }), ); diff --git a/src/browser/server.skips-default-maxchars-explicitly-set-zero.test.ts b/src/browser/server.skips-default-maxchars-explicitly-set-zero.test.ts index 041d4aa921..c02fad6fb8 100644 --- a/src/browser/server.skips-default-maxchars-explicitly-set-zero.test.ts +++ b/src/browser/server.skips-default-maxchars-explicitly-set-zero.test.ts @@ -72,7 +72,9 @@ function makeProc(pid = 123) { return undefined; }, emitExit: () => { - for (const cb of handlers.get("exit") ?? []) cb(0); + for (const cb of handlers.get("exit") ?? []) { + cb(0); + } }, kill: () => { return true; @@ -162,7 +164,9 @@ async function getFreePort(): Promise { s.close((err) => (err ? reject(err) : resolve(assigned))); }); }); - if (port < 65535) return port; + if (port < 65535) { + return port; + } } } @@ -188,12 +192,18 @@ describe("browser control server", () => { createTargetId = null; cdpMocks.createTargetViaCdp.mockImplementation(async () => { - if (createTargetId) return { targetId: createTargetId }; + if (createTargetId) { + return { targetId: createTargetId }; + } throw new Error("cdp disabled"); }); - for (const fn of Object.values(pwMocks)) fn.mockClear(); - for (const fn of Object.values(cdpMocks)) fn.mockClear(); + for (const fn of Object.values(pwMocks)) { + fn.mockClear(); + } + for (const fn of Object.values(cdpMocks)) { + fn.mockClear(); + } testPort = await getFreePort(); cdpBaseUrl = `http://127.0.0.1:${testPort + 1}`; @@ -207,7 +217,9 @@ describe("browser control server", () => { vi.fn(async (url: string, init?: RequestInit) => { const u = String(url); if (u.includes("/json/list")) { - if (!reachable) return makeResponse([]); + if (!reachable) { + return makeResponse([]); + } return makeResponse([ { id: "abcd1234", @@ -240,8 +252,12 @@ describe("browser control server", () => { type: "page", }); } - if (u.includes("/json/activate/")) return makeResponse("ok"); - if (u.includes("/json/close/")) return makeResponse("ok"); + if (u.includes("/json/activate/")) { + return makeResponse("ok"); + } + if (u.includes("/json/close/")) { + return makeResponse("ok"); + } return makeResponse({}, { ok: false, status: 500, text: "unexpected" }); }), ); diff --git a/src/browser/server.ts b/src/browser/server.ts index 791744fb3c..1c71a0803c 100644 --- a/src/browser/server.ts +++ b/src/browser/server.ts @@ -14,11 +14,15 @@ const log = createSubsystemLogger("browser"); const logServer = log.child("server"); export async function startBrowserControlServerFromConfig(): Promise { - if (state) return state; + if (state) { + return state; + } const cfg = loadConfig(); const resolved = resolveBrowserConfig(cfg.browser, cfg); - if (!resolved.enabled) return null; + if (!resolved.enabled) { + return null; + } const app = express(); app.use(express.json({ limit: "1mb" })); @@ -37,7 +41,9 @@ export async function startBrowserControlServerFromConfig(): Promise { logServer.warn(`Chrome extension relay init failed for profile "${name}": ${String(err)}`); }); @@ -62,7 +70,9 @@ export async function startBrowserControlServerFromConfig(): Promise { const current = state; - if (!current) return; + if (!current) { + return; + } const ctx = createBrowserRouteContext({ getState: () => state, diff --git a/src/browser/target-id.ts b/src/browser/target-id.ts index 4721aaf37b..6ae0f31bf0 100644 --- a/src/browser/target-id.ts +++ b/src/browser/target-id.ts @@ -7,16 +7,24 @@ export function resolveTargetIdFromTabs( tabs: Array<{ targetId: string }>, ): TargetIdResolution { const needle = input.trim(); - if (!needle) return { ok: false, reason: "not_found" }; + if (!needle) { + return { ok: false, reason: "not_found" }; + } const exact = tabs.find((t) => t.targetId === needle); - if (exact) return { ok: true, targetId: exact.targetId }; + if (exact) { + return { ok: true, targetId: exact.targetId }; + } const lower = needle.toLowerCase(); const matches = tabs.map((t) => t.targetId).filter((id) => id.toLowerCase().startsWith(lower)); const only = matches.length === 1 ? matches[0] : undefined; - if (only) return { ok: true, targetId: only }; - if (matches.length === 0) return { ok: false, reason: "not_found" }; + if (only) { + return { ok: true, targetId: only }; + } + if (matches.length === 0) { + return { ok: false, reason: "not_found" }; + } return { ok: false, reason: "ambiguous", matches }; } diff --git a/src/canvas-host/a2ui.ts b/src/canvas-host/a2ui.ts index f1327f6225..9d89cd84a9 100644 --- a/src/canvas-host/a2ui.ts +++ b/src/canvas-host/a2ui.ts @@ -44,7 +44,9 @@ async function resolveA2uiRoot(): Promise { } async function resolveA2uiRootReal(): Promise { - if (cachedA2uiRootReal !== undefined) return cachedA2uiRootReal; + if (cachedA2uiRootReal !== undefined) { + return cachedA2uiRootReal; + } if (!resolvingA2uiRoot) { resolvingA2uiRoot = (async () => { const root = await resolveA2uiRoot(); @@ -64,7 +66,9 @@ function normalizeUrlPath(rawPath: string): string { async function resolveA2uiFilePath(rootReal: string, urlPath: string) { const normalized = normalizeUrlPath(urlPath); const rel = normalized.replace(/^\/+/, ""); - if (rel.split("/").some((p) => p === "..")) return null; + if (rel.split("/").some((p) => p === "..")) { + return null; + } let candidate = path.join(rootReal, rel); if (normalized.endsWith("/")) { @@ -83,9 +87,13 @@ async function resolveA2uiFilePath(rootReal: string, urlPath: string) { const rootPrefix = rootReal.endsWith(path.sep) ? rootReal : `${rootReal}${path.sep}`; try { const lstat = await fs.lstat(candidate); - if (lstat.isSymbolicLink()) return null; + if (lstat.isSymbolicLink()) { + return null; + } const real = await fs.realpath(candidate); - if (!real.startsWith(rootPrefix)) return null; + if (!real.startsWith(rootPrefix)) { + return null; + } return real; } catch { return null; @@ -156,12 +164,16 @@ export async function handleA2uiHttpRequest( res: ServerResponse, ): Promise { const urlRaw = req.url; - if (!urlRaw) return false; + if (!urlRaw) { + return false; + } const url = new URL(urlRaw, "http://localhost"); const basePath = url.pathname === A2UI_PATH || url.pathname.startsWith(`${A2UI_PATH}/`) ? A2UI_PATH : undefined; - if (!basePath) return false; + if (!basePath) { + return false; + } if (req.method !== "GET" && req.method !== "HEAD") { res.statusCode = 405; diff --git a/src/canvas-host/server.test.ts b/src/canvas-host/server.test.ts index 3419277707..4f007e1e00 100644 --- a/src/canvas-host/server.test.ts +++ b/src/canvas-host/server.test.ts @@ -84,14 +84,18 @@ describe("canvas host", () => { const server = createServer((req, res) => { void (async () => { - if (await handler.handleHttpRequest(req, res)) return; + if (await handler.handleHttpRequest(req, res)) { + return; + } res.statusCode = 404; res.setHeader("Content-Type", "text/plain; charset=utf-8"); res.end("Not Found"); })(); }); server.on("upgrade", (req, socket, head) => { - if (handler.handleUpgrade(req, socket, head)) return; + if (handler.handleUpgrade(req, socket, head)) { + return; + } socket.destroy(); }); diff --git a/src/canvas-host/server.ts b/src/canvas-host/server.ts index 50d5af97d4..ecec8bb92b 100644 --- a/src/canvas-host/server.ts +++ b/src/canvas-host/server.ts @@ -159,13 +159,17 @@ function normalizeUrlPath(rawPath: string): string { async function resolveFilePath(rootReal: string, urlPath: string) { const normalized = normalizeUrlPath(urlPath); const rel = normalized.replace(/^\/+/, ""); - if (rel.split("/").some((p) => p === "..")) return null; + if (rel.split("/").some((p) => p === "..")) { + return null; + } const tryOpen = async (relative: string) => { try { return await openFileWithinRoot({ rootDir: rootReal, relativePath: relative }); } catch (err) { - if (err instanceof SafeOpenError) return null; + if (err instanceof SafeOpenError) { + return null; + } throw err; } }; @@ -177,7 +181,9 @@ async function resolveFilePath(rootReal: string, urlPath: string) { const candidate = path.join(rootReal, rel); try { const st = await fs.lstat(candidate); - if (st.isSymbolicLink()) return null; + if (st.isSymbolicLink()) { + return null; + } if (st.isDirectory()) { return await tryOpen(path.posix.join(rel, "index.html")); } @@ -189,17 +195,27 @@ async function resolveFilePath(rootReal: string, urlPath: string) { } function isDisabledByEnv() { - if (isTruthyEnvValue(process.env.OPENCLAW_SKIP_CANVAS_HOST)) return true; - if (isTruthyEnvValue(process.env.OPENCLAW_SKIP_CANVAS_HOST)) return true; - if (process.env.NODE_ENV === "test") return true; - if (process.env.VITEST) return true; + if (isTruthyEnvValue(process.env.OPENCLAW_SKIP_CANVAS_HOST)) { + return true; + } + if (isTruthyEnvValue(process.env.OPENCLAW_SKIP_CANVAS_HOST)) { + return true; + } + if (process.env.NODE_ENV === "test") { + return true; + } + if (process.env.VITEST) { + return true; + } return false; } function normalizeBasePath(rawPath: string | undefined) { const trimmed = (rawPath ?? CANVAS_HOST_PATH).trim(); const normalized = normalizeUrlPath(trimmed || CANVAS_HOST_PATH); - if (normalized === "/") return "/"; + if (normalized === "/") { + return "/"; + } return normalized.replace(/\/+$/, ""); } @@ -260,7 +276,9 @@ export async function createCanvasHostHandler( let debounce: NodeJS.Timeout | null = null; const broadcastReload = () => { - if (!liveReload) return; + if (!liveReload) { + return; + } for (const ws of sockets) { try { ws.send("reload"); @@ -270,7 +288,9 @@ export async function createCanvasHostHandler( } }; const scheduleReload = () => { - if (debounce) clearTimeout(debounce); + if (debounce) { + clearTimeout(debounce); + } debounce = setTimeout(() => { debounce = null; broadcastReload(); @@ -292,7 +312,9 @@ export async function createCanvasHostHandler( : null; watcher?.on("all", () => scheduleReload()); watcher?.on("error", (err) => { - if (watcherClosed) return; + if (watcherClosed) { + return; + } watcherClosed = true; opts.runtime.error( `canvasHost watcher error: ${String(err)} (live reload disabled; consider canvasHost.liveReload=false or a smaller canvasHost.root)`, @@ -301,9 +323,13 @@ export async function createCanvasHostHandler( }); const handleUpgrade = (req: IncomingMessage, socket: Duplex, head: Buffer) => { - if (!wss) return false; + if (!wss) { + return false; + } const url = new URL(req.url ?? "/", "http://localhost"); - if (url.pathname !== CANVAS_WS_PATH) return false; + if (url.pathname !== CANVAS_WS_PATH) { + return false; + } wss.handleUpgrade(req, socket as Socket, head, (ws) => { wss.emit("connection", ws, req); }); @@ -312,7 +338,9 @@ export async function createCanvasHostHandler( const handleHttpRequest = async (req: IncomingMessage, res: ServerResponse) => { const urlRaw = req.url; - if (!urlRaw) return false; + if (!urlRaw) { + return false; + } try { const url = new URL(urlRaw, "http://localhost"); @@ -325,7 +353,9 @@ export async function createCanvasHostHandler( let urlPath = url.pathname; if (basePath !== "/") { - if (urlPath !== basePath && !urlPath.startsWith(`${basePath}/`)) return false; + if (urlPath !== basePath && !urlPath.startsWith(`${basePath}/`)) { + return false; + } urlPath = urlPath === basePath ? "/" : urlPath.slice(basePath.length) || "/"; } @@ -392,7 +422,9 @@ export async function createCanvasHostHandler( handleHttpRequest, handleUpgrade, close: async () => { - if (debounce) clearTimeout(debounce); + if (debounce) { + clearTimeout(debounce); + } watcherClosed = true; await watcher?.close().catch(() => {}); if (wss) { @@ -420,10 +452,16 @@ export async function startCanvasHost(opts: CanvasHostServerOpts): Promise { - if (String(req.headers.upgrade ?? "").toLowerCase() === "websocket") return; + if (String(req.headers.upgrade ?? "").toLowerCase() === "websocket") { + return; + } void (async () => { - if (await handleA2uiHttpRequest(req, res)) return; - if (await handler.handleHttpRequest(req, res)) return; + if (await handleA2uiHttpRequest(req, res)) { + return; + } + if (await handler.handleHttpRequest(req, res)) { + return; + } res.statusCode = 404; res.setHeader("Content-Type", "text/plain; charset=utf-8"); res.end("Not Found"); @@ -435,7 +473,9 @@ export async function startCanvasHost(opts: CanvasHostServerOpts): Promise { - if (handler.handleUpgrade(req, socket, head)) return; + if (handler.handleUpgrade(req, socket, head)) { + return; + } socket.destroy(); }); @@ -465,7 +505,9 @@ export async function startCanvasHost(opts: CanvasHostServerOpts): Promise { - if (ownsHandler) await handler.close(); + if (ownsHandler) { + await handler.close(); + } await new Promise((resolve, reject) => server.close((err) => (err ? reject(err) : resolve())), ); diff --git a/src/channels/ack-reactions.ts b/src/channels/ack-reactions.ts index f35ae76d87..85bb141e0f 100644 --- a/src/channels/ack-reactions.ts +++ b/src/channels/ack-reactions.ts @@ -15,14 +15,28 @@ export type AckReactionGateParams = { export function shouldAckReaction(params: AckReactionGateParams): boolean { const scope = params.scope ?? "group-mentions"; - if (scope === "off" || scope === "none") return false; - if (scope === "all") return true; - if (scope === "direct") return params.isDirect; - if (scope === "group-all") return params.isGroup; + if (scope === "off" || scope === "none") { + return false; + } + if (scope === "all") { + return true; + } + if (scope === "direct") { + return params.isDirect; + } + if (scope === "group-all") { + return params.isGroup; + } if (scope === "group-mentions") { - if (!params.isMentionableGroup) return false; - if (!params.requireMention) return false; - if (!params.canDetectMention) return false; + if (!params.isMentionableGroup) { + return false; + } + if (!params.requireMention) { + return false; + } + if (!params.canDetectMention) { + return false; + } return params.effectiveWasMentioned || params.shouldBypassMention === true; } return false; @@ -37,11 +51,21 @@ export function shouldAckReactionForWhatsApp(params: { wasMentioned: boolean; groupActivated: boolean; }): boolean { - if (!params.emoji) return false; - if (params.isDirect) return params.directEnabled; - if (!params.isGroup) return false; - if (params.groupMode === "never") return false; - if (params.groupMode === "always") return true; + if (!params.emoji) { + return false; + } + if (params.isDirect) { + return params.directEnabled; + } + if (!params.isGroup) { + return false; + } + if (params.groupMode === "never") { + return false; + } + if (params.groupMode === "always") { + return true; + } return shouldAckReaction({ scope: "group-mentions", isDirect: false, @@ -61,11 +85,19 @@ export function removeAckReactionAfterReply(params: { remove: () => Promise; onError?: (err: unknown) => void; }) { - if (!params.removeAfterReply) return; - if (!params.ackReactionPromise) return; - if (!params.ackReactionValue) return; + if (!params.removeAfterReply) { + return; + } + if (!params.ackReactionPromise) { + return; + } + if (!params.ackReactionValue) { + return; + } void params.ackReactionPromise.then((didAck) => { - if (!didAck) return; + if (!didAck) { + return; + } params.remove().catch((err) => params.onError?.(err)); }); } diff --git a/src/channels/allowlists/resolve-utils.ts b/src/channels/allowlists/resolve-utils.ts index 94ed8fb2a9..0e302836a3 100644 --- a/src/channels/allowlists/resolve-utils.ts +++ b/src/channels/allowlists/resolve-utils.ts @@ -8,9 +8,13 @@ export function mergeAllowlist(params: { const merged: string[] = []; const push = (value: string) => { const normalized = value.trim(); - if (!normalized) return; + if (!normalized) { + return; + } const key = normalized.toLowerCase(); - if (seen.has(key)) return; + if (seen.has(key)) { + return; + } seen.add(key); merged.push(normalized); }; diff --git a/src/channels/channel-config.ts b/src/channels/channel-config.ts index af38986670..e13ecb9683 100644 --- a/src/channels/channel-config.ts +++ b/src/channels/channel-config.ts @@ -25,7 +25,9 @@ export function resolveChannelMatchConfig< TEntry, TResult extends { matchKey?: string; matchSource?: ChannelMatchSource }, >(match: ChannelEntryMatch, resolveEntry: (entry: TEntry) => TResult): TResult | null { - if (!match.entry) return null; + if (!match.entry) { + return null; + } return applyChannelMatchMeta(resolveEntry(match.entry), match); } @@ -42,9 +44,13 @@ export function buildChannelKeyCandidates(...keys: Array(); const candidates: string[] = []; for (const key of keys) { - if (typeof key !== "string") continue; + if (typeof key !== "string") { + continue; + } const trimmed = key.trim(); - if (!trimmed || seen.has(trimmed)) continue; + if (!trimmed || seen.has(trimmed)) { + continue; + } seen.add(trimmed); candidates.push(trimmed); } @@ -59,7 +65,9 @@ export function resolveChannelEntryMatch(params: { const entries = params.entries ?? {}; const match: ChannelEntryMatch = {}; for (const key of params.keys) { - if (!Object.prototype.hasOwnProperty.call(entries, key)) continue; + if (!Object.prototype.hasOwnProperty.call(entries, key)) { + continue; + } match.entry = entries[key]; match.key = key; break; @@ -161,8 +169,14 @@ export function resolveNestedAllowlistDecision(params: { innerConfigured: boolean; innerMatched: boolean; }): boolean { - if (!params.outerConfigured) return true; - if (!params.outerMatched) return false; - if (!params.innerConfigured) return true; + if (!params.outerConfigured) { + return true; + } + if (!params.outerMatched) { + return false; + } + if (!params.innerConfigured) { + return true; + } return params.innerMatched; } diff --git a/src/channels/chat-type.ts b/src/channels/chat-type.ts index 5aba48dbf0..3cc49e2c60 100644 --- a/src/channels/chat-type.ts +++ b/src/channels/chat-type.ts @@ -2,9 +2,17 @@ export type NormalizedChatType = "direct" | "group" | "channel"; export function normalizeChatType(raw?: string): NormalizedChatType | undefined { const value = raw?.trim().toLowerCase(); - if (!value) return undefined; - if (value === "direct" || value === "dm") return "direct"; - if (value === "group") return "group"; - if (value === "channel") return "channel"; + if (!value) { + return undefined; + } + if (value === "direct" || value === "dm") { + return "direct"; + } + if (value === "group") { + return "group"; + } + if (value === "channel") { + return "channel"; + } return undefined; } diff --git a/src/channels/command-gating.ts b/src/channels/command-gating.ts index 803fecc877..1492d4760a 100644 --- a/src/channels/command-gating.ts +++ b/src/channels/command-gating.ts @@ -13,10 +13,16 @@ export function resolveCommandAuthorizedFromAuthorizers(params: { const { useAccessGroups, authorizers } = params; const mode = params.modeWhenAccessGroupsOff ?? "allow"; if (!useAccessGroups) { - if (mode === "allow") return true; - if (mode === "deny") return false; + if (mode === "allow") { + return true; + } + if (mode === "deny") { + return false; + } const anyConfigured = authorizers.some((entry) => entry.configured); - if (!anyConfigured) return true; + if (!anyConfigured) { + return true; + } return authorizers.some((entry) => entry.configured && entry.allowed); } return authorizers.some((entry) => entry.configured && entry.allowed); diff --git a/src/channels/conversation-label.ts b/src/channels/conversation-label.ts index fb4719779d..1d16fb617f 100644 --- a/src/channels/conversation-label.ts +++ b/src/channels/conversation-label.ts @@ -3,23 +3,33 @@ import { normalizeChatType } from "./chat-type.js"; function extractConversationId(from?: string): string | undefined { const trimmed = from?.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } const parts = trimmed.split(":").filter(Boolean); return parts.length > 0 ? parts[parts.length - 1] : trimmed; } function shouldAppendId(id: string): boolean { - if (/^[0-9]+$/.test(id)) return true; - if (id.includes("@g.us")) return true; + if (/^[0-9]+$/.test(id)) { + return true; + } + if (id.includes("@g.us")) { + return true; + } return false; } export function resolveConversationLabel(ctx: MsgContext): string | undefined { const explicit = ctx.ConversationLabel?.trim(); - if (explicit) return explicit; + if (explicit) { + return explicit; + } const threadLabel = ctx.ThreadLabel?.trim(); - if (threadLabel) return threadLabel; + if (threadLabel) { + return threadLabel; + } const chatType = normalizeChatType(ctx.ChatType); if (chatType === "direct") { @@ -32,14 +42,28 @@ export function resolveConversationLabel(ctx: MsgContext): string | undefined { ctx.GroupSpace?.trim() || ctx.From?.trim() || ""; - if (!base) return undefined; + if (!base) { + return undefined; + } const id = extractConversationId(ctx.From); - if (!id) return base; - if (!shouldAppendId(id)) return base; - if (base === id) return base; - if (base.includes(id)) return base; - if (base.toLowerCase().includes(" id:")) return base; - if (base.startsWith("#") || base.startsWith("@")) return base; + if (!id) { + return base; + } + if (!shouldAppendId(id)) { + return base; + } + if (base === id) { + return base; + } + if (base.includes(id)) { + return base; + } + if (base.toLowerCase().includes(" id:")) { + return base; + } + if (base.startsWith("#") || base.startsWith("@")) { + return base; + } return `${base} id:${id}`; } diff --git a/src/channels/dock.ts b/src/channels/dock.ts index d0a530ef2f..632f725fb2 100644 --- a/src/channels/dock.ts +++ b/src/channels/dock.ts @@ -157,7 +157,9 @@ const DOCKS: Record = { mentions: { stripPatterns: ({ ctx }) => { const selfE164 = (ctx.To ?? "").replace(/^whatsapp:/, ""); - if (!selfE164) return []; + if (!selfE164) { + return []; + } const escaped = escapeRegExp(selfE164); return [escaped, `@${escaped}`]; }, @@ -403,9 +405,13 @@ function listPluginDockEntries(): Array<{ id: ChannelId; dock: ChannelDock; orde for (const entry of registry.channels) { const plugin = entry.plugin; const id = String(plugin.id).trim(); - if (!id || seen.has(id)) continue; + if (!id || seen.has(id)) { + continue; + } seen.add(id); - if (CHAT_CHANNEL_ORDER.includes(plugin.id as ChatChannelId)) continue; + if (CHAT_CHANNEL_ORDER.includes(plugin.id as ChatChannelId)) { + continue; + } const dock = entry.dock ?? buildDockFromPlugin(plugin); entries.push({ id: plugin.id, dock, order: plugin.meta.order }); } @@ -425,7 +431,9 @@ export function listChannelDocks(): ChannelDock[] { const indexB = CHAT_CHANNEL_ORDER.indexOf(b.id as ChatChannelId); const orderA = a.order ?? (indexA === -1 ? 999 : indexA); const orderB = b.order ?? (indexB === -1 ? 999 : indexB); - if (orderA !== orderB) return orderA - orderB; + if (orderA !== orderB) { + return orderA - orderB; + } return String(a.id).localeCompare(String(b.id)); }); return combined.map((entry) => entry.dock); @@ -433,9 +441,13 @@ export function listChannelDocks(): ChannelDock[] { export function getChannelDock(id: ChannelId): ChannelDock | undefined { const core = DOCKS[id as ChatChannelId]; - if (core) return core; + if (core) { + return core; + } const registry = requireActivePluginRegistry(); const pluginEntry = registry.channels.find((entry) => entry.plugin.id === id); - if (!pluginEntry) return undefined; + if (!pluginEntry) { + return undefined; + } return pluginEntry.dock ?? buildDockFromPlugin(pluginEntry.plugin); } diff --git a/src/channels/location.ts b/src/channels/location.ts index c14a7defe1..d3bc73780a 100644 --- a/src/channels/location.ts +++ b/src/channels/location.ts @@ -25,7 +25,9 @@ function resolveLocation(location: NormalizedLocation): ResolvedLocation { } function formatAccuracy(accuracy?: number): string { - if (!Number.isFinite(accuracy)) return ""; + if (!Number.isFinite(accuracy)) { + return ""; + } return ` ±${Math.round(accuracy ?? 0)}m`; } diff --git a/src/channels/plugins/actions/discord.ts b/src/channels/plugins/actions/discord.ts index ebed5eb0d8..9720d4f0bf 100644 --- a/src/channels/plugins/actions/discord.ts +++ b/src/channels/plugins/actions/discord.ts @@ -8,10 +8,14 @@ export const discordMessageActions: ChannelMessageActionAdapter = { const accounts = listEnabledDiscordAccounts(cfg).filter( (account) => account.tokenSource !== "none", ); - if (accounts.length === 0) return []; + if (accounts.length === 0) { + return []; + } const gate = createActionGate(cfg.channels?.discord?.actions); const actions = new Set(["send"]); - if (gate("polls")) actions.add("poll"); + if (gate("polls")) { + actions.add("poll"); + } if (gate("reactions")) { actions.add("react"); actions.add("reactions"); @@ -26,19 +30,35 @@ export const discordMessageActions: ChannelMessageActionAdapter = { actions.add("unpin"); actions.add("list-pins"); } - if (gate("permissions")) actions.add("permissions"); + if (gate("permissions")) { + actions.add("permissions"); + } if (gate("threads")) { actions.add("thread-create"); actions.add("thread-list"); actions.add("thread-reply"); } - if (gate("search")) actions.add("search"); - if (gate("stickers")) actions.add("sticker"); - if (gate("memberInfo")) actions.add("member-info"); - if (gate("roleInfo")) actions.add("role-info"); - if (gate("reactions")) actions.add("emoji-list"); - if (gate("emojiUploads")) actions.add("emoji-upload"); - if (gate("stickerUploads")) actions.add("sticker-upload"); + if (gate("search")) { + actions.add("search"); + } + if (gate("stickers")) { + actions.add("sticker"); + } + if (gate("memberInfo")) { + actions.add("member-info"); + } + if (gate("roleInfo")) { + actions.add("role-info"); + } + if (gate("reactions")) { + actions.add("emoji-list"); + } + if (gate("emojiUploads")) { + actions.add("emoji-upload"); + } + if (gate("stickerUploads")) { + actions.add("sticker-upload"); + } if (gate("roles", false)) { actions.add("role-add"); actions.add("role-remove"); @@ -56,7 +76,9 @@ export const discordMessageActions: ChannelMessageActionAdapter = { actions.add("category-edit"); actions.add("category-delete"); } - if (gate("voiceStatus")) actions.add("voice-status"); + if (gate("voiceStatus")) { + actions.add("voice-status"); + } if (gate("events")) { actions.add("event-list"); actions.add("event-create"); diff --git a/src/channels/plugins/actions/discord/handle-action.ts b/src/channels/plugins/actions/discord/handle-action.ts index 90e95d14d8..bf8736dd1e 100644 --- a/src/channels/plugins/actions/discord/handle-action.ts +++ b/src/channels/plugins/actions/discord/handle-action.ts @@ -12,8 +12,12 @@ import { resolveDiscordChannelId } from "../../../../discord/targets.js"; const providerId = "discord"; function readParentIdParam(params: Record): string | null | undefined { - if (params.clearParent === true) return null; - if (params.parentId === null) return null; + if (params.clearParent === true) { + return null; + } + if (params.parentId === null) { + return null; + } return readStringParam(params, "parentId"); } @@ -219,7 +223,9 @@ export async function handleDiscordMessageAction( resolveChannelId, readParentIdParam, }); - if (adminResult !== undefined) return adminResult; + if (adminResult !== undefined) { + return adminResult; + } throw new Error(`Action ${String(action)} is not supported for provider ${providerId}.`); } diff --git a/src/channels/plugins/actions/signal.ts b/src/channels/plugins/actions/signal.ts index e07f48b53e..7a7ec55bd7 100644 --- a/src/channels/plugins/actions/signal.ts +++ b/src/channels/plugins/actions/signal.ts @@ -9,9 +9,13 @@ const GROUP_PREFIX = "group:"; function normalizeSignalReactionRecipient(raw: string): string { const trimmed = raw.trim(); - if (!trimmed) return trimmed; + if (!trimmed) { + return trimmed; + } const withoutSignal = trimmed.replace(/^signal:/i, "").trim(); - if (!withoutSignal) return withoutSignal; + if (!withoutSignal) { + return withoutSignal; + } if (withoutSignal.toLowerCase().startsWith("uuid:")) { return withoutSignal.slice("uuid:".length).trim(); } @@ -20,9 +24,13 @@ function normalizeSignalReactionRecipient(raw: string): string { function resolveSignalReactionTarget(raw: string): { recipient?: string; groupId?: string } { const trimmed = raw.trim(); - if (!trimmed) return {}; + if (!trimmed) { + return {}; + } const withoutSignal = trimmed.replace(/^signal:/i, "").trim(); - if (!withoutSignal) return {}; + if (!withoutSignal) { + return {}; + } if (withoutSignal.toLowerCase().startsWith(GROUP_PREFIX)) { const groupId = withoutSignal.slice(GROUP_PREFIX.length).trim(); return groupId ? { groupId } : {}; @@ -33,9 +41,13 @@ function resolveSignalReactionTarget(raw: string): { recipient?: string; groupId export const signalMessageActions: ChannelMessageActionAdapter = { listActions: ({ cfg }) => { const accounts = listEnabledSignalAccounts(cfg); - if (accounts.length === 0) return []; + if (accounts.length === 0) { + return []; + } const configuredAccounts = accounts.filter((account) => account.configured); - if (configuredAccounts.length === 0) return []; + if (configuredAccounts.length === 0) { + return []; + } const actions = new Set(["send"]); @@ -105,7 +117,9 @@ export const signalMessageActions: ChannelMessageActionAdapter = { } if (remove) { - if (!emoji) throw new Error("Emoji required to remove reaction."); + if (!emoji) { + throw new Error("Emoji required to remove reaction."); + } await removeReactionSignal(target.recipient ?? "", timestamp, emoji, { accountId: accountId ?? undefined, groupId: target.groupId, @@ -115,7 +129,9 @@ export const signalMessageActions: ChannelMessageActionAdapter = { return jsonResult({ ok: true, removed: emoji }); } - if (!emoji) throw new Error("Emoji required to add reaction."); + if (!emoji) { + throw new Error("Emoji required to add reaction."); + } await sendReactionSignal(target.recipient ?? "", timestamp, emoji, { accountId: accountId ?? undefined, groupId: target.groupId, diff --git a/src/channels/plugins/actions/telegram.ts b/src/channels/plugins/actions/telegram.ts index 17df9adbc2..6a0b5751b7 100644 --- a/src/channels/plugins/actions/telegram.ts +++ b/src/channels/plugins/actions/telegram.ts @@ -42,12 +42,20 @@ export const telegramMessageActions: ChannelMessageActionAdapter = { const accounts = listEnabledTelegramAccounts(cfg).filter( (account) => account.tokenSource !== "none", ); - if (accounts.length === 0) return []; + if (accounts.length === 0) { + return []; + } const gate = createActionGate(cfg.channels?.telegram?.actions); const actions = new Set(["send"]); - if (gate("reactions")) actions.add("react"); - if (gate("deleteMessage")) actions.add("delete"); - if (gate("editMessage")) actions.add("edit"); + if (gate("reactions")) { + actions.add("react"); + } + if (gate("deleteMessage")) { + actions.add("delete"); + } + if (gate("editMessage")) { + actions.add("edit"); + } if (gate("sticker", false)) { actions.add("sticker"); actions.add("sticker-search"); @@ -58,16 +66,22 @@ export const telegramMessageActions: ChannelMessageActionAdapter = { const accounts = listEnabledTelegramAccounts(cfg).filter( (account) => account.tokenSource !== "none", ); - if (accounts.length === 0) return false; + if (accounts.length === 0) { + return false; + } return accounts.some((account) => isTelegramInlineButtonsEnabled({ cfg, accountId: account.accountId }), ); }, extractToolSend: ({ args }) => { const action = typeof args.action === "string" ? args.action.trim() : ""; - if (action !== "sendMessage") return null; + if (action !== "sendMessage") { + return null; + } const to = typeof args.to === "string" ? args.to : undefined; - if (!to) return null; + if (!to) { + return null; + } const accountId = typeof args.accountId === "string" ? args.accountId.trim() : undefined; return { to, accountId }; }, diff --git a/src/channels/plugins/catalog.ts b/src/channels/plugins/catalog.ts index acef58e359..57d7add8dc 100644 --- a/src/channels/plugins/catalog.ts +++ b/src/channels/plugins/catalog.ts @@ -70,15 +70,21 @@ function parseCatalogEntries(raw: unknown): ExternalCatalogEntry[] { if (Array.isArray(raw)) { return raw.filter((entry): entry is ExternalCatalogEntry => isRecord(entry)); } - if (!isRecord(raw)) return []; + if (!isRecord(raw)) { + return []; + } const list = raw.entries ?? raw.packages ?? raw.plugins; - if (!Array.isArray(list)) return []; + if (!Array.isArray(list)) { + return []; + } return list.filter((entry): entry is ExternalCatalogEntry => isRecord(entry)); } function splitEnvPaths(value: string): string[] { const trimmed = value.trim(); - if (!trimmed) return []; + if (!trimmed) { + return []; + } return trimmed .split(/[;,]/g) .flatMap((chunk) => chunk.split(path.delimiter)) @@ -104,7 +110,9 @@ function loadExternalCatalogEntries(options: CatalogOptions): ExternalCatalogEnt const entries: ExternalCatalogEntry[] = []; for (const rawPath of paths) { const resolved = resolveUserPath(rawPath); - if (!fs.existsSync(resolved)) continue; + if (!fs.existsSync(resolved)) { + continue; + } try { const payload = JSON.parse(fs.readFileSync(resolved, "utf-8")) as unknown; entries.push(...parseCatalogEntries(payload)); @@ -120,7 +128,9 @@ function toChannelMeta(params: { id: string; }): ChannelMeta | null { const label = params.channel.label?.trim(); - if (!label) return null; + if (!label) { + return null; + } const selectionLabel = params.channel.selectionLabel?.trim() || label; const detailLabel = params.channel.detailLabel?.trim(); const docsPath = params.channel.docsPath?.trim() || `/channels/${params.id}`; @@ -170,7 +180,9 @@ function resolveInstallInfo(params: { workspaceDir?: string; }): ChannelPluginCatalogEntry["install"] | null { const npmSpec = params.manifest.install?.npmSpec?.trim() ?? params.packageName?.trim(); - if (!npmSpec) return null; + if (!npmSpec) { + return null; + } let localPath = params.manifest.install?.localPath?.trim() || undefined; if (!localPath && params.workspaceDir && params.packageDir) { localPath = path.relative(params.workspaceDir, params.packageDir) || undefined; @@ -190,18 +202,26 @@ function buildCatalogEntry(candidate: { packageManifest?: OpenClawPackageManifest; }): ChannelPluginCatalogEntry | null { const manifest = candidate.packageManifest; - if (!manifest?.channel) return null; + if (!manifest?.channel) { + return null; + } const id = manifest.channel.id?.trim(); - if (!id) return null; + if (!id) { + return null; + } const meta = toChannelMeta({ channel: manifest.channel, id }); - if (!meta) return null; + if (!meta) { + return null; + } const install = resolveInstallInfo({ manifest, packageName: candidate.packageName, packageDir: candidate.packageDir, workspaceDir: candidate.workspaceDir, }); - if (!install) return null; + if (!install) { + return null; + } return { id, meta, install }; } @@ -249,7 +269,9 @@ export function listChannelPluginCatalogEntries( for (const candidate of discovery.candidates) { const entry = buildCatalogEntry(candidate); - if (!entry) continue; + if (!entry) { + continue; + } const priority = ORIGIN_PRIORITY[candidate.origin] ?? 99; const existing = resolved.get(entry.id); if (!existing || priority < existing.priority) { @@ -271,7 +293,9 @@ export function listChannelPluginCatalogEntries( .toSorted((a, b) => { const orderA = a.meta.order ?? 999; const orderB = b.meta.order ?? 999; - if (orderA !== orderB) return orderA - orderB; + if (orderA !== orderB) { + return orderA - orderB; + } return a.meta.label.localeCompare(b.meta.label); }); } @@ -281,6 +305,8 @@ export function getChannelPluginCatalogEntry( options: CatalogOptions = {}, ): ChannelPluginCatalogEntry | undefined { const trimmed = id.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } return listChannelPluginCatalogEntries(options).find((entry) => entry.id === trimmed); } diff --git a/src/channels/plugins/config-helpers.ts b/src/channels/plugins/config-helpers.ts index 80b8ec2542..ebf6f18a51 100644 --- a/src/channels/plugins/config-helpers.ts +++ b/src/channels/plugins/config-helpers.ts @@ -59,7 +59,9 @@ export function deleteAccountFromConfigSection(params: { const accountKey = params.accountId || DEFAULT_ACCOUNT_ID; const channels = params.cfg.channels as Record | undefined; const base = channels?.[params.sectionKey] as ChannelSection | undefined; - if (!base) return params.cfg; + if (!base) { + return params.cfg; + } const baseAccounts = base.accounts && typeof base.accounts === "object" ? { ...base.accounts } : undefined; @@ -83,7 +85,9 @@ export function deleteAccountFromConfigSection(params: { delete baseAccounts[accountKey]; const baseRecord = { ...(base as Record) }; for (const field of params.clearBaseFields ?? []) { - if (field in baseRecord) baseRecord[field] = undefined; + if (field in baseRecord) { + baseRecord[field] = undefined; + } } return { ...params.cfg, diff --git a/src/channels/plugins/config-writes.ts b/src/channels/plugins/config-writes.ts index f6ebaa0a6e..db8e1e3bc0 100644 --- a/src/channels/plugins/config-writes.ts +++ b/src/channels/plugins/config-writes.ts @@ -8,8 +8,12 @@ type ChannelConfigWithAccounts = { }; function resolveAccountConfig(accounts: ChannelConfigWithAccounts["accounts"], accountId: string) { - if (!accounts || typeof accounts !== "object") return undefined; - if (accountId in accounts) return accounts[accountId]; + if (!accounts || typeof accounts !== "object") { + return undefined; + } + if (accountId in accounts) { + return accounts[accountId]; + } const matchKey = Object.keys(accounts).find( (key) => key.toLowerCase() === accountId.toLowerCase(), ); @@ -21,10 +25,14 @@ export function resolveChannelConfigWrites(params: { channelId?: ChannelId | null; accountId?: string | null; }): boolean { - if (!params.channelId) return true; + if (!params.channelId) { + return true; + } const channels = params.cfg.channels as Record | undefined; const channelConfig = channels?.[params.channelId]; - if (!channelConfig) return true; + if (!channelConfig) { + return true; + } const accountId = normalizeAccountId(params.accountId); const accountConfig = resolveAccountConfig(channelConfig.accounts, accountId); const value = accountConfig?.configWrites ?? channelConfig.configWrites; diff --git a/src/channels/plugins/directory-config.ts b/src/channels/plugins/directory-config.ts index f902ad6448..2afd45df44 100644 --- a/src/channels/plugins/directory-config.ts +++ b/src/channels/plugins/directory-config.ts @@ -23,17 +23,23 @@ export async function listSlackDirectoryPeersFromConfig( for (const entry of account.dm?.allowFrom ?? []) { const raw = String(entry).trim(); - if (!raw || raw === "*") continue; + if (!raw || raw === "*") { + continue; + } ids.add(raw); } for (const id of Object.keys(account.config.dms ?? {})) { const trimmed = id.trim(); - if (trimmed) ids.add(trimmed); + if (trimmed) { + ids.add(trimmed); + } } for (const channel of Object.values(account.config.channels ?? {})) { for (const user of channel.users ?? []) { const raw = String(user).trim(); - if (raw) ids.add(raw); + if (raw) { + ids.add(raw); + } } } @@ -43,7 +49,9 @@ export async function listSlackDirectoryPeersFromConfig( .map((raw) => { const mention = raw.match(/^<@([A-Z0-9]+)>$/i); const normalizedUserId = (mention?.[1] ?? raw).replace(/^(slack|user):/i, "").trim(); - if (!normalizedUserId) return null; + if (!normalizedUserId) { + return null; + } const target = `user:${normalizedUserId}`; return normalizeSlackMessagingTarget(target) ?? target.toLowerCase(); }) @@ -78,22 +86,30 @@ export async function listDiscordDirectoryPeersFromConfig( for (const entry of account.config.dm?.allowFrom ?? []) { const raw = String(entry).trim(); - if (!raw || raw === "*") continue; + if (!raw || raw === "*") { + continue; + } ids.add(raw); } for (const id of Object.keys(account.config.dms ?? {})) { const trimmed = id.trim(); - if (trimmed) ids.add(trimmed); + if (trimmed) { + ids.add(trimmed); + } } for (const guild of Object.values(account.config.guilds ?? {})) { for (const entry of guild.users ?? []) { const raw = String(entry).trim(); - if (raw) ids.add(raw); + if (raw) { + ids.add(raw); + } } for (const channel of Object.values(guild.channels ?? {})) { for (const user of channel.users ?? []) { const raw = String(user).trim(); - if (raw) ids.add(raw); + if (raw) { + ids.add(raw); + } } } } @@ -104,7 +120,9 @@ export async function listDiscordDirectoryPeersFromConfig( .map((raw) => { const mention = raw.match(/^<@!?(\d+)>$/); const cleaned = (mention?.[1] ?? raw).replace(/^(discord|user):/i, "").trim(); - if (!/^\d+$/.test(cleaned)) return null; + if (!/^\d+$/.test(cleaned)) { + return null; + } return `user:${cleaned}`; }) .filter((id): id is string => Boolean(id)) @@ -122,7 +140,9 @@ export async function listDiscordDirectoryGroupsFromConfig( for (const guild of Object.values(account.config.guilds ?? {})) { for (const channelId of Object.keys(guild.channels ?? {})) { const trimmed = channelId.trim(); - if (trimmed) ids.add(trimmed); + if (trimmed) { + ids.add(trimmed); + } } } @@ -132,7 +152,9 @@ export async function listDiscordDirectoryGroupsFromConfig( .map((raw) => { const mention = raw.match(/^<#(\d+)>$/); const cleaned = (mention?.[1] ?? raw).replace(/^(discord|channel|group):/i, "").trim(); - if (!/^\d+$/.test(cleaned)) return null; + if (!/^\d+$/.test(cleaned)) { + return null; + } return `channel:${cleaned}`; }) .filter((id): id is string => Boolean(id)) @@ -160,8 +182,12 @@ export async function listTelegramDirectoryPeersFromConfig( ) .map((entry) => { const trimmed = entry.trim(); - if (!trimmed) return null; - if (/^-?\d+$/.test(trimmed)) return trimmed; + if (!trimmed) { + return null; + } + if (/^-?\d+$/.test(trimmed)) { + return trimmed; + } const withAt = trimmed.startsWith("@") ? trimmed : `@${trimmed}`; return withAt; }) diff --git a/src/channels/plugins/group-mentions.ts b/src/channels/plugins/group-mentions.ts index e7890d102d..b274312166 100644 --- a/src/channels/plugins/group-mentions.ts +++ b/src/channels/plugins/group-mentions.ts @@ -24,9 +24,13 @@ type GroupMentionParams = { }; function normalizeDiscordSlug(value?: string | null) { - if (!value) return ""; + if (!value) { + return ""; + } let text = value.trim().toLowerCase(); - if (!text) return ""; + if (!text) { + return ""; + } text = text.replace(/^[@#]+/, ""); text = text.replace(/[\s_]+/g, "-"); text = text.replace(/[^a-z0-9-]+/g, "-"); @@ -36,7 +40,9 @@ function normalizeDiscordSlug(value?: string | null) { function normalizeSlackSlug(raw?: string | null) { const trimmed = raw?.trim().toLowerCase() ?? ""; - if (!trimmed) return ""; + if (!trimmed) { + return ""; + } const dashed = trimmed.replace(/\s+/g, "-"); const cleaned = dashed.replace(/[^a-z0-9#@._+-]+/g, "-"); return cleaned.replace(/-{2,}/g, "-").replace(/^[-.]+|[-.]+$/g, ""); @@ -44,7 +50,9 @@ function normalizeSlackSlug(raw?: string | null) { function parseTelegramGroupId(value?: string | null) { const raw = value?.trim() ?? ""; - if (!raw) return { chatId: undefined, topicId: undefined }; + if (!raw) { + return { chatId: undefined, topicId: undefined }; + } const parts = raw.split(":").filter(Boolean); if ( parts.length >= 3 && @@ -66,7 +74,9 @@ function resolveTelegramRequireMention(params: { topicId?: string; }): boolean | undefined { const { cfg, chatId, topicId } = params; - if (!chatId) return undefined; + if (!chatId) { + return undefined; + } const groupConfig = cfg.channels?.telegram?.groups?.[chatId]; const groupDefault = cfg.channels?.telegram?.groups?.["*"]; const topicConfig = topicId && groupConfig?.topics ? groupConfig.topics[topicId] : undefined; @@ -88,16 +98,24 @@ function resolveTelegramRequireMention(params: { } function resolveDiscordGuildEntry(guilds: DiscordConfig["guilds"], groupSpace?: string | null) { - if (!guilds || Object.keys(guilds).length === 0) return null; + if (!guilds || Object.keys(guilds).length === 0) { + return null; + } const space = groupSpace?.trim() ?? ""; - if (space && guilds[space]) return guilds[space]; + if (space && guilds[space]) { + return guilds[space]; + } const normalized = normalizeDiscordSlug(space); - if (normalized && guilds[normalized]) return guilds[normalized]; + if (normalized && guilds[normalized]) { + return guilds[normalized]; + } if (normalized) { const match = Object.values(guilds).find( (entry) => normalizeDiscordSlug(entry?.slug ?? undefined) === normalized, ); - if (match) return match; + if (match) { + return match; + } } return guilds["*"] ?? null; } @@ -111,7 +129,9 @@ export function resolveTelegramGroupRequireMention( chatId, topicId, }); - if (typeof requireMention === "boolean") return requireMention; + if (typeof requireMention === "boolean") { + return requireMention; + } return resolveChannelGroupRequireMention({ cfg: params.cfg, channel: "telegram", @@ -194,7 +214,9 @@ export function resolveSlackGroupRequireMention(params: GroupMentionParams): boo }); const channels = account.channels ?? {}; const keys = Object.keys(channels); - if (keys.length === 0) return true; + if (keys.length === 0) { + return true; + } const channelId = params.groupId?.trim(); const groupChannel = params.groupChannel; const channelName = groupChannel?.replace(/^#/, ""); @@ -299,8 +321,12 @@ export function resolveDiscordGroupToolPolicy( senderUsername: params.senderUsername, senderE164: params.senderE164, }); - if (senderPolicy) return senderPolicy; - if (entry?.tools) return entry.tools; + if (senderPolicy) { + return senderPolicy; + } + if (entry?.tools) { + return entry.tools; + } } const guildSenderPolicy = resolveToolsBySender({ toolsBySender: guildEntry?.toolsBySender, @@ -309,8 +335,12 @@ export function resolveDiscordGroupToolPolicy( senderUsername: params.senderUsername, senderE164: params.senderE164, }); - if (guildSenderPolicy) return guildSenderPolicy; - if (guildEntry?.tools) return guildEntry.tools; + if (guildSenderPolicy) { + return guildSenderPolicy; + } + if (guildEntry?.tools) { + return guildEntry.tools; + } return undefined; } @@ -323,7 +353,9 @@ export function resolveSlackGroupToolPolicy( }); const channels = account.channels ?? {}; const keys = Object.keys(channels); - if (keys.length === 0) return undefined; + if (keys.length === 0) { + return undefined; + } const channelId = params.groupId?.trim(); const groupChannel = params.groupChannel; const channelName = groupChannel?.replace(/^#/, ""); @@ -351,8 +383,12 @@ export function resolveSlackGroupToolPolicy( senderUsername: params.senderUsername, senderE164: params.senderE164, }); - if (senderPolicy) return senderPolicy; - if (resolved?.tools) return resolved.tools; + if (senderPolicy) { + return senderPolicy; + } + if (resolved?.tools) { + return resolved.tools; + } return undefined; } diff --git a/src/channels/plugins/index.ts b/src/channels/plugins/index.ts index 206a7a40f1..ffa00b20a1 100644 --- a/src/channels/plugins/index.ts +++ b/src/channels/plugins/index.ts @@ -19,7 +19,9 @@ function dedupeChannels(channels: ChannelPlugin[]): ChannelPlugin[] { const resolved: ChannelPlugin[] = []; for (const plugin of channels) { const id = String(plugin.id).trim(); - if (!id || seen.has(id)) continue; + if (!id || seen.has(id)) { + continue; + } seen.add(id); resolved.push(plugin); } @@ -33,14 +35,18 @@ export function listChannelPlugins(): ChannelPlugin[] { const indexB = CHAT_CHANNEL_ORDER.indexOf(b.id as ChatChannelId); const orderA = a.meta.order ?? (indexA === -1 ? 999 : indexA); const orderB = b.meta.order ?? (indexB === -1 ? 999 : indexB); - if (orderA !== orderB) return orderA - orderB; + if (orderA !== orderB) { + return orderA - orderB; + } return a.id.localeCompare(b.id); }); } export function getChannelPlugin(id: ChannelId): ChannelPlugin | undefined { const resolvedId = String(id).trim(); - if (!resolvedId) return undefined; + if (!resolvedId) { + return undefined; + } return listChannelPlugins().find((plugin) => plugin.id === resolvedId); } diff --git a/src/channels/plugins/load.ts b/src/channels/plugins/load.ts index 7153149d5c..565b0a5671 100644 --- a/src/channels/plugins/load.ts +++ b/src/channels/plugins/load.ts @@ -6,7 +6,9 @@ const cache = new Map(); let lastRegistry: PluginRegistry | null = null; function ensureCacheForRegistry(registry: PluginRegistry | null) { - if (registry === lastRegistry) return; + if (registry === lastRegistry) { + return; + } cache.clear(); lastRegistry = registry; } @@ -15,7 +17,9 @@ export async function loadChannelPlugin(id: ChannelId): Promise entry.plugin.id === id); if (pluginEntry) { cache.set(id, pluginEntry.plugin); diff --git a/src/channels/plugins/media-limits.ts b/src/channels/plugins/media-limits.ts index 24a6bbc605..ee8f373f64 100644 --- a/src/channels/plugins/media-limits.ts +++ b/src/channels/plugins/media-limits.ts @@ -15,7 +15,9 @@ export function resolveChannelMediaMaxBytes(params: { cfg: params.cfg, accountId, }); - if (channelLimit) return channelLimit * MB; + if (channelLimit) { + return channelLimit * MB; + } if (params.cfg.agents?.defaults?.mediaMaxMb) { return params.cfg.agents.defaults.mediaMaxMb * MB; } diff --git a/src/channels/plugins/message-actions.ts b/src/channels/plugins/message-actions.ts index 45cfc21c72..491d76abb7 100644 --- a/src/channels/plugins/message-actions.ts +++ b/src/channels/plugins/message-actions.ts @@ -8,22 +8,30 @@ export function listChannelMessageActions(cfg: OpenClawConfig): ChannelMessageAc const actions = new Set(["send", "broadcast"]); for (const plugin of listChannelPlugins()) { const list = plugin.actions?.listActions?.({ cfg }); - if (!list) continue; - for (const action of list) actions.add(action); + if (!list) { + continue; + } + for (const action of list) { + actions.add(action); + } } return Array.from(actions); } export function supportsChannelMessageButtons(cfg: OpenClawConfig): boolean { for (const plugin of listChannelPlugins()) { - if (plugin.actions?.supportsButtons?.({ cfg })) return true; + if (plugin.actions?.supportsButtons?.({ cfg })) { + return true; + } } return false; } export function supportsChannelMessageCards(cfg: OpenClawConfig): boolean { for (const plugin of listChannelPlugins()) { - if (plugin.actions?.supportsCards?.({ cfg })) return true; + if (plugin.actions?.supportsCards?.({ cfg })) { + return true; + } } return false; } @@ -32,7 +40,9 @@ export async function dispatchChannelMessageAction( ctx: ChannelMessageActionContext, ): Promise | null> { const plugin = getChannelPlugin(ctx.channel); - if (!plugin?.actions?.handleAction) return null; + if (!plugin?.actions?.handleAction) { + return null; + } if (plugin.actions.supportsAction && !plugin.actions.supportsAction({ action: ctx.action })) { return null; } diff --git a/src/channels/plugins/normalize/discord.ts b/src/channels/plugins/normalize/discord.ts index b701e5da86..43c3f67dda 100644 --- a/src/channels/plugins/normalize/discord.ts +++ b/src/channels/plugins/normalize/discord.ts @@ -8,9 +8,17 @@ export function normalizeDiscordMessagingTarget(raw: string): string | undefined export function looksLikeDiscordTargetId(raw: string): boolean { const trimmed = raw.trim(); - if (!trimmed) return false; - if (/^<@!?\d+>$/.test(trimmed)) return true; - if (/^(user|channel|discord):/i.test(trimmed)) return true; - if (/^\d{6,}$/.test(trimmed)) return true; + if (!trimmed) { + return false; + } + if (/^<@!?\d+>$/.test(trimmed)) { + return true; + } + if (/^(user|channel|discord):/i.test(trimmed)) { + return true; + } + if (/^\d{6,}$/.test(trimmed)) { + return true; + } return false; } diff --git a/src/channels/plugins/normalize/imessage.ts b/src/channels/plugins/normalize/imessage.ts index ec04d65571..aa5b542dee 100644 --- a/src/channels/plugins/normalize/imessage.ts +++ b/src/channels/plugins/normalize/imessage.ts @@ -7,7 +7,9 @@ const CHAT_TARGET_PREFIX_RE = export function normalizeIMessageMessagingTarget(raw: string): string | undefined { const trimmed = raw.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } // Preserve service prefix if present (e.g., "sms:+1555" → "sms:+15551234567") const lower = trimmed.toLowerCase(); @@ -15,8 +17,12 @@ export function normalizeIMessageMessagingTarget(raw: string): string | undefine if (lower.startsWith(prefix)) { const remainder = trimmed.slice(prefix.length).trim(); const normalizedHandle = normalizeIMessageHandle(remainder); - if (!normalizedHandle) return undefined; - if (CHAT_TARGET_PREFIX_RE.test(normalizedHandle)) return normalizedHandle; + if (!normalizedHandle) { + return undefined; + } + if (CHAT_TARGET_PREFIX_RE.test(normalizedHandle)) { + return normalizedHandle; + } return `${prefix}${normalizedHandle}`; } } @@ -27,9 +33,17 @@ export function normalizeIMessageMessagingTarget(raw: string): string | undefine export function looksLikeIMessageTargetId(raw: string): boolean { const trimmed = raw.trim(); - if (!trimmed) return false; - if (/^(imessage:|sms:|auto:)/i.test(trimmed)) return true; - if (CHAT_TARGET_PREFIX_RE.test(trimmed)) return true; - if (trimmed.includes("@")) return true; + if (!trimmed) { + return false; + } + if (/^(imessage:|sms:|auto:)/i.test(trimmed)) { + return true; + } + if (CHAT_TARGET_PREFIX_RE.test(trimmed)) { + return true; + } + if (trimmed.includes("@")) { + return true; + } return /^\+?\d{3,}$/.test(trimmed); } diff --git a/src/channels/plugins/normalize/signal.ts b/src/channels/plugins/normalize/signal.ts index c8ff17da61..f957b5d5e7 100644 --- a/src/channels/plugins/normalize/signal.ts +++ b/src/channels/plugins/normalize/signal.ts @@ -1,11 +1,15 @@ export function normalizeSignalMessagingTarget(raw: string): string | undefined { const trimmed = raw.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } let normalized = trimmed; if (normalized.toLowerCase().startsWith("signal:")) { normalized = normalized.slice("signal:".length).trim(); } - if (!normalized) return undefined; + if (!normalized) { + return undefined; + } const lower = normalized.toLowerCase(); if (lower.startsWith("group:")) { const id = normalized.slice("group:".length).trim(); @@ -32,17 +36,25 @@ const UUID_COMPACT_PATTERN = /^[0-9a-f]{32}$/i; export function looksLikeSignalTargetId(raw: string): boolean { const trimmed = raw.trim(); - if (!trimmed) return false; - if (/^(signal:)?(group:|username:|u:)/i.test(trimmed)) return true; + if (!trimmed) { + return false; + } + if (/^(signal:)?(group:|username:|u:)/i.test(trimmed)) { + return true; + } if (/^(signal:)?uuid:/i.test(trimmed)) { const stripped = trimmed .replace(/^signal:/i, "") .replace(/^uuid:/i, "") .trim(); - if (!stripped) return false; + if (!stripped) { + return false; + } return UUID_PATTERN.test(stripped) || UUID_COMPACT_PATTERN.test(stripped); } // Accept UUIDs (used by signal-cli for reactions) - if (UUID_PATTERN.test(trimmed) || UUID_COMPACT_PATTERN.test(trimmed)) return true; + if (UUID_PATTERN.test(trimmed) || UUID_COMPACT_PATTERN.test(trimmed)) { + return true; + } return /^\+?\d{3,}$/.test(trimmed); } diff --git a/src/channels/plugins/normalize/slack.ts b/src/channels/plugins/normalize/slack.ts index 876785e7d0..33dcfb7ee2 100644 --- a/src/channels/plugins/normalize/slack.ts +++ b/src/channels/plugins/normalize/slack.ts @@ -7,10 +7,20 @@ export function normalizeSlackMessagingTarget(raw: string): string | undefined { export function looksLikeSlackTargetId(raw: string): boolean { const trimmed = raw.trim(); - if (!trimmed) return false; - if (/^<@([A-Z0-9]+)>$/i.test(trimmed)) return true; - if (/^(user|channel):/i.test(trimmed)) return true; - if (/^slack:/i.test(trimmed)) return true; - if (/^[@#]/.test(trimmed)) return true; + if (!trimmed) { + return false; + } + if (/^<@([A-Z0-9]+)>$/i.test(trimmed)) { + return true; + } + if (/^(user|channel):/i.test(trimmed)) { + return true; + } + if (/^slack:/i.test(trimmed)) { + return true; + } + if (/^[@#]/.test(trimmed)) { + return true; + } return /^[CUWGD][A-Z0-9]{8,}$/i.test(trimmed); } diff --git a/src/channels/plugins/normalize/telegram.ts b/src/channels/plugins/normalize/telegram.ts index ea5531ce1d..ebbb852e87 100644 --- a/src/channels/plugins/normalize/telegram.ts +++ b/src/channels/plugins/normalize/telegram.ts @@ -1,25 +1,39 @@ export function normalizeTelegramMessagingTarget(raw: string): string | undefined { const trimmed = raw.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } let normalized = trimmed; if (normalized.startsWith("telegram:")) { normalized = normalized.slice("telegram:".length).trim(); } else if (normalized.startsWith("tg:")) { normalized = normalized.slice("tg:".length).trim(); } - if (!normalized) return undefined; + if (!normalized) { + return undefined; + } const tmeMatch = /^https?:\/\/t\.me\/([A-Za-z0-9_]+)$/i.exec(normalized) ?? /^t\.me\/([A-Za-z0-9_]+)$/i.exec(normalized); - if (tmeMatch?.[1]) normalized = `@${tmeMatch[1]}`; - if (!normalized) return undefined; + if (tmeMatch?.[1]) { + normalized = `@${tmeMatch[1]}`; + } + if (!normalized) { + return undefined; + } return `telegram:${normalized}`.toLowerCase(); } export function looksLikeTelegramTargetId(raw: string): boolean { const trimmed = raw.trim(); - if (!trimmed) return false; - if (/^(telegram|tg):/i.test(trimmed)) return true; - if (trimmed.startsWith("@")) return true; + if (!trimmed) { + return false; + } + if (/^(telegram|tg):/i.test(trimmed)) { + return true; + } + if (trimmed.startsWith("@")) { + return true; + } return /^-?\d{6,}$/.test(trimmed); } diff --git a/src/channels/plugins/normalize/whatsapp.ts b/src/channels/plugins/normalize/whatsapp.ts index 1f4a951670..af7f5fffa6 100644 --- a/src/channels/plugins/normalize/whatsapp.ts +++ b/src/channels/plugins/normalize/whatsapp.ts @@ -2,14 +2,22 @@ import { normalizeWhatsAppTarget } from "../../../whatsapp/normalize.js"; export function normalizeWhatsAppMessagingTarget(raw: string): string | undefined { const trimmed = raw.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } return normalizeWhatsAppTarget(trimmed) ?? undefined; } export function looksLikeWhatsAppTargetId(raw: string): boolean { const trimmed = raw.trim(); - if (!trimmed) return false; - if (/^whatsapp:/i.test(trimmed)) return true; - if (trimmed.includes("@")) return true; + if (!trimmed) { + return false; + } + if (/^whatsapp:/i.test(trimmed)) { + return true; + } + if (trimmed.includes("@")) { + return true; + } return /^\+?\d{3,}$/.test(trimmed); } diff --git a/src/channels/plugins/onboarding/channel-access.ts b/src/channels/plugins/onboarding/channel-access.ts index b01fe0afae..58e2822660 100644 --- a/src/channels/plugins/onboarding/channel-access.ts +++ b/src/channels/plugins/onboarding/channel-access.ts @@ -77,7 +77,9 @@ export async function promptChannelAccessConfig(params: { : `Configure ${params.label} access?`, initialValue: shouldPrompt, }); - if (!wants) return null; + if (!wants) { + return null; + } const policy = await promptChannelAccessPolicy({ prompter: params.prompter, label: params.label, @@ -85,7 +87,9 @@ export async function promptChannelAccessConfig(params: { allowOpen: params.allowOpen, allowDisabled: params.allowDisabled, }); - if (policy !== "allowlist") return { policy, entries: [] }; + if (policy !== "allowlist") { + return { policy, entries: [] }; + } const entries = await promptChannelAllowlist({ prompter: params.prompter, label: params.label, diff --git a/src/channels/plugins/onboarding/discord.ts b/src/channels/plugins/onboarding/discord.ts index 7c29c4bd0d..79c80b41eb 100644 --- a/src/channels/plugins/onboarding/discord.ts +++ b/src/channels/plugins/onboarding/discord.ts @@ -201,11 +201,17 @@ async function promptDiscordAllowFrom(params: { const parseInputs = (value: string) => parseDiscordAllowFromInput(value); const parseId = (value: string) => { const trimmed = value.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const mention = trimmed.match(/^<@!?(\d+)>$/); - if (mention) return mention[1]; + if (mention) { + return mention[1]; + } const prefixed = trimmed.replace(/^(user:|discord:)/i, ""); - if (/^\d+$/.test(prefixed)) return prefixed; + if (/^\d+$/.test(prefixed)) { + return prefixed; + } return null; }; @@ -387,7 +393,9 @@ export const discordOnboardingAdapter: ChannelOnboardingAdapter = { ([guildKey, value]) => { const channels = value?.channels ?? {}; const channelKeys = Object.keys(channels); - if (channelKeys.length === 0) return [guildKey]; + if (channelKeys.length === 0) { + return [guildKey]; + } return channelKeys.map((channelKey) => `${guildKey}/${channelKey}`); }, ); @@ -463,7 +471,9 @@ export const discordOnboardingAdapter: ChannelOnboardingAdapter = { const channelKey = entry.channelId ?? (entry.channelName ? normalizeDiscordSlug(entry.channelName) : undefined); - if (!channelKey && guildKey === "*") continue; + if (!channelKey && guildKey === "*") { + continue; + } allowlistEntries.push({ guildKey, ...(channelKey ? { channelKey } : {}) }); } next = setDiscordGroupPolicy(next, discordAccountId, "allowlist"); diff --git a/src/channels/plugins/onboarding/helpers.ts b/src/channels/plugins/onboarding/helpers.ts index ea09495024..6469eaa4e4 100644 --- a/src/channels/plugins/onboarding/helpers.ts +++ b/src/channels/plugins/onboarding/helpers.ts @@ -16,7 +16,9 @@ export const promptAccountId: PromptAccountId = async (params: PromptAccountIdPa initialValue: initial, }); - if (choice !== "__new__") return normalizeAccountId(choice); + if (choice !== "__new__") { + return normalizeAccountId(choice); + } const entered = await params.prompter.text({ message: `New ${params.label} account id`, @@ -36,6 +38,8 @@ export function addWildcardAllowFrom( allowFrom?: Array | null, ): Array { const next = (allowFrom ?? []).map((v) => String(v).trim()).filter(Boolean); - if (!next.includes("*")) next.push("*"); + if (!next.includes("*")) { + next.push("*"); + } return next; } diff --git a/src/channels/plugins/onboarding/imessage.ts b/src/channels/plugins/onboarding/imessage.ts index 18b03b73c4..573e3f3aec 100644 --- a/src/channels/plugins/onboarding/imessage.ts +++ b/src/channels/plugins/onboarding/imessage.ts @@ -103,24 +103,36 @@ async function promptIMessageAllowFrom(params: { initialValue: existing[0] ? String(existing[0]) : undefined, validate: (value) => { const raw = String(value ?? "").trim(); - if (!raw) return "Required"; + if (!raw) { + return "Required"; + } const parts = parseIMessageAllowFromInput(raw); for (const part of parts) { - if (part === "*") continue; + if (part === "*") { + continue; + } if (part.toLowerCase().startsWith("chat_id:")) { const id = part.slice("chat_id:".length).trim(); - if (!/^\d+$/.test(id)) return `Invalid chat_id: ${part}`; + if (!/^\d+$/.test(id)) { + return `Invalid chat_id: ${part}`; + } continue; } if (part.toLowerCase().startsWith("chat_guid:")) { - if (!part.slice("chat_guid:".length).trim()) return "Invalid chat_guid entry"; + if (!part.slice("chat_guid:".length).trim()) { + return "Invalid chat_guid entry"; + } continue; } if (part.toLowerCase().startsWith("chat_identifier:")) { - if (!part.slice("chat_identifier:".length).trim()) return "Invalid chat_identifier entry"; + if (!part.slice("chat_identifier:".length).trim()) { + return "Invalid chat_identifier entry"; + } continue; } - if (!normalizeIMessageHandle(part)) return `Invalid handle: ${part}`; + if (!normalizeIMessageHandle(part)) { + return `Invalid handle: ${part}`; + } } return undefined; }, diff --git a/src/channels/plugins/onboarding/signal.ts b/src/channels/plugins/onboarding/signal.ts index f042082285..ed594e9ed8 100644 --- a/src/channels/plugins/onboarding/signal.ts +++ b/src/channels/plugins/onboarding/signal.ts @@ -107,16 +107,26 @@ async function promptSignalAllowFrom(params: { initialValue: existing[0] ? String(existing[0]) : undefined, validate: (value) => { const raw = String(value ?? "").trim(); - if (!raw) return "Required"; + if (!raw) { + return "Required"; + } const parts = parseSignalAllowFromInput(raw); for (const part of parts) { - if (part === "*") continue; - if (part.toLowerCase().startsWith("uuid:")) { - if (!part.slice("uuid:".length).trim()) return "Invalid uuid entry"; + if (part === "*") { continue; } - if (isUuidLike(part)) continue; - if (!normalizeE164(part)) return `Invalid entry: ${part}`; + if (part.toLowerCase().startsWith("uuid:")) { + if (!part.slice("uuid:".length).trim()) { + return "Invalid uuid entry"; + } + continue; + } + if (isUuidLike(part)) { + continue; + } + if (!normalizeE164(part)) { + return `Invalid entry: ${part}`; + } } return undefined; }, @@ -124,9 +134,15 @@ async function promptSignalAllowFrom(params: { const parts = parseSignalAllowFromInput(String(entry)); const normalized = parts .map((part) => { - if (part === "*") return "*"; - if (part.toLowerCase().startsWith("uuid:")) return `uuid:${part.slice(5).trim()}`; - if (isUuidLike(part)) return `uuid:${part}`; + if (part === "*") { + return "*"; + } + if (part.toLowerCase().startsWith("uuid:")) { + return `uuid:${part.slice(5).trim()}`; + } + if (isUuidLike(part)) { + return `uuid:${part}`; + } return normalizeE164(part); }) .filter(Boolean); @@ -231,7 +247,9 @@ export const signalOnboardingAdapter: ChannelOnboardingAdapter = { message: `Signal account set (${account}). Keep it?`, initialValue: true, }); - if (!keep) account = ""; + if (!keep) { + account = ""; + } } if (!account) { diff --git a/src/channels/plugins/onboarding/slack.ts b/src/channels/plugins/onboarding/slack.ts index 77a6aeda60..860534209c 100644 --- a/src/channels/plugins/onboarding/slack.ts +++ b/src/channels/plugins/onboarding/slack.ts @@ -251,11 +251,17 @@ async function promptSlackAllowFrom(params: { const parseInputs = (value: string) => parseSlackAllowFromInput(value); const parseId = (value: string) => { const trimmed = value.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const mention = trimmed.match(/^<@([A-Z0-9]+)>$/i); - if (mention) return mention[1]?.toUpperCase(); + if (mention) { + return mention[1]?.toUpperCase(); + } const prefixed = trimmed.replace(/^(slack:|user:)/i, ""); - if (/^[A-Z][A-Z0-9]+$/i.test(prefixed)) return prefixed.toUpperCase(); + if (/^[A-Z][A-Z0-9]+$/i.test(prefixed)) { + return prefixed.toUpperCase(); + } return null; }; diff --git a/src/channels/plugins/onboarding/telegram.ts b/src/channels/plugins/onboarding/telegram.ts index e4bf894fca..0923d5d40a 100644 --- a/src/channels/plugins/onboarding/telegram.ts +++ b/src/channels/plugins/onboarding/telegram.ts @@ -74,21 +74,31 @@ async function promptTelegramAllowFrom(params: { const resolveTelegramUserId = async (raw: string): Promise => { const trimmed = raw.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const stripped = trimmed.replace(/^(telegram|tg):/i, "").trim(); - if (/^\d+$/.test(stripped)) return stripped; - if (!token) return null; + if (/^\d+$/.test(stripped)) { + return stripped; + } + if (!token) { + return null; + } const username = stripped.startsWith("@") ? stripped : `@${stripped}`; const url = `https://api.telegram.org/bot${token}/getChat?chat_id=${encodeURIComponent(username)}`; try { const res = await fetch(url); - if (!res.ok) return null; + if (!res.ok) { + return null; + } const data = (await res.json().catch(() => null)) as { ok?: boolean; result?: { id?: number | string }; } | null; const id = data?.ok ? data?.result?.id : undefined; - if (typeof id === "number" || typeof id === "string") return String(id); + if (typeof id === "number" || typeof id === "string") { + return String(id); + } return null; } catch { // Network error during username lookup - return null to prompt user for numeric ID diff --git a/src/channels/plugins/onboarding/whatsapp.ts b/src/channels/plugins/onboarding/whatsapp.ts index 263fdeafaa..c337ce9a45 100644 --- a/src/channels/plugins/onboarding/whatsapp.ts +++ b/src/channels/plugins/onboarding/whatsapp.ts @@ -68,9 +68,13 @@ async function promptWhatsAppAllowFrom( initialValue: existingAllowFrom[0], validate: (value) => { const raw = String(value ?? "").trim(); - if (!raw) return "Required"; + if (!raw) { + return "Required"; + } const normalized = normalizeE164(raw); - if (!normalized) return `Invalid number: ${raw}`; + if (!normalized) { + return `Invalid number: ${raw}`; + } return undefined; }, }); @@ -126,9 +130,13 @@ async function promptWhatsAppAllowFrom( initialValue: existingAllowFrom[0], validate: (value) => { const raw = String(value ?? "").trim(); - if (!raw) return "Required"; + if (!raw) { + return "Required"; + } const normalized = normalizeE164(raw); - if (!normalized) return `Invalid number: ${raw}`; + if (!normalized) { + return `Invalid number: ${raw}`; + } return undefined; }, }); @@ -170,7 +178,9 @@ async function promptWhatsAppAllowFrom( if (policy === "open") { next = setWhatsAppAllowFrom(next, ["*"]); } - if (policy === "disabled") return next; + if (policy === "disabled") { + return next; + } const allowOptions = existingAllowFrom.length > 0 @@ -205,16 +215,24 @@ async function promptWhatsAppAllowFrom( placeholder: "+15555550123, +447700900123", validate: (value) => { const raw = String(value ?? "").trim(); - if (!raw) return "Required"; + if (!raw) { + return "Required"; + } const parts = raw .split(/[\n,;]+/g) .map((p) => p.trim()) .filter(Boolean); - if (parts.length === 0) return "Required"; + if (parts.length === 0) { + return "Required"; + } for (const part of parts) { - if (part === "*") continue; + if (part === "*") { + continue; + } const normalized = normalizeE164(part); - if (!normalized) return `Invalid number: ${part}`; + if (!normalized) { + return `Invalid number: ${part}`; + } } return undefined; }, diff --git a/src/channels/plugins/outbound/load.ts b/src/channels/plugins/outbound/load.ts index 9c209cb59d..b4697b052b 100644 --- a/src/channels/plugins/outbound/load.ts +++ b/src/channels/plugins/outbound/load.ts @@ -11,7 +11,9 @@ const cache = new Map(); let lastRegistry: PluginRegistry | null = null; function ensureCacheForRegistry(registry: PluginRegistry | null) { - if (registry === lastRegistry) return; + if (registry === lastRegistry) { + return; + } cache.clear(); lastRegistry = registry; } @@ -22,7 +24,9 @@ export async function loadChannelOutboundAdapter( const registry = getActivePluginRegistry(); ensureCacheForRegistry(registry); const cached = cache.get(id); - if (cached) return cached; + if (cached) { + return cached; + } const pluginEntry = registry?.channels.find((entry) => entry.plugin.id === id); const outbound = pluginEntry?.plugin.outbound; if (outbound) { diff --git a/src/channels/plugins/outbound/telegram.ts b/src/channels/plugins/outbound/telegram.ts index 04abb77e04..a42550292d 100644 --- a/src/channels/plugins/outbound/telegram.ts +++ b/src/channels/plugins/outbound/telegram.ts @@ -3,18 +3,24 @@ import { sendMessageTelegram } from "../../../telegram/send.js"; import type { ChannelOutboundAdapter } from "../types.js"; function parseReplyToMessageId(replyToId?: string | null) { - if (!replyToId) return undefined; + if (!replyToId) { + return undefined; + } const parsed = Number.parseInt(replyToId, 10); return Number.isFinite(parsed) ? parsed : undefined; } function parseThreadId(threadId?: string | number | null) { - if (threadId == null) return undefined; + if (threadId == null) { + return undefined; + } if (typeof threadId === "number") { return Number.isFinite(threadId) ? Math.trunc(threadId) : undefined; } const trimmed = threadId.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } const parsed = Number.parseInt(trimmed, 10); return Number.isFinite(parsed) ? parsed : undefined; } diff --git a/src/channels/plugins/pairing.ts b/src/channels/plugins/pairing.ts index 1ee2affe2b..f179ae6983 100644 --- a/src/channels/plugins/pairing.ts +++ b/src/channels/plugins/pairing.ts @@ -58,7 +58,9 @@ export async function notifyPairingApproved(params: { }): Promise { // Extensions may provide adapter directly to bypass ESM module isolation const adapter = params.pairingAdapter ?? requirePairingAdapter(params.channelId); - if (!adapter.notifyApproval) return; + if (!adapter.notifyApproval) { + return; + } await adapter.notifyApproval({ cfg: params.cfg, id: params.id, diff --git a/src/channels/plugins/setup-helpers.ts b/src/channels/plugins/setup-helpers.ts index 204700bbcc..c6a695b1e8 100644 --- a/src/channels/plugins/setup-helpers.ts +++ b/src/channels/plugins/setup-helpers.ts @@ -18,8 +18,12 @@ function shouldStoreNameInAccounts(params: { accountId: string; alwaysUseAccounts?: boolean; }): boolean { - if (params.alwaysUseAccounts) return true; - if (params.accountId !== DEFAULT_ACCOUNT_ID) return true; + if (params.alwaysUseAccounts) { + return true; + } + if (params.accountId !== DEFAULT_ACCOUNT_ID) { + return true; + } return channelHasAccounts(params.cfg, params.channelKey); } @@ -31,7 +35,9 @@ export function applyAccountNameToChannelSection(params: { alwaysUseAccounts?: boolean; }): OpenClawConfig { const trimmed = params.name?.trim(); - if (!trimmed) return params.cfg; + if (!trimmed) { + return params.cfg; + } const accountId = normalizeAccountId(params.accountId); const channels = params.cfg.channels as Record | undefined; const baseConfig = channels?.[params.channelKey]; @@ -85,11 +91,15 @@ export function migrateBaseNameToDefaultAccount(params: { channelKey: string; alwaysUseAccounts?: boolean; }): OpenClawConfig { - if (params.alwaysUseAccounts) return params.cfg; + if (params.alwaysUseAccounts) { + return params.cfg; + } const channels = params.cfg.channels as Record | undefined; const base = channels?.[params.channelKey] as ChannelSectionBase | undefined; const baseName = base?.name?.trim(); - if (!baseName) return params.cfg; + if (!baseName) { + return params.cfg; + } const accounts: Record> = { ...base?.accounts, }; diff --git a/src/channels/plugins/slack.actions.ts b/src/channels/plugins/slack.actions.ts index ca8aa6fb81..13fef01155 100644 --- a/src/channels/plugins/slack.actions.ts +++ b/src/channels/plugins/slack.actions.ts @@ -15,7 +15,9 @@ export function createSlackActions(providerId: string): ChannelMessageActionAdap const accounts = listEnabledSlackAccounts(cfg).filter( (account) => account.botTokenSource !== "none", ); - if (accounts.length === 0) return []; + if (accounts.length === 0) { + return []; + } const isActionEnabled = (key: string, defaultValue = true) => { for (const account of accounts) { const gate = createActionGate( @@ -24,7 +26,9 @@ export function createSlackActions(providerId: string): ChannelMessageActionAdap boolean | undefined >, ); - if (gate(key, defaultValue)) return true; + if (gate(key, defaultValue)) { + return true; + } } return false; }; @@ -44,15 +48,23 @@ export function createSlackActions(providerId: string): ChannelMessageActionAdap actions.add("unpin"); actions.add("list-pins"); } - if (isActionEnabled("memberInfo")) actions.add("member-info"); - if (isActionEnabled("emojiList")) actions.add("emoji-list"); + if (isActionEnabled("memberInfo")) { + actions.add("member-info"); + } + if (isActionEnabled("emojiList")) { + actions.add("emoji-list"); + } return Array.from(actions); }, extractToolSend: ({ args }): ChannelToolSend | null => { const action = typeof args.action === "string" ? args.action.trim() : ""; - if (action !== "sendMessage") return null; + if (action !== "sendMessage") { + return null; + } const to = typeof args.to === "string" ? args.to : undefined; - if (!to) return null; + if (!to) { + return null; + } const accountId = typeof args.accountId === "string" ? args.accountId.trim() : undefined; return { to, accountId }; }, diff --git a/src/channels/plugins/status-issues/bluebubbles.ts b/src/channels/plugins/status-issues/bluebubbles.ts index 1d1487c39b..967226438e 100644 --- a/src/channels/plugins/status-issues/bluebubbles.ts +++ b/src/channels/plugins/status-issues/bluebubbles.ts @@ -20,7 +20,9 @@ type BlueBubblesProbeResult = { function readBlueBubblesAccountStatus( value: ChannelAccountSnapshot, ): BlueBubblesAccountStatus | null { - if (!isRecord(value)) return null; + if (!isRecord(value)) { + return null; + } return { accountId: value.accountId, enabled: value.enabled, @@ -33,7 +35,9 @@ function readBlueBubblesAccountStatus( } function readBlueBubblesProbeResult(value: unknown): BlueBubblesProbeResult | null { - if (!isRecord(value)) return null; + if (!isRecord(value)) { + return null; + } return { ok: typeof value.ok === "boolean" ? value.ok : undefined, status: typeof value.status === "number" ? value.status : null, @@ -47,10 +51,14 @@ export function collectBlueBubblesStatusIssues( const issues: ChannelStatusIssue[] = []; for (const entry of accounts) { const account = readBlueBubblesAccountStatus(entry); - if (!account) continue; + if (!account) { + continue; + } const accountId = asString(account.accountId) ?? "default"; const enabled = account.enabled !== false; - if (!enabled) continue; + if (!enabled) { + continue; + } const configured = account.configured === true; const running = account.running === true; diff --git a/src/channels/plugins/status-issues/discord.ts b/src/channels/plugins/status-issues/discord.ts index 85a1e84b7e..d3e6b795ca 100644 --- a/src/channels/plugins/status-issues/discord.ts +++ b/src/channels/plugins/status-issues/discord.ts @@ -30,7 +30,9 @@ type DiscordPermissionsAuditSummary = { }; function readDiscordAccountStatus(value: ChannelAccountSnapshot): DiscordAccountStatus | null { - if (!isRecord(value)) return null; + if (!isRecord(value)) { + return null; + } return { accountId: value.accountId, enabled: value.enabled, @@ -41,9 +43,13 @@ function readDiscordAccountStatus(value: ChannelAccountSnapshot): DiscordAccount } function readDiscordApplicationSummary(value: unknown): DiscordApplicationSummary { - if (!isRecord(value)) return {}; + if (!isRecord(value)) { + return {}; + } const intentsRaw = value.intents; - if (!isRecord(intentsRaw)) return {}; + if (!isRecord(intentsRaw)) { + return {}; + } return { intents: { messageContent: @@ -57,7 +63,9 @@ function readDiscordApplicationSummary(value: unknown): DiscordApplicationSummar } function readDiscordPermissionsAuditSummary(value: unknown): DiscordPermissionsAuditSummary { - if (!isRecord(value)) return {}; + if (!isRecord(value)) { + return {}; + } const unresolvedChannels = typeof value.unresolvedChannels === "number" && Number.isFinite(value.unresolvedChannels) ? value.unresolvedChannels @@ -66,9 +74,13 @@ function readDiscordPermissionsAuditSummary(value: unknown): DiscordPermissionsA const channels = Array.isArray(channelsRaw) ? (channelsRaw .map((entry) => { - if (!isRecord(entry)) return null; + if (!isRecord(entry)) { + return null; + } const channelId = asString(entry.channelId); - if (!channelId) return null; + if (!channelId) { + return null; + } const ok = typeof entry.ok === "boolean" ? entry.ok : undefined; const missing = Array.isArray(entry.missing) ? entry.missing.map((v) => asString(v)).filter(Boolean) @@ -96,11 +108,15 @@ export function collectDiscordStatusIssues( const issues: ChannelStatusIssue[] = []; for (const entry of accounts) { const account = readDiscordAccountStatus(entry); - if (!account) continue; + if (!account) { + continue; + } const accountId = asString(account.accountId) ?? "default"; const enabled = account.enabled !== false; const configured = account.configured === true; - if (!enabled || !configured) continue; + if (!enabled || !configured) { + continue; + } const app = readDiscordApplicationSummary(account.application); const messageContent = app.intents?.messageContent; @@ -125,7 +141,9 @@ export function collectDiscordStatusIssues( }); } for (const channel of audit.channels ?? []) { - if (channel.ok === true) continue; + if (channel.ok === true) { + continue; + } const missing = channel.missing?.length ? ` missing ${channel.missing.join(", ")}` : ""; const error = channel.error ? `: ${channel.error}` : ""; const baseMessage = `Channel ${channel.channelId} permission check failed.${missing}${error}`; diff --git a/src/channels/plugins/status-issues/telegram.ts b/src/channels/plugins/status-issues/telegram.ts index 5d340a2643..8853bf4b1c 100644 --- a/src/channels/plugins/status-issues/telegram.ts +++ b/src/channels/plugins/status-issues/telegram.ts @@ -23,7 +23,9 @@ type TelegramGroupMembershipAuditSummary = { }; function readTelegramAccountStatus(value: ChannelAccountSnapshot): TelegramAccountStatus | null { - if (!isRecord(value)) return null; + if (!isRecord(value)) { + return null; + } return { accountId: value.accountId, enabled: value.enabled, @@ -36,7 +38,9 @@ function readTelegramAccountStatus(value: ChannelAccountSnapshot): TelegramAccou function readTelegramGroupMembershipAuditSummary( value: unknown, ): TelegramGroupMembershipAuditSummary { - if (!isRecord(value)) return {}; + if (!isRecord(value)) { + return {}; + } const unresolvedGroups = typeof value.unresolvedGroups === "number" && Number.isFinite(value.unresolvedGroups) ? value.unresolvedGroups @@ -49,9 +53,13 @@ function readTelegramGroupMembershipAuditSummary( const groups = Array.isArray(groupsRaw) ? (groupsRaw .map((entry) => { - if (!isRecord(entry)) return null; + if (!isRecord(entry)) { + return null; + } const chatId = asString(entry.chatId); - if (!chatId) return null; + if (!chatId) { + return null; + } const ok = typeof entry.ok === "boolean" ? entry.ok : undefined; const status = asString(entry.status) ?? null; const error = asString(entry.error) ?? null; @@ -70,11 +78,15 @@ export function collectTelegramStatusIssues( const issues: ChannelStatusIssue[] = []; for (const entry of accounts) { const account = readTelegramAccountStatus(entry); - if (!account) continue; + if (!account) { + continue; + } const accountId = asString(account.accountId) ?? "default"; const enabled = account.enabled !== false; const configured = account.configured === true; - if (!enabled || !configured) continue; + if (!enabled || !configured) { + continue; + } if (account.allowUnmentionedGroups === true) { issues.push({ @@ -108,7 +120,9 @@ export function collectTelegramStatusIssues( }); } for (const group of audit.groups ?? []) { - if (group.ok === true) continue; + if (group.ok === true) { + continue; + } const status = group.status ? ` status=${group.status}` : ""; const err = group.error ? `: ${group.error}` : ""; const baseMessage = `Group ${group.chatId} not reachable by bot.${status}${err}`; diff --git a/src/channels/plugins/status-issues/whatsapp.ts b/src/channels/plugins/status-issues/whatsapp.ts index 7499988fed..99ed65a000 100644 --- a/src/channels/plugins/status-issues/whatsapp.ts +++ b/src/channels/plugins/status-issues/whatsapp.ts @@ -13,7 +13,9 @@ type WhatsAppAccountStatus = { }; function readWhatsAppAccountStatus(value: ChannelAccountSnapshot): WhatsAppAccountStatus | null { - if (!isRecord(value)) return null; + if (!isRecord(value)) { + return null; + } return { accountId: value.accountId, enabled: value.enabled, @@ -31,10 +33,14 @@ export function collectWhatsAppStatusIssues( const issues: ChannelStatusIssue[] = []; for (const entry of accounts) { const account = readWhatsAppAccountStatus(entry); - if (!account) continue; + if (!account) { + continue; + } const accountId = asString(account.accountId) ?? "default"; const enabled = account.enabled !== false; - if (!enabled) continue; + if (!enabled) { + continue; + } const linked = account.linked === true; const running = account.running === true; const connected = account.connected === true; diff --git a/src/channels/plugins/whatsapp-heartbeat.ts b/src/channels/plugins/whatsapp-heartbeat.ts index ae1d7b5d49..ba19747577 100644 --- a/src/channels/plugins/whatsapp-heartbeat.ts +++ b/src/channels/plugins/whatsapp-heartbeat.ts @@ -9,7 +9,9 @@ type HeartbeatRecipientsOpts = { to?: string; all?: boolean }; function getSessionRecipients(cfg: OpenClawConfig) { const sessionCfg = cfg.session; const scope = sessionCfg?.scope ?? "per-sender"; - if (scope === "global") return []; + if (scope === "global") { + return []; + } const storePath = resolveStorePath(cfg.session?.store); const store = loadSessionStore(storePath); const isGroupKey = (key: string) => @@ -32,7 +34,9 @@ function getSessionRecipients(cfg: OpenClawConfig) { // Dedupe while preserving recency ordering. const seen = new Set(); return recipients.filter((r) => { - if (seen.has(r.to)) return false; + if (seen.has(r.to)) { + return false; + } seen.add(r.to); return true; }); diff --git a/src/channels/registry.test.ts b/src/channels/registry.test.ts index 95b86496f2..c5da14e9a1 100644 --- a/src/channels/registry.test.ts +++ b/src/channels/registry.test.ts @@ -27,7 +27,9 @@ describe("channel registry", () => { it("formats selection lines with docs labels", () => { const channels = listChatChannels(); const first = channels[0]; - if (!first) throw new Error("Missing channel metadata."); + if (!first) { + throw new Error("Missing channel metadata."); + } const line = formatChannelSelectionLine(first, (path, label) => [label, path].filter(Boolean).join(":"), ); diff --git a/src/channels/registry.ts b/src/channels/registry.ts index e36c935904..701516a0c8 100644 --- a/src/channels/registry.ts +++ b/src/channels/registry.ts @@ -125,7 +125,9 @@ export function getChatChannelMeta(id: ChatChannelId): ChatChannelMeta { export function normalizeChatChannelId(raw?: string | null): ChatChannelId | null { const normalized = normalizeChannelKey(raw); - if (!normalized) return null; + if (!normalized) { + return null; + } const resolved = CHAT_CHANNEL_ALIASES[normalized] ?? normalized; return CHAT_CHANNEL_ORDER.includes(resolved) ? resolved : null; } @@ -142,14 +144,18 @@ export function normalizeChannelId(raw?: string | null): ChatChannelId | null { // monitors, web login, etc). The plugin registry must be initialized first. export function normalizeAnyChannelId(raw?: string | null): ChannelId | null { const key = normalizeChannelKey(raw); - if (!key) return null; + if (!key) { + return null; + } const registry = requireActivePluginRegistry(); const hit = registry.channels.find((entry) => { const id = String(entry.plugin.id ?? "") .trim() .toLowerCase(); - if (id && id === key) return true; + if (id && id === key) { + return true; + } return (entry.plugin.meta.aliases ?? []).some((alias) => alias.trim().toLowerCase() === key); }); return hit?.plugin.id ?? null; diff --git a/src/channels/sender-identity.ts b/src/channels/sender-identity.ts index 8c2ebbc88b..d3117f0a57 100644 --- a/src/channels/sender-identity.ts +++ b/src/channels/sender-identity.ts @@ -25,10 +25,12 @@ export function validateSenderIdentity(ctx: MsgContext): string[] { } if (senderUsername) { - if (senderUsername.includes("@")) + if (senderUsername.includes("@")) { issues.push(`SenderUsername should not include "@": ${senderUsername}`); - if (/\s/.test(senderUsername)) + } + if (/\s/.test(senderUsername)) { issues.push(`SenderUsername should not include whitespace: ${senderUsername}`); + } } if (ctx.SenderId != null && !senderId) { diff --git a/src/channels/sender-label.ts b/src/channels/sender-label.ts index 2b230ab73b..208c5d5a49 100644 --- a/src/channels/sender-label.ts +++ b/src/channels/sender-label.ts @@ -20,7 +20,9 @@ export function resolveSenderLabel(params: SenderLabelParams): string | null { const display = name ?? username ?? tag ?? ""; const idPart = e164 ?? id ?? ""; - if (display && idPart && display !== idPart) return `${display} (${idPart})`; + if (display && idPart && display !== idPart) { + return `${display} (${idPart})`; + } return display || idPart || null; } @@ -32,12 +34,24 @@ export function listSenderLabelCandidates(params: SenderLabelParams): string[] { const e164 = normalize(params.e164); const id = normalize(params.id); - if (name) candidates.add(name); - if (username) candidates.add(username); - if (tag) candidates.add(tag); - if (e164) candidates.add(e164); - if (id) candidates.add(id); + if (name) { + candidates.add(name); + } + if (username) { + candidates.add(username); + } + if (tag) { + candidates.add(tag); + } + if (e164) { + candidates.add(e164); + } + if (id) { + candidates.add(id); + } const resolved = resolveSenderLabel(params); - if (resolved) candidates.add(resolved); + if (resolved) { + candidates.add(resolved); + } return Array.from(candidates); } diff --git a/src/channels/session.ts b/src/channels/session.ts index 2d34d7f74c..8aeb371dbb 100644 --- a/src/channels/session.ts +++ b/src/channels/session.ts @@ -33,7 +33,9 @@ export async function recordInboundSession(params: { }).catch(params.onRecordError); const update = params.updateLastRoute; - if (!update) return; + if (!update) { + return; + } await updateLastRoute({ storePath, sessionKey: update.sessionKey, diff --git a/src/cli/argv.ts b/src/cli/argv.ts index 9356f66034..d922e78638 100644 --- a/src/cli/argv.ts +++ b/src/cli/argv.ts @@ -7,23 +7,35 @@ export function hasHelpOrVersion(argv: string[]): boolean { } function isValueToken(arg: string | undefined): boolean { - if (!arg) return false; - if (arg === FLAG_TERMINATOR) return false; - if (!arg.startsWith("-")) return true; + if (!arg) { + return false; + } + if (arg === FLAG_TERMINATOR) { + return false; + } + if (!arg.startsWith("-")) { + return true; + } return /^-\d+(?:\.\d+)?$/.test(arg); } function parsePositiveInt(value: string): number | undefined { const parsed = Number.parseInt(value, 10); - if (Number.isNaN(parsed) || parsed <= 0) return undefined; + if (Number.isNaN(parsed) || parsed <= 0) { + return undefined; + } return parsed; } export function hasFlag(argv: string[], name: string): boolean { const args = argv.slice(2); for (const arg of args) { - if (arg === FLAG_TERMINATOR) break; - if (arg === name) return true; + if (arg === FLAG_TERMINATOR) { + break; + } + if (arg === name) { + return true; + } } return false; } @@ -32,7 +44,9 @@ export function getFlagValue(argv: string[], name: string): string | null | unde const args = argv.slice(2); for (let i = 0; i < args.length; i += 1) { const arg = args[i]; - if (arg === FLAG_TERMINATOR) break; + if (arg === FLAG_TERMINATOR) { + break; + } if (arg === name) { const next = args[i + 1]; return isValueToken(next) ? next : null; @@ -46,14 +60,20 @@ export function getFlagValue(argv: string[], name: string): string | null | unde } export function getVerboseFlag(argv: string[], options?: { includeDebug?: boolean }): boolean { - if (hasFlag(argv, "--verbose")) return true; - if (options?.includeDebug && hasFlag(argv, "--debug")) return true; + if (hasFlag(argv, "--verbose")) { + return true; + } + if (options?.includeDebug && hasFlag(argv, "--debug")) { + return true; + } return false; } export function getPositiveIntFlagValue(argv: string[], name: string): number | null | undefined { const raw = getFlagValue(argv, name); - if (raw === null || raw === undefined) return raw; + if (raw === null || raw === undefined) { + return raw; + } return parsePositiveInt(raw); } @@ -62,11 +82,19 @@ export function getCommandPath(argv: string[], depth = 2): string[] { const path: string[] = []; for (let i = 0; i < args.length; i += 1) { const arg = args[i]; - if (!arg) continue; - if (arg === "--") break; - if (arg.startsWith("-")) continue; + if (!arg) { + continue; + } + if (arg === "--") { + break; + } + if (arg.startsWith("-")) { + continue; + } path.push(arg); - if (path.length >= depth) break; + if (path.length >= depth) { + break; + } } return path; } @@ -97,7 +125,9 @@ export function buildParseArgv(params: { const executable = (normalizedArgv[0]?.split(/[/\\]/).pop() ?? "").toLowerCase(); const looksLikeNode = normalizedArgv.length >= 2 && (isNodeExecutable(executable) || isBunExecutable(executable)); - if (looksLikeNode) return normalizedArgv; + if (looksLikeNode) { + return normalizedArgv; + } return ["node", programName || "openclaw", ...normalizedArgv]; } @@ -118,11 +148,19 @@ function isBunExecutable(executable: string): boolean { } export function shouldMigrateStateFromPath(path: string[]): boolean { - if (path.length === 0) return true; + if (path.length === 0) { + return true; + } const [primary, secondary] = path; - if (primary === "health" || primary === "status" || primary === "sessions") return false; - if (primary === "memory" && secondary === "status") return false; - if (primary === "agent") return false; + if (primary === "health" || primary === "status" || primary === "sessions") { + return false; + } + if (primary === "memory" && secondary === "status") { + return false; + } + if (primary === "agent") { + return false; + } return true; } diff --git a/src/cli/banner.ts b/src/cli/banner.ts index a3a452bfcc..629a1f24c9 100644 --- a/src/cli/banner.ts +++ b/src/cli/banner.ts @@ -18,7 +18,9 @@ const graphemeSegmenter = : null; function splitGraphemes(value: string): string[] { - if (!graphemeSegmenter) return Array.from(value); + if (!graphemeSegmenter) { + return Array.from(value); + } try { return Array.from(graphemeSegmenter.segment(value), (seg) => seg.segment); } catch { @@ -74,12 +76,20 @@ const LOBSTER_ASCII = [ export function formatCliBannerArt(options: BannerOptions = {}): string { const rich = options.richTty ?? isRich(); - if (!rich) return LOBSTER_ASCII.join("\n"); + if (!rich) { + return LOBSTER_ASCII.join("\n"); + } const colorChar = (ch: string) => { - if (ch === "█") return theme.accentBright(ch); - if (ch === "░") return theme.accentDim(ch); - if (ch === "▀") return theme.accent(ch); + if (ch === "█") { + return theme.accentBright(ch); + } + if (ch === "░") { + return theme.accentDim(ch); + } + if (ch === "▀") { + return theme.accent(ch); + } return theme.muted(ch); }; @@ -99,11 +109,19 @@ export function formatCliBannerArt(options: BannerOptions = {}): string { } export function emitCliBanner(version: string, options: BannerOptions = {}) { - if (bannerEmitted) return; + if (bannerEmitted) { + return; + } const argv = options.argv ?? process.argv; - if (!process.stdout.isTTY) return; - if (hasJsonFlag(argv)) return; - if (hasVersionFlag(argv)) return; + if (!process.stdout.isTTY) { + return; + } + if (hasJsonFlag(argv)) { + return; + } + if (hasVersionFlag(argv)) { + return; + } const line = formatCliBannerLine(version, options); process.stdout.write(`\n${line}\n\n`); bannerEmitted = true; diff --git a/src/cli/browser-cli-actions-input/register.element.ts b/src/cli/browser-cli-actions-input/register.element.ts index fff318ccf5..270d59d682 100644 --- a/src/cli/browser-cli-actions-input/register.element.ts +++ b/src/cli/browser-cli-actions-input/register.element.ts @@ -19,7 +19,9 @@ export function registerBrowserElementCommands( .action(async (ref: string | undefined, opts, cmd) => { const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts); const refValue = requireRef(ref); - if (!refValue) return; + if (!refValue) { + return; + } const modifiers = opts.modifiers ? String(opts.modifiers) .split(",") @@ -62,7 +64,9 @@ export function registerBrowserElementCommands( .action(async (ref: string | undefined, text: string, opts, cmd) => { const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts); const refValue = requireRef(ref); - if (!refValue) return; + if (!refValue) { + return; + } try { const result = await callBrowserAct({ parent, @@ -146,7 +150,9 @@ export function registerBrowserElementCommands( .action(async (ref: string | undefined, opts, cmd) => { const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts); const refValue = requireRef(ref); - if (!refValue) return; + if (!refValue) { + return; + } try { const result = await callBrowserAct({ parent, diff --git a/src/cli/browser-cli-actions-input/shared.ts b/src/cli/browser-cli-actions-input/shared.ts index a04449d548..c3a68aa0ba 100644 --- a/src/cli/browser-cli-actions-input/shared.ts +++ b/src/cli/browser-cli-actions-input/shared.ts @@ -56,9 +56,13 @@ export async function readFields(opts: { fieldsFile?: string; }): Promise { const payload = opts.fieldsFile ? await readFile(opts.fieldsFile) : (opts.fields ?? ""); - if (!payload.trim()) throw new Error("fields are required"); + if (!payload.trim()) { + throw new Error("fields are required"); + } const parsed = JSON.parse(payload) as unknown; - if (!Array.isArray(parsed)) throw new Error("fields must be an array"); + if (!Array.isArray(parsed)) { + throw new Error("fields must be an array"); + } return parsed.map((entry, index) => { if (!entry || typeof entry !== "object") { throw new Error(`fields[${index}] must be an object`); diff --git a/src/cli/browser-cli-extension.test.ts b/src/cli/browser-cli-extension.test.ts index fe477e45a2..6b3df4b8d5 100644 --- a/src/cli/browser-cli-extension.test.ts +++ b/src/cli/browser-cli-extension.test.ts @@ -63,8 +63,11 @@ describe("browser extension install", () => { expect(copyToClipboard).toHaveBeenCalledWith(dir); } finally { - if (prev === undefined) delete process.env.OPENCLAW_STATE_DIR; - else process.env.OPENCLAW_STATE_DIR = prev; + if (prev === undefined) { + delete process.env.OPENCLAW_STATE_DIR; + } else { + process.env.OPENCLAW_STATE_DIR = prev; + } } }); }); diff --git a/src/cli/browser-cli-extension.ts b/src/cli/browser-cli-extension.ts index 6d25a9d5d5..a8cc25f163 100644 --- a/src/cli/browser-cli-extension.ts +++ b/src/cli/browser-cli-extension.ts @@ -120,6 +120,8 @@ export function registerBrowserExtensionCommands( const displayPath = shortenHomePath(dir); defaultRuntime.log(displayPath); const copied = await copyToClipboard(dir).catch(() => false); - if (copied) defaultRuntime.error(info("Copied to clipboard.")); + if (copied) { + defaultRuntime.error(info("Copied to clipboard.")); + } }); } diff --git a/src/cli/browser-cli-shared.ts b/src/cli/browser-cli-shared.ts index aa78eff27c..34a7bc9b95 100644 --- a/src/cli/browser-cli-shared.ts +++ b/src/cli/browser-cli-shared.ts @@ -14,10 +14,14 @@ type BrowserRequestParams = { }; function normalizeQuery(query: BrowserRequestParams["query"]): Record | undefined { - if (!query) return undefined; + if (!query) { + return undefined; + } const out: Record = {}; for (const [key, value] of Object.entries(query)) { - if (value === undefined) continue; + if (value === undefined) { + continue; + } out[key] = String(value); } return Object.keys(out).length ? out : undefined; diff --git a/src/cli/browser-cli-state.ts b/src/cli/browser-cli-state.ts index 335f9547aa..81e21162ca 100644 --- a/src/cli/browser-cli-state.ts +++ b/src/cli/browser-cli-state.ts @@ -116,7 +116,9 @@ export function registerBrowserStateCommands( } const headers: Record = {}; for (const [k, v] of Object.entries(parsed as Record)) { - if (typeof v === "string") headers[k] = v; + if (typeof v === "string") { + headers[k] = v; + } } const result = await callBrowserRequest( parent, diff --git a/src/cli/channel-options.ts b/src/cli/channel-options.ts index 43c0878bd4..97ba059887 100644 --- a/src/cli/channel-options.ts +++ b/src/cli/channel-options.ts @@ -8,7 +8,9 @@ function dedupe(values: string[]): string[] { const seen = new Set(); const resolved: string[] = []; for (const value of values) { - if (!value || seen.has(value)) continue; + if (!value || seen.has(value)) { + continue; + } seen.add(value); resolved.push(value); } diff --git a/src/cli/cli-name.ts b/src/cli/cli-name.ts index d79a40c3c0..041c2a30dd 100644 --- a/src/cli/cli-name.ts +++ b/src/cli/cli-name.ts @@ -7,15 +7,23 @@ const CLI_PREFIX_RE = /^(?:((?:pnpm|npm|bunx|npx)\s+))?(openclaw)\b/; export function resolveCliName(argv: string[] = process.argv): string { const argv1 = argv[1]; - if (!argv1) return DEFAULT_CLI_NAME; + if (!argv1) { + return DEFAULT_CLI_NAME; + } const base = path.basename(argv1).trim(); - if (KNOWN_CLI_NAMES.has(base)) return base; + if (KNOWN_CLI_NAMES.has(base)) { + return base; + } return DEFAULT_CLI_NAME; } export function replaceCliName(command: string, cliName = resolveCliName()): string { - if (!command.trim()) return command; - if (!CLI_PREFIX_RE.test(command)) return command; + if (!command.trim()) { + return command; + } + if (!CLI_PREFIX_RE.test(command)) { + return command; + } return command.replace(CLI_PREFIX_RE, (_match, runner: string | undefined) => { return `${runner ?? ""}${cliName}`; }); diff --git a/src/cli/cli-utils.ts b/src/cli/cli-utils.ts index 545cb42237..72cd3e11bd 100644 --- a/src/cli/cli-utils.ts +++ b/src/cli/cli-utils.ts @@ -56,7 +56,9 @@ export function resolveOptionFromCommand( let current: Command | null | undefined = command; while (current) { const opts = current.opts?.() ?? {}; - if (opts[key] !== undefined) return opts[key]; + if (opts[key] !== undefined) { + return opts[key]; + } current = current.parent ?? undefined; } return undefined; diff --git a/src/cli/command-format.ts b/src/cli/command-format.ts index bb773beb41..8fd4e64ea4 100644 --- a/src/cli/command-format.ts +++ b/src/cli/command-format.ts @@ -12,8 +12,12 @@ export function formatCliCommand( const cliName = resolveCliName(); const normalizedCommand = replaceCliName(command, cliName); const profile = normalizeProfileName(env.OPENCLAW_PROFILE); - if (!profile) return normalizedCommand; - if (!CLI_PREFIX_RE.test(normalizedCommand)) return normalizedCommand; + if (!profile) { + return normalizedCommand; + } + if (!CLI_PREFIX_RE.test(normalizedCommand)) { + return normalizedCommand; + } if (PROFILE_FLAG_RE.test(normalizedCommand) || DEV_FLAG_RE.test(normalizedCommand)) { return normalizedCommand; } diff --git a/src/cli/completion-cli.ts b/src/cli/completion-cli.ts index 42916c1d41..0d2aef7bfb 100644 --- a/src/cli/completion-cli.ts +++ b/src/cli/completion-cli.ts @@ -21,7 +21,9 @@ export function registerCompletionCli(program: Command) { const entries = getSubCliEntries(); for (const entry of entries) { // Skip completion command itself to avoid cycle if we were to add it to the list - if (entry.name === "completion") continue; + if (entry.name === "completion") { + continue; + } await registerSubCliByName(program, entry.name); } @@ -84,7 +86,9 @@ export async function installCompletion(shell: string, yes: boolean, binName = " const content = await fs.readFile(profilePath, "utf-8"); if (content.includes(`${binName} completion`)) { - if (!yes) console.log(`Completion already installed in ${profilePath}`); + if (!yes) { + console.log(`Completion already installed in ${profilePath}`); + } return; } @@ -318,7 +322,9 @@ function generateFishCompletion(program: Command): string { const visit = (cmd: Command, parents: string[]) => { const cmdName = cmd.name(); const fullPath = [...parents]; - if (parents.length > 0) fullPath.push(cmdName); // Only push if not root, or consistent root handling + if (parents.length > 0) { + fullPath.push(cmdName); + } // Only push if not root, or consistent root handling // Fish uses 'seen_subcommand_from' to determine context. // For root: complete -c openclaw -n "__fish_use_subcommand" -a "subcmd" -d "desc" @@ -339,8 +345,12 @@ function generateFishCompletion(program: Command): string { ?.replace(/^-/, ""); const desc = opt.description.replace(/'/g, "'\\''"); let line = `complete -c ${rootCmd} -n "__fish_use_subcommand"`; - if (short) line += ` -s ${short}`; - if (long) line += ` -l ${long}`; + if (short) { + line += ` -s ${short}`; + } + if (long) { + line += ` -l ${long}`; + } line += ` -d '${desc}'\n`; script += line; } @@ -368,8 +378,12 @@ function generateFishCompletion(program: Command): string { ?.replace(/^-/, ""); const desc = opt.description.replace(/'/g, "'\\''"); let line = `complete -c ${rootCmd} -n "__fish_seen_subcommand_from ${cmdName}"`; - if (short) line += ` -s ${short}`; - if (long) line += ` -l ${long}`; + if (short) { + line += ` -s ${short}`; + } + if (long) { + line += ` -l ${long}`; + } line += ` -d '${desc}'\n`; script += line; } diff --git a/src/cli/config-cli.ts b/src/cli/config-cli.ts index c613bcd6f3..a88ea70de0 100644 --- a/src/cli/config-cli.ts +++ b/src/cli/config-cli.ts @@ -17,7 +17,9 @@ function isIndexSegment(raw: string): boolean { function parsePath(raw: string): PathSegment[] { const trimmed = raw.trim(); - if (!trimmed) return []; + if (!trimmed) { + return []; + } const parts: string[] = []; let current = ""; let i = 0; @@ -25,23 +27,33 @@ function parsePath(raw: string): PathSegment[] { const ch = trimmed[i]; if (ch === "\\") { const next = trimmed[i + 1]; - if (next) current += next; + if (next) { + current += next; + } i += 2; continue; } if (ch === ".") { - if (current) parts.push(current); + if (current) { + parts.push(current); + } current = ""; i += 1; continue; } if (ch === "[") { - if (current) parts.push(current); + if (current) { + parts.push(current); + } current = ""; const close = trimmed.indexOf("]", i); - if (close === -1) throw new Error(`Invalid path (missing "]"): ${raw}`); + if (close === -1) { + throw new Error(`Invalid path (missing "]"): ${raw}`); + } const inside = trimmed.slice(i + 1, close).trim(); - if (!inside) throw new Error(`Invalid path (empty "[]"): ${raw}`); + if (!inside) { + throw new Error(`Invalid path (empty "[]"): ${raw}`); + } parts.push(inside); i = close + 1; continue; @@ -49,7 +61,9 @@ function parsePath(raw: string): PathSegment[] { current += ch; i += 1; } - if (current) parts.push(current); + if (current) { + parts.push(current); + } return parts.map((part) => part.trim()).filter(Boolean); } @@ -73,9 +87,13 @@ function parseValue(raw: string, opts: { json?: boolean }): unknown { function getAtPath(root: unknown, path: PathSegment[]): { found: boolean; value?: unknown } { let current: unknown = root; for (const segment of path) { - if (!current || typeof current !== "object") return { found: false }; + if (!current || typeof current !== "object") { + return { found: false }; + } if (Array.isArray(current)) { - if (!isIndexSegment(segment)) return { found: false }; + if (!isIndexSegment(segment)) { + return { found: false }; + } const index = Number.parseInt(segment, 10); if (!Number.isFinite(index) || index < 0 || index >= current.length) { return { found: false }; @@ -84,7 +102,9 @@ function getAtPath(root: unknown, path: PathSegment[]): { found: boolean; value? continue; } const record = current as Record; - if (!(segment in record)) return { found: false }; + if (!(segment in record)) { + return { found: false }; + } current = record[segment]; } return { found: true, value: current }; @@ -138,37 +158,55 @@ function unsetAtPath(root: Record, path: PathSegment[]): boolea let current: unknown = root; for (let i = 0; i < path.length - 1; i += 1) { const segment = path[i]; - if (!current || typeof current !== "object") return false; + if (!current || typeof current !== "object") { + return false; + } if (Array.isArray(current)) { - if (!isIndexSegment(segment)) return false; + if (!isIndexSegment(segment)) { + return false; + } const index = Number.parseInt(segment, 10); - if (!Number.isFinite(index) || index < 0 || index >= current.length) return false; + if (!Number.isFinite(index) || index < 0 || index >= current.length) { + return false; + } current = current[index]; continue; } const record = current as Record; - if (!(segment in record)) return false; + if (!(segment in record)) { + return false; + } current = record[segment]; } const last = path[path.length - 1]; if (Array.isArray(current)) { - if (!isIndexSegment(last)) return false; + if (!isIndexSegment(last)) { + return false; + } const index = Number.parseInt(last, 10); - if (!Number.isFinite(index) || index < 0 || index >= current.length) return false; + if (!Number.isFinite(index) || index < 0 || index >= current.length) { + return false; + } current.splice(index, 1); return true; } - if (!current || typeof current !== "object") return false; + if (!current || typeof current !== "object") { + return false; + } const record = current as Record; - if (!(last in record)) return false; + if (!(last in record)) { + return false; + } delete record[last]; return true; } async function loadValidConfig() { const snapshot = await readConfigFileSnapshot(); - if (snapshot.valid) return snapshot; + if (snapshot.valid) { + return snapshot; + } defaultRuntime.error(`Config invalid at ${shortenHomePath(snapshot.path)}.`); for (const issue of snapshot.issues) { defaultRuntime.error(`- ${issue.path || ""}: ${issue.message}`); @@ -264,7 +302,9 @@ export function registerConfigCli(program: Command) { .action(async (path: string, value: string, opts) => { try { const parsedPath = parsePath(path); - if (parsedPath.length === 0) throw new Error("Path is empty."); + if (parsedPath.length === 0) { + throw new Error("Path is empty."); + } const parsedValue = parseValue(value, opts); const snapshot = await loadValidConfig(); const next = snapshot.config as Record; @@ -284,7 +324,9 @@ export function registerConfigCli(program: Command) { .action(async (path: string) => { try { const parsedPath = parsePath(path); - if (parsedPath.length === 0) throw new Error("Path is empty."); + if (parsedPath.length === 0) { + throw new Error("Path is empty."); + } const snapshot = await loadValidConfig(); const next = snapshot.config as Record; const removed = unsetAtPath(next, parsedPath); diff --git a/src/cli/cron-cli.test.ts b/src/cli/cron-cli.test.ts index 459988246a..4176966d0b 100644 --- a/src/cli/cron-cli.test.ts +++ b/src/cli/cron-cli.test.ts @@ -2,7 +2,9 @@ import { Command } from "commander"; import { describe, expect, it, vi } from "vitest"; const callGatewayFromCli = vi.fn(async (method: string, _opts: unknown, params?: unknown) => { - if (method === "cron.status") return { enabled: true }; + if (method === "cron.status") { + return { enabled: true }; + } return { ok: true, params }; }); diff --git a/src/cli/cron-cli/register.cron-add.ts b/src/cli/cron-cli/register.cron-add.ts index ee311ac43e..588669655f 100644 --- a/src/cli/cron-cli/register.cron-add.ts +++ b/src/cli/cron-cli/register.cron-add.ts @@ -111,12 +111,16 @@ export function registerCronAddCommand(cron: Command) { } if (at) { const atMs = parseAtMs(at); - if (!atMs) throw new Error("Invalid --at; use ISO time or duration like 20m"); + if (!atMs) { + throw new Error("Invalid --at; use ISO time or duration like 20m"); + } return { kind: "at" as const, atMs }; } if (every) { const everyMs = parseDurationMs(every); - if (!everyMs) throw new Error("Invalid --every; use e.g. 10m, 1h, 1d"); + if (!everyMs) { + throw new Error("Invalid --every; use e.g. 10m, 1h, 1d"); + } return { kind: "every" as const, everyMs }; } return { @@ -150,7 +154,9 @@ export function registerCronAddCommand(cron: Command) { if (chosen !== 1) { throw new Error("Choose exactly one payload: --system-event or --message"); } - if (systemEvent) return { kind: "systemEvent" as const, text: systemEvent }; + if (systemEvent) { + return { kind: "systemEvent" as const, text: systemEvent }; + } const timeoutSeconds = parsePositiveIntOrUndefined(opts.timeoutSeconds); return { kind: "agentTurn" as const, @@ -197,7 +203,9 @@ export function registerCronAddCommand(cron: Command) { const nameRaw = typeof opts.name === "string" ? opts.name : ""; const name = nameRaw.trim(); - if (!name) throw new Error("--name is required"); + if (!name) { + throw new Error("--name is required"); + } const description = typeof opts.description === "string" && opts.description.trim() diff --git a/src/cli/cron-cli/register.cron-edit.ts b/src/cli/cron-cli/register.cron-edit.ts index 1f600c5abb..b8dbe02f6b 100644 --- a/src/cli/cron-cli/register.cron-edit.ts +++ b/src/cli/cron-cli/register.cron-edit.ts @@ -16,7 +16,9 @@ const assignIf = ( value: unknown, shouldAssign: boolean, ) => { - if (shouldAssign) target[key] = value; + if (shouldAssign) { + target[key] = value; + } }; export function registerCronEditCommand(cron: Command) { @@ -74,19 +76,36 @@ export function registerCronEditCommand(cron: Command) { } const patch: Record = {}; - if (typeof opts.name === "string") patch.name = opts.name; - if (typeof opts.description === "string") patch.description = opts.description; - if (opts.enable && opts.disable) + if (typeof opts.name === "string") { + patch.name = opts.name; + } + if (typeof opts.description === "string") { + patch.description = opts.description; + } + if (opts.enable && opts.disable) { throw new Error("Choose --enable or --disable, not both"); - if (opts.enable) patch.enabled = true; - if (opts.disable) patch.enabled = false; + } + if (opts.enable) { + patch.enabled = true; + } + if (opts.disable) { + patch.enabled = false; + } if (opts.deleteAfterRun && opts.keepAfterRun) { throw new Error("Choose --delete-after-run or --keep-after-run, not both"); } - if (opts.deleteAfterRun) patch.deleteAfterRun = true; - if (opts.keepAfterRun) patch.deleteAfterRun = false; - if (typeof opts.session === "string") patch.sessionTarget = opts.session; - if (typeof opts.wake === "string") patch.wakeMode = opts.wake; + if (opts.deleteAfterRun) { + patch.deleteAfterRun = true; + } + if (opts.keepAfterRun) { + patch.deleteAfterRun = false; + } + if (typeof opts.session === "string") { + patch.sessionTarget = opts.session; + } + if (typeof opts.wake === "string") { + patch.wakeMode = opts.wake; + } if (opts.agent && opts.clearAgent) { throw new Error("Use --agent or --clear-agent, not both"); } @@ -98,14 +117,20 @@ export function registerCronEditCommand(cron: Command) { } const scheduleChosen = [opts.at, opts.every, opts.cron].filter(Boolean).length; - if (scheduleChosen > 1) throw new Error("Choose at most one schedule change"); + if (scheduleChosen > 1) { + throw new Error("Choose at most one schedule change"); + } if (opts.at) { const atMs = parseAtMs(String(opts.at)); - if (!atMs) throw new Error("Invalid --at"); + if (!atMs) { + throw new Error("Invalid --at"); + } patch.schedule = { kind: "at", atMs }; } else if (opts.every) { const everyMs = parseDurationMs(String(opts.every)); - if (!everyMs) throw new Error("Invalid --every"); + if (!everyMs) { + throw new Error("Invalid --every"); + } patch.schedule = { kind: "every", everyMs }; } else if (opts.cron) { patch.schedule = { diff --git a/src/cli/cron-cli/shared.ts b/src/cli/cron-cli/shared.ts index 5e5efc81ad..9481e51002 100644 --- a/src/cli/cron-cli/shared.ts +++ b/src/cli/cron-cli/shared.ts @@ -15,7 +15,9 @@ export async function warnIfCronSchedulerDisabled(opts: GatewayRpcOpts) { enabled?: boolean; storePath?: string; }; - if (res?.enabled === true) return; + if (res?.enabled === true) { + return; + } const store = typeof res?.storePath === "string" ? res.storePath : ""; defaultRuntime.error( [ @@ -33,11 +35,17 @@ export async function warnIfCronSchedulerDisabled(opts: GatewayRpcOpts) { export function parseDurationMs(input: string): number | null { const raw = input.trim(); - if (!raw) return null; + if (!raw) { + return null; + } const match = raw.match(/^(\d+(?:\.\d+)?)(ms|s|m|h|d)$/i); - if (!match) return null; + if (!match) { + return null; + } const n = Number.parseFloat(match[1] ?? ""); - if (!Number.isFinite(n) || n <= 0) return null; + if (!Number.isFinite(n) || n <= 0) { + return null; + } const unit = (match[2] ?? "").toLowerCase(); const factor = unit === "ms" @@ -54,11 +62,17 @@ export function parseDurationMs(input: string): number | null { export function parseAtMs(input: string): number | null { const raw = input.trim(); - if (!raw) return null; + if (!raw) { + return null; + } const absolute = parseAbsoluteTimeMs(raw); - if (absolute) return absolute; + if (absolute) { + return absolute; + } const dur = parseDurationMs(raw); - if (dur) return Date.now() + dur; + if (dur) { + return Date.now() + dur; + } return null; } @@ -74,48 +88,76 @@ const CRON_AGENT_PAD = 10; const pad = (value: string, width: number) => value.padEnd(width); const truncate = (value: string, width: number) => { - if (value.length <= width) return value; - if (width <= 3) return value.slice(0, width); + if (value.length <= width) { + return value; + } + if (width <= 3) { + return value.slice(0, width); + } return `${value.slice(0, width - 3)}...`; }; const formatIsoMinute = (ms: number) => { const d = new Date(ms); - if (Number.isNaN(d.getTime())) return "-"; + if (Number.isNaN(d.getTime())) { + return "-"; + } const iso = d.toISOString(); return `${iso.slice(0, 10)} ${iso.slice(11, 16)}Z`; }; const formatDuration = (ms: number) => { - if (ms < 60_000) return `${Math.max(1, Math.round(ms / 1000))}s`; - if (ms < 3_600_000) return `${Math.round(ms / 60_000)}m`; - if (ms < 86_400_000) return `${Math.round(ms / 3_600_000)}h`; + if (ms < 60_000) { + return `${Math.max(1, Math.round(ms / 1000))}s`; + } + if (ms < 3_600_000) { + return `${Math.round(ms / 60_000)}m`; + } + if (ms < 86_400_000) { + return `${Math.round(ms / 3_600_000)}h`; + } return `${Math.round(ms / 86_400_000)}d`; }; const formatSpan = (ms: number) => { - if (ms < 60_000) return "<1m"; - if (ms < 3_600_000) return `${Math.round(ms / 60_000)}m`; - if (ms < 86_400_000) return `${Math.round(ms / 3_600_000)}h`; + if (ms < 60_000) { + return "<1m"; + } + if (ms < 3_600_000) { + return `${Math.round(ms / 60_000)}m`; + } + if (ms < 86_400_000) { + return `${Math.round(ms / 3_600_000)}h`; + } return `${Math.round(ms / 86_400_000)}d`; }; const formatRelative = (ms: number | null | undefined, nowMs: number) => { - if (!ms) return "-"; + if (!ms) { + return "-"; + } const delta = ms - nowMs; const label = formatSpan(Math.abs(delta)); return delta >= 0 ? `in ${label}` : `${label} ago`; }; const formatSchedule = (schedule: CronSchedule) => { - if (schedule.kind === "at") return `at ${formatIsoMinute(schedule.atMs)}`; - if (schedule.kind === "every") return `every ${formatDuration(schedule.everyMs)}`; + if (schedule.kind === "at") { + return `at ${formatIsoMinute(schedule.atMs)}`; + } + if (schedule.kind === "every") { + return `every ${formatDuration(schedule.everyMs)}`; + } return schedule.tz ? `cron ${schedule.expr} @ ${schedule.tz}` : `cron ${schedule.expr}`; }; const formatStatus = (job: CronJob) => { - if (!job.enabled) return "disabled"; - if (job.state.runningAtMs) return "running"; + if (!job.enabled) { + return "disabled"; + } + if (job.state.runningAtMs) { + return "running"; + } return job.state.lastStatus ?? "idle"; }; @@ -158,10 +200,18 @@ export function printCronList(jobs: CronJob[], runtime = defaultRuntime) { const agentLabel = pad(truncate(job.agentId ?? "default", CRON_AGENT_PAD), CRON_AGENT_PAD); const coloredStatus = (() => { - if (statusRaw === "ok") return colorize(rich, theme.success, statusLabel); - if (statusRaw === "error") return colorize(rich, theme.error, statusLabel); - if (statusRaw === "running") return colorize(rich, theme.warn, statusLabel); - if (statusRaw === "skipped") return colorize(rich, theme.muted, statusLabel); + if (statusRaw === "ok") { + return colorize(rich, theme.success, statusLabel); + } + if (statusRaw === "error") { + return colorize(rich, theme.error, statusLabel); + } + if (statusRaw === "running") { + return colorize(rich, theme.warn, statusLabel); + } + if (statusRaw === "skipped") { + return colorize(rich, theme.muted, statusLabel); + } return colorize(rich, theme.muted, statusLabel); })(); diff --git a/src/cli/daemon-cli.coverage.test.ts b/src/cli/daemon-cli.coverage.test.ts index a458069e73..59adbf74e9 100644 --- a/src/cli/daemon-cli.coverage.test.ts +++ b/src/cli/daemon-cli.coverage.test.ts @@ -96,21 +96,29 @@ describe("daemon-cli coverage", () => { }); afterEach(() => { - if (originalEnv.OPENCLAW_STATE_DIR !== undefined) + if (originalEnv.OPENCLAW_STATE_DIR !== undefined) { process.env.OPENCLAW_STATE_DIR = originalEnv.OPENCLAW_STATE_DIR; - else delete process.env.OPENCLAW_STATE_DIR; + } else { + delete process.env.OPENCLAW_STATE_DIR; + } - if (originalEnv.OPENCLAW_CONFIG_PATH !== undefined) + if (originalEnv.OPENCLAW_CONFIG_PATH !== undefined) { process.env.OPENCLAW_CONFIG_PATH = originalEnv.OPENCLAW_CONFIG_PATH; - else delete process.env.OPENCLAW_CONFIG_PATH; + } else { + delete process.env.OPENCLAW_CONFIG_PATH; + } - if (originalEnv.OPENCLAW_GATEWAY_PORT !== undefined) + if (originalEnv.OPENCLAW_GATEWAY_PORT !== undefined) { process.env.OPENCLAW_GATEWAY_PORT = originalEnv.OPENCLAW_GATEWAY_PORT; - else delete process.env.OPENCLAW_GATEWAY_PORT; + } else { + delete process.env.OPENCLAW_GATEWAY_PORT; + } - if (originalEnv.OPENCLAW_PROFILE !== undefined) + if (originalEnv.OPENCLAW_PROFILE !== undefined) { process.env.OPENCLAW_PROFILE = originalEnv.OPENCLAW_PROFILE; - else delete process.env.OPENCLAW_PROFILE; + } else { + delete process.env.OPENCLAW_PROFILE; + } }); it("probes gateway status by default", async () => { diff --git a/src/cli/daemon-cli/install.ts b/src/cli/daemon-cli/install.ts index 55f9f128cd..54c9c5a394 100644 --- a/src/cli/daemon-cli/install.ts +++ b/src/cli/daemon-cli/install.ts @@ -30,7 +30,9 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) { hints?: string[]; warnings?: string[]; }) => { - if (!json) return; + if (!json) { + return; + } emitDaemonActionJson({ action: "install", ...payload }); }; const fail = (message: string) => { @@ -97,8 +99,11 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) { token: opts.token || cfg.gateway?.auth?.token || process.env.OPENCLAW_GATEWAY_TOKEN, runtime: runtimeRaw, warn: (message) => { - if (json) warnings.push(message); - else defaultRuntime.log(message); + if (json) { + warnings.push(message); + } else { + defaultRuntime.log(message); + } }, config: cfg, }); diff --git a/src/cli/daemon-cli/lifecycle.ts b/src/cli/daemon-cli/lifecycle.ts index 675d08bfb8..55458b6f23 100644 --- a/src/cli/daemon-cli/lifecycle.ts +++ b/src/cli/daemon-cli/lifecycle.ts @@ -23,12 +23,17 @@ export async function runDaemonUninstall(opts: DaemonLifecycleOptions = {}) { notLoadedText: string; }; }) => { - if (!json) return; + if (!json) { + return; + } emitDaemonActionJson({ action: "uninstall", ...payload }); }; const fail = (message: string) => { - if (json) emit({ ok: false, error: message }); - else defaultRuntime.error(message); + if (json) { + emit({ ok: false, error: message }); + } else { + defaultRuntime.error(message); + } defaultRuntime.exit(1); }; @@ -91,12 +96,17 @@ export async function runDaemonStart(opts: DaemonLifecycleOptions = {}) { notLoadedText: string; }; }) => { - if (!json) return; + if (!json) { + return; + } emitDaemonActionJson({ action: "start", ...payload }); }; const fail = (message: string, hints?: string[]) => { - if (json) emit({ ok: false, error: message, hints }); - else defaultRuntime.error(message); + if (json) { + emit({ ok: false, error: message, hints }); + } else { + defaultRuntime.error(message); + } defaultRuntime.exit(1); }; @@ -167,12 +177,17 @@ export async function runDaemonStop(opts: DaemonLifecycleOptions = {}) { notLoadedText: string; }; }) => { - if (!json) return; + if (!json) { + return; + } emitDaemonActionJson({ action: "stop", ...payload }); }; const fail = (message: string) => { - if (json) emit({ ok: false, error: message }); - else defaultRuntime.error(message); + if (json) { + emit({ ok: false, error: message }); + } else { + defaultRuntime.error(message); + } defaultRuntime.exit(1); }; @@ -237,12 +252,17 @@ export async function runDaemonRestart(opts: DaemonLifecycleOptions = {}): Promi notLoadedText: string; }; }) => { - if (!json) return; + if (!json) { + return; + } emitDaemonActionJson({ action: "restart", ...payload }); }; const fail = (message: string, hints?: string[]) => { - if (json) emit({ ok: false, error: message, hints }); - else defaultRuntime.error(message); + if (json) { + emit({ ok: false, error: message, hints }); + } else { + defaultRuntime.error(message); + } defaultRuntime.exit(1); }; diff --git a/src/cli/daemon-cli/shared.ts b/src/cli/daemon-cli/shared.ts index 566c37f862..7ad647b213 100644 --- a/src/cli/daemon-cli/shared.ts +++ b/src/cli/daemon-cli/shared.ts @@ -8,31 +8,43 @@ import { getResolvedLoggerSettings } from "../../logging.js"; import { formatCliCommand } from "../command-format.js"; export function parsePort(raw: unknown): number | null { - if (raw === undefined || raw === null) return null; + if (raw === undefined || raw === null) { + return null; + } const value = typeof raw === "string" ? raw : typeof raw === "number" || typeof raw === "bigint" ? raw.toString() : null; - if (value === null) return null; + if (value === null) { + return null; + } const parsed = Number.parseInt(value, 10); - if (!Number.isFinite(parsed) || parsed <= 0) return null; + if (!Number.isFinite(parsed) || parsed <= 0) { + return null; + } return parsed; } export function parsePortFromArgs(programArguments: string[] | undefined): number | null { - if (!programArguments?.length) return null; + if (!programArguments?.length) { + return null; + } for (let i = 0; i < programArguments.length; i += 1) { const arg = programArguments[i]; if (arg === "--port") { const next = programArguments[i + 1]; const parsed = parsePort(next); - if (parsed) return parsed; + if (parsed) { + return parsed; + } } if (arg?.startsWith("--port=")) { const parsed = parsePort(arg.split("=", 2)[1]); - if (parsed) return parsed; + if (parsed) { + return parsed; + } } } return null; @@ -46,7 +58,9 @@ export function pickProbeHostForBind( if (bindMode === "custom" && customBindHost?.trim()) { return customBindHost.trim(); } - if (bindMode === "tailnet") return tailnetIPv4 ?? "127.0.0.1"; + if (bindMode === "tailnet") { + return tailnetIPv4 ?? "127.0.0.1"; + } return "127.0.0.1"; } @@ -59,11 +73,15 @@ const SAFE_DAEMON_ENV_KEYS = [ ]; export function filterDaemonEnv(env: Record | undefined): Record { - if (!env) return {}; + if (!env) { + return {}; + } const filtered: Record = {}; for (const key of SAFE_DAEMON_ENV_KEYS) { const value = env[key]; - if (!value?.trim()) continue; + if (!value?.trim()) { + continue; + } filtered[key] = value.trim(); } return filtered; @@ -76,7 +94,9 @@ export function safeDaemonEnv(env: Record | undefined): string[] export function normalizeListenerAddress(raw: string): string { let value = raw.trim(); - if (!value) return value; + if (!value) { + return value; + } value = value.replace(/^TCP\s+/i, ""); value = value.replace(/\s+\(LISTEN\)\s*$/i, ""); return value.trim(); @@ -97,21 +117,35 @@ export function formatRuntimeStatus( } | undefined, ) { - if (!runtime) return null; + if (!runtime) { + return null; + } const status = runtime.status ?? "unknown"; const details: string[] = []; - if (runtime.pid) details.push(`pid ${runtime.pid}`); + if (runtime.pid) { + details.push(`pid ${runtime.pid}`); + } if (runtime.state && runtime.state.toLowerCase() !== status) { details.push(`state ${runtime.state}`); } - if (runtime.subState) details.push(`sub ${runtime.subState}`); + if (runtime.subState) { + details.push(`sub ${runtime.subState}`); + } if (runtime.lastExitStatus !== undefined) { details.push(`last exit ${runtime.lastExitStatus}`); } - if (runtime.lastExitReason) details.push(`reason ${runtime.lastExitReason}`); - if (runtime.lastRunResult) details.push(`last run ${runtime.lastRunResult}`); - if (runtime.lastRunTime) details.push(`last run time ${runtime.lastRunTime}`); - if (runtime.detail) details.push(runtime.detail); + if (runtime.lastExitReason) { + details.push(`reason ${runtime.lastExitReason}`); + } + if (runtime.lastRunResult) { + details.push(`last run ${runtime.lastRunResult}`); + } + if (runtime.lastRunTime) { + details.push(`last run time ${runtime.lastRunTime}`); + } + if (runtime.detail) { + details.push(runtime.detail); + } return details.length > 0 ? `${status} (${details.join(", ")})` : status; } @@ -119,7 +153,9 @@ export function renderRuntimeHints( runtime: { missingUnit?: boolean; status?: string } | undefined, env: NodeJS.ProcessEnv = process.env, ): string[] { - if (!runtime) return []; + if (!runtime) { + return []; + } const hints: string[] = []; const fileLog = (() => { try { @@ -130,11 +166,15 @@ export function renderRuntimeHints( })(); if (runtime.missingUnit) { hints.push(`Service not installed. Run: ${formatCliCommand("openclaw gateway install", env)}`); - if (fileLog) hints.push(`File logs: ${fileLog}`); + if (fileLog) { + hints.push(`File logs: ${fileLog}`); + } return hints; } if (runtime.status === "stopped") { - if (fileLog) hints.push(`File logs: ${fileLog}`); + if (fileLog) { + hints.push(`File logs: ${fileLog}`); + } if (process.platform === "darwin") { const logs = resolveGatewayLogPaths(env); hints.push(`Launchd stdout (if installed): ${logs.stdoutPath}`); diff --git a/src/cli/daemon-cli/status.gather.ts b/src/cli/daemon-cli/status.gather.ts index 7b04c0b301..afa39143ec 100644 --- a/src/cli/daemon-cli/status.gather.ts +++ b/src/cli/daemon-cli/status.gather.ts @@ -96,8 +96,12 @@ export type DaemonStatus = { }; function shouldReportPortUsage(status: PortUsageStatus | undefined, rpcOk?: boolean) { - if (status !== "busy") return false; - if (rpcOk === true) return false; + if (status !== "busy") { + return false; + } + if (rpcOk === true) { + return false; + } return true; } @@ -271,7 +275,9 @@ export async function gatherDaemonStatus( } export function renderPortDiagnosticsForCli(status: DaemonStatus, rpcOk?: boolean): string[] { - if (!status.port || !shouldReportPortUsage(status.port.status, rpcOk)) return []; + if (!status.port || !shouldReportPortUsage(status.port.status, rpcOk)) { + return []; + } return formatPortDiagnostics({ port: status.port.port, status: status.port.status, diff --git a/src/cli/daemon-cli/status.print.ts b/src/cli/daemon-cli/status.print.ts index ca6d1d4401..24249ab1dc 100644 --- a/src/cli/daemon-cli/status.print.ts +++ b/src/cli/daemon-cli/status.print.ts @@ -29,7 +29,9 @@ import { function sanitizeDaemonStatusForJson(status: DaemonStatus): DaemonStatus { const command = status.service.command; - if (!command?.environment) return status; + if (!command?.environment) { + return status; + } const safeEnv = filterDaemonEnv(command.environment); const nextCommand = { ...command, @@ -189,7 +191,9 @@ export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean }) defaultRuntime.log(`${label("RPC probe:")} ${okText("ok")}`); } else { defaultRuntime.error(`${label("RPC probe:")} ${errorText("failed")}`); - if (rpc.url) defaultRuntime.error(`${label("RPC target:")} ${rpc.url}`); + if (rpc.url) { + defaultRuntime.error(`${label("RPC target:")} ${rpc.url}`); + } const lines = String(rpc.error ?? "unknown") .split(/\r?\n/) .filter(Boolean); diff --git a/src/cli/devices-cli.ts b/src/cli/devices-cli.ts index 97ce97695a..3633774a78 100644 --- a/src/cli/devices-cli.ts +++ b/src/cli/devices-cli.ts @@ -52,11 +52,17 @@ type DevicePairingList = { function formatAge(msAgo: number) { const s = Math.max(0, Math.floor(msAgo / 1000)); - if (s < 60) return `${s}s`; + if (s < 60) { + return `${s}s`; + } const m = Math.floor(s / 60); - if (m < 60) return `${m}m`; + if (m < 60) { + return `${m}m`; + } const h = Math.floor(m / 60); - if (h < 24) return `${h}h`; + if (h < 24) { + return `${h}h`; + } const d = Math.floor(h / 24); return `${d}d`; } @@ -98,7 +104,9 @@ function parseDevicePairingList(value: unknown): DevicePairingList { } function formatTokenSummary(tokens: DeviceTokenSummary[] | undefined) { - if (!tokens || tokens.length === 0) return "none"; + if (!tokens || tokens.length === 0) { + return "none"; + } const parts = tokens .map((t) => `${t.role}${t.revokedAtMs ? " (revoked)" : ""}`) .toSorted((a, b) => a.localeCompare(b)); diff --git a/src/cli/directory-cli.ts b/src/cli/directory-cli.ts index 1ad7907d2e..5bb9bad9ce 100644 --- a/src/cli/directory-cli.ts +++ b/src/cli/directory-cli.ts @@ -12,14 +12,22 @@ import { renderTable } from "../terminal/table.js"; function parseLimit(value: unknown): number | null { if (typeof value === "number" && Number.isFinite(value)) { - if (value <= 0) return null; + if (value <= 0) { + return null; + } return Math.floor(value); } - if (typeof value !== "string") return null; + if (typeof value !== "string") { + return null; + } const raw = value.trim(); - if (!raw) return null; + if (!raw) { + return null; + } const parsed = Number.parseInt(raw, 10); - if (!Number.isFinite(parsed) || parsed <= 0) return null; + if (!Number.isFinite(parsed) || parsed <= 0) { + return null; + } return parsed; } @@ -60,7 +68,9 @@ export function registerDirectoryCli(program: Command) { }); const channelId = selection.channel; const plugin = getChannelPlugin(channelId); - if (!plugin) throw new Error(`Unsupported channel: ${String(channelId)}`); + if (!plugin) { + throw new Error(`Unsupported channel: ${String(channelId)}`); + } const accountId = opts.account?.trim() || resolveChannelDefaultAccountId({ plugin, cfg }); return { cfg, channelId, accountId, plugin }; }; @@ -73,7 +83,9 @@ export function registerDirectoryCli(program: Command) { account: opts.account as string | undefined, }); const fn = plugin.directory?.self; - if (!fn) throw new Error(`Channel ${channelId} does not support directory self`); + if (!fn) { + throw new Error(`Channel ${channelId} does not support directory self`); + } const result = await fn({ cfg, accountId, runtime: defaultRuntime }); if (opts.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); @@ -113,7 +125,9 @@ export function registerDirectoryCli(program: Command) { account: opts.account as string | undefined, }); const fn = plugin.directory?.listPeers; - if (!fn) throw new Error(`Channel ${channelId} does not support directory peers`); + if (!fn) { + throw new Error(`Channel ${channelId} does not support directory peers`); + } const result = await fn({ cfg, accountId, @@ -158,7 +172,9 @@ export function registerDirectoryCli(program: Command) { account: opts.account as string | undefined, }); const fn = plugin.directory?.listGroups; - if (!fn) throw new Error(`Channel ${channelId} does not support directory groups`); + if (!fn) { + throw new Error(`Channel ${channelId} does not support directory groups`); + } const result = await fn({ cfg, accountId, @@ -206,9 +222,13 @@ export function registerDirectoryCli(program: Command) { account: opts.account as string | undefined, }); const fn = plugin.directory?.listGroupMembers; - if (!fn) throw new Error(`Channel ${channelId} does not support group members listing`); + if (!fn) { + throw new Error(`Channel ${channelId} does not support group members listing`); + } const groupId = String(opts.groupId ?? "").trim(); - if (!groupId) throw new Error("Missing --group-id"); + if (!groupId) { + throw new Error("Missing --group-id"); + } const result = await fn({ cfg, accountId, diff --git a/src/cli/dns-cli.ts b/src/cli/dns-cli.ts index 7cbf3858a3..8de558cdfa 100644 --- a/src/cli/dns-cli.ts +++ b/src/cli/dns-cli.ts @@ -19,7 +19,9 @@ function run(cmd: string, args: string[], opts?: RunOpts): string { encoding: "utf-8", stdio: opts?.inherit ? "inherit" : "pipe", }); - if (res.error) throw res.error; + if (res.error) { + throw res.error; + } if (!opts?.allowFailure && res.status !== 0) { const errText = typeof res.stderr === "string" && res.stderr.trim() @@ -46,7 +48,9 @@ function writeFileSudoIfNeeded(filePath: string, content: string): void { encoding: "utf-8", stdio: ["pipe", "ignore", "inherit"], }); - if (res.error) throw res.error; + if (res.error) { + throw res.error; + } if (res.status !== 0) { throw new Error(`sudo tee ${filePath} failed: exit ${res.status ?? "unknown"}`); } @@ -67,7 +71,9 @@ function mkdirSudoIfNeeded(dirPath: string): void { } function zoneFileNeedsBootstrap(zonePath: string): boolean { - if (!fs.existsSync(zonePath)) return true; + if (!fs.existsSync(zonePath)) { + return true; + } try { const content = fs.readFileSync(zonePath, "utf-8"); return !/\bSOA\b/.test(content) || !/\bNS\b/.test(content); @@ -79,13 +85,17 @@ function zoneFileNeedsBootstrap(zonePath: string): boolean { function detectBrewPrefix(): string { const out = run("brew", ["--prefix"]); const prefix = out.trim(); - if (!prefix) throw new Error("failed to resolve Homebrew prefix"); + if (!prefix) { + throw new Error("failed to resolve Homebrew prefix"); + } return prefix; } function ensureImportLine(corefilePath: string, importGlob: string): boolean { const existing = fs.readFileSync(corefilePath, "utf-8"); - if (existing.includes(importGlob)) return false; + if (existing.includes(importGlob)) { + return false; + } const next = `${existing.replace(/\s*$/, "")}\n\nimport ${importGlob}\n`; writeFileSudoIfNeeded(corefilePath, next); return true; diff --git a/src/cli/exec-approvals-cli.ts b/src/cli/exec-approvals-cli.ts index c27f9b2c3e..725c922d92 100644 --- a/src/cli/exec-approvals-cli.ts +++ b/src/cli/exec-approvals-cli.ts @@ -34,11 +34,17 @@ type ExecApprovalsCliOpts = NodesRpcOpts & { function formatAge(msAgo: number) { const s = Math.max(0, Math.floor(msAgo / 1000)); - if (s < 60) return `${s}s`; + if (s < 60) { + return `${s}s`; + } const m = Math.floor(s / 60); - if (m < 60) return `${m}m`; + if (m < 60) { + return `${m}m`; + } const h = Math.floor(m / 60); - if (h < 24) return `${h}h`; + if (h < 24) { + return `${h}h`; + } const d = Math.floor(h / 24); return `${d}d`; } @@ -52,9 +58,13 @@ async function readStdin(): Promise { } async function resolveTargetNodeId(opts: ExecApprovalsCliOpts): Promise { - if (opts.gateway) return null; + if (opts.gateway) { + return null; + } const raw = opts.node?.trim() ?? ""; - if (!raw) return null; + if (!raw) { + return null; + } return await resolveNodeId(opts as NodesRpcOpts, raw); } @@ -125,7 +135,9 @@ function renderApprovalsSnapshot(snapshot: ExecApprovalsSnapshot, targetLabel: s const allowlist = Array.isArray(agent.allowlist) ? agent.allowlist : []; for (const entry of allowlist) { const pattern = entry?.pattern?.trim() ?? ""; - if (!pattern) continue; + if (!pattern) { + continue; + } const lastUsedAt = typeof entry.lastUsedAt === "number" ? entry.lastUsedAt : null; allowlistRows.push({ Target: targetLabel, diff --git a/src/cli/gateway-cli.coverage.test.ts b/src/cli/gateway-cli.coverage.test.ts index 048dcd2f8e..f7adb39333 100644 --- a/src/cli/gateway-cli.coverage.test.ts +++ b/src/cli/gateway-cli.coverage.test.ts @@ -32,16 +32,22 @@ async function withEnvOverride( const saved: Record = {}; for (const key of Object.keys(overrides)) { saved[key] = process.env[key]; - if (overrides[key] === undefined) delete process.env[key]; - else process.env[key] = overrides[key]; + if (overrides[key] === undefined) { + delete process.env[key]; + } else { + process.env[key] = overrides[key]; + } } vi.resetModules(); try { return await fn(); } finally { for (const key of Object.keys(saved)) { - if (saved[key] === undefined) delete process.env[key]; - else process.env[key] = saved[key]; + if (saved[key] === undefined) { + delete process.env[key]; + } else { + process.env[key] = saved[key]; + } } vi.resetModules(); } @@ -284,10 +290,14 @@ describe("gateway-cli coverage", () => { ), ).rejects.toThrow("__exit__:1"); for (const listener of process.listeners("SIGTERM")) { - if (!beforeSigterm.has(listener)) process.removeListener("SIGTERM", listener); + if (!beforeSigterm.has(listener)) { + process.removeListener("SIGTERM", listener); + } } for (const listener of process.listeners("SIGINT")) { - if (!beforeSigint.has(listener)) process.removeListener("SIGINT", listener); + if (!beforeSigint.has(listener)) { + process.removeListener("SIGINT", listener); + } } }); diff --git a/src/cli/gateway-cli/dev.ts b/src/cli/gateway-cli/dev.ts index 7a01d5bd11..2503c05522 100644 --- a/src/cli/gateway-cli/dev.ts +++ b/src/cli/gateway-cli/dev.ts @@ -21,9 +21,13 @@ const DEV_TEMPLATE_DIR = path.resolve( async function loadDevTemplate(name: string, fallback: string): Promise { try { const raw = await fs.promises.readFile(path.join(DEV_TEMPLATE_DIR, name), "utf-8"); - if (!raw.startsWith("---")) return raw; + if (!raw.startsWith("---")) { + return raw; + } const endIndex = raw.indexOf("\n---", 3); - if (endIndex === -1) return raw; + if (endIndex === -1) { + return raw; + } return raw.slice(endIndex + "\n---".length).replace(/^\s+/, ""); } catch { return fallback; @@ -33,7 +37,9 @@ async function loadDevTemplate(name: string, fallback: string): Promise const resolveDevWorkspaceDir = (env: NodeJS.ProcessEnv = process.env): string => { const baseDir = resolveDefaultAgentWorkspaceDir(env, os.homedir); const profile = env.OPENCLAW_PROFILE?.trim().toLowerCase(); - if (profile === "dev") return baseDir; + if (profile === "dev") { + return baseDir; + } return `${baseDir}-${DEV_AGENT_WORKSPACE_SUFFIX}`; }; @@ -45,7 +51,9 @@ async function writeFileIfMissing(filePath: string, content: string) { }); } catch (err) { const anyErr = err as { code?: string }; - if (anyErr.code !== "EEXIST") throw err; + if (anyErr.code !== "EEXIST") { + throw err; + } } } @@ -92,7 +100,9 @@ export async function ensureDevGatewayConfig(opts: { reset?: boolean }) { const io = createConfigIO(); const configPath = io.configPath; const configExists = fs.existsSync(configPath); - if (!opts.reset && configExists) return; + if (!opts.reset && configExists) { + return; + } await writeConfigFile({ gateway: { diff --git a/src/cli/gateway-cli/discover.ts b/src/cli/gateway-cli/discover.ts index 854ccb484f..51a3b12f7d 100644 --- a/src/cli/gateway-cli/discover.ts +++ b/src/cli/gateway-cli/discover.ts @@ -7,7 +7,9 @@ export type GatewayDiscoverOpts = { }; export function parseDiscoverTimeoutMs(raw: unknown, fallbackMs: number): number { - if (raw === undefined || raw === null) return fallbackMs; + if (raw === undefined || raw === null) { + return fallbackMs; + } const value = typeof raw === "string" ? raw.trim() @@ -17,7 +19,9 @@ export function parseDiscoverTimeoutMs(raw: unknown, fallbackMs: number): number if (value === null) { throw new Error("invalid --timeout"); } - if (!value) return fallbackMs; + if (!value) { + return fallbackMs; + } const parsed = Number.parseInt(value, 10); if (!Number.isFinite(parsed) || parsed <= 0) { throw new Error(`invalid --timeout: ${value}`); @@ -48,7 +52,9 @@ export function dedupeBeacons(beacons: GatewayBonjourBeacon[]): GatewayBonjourBe String(b.port ?? ""), String(b.gatewayPort ?? ""), ].join("|"); - if (seen.has(key)) continue; + if (seen.has(key)) { + continue; + } seen.add(key); out.push(b); } diff --git a/src/cli/gateway-cli/register.ts b/src/cli/gateway-cli/register.ts index f6f08d9128..9d1bd4b4d6 100644 --- a/src/cli/gateway-cli/register.ts +++ b/src/cli/gateway-cli/register.ts @@ -31,9 +31,13 @@ import { import { addGatewayRunCommand } from "./run.js"; function styleHealthChannelLine(line: string, rich: boolean): string { - if (!rich) return line; + if (!rich) { + return line; + } const colon = line.indexOf(":"); - if (colon === -1) return line; + if (colon === -1) { + return line; + } const label = line.slice(0, colon + 1); const detail = line.slice(colon + 1).trimStart(); @@ -42,13 +46,27 @@ function styleHealthChannelLine(line: string, rich: boolean): string { const applyPrefix = (prefix: string, color: (value: string) => string) => `${label} ${color(detail.slice(0, prefix.length))}${detail.slice(prefix.length)}`; - if (normalized.startsWith("failed")) return applyPrefix("failed", theme.error); - if (normalized.startsWith("ok")) return applyPrefix("ok", theme.success); - if (normalized.startsWith("linked")) return applyPrefix("linked", theme.success); - if (normalized.startsWith("configured")) return applyPrefix("configured", theme.success); - if (normalized.startsWith("not linked")) return applyPrefix("not linked", theme.warn); - if (normalized.startsWith("not configured")) return applyPrefix("not configured", theme.muted); - if (normalized.startsWith("unknown")) return applyPrefix("unknown", theme.warn); + if (normalized.startsWith("failed")) { + return applyPrefix("failed", theme.error); + } + if (normalized.startsWith("ok")) { + return applyPrefix("ok", theme.success); + } + if (normalized.startsWith("linked")) { + return applyPrefix("linked", theme.success); + } + if (normalized.startsWith("configured")) { + return applyPrefix("configured", theme.success); + } + if (normalized.startsWith("not linked")) { + return applyPrefix("not linked", theme.warn); + } + if (normalized.startsWith("not configured")) { + return applyPrefix("not configured", theme.muted); + } + if (normalized.startsWith("unknown")) { + return applyPrefix("unknown", theme.warn); + } return line; } @@ -62,10 +80,14 @@ function runGatewayCommand(action: () => Promise, label?: string) { } function parseDaysOption(raw: unknown, fallback = 30): number { - if (typeof raw === "number" && Number.isFinite(raw)) return Math.max(1, Math.floor(raw)); + if (typeof raw === "number" && Number.isFinite(raw)) { + return Math.max(1, Math.floor(raw)); + } if (typeof raw === "string" && raw.trim() !== "") { const parsed = Number(raw); - if (Number.isFinite(parsed)) return Math.max(1, Math.floor(parsed)); + if (Number.isFinite(parsed)) { + return Math.max(1, Math.floor(parsed)); + } } return fallback; } @@ -324,7 +346,9 @@ export function registerGatewayCli(program: Command) { `Found ${deduped.length} gateway(s) · domains: ${domains.join(", ")}`, ), ); - if (deduped.length === 0) return; + if (deduped.length === 0) { + return; + } for (const beacon of deduped) { for (const line of renderBeaconLines(beacon, rich)) { diff --git a/src/cli/gateway-cli/shared.ts b/src/cli/gateway-cli/shared.ts index d476ad98b4..1beef3e206 100644 --- a/src/cli/gateway-cli/shared.ts +++ b/src/cli/gateway-cli/shared.ts @@ -8,30 +8,48 @@ import { defaultRuntime } from "../../runtime.js"; import { formatCliCommand } from "../command-format.js"; export function parsePort(raw: unknown): number | null { - if (raw === undefined || raw === null) return null; + if (raw === undefined || raw === null) { + return null; + } const value = typeof raw === "string" ? raw : typeof raw === "number" || typeof raw === "bigint" ? raw.toString() : null; - if (value === null) return null; + if (value === null) { + return null; + } const parsed = Number.parseInt(value, 10); - if (!Number.isFinite(parsed) || parsed <= 0) return null; + if (!Number.isFinite(parsed) || parsed <= 0) { + return null; + } return parsed; } export const toOptionString = (value: unknown): string | undefined => { - if (typeof value === "string") return value; - if (typeof value === "number" || typeof value === "bigint") return value.toString(); + if (typeof value === "string") { + return value; + } + if (typeof value === "number" || typeof value === "bigint") { + return value.toString(); + } return undefined; }; export function describeUnknownError(err: unknown): string { - if (err instanceof Error) return err.message; - if (typeof err === "string") return err; - if (typeof err === "number" || typeof err === "bigint") return err.toString(); - if (typeof err === "boolean") return err ? "true" : "false"; + if (err instanceof Error) { + return err.message; + } + if (typeof err === "string") { + return err; + } + if (typeof err === "number" || typeof err === "bigint") { + return err.toString(); + } + if (typeof err === "boolean") { + return err ? "true" : "false"; + } if (err && typeof err === "object") { if ("message" in err && typeof err.message === "string") { return err.message; @@ -94,7 +112,9 @@ export async function maybeExplainGatewayServiceStop() { } catch { loaded = null; } - if (loaded === false) return; + if (loaded === false) { + return; + } defaultRuntime.error( loaded ? `Gateway service appears ${service.loadedText}. Stop it first.` diff --git a/src/cli/gateway.sigterm.test.ts b/src/cli/gateway.sigterm.test.ts index 6220901ab7..56d452521e 100644 --- a/src/cli/gateway.sigterm.test.ts +++ b/src/cli/gateway.sigterm.test.ts @@ -67,7 +67,9 @@ describe("gateway SIGTERM", () => { let child: ReturnType | null = null; afterEach(() => { - if (!child || child.killed) return; + if (!child || child.killed) { + return; + } try { child.kill("SIGKILL"); } catch { @@ -124,7 +126,9 @@ describe("gateway SIGTERM", () => { }); const proc = child; - if (!proc) throw new Error("failed to spawn gateway"); + if (!proc) { + throw new Error("failed to spawn gateway"); + } child.stdout?.setEncoding("utf8"); child.stderr?.setEncoding("utf8"); @@ -148,7 +152,9 @@ describe("gateway SIGTERM", () => { `--- stdout ---\n${stdout}\n--- stderr ---\n${stderr}`, ); } - if (result.code === null && result.signal === "SIGTERM") return; + if (result.code === null && result.signal === "SIGTERM") { + return; + } expect(result.signal).toBeNull(); }); }); diff --git a/src/cli/help-format.ts b/src/cli/help-format.ts index 20f2da7334..1a58ea1dc0 100644 --- a/src/cli/help-format.ts +++ b/src/cli/help-format.ts @@ -7,7 +7,9 @@ export function formatHelpExample(command: string, description: string): string } export function formatHelpExampleLine(command: string, description: string): string { - if (!description) return ` ${theme.command(command)}`; + if (!description) { + return ` ${theme.command(command)}`; + } return ` ${theme.command(command)} ${theme.muted(`# ${description}`)}`; } diff --git a/src/cli/hooks-cli.ts b/src/cli/hooks-cli.ts index b88a2ad1b0..aa5c25f7f4 100644 --- a/src/cli/hooks-cli.ts +++ b/src/cli/hooks-cli.ts @@ -67,8 +67,12 @@ function buildHooksReport(config: OpenClawConfig): HookStatusReport { } function formatHookStatus(hook: HookStatusEntry): string { - if (hook.eligible) return theme.success("✓ ready"); - if (hook.disabled) return theme.warn("⏸ disabled"); + if (hook.eligible) { + return theme.success("✓ ready"); + } + if (hook.disabled) { + return theme.warn("⏸ disabled"); + } return theme.error("✗ missing"); } @@ -78,7 +82,9 @@ function formatHookName(hook: HookStatusEntry): string { } function formatHookSource(hook: HookStatusEntry): string { - if (!hook.managedByPlugin) return hook.source; + if (!hook.managedByPlugin) { + return hook.source; + } return `plugin:${hook.pluginId ?? "unknown"}`; } @@ -326,13 +332,24 @@ export function formatHooksCheck(report: HookStatusReport, opts: HooksCheckOptio lines.push(theme.heading("Hooks not ready:")); for (const hook of notEligible) { const reasons = []; - if (hook.disabled) reasons.push("disabled"); - if (hook.missing.bins.length > 0) reasons.push(`bins: ${hook.missing.bins.join(", ")}`); - if (hook.missing.anyBins.length > 0) + if (hook.disabled) { + reasons.push("disabled"); + } + if (hook.missing.bins.length > 0) { + reasons.push(`bins: ${hook.missing.bins.join(", ")}`); + } + if (hook.missing.anyBins.length > 0) { reasons.push(`anyBins: ${hook.missing.anyBins.join(", ")}`); - if (hook.missing.env.length > 0) reasons.push(`env: ${hook.missing.env.join(", ")}`); - if (hook.missing.config.length > 0) reasons.push(`config: ${hook.missing.config.join(", ")}`); - if (hook.missing.os.length > 0) reasons.push(`os: ${hook.missing.os.join(", ")}`); + } + if (hook.missing.env.length > 0) { + reasons.push(`env: ${hook.missing.env.join(", ")}`); + } + if (hook.missing.config.length > 0) { + reasons.push(`config: ${hook.missing.config.join(", ")}`); + } + if (hook.missing.os.length > 0) { + reasons.push(`os: ${hook.missing.os.join(", ")}`); + } lines.push(` ${hook.emoji ?? "🔗"} ${hook.name} - ${reasons.join("; ")}`); } } diff --git a/src/cli/logs-cli.ts b/src/cli/logs-cli.ts index 061db31fc7..b29e1b822a 100644 --- a/src/cli/logs-cli.ts +++ b/src/cli/logs-cli.ts @@ -33,7 +33,9 @@ type LogsCliOptions = { }; function parsePositiveInt(value: string | undefined, fallback: number): number { - if (!value) return fallback; + if (!value) { + return fallback; + } const parsed = Number.parseInt(value, 10); return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback; } @@ -58,10 +60,16 @@ async function fetchLogs( } function formatLogTimestamp(value?: string, mode: "pretty" | "plain" = "plain") { - if (!value) return ""; + if (!value) { + return ""; + } const parsed = new Date(value); - if (Number.isNaN(parsed.getTime())) return value; - if (mode === "pretty") return parsed.toISOString().slice(11, 19); + if (Number.isNaN(parsed.getTime())) { + return value; + } + if (mode === "pretty") { + return parsed.toISOString().slice(11, 19); + } return parsed.toISOString(); } @@ -73,7 +81,9 @@ function formatLogLine( }, ): string { const parsed = parseLogLine(raw); - if (!parsed) return raw; + if (!parsed) { + return raw; + } const label = parsed.subsystem ?? parsed.module ?? ""; const time = formatLogTimestamp(parsed.time, opts.pretty ? "pretty" : "plain"); const level = parsed.level ?? ""; @@ -162,8 +172,12 @@ function emitGatewayError( return; } - if (!errorLine(colorize(rich, theme.error, message))) return; - if (!errorLine(details.message)) return; + if (!errorLine(colorize(rich, theme.error, message))) { + return; + } + if (!errorLine(details.message)) { + return; + } errorLine(colorize(rich, theme.muted, hint)); } @@ -288,7 +302,9 @@ export function registerLogsCli(program: Command) { : cursor; first = false; - if (!opts.follow) return; + if (!opts.follow) { + return; + } await delay(interval); } }); diff --git a/src/cli/memory-cli.ts b/src/cli/memory-cli.ts index b34da11a33..38b45e0794 100644 --- a/src/cli/memory-cli.ts +++ b/src/cli/memory-cli.ts @@ -60,13 +60,17 @@ function formatSourceLabel(source: string, workspaceDir: string, agentId: string function resolveAgent(cfg: ReturnType, agent?: string) { const trimmed = agent?.trim(); - if (trimmed) return trimmed; + if (trimmed) { + return trimmed; + } return resolveDefaultAgentId(cfg); } function resolveAgentIds(cfg: ReturnType, agent?: string): string[] { const trimmed = agent?.trim(); - if (trimmed) return [trimmed]; + if (trimmed) { + return [trimmed]; + } const list = cfg.agents?.list ?? []; if (list.length > 0) { return list.map((entry) => entry.id).filter(Boolean); @@ -84,7 +88,9 @@ async function checkReadableFile(pathname: string): Promise<{ exists: boolean; i return { exists: true }; } catch (err) { const code = (err as NodeJS.ErrnoException).code; - if (code === "ENOENT") return { exists: false }; + if (code === "ENOENT") { + return { exists: false }; + } return { exists: true, issue: `${shortenHomePath(pathname)} not readable (${code ?? "error"})`, @@ -125,16 +131,24 @@ async function scanMemoryFiles( const primary = await checkReadableFile(memoryFile); const alt = await checkReadableFile(altMemoryFile); - if (primary.issue) issues.push(primary.issue); - if (alt.issue) issues.push(alt.issue); + if (primary.issue) { + issues.push(primary.issue); + } + if (alt.issue) { + issues.push(alt.issue); + } const resolvedExtraPaths = normalizeExtraMemoryPaths(workspaceDir, extraPaths); for (const extraPath of resolvedExtraPaths) { try { const stat = await fs.lstat(extraPath); - if (stat.isSymbolicLink()) continue; + if (stat.isSymbolicLink()) { + continue; + } const extraCheck = await checkReadableFile(extraPath); - if (extraCheck.issue) issues.push(extraCheck.issue); + if (extraCheck.issue) { + issues.push(extraCheck.issue); + } } catch (err) { const code = (err as NodeJS.ErrnoException).code; if (code === "ENOENT") { @@ -185,8 +199,12 @@ async function scanMemoryFiles( } else { const files = new Set(listedOk ? listed : []); if (!listedOk) { - if (primary.exists) files.add(memoryFile); - if (alt.exists) files.add(altMemoryFile); + if (primary.exists) { + files.add(memoryFile); + } + if (alt.exists) { + files.add(altMemoryFile); + } } totalFiles = files.size; } @@ -274,7 +292,9 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) { total: syncUpdate.total, label: syncUpdate.label, }); - if (syncUpdate.label) progress.setLabel(syncUpdate.label); + if (syncUpdate.label) { + progress.setLabel(syncUpdate.label); + } }, }); } catch (err) { @@ -532,10 +552,14 @@ export function registerMemoryCli(program: Command) { return `${minutes}:${String(remainingSeconds).padStart(2, "0")}`; }; const formatEta = () => { - if (lastTotal <= 0 || lastCompleted <= 0) return null; + if (lastTotal <= 0 || lastCompleted <= 0) { + return null; + } const elapsedMs = Math.max(1, Date.now() - startedAt); const rate = lastCompleted / elapsedMs; - if (!Number.isFinite(rate) || rate <= 0) return null; + if (!Number.isFinite(rate) || rate <= 0) { + return null; + } const remainingMs = Math.max(0, (lastTotal - lastCompleted) / rate); const seconds = Math.floor(remainingMs / 1000); const minutes = Math.floor(seconds / 60); @@ -564,7 +588,9 @@ export function registerMemoryCli(program: Command) { reason: "cli", force: opts.force, progress: (syncUpdate) => { - if (syncUpdate.label) lastLabel = syncUpdate.label; + if (syncUpdate.label) { + lastLabel = syncUpdate.label; + } lastCompleted = syncUpdate.completed; lastTotal = syncUpdate.total; update({ diff --git a/src/cli/node-cli/daemon.ts b/src/cli/node-cli/daemon.ts index 7326446e65..db5bf394f9 100644 --- a/src/cli/node-cli/daemon.ts +++ b/src/cli/node-cli/daemon.ts @@ -113,7 +113,9 @@ export async function runNodeDaemonInstall(opts: NodeDaemonInstallOptions) { hints?: string[]; warnings?: string[]; }) => { - if (!json) return; + if (!json) { + return; + } emitDaemonActionJson({ action: "install", ...payload }); }; const fail = (message: string, hints?: string[]) => { @@ -127,7 +129,9 @@ export async function runNodeDaemonInstall(opts: NodeDaemonInstallOptions) { } else { defaultRuntime.error(message); if (hints?.length) { - for (const hint of hints) defaultRuntime.log(`Tip: ${hint}`); + for (const hint of hints) { + defaultRuntime.log(`Tip: ${hint}`); + } } } defaultRuntime.exit(1); @@ -187,8 +191,11 @@ export async function runNodeDaemonInstall(opts: NodeDaemonInstallOptions) { displayName: opts.displayName, runtime: runtimeRaw, warn: (message) => { - if (json) warnings.push(message); - else defaultRuntime.log(message); + if (json) { + warnings.push(message); + } else { + defaultRuntime.log(message); + } }, }); @@ -235,12 +242,17 @@ export async function runNodeDaemonUninstall(opts: NodeDaemonLifecycleOptions = notLoadedText: string; }; }) => { - if (!json) return; + if (!json) { + return; + } emitDaemonActionJson({ action: "uninstall", ...payload }); }; const fail = (message: string) => { - if (json) emit({ ok: false, error: message }); - else defaultRuntime.error(message); + if (json) { + emit({ ok: false, error: message }); + } else { + defaultRuntime.error(message); + } defaultRuntime.exit(1); }; @@ -286,12 +298,17 @@ export async function runNodeDaemonStart(opts: NodeDaemonLifecycleOptions = {}) notLoadedText: string; }; }) => { - if (!json) return; + if (!json) { + return; + } emitDaemonActionJson({ action: "start", ...payload }); }; const fail = (message: string, hints?: string[]) => { - if (json) emit({ ok: false, error: message, hints }); - else defaultRuntime.error(message); + if (json) { + emit({ ok: false, error: message, hints }); + } else { + defaultRuntime.error(message); + } defaultRuntime.exit(1); }; @@ -363,12 +380,17 @@ export async function runNodeDaemonRestart(opts: NodeDaemonLifecycleOptions = {} notLoadedText: string; }; }) => { - if (!json) return; + if (!json) { + return; + } emitDaemonActionJson({ action: "restart", ...payload }); }; const fail = (message: string, hints?: string[]) => { - if (json) emit({ ok: false, error: message, hints }); - else defaultRuntime.error(message); + if (json) { + emit({ ok: false, error: message, hints }); + } else { + defaultRuntime.error(message); + } defaultRuntime.exit(1); }; @@ -439,12 +461,17 @@ export async function runNodeDaemonStop(opts: NodeDaemonLifecycleOptions = {}) { notLoadedText: string; }; }) => { - if (!json) return; + if (!json) { + return; + } emitDaemonActionJson({ action: "stop", ...payload }); }; const fail = (message: string) => { - if (json) emit({ ok: false, error: message }); - else defaultRuntime.error(message); + if (json) { + emit({ ok: false, error: message }); + } else { + defaultRuntime.error(message); + } defaultRuntime.exit(1); }; diff --git a/src/cli/nodes-cli/a2ui-jsonl.ts b/src/cli/nodes-cli/a2ui-jsonl.ts index 8748882565..80c73e567d 100644 --- a/src/cli/nodes-cli/a2ui-jsonl.ts +++ b/src/cli/nodes-cli/a2ui-jsonl.ts @@ -44,7 +44,9 @@ export function validateA2UIJsonl(jsonl: string) { lines.forEach((line, idx) => { const trimmed = line.trim(); - if (!trimmed) return; + if (!trimmed) { + return; + } messageCount += 1; let obj: unknown; try { diff --git a/src/cli/nodes-cli/cli-utils.ts b/src/cli/nodes-cli/cli-utils.ts index 5d4bef1a8a..38d9498510 100644 --- a/src/cli/nodes-cli/cli-utils.ts +++ b/src/cli/nodes-cli/cli-utils.ts @@ -22,7 +22,9 @@ export function runNodesCommand(label: string, action: () => Promise) { const { error, warn } = getNodesTheme(); defaultRuntime.error(error(`nodes ${label} failed: ${message}`)); const hint = unauthorizedHintForMessage(message); - if (hint) defaultRuntime.error(warn(hint)); + if (hint) { + defaultRuntime.error(warn(hint)); + } defaultRuntime.exit(1); }); } diff --git a/src/cli/nodes-cli/format.ts b/src/cli/nodes-cli/format.ts index 236a78c2c9..f4dc94fc88 100644 --- a/src/cli/nodes-cli/format.ts +++ b/src/cli/nodes-cli/format.ts @@ -2,11 +2,17 @@ import type { NodeListNode, PairedNode, PairingList, PendingRequest } from "./ty export function formatAge(msAgo: number) { const s = Math.max(0, Math.floor(msAgo / 1000)); - if (s < 60) return `${s}s`; + if (s < 60) { + return `${s}s`; + } const m = Math.floor(s / 60); - if (m < 60) return `${m}m`; + if (m < 60) { + return `${m}m`; + } const h = Math.floor(m / 60); - if (h < 24) return `${h}h`; + if (h < 24) { + return `${h}h`; + } const d = Math.floor(h / 24); return `${d}d`; } @@ -24,12 +30,16 @@ export function parseNodeList(value: unknown): NodeListNode[] { } export function formatPermissions(raw: unknown) { - if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null; + if (!raw || typeof raw !== "object" || Array.isArray(raw)) { + return null; + } const entries = Object.entries(raw as Record) .map(([key, value]) => [String(key).trim(), value === true] as const) .filter(([key]) => key.length > 0) .toSorted((a, b) => a[0].localeCompare(b[0])); - if (entries.length === 0) return null; + if (entries.length === 0) { + return null; + } const parts = entries.map(([key, granted]) => `${key}=${granted ? "yes" : "no"}`); return `[${parts.join(", ")}]`; } diff --git a/src/cli/nodes-cli/register.camera.ts b/src/cli/nodes-cli/register.camera.ts index b5fb1b8104..58ca330eb7 100644 --- a/src/cli/nodes-cli/register.camera.ts +++ b/src/cli/nodes-cli/register.camera.ts @@ -19,7 +19,9 @@ const parseFacing = (value: string): CameraFacing => { const v = String(value ?? "") .trim() .toLowerCase(); - if (v === "front" || v === "back") return v; + if (v === "front" || v === "back") { + return v; + } throw new Error(`invalid facing: ${value} (expected front|back)`); }; diff --git a/src/cli/nodes-cli/register.canvas.ts b/src/cli/nodes-cli/register.canvas.ts index eb6ec2a126..5a328c23e5 100644 --- a/src/cli/nodes-cli/register.canvas.ts +++ b/src/cli/nodes-cli/register.canvas.ts @@ -112,7 +112,9 @@ export function registerNodesCanvasCommands(nodes: Command) { height: opts.height ? Number.parseFloat(opts.height) : undefined, }; const params: Record = {}; - if (opts.target) params.url = String(opts.target); + if (opts.target) { + params.url = String(opts.target); + } if ( Number.isFinite(placement.x) || Number.isFinite(placement.y) || @@ -176,7 +178,9 @@ export function registerNodesCanvasCommands(nodes: Command) { .action(async (jsArg: string | undefined, opts: NodesRpcOpts) => { await runNodesCommand("canvas eval", async () => { const js = opts.js ?? jsArg; - if (!js) throw new Error("missing --js or "); + if (!js) { + throw new Error("missing --js or "); + } const raw = await invokeCanvas(opts, "canvas.eval", { javaScript: js, }); @@ -188,8 +192,9 @@ export function registerNodesCanvasCommands(nodes: Command) { typeof raw === "object" && raw !== null ? (raw as { payload?: { result?: string } }).payload : undefined; - if (payload?.result) defaultRuntime.log(payload.result); - else { + if (payload?.result) { + defaultRuntime.log(payload.result); + } else { const { ok } = getNodesTheme(); defaultRuntime.log(ok("canvas eval ok")); } diff --git a/src/cli/nodes-cli/register.invoke.ts b/src/cli/nodes-cli/register.invoke.ts index 013610d6c8..2f75bff35c 100644 --- a/src/cli/nodes-cli/register.invoke.ts +++ b/src/cli/nodes-cli/register.invoke.ts @@ -58,7 +58,9 @@ function normalizeExecAsk(value?: string | null): ExecAsk | null { } function mergePathPrepend(existing: string | undefined, prepend: string[]) { - if (prepend.length === 0) return existing; + if (prepend.length === 0) { + return existing; + } const partsExisting = (existing ?? "") .split(path.delimiter) .map((part) => part.trim()) @@ -66,7 +68,9 @@ function mergePathPrepend(existing: string | undefined, prepend: string[]) { const merged: string[] = []; const seen = new Set(); for (const part of [...prepend, ...partsExisting]) { - if (seen.has(part)) continue; + if (seen.has(part)) { + continue; + } seen.add(part); merged.push(part); } @@ -78,10 +82,16 @@ function applyPathPrepend( prepend: string[] | undefined, options?: { requireExisting?: boolean }, ) { - if (!Array.isArray(prepend) || prepend.length === 0) return; - if (options?.requireExisting && !env.PATH) return; + if (!Array.isArray(prepend) || prepend.length === 0) { + return; + } + if (options?.requireExisting && !env.PATH) { + return; + } const merged = mergePathPrepend(env.PATH, prepend); - if (merged) env.PATH = merged; + if (merged) { + env.PATH = merged; + } } function resolveExecDefaults( @@ -341,8 +351,12 @@ export function registerNodesInvokeCommands(nodes: Command) { const timedOut = payload?.timedOut === true; const success = payload?.success === true; - if (stdout) process.stdout.write(stdout); - if (stderr) process.stderr.write(stderr); + if (stdout) { + process.stdout.write(stdout); + } + if (stderr) { + process.stderr.write(stderr); + } if (timedOut) { const { error } = getNodesTheme(); defaultRuntime.error(error("run timed out")); diff --git a/src/cli/nodes-cli/register.status.ts b/src/cli/nodes-cli/register.status.ts index 9c6a6dab99..8642a60f29 100644 --- a/src/cli/nodes-cli/register.status.ts +++ b/src/cli/nodes-cli/register.status.ts @@ -10,8 +10,12 @@ import { shortenHomeInString } from "../../utils.js"; function formatVersionLabel(raw: string) { const trimmed = raw.trim(); - if (!trimmed) return raw; - if (trimmed.toLowerCase().startsWith("v")) return trimmed; + if (!trimmed) { + return raw; + } + if (trimmed.toLowerCase().startsWith("v")) { + return trimmed; + } return /^\d/.test(trimmed) ? `v${trimmed}` : trimmed; } @@ -23,9 +27,13 @@ function resolveNodeVersions(node: { }) { const core = node.coreVersion?.trim() || undefined; const ui = node.uiVersion?.trim() || undefined; - if (core || ui) return { core, ui }; + if (core || ui) { + return { core, ui }; + } const legacy = node.version?.trim(); - if (!legacy) return { core: undefined, ui: undefined }; + if (!legacy) { + return { core: undefined, ui: undefined }; + } const platform = node.platform?.trim().toLowerCase() ?? ""; const headless = platform === "darwin" || platform === "linux" || platform === "win32" || platform === "windows"; @@ -40,15 +48,23 @@ function formatNodeVersions(node: { }) { const { core, ui } = resolveNodeVersions(node); const parts: string[] = []; - if (core) parts.push(`core ${formatVersionLabel(core)}`); - if (ui) parts.push(`ui ${formatVersionLabel(ui)}`); + if (core) { + parts.push(`core ${formatVersionLabel(core)}`); + } + if (ui) { + parts.push(`ui ${formatVersionLabel(ui)}`); + } return parts.length > 0 ? parts.join(" · ") : null; } function formatPathEnv(raw?: string): string | null { - if (typeof raw !== "string") return null; + if (typeof raw !== "string") { + return null; + } const trimmed = raw.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const parts = trimmed.split(":").filter(Boolean); const display = parts.length <= 3 ? trimmed : `${parts.slice(0, 2).join(":")}:…:${parts.slice(-1)[0]}`; @@ -56,7 +72,9 @@ function formatPathEnv(raw?: string): string | null { } function parseSinceMs(raw: unknown, label: string): number | undefined { - if (raw === undefined || raw === null) return undefined; + if (raw === undefined || raw === null) { + return undefined; + } const value = typeof raw === "string" ? raw.trim() : typeof raw === "number" ? String(raw).trim() : null; if (value === null) { @@ -64,7 +82,9 @@ function parseSinceMs(raw: unknown, label: string): number | undefined { defaultRuntime.exit(1); return undefined; } - if (!value) return undefined; + if (!value) { + return undefined; + } try { return parseDurationMs(value); } catch (err) { @@ -104,7 +124,9 @@ export function registerNodesStatusCommands(nodes: Command) { ) : null; const filtered = nodes.filter((n) => { - if (connectedOnly && !n.connected) return false; + if (connectedOnly && !n.connected) { + return false; + } if (sinceMs !== undefined) { const paired = lastConnectedById?.get(n.nodeId); const lastConnectedAtMs = @@ -113,8 +135,12 @@ export function registerNodesStatusCommands(nodes: Command) { : typeof n.connectedAtMs === "number" ? n.connectedAtMs : undefined; - if (typeof lastConnectedAtMs !== "number") return false; - if (now - lastConnectedAtMs > sinceMs) return false; + if (typeof lastConnectedAtMs !== "number") { + return false; + } + if (now - lastConnectedAtMs > sinceMs) { + return false; + } } return true; }); @@ -131,7 +157,9 @@ export function registerNodesStatusCommands(nodes: Command) { defaultRuntime.log( `Known: ${filtered.length}${filteredLabel} · Paired: ${pairedCount} · Connected: ${connectedCount}`, ); - if (filtered.length === 0) return; + if (filtered.length === 0) { + return; + } const rows = filtered.map((n) => { const name = n.displayName?.trim() ? n.displayName.trim() : n.nodeId; @@ -261,7 +289,9 @@ export function registerNodesStatusCommands(nodes: Command) { defaultRuntime.log(muted("- (none reported)")); return; } - for (const c of commands) defaultRuntime.log(`- ${c}`); + for (const c of commands) { + defaultRuntime.log(`- ${c}`); + } }); }), ); @@ -294,7 +324,9 @@ export function registerNodesStatusCommands(nodes: Command) { const filteredPaired = paired.filter((node) => { if (connectedOnly) { const live = connectedById?.get(node.nodeId); - if (!live?.connected) return false; + if (!live?.connected) { + return false; + } } if (sinceMs !== undefined) { const live = connectedById?.get(node.nodeId); @@ -304,8 +336,12 @@ export function registerNodesStatusCommands(nodes: Command) { : typeof live?.connectedAtMs === "number" ? live.connectedAtMs : undefined; - if (typeof lastConnectedAtMs !== "number") return false; - if (now - lastConnectedAtMs > sinceMs) return false; + if (typeof lastConnectedAtMs !== "number") { + return false; + } + if (now - lastConnectedAtMs > sinceMs) { + return false; + } } return true; }); diff --git a/src/cli/nodes-cli/rpc.ts b/src/cli/nodes-cli/rpc.ts index d2807bc482..742e581125 100644 --- a/src/cli/nodes-cli/rpc.ts +++ b/src/cli/nodes-cli/rpc.ts @@ -57,7 +57,9 @@ function normalizeNodeKey(value: string) { export async function resolveNodeId(opts: NodesRpcOpts, query: string) { const q = String(query ?? "").trim(); - if (!q) throw new Error("node required"); + if (!q) { + throw new Error("node required"); + } let nodes: NodeListNode[] = []; try { @@ -77,15 +79,25 @@ export async function resolveNodeId(opts: NodesRpcOpts, query: string) { const qNorm = normalizeNodeKey(q); const matches = nodes.filter((n) => { - if (n.nodeId === q) return true; - if (typeof n.remoteIp === "string" && n.remoteIp === q) return true; + if (n.nodeId === q) { + return true; + } + if (typeof n.remoteIp === "string" && n.remoteIp === q) { + return true; + } const name = typeof n.displayName === "string" ? n.displayName : ""; - if (name && normalizeNodeKey(name) === qNorm) return true; - if (q.length >= 6 && n.nodeId.startsWith(q)) return true; + if (name && normalizeNodeKey(name) === qNorm) { + return true; + } + if (q.length >= 6 && n.nodeId.startsWith(q)) { + return true; + } return false; }); - if (matches.length === 1) return matches[0].nodeId; + if (matches.length === 1) { + return matches[0].nodeId; + } if (matches.length === 0) { const known = nodes .map((n) => n.displayName || n.remoteIp || n.nodeId) diff --git a/src/cli/nodes-run.ts b/src/cli/nodes-run.ts index 15964d9f6e..9df8deee0f 100644 --- a/src/cli/nodes-run.ts +++ b/src/cli/nodes-run.ts @@ -1,14 +1,22 @@ import { parseTimeoutMs } from "./parse-timeout.js"; export function parseEnvPairs(pairs: unknown): Record | undefined { - if (!Array.isArray(pairs) || pairs.length === 0) return undefined; + if (!Array.isArray(pairs) || pairs.length === 0) { + return undefined; + } const env: Record = {}; for (const pair of pairs) { - if (typeof pair !== "string") continue; + if (typeof pair !== "string") { + continue; + } const idx = pair.indexOf("="); - if (idx <= 0) continue; + if (idx <= 0) { + continue; + } const key = pair.slice(0, idx).trim(); - if (!key) continue; + if (!key) { + continue; + } env[key] = pair.slice(idx + 1); } return Object.keys(env).length > 0 ? env : undefined; diff --git a/src/cli/pairing-cli.test.ts b/src/cli/pairing-cli.test.ts index 0ff052d0cb..5efb7f729d 100644 --- a/src/cli/pairing-cli.test.ts +++ b/src/cli/pairing-cli.test.ts @@ -9,9 +9,15 @@ const pairingIdLabels: Record = { discord: "discordUserId", }; const normalizeChannelId = vi.fn((raw: string) => { - if (!raw) return null; - if (raw === "imsg") return "imessage"; - if (["telegram", "discord", "imessage"].includes(raw)) return raw; + if (!raw) { + return null; + } + if (raw === "imsg") { + return "imessage"; + } + if (["telegram", "discord", "imessage"].includes(raw)) { + return raw; + } return null; }); const getPairingAdapter = vi.fn((channel: string) => ({ diff --git a/src/cli/pairing-cli.ts b/src/cli/pairing-cli.ts index 72cec23078..b0362dc1b5 100644 --- a/src/cli/pairing-cli.ts +++ b/src/cli/pairing-cli.ts @@ -25,7 +25,9 @@ function parseChannel(raw: unknown, channels: PairingChannel[]): PairingChannel ) .trim() .toLowerCase(); - if (!value) throw new Error("Channel required"); + if (!value) { + throw new Error("Channel required"); + } const normalized = normalizeChannelId(value); if (normalized) { @@ -36,7 +38,9 @@ function parseChannel(raw: unknown, channels: PairingChannel[]): PairingChannel } // Allow extension channels: validate format but don't require registry - if (/^[a-z][a-z0-9_-]{0,63}$/.test(value)) return value as PairingChannel; + if (/^[a-z][a-z0-9_-]{0,63}$/.test(value)) { + return value as PairingChannel; + } throw new Error(`Invalid channel: ${value}`); } @@ -136,7 +140,9 @@ export function registerPairingCli(program: Command) { `${theme.success("Approved")} ${theme.muted(channel)} sender ${theme.command(approved.id)}.`, ); - if (!opts.notify) return; + if (!opts.notify) { + return; + } await notifyApproved(channel, approved.id).catch((err) => { defaultRuntime.log(theme.warn(`Failed to notify requester: ${String(err)}`)); }); diff --git a/src/cli/parse-duration.ts b/src/cli/parse-duration.ts index 69efb1abef..38e0aedd8c 100644 --- a/src/cli/parse-duration.ts +++ b/src/cli/parse-duration.ts @@ -6,10 +6,14 @@ export function parseDurationMs(raw: string, opts?: DurationMsParseOptions): num const trimmed = String(raw ?? "") .trim() .toLowerCase(); - if (!trimmed) throw new Error("invalid duration (empty)"); + if (!trimmed) { + throw new Error("invalid duration (empty)"); + } const m = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)?$/.exec(trimmed); - if (!m) throw new Error(`invalid duration: ${raw}`); + if (!m) { + throw new Error(`invalid duration: ${raw}`); + } const value = Number(m[1]); if (!Number.isFinite(value) || value < 0) { @@ -28,6 +32,8 @@ export function parseDurationMs(raw: string, opts?: DurationMsParseOptions): num ? 3_600_000 : 86_400_000; const ms = Math.round(value * multiplier); - if (!Number.isFinite(ms)) throw new Error(`invalid duration: ${raw}`); + if (!Number.isFinite(ms)) { + throw new Error(`invalid duration: ${raw}`); + } return ms; } diff --git a/src/cli/parse-timeout.ts b/src/cli/parse-timeout.ts index 19b42e0b56..090559add6 100644 --- a/src/cli/parse-timeout.ts +++ b/src/cli/parse-timeout.ts @@ -1,5 +1,7 @@ export function parseTimeoutMs(raw: unknown): number | undefined { - if (raw === undefined || raw === null) return undefined; + if (raw === undefined || raw === null) { + return undefined; + } let value = Number.NaN; if (typeof raw === "number") { value = raw; @@ -7,7 +9,9 @@ export function parseTimeoutMs(raw: unknown): number | undefined { value = Number(raw); } else if (typeof raw === "string") { const trimmed = raw.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } value = Number.parseInt(trimmed, 10); } return Number.isFinite(value) ? value : undefined; diff --git a/src/cli/plugin-registry.ts b/src/cli/plugin-registry.ts index 37017a3040..ce7af63e15 100644 --- a/src/cli/plugin-registry.ts +++ b/src/cli/plugin-registry.ts @@ -8,7 +8,9 @@ const log = createSubsystemLogger("plugins"); let pluginRegistryLoaded = false; export function ensurePluginRegistryLoaded(): void { - if (pluginRegistryLoaded) return; + if (pluginRegistryLoaded) { + return; + } const config = loadConfig(); const workspaceDir = resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config)); const logger: PluginLogger = { diff --git a/src/cli/plugins-cli.ts b/src/cli/plugins-cli.ts index cf41216b41..87ab458f11 100644 --- a/src/cli/plugins-cli.ts +++ b/src/cli/plugins-cli.ts @@ -58,11 +58,15 @@ function formatPluginLine(plugin: PluginRecord, verbose = false): string { ` source: ${theme.muted(shortenHomeInString(plugin.source))}`, ` origin: ${plugin.origin}`, ]; - if (plugin.version) parts.push(` version: ${plugin.version}`); + if (plugin.version) { + parts.push(` version: ${plugin.version}`); + } if (plugin.providerIds.length > 0) { parts.push(` providers: ${plugin.providerIds.join(", ")}`); } - if (plugin.error) parts.push(theme.error(` error: ${plugin.error}`)); + if (plugin.error) { + parts.push(theme.error(` error: ${plugin.error}`)); + } return parts.join("\n"); } @@ -85,7 +89,9 @@ function applySlotSelectionForPlugin( } function logSlotWarnings(warnings: string[]) { - if (warnings.length === 0) return; + if (warnings.length === 0) { + return; + } for (const warning of warnings) { defaultRuntime.log(theme.warn(warning)); } @@ -200,12 +206,16 @@ export function registerPluginsCli(program: Command) { if (plugin.name && plugin.name !== plugin.id) { lines.push(theme.muted(`id: ${plugin.id}`)); } - if (plugin.description) lines.push(plugin.description); + if (plugin.description) { + lines.push(plugin.description); + } lines.push(""); lines.push(`${theme.muted("Status:")} ${plugin.status}`); lines.push(`${theme.muted("Source:")} ${shortenHomeInString(plugin.source)}`); lines.push(`${theme.muted("Origin:")} ${plugin.origin}`); - if (plugin.version) lines.push(`${theme.muted("Version:")} ${plugin.version}`); + if (plugin.version) { + lines.push(`${theme.muted("Version:")} ${plugin.version}`); + } if (plugin.toolNames.length > 0) { lines.push(`${theme.muted("Tools:")} ${plugin.toolNames.join(", ")}`); } @@ -224,18 +234,27 @@ export function registerPluginsCli(program: Command) { if (plugin.services.length > 0) { lines.push(`${theme.muted("Services:")} ${plugin.services.join(", ")}`); } - if (plugin.error) lines.push(`${theme.error("Error:")} ${plugin.error}`); + if (plugin.error) { + lines.push(`${theme.error("Error:")} ${plugin.error}`); + } if (install) { lines.push(""); lines.push(`${theme.muted("Install:")} ${install.source}`); - if (install.spec) lines.push(`${theme.muted("Spec:")} ${install.spec}`); - if (install.sourcePath) + if (install.spec) { + lines.push(`${theme.muted("Spec:")} ${install.spec}`); + } + if (install.sourcePath) { lines.push(`${theme.muted("Source path:")} ${shortenHomePath(install.sourcePath)}`); - if (install.installPath) + } + if (install.installPath) { lines.push(`${theme.muted("Install path:")} ${shortenHomePath(install.installPath)}`); - if (install.version) lines.push(`${theme.muted("Recorded version:")} ${install.version}`); - if (install.installedAt) + } + if (install.version) { + lines.push(`${theme.muted("Recorded version:")} ${install.version}`); + } + if (install.installedAt) { lines.push(`${theme.muted("Installed at:")} ${install.installedAt}`); + } } defaultRuntime.log(lines.join("\n")); }); @@ -514,7 +533,9 @@ export function registerPluginsCli(program: Command) { } } if (diags.length > 0) { - if (lines.length > 0) lines.push(""); + if (lines.length > 0) { + lines.push(""); + } lines.push(theme.warn("Diagnostics:")); for (const diag of diags) { const target = diag.pluginId ? `${diag.pluginId}: ` : ""; diff --git a/src/cli/ports.ts b/src/cli/ports.ts index 19b5689ae3..c12ea1669a 100644 --- a/src/cli/ports.ts +++ b/src/cli/ports.ts @@ -19,13 +19,17 @@ export function parseLsofOutput(output: string): PortProcess[] { let current: Partial = {}; for (const line of lines) { if (line.startsWith("p")) { - if (current.pid) results.push(current as PortProcess); + if (current.pid) { + results.push(current as PortProcess); + } current = { pid: Number.parseInt(line.slice(1), 10) }; } else if (line.startsWith("c")) { current.command = line.slice(1); } } - if (current.pid) results.push(current as PortProcess); + if (current.pid) { + results.push(current as PortProcess); + } return results; } @@ -42,7 +46,9 @@ export function listPortListeners(port: number): PortProcess[] { if (code === "ENOENT") { throw new Error("lsof not found; required for --force", { cause: err }); } - if (status === 1) return []; // no listeners + if (status === 1) { + return []; + } // no listeners throw err instanceof Error ? err : new Error(String(err)); } } diff --git a/src/cli/profile-utils.ts b/src/cli/profile-utils.ts index cef12a3b0c..2e89a8243f 100644 --- a/src/cli/profile-utils.ts +++ b/src/cli/profile-utils.ts @@ -1,15 +1,23 @@ const PROFILE_NAME_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i; export function isValidProfileName(value: string): boolean { - if (!value) return false; + if (!value) { + return false; + } // Keep it path-safe + shell-friendly. return PROFILE_NAME_RE.test(value); } export function normalizeProfileName(raw?: string | null): string | null { const profile = raw?.trim(); - if (!profile) return null; - if (profile.toLowerCase() === "default") return null; - if (!isValidProfileName(profile)) return null; + if (!profile) { + return null; + } + if (profile.toLowerCase() === "default") { + return null; + } + if (!isValidProfileName(profile)) { + return null; + } return profile; } diff --git a/src/cli/profile.test.ts b/src/cli/profile.test.ts index 3b00241276..f8ac607a90 100644 --- a/src/cli/profile.test.ts +++ b/src/cli/profile.test.ts @@ -12,21 +12,27 @@ describe("parseCliProfileArgs", () => { "--dev", "--allow-unconfigured", ]); - if (!res.ok) throw new Error(res.error); + if (!res.ok) { + throw new Error(res.error); + } expect(res.profile).toBeNull(); expect(res.argv).toEqual(["node", "openclaw", "gateway", "--dev", "--allow-unconfigured"]); }); it("still accepts global --dev before subcommand", () => { const res = parseCliProfileArgs(["node", "openclaw", "--dev", "gateway"]); - if (!res.ok) throw new Error(res.error); + if (!res.ok) { + throw new Error(res.error); + } expect(res.profile).toBe("dev"); expect(res.argv).toEqual(["node", "openclaw", "gateway"]); }); it("parses --profile value and strips it", () => { const res = parseCliProfileArgs(["node", "openclaw", "--profile", "work", "status"]); - if (!res.ok) throw new Error(res.error); + if (!res.ok) { + throw new Error(res.error); + } expect(res.profile).toBe("work"); expect(res.argv).toEqual(["node", "openclaw", "status"]); }); diff --git a/src/cli/profile.ts b/src/cli/profile.ts index 8690f1fc13..ac3e965d5d 100644 --- a/src/cli/profile.ts +++ b/src/cli/profile.ts @@ -24,7 +24,9 @@ function takeValue( } export function parseCliProfileArgs(argv: string[]): CliProfileParseResult { - if (argv.length < 2) return { ok: true, profile: null, argv }; + if (argv.length < 2) { + return { ok: true, profile: null, argv }; + } const out: string[] = argv.slice(0, 2); let profile: string | null = null; @@ -34,7 +36,9 @@ export function parseCliProfileArgs(argv: string[]): CliProfileParseResult { const args = argv.slice(2); for (let i = 0; i < args.length; i += 1) { const arg = args[i]; - if (arg === undefined) continue; + if (arg === undefined) { + continue; + } if (sawCommand) { out.push(arg); @@ -56,8 +60,12 @@ export function parseCliProfileArgs(argv: string[]): CliProfileParseResult { } const next = args[i + 1]; const { value, consumedNext } = takeValue(arg, next); - if (consumedNext) i += 1; - if (!value) return { ok: false, error: "--profile requires a value" }; + if (consumedNext) { + i += 1; + } + if (!value) { + return { ok: false, error: "--profile requires a value" }; + } if (!isValidProfileName(value)) { return { ok: false, @@ -93,13 +101,17 @@ export function applyCliProfileEnv(params: { const env = params.env ?? (process.env as Record); const homedir = params.homedir ?? os.homedir; const profile = params.profile.trim(); - if (!profile) return; + if (!profile) { + return; + } // Convenience only: fill defaults, never override explicit env values. env.OPENCLAW_PROFILE = profile; const stateDir = env.OPENCLAW_STATE_DIR?.trim() || resolveProfileStateDir(profile, homedir); - if (!env.OPENCLAW_STATE_DIR?.trim()) env.OPENCLAW_STATE_DIR = stateDir; + if (!env.OPENCLAW_STATE_DIR?.trim()) { + env.OPENCLAW_STATE_DIR = stateDir; + } if (!env.OPENCLAW_CONFIG_PATH?.trim()) { env.OPENCLAW_CONFIG_PATH = path.join(stateDir, "openclaw.json"); diff --git a/src/cli/program.force.test.ts b/src/cli/program.force.test.ts index 9d5e023f79..bf5b8509d9 100644 --- a/src/cli/program.force.test.ts +++ b/src/cli/program.force.test.ts @@ -84,8 +84,12 @@ describe("gateway --force helpers", () => { (execFileSync as unknown as vi.Mock).mockImplementation(() => { call += 1; // 1st call: initial listeners to kill; 2nd call: still listed; 3rd call: gone. - if (call === 1) return ["p42", "cnode", ""].join("\n"); - if (call === 2) return ["p42", "cnode", ""].join("\n"); + if (call === 1) { + return ["p42", "cnode", ""].join("\n"); + } + if (call === 2) { + return ["p42", "cnode", ""].join("\n"); + } return ""; }); @@ -116,7 +120,9 @@ describe("gateway --force helpers", () => { (execFileSync as unknown as vi.Mock).mockImplementation(() => { call += 1; // 1st call: initial kill list; then keep showing until after SIGKILL. - if (call <= 6) return ["p42", "cnode", ""].join("\n"); + if (call <= 6) { + return ["p42", "cnode", ""].join("\n"); + } return ""; }); diff --git a/src/cli/program/command-registry.ts b/src/cli/program/command-registry.ts index 0b4618ef0b..70f0dc1969 100644 --- a/src/cli/program/command-registry.ts +++ b/src/cli/program/command-registry.ts @@ -44,7 +44,9 @@ const routeHealth: RouteSpec = { const json = hasFlag(argv, "--json"); const verbose = getVerboseFlag(argv, { includeDebug: true }); const timeoutMs = getPositiveIntFlagValue(argv, "--timeout"); - if (timeoutMs === null) return false; + if (timeoutMs === null) { + return false; + } await healthCommand({ json, timeoutMs, verbose }, defaultRuntime); return true; }, @@ -60,7 +62,9 @@ const routeStatus: RouteSpec = { const usage = hasFlag(argv, "--usage"); const verbose = getVerboseFlag(argv, { includeDebug: true }); const timeoutMs = getPositiveIntFlagValue(argv, "--timeout"); - if (timeoutMs === null) return false; + if (timeoutMs === null) { + return false; + } await statusCommand({ json, deep, all, usage, timeoutMs, verbose }, defaultRuntime); return true; }, @@ -71,9 +75,13 @@ const routeSessions: RouteSpec = { run: async (argv) => { const json = hasFlag(argv, "--json"); const store = getFlagValue(argv, "--store"); - if (store === null) return false; + if (store === null) { + return false; + } const active = getFlagValue(argv, "--active"); - if (active === null) return false; + if (active === null) { + return false; + } await sessionsCommand({ json, store, active }, defaultRuntime); return true; }, @@ -93,7 +101,9 @@ const routeMemoryStatus: RouteSpec = { match: (path) => path[0] === "memory" && path[1] === "status", run: async (argv) => { const agent = getFlagValue(argv, "--agent"); - if (agent === null) return false; + if (agent === null) { + return false; + } const json = hasFlag(argv, "--json"); const deep = hasFlag(argv, "--deep"); const index = hasFlag(argv, "--index"); @@ -166,9 +176,13 @@ export function registerProgramCommands( export function findRoutedCommand(path: string[]): RouteSpec | null { for (const entry of commandRegistry) { - if (!entry.routes) continue; + if (!entry.routes) { + continue; + } for (const route of entry.routes) { - if (route.match(path)) return route; + if (route.match(path)) { + return route; + } } } return null; diff --git a/src/cli/program/config-guard.ts b/src/cli/program/config-guard.ts index 3989979f92..89e6183997 100644 --- a/src/cli/program/config-guard.ts +++ b/src/cli/program/config-guard.ts @@ -52,7 +52,9 @@ export async function ensureConfigReady(params: { : []; const invalid = snapshot.exists && !snapshot.valid; - if (!invalid) return; + if (!invalid) { + return; + } const rich = isRich(); const muted = (value: string) => colorize(rich, theme.muted, value); diff --git a/src/cli/program/help.ts b/src/cli/program/help.ts index d793d7f051..109c4270a1 100644 --- a/src/cli/program/help.ts +++ b/src/cli/program/help.ts @@ -73,7 +73,9 @@ export function configureProgramHelp(program: Command, ctx: ProgramContext) { } program.addHelpText("beforeAll", () => { - if (hasEmittedCliBanner()) return ""; + if (hasEmittedCliBanner()) { + return ""; + } const rich = isRich(); const line = formatCliBannerLine(ctx.programVersion, { richTty: rich }); return `\n${line}\n`; @@ -84,7 +86,9 @@ export function configureProgramHelp(program: Command, ctx: ProgramContext) { ).join("\n"); program.addHelpText("afterAll", ({ command }) => { - if (command !== program) return ""; + if (command !== program) { + return ""; + } const docs = formatDocsLink("/cli", "docs.openclaw.ai/cli"); return `\n${theme.heading("Examples:")}\n${fmtExamples}\n\n${theme.muted("Docs:")} ${docs}\n`; }); diff --git a/src/cli/program/helpers.ts b/src/cli/program/helpers.ts index 160bbb1627..60ed2cbb3d 100644 --- a/src/cli/program/helpers.ts +++ b/src/cli/program/helpers.ts @@ -3,22 +3,30 @@ export function collectOption(value: string, previous: string[] = []): string[] } export function parsePositiveIntOrUndefined(value: unknown): number | undefined { - if (value === undefined || value === null || value === "") return undefined; + if (value === undefined || value === null || value === "") { + return undefined; + } if (typeof value === "number") { - if (!Number.isFinite(value)) return undefined; + if (!Number.isFinite(value)) { + return undefined; + } const parsed = Math.trunc(value); return parsed > 0 ? parsed : undefined; } if (typeof value === "string") { const parsed = Number.parseInt(value, 10); - if (Number.isNaN(parsed) || parsed <= 0) return undefined; + if (Number.isNaN(parsed) || parsed <= 0) { + return undefined; + } return parsed; } return undefined; } export function resolveActionArgs(actionCommand?: import("commander").Command): string[] { - if (!actionCommand) return []; + if (!actionCommand) { + return []; + } const args = (actionCommand as import("commander").Command & { args?: string[] }).args; return Array.isArray(args) ? args : []; } diff --git a/src/cli/program/preaction.ts b/src/cli/program/preaction.ts index 27d196ec02..d38c688c68 100644 --- a/src/cli/program/preaction.ts +++ b/src/cli/program/preaction.ts @@ -15,7 +15,9 @@ function setProcessTitleForCommand(actionCommand: Command) { } const name = current.name(); const cliName = resolveCliName(); - if (!name || name === cliName) return; + if (!name || name === cliName) { + return; + } process.title = `${cliName}-${name}`; } @@ -26,7 +28,9 @@ export function registerPreActionHooks(program: Command, programVersion: string) program.hook("preAction", async (_thisCommand, actionCommand) => { setProcessTitleForCommand(actionCommand); const argv = process.argv; - if (hasHelpOrVersion(argv)) return; + if (hasHelpOrVersion(argv)) { + return; + } const commandPath = getCommandPath(argv, 2); const hideBanner = isTruthyEnvValue(process.env.OPENCLAW_HIDE_BANNER) || @@ -41,7 +45,9 @@ export function registerPreActionHooks(program: Command, programVersion: string) if (!verbose) { process.env.NODE_NO_WARNINGS ??= "1"; } - if (commandPath[0] === "doctor" || commandPath[0] === "completion") return; + if (commandPath[0] === "doctor" || commandPath[0] === "completion") { + return; + } await ensureConfigReady({ runtime: defaultRuntime, commandPath }); // Load plugins for commands that need channel access if (PLUGIN_REQUIRED_COMMANDS.has(commandPath[0])) { diff --git a/src/cli/program/register.onboard.ts b/src/cli/program/register.onboard.ts index 7a5a9c857e..086c8fd9d8 100644 --- a/src/cli/program/register.onboard.ts +++ b/src/cli/program/register.onboard.ts @@ -17,14 +17,20 @@ function resolveInstallDaemonFlag( command: unknown, opts: { installDaemon?: boolean }, ): boolean | undefined { - if (!command || typeof command !== "object") return undefined; + if (!command || typeof command !== "object") { + return undefined; + } const getOptionValueSource = "getOptionValueSource" in command ? command.getOptionValueSource : undefined; - if (typeof getOptionValueSource !== "function") return undefined; + if (typeof getOptionValueSource !== "function") { + return undefined; + } // Commander doesn't support option conflicts natively; keep original behavior. // If --skip-daemon is explicitly passed, it wins. - if (getOptionValueSource.call(command, "skipDaemon") === "cli") return false; + if (getOptionValueSource.call(command, "skipDaemon") === "cli") { + return false; + } if (getOptionValueSource.call(command, "installDaemon") === "cli") { return Boolean(opts.installDaemon); } diff --git a/src/cli/program/register.subclis.ts b/src/cli/program/register.subclis.ts index 34dff02d07..a822113e6b 100644 --- a/src/cli/program/register.subclis.ts +++ b/src/cli/program/register.subclis.ts @@ -13,8 +13,12 @@ type SubCliEntry = { }; const shouldRegisterPrimaryOnly = (argv: string[]) => { - if (isTruthyEnvValue(process.env.OPENCLAW_DISABLE_LAZY_SUBCOMMANDS)) return false; - if (hasHelpOrVersion(argv)) return false; + if (isTruthyEnvValue(process.env.OPENCLAW_DISABLE_LAZY_SUBCOMMANDS)) { + return false; + } + if (hasHelpOrVersion(argv)) { + return false; + } return true; }; @@ -251,9 +255,13 @@ function removeCommand(program: Command, command: Command) { export async function registerSubCliByName(program: Command, name: string): Promise { const entry = entries.find((candidate) => candidate.name === name); - if (!entry) return false; + if (!entry) { + return false; + } const existing = program.commands.find((cmd) => cmd.name() === entry.name); - if (existing) removeCommand(program, existing); + if (existing) { + removeCommand(program, existing); + } await entry.register(program); return true; } diff --git a/src/cli/progress.ts b/src/cli/progress.ts index 2301554012..64974e5c86 100644 --- a/src/cli/progress.ts +++ b/src/cli/progress.ts @@ -41,13 +41,19 @@ const noopReporter: ProgressReporter = { }; export function createCliProgress(options: ProgressOptions): ProgressReporter { - if (options.enabled === false) return noopReporter; - if (activeProgress > 0) return noopReporter; + if (options.enabled === false) { + return noopReporter; + } + if (activeProgress > 0) { + return noopReporter; + } const stream = options.stream ?? process.stderr; const isTty = stream.isTTY; const allowLog = !isTty && options.fallback === "log"; - if (!isTty && !allowLog) return noopReporter; + if (!isTty && !allowLog) { + return noopReporter; + } const delayMs = typeof options.delayMs === "number" ? options.delayMs : DEFAULT_DELAY_MS; const canOsc = isTty && supportsOscProgress(process.env, isTty); @@ -78,7 +84,9 @@ export function createCliProgress(options: ProgressOptions): ProgressReporter { const spin = allowSpinner ? spinner() : null; const renderLine = allowLine ? () => { - if (!started) return; + if (!started) { + return; + } const suffix = indeterminate ? "" : ` ${percent}%`; clearActiveProgressLine(); stream.write(`${theme.accent(label)}${suffix}`); @@ -90,11 +98,15 @@ export function createCliProgress(options: ProgressOptions): ProgressReporter { let lastAt = 0; const throttleMs = 250; return () => { - if (!started) return; + if (!started) { + return; + } const suffix = indeterminate ? "" : ` ${percent}%`; const nextLine = `${label}${suffix}`; const now = Date.now(); - if (nextLine === lastLine && now - lastAt < throttleMs) return; + if (nextLine === lastLine && now - lastAt < throttleMs) { + return; + } lastLine = nextLine; lastAt = now; stream.write(`${nextLine}\n`); @@ -104,10 +116,15 @@ export function createCliProgress(options: ProgressOptions): ProgressReporter { let timer: NodeJS.Timeout | null = null; const applyState = () => { - if (!started) return; + if (!started) { + return; + } if (controller) { - if (indeterminate) controller.setIndeterminate(label); - else controller.setPercent(label, percent); + if (indeterminate) { + controller.setIndeterminate(label); + } else { + controller.setPercent(label, percent); + } } if (spin) { spin.message(theme.accent(label)); @@ -121,7 +138,9 @@ export function createCliProgress(options: ProgressOptions): ProgressReporter { }; const start = () => { - if (started) return; + if (started) { + return; + } started = true; if (spin) { spin.start(theme.accent(label)); @@ -147,7 +166,9 @@ export function createCliProgress(options: ProgressOptions): ProgressReporter { }; const tick = (delta = 1) => { - if (!total) return; + if (!total) { + return; + } completed = Math.min(total, completed + delta); const nextPercent = total > 0 ? Math.round((completed / total) * 100) : 0; setPercent(nextPercent); @@ -162,8 +183,12 @@ export function createCliProgress(options: ProgressOptions): ProgressReporter { activeProgress = Math.max(0, activeProgress - 1); return; } - if (controller) controller.clear(); - if (spin) spin.stop(); + if (controller) { + controller.clear(); + } + if (spin) { + spin.stop(); + } clearActiveProgressLine(); if (isTty) { unregisterActiveProgressLine(stream); @@ -192,8 +217,12 @@ export async function withProgressTotals( ): Promise { return await withProgress(options, async (progress) => { const update = ({ completed, total, label }: ProgressTotalsUpdate) => { - if (label) progress.setLabel(label); - if (!Number.isFinite(total) || total <= 0) return; + if (label) { + progress.setLabel(label); + } + if (!Number.isFinite(total) || total <= 0) { + return; + } progress.setPercent((completed / total) * 100); }; return await work(update, progress); diff --git a/src/cli/prompt.ts b/src/cli/prompt.ts index 8f5122b19e..7b78c18584 100644 --- a/src/cli/prompt.ts +++ b/src/cli/prompt.ts @@ -5,12 +5,18 @@ import { isVerbose, isYes } from "../globals.js"; export async function promptYesNo(question: string, defaultYes = false): Promise { // Simple Y/N prompt honoring global --yes and verbosity flags. - if (isVerbose() && isYes()) return true; // redundant guard when both flags set - if (isYes()) return true; + if (isVerbose() && isYes()) { + return true; + } // redundant guard when both flags set + if (isYes()) { + return true; + } const rl = readline.createInterface({ input, output }); const suffix = defaultYes ? " [Y/n] " : " [y/N] "; const answer = (await rl.question(`${question}${suffix}`)).trim().toLowerCase(); rl.close(); - if (!answer) return defaultYes; + if (!answer) { + return defaultYes; + } return answer.startsWith("y"); } diff --git a/src/cli/route.ts b/src/cli/route.ts index 48a4f18aad..39a34d9864 100644 --- a/src/cli/route.ts +++ b/src/cli/route.ts @@ -20,13 +20,21 @@ async function prepareRoutedCommand(params: { } export async function tryRouteCli(argv: string[]): Promise { - if (isTruthyEnvValue(process.env.OPENCLAW_DISABLE_ROUTE_FIRST)) return false; - if (hasHelpOrVersion(argv)) return false; + if (isTruthyEnvValue(process.env.OPENCLAW_DISABLE_ROUTE_FIRST)) { + return false; + } + if (hasHelpOrVersion(argv)) { + return false; + } const path = getCommandPath(argv, 2); - if (!path[0]) return false; + if (!path[0]) { + return false; + } const route = findRoutedCommand(path); - if (!route) return false; + if (!route) { + return false; + } await prepareRoutedCommand({ argv, commandPath: path, loadPlugins: route.loadPlugins }); return route.run(argv); } diff --git a/src/cli/run-main.ts b/src/cli/run-main.ts index b9cfd5c31b..92944937ef 100644 --- a/src/cli/run-main.ts +++ b/src/cli/run-main.ts @@ -16,7 +16,9 @@ import { tryRouteCli } from "./route.js"; export function rewriteUpdateFlagArgv(argv: string[]): string[] { const index = argv.indexOf("--update"); - if (index === -1) return argv; + if (index === -1) { + return argv; + } const next = [...argv]; next.splice(index, 1, "update"); @@ -32,7 +34,9 @@ export async function runCli(argv: string[] = process.argv) { // Enforce the minimum supported runtime before doing any work. assertSupportedRuntime(); - if (await tryRouteCli(normalizedArgv)) return; + if (await tryRouteCli(normalizedArgv)) { + return; + } // Capture all console output into structured logs while keeping stdout/stderr behavior. enableConsoleCapture(); @@ -69,7 +73,9 @@ export async function runCli(argv: string[] = process.argv) { } function stripWindowsNodeExec(argv: string[]): string[] { - if (process.platform !== "win32") return argv; + if (process.platform !== "win32") { + return argv; + } const stripControlChars = (value: string): string => { let out = ""; for (let i = 0; i < value.length; i += 1) { @@ -90,9 +96,13 @@ function stripWindowsNodeExec(argv: string[]): string[] { const execPathLower = execPath.toLowerCase(); const execBase = path.basename(execPath).toLowerCase(); const isExecPath = (value: string | undefined): boolean => { - if (!value) return false; + if (!value) { + return false; + } const normalized = normalizeCandidate(value); - if (!normalized) return false; + if (!normalized) { + return false; + } const lower = normalized.toLowerCase(); return ( lower === execPathLower || @@ -104,7 +114,9 @@ function stripWindowsNodeExec(argv: string[]): string[] { ); }; const filtered = argv.filter((arg, index) => index === 0 || !isExecPath(arg)); - if (filtered.length < 3) return filtered; + if (filtered.length < 3) { + return filtered; + } const cleaned = [...filtered]; if (isExecPath(cleaned[1])) { cleaned.splice(1, 1); diff --git a/src/cli/security-cli.ts b/src/cli/security-cli.ts index 6096892a55..a2570b1ce9 100644 --- a/src/cli/security-cli.ts +++ b/src/cli/security-cli.ts @@ -89,21 +89,27 @@ export function registerSecurityCli(program: Command) { for (const action of fixResult.actions) { if (action.kind === "chmod") { const mode = action.mode.toString(8).padStart(3, "0"); - if (action.ok) lines.push(muted(` chmod ${mode} ${shortenHomePath(action.path)}`)); - else if (action.skipped) + if (action.ok) { + lines.push(muted(` chmod ${mode} ${shortenHomePath(action.path)}`)); + } else if (action.skipped) { lines.push( muted(` skip chmod ${mode} ${shortenHomePath(action.path)} (${action.skipped})`), ); - else if (action.error) + } else if (action.error) { lines.push( muted(` chmod ${mode} ${shortenHomePath(action.path)} failed: ${action.error}`), ); + } continue; } const command = shortenHomeInString(action.command); - if (action.ok) lines.push(muted(` ${command}`)); - else if (action.skipped) lines.push(muted(` skip ${command} (${action.skipped})`)); - else if (action.error) lines.push(muted(` ${command} failed: ${action.error}`)); + if (action.ok) { + lines.push(muted(` ${command}`)); + } else if (action.skipped) { + lines.push(muted(` skip ${command} (${action.skipped})`)); + } else if (action.error) { + lines.push(muted(` ${command} failed: ${action.error}`)); + } } if (fixResult.errors.length > 0) { for (const err of fixResult.errors) { @@ -118,7 +124,9 @@ export function registerSecurityCli(program: Command) { const render = (sev: "critical" | "warn" | "info") => { const list = bySeverity(sev); - if (list.length === 0) return; + if (list.length === 0) { + return; + } const label = sev === "critical" ? rich @@ -136,7 +144,9 @@ export function registerSecurityCli(program: Command) { for (const f of list) { lines.push(`${theme.muted(f.checkId)} ${f.title}`); lines.push(` ${f.detail}`); - if (f.remediation?.trim()) lines.push(` ${muted(`Fix: ${f.remediation.trim()}`)}`); + if (f.remediation?.trim()) { + lines.push(` ${muted(`Fix: ${f.remediation.trim()}`)}`); + } } }; diff --git a/src/cli/skills-cli.test.ts b/src/cli/skills-cli.test.ts index b7c82b3f2c..ca644698a2 100644 --- a/src/cli/skills-cli.test.ts +++ b/src/cli/skills-cli.test.ts @@ -218,7 +218,9 @@ describe("skills-cli", () => { const moduleDir = path.dirname(fileURLToPath(import.meta.url)); const root = path.resolve(moduleDir, "..", ".."); const candidate = path.join(root, "skills"); - if (fs.existsSync(candidate)) return candidate; + if (fs.existsSync(candidate)) { + return candidate; + } return undefined; } @@ -251,7 +253,9 @@ describe("skills-cli", () => { it("formats info for a real bundled skill (peekaboo)", () => { const bundledDir = resolveBundledSkillsDir(); - if (!bundledDir) return; + if (!bundledDir) { + return; + } const report = buildWorkspaceSkillStatus("/tmp", { managedSkillsDir: "/nonexistent", diff --git a/src/cli/skills-cli.ts b/src/cli/skills-cli.ts index a3b55d7e75..d4c5e4f923 100644 --- a/src/cli/skills-cli.ts +++ b/src/cli/skills-cli.ts @@ -28,14 +28,22 @@ export type SkillsCheckOptions = { }; function appendClawHubHint(output: string, json?: boolean): string { - if (json) return output; + if (json) { + return output; + } return `${output}\n\nTip: use \`npx clawhub\` to search, install, and sync skills.`; } function formatSkillStatus(skill: SkillStatusEntry): string { - if (skill.eligible) return theme.success("✓ ready"); - if (skill.disabled) return theme.warn("⏸ disabled"); - if (skill.blockedByAllowlist) return theme.warn("🚫 blocked"); + if (skill.eligible) { + return theme.success("✓ ready"); + } + if (skill.disabled) { + return theme.warn("⏸ disabled"); + } + if (skill.blockedByAllowlist) { + return theme.warn("🚫 blocked"); + } return theme.error("✗ missing"); } diff --git a/src/cli/system-cli.ts b/src/cli/system-cli.ts index 0082dcb52d..2cc95412e3 100644 --- a/src/cli/system-cli.ts +++ b/src/cli/system-cli.ts @@ -11,8 +11,12 @@ type SystemEventOpts = GatewayRpcOpts & { text?: string; mode?: string; json?: b const normalizeWakeMode = (raw: unknown) => { const mode = typeof raw === "string" ? raw.trim() : ""; - if (!mode) return "next-heartbeat" as const; - if (mode === "now" || mode === "next-heartbeat") return mode; + if (!mode) { + return "next-heartbeat" as const; + } + if (mode === "now" || mode === "next-heartbeat") { + return mode; + } throw new Error("--mode must be now or next-heartbeat"); }; @@ -36,11 +40,16 @@ export function registerSystemCli(program: Command) { ).action(async (opts: SystemEventOpts) => { try { const text = typeof opts.text === "string" ? opts.text.trim() : ""; - if (!text) throw new Error("--text is required"); + if (!text) { + throw new Error("--text is required"); + } const mode = normalizeWakeMode(opts.mode); const result = await callGatewayFromCli("wake", opts, { mode, text }, { expectFinal: false }); - if (opts.json) defaultRuntime.log(JSON.stringify(result, null, 2)); - else defaultRuntime.log("ok"); + if (opts.json) { + defaultRuntime.log(JSON.stringify(result, null, 2)); + } else { + defaultRuntime.log("ok"); + } } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); diff --git a/src/cli/tagline.ts b/src/cli/tagline.ts index 1a6d86c211..206b1a7ffa 100644 --- a/src/cli/tagline.ts +++ b/src/cli/tagline.ts @@ -127,7 +127,9 @@ const onSpecificDates = (date) => { const parts = utcParts(date); return dates.some(([year, month, day]) => { - if (parts.year !== year) return false; + if (parts.year !== year) { + return false; + } const start = Date.UTC(year, month, day); const current = Date.UTC(parts.year, parts.month, parts.day); return current >= start && current < start + durationDays * DAY_MS; @@ -146,7 +148,9 @@ const inYearWindow = (date) => { const parts = utcParts(date); const window = windows.find((entry) => entry.year === parts.year); - if (!window) return false; + if (!window) { + return false; + } const start = Date.UTC(window.year, window.month, window.day); const current = Date.UTC(parts.year, parts.month, parts.day); return current >= start && current < start + window.duration * DAY_MS; @@ -154,7 +158,9 @@ const inYearWindow = const isFourthThursdayOfNovember: HolidayRule = (date) => { const parts = utcParts(date); - if (parts.month !== 10) return false; // November + if (parts.month !== 10) { + return false; + } // November const firstDay = new Date(Date.UTC(parts.year, 10, 1)).getUTCDay(); const offsetToThursday = (4 - firstDay + 7) % 7; // 4 = Thursday const fourthThursday = 1 + offsetToThursday + 21; // 1st + offset + 3 weeks @@ -224,7 +230,9 @@ const HOLIDAY_RULES = new Map([ function isTaglineActive(tagline: string, date: Date): boolean { const rule = HOLIDAY_RULES.get(tagline); - if (!rule) return true; + if (!rule) { + return true; + } return rule(date); } @@ -235,7 +243,9 @@ export interface TaglineOptions { } export function activeTaglines(options: TaglineOptions = {}): string[] { - if (TAGLINES.length === 0) return [DEFAULT_TAGLINE]; + if (TAGLINES.length === 0) { + return [DEFAULT_TAGLINE]; + } const today = options.now ? options.now() : new Date(); const filtered = TAGLINES.filter((tagline) => isTaglineActive(tagline, today)); return filtered.length > 0 ? filtered : TAGLINES; diff --git a/src/cli/update-cli.ts b/src/cli/update-cli.ts index 3fcba914df..fd91e13518 100644 --- a/src/cli/update-cli.ts +++ b/src/cli/update-cli.ts @@ -118,10 +118,16 @@ const OPENCLAW_REPO_URL = "https://github.com/openclaw/openclaw.git"; const DEFAULT_GIT_DIR = path.join(os.homedir(), ".openclaw"); function normalizeTag(value?: string | null): string | null { - if (!value) return null; + if (!value) { + return null; + } const trimmed = value.trim(); - if (!trimmed) return null; - if (trimmed.startsWith("openclaw@")) return trimmed.slice("openclaw@".length); + if (!trimmed) { + return null; + } + if (trimmed.startsWith("openclaw@")) { + return trimmed.slice("openclaw@".length); + } if (trimmed.startsWith(`${DEFAULT_PACKAGE_NAME}@`)) { return trimmed.slice(`${DEFAULT_PACKAGE_NAME}@`.length); } @@ -134,7 +140,9 @@ function pickUpdateQuip(): string { function normalizeVersionTag(tag: string): string | null { const trimmed = tag.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const cleaned = trimmed.startsWith("v") ? trimmed.slice(1) : trimmed; return parseSemver(cleaned) ? cleaned : null; } @@ -151,7 +159,9 @@ async function readPackageVersion(root: string): Promise { async function resolveTargetVersion(tag: string, timeoutMs?: number): Promise { const direct = normalizeVersionTag(tag); - if (direct) return direct; + if (direct) { + return direct; + } const res = await fetchNpmTagVersion({ tag, timeoutMs }); return res.version ?? null; } @@ -201,7 +211,9 @@ async function isEmptyDir(targetPath: string): Promise { function resolveGitInstallDir(): string { const override = process.env.OPENCLAW_GIT_DIR?.trim(); - if (override) return path.resolve(override); + if (override) { + return path.resolve(override); + } return resolveDefaultGitDir(); } @@ -211,7 +223,9 @@ function resolveDefaultGitDir(): string { function resolveNodeRunner(): string { const base = path.basename(process.execPath).toLowerCase(); - if (base === "node" || base === "node.exe") return process.execPath; + if (base === "node" || base === "node.exe") { + return process.execPath; + } return "node"; } @@ -309,7 +323,9 @@ async function resolveGlobalManager(params: { params.root, params.timeoutMs, ); - if (detected) return detected; + if (detected) { + return detected; + } } const byPresence = await detectGlobalInstallManagerByPresence(runCommand, params.timeoutMs); return byPresence ?? "npm"; @@ -459,7 +475,9 @@ function createUpdateProgress(enabled: boolean): ProgressController { currentSpinner.start(theme.accent(getStepLabel(step))); }, onStepComplete: (step) => { - if (!currentSpinner) return; + if (!currentSpinner) { + return; + } const label = getStepLabel(step); const duration = theme.muted(`(${formatDuration(step.durationMs)})`); @@ -491,14 +509,20 @@ function createUpdateProgress(enabled: boolean): ProgressController { } function formatDuration(ms: number): string { - if (ms < 1000) return `${ms}ms`; + if (ms < 1000) { + return `${ms}ms`; + } const seconds = (ms / 1000).toFixed(1); return `${seconds}s`; } function formatStepStatus(exitCode: number | null): string { - if (exitCode === 0) return theme.success("\u2713"); - if (exitCode === null) return theme.warn("?"); + if (exitCode === 0) { + return theme.success("\u2713"); + } + if (exitCode === null) { + return theme.warn("?"); + } return theme.error("\u2717"); } @@ -875,7 +899,9 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise { if (!opts.json) { const summarizeList = (list: string[]) => { - if (list.length <= 6) return list.join(", "); + if (list.length <= 6) { + return list.join(", "); + } return `${list.slice(0, 6).join(", ")} +${list.length - 6} more`; }; @@ -907,13 +933,19 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise { defaultRuntime.log(theme.muted("No plugin updates needed.")); } else { const parts = [`${updated} updated`, `${unchanged} unchanged`]; - if (failed > 0) parts.push(`${failed} failed`); - if (skipped > 0) parts.push(`${skipped} skipped`); + if (failed > 0) { + parts.push(`${failed} failed`); + } + if (skipped > 0) { + parts.push(`${skipped} skipped`); + } defaultRuntime.log(theme.muted(`npm plugins: ${parts.join(", ")}.`)); } for (const outcome of npmResult.outcomes) { - if (outcome.status !== "error") continue; + if (outcome.status !== "error") { + continue; + } defaultRuntime.log(theme.error(outcome.message)); } } diff --git a/src/cli/webhooks-cli.ts b/src/cli/webhooks-cli.ts index c5883ff062..58ca9c502b 100644 --- a/src/cli/webhooks-cli.ts +++ b/src/cli/webhooks-cli.ts @@ -108,7 +108,9 @@ export function registerWebhooksCli(program: Command) { function parseGmailSetupOptions(raw: Record): GmailSetupOptions { const accountRaw = raw.account; const account = typeof accountRaw === "string" ? accountRaw.trim() : ""; - if (!account) throw new Error("--account is required"); + if (!account) { + throw new Error("--account is required"); + } return { account, project: stringOption(raw.project), @@ -154,19 +156,27 @@ function parseGmailRunOptions(raw: Record): GmailRunOptions { } function stringOption(value: unknown): string | undefined { - if (typeof value !== "string") return undefined; + if (typeof value !== "string") { + return undefined; + } const trimmed = value.trim(); return trimmed ? trimmed : undefined; } function numberOption(value: unknown): number | undefined { - if (value === undefined || value === null) return undefined; + if (value === undefined || value === null) { + return undefined; + } const n = typeof value === "number" ? value : Number(value); - if (!Number.isFinite(n) || n <= 0) return undefined; + if (!Number.isFinite(n) || n <= 0) { + return undefined; + } return Math.floor(n); } function booleanOption(value: unknown): boolean | undefined { - if (value === undefined || value === null) return undefined; + if (value === undefined || value === null) { + return undefined; + } return Boolean(value); } diff --git a/src/commands/agent-via-gateway.ts b/src/commands/agent-via-gateway.ts index dedaf1a0d1..49270a9f07 100644 --- a/src/commands/agent-via-gateway.ts +++ b/src/commands/agent-via-gateway.ts @@ -69,19 +69,25 @@ function formatPayloadForLog(payload: { mediaUrl?: string | null; }) { const lines: string[] = []; - if (payload.text) lines.push(payload.text.trimEnd()); + if (payload.text) { + lines.push(payload.text.trimEnd()); + } const mediaUrl = typeof payload.mediaUrl === "string" && payload.mediaUrl.trim() ? payload.mediaUrl.trim() : undefined; const media = payload.mediaUrls ?? (mediaUrl ? [mediaUrl] : []); - for (const url of media) lines.push(`MEDIA:${url}`); + for (const url of media) { + lines.push(`MEDIA:${url}`); + } return lines.join("\n").trimEnd(); } export async function agentViaGatewayCommand(opts: AgentCliOpts, runtime: RuntimeEnv) { const body = (opts.message ?? "").trim(); - if (!body) throw new Error("Message (--message) is required"); + if (!body) { + throw new Error("Message (--message) is required"); + } if (!opts.to && !opts.sessionId && !opts.agent) { throw new Error("Pass --to , --session-id, or --agent to choose a session"); } @@ -158,7 +164,9 @@ export async function agentViaGatewayCommand(opts: AgentCliOpts, runtime: Runtim for (const payload of payloads) { const out = formatPayloadForLog(payload); - if (out) runtime.log(out); + if (out) { + runtime.log(out); + } } return response; diff --git a/src/commands/agent.test.ts b/src/commands/agent.test.ts index 31e68d686a..96a7221899 100644 --- a/src/commands/agent.test.ts +++ b/src/commands/agent.test.ts @@ -147,7 +147,9 @@ describe("agentCommand", () => { const assistantEvents: Array<{ runId: string; text?: string }> = []; const stop = onAgentEvent((evt) => { - if (evt.stream !== "assistant") return; + if (evt.stream !== "assistant") { + return; + } assistantEvents.push({ runId: evt.runId, text: typeof evt.data?.text === "string" ? evt.data.text : undefined, diff --git a/src/commands/agent.ts b/src/commands/agent.ts index 0bcb444b97..675ab39ed0 100644 --- a/src/commands/agent.ts +++ b/src/commands/agent.ts @@ -66,7 +66,9 @@ export async function agentCommand( deps: CliDeps = createDefaultDeps(), ) { const body = (opts.message ?? "").trim(); - if (!body) throw new Error("Message (--message) is required"); + if (!body) { + throw new Error("Message (--message) is required"); + } if (!opts.to && !opts.sessionId && !opts.sessionKey && !opts.agentId) { throw new Error("Pass --to , --session-id, or --agent to choose a session"); } @@ -217,8 +219,11 @@ export async function agentCommand( sessionEntry ?? { sessionId, updatedAt: Date.now() }; const next: SessionEntry = { ...entry, sessionId, updatedAt: Date.now() }; if (thinkOverride) { - if (thinkOverride === "off") delete next.thinkingLevel; - else next.thinkingLevel = thinkOverride; + if (thinkOverride === "off") { + delete next.thinkingLevel; + } else { + next.thinkingLevel = thinkOverride; + } } applyVerboseOverride(next, verboseOverride); sessionStore[sessionKey] = next; diff --git a/src/commands/agent/delivery.ts b/src/commands/agent/delivery.ts index d2f6d3ebb0..79ef7ca743 100644 --- a/src/commands/agent/delivery.ts +++ b/src/commands/agent/delivery.ts @@ -28,19 +28,31 @@ const NESTED_LOG_PREFIX = "[agent:nested]"; function formatNestedLogPrefix(opts: AgentCommandOpts): string { const parts = [NESTED_LOG_PREFIX]; const session = opts.sessionKey ?? opts.sessionId; - if (session) parts.push(`session=${session}`); - if (opts.runId) parts.push(`run=${opts.runId}`); + if (session) { + parts.push(`session=${session}`); + } + if (opts.runId) { + parts.push(`run=${opts.runId}`); + } const channel = opts.messageChannel ?? opts.channel; - if (channel) parts.push(`channel=${channel}`); - if (opts.to) parts.push(`to=${opts.to}`); - if (opts.accountId) parts.push(`account=${opts.accountId}`); + if (channel) { + parts.push(`channel=${channel}`); + } + if (opts.to) { + parts.push(`to=${opts.to}`); + } + if (opts.accountId) { + parts.push(`account=${opts.accountId}`); + } return parts.join(" "); } function logNestedOutput(runtime: RuntimeEnv, opts: AgentCommandOpts, output: string) { const prefix = formatNestedLogPrefix(opts); for (const line of output.split(/\r?\n/)) { - if (!line) continue; + if (!line) { + continue; + } runtime.log(`${prefix} ${line}`); } } @@ -102,16 +114,22 @@ export async function deliverAgentCommandResult(params: { const logDeliveryError = (err: unknown) => { const message = `Delivery failed (${deliveryChannel}${deliveryTarget ? ` to ${deliveryTarget}` : ""}): ${String(err)}`; runtime.error?.(message); - if (!runtime.error) runtime.log(message); + if (!runtime.error) { + runtime.log(message); + } }; if (deliver) { if (!isDeliveryChannelKnown) { const err = new Error(`Unknown channel: ${deliveryChannel}`); - if (!bestEffortDeliver) throw err; + if (!bestEffortDeliver) { + throw err; + } logDeliveryError(err); } else if (resolvedTarget && !resolvedTarget.ok) { - if (!bestEffortDeliver) throw resolvedTarget.error; + if (!bestEffortDeliver) { + throw resolvedTarget.error; + } logDeliveryError(resolvedTarget.error); } } @@ -128,7 +146,9 @@ export async function deliverAgentCommandResult(params: { 2, ), ); - if (!deliver) return { payloads: normalizedPayloads, meta: result.meta }; + if (!deliver) { + return { payloads: normalizedPayloads, meta: result.meta }; + } } if (!payloads || payloads.length === 0) { @@ -138,9 +158,13 @@ export async function deliverAgentCommandResult(params: { const deliveryPayloads = normalizeOutboundPayloads(payloads); const logPayload = (payload: NormalizedOutboundPayload) => { - if (opts.json) return; + if (opts.json) { + return; + } const output = formatOutboundPayloadLog(payload); - if (!output) return; + if (!output) { + return; + } if (opts.lane === AGENT_LANE_NESTED) { logNestedOutput(runtime, opts, output); return; @@ -148,7 +172,9 @@ export async function deliverAgentCommandResult(params: { runtime.log(output); }; if (!deliver) { - for (const payload of deliveryPayloads) logPayload(payload); + for (const payload of deliveryPayloads) { + logPayload(payload); + } } if (deliver && deliveryChannel && !isInternalMessageChannel(deliveryChannel)) { if (deliveryTarget) { diff --git a/src/commands/agent/run-context.ts b/src/commands/agent/run-context.ts index cc21a73cc3..fcf8d0845b 100644 --- a/src/commands/agent/run-context.ts +++ b/src/commands/agent/run-context.ts @@ -9,19 +9,29 @@ export function resolveAgentRunContext(opts: AgentCommandOpts): AgentRunContext merged.messageChannel ?? opts.messageChannel, opts.replyChannel ?? opts.channel, ); - if (normalizedChannel) merged.messageChannel = normalizedChannel; + if (normalizedChannel) { + merged.messageChannel = normalizedChannel; + } const normalizedAccountId = normalizeAccountId(merged.accountId ?? opts.accountId); - if (normalizedAccountId) merged.accountId = normalizedAccountId; + if (normalizedAccountId) { + merged.accountId = normalizedAccountId; + } const groupId = (merged.groupId ?? opts.groupId)?.toString().trim(); - if (groupId) merged.groupId = groupId; + if (groupId) { + merged.groupId = groupId; + } const groupChannel = (merged.groupChannel ?? opts.groupChannel)?.toString().trim(); - if (groupChannel) merged.groupChannel = groupChannel; + if (groupChannel) { + merged.groupChannel = groupChannel; + } const groupSpace = (merged.groupSpace ?? opts.groupSpace)?.toString().trim(); - if (groupSpace) merged.groupSpace = groupSpace; + if (groupSpace) { + merged.groupSpace = groupSpace; + } if ( merged.currentThreadTs == null && diff --git a/src/commands/agent/session-store.ts b/src/commands/agent/session-store.ts index 56a22fede8..5680af5ea5 100644 --- a/src/commands/agent/session-store.ts +++ b/src/commands/agent/session-store.ts @@ -56,7 +56,9 @@ export async function updateSessionStoreAfterAgentRun(params: { }; if (isCliProvider(providerUsed, cfg)) { const cliSessionId = result.meta.agentMeta?.sessionId?.trim(); - if (cliSessionId) setCliSessionId(next, providerUsed, cliSessionId); + if (cliSessionId) { + setCliSessionId(next, providerUsed, cliSessionId); + } } next.abortedLastRun = result.meta.aborted ?? false; if (hasNonzeroUsage(usage)) { diff --git a/src/commands/agent/session.ts b/src/commands/agent/session.ts index 3148b55374..e2fd1a7521 100644 --- a/src/commands/agent/session.ts +++ b/src/commands/agent/session.ts @@ -74,7 +74,9 @@ export function resolveSessionKeyForRequest(opts: { const foundKey = Object.keys(sessionStore).find( (key) => sessionStore[key]?.sessionId === opts.sessionId, ); - if (foundKey) sessionKey = foundKey; + if (foundKey) { + sessionKey = foundKey; + } } return { sessionKey, sessionStore, storePath }; diff --git a/src/commands/agents.bindings.ts b/src/commands/agents.bindings.ts index aa04888d7e..f0eaf959e1 100644 --- a/src/commands/agents.bindings.ts +++ b/src/commands/agents.bindings.ts @@ -21,10 +21,18 @@ function bindingMatchKey(match: AgentBinding["match"]) { export function describeBinding(binding: AgentBinding) { const match = binding.match; const parts = [match.channel]; - if (match.accountId) parts.push(`accountId=${match.accountId}`); - if (match.peer) parts.push(`peer=${match.peer.kind}:${match.peer.id}`); - if (match.guildId) parts.push(`guild=${match.guildId}`); - if (match.teamId) parts.push(`team=${match.teamId}`); + if (match.accountId) { + parts.push(`accountId=${match.accountId}`); + } + if (match.peer) { + parts.push(`peer=${match.peer.kind}:${match.peer.id}`); + } + if (match.guildId) { + parts.push(`guild=${match.guildId}`); + } + if (match.teamId) { + parts.push(`team=${match.teamId}`); + } return parts.join(" "); } @@ -83,7 +91,9 @@ export function applyAgentBindings( function resolveDefaultAccountId(cfg: OpenClawConfig, provider: ChannelId): string { const plugin = getChannelPlugin(provider); - if (!plugin) return DEFAULT_ACCOUNT_ID; + if (!plugin) { + return DEFAULT_ACCOUNT_ID; + } return resolveChannelDefaultAccountId({ plugin, cfg }); } @@ -122,7 +132,9 @@ export function parseBindingSpecs(params: { const agentId = normalizeAgentId(params.agentId); for (const raw of specs) { const trimmed = raw?.trim(); - if (!trimmed) continue; + if (!trimmed) { + continue; + } const [channelRaw, accountRaw] = trimmed.split(":", 2); const channel = normalizeChannelId(channelRaw); if (!channel) { @@ -141,7 +153,9 @@ export function parseBindingSpecs(params: { } } const match: AgentBinding["match"] = { channel }; - if (accountId) match.accountId = accountId; + if (accountId) { + match.accountId = accountId; + } bindings.push({ agentId, match }); } return { bindings, errors }; diff --git a/src/commands/agents.commands.add.ts b/src/commands/agents.commands.add.ts index e98d1e5b38..9ee3c3742b 100644 --- a/src/commands/agents.commands.add.ts +++ b/src/commands/agents.commands.add.ts @@ -55,7 +55,9 @@ export async function agentsAddCommand( params?: { hasFlags?: boolean }, ) { const cfg = await requireValidConfig(runtime); - if (!cfg) return; + if (!cfg) { + return; + } const workspaceFlag = opts.workspace?.trim(); const nameInput = opts.name?.trim(); @@ -127,7 +129,9 @@ export async function agentsAddCommand( : { config: nextConfig, added: [], skipped: [], conflicts: [] }; await writeConfigFile(bindingResult.config); - if (!opts.json) logConfigUpdated(runtime); + if (!opts.json) { + logConfigUpdated(runtime); + } const quietRuntime = opts.json ? createQuietRuntime(runtime) : runtime; await ensureWorkspaceAndSessions(workspaceDir, quietRuntime, { skipBootstrap: Boolean(bindingResult.config.agents?.defaults?.skipBootstrap), @@ -154,7 +158,9 @@ export async function agentsAddCommand( runtime.log(`Agent: ${agentId}`); runtime.log(`Workspace: ${shortenHomePath(workspaceDir)}`); runtime.log(`Agent dir: ${shortenHomePath(agentDir)}`); - if (model) runtime.log(`Model: ${model}`); + if (model) { + runtime.log(`Model: ${model}`); + } if (bindingResult.conflicts.length > 0) { runtime.error( [ @@ -178,7 +184,9 @@ export async function agentsAddCommand( (await prompter.text({ message: "Agent name", validate: (value) => { - if (!value?.trim()) return "Required"; + if (!value?.trim()) { + return "Required"; + } const normalized = normalizeAgentId(value); if (normalized === DEFAULT_AGENT_ID) { return `"${DEFAULT_AGENT_ID}" is reserved. Choose another name.`; diff --git a/src/commands/agents.commands.delete.ts b/src/commands/agents.commands.delete.ts index a393089740..b5bd935736 100644 --- a/src/commands/agents.commands.delete.ts +++ b/src/commands/agents.commands.delete.ts @@ -22,7 +22,9 @@ export async function agentsDeleteCommand( runtime: RuntimeEnv = defaultRuntime, ) { const cfg = await requireValidConfig(runtime); - if (!cfg) return; + if (!cfg) { + return; + } const input = opts.id?.trim(); if (!input) { @@ -70,7 +72,9 @@ export async function agentsDeleteCommand( const result = pruneAgentConfig(cfg, agentId); await writeConfigFile(result.config); - if (!opts.json) logConfigUpdated(runtime); + if (!opts.json) { + logConfigUpdated(runtime); + } const quietRuntime = opts.json ? createQuietRuntime(runtime) : runtime; await moveToTrash(workspaceDir, quietRuntime); diff --git a/src/commands/agents.commands.identity.ts b/src/commands/agents.commands.identity.ts index 58fb51ecd4..e93ab74b19 100644 --- a/src/commands/agents.commands.identity.ts +++ b/src/commands/agents.commands.identity.ts @@ -71,7 +71,9 @@ export async function agentsSetIdentityCommand( runtime: RuntimeEnv = defaultRuntime, ) { const cfg = await requireValidConfig(runtime); - if (!cfg) return; + if (!cfg) { + return; + } const agentRaw = coerceTrimmed(opts.agent); const nameRaw = coerceTrimmed(opts.name); @@ -214,9 +216,19 @@ export async function agentsSetIdentityCommand( logConfigUpdated(runtime); runtime.log(`Agent: ${agentId}`); - if (nextIdentity.name) runtime.log(`Name: ${nextIdentity.name}`); - if (nextIdentity.theme) runtime.log(`Theme: ${nextIdentity.theme}`); - if (nextIdentity.emoji) runtime.log(`Emoji: ${nextIdentity.emoji}`); - if (nextIdentity.avatar) runtime.log(`Avatar: ${nextIdentity.avatar}`); - if (workspaceDir) runtime.log(`Workspace: ${shortenHomePath(workspaceDir)}`); + if (nextIdentity.name) { + runtime.log(`Name: ${nextIdentity.name}`); + } + if (nextIdentity.theme) { + runtime.log(`Theme: ${nextIdentity.theme}`); + } + if (nextIdentity.emoji) { + runtime.log(`Emoji: ${nextIdentity.emoji}`); + } + if (nextIdentity.avatar) { + runtime.log(`Avatar: ${nextIdentity.avatar}`); + } + if (workspaceDir) { + runtime.log(`Workspace: ${shortenHomePath(workspaceDir)}`); + } } diff --git a/src/commands/agents.commands.list.ts b/src/commands/agents.commands.list.ts index 084e5cb516..e9571c2365 100644 --- a/src/commands/agents.commands.list.ts +++ b/src/commands/agents.commands.list.ts @@ -27,8 +27,12 @@ function formatSummary(summary: AgentSummary) { : `${summary.id}${defaultTag}`; const identityParts = []; - if (summary.identityEmoji) identityParts.push(summary.identityEmoji); - if (summary.identityName) identityParts.push(summary.identityName); + if (summary.identityEmoji) { + identityParts.push(summary.identityEmoji); + } + if (summary.identityName) { + identityParts.push(summary.identityName); + } const identityLine = identityParts.length > 0 ? identityParts.join(" ") : null; const identitySource = summary.identitySource === "identity" @@ -43,7 +47,9 @@ function formatSummary(summary: AgentSummary) { } lines.push(` Workspace: ${shortenHomePath(summary.workspace)}`); lines.push(` Agent dir: ${shortenHomePath(summary.agentDir)}`); - if (summary.model) lines.push(` Model: ${summary.model}`); + if (summary.model) { + lines.push(` Model: ${summary.model}`); + } lines.push(` Routing rules: ${summary.bindings}`); if (summary.routes?.length) { @@ -70,7 +76,9 @@ export async function agentsListCommand( runtime: RuntimeEnv = defaultRuntime, ) { const cfg = await requireValidConfig(runtime); - if (!cfg) return; + if (!cfg) { + return; + } const summaries = buildAgentSummaries(cfg); const bindingMap = new Map(); @@ -107,7 +115,9 @@ export async function agentsListCommand( bindings, providerStatus, }); - if (providerLines.length > 0) summary.providers = providerLines; + if (providerLines.length > 0) { + summary.providers = providerLines; + } } if (opts.json) { diff --git a/src/commands/agents.config.ts b/src/commands/agents.config.ts index 09af312870..5647ea990f 100644 --- a/src/commands/agents.config.ts +++ b/src/commands/agents.config.ts @@ -34,7 +34,9 @@ export type AgentIdentity = AgentIdentityFile; export function listAgentEntries(cfg: OpenClawConfig): AgentEntry[] { const list = cfg.agents?.list; - if (!Array.isArray(list)) return []; + if (!Array.isArray(list)) { + return []; + } return list.filter((entry): entry is AgentEntry => Boolean(entry && typeof entry === "object")); } @@ -60,11 +62,15 @@ function resolveAgentModel(cfg: OpenClawConfig, agentId: string) { } if (typeof entry.model === "object") { const primary = entry.model.primary?.trim(); - if (primary) return primary; + if (primary) { + return primary; + } } } const raw = cfg.agents?.defaults?.model; - if (typeof raw === "string") return raw; + if (typeof raw === "string") { + return raw; + } return raw?.primary?.trim() || undefined; } @@ -74,7 +80,9 @@ export function parseIdentityMarkdown(content: string): AgentIdentity { export function loadAgentIdentity(workspace: string): AgentIdentity | null { const parsed = loadAgentIdentityFromWorkspace(workspace); - if (!parsed) return null; + if (!parsed) { + return null; + } return identityHasValues(parsed) ? parsed : null; } diff --git a/src/commands/agents.providers.ts b/src/commands/agents.providers.ts index f968f74b59..0d53a62051 100644 --- a/src/commands/agents.providers.ts +++ b/src/commands/agents.providers.ts @@ -93,13 +93,17 @@ export async function buildProviderStatusIndex( function resolveDefaultAccountId(cfg: OpenClawConfig, provider: ChannelId): string { const plugin = getChannelPlugin(provider); - if (!plugin) return DEFAULT_ACCOUNT_ID; + if (!plugin) { + return DEFAULT_ACCOUNT_ID; + } return resolveChannelDefaultAccountId({ plugin, cfg }); } function shouldShowProviderEntry(entry: ProviderAccountStatus, cfg: OpenClawConfig): boolean { const plugin = getChannelPlugin(entry.provider); - if (!plugin) return Boolean(entry.configured); + if (!plugin) { + return Boolean(entry.configured); + } if (plugin.meta.showConfigured === false) { const providerConfig = (cfg as Record)[plugin.id]; return Boolean(entry.configured) || Boolean(providerConfig); @@ -117,11 +121,15 @@ function formatProviderEntry(entry: ProviderAccountStatus): string { } export function summarizeBindings(cfg: OpenClawConfig, bindings: AgentBinding[]): string[] { - if (bindings.length === 0) return []; + if (bindings.length === 0) { + return []; + } const seen = new Map(); for (const binding of bindings) { const channel = normalizeChannelId(binding.match.channel); - if (!channel) continue; + if (!channel) { + continue; + } const accountId = binding.match.accountId ?? resolveDefaultAccountId(cfg, channel); const key = providerAccountKey(channel, accountId); if (!seen.has(key)) { @@ -147,10 +155,14 @@ export function listProvidersForAgent(params: { const seen = new Set(); for (const binding of params.bindings) { const channel = normalizeChannelId(binding.match.channel); - if (!channel) continue; + if (!channel) { + continue; + } const accountId = binding.match.accountId ?? resolveDefaultAccountId(params.cfg, channel); const key = providerAccountKey(channel, accountId); - if (seen.has(key)) continue; + if (seen.has(key)) { + continue; + } seen.add(key); const status = params.providerStatus.get(key); if (status) { diff --git a/src/commands/auth-choice.api-key.ts b/src/commands/auth-choice.api-key.ts index 6042b6fb5a..59a7ca08e6 100644 --- a/src/commands/auth-choice.api-key.ts +++ b/src/commands/auth-choice.api-key.ts @@ -2,7 +2,9 @@ const DEFAULT_KEY_PREVIEW = { head: 4, tail: 4 }; export function normalizeApiKeyInput(raw: string): string { const trimmed = String(raw ?? "").trim(); - if (!trimmed) return ""; + if (!trimmed) { + return ""; + } // Handle shell-style assignments: export KEY="value" or KEY=value const assignmentMatch = trimmed.match(/^(?:export\s+)?[A-Za-z_][A-Za-z0-9_]*\s*=\s*(.+)$/); @@ -29,7 +31,9 @@ export function formatApiKeyPreview( opts: { head?: number; tail?: number } = {}, ): string { const trimmed = raw.trim(); - if (!trimmed) return "…"; + if (!trimmed) { + return "…"; + } const head = opts.head ?? DEFAULT_KEY_PREVIEW.head; const tail = opts.tail ?? DEFAULT_KEY_PREVIEW.tail; if (trimmed.length <= head + tail) { diff --git a/src/commands/auth-choice.apply.api-providers.ts b/src/commands/auth-choice.apply.api-providers.ts index b35b4f68b6..001517b4b0 100644 --- a/src/commands/auth-choice.apply.api-providers.ts +++ b/src/commands/auth-choice.apply.api-providers.ts @@ -57,7 +57,9 @@ export async function applyAuthChoiceApiProviders( let nextConfig = params.config; let agentModelOverride: string | undefined; const noteAgentModel = async (model: string) => { - if (!params.agentId) return; + if (!params.agentId) { + return; + } await params.prompter.note( `Default model set to ${model} for agent "${params.agentId}".`, "Model configured", diff --git a/src/commands/auth-choice.apply.github-copilot.ts b/src/commands/auth-choice.apply.github-copilot.ts index 30a1591b2e..cd67ae1cbd 100644 --- a/src/commands/auth-choice.apply.github-copilot.ts +++ b/src/commands/auth-choice.apply.github-copilot.ts @@ -5,7 +5,9 @@ import { applyAuthProfileConfig } from "./onboard-auth.js"; export async function applyAuthChoiceGitHubCopilot( params: ApplyAuthChoiceParams, ): Promise { - if (params.authChoice !== "github-copilot") return null; + if (params.authChoice !== "github-copilot") { + return null; + } let nextConfig = params.config; diff --git a/src/commands/auth-choice.apply.minimax.ts b/src/commands/auth-choice.apply.minimax.ts index bf69179ca5..25a13bc233 100644 --- a/src/commands/auth-choice.apply.minimax.ts +++ b/src/commands/auth-choice.apply.minimax.ts @@ -21,7 +21,9 @@ export async function applyAuthChoiceMiniMax( let nextConfig = params.config; let agentModelOverride: string | undefined; const noteAgentModel = async (model: string) => { - if (!params.agentId) return; + if (!params.agentId) { + return; + } await params.prompter.note( `Default model set to ${model} for agent "${params.agentId}".`, "Model configured", diff --git a/src/commands/auth-choice.apply.openai.ts b/src/commands/auth-choice.apply.openai.ts index 8319855a2c..9ecf42f079 100644 --- a/src/commands/auth-choice.apply.openai.ts +++ b/src/commands/auth-choice.apply.openai.ts @@ -74,7 +74,9 @@ export async function applyAuthChoiceOpenAI( let nextConfig = params.config; let agentModelOverride: string | undefined; const noteAgentModel = async (model: string) => { - if (!params.agentId) return; + if (!params.agentId) { + return; + } await params.prompter.note( `Default model set to ${model} for agent "${params.agentId}".`, "Model configured", diff --git a/src/commands/auth-choice.apply.plugin-provider.ts b/src/commands/auth-choice.apply.plugin-provider.ts index 4deb89c9d6..098e2fff36 100644 --- a/src/commands/auth-choice.apply.plugin-provider.ts +++ b/src/commands/auth-choice.apply.plugin-provider.ts @@ -42,7 +42,9 @@ function resolveProviderMatch( function pickAuthMethod(provider: ProviderPlugin, rawMethod?: string): ProviderAuthMethod | null { const raw = rawMethod?.trim(); - if (!raw) return null; + if (!raw) { + return null; + } const normalized = raw.toLowerCase(); return ( provider.auth.find((method) => method.id.toLowerCase() === normalized) ?? @@ -99,7 +101,9 @@ export async function applyAuthChoicePluginProvider( params: ApplyAuthChoiceParams, options: PluginProviderAuthChoiceOptions, ): Promise { - if (params.authChoice !== options.authChoice) return null; + if (params.authChoice !== options.authChoice) { + return null; + } const enableResult = enablePluginInConfig(params.config, options.pluginId); let nextConfig = enableResult.config; diff --git a/src/commands/auth-choice.apply.ts b/src/commands/auth-choice.apply.ts index c36a3981a1..5abd026f44 100644 --- a/src/commands/auth-choice.apply.ts +++ b/src/commands/auth-choice.apply.ts @@ -50,7 +50,9 @@ export async function applyAuthChoice( for (const handler of handlers) { const result = await handler(params); - if (result) return result; + if (result) { + return result; + } } return { config: params.config }; diff --git a/src/commands/auth-choice.test.ts b/src/commands/auth-choice.test.ts index cadf962b10..d36423c7ea 100644 --- a/src/commands/auth-choice.test.ts +++ b/src/commands/auth-choice.test.ts @@ -23,7 +23,9 @@ const noop = () => {}; const authProfilePathFor = (agentDir: string) => path.join(agentDir, "auth-profiles.json"); const requireAgentDir = () => { const agentDir = process.env.OPENCLAW_AGENT_DIR; - if (!agentDir) throw new Error("OPENCLAW_AGENT_DIR not set"); + if (!agentDir) { + throw new Error("OPENCLAW_AGENT_DIR not set"); + } return agentDir; }; diff --git a/src/commands/auth-token.ts b/src/commands/auth-token.ts index 4527b14949..d003c2aa1b 100644 --- a/src/commands/auth-token.ts +++ b/src/commands/auth-token.ts @@ -6,7 +6,9 @@ export const DEFAULT_TOKEN_PROFILE_NAME = "default"; export function normalizeTokenProfileName(raw: string): string { const trimmed = raw.trim(); - if (!trimmed) return DEFAULT_TOKEN_PROFILE_NAME; + if (!trimmed) { + return DEFAULT_TOKEN_PROFILE_NAME; + } const slug = trimmed .toLowerCase() .replace(/[^a-z0-9._-]+/g, "-") @@ -23,7 +25,9 @@ export function buildTokenProfileId(params: { provider: string; name: string }): export function validateAnthropicSetupToken(raw: string): string | undefined { const trimmed = raw.trim(); - if (!trimmed) return "Required"; + if (!trimmed) { + return "Required"; + } if (!trimmed.startsWith(ANTHROPIC_SETUP_TOKEN_PREFIX)) { return `Expected token starting with ${ANTHROPIC_SETUP_TOKEN_PREFIX}`; } diff --git a/src/commands/channels/add-mutators.ts b/src/commands/channels/add-mutators.ts index f6d9d3a565..1ec280cfc1 100644 --- a/src/commands/channels/add-mutators.ts +++ b/src/commands/channels/add-mutators.ts @@ -56,7 +56,9 @@ export function applyChannelAccountConfig(params: { const accountId = normalizeAccountId(params.accountId); const plugin = getChannelPlugin(params.channel); const apply = plugin?.setup?.applyAccountConfig; - if (!apply) return params.cfg; + if (!apply) { + return params.cfg; + } const input: ChannelSetupInput = { name: params.name, token: params.token, diff --git a/src/commands/channels/add.ts b/src/commands/channels/add.ts index 39c0842318..524f86ceee 100644 --- a/src/commands/channels/add.ts +++ b/src/commands/channels/add.ts @@ -52,7 +52,9 @@ export type ChannelsAddOptions = { }; function parseList(value: string | undefined): string[] | undefined { - if (!value?.trim()) return undefined; + if (!value?.trim()) { + return undefined; + } const parsed = value .split(/[\n,;]+/g) .map((entry) => entry.trim()) @@ -62,10 +64,14 @@ function parseList(value: string | undefined): string[] | undefined { function resolveCatalogChannelEntry(raw: string, cfg: OpenClawConfig | null) { const trimmed = raw.trim().toLowerCase(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } const workspaceDir = cfg ? resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg)) : undefined; return listChannelPluginCatalogEntries({ workspaceDir }).find((entry) => { - if (entry.id.toLowerCase() === trimmed) return true; + if (entry.id.toLowerCase() === trimmed) { + return true; + } return (entry.meta.aliases ?? []).some((alias) => alias.trim().toLowerCase() === trimmed); }); } @@ -76,7 +82,9 @@ export async function channelsAddCommand( params?: { hasFlags?: boolean }, ) { const cfg = await requireValidConfig(runtime); - if (!cfg) return; + if (!cfg) { + return; + } let nextConfig = cfg; const useWizard = shouldUseWizard(params); @@ -149,7 +157,9 @@ export async function channelsAddCommand( workspaceDir, }); nextConfig = result.cfg; - if (!result.installed) return; + if (!result.installed) { + return; + } reloadOnboardingPluginRegistry({ cfg: nextConfig, runtime, workspaceDir }); channel = normalizeChannelId(catalogEntry.id) ?? (catalogEntry.id as ChannelId); } diff --git a/src/commands/channels/capabilities.ts b/src/commands/channels/capabilities.ts index 423b02f65a..968108ac1c 100644 --- a/src/commands/channels/capabilities.ts +++ b/src/commands/channels/capabilities.ts @@ -67,34 +67,64 @@ const TEAMS_GRAPH_PERMISSION_HINTS: Record = { function normalizeTimeout(raw: unknown, fallback = 10_000) { const value = typeof raw === "string" ? Number(raw) : Number(raw); - if (!Number.isFinite(value) || value <= 0) return fallback; + if (!Number.isFinite(value) || value <= 0) { + return fallback; + } return value; } function formatSupport(capabilities?: ChannelCapabilities) { - if (!capabilities) return "unknown"; + if (!capabilities) { + return "unknown"; + } const bits: string[] = []; if (capabilities.chatTypes?.length) { bits.push(`chatTypes=${capabilities.chatTypes.join(",")}`); } - if (capabilities.polls) bits.push("polls"); - if (capabilities.reactions) bits.push("reactions"); - if (capabilities.edit) bits.push("edit"); - if (capabilities.unsend) bits.push("unsend"); - if (capabilities.reply) bits.push("reply"); - if (capabilities.effects) bits.push("effects"); - if (capabilities.groupManagement) bits.push("groupManagement"); - if (capabilities.threads) bits.push("threads"); - if (capabilities.media) bits.push("media"); - if (capabilities.nativeCommands) bits.push("nativeCommands"); - if (capabilities.blockStreaming) bits.push("blockStreaming"); + if (capabilities.polls) { + bits.push("polls"); + } + if (capabilities.reactions) { + bits.push("reactions"); + } + if (capabilities.edit) { + bits.push("edit"); + } + if (capabilities.unsend) { + bits.push("unsend"); + } + if (capabilities.reply) { + bits.push("reply"); + } + if (capabilities.effects) { + bits.push("effects"); + } + if (capabilities.groupManagement) { + bits.push("groupManagement"); + } + if (capabilities.threads) { + bits.push("threads"); + } + if (capabilities.media) { + bits.push("media"); + } + if (capabilities.nativeCommands) { + bits.push("nativeCommands"); + } + if (capabilities.blockStreaming) { + bits.push("blockStreaming"); + } return bits.length ? bits.join(" ") : "none"; } function summarizeDiscordTarget(raw?: string): DiscordTargetSummary | undefined { - if (!raw) return undefined; + if (!raw) { + return undefined; + } const target = parseDiscordTarget(raw, { defaultKind: "channel" }); - if (!target) return { raw }; + if (!target) { + return { raw }; + } if (target.kind === "channel") { return { raw, @@ -118,7 +148,9 @@ function formatDiscordIntents(intents?: { guildMembers?: string; presence?: string; }) { - if (!intents) return "unknown"; + if (!intents) { + return "unknown"; + } return [ `messageContent=${intents.messageContent ?? "unknown"}`, `guildMembers=${intents.guildMembers ?? "unknown"}`, @@ -128,7 +160,9 @@ function formatDiscordIntents(intents?: { function formatProbeLines(channelId: string, probe: unknown): string[] { const lines: string[] = []; - if (!probe || typeof probe !== "object") return lines; + if (!probe || typeof probe !== "object") { + return lines; + } const probeObj = probe as Record; if (channelId === "discord") { @@ -155,10 +189,18 @@ function formatProbeLines(channelId: string, probe: unknown): string[] { ?.canReadAllGroupMessages; const inlineQueries = (bot as { supportsInlineQueries?: boolean | null }) ?.supportsInlineQueries; - if (typeof canJoinGroups === "boolean") flags.push(`joinGroups=${canJoinGroups}`); - if (typeof canReadAll === "boolean") flags.push(`readAllGroupMessages=${canReadAll}`); - if (typeof inlineQueries === "boolean") flags.push(`inlineQueries=${inlineQueries}`); - if (flags.length > 0) lines.push(`Flags: ${flags.join(" ")}`); + if (typeof canJoinGroups === "boolean") { + flags.push(`joinGroups=${canJoinGroups}`); + } + if (typeof canReadAll === "boolean") { + flags.push(`readAllGroupMessages=${canReadAll}`); + } + if (typeof inlineQueries === "boolean") { + flags.push(`inlineQueries=${inlineQueries}`); + } + if (flags.length > 0) { + lines.push(`Flags: ${flags.join(" ")}`); + } const webhook = probeObj.webhook as { url?: string | null } | undefined; if (webhook?.url !== undefined) { lines.push(`Webhook: ${webhook.url || "none"}`); @@ -186,7 +228,9 @@ function formatProbeLines(channelId: string, probe: unknown): string[] { if (channelId === "msteams") { const appId = typeof probeObj.appId === "string" ? probeObj.appId.trim() : ""; - if (appId) lines.push(`App: ${theme.accent(appId)}`); + if (appId) { + lines.push(`App: ${theme.accent(appId)}`); + } const graph = probeObj.graph as | { ok?: boolean; roles?: unknown; scopes?: unknown; error?: string } | undefined; @@ -239,7 +283,9 @@ async function buildDiscordPermissions(params: { target?: string; }): Promise<{ target?: DiscordTargetSummary; report?: DiscordPermissionsReport }> { const target = summarizeDiscordTarget(params.target?.trim()); - if (!target) return {}; + if (!target) { + return {}; + } if (target.kind !== "channel" || !target.channelId) { return { target, @@ -395,7 +441,9 @@ export async function channelsCapabilitiesCommand( runtime: RuntimeEnv = defaultRuntime, ) { const cfg = await requireValidConfig(runtime); - if (!cfg) return; + if (!cfg) { + return; + } const timeoutMs = normalizeTimeout(opts.timeout, 10_000); const rawChannel = typeof opts.channel === "string" ? opts.channel.trim().toLowerCase() : ""; const rawTarget = typeof opts.target === "string" ? opts.target.trim() : ""; @@ -417,7 +465,9 @@ export async function channelsCapabilitiesCommand( ? plugins : (() => { const plugin = getChannelPlugin(rawChannel); - if (!plugin) return null; + if (!plugin) { + return null; + } return [plugin]; })(); diff --git a/src/commands/channels/list.ts b/src/commands/channels/list.ts index bd707e4e09..06caefb3d9 100644 --- a/src/commands/channels/list.ts +++ b/src/commands/channels/list.ts @@ -15,8 +15,12 @@ export type ChannelsListOptions = { }; const colorValue = (value: string) => { - if (value === "none") return theme.error(value); - if (value === "env") return theme.accent(value); + if (value === "none") { + return theme.error(value); + } + if (value === "env") { + return theme.accent(value); + } return theme.success(value); }; @@ -101,7 +105,9 @@ export async function channelsListCommand( runtime: RuntimeEnv = defaultRuntime, ) { const cfg = await requireValidConfig(runtime); - if (!cfg) return; + if (!cfg) { + return; + } const includeUsage = opts.usage !== false; const plugins = listChannelPlugins(); @@ -129,7 +135,9 @@ export async function channelsListCommand( for (const plugin of plugins) { const accounts = plugin.config.listAccountIds(cfg); - if (!accounts || accounts.length === 0) continue; + if (!accounts || accounts.length === 0) { + continue; + } for (const accountId of accounts) { const snapshot = await buildChannelAccountSnapshot({ plugin, diff --git a/src/commands/channels/logs.ts b/src/commands/channels/logs.ts index 9474f2479b..4a910efd40 100644 --- a/src/commands/channels/logs.ts +++ b/src/commands/channels/logs.ts @@ -21,32 +21,46 @@ const getChannelSet = () => function parseChannelFilter(raw?: string) { const trimmed = raw?.trim().toLowerCase(); - if (!trimmed) return "all"; + if (!trimmed) { + return "all"; + } return getChannelSet().has(trimmed) ? trimmed : "all"; } function matchesChannel(line: NonNullable, channel: string) { - if (channel === "all") return true; + if (channel === "all") { + return true; + } const needle = `gateway/channels/${channel}`; - if (line.subsystem?.includes(needle)) return true; - if (line.module?.includes(channel)) return true; + if (line.subsystem?.includes(needle)) { + return true; + } + if (line.module?.includes(channel)) { + return true; + } return false; } async function readTailLines(file: string, limit: number): Promise { const stat = await fs.stat(file).catch(() => null); - if (!stat) return []; + if (!stat) { + return []; + } const size = stat.size; const start = Math.max(0, size - MAX_BYTES); const handle = await fs.open(file, "r"); try { const length = Math.max(0, size - start); - if (length === 0) return []; + if (length === 0) { + return []; + } const buffer = Buffer.alloc(length); const readResult = await handle.read(buffer, 0, length, start); const text = buffer.toString("utf8", 0, readResult.bytesRead); let lines = text.split("\n"); - if (start > 0) lines = lines.slice(1); + if (start > 0) { + lines = lines.slice(1); + } if (lines.length && lines[lines.length - 1] === "") { lines = lines.slice(0, -1); } diff --git a/src/commands/channels/remove.ts b/src/commands/channels/remove.ts index e0f61c699d..b0700c5566 100644 --- a/src/commands/channels/remove.ts +++ b/src/commands/channels/remove.ts @@ -18,7 +18,9 @@ export type ChannelsRemoveOptions = { function listAccountIds(cfg: OpenClawConfig, channel: ChatChannel): string[] { const plugin = getChannelPlugin(channel); - if (!plugin) return []; + if (!plugin) { + return []; + } return plugin.config.listAccountIds(cfg); } @@ -28,7 +30,9 @@ export async function channelsRemoveCommand( params?: { hasFlags?: boolean }, ) { const cfg = await requireValidConfig(runtime); - if (!cfg) return; + if (!cfg) { + return; + } const useWizard = shouldUseWizard(params); const prompter = useWizard ? createClackPrompter() : null; diff --git a/src/commands/channels/resolve.ts b/src/commands/channels/resolve.ts index e2944a9722..9bcd633a0e 100644 --- a/src/commands/channels/resolve.ts +++ b/src/commands/channels/resolve.ts @@ -25,17 +25,29 @@ type ResolveResult = { function resolvePreferredKind( kind?: ChannelsResolveOptions["kind"], ): ChannelResolveKind | undefined { - if (!kind || kind === "auto") return undefined; - if (kind === "user") return "user"; + if (!kind || kind === "auto") { + return undefined; + } + if (kind === "user") { + return "user"; + } return "group"; } function detectAutoKind(input: string): ChannelResolveKind { const trimmed = input.trim(); - if (!trimmed) return "group"; - if (trimmed.startsWith("@")) return "user"; - if (/^<@!?/.test(trimmed)) return "user"; - if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) return "user"; + if (!trimmed) { + return "group"; + } + if (trimmed.startsWith("@")) { + return "user"; + } + if (/^<@!?/.test(trimmed)) { + return "user"; + } + if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) { + return "user"; + } if ( /^(user|discord|slack|matrix|msteams|teams|zalo|zalouser|googlechat|google-chat|gchat):/i.test( trimmed, @@ -47,7 +59,9 @@ function detectAutoKind(input: string): ChannelResolveKind { } function formatResolveResult(result: ResolveResult): string { - if (!result.resolved || !result.id) return `${result.input} -> unresolved`; + if (!result.resolved || !result.id) { + return `${result.input} -> unresolved`; + } const name = result.name ? ` (${result.name})` : ""; const note = result.note ? ` [${result.note}]` : ""; return `${result.input} -> ${result.id}${name}${note}`; diff --git a/src/commands/channels/shared.ts b/src/commands/channels/shared.ts index 770ba1cdc6..a2659ac2da 100644 --- a/src/commands/channels/shared.ts +++ b/src/commands/channels/shared.ts @@ -25,7 +25,9 @@ export async function requireValidConfig( export function formatAccountLabel(params: { accountId: string; name?: string }) { const base = params.accountId || DEFAULT_ACCOUNT_ID; - if (params.name?.trim()) return `${base} (${params.name.trim()})`; + if (params.name?.trim()) { + return `${base} (${params.name.trim()})`; + } return base; } diff --git a/src/commands/channels/status.ts b/src/commands/channels/status.ts index 95e9ef134b..bc45b221d2 100644 --- a/src/commands/channels/status.ts +++ b/src/commands/channels/status.ts @@ -47,8 +47,12 @@ export function formatGatewayChannelsStatusLines(payload: Record 0) { bits.push(`mode:${account.mode}`); } @@ -56,9 +60,13 @@ export function formatGatewayChannelsStatusLines(payload: Record>) => accounts.map((account) => { @@ -206,7 +216,9 @@ async function formatConfigChannelsStatusLines( const plugins = listChannelPlugins(); for (const plugin of plugins) { const accountIds = plugin.config.listAccountIds(cfg); - if (!accountIds.length) continue; + if (!accountIds.length) { + continue; + } const snapshots: ChannelAccountSnapshot[] = []; for (const accountId of accountIds) { const snapshot = await buildChannelAccountSnapshot({ @@ -235,7 +247,9 @@ export async function channelsStatusCommand( const timeoutMs = Number(opts.timeout ?? 10_000); const statusLabel = opts.probe ? "Checking channel status (probe)…" : "Checking channel status…"; const shouldLogStatus = opts.json !== true && !process.stderr.isTTY; - if (shouldLogStatus) runtime.log(statusLabel); + if (shouldLogStatus) { + runtime.log(statusLabel); + } try { const payload = await withProgress( { @@ -258,7 +272,9 @@ export async function channelsStatusCommand( } catch (err) { runtime.error(`Gateway not reachable: ${String(err)}`); const cfg = await requireValidConfig(runtime); - if (!cfg) return; + if (!cfg) { + return; + } const snapshot = await readConfigFileSnapshot(); const mode = cfg.gateway?.mode === "remote" ? "remote" : "local"; runtime.log( diff --git a/src/commands/chutes-oauth.ts b/src/commands/chutes-oauth.ts index c322f2e0d1..5e98e05721 100644 --- a/src/commands/chutes-oauth.ts +++ b/src/commands/chutes-oauth.ts @@ -87,18 +87,24 @@ async function waitForLocalCallback(params: { "

You can close this window and return to OpenClaw.

", ].join(""), ); - if (timeout) clearTimeout(timeout); + if (timeout) { + clearTimeout(timeout); + } server.close(); resolve({ code, state }); } catch (err) { - if (timeout) clearTimeout(timeout); + if (timeout) { + clearTimeout(timeout); + } server.close(); reject(err); } }); server.once("error", (err) => { - if (timeout) clearTimeout(timeout); + if (timeout) { + clearTimeout(timeout); + } server.close(); reject(err); }); @@ -150,8 +156,12 @@ export async function loginChutes(params: { placeholder: `${params.app.redirectUri}?code=...&state=...`, }); const parsed = parseOAuthCallbackInput(String(input), state); - if ("error" in parsed) throw new Error(parsed.error); - if (parsed.state !== state) throw new Error("Invalid OAuth state"); + if ("error" in parsed) { + throw new Error(parsed.error); + } + if (parsed.state !== state) { + throw new Error("Invalid OAuth state"); + } codeAndState = parsed; } else { const callback = waitForLocalCallback({ @@ -166,8 +176,12 @@ export async function loginChutes(params: { placeholder: `${params.app.redirectUri}?code=...&state=...`, }); const parsed = parseOAuthCallbackInput(String(input), state); - if ("error" in parsed) throw new Error(parsed.error); - if (parsed.state !== state) throw new Error("Invalid OAuth state"); + if ("error" in parsed) { + throw new Error(parsed.error); + } + if (parsed.state !== state) { + throw new Error("Invalid OAuth state"); + } return parsed; }); diff --git a/src/commands/cleanup-utils.ts b/src/commands/cleanup-utils.ts index c265e80df5..f4d235fac7 100644 --- a/src/commands/cleanup-utils.ts +++ b/src/commands/cleanup-utils.ts @@ -36,12 +36,18 @@ export function isPathWithin(child: string, parent: string): boolean { } function isUnsafeRemovalTarget(target: string): boolean { - if (!target.trim()) return true; + if (!target.trim()) { + return true; + } const resolved = path.resolve(target); const root = path.parse(resolved).root; - if (resolved === root) return true; + if (resolved === root) { + return true; + } const home = resolveHomeDir(); - if (home && resolved === path.resolve(home)) return true; + if (home && resolved === path.resolve(home)) { + return true; + } return false; } @@ -50,7 +56,9 @@ export async function removePath( runtime: RuntimeEnv, opts?: { dryRun?: boolean; label?: string }, ): Promise { - if (!target?.trim()) return { ok: false, skipped: true }; + if (!target?.trim()) { + return { ok: false, skipped: true }; + } const resolved = path.resolve(target); const label = opts?.label ?? resolved; const displayLabel = shortenHomeInString(label); diff --git a/src/commands/configure.channels.ts b/src/commands/configure.channels.ts index 5a7ffb01a2..7170fa88eb 100644 --- a/src/commands/configure.channels.ts +++ b/src/commands/configure.channels.ts @@ -47,7 +47,9 @@ export async function removeChannelConfigWizard( runtime, ); - if (channel === "done") return next; + if (channel === "done") { + return next; + } const label = getChannelPlugin(channel)?.meta.label ?? channel; const confirmed = guardCancel( @@ -57,7 +59,9 @@ export async function removeChannelConfigWizard( }), runtime, ); - if (!confirmed) continue; + if (!confirmed) { + continue; + } const nextChannels: Record = { ...next.channels }; delete nextChannels[channel]; diff --git a/src/commands/configure.daemon.ts b/src/commands/configure.daemon.ts index 1f47c6149f..3cf45b66e7 100644 --- a/src/commands/configure.daemon.ts +++ b/src/commands/configure.daemon.ts @@ -51,7 +51,9 @@ export async function maybeInstallDaemon(params: { shouldCheckLinger = true; shouldInstall = false; } - if (action === "skip") return; + if (action === "skip") { + return; + } if (action === "reinstall") { await withProgress( { label: "Gateway service", indeterminate: true, delayMs: 0 }, diff --git a/src/commands/configure.gateway-auth.ts b/src/commands/configure.gateway-auth.ts index 508ed6cf05..01ac0d9a0d 100644 --- a/src/commands/configure.gateway-auth.ts +++ b/src/commands/configure.gateway-auth.ts @@ -28,7 +28,9 @@ export function buildGatewayAuthConfig(params: { }): GatewayAuthConfig | undefined { const allowTailscale = params.existing?.allowTailscale; const base: GatewayAuthConfig = {}; - if (typeof allowTailscale === "boolean") base.allowTailscale = allowTailscale; + if (typeof allowTailscale === "boolean") { + base.allowTailscale = allowTailscale; + } if (params.mode === "token") { return { ...base, mode: "token", token: params.token }; diff --git a/src/commands/configure.gateway.ts b/src/commands/configure.gateway.ts index a0fc2f4c81..398321c605 100644 --- a/src/commands/configure.gateway.ts +++ b/src/commands/configure.gateway.ts @@ -68,17 +68,22 @@ export async function promptGatewayConfig( message: "Custom IP address", placeholder: "192.168.1.100", validate: (value) => { - if (!value) return "IP address is required for custom bind mode"; + if (!value) { + return "IP address is required for custom bind mode"; + } const trimmed = value.trim(); const parts = trimmed.split("."); - if (parts.length !== 4) return "Invalid IPv4 address (e.g., 192.168.1.100)"; + if (parts.length !== 4) { + return "Invalid IPv4 address (e.g., 192.168.1.100)"; + } if ( parts.every((part) => { const n = parseInt(part, 10); return !Number.isNaN(n) && n >= 0 && n <= 255 && part === String(n); }) - ) + ) { return undefined; + } return "Invalid IPv4 address (each octet must be 0-255)"; }, }), diff --git a/src/commands/configure.wizard.ts b/src/commands/configure.wizard.ts index 2788bc034f..3182fe17b4 100644 --- a/src/commands/configure.wizard.ts +++ b/src/commands/configure.wizard.ts @@ -406,7 +406,9 @@ export async function runConfigureWizard( while (true) { const choice = await promptConfigureSection(runtime, ranSection); - if (choice === "__continue") break; + if (choice === "__continue") { + break; + } ranSection = true; if (choice === "workspace") { diff --git a/src/commands/daemon-install-helpers.ts b/src/commands/daemon-install-helpers.ts index 1f432eddaf..6584671423 100644 --- a/src/commands/daemon-install-helpers.ts +++ b/src/commands/daemon-install-helpers.ts @@ -52,7 +52,9 @@ export async function buildGatewayInstallPlan(params: { if (params.runtime === "node") { const systemNode = await resolveSystemNodeInfo({ env: params.env }); const warning = renderSystemNodeWarning(systemNode, programArguments[0]); - if (warning) params.warn?.(warning, "Gateway runtime"); + if (warning) { + params.warn?.(warning, "Gateway runtime"); + } } const serviceEnvironment = buildServiceEnvironment({ env: params.env, diff --git a/src/commands/docs.ts b/src/commands/docs.ts index 8e50a1a332..b72498e466 100644 --- a/src/commands/docs.ts +++ b/src/commands/docs.ts @@ -26,8 +26,12 @@ type ToolRunOptions = { }; function resolveNodeRunner(): NodeRunner { - if (hasBinary("pnpm")) return { cmd: "pnpm", args: ["dlx"] }; - if (hasBinary("npx")) return { cmd: "npx", args: ["-y"] }; + if (hasBinary("pnpm")) { + return { cmd: "pnpm", args: ["dlx"] }; + } + if (hasBinary("npx")) { + return { cmd: "npx", args: ["-y"] }; + } throw new Error("Missing pnpm or npx; install a Node package runner."); } @@ -52,15 +56,21 @@ async function runTool(tool: string, toolArgs: string[], options: ToolRunOptions function extractLine(lines: string[], prefix: string): string | undefined { const line = lines.find((value) => value.startsWith(prefix)); - if (!line) return undefined; + if (!line) { + return undefined; + } return line.slice(prefix.length).trim(); } function normalizeSnippet(raw: string | undefined, fallback: string): string { const base = raw && raw.trim().length > 0 ? raw : fallback; const cleaned = base.replace(/\s+/g, " ").trim(); - if (!cleaned) return ""; - if (cleaned.length <= DEFAULT_SNIPPET_MAX) return cleaned; + if (!cleaned) { + return ""; + } + if (cleaned.length <= DEFAULT_SNIPPET_MAX) { + return cleaned; + } return `${cleaned.slice(0, DEFAULT_SNIPPET_MAX - 3)}...`; } @@ -84,7 +94,9 @@ function parseSearchOutput(raw: string): DocResult[] { const lines = block.split("\n"); const title = extractLine(lines, "Title:"); const link = extractLine(lines, "Link:"); - if (!title || !link) continue; + if (!title || !link) { + continue; + } const content = extractLine(lines, "Content:"); const contentIndex = lines.findIndex((line) => line.startsWith("Content:")); const body = diff --git a/src/commands/doctor-auth.deprecated-cli-profiles.test.ts b/src/commands/doctor-auth.deprecated-cli-profiles.test.ts index 3de0c9a026..38d01e8bab 100644 --- a/src/commands/doctor-auth.deprecated-cli-profiles.test.ts +++ b/src/commands/doctor-auth.deprecated-cli-profiles.test.ts @@ -50,7 +50,9 @@ afterEach(() => { describe("maybeRemoveDeprecatedCliAuthProfiles", () => { it("removes deprecated CLI auth profiles from store + config", async () => { - if (!tempAgentDir) throw new Error("Missing temp agent dir"); + if (!tempAgentDir) { + throw new Error("Missing temp agent dir"); + } const authPath = path.join(tempAgentDir, "auth-profiles.json"); fs.writeFileSync( authPath, diff --git a/src/commands/doctor-auth.ts b/src/commands/doctor-auth.ts index fc62a21c2a..c735c350cd 100644 --- a/src/commands/doctor-auth.ts +++ b/src/commands/doctor-auth.ts @@ -28,14 +28,18 @@ export async function maybeRepairAnthropicOAuthProfileId( provider: "anthropic", legacyProfileId: "anthropic:default", }); - if (!repair.migrated || repair.changes.length === 0) return cfg; + if (!repair.migrated || repair.changes.length === 0) { + return cfg; + } note(repair.changes.map((c) => `- ${c}`).join("\n"), "Auth profiles"); const apply = await prompter.confirm({ message: "Update Anthropic OAuth profile id in config now?", initialValue: true, }); - if (!apply) return cfg; + if (!apply) { + return cfg; + } return repair.config; } @@ -43,13 +47,19 @@ function pruneAuthOrder( order: Record | undefined, profileIds: Set, ): { next: Record | undefined; changed: boolean } { - if (!order) return { next: order, changed: false }; + if (!order) { + return { next: order, changed: false }; + } let changed = false; const next: Record = {}; for (const [provider, list] of Object.entries(order)) { const filtered = list.filter((id) => !profileIds.has(id)); - if (filtered.length !== list.length) changed = true; - if (filtered.length > 0) next[provider] = filtered; + if (filtered.length !== list.length) { + changed = true; + } + if (filtered.length > 0) { + next[provider] = filtered; + } } return { next: Object.keys(next).length > 0 ? next : undefined, changed }; } @@ -73,9 +83,13 @@ function pruneAuthProfiles( } const prunedOrder = pruneAuthOrder(order, profileIds); - if (prunedOrder.changed) changed = true; + if (prunedOrder.changed) { + changed = true; + } - if (!changed) return { next: cfg, changed: false }; + if (!changed) { + return { next: cfg, changed: false }; + } const nextAuth = nextProfiles || prunedOrder.next @@ -108,7 +122,9 @@ export async function maybeRemoveDeprecatedCliAuthProfiles( deprecated.add(CODEX_CLI_PROFILE_ID); } - if (deprecated.size === 0) return cfg; + if (deprecated.size === 0) { + return cfg; + } const lines = ["Deprecated external CLI auth profiles detected (no longer supported):"]; if (deprecated.has(CLAUDE_CLI_PROFILE_ID)) { @@ -129,7 +145,9 @@ export async function maybeRemoveDeprecatedCliAuthProfiles( message: "Remove deprecated CLI auth profiles now?", initialValue: true, }); - if (!shouldRemove) return cfg; + if (!shouldRemove) { + return cfg; + } await updateAuthProfileStoreWithLock({ updater: (nextStore) => { @@ -222,7 +240,9 @@ export async function noteAuthProfileHealth(params: { const out: string[] = []; for (const profileId of Object.keys(store.usageStats ?? {})) { const until = resolveProfileUnusableUntilForDisplay(store, profileId); - if (!until || now >= until) continue; + if (!until || now >= until) { + continue; + } const stats = store.usageStats?.[profileId]; const remaining = formatRemainingShort(until - now); const kind = @@ -257,7 +277,9 @@ export async function noteAuthProfileHealth(params: { ); let issues = findIssues(); - if (issues.length === 0) return; + if (issues.length === 0) { + return; + } const shouldRefresh = await params.prompter.confirmRepair({ message: "Refresh expiring OAuth tokens now? (static tokens need re-auth)", diff --git a/src/commands/doctor-config-flow.ts b/src/commands/doctor-config-flow.ts index d453f1a2bd..516bba716c 100644 --- a/src/commands/doctor-config-flow.ts +++ b/src/commands/doctor-config-flow.ts @@ -36,7 +36,9 @@ function isUnrecognizedKeysIssue(issue: ZodIssue): issue is UnrecognizedKeysIssu } function formatPath(parts: Array): string { - if (parts.length === 0) return ""; + if (parts.length === 0) { + return ""; + } let out = ""; for (const part of parts) { if (typeof part === "number") { @@ -52,14 +54,22 @@ function resolvePathTarget(root: unknown, path: Array): unknown let current: unknown = root; for (const part of path) { if (typeof part === "number") { - if (!Array.isArray(current)) return null; - if (part < 0 || part >= current.length) return null; + if (!Array.isArray(current)) { + return null; + } + if (part < 0 || part >= current.length) { + return null; + } current = current[part]; continue; } - if (!current || typeof current !== "object" || Array.isArray(current)) return null; + if (!current || typeof current !== "object" || Array.isArray(current)) { + return null; + } const record = current as Record; - if (!(part in record)) return null; + if (!(part in record)) { + return null; + } current = record[part]; } return current; @@ -77,14 +87,22 @@ function stripUnknownConfigKeys(config: OpenClawConfig): { const next = structuredClone(config); const removed: string[] = []; for (const issue of parsed.error.issues) { - if (!isUnrecognizedKeysIssue(issue)) continue; + if (!isUnrecognizedKeysIssue(issue)) { + continue; + } const path = normalizeIssuePath(issue.path); const target = resolvePathTarget(next, path); - if (!target || typeof target !== "object" || Array.isArray(target)) continue; + if (!target || typeof target !== "object" || Array.isArray(target)) { + continue; + } const record = target as Record; for (const key of issue.keys) { - if (typeof key !== "string") continue; - if (!(key in record)) continue; + if (typeof key !== "string") { + continue; + } + if (!(key in record)) { + continue; + } delete record[key]; removed.push(formatPath([...path, key])); } @@ -95,13 +113,21 @@ function stripUnknownConfigKeys(config: OpenClawConfig): { function noteOpencodeProviderOverrides(cfg: OpenClawConfig) { const providers = cfg.models?.providers; - if (!providers) return; + if (!providers) { + return; + } // 2026-01-10: warn when OpenCode Zen overrides mask built-in routing/costs (8a194b4abc360c6098f157956bb9322576b44d51, 2d105d16f8a099276114173836d46b46cdfbdbae). const overrides: string[] = []; - if (providers.opencode) overrides.push("opencode"); - if (providers["opencode-zen"]) overrides.push("opencode-zen"); - if (overrides.length === 0) return; + if (providers.opencode) { + overrides.push("opencode"); + } + if (providers["opencode-zen"]) { + overrides.push("opencode-zen"); + } + if (overrides.length === 0) { + return; + } const lines = overrides.flatMap((id) => { const providerEntry = providers[id]; @@ -125,7 +151,9 @@ function noteOpencodeProviderOverrides(cfg: OpenClawConfig) { async function maybeMigrateLegacyConfig(): Promise { const changes: string[] = []; const home = resolveHomeDir(); - if (!home) return changes; + if (!home) { + return changes; + } const targetDir = path.join(home, ".openclaw"); const targetPath = path.join(targetDir, "openclaw.json"); @@ -152,7 +180,9 @@ async function maybeMigrateLegacyConfig(): Promise { // continue } } - if (!legacyPath) return changes; + if (!legacyPath) { + return changes; + } await fs.mkdir(targetDir, { recursive: true }); try { @@ -214,7 +244,9 @@ export async function loadAndMaybeMigrateDoctorConfig(params: { } if (shouldRepair) { // Legacy migration (2026-01-02, commit: 16420e5b) — normalize per-provider allowlists; move WhatsApp gating into channels.whatsapp.allowFrom. - if (migrated) cfg = migrated; + if (migrated) { + cfg = migrated; + } } else { fixHints.push( `Run "${formatCliCommand("openclaw doctor --fix")}" to apply legacy migrations.`, diff --git a/src/commands/doctor-format.ts b/src/commands/doctor-format.ts index 9478c57b13..6eecd7f7cb 100644 --- a/src/commands/doctor-format.ts +++ b/src/commands/doctor-format.ts @@ -21,14 +21,20 @@ type RuntimeHintOptions = { export function formatGatewayRuntimeSummary( runtime: GatewayServiceRuntime | undefined, ): string | null { - if (!runtime) return null; + if (!runtime) { + return null; + } const status = runtime.status ?? "unknown"; const details: string[] = []; - if (runtime.pid) details.push(`pid ${runtime.pid}`); + if (runtime.pid) { + details.push(`pid ${runtime.pid}`); + } if (runtime.state && runtime.state.toLowerCase() !== status) { details.push(`state ${runtime.state}`); } - if (runtime.subState) details.push(`sub ${runtime.subState}`); + if (runtime.subState) { + details.push(`sub ${runtime.subState}`); + } if (runtime.lastExitStatus !== undefined) { details.push(`last exit ${runtime.lastExitStatus}`); } @@ -41,7 +47,9 @@ export function formatGatewayRuntimeSummary( if (runtime.lastRunTime) { details.push(`last run time ${runtime.lastRunTime}`); } - if (runtime.detail) details.push(runtime.detail); + if (runtime.detail) { + details.push(runtime.detail); + } return details.length > 0 ? `${status} (${details.join(", ")})` : status; } @@ -50,7 +58,9 @@ export function buildGatewayRuntimeHints( options: RuntimeHintOptions = {}, ): string[] { const hints: string[] = []; - if (!runtime) return hints; + if (!runtime) { + return hints; + } const platform = options.platform ?? process.platform; const env = options.env ?? process.env; const fileLog = (() => { @@ -62,7 +72,9 @@ export function buildGatewayRuntimeHints( })(); if (platform === "linux" && isSystemdUnavailableDetail(runtime.detail)) { hints.push(...renderSystemdUnavailableHints({ wsl: isWSLEnv() })); - if (fileLog) hints.push(`File logs: ${fileLog}`); + if (fileLog) { + hints.push(`File logs: ${fileLog}`); + } return hints; } if (runtime.cachedLabel && platform === "darwin") { @@ -74,12 +86,16 @@ export function buildGatewayRuntimeHints( } if (runtime.missingUnit) { hints.push(`Service not installed. Run: ${formatCliCommand("openclaw gateway install", env)}`); - if (fileLog) hints.push(`File logs: ${fileLog}`); + if (fileLog) { + hints.push(`File logs: ${fileLog}`); + } return hints; } if (runtime.status === "stopped") { hints.push("Service is loaded but not running (likely exited immediately)."); - if (fileLog) hints.push(`File logs: ${fileLog}`); + if (fileLog) { + hints.push(`File logs: ${fileLog}`); + } if (platform === "darwin") { const logs = resolveGatewayLogPaths(env); hints.push(`Launchd stdout (if installed): ${logs.stdoutPath}`); diff --git a/src/commands/doctor-gateway-daemon-flow.ts b/src/commands/doctor-gateway-daemon-flow.ts index 8f230abb6e..e859903ddd 100644 --- a/src/commands/doctor-gateway-daemon-flow.ts +++ b/src/commands/doctor-gateway-daemon-flow.ts @@ -37,16 +37,24 @@ async function maybeRepairLaunchAgentBootstrap(params: { runtime: RuntimeEnv; prompter: DoctorPrompter; }): Promise { - if (process.platform !== "darwin") return false; + if (process.platform !== "darwin") { + return false; + } const listed = await isLaunchAgentListed({ env: params.env }); - if (!listed) return false; + if (!listed) { + return false; + } const loaded = await isLaunchAgentLoaded({ env: params.env }); - if (loaded) return false; + if (loaded) { + return false; + } const plistExists = await launchAgentPlistExists(params.env); - if (!plistExists) return false; + if (!plistExists) { + return false; + } note("LaunchAgent is listed but not loaded in launchd.", `${params.title} LaunchAgent`); @@ -54,7 +62,9 @@ async function maybeRepairLaunchAgentBootstrap(params: { message: `Repair ${params.title} LaunchAgent bootstrap now?`, initialValue: true, }); - if (!shouldFix) return false; + if (!shouldFix) { + return false; + } params.runtime.log(`Bootstrapping ${params.title} LaunchAgent...`); const repair = await repairLaunchAgentBootstrap({ env: params.env }); @@ -83,7 +93,9 @@ export async function maybeRepairGatewayDaemon(params: { gatewayDetailsMessage: string; healthOk: boolean; }) { - if (params.healthOk) return; + if (params.healthOk) { + return; + } const service = resolveGatewayService(); // systemd can throw in containers/WSL; treat as "not loaded" and fall back to hints. @@ -129,7 +141,9 @@ export async function maybeRepairGatewayDaemon(params: { note(formatPortDiagnostics(diagnostics).join("\n"), "Gateway port"); } else if (loaded && serviceRuntime?.status === "running") { const lastError = await readLastGatewayErrorLine(process.env); - if (lastError) note(`Last gateway error: ${lastError}`, "Gateway"); + if (lastError) { + note(`Last gateway error: ${lastError}`, "Gateway"); + } } } @@ -190,7 +204,9 @@ export async function maybeRepairGatewayDaemon(params: { }); if (summary || hints.length > 0) { const lines: string[] = []; - if (summary) lines.push(`Runtime: ${summary}`); + if (summary) { + lines.push(`Runtime: ${summary}`); + } lines.push(...hints); note(lines.join("\n"), "Gateway"); } diff --git a/src/commands/doctor-gateway-services.ts b/src/commands/doctor-gateway-services.ts index 7ca23a1302..ac6b6af0be 100644 --- a/src/commands/doctor-gateway-services.ts +++ b/src/commands/doctor-gateway-services.ts @@ -26,16 +26,24 @@ function detectGatewayRuntime(programArguments: string[] | undefined): GatewayDa const first = programArguments?.[0]; if (first) { const base = path.basename(first).toLowerCase(); - if (base === "bun" || base === "bun.exe") return "bun"; - if (base === "node" || base === "node.exe") return "node"; + if (base === "bun" || base === "bun.exe") { + return "bun"; + } + if (base === "node" || base === "node.exe") { + return "node"; + } } return DEFAULT_GATEWAY_DAEMON_RUNTIME; } function findGatewayEntrypoint(programArguments?: string[]): string | null { - if (!programArguments || programArguments.length === 0) return null; + if (!programArguments || programArguments.length === 0) { + return null; + } const gatewayIndex = programArguments.indexOf("gateway"); - if (gatewayIndex <= 0) return null; + if (gatewayIndex <= 0) { + return null; + } return programArguments[gatewayIndex - 1] ?? null; } @@ -44,7 +52,9 @@ function normalizeExecutablePath(value: string): string { } function extractDetailPath(detail: string, prefix: string): string | null { - if (!detail.startsWith(prefix)) return null; + if (!detail.startsWith(prefix)) { + return null; + } const value = detail.slice(prefix.length).trim(); return value.length > 0 ? value : null; } @@ -102,7 +112,9 @@ export async function maybeRepairGatewayServiceConfig( } catch { command = null; } - if (!command) return; + if (!command) { + return; + } const audit = await auditGatewayServiceConfig({ env: process.env, @@ -115,7 +127,9 @@ export async function maybeRepairGatewayServiceConfig( const systemNodePath = systemNodeInfo?.supported ? systemNodeInfo.path : null; if (needsNodeRuntime && !systemNodePath) { const warning = renderSystemNodeWarning(systemNodeInfo); - if (warning) note(warning, "Gateway runtime"); + if (warning) { + note(warning, "Gateway runtime"); + } note( "System Node 22+ not found. Install via Homebrew/apt/choco and rerun doctor to migrate off Bun/version managers.", "Gateway runtime", @@ -148,7 +162,9 @@ export async function maybeRepairGatewayServiceConfig( }); } - if (audit.issues.length === 0) return; + if (audit.issues.length === 0) { + return; + } note( audit.issues @@ -178,7 +194,9 @@ export async function maybeRepairGatewayServiceConfig( message: "Update gateway service config to the recommended defaults now?", initialValue: true, }); - if (!repair) return; + if (!repair) { + return; + } try { await service.install({ env: process.env, @@ -200,7 +218,9 @@ export async function maybeScanExtraGatewayServices( const extraServices = await findExtraGatewayServices(process.env, { deep: options.deep, }); - if (extraServices.length === 0) return; + if (extraServices.length === 0) { + return; + } note( extraServices.map((svc) => `- ${svc.label} (${svc.scope}, ${svc.detail})`).join("\n"), diff --git a/src/commands/doctor-install.ts b/src/commands/doctor-install.ts index 3e88f072d2..ca493c95bc 100644 --- a/src/commands/doctor-install.ts +++ b/src/commands/doctor-install.ts @@ -4,10 +4,14 @@ import path from "node:path"; import { note } from "../terminal/note.js"; export function noteSourceInstallIssues(root: string | null) { - if (!root) return; + if (!root) { + return; + } const workspaceMarker = path.join(root, "pnpm-workspace.yaml"); - if (!fs.existsSync(workspaceMarker)) return; + if (!fs.existsSync(workspaceMarker)) { + return; + } const warnings: string[] = []; const nodeModules = path.join(root, "node_modules"); diff --git a/src/commands/doctor-platform-notes.ts b/src/commands/doctor-platform-notes.ts index 2517061004..940b80e8ca 100644 --- a/src/commands/doctor-platform-notes.ts +++ b/src/commands/doctor-platform-notes.ts @@ -15,11 +15,15 @@ function resolveHomeDir(): string { } export async function noteMacLaunchAgentOverrides() { - if (process.platform !== "darwin") return; + if (process.platform !== "darwin") { + return; + } const home = resolveHomeDir(); const markerCandidates = [path.join(home, ".openclaw", "disable-launchagent")]; const markerPath = markerCandidates.find((candidate) => fs.existsSync(candidate)); - if (!markerPath) return; + if (!markerPath) { + return; + } const displayMarkerPath = shortenHomePath(markerPath); const lines = [ @@ -61,8 +65,12 @@ export async function noteMacLaunchctlGatewayEnvOverrides( }, ) { const platform = deps?.platform ?? process.platform; - if (platform !== "darwin") return; - if (!hasConfigGatewayCreds(cfg)) return; + if (platform !== "darwin") { + return; + } + if (!hasConfigGatewayCreds(cfg)) { + return; + } const getenv = deps?.getenv ?? launchctlGetenv; const deprecatedLaunchctlEntries = [ @@ -94,7 +102,9 @@ export async function noteMacLaunchctlGatewayEnvOverrides( const envPassword = passwordEntry?.[1]?.trim() ?? ""; const envTokenKey = tokenEntry?.[0]; const envPasswordKey = passwordEntry?.[0]; - if (!envToken && !envPassword) return; + if (!envToken && !envPassword) { + return; + } const lines = [ "- launchctl environment overrides detected (can cause confusing unauthorized errors).", @@ -122,7 +132,9 @@ export function noteDeprecatedLegacyEnvVars( (key.startsWith("MOLTBOT_") || key.startsWith("CLAWDBOT_")) && value?.trim(), ) .map(([key]) => key); - if (entries.length === 0) return; + if (entries.length === 0) { + return; + } const lines = [ "- Deprecated legacy environment variables detected (ignored).", diff --git a/src/commands/doctor-prompter.ts b/src/commands/doctor-prompter.ts index b0f037edf1..e1cb27de8f 100644 --- a/src/commands/doctor-prompter.ts +++ b/src/commands/doctor-prompter.ts @@ -37,9 +37,15 @@ export function createDoctorPrompter(params: { const canPrompt = isTty && !yes && !nonInteractive; const confirmDefault = async (p: Parameters[0]) => { - if (nonInteractive) return false; - if (shouldRepair) return true; - if (!canPrompt) return Boolean(p.initialValue ?? false); + if (nonInteractive) { + return false; + } + if (shouldRepair) { + return true; + } + if (!canPrompt) { + return Boolean(p.initialValue ?? false); + } return guardCancel( await confirm({ ...p, @@ -52,14 +58,24 @@ export function createDoctorPrompter(params: { return { confirm: confirmDefault, confirmRepair: async (p) => { - if (nonInteractive) return false; + if (nonInteractive) { + return false; + } return confirmDefault(p); }, confirmAggressive: async (p) => { - if (nonInteractive) return false; - if (shouldRepair && shouldForce) return true; - if (shouldRepair && !shouldForce) return false; - if (!canPrompt) return Boolean(p.initialValue ?? false); + if (nonInteractive) { + return false; + } + if (shouldRepair && shouldForce) { + return true; + } + if (shouldRepair && !shouldForce) { + return false; + } + if (!canPrompt) { + return Boolean(p.initialValue ?? false); + } return guardCancel( await confirm({ ...p, @@ -69,12 +85,18 @@ export function createDoctorPrompter(params: { ); }, confirmSkipInNonInteractive: async (p) => { - if (nonInteractive) return false; - if (shouldRepair) return true; + if (nonInteractive) { + return false; + } + if (shouldRepair) { + return true; + } return confirmDefault(p); }, select: async (p: Parameters[0], fallback: T) => { - if (!canPrompt || shouldRepair) return fallback; + if (!canPrompt || shouldRepair) { + return fallback; + } return guardCancel( await select({ ...p, diff --git a/src/commands/doctor-sandbox.ts b/src/commands/doctor-sandbox.ts index b9c4921330..d39b5353ec 100644 --- a/src/commands/doctor-sandbox.ts +++ b/src/commands/doctor-sandbox.ts @@ -148,7 +148,9 @@ async function handleMissingSandboxImage( prompter: DoctorPrompter, ) { const exists = await dockerImageExists(params.image); - if (exists) return; + if (exists) { + return; + } const buildHint = params.buildScript ? `Build it with ${params.buildScript}.` @@ -166,7 +168,9 @@ async function handleMissingSandboxImage( } } - if (built) return; + if (built) { + return; + } } export async function maybeRepairSandboxImages( @@ -176,7 +180,9 @@ export async function maybeRepairSandboxImages( ): Promise { const sandbox = cfg.agents?.defaults?.sandbox; const mode = sandbox?.mode ?? "off"; - if (!sandbox || mode === "off") return cfg; + if (!sandbox || mode === "off") { + return cfg; + } const dockerAvailable = await isDockerAvailable(); if (!dockerAvailable) { @@ -238,14 +244,18 @@ export function noteSandboxScopeWarnings(cfg: OpenClawConfig) { for (const agent of agents) { const agentId = agent.id; const agentSandbox = agent.sandbox; - if (!agentSandbox) continue; + if (!agentSandbox) { + continue; + } const scope = resolveSandboxScope({ scope: agentSandbox.scope ?? globalSandbox?.scope, perSession: agentSandbox.perSession ?? globalSandbox?.perSession, }); - if (scope !== "shared") continue; + if (scope !== "shared") { + continue; + } const overrides: string[] = []; if (agentSandbox.docker && Object.keys(agentSandbox.docker).length > 0) { @@ -258,7 +268,9 @@ export function noteSandboxScopeWarnings(cfg: OpenClawConfig) { overrides.push("prune"); } - if (overrides.length === 0) continue; + if (overrides.length === 0) { + continue; + } warnings.push( [ diff --git a/src/commands/doctor-security.test.ts b/src/commands/doctor-security.test.ts index b9fef53977..66a595b402 100644 --- a/src/commands/doctor-security.test.ts +++ b/src/commands/doctor-security.test.ts @@ -27,10 +27,16 @@ describe("noteSecurityWarnings gateway exposure", () => { }); afterEach(() => { - if (prevToken === undefined) delete process.env.OPENCLAW_GATEWAY_TOKEN; - else process.env.OPENCLAW_GATEWAY_TOKEN = prevToken; - if (prevPassword === undefined) delete process.env.OPENCLAW_GATEWAY_PASSWORD; - else process.env.OPENCLAW_GATEWAY_PASSWORD = prevPassword; + if (prevToken === undefined) { + delete process.env.OPENCLAW_GATEWAY_TOKEN; + } else { + process.env.OPENCLAW_GATEWAY_TOKEN = prevToken; + } + if (prevPassword === undefined) { + delete process.env.OPENCLAW_GATEWAY_PASSWORD; + } else { + process.env.OPENCLAW_GATEWAY_PASSWORD = prevPassword; + } }); const lastMessage = () => String(note.mock.calls.at(-1)?.[0] ?? ""); diff --git a/src/commands/doctor-security.ts b/src/commands/doctor-security.ts index 3579cb5d9b..5c7b8f0ef0 100644 --- a/src/commands/doctor-security.ts +++ b/src/commands/doctor-security.ts @@ -130,7 +130,9 @@ export async function noteSecurityWarnings(cfg: OpenClawConfig) { }; for (const plugin of listChannelPlugins()) { - if (!plugin.security) continue; + if (!plugin.security) { + continue; + } const accountIds = plugin.config.listAccountIds(cfg); const defaultAccountId = resolveChannelDefaultAccountId({ plugin, @@ -139,11 +141,15 @@ export async function noteSecurityWarnings(cfg: OpenClawConfig) { }); const account = plugin.config.resolveAccount(cfg, defaultAccountId); const enabled = plugin.config.isEnabled ? plugin.config.isEnabled(account, cfg) : true; - if (!enabled) continue; + if (!enabled) { + continue; + } const configured = plugin.config.isConfigured ? await plugin.config.isConfigured(account, cfg) : true; - if (!configured) continue; + if (!configured) { + continue; + } const dmPolicy = plugin.security.resolveDmPolicy?.({ cfg, accountId: defaultAccountId, @@ -167,7 +173,9 @@ export async function noteSecurityWarnings(cfg: OpenClawConfig) { accountId: defaultAccountId, account, }); - if (extra?.length) warnings.push(...extra); + if (extra?.length) { + warnings.push(...extra); + } } } diff --git a/src/commands/doctor-state-integrity.ts b/src/commands/doctor-state-integrity.ts index 4fad602b70..20d58b09ea 100644 --- a/src/commands/doctor-state-integrity.ts +++ b/src/commands/doctor-state-integrity.ts @@ -81,12 +81,18 @@ function addUserRwx(mode: number): number { function countJsonlLines(filePath: string): number { try { const raw = fs.readFileSync(filePath, "utf-8"); - if (!raw) return 0; + if (!raw) { + return 0; + } let count = 0; for (let i = 0; i < raw.length; i += 1) { - if (raw[i] === "\n") count += 1; + if (raw[i] === "\n") { + count += 1; + } + } + if (!raw.endsWith("\n")) { + count += 1; } - if (!raw.endsWith("\n")) count += 1; return count; } catch { return 0; @@ -106,12 +112,20 @@ function findOtherStateDirs(stateDir: string): string[] { continue; } for (const entry of entries) { - if (!entry.isDirectory()) continue; - if (entry.name.startsWith(".")) continue; + if (!entry.isDirectory()) { + continue; + } + if (entry.name.startsWith(".")) { + continue; + } const candidates = [".openclaw"].map((dir) => path.resolve(root, entry.name, dir)); for (const candidate of candidates) { - if (candidate === resolvedState) continue; - if (existsDir(candidate)) found.push(candidate); + if (candidate === resolvedState) { + continue; + } + if (existsDir(candidate)) { + found.push(candidate); + } } } } @@ -168,7 +182,9 @@ export async function noteStateIntegrity( if (stateDirExists && !canWriteDir(stateDir)) { warnings.push(`- State directory not writable (${displayStateDir}).`); const hint = dirPermissionHint(stateDir); - if (hint) warnings.push(` ${hint}`); + if (hint) { + warnings.push(` ${hint}`); + } const repair = await prompter.confirmSkipInNonInteractive({ message: `Repair permissions on ${displayStateDir}?`, initialValue: true, @@ -234,9 +250,15 @@ export async function noteStateIntegrity( dirCandidates.set(storeDir, "Session store dir"); dirCandidates.set(oauthDir, "OAuth dir"); const displayDirFor = (dir: string) => { - if (dir === sessionsDir) return displaySessionsDir; - if (dir === storeDir) return displayStoreDir; - if (dir === oauthDir) return displayOauthDir; + if (dir === sessionsDir) { + return displaySessionsDir; + } + if (dir === storeDir) { + return displayStoreDir; + } + if (dir === oauthDir) { + return displayOauthDir; + } return shortenHomePath(dir); }; @@ -261,7 +283,9 @@ export async function noteStateIntegrity( if (!canWriteDir(dir)) { warnings.push(`- ${label} not writable (${displayDir}).`); const hint = dirPermissionHint(dir); - if (hint) warnings.push(` ${hint}`); + if (hint) { + warnings.push(` ${hint}`); + } const repair = await prompter.confirmSkipInNonInteractive({ message: `Repair permissions on ${label}?`, initialValue: true, @@ -282,7 +306,9 @@ export async function noteStateIntegrity( const extraStateDirs = new Set(); if (path.resolve(stateDir) !== path.resolve(defaultStateDir)) { - if (existsDir(defaultStateDir)) extraStateDirs.add(defaultStateDir); + if (existsDir(defaultStateDir)) { + extraStateDirs.add(defaultStateDir); + } } for (const other of findOtherStateDirs(stateDir)) { extraStateDirs.add(other); @@ -310,7 +336,9 @@ export async function noteStateIntegrity( .slice(0, 5); const missing = recent.filter(([, entry]) => { const sessionId = entry.sessionId; - if (!sessionId) return false; + if (!sessionId) { + return false; + } const transcriptPath = resolveSessionFilePath(sessionId, entry, { agentId, }); @@ -350,9 +378,13 @@ export async function noteStateIntegrity( } export function noteWorkspaceBackupTip(workspaceDir: string) { - if (!existsDir(workspaceDir)) return; + if (!existsDir(workspaceDir)) { + return; + } const gitMarker = path.join(workspaceDir, ".git"); - if (fs.existsSync(gitMarker)) return; + if (fs.existsSync(gitMarker)) { + return; + } note( [ "- Tip: back up the workspace in a private git repo (GitHub or GitLab).", diff --git a/src/commands/doctor-state-migrations.test.ts b/src/commands/doctor-state-migrations.test.ts index 82fb2c4892..e9fe16f1ba 100644 --- a/src/commands/doctor-state-migrations.test.ts +++ b/src/commands/doctor-state-migrations.test.ts @@ -25,7 +25,9 @@ async function makeTempRoot() { afterEach(async () => { resetAutoMigrateLegacyStateForTest(); resetAutoMigrateLegacyStateDirForTest(); - if (!tempRoot) return; + if (!tempRoot) { + return; + } await fs.promises.rm(tempRoot, { recursive: true, force: true }); tempRoot = null; }); diff --git a/src/commands/doctor-ui.ts b/src/commands/doctor-ui.ts index 019a1725a1..92c6125288 100644 --- a/src/commands/doctor-ui.ts +++ b/src/commands/doctor-ui.ts @@ -16,7 +16,9 @@ export async function maybeRepairUiProtocolFreshness( cwd: process.cwd(), }); - if (!root) return; + if (!root) { + return; + } const schemaPath = path.join(root, "src/gateway/protocol/schema.ts"); const uiIndexPath = path.join(root, "dist/control-ui/index.html"); @@ -67,7 +69,9 @@ export async function maybeRepairUiProtocolFreshness( return; } - if (!schemaStats || !uiStats) return; + if (!schemaStats || !uiStats) { + return; + } if (schemaStats.mtime > uiStats.mtime) { const uiMtimeIso = uiStats.mtime.toISOString(); diff --git a/src/commands/doctor-update.ts b/src/commands/doctor-update.ts index 74b63894a6..ad62ec4b6c 100644 --- a/src/commands/doctor-update.ts +++ b/src/commands/doctor-update.ts @@ -10,7 +10,9 @@ async function detectOpenClawGitCheckout(root: string): Promise<"git" | "not-git const res = await runCommandWithTimeout(["git", "-C", root, "rev-parse", "--show-toplevel"], { timeoutMs: 5000, }).catch(() => null); - if (!res) return "unknown"; + if (!res) { + return "unknown"; + } if (res.code !== 0) { // Avoid noisy "Update via package manager" notes when git is missing/broken, // but do show it when this is clearly not a git checkout. @@ -36,7 +38,9 @@ export async function maybeOfferUpdateBeforeDoctor(params: { params.options.yes !== true && params.options.repair !== true && Boolean(process.stdin.isTTY); - if (!canOfferUpdate || !params.root) return { updated: false }; + if (!canOfferUpdate || !params.root) { + return { updated: false }; + } const git = await detectOpenClawGitCheckout(params.root); if (git === "git") { @@ -44,7 +48,9 @@ export async function maybeOfferUpdateBeforeDoctor(params: { message: "Update OpenClaw from git before running doctor?", initialValue: true, }); - if (!shouldUpdate) return { updated: false }; + if (!shouldUpdate) { + return { updated: false }; + } note("Running update (fetch/rebase/build/ui:build/doctor)…", "Update"); const result = await runGatewayUpdate({ cwd: params.root, diff --git a/src/commands/doctor-workspace.ts b/src/commands/doctor-workspace.ts index 02a7528f6c..fcc006b8d3 100644 --- a/src/commands/doctor-workspace.ts +++ b/src/commands/doctor-workspace.ts @@ -28,7 +28,9 @@ export async function shouldSuggestMemorySystem(workspaceDir: string): Promise prompter.confirm(p), outro, }); - if (updateResult.handled) return; + if (updateResult.handled) { + return; + } await maybeRepairUiProtocolFreshness(runtime, prompter); noteSourceInstallIssues(root); diff --git a/src/commands/doctor.warns-per-agent-sandbox-docker-browser-prune.test.ts b/src/commands/doctor.warns-per-agent-sandbox-docker-browser-prune.test.ts index 13b12b103b..d39a548851 100644 --- a/src/commands/doctor.warns-per-agent-sandbox-docker-browser-prune.test.ts +++ b/src/commands/doctor.warns-per-agent-sandbox-docker-browser-prune.test.ts @@ -377,7 +377,9 @@ describe("doctor command", () => { expect( note.mock.calls.some(([message, title]) => { - if (title !== "Sandbox" || typeof message !== "string") return false; + if (title !== "Sandbox" || typeof message !== "string") { + return false; + } const normalized = message.replace(/\s+/g, " ").trim(); return ( normalized.includes('agents.list (id "work") sandbox docker') && @@ -411,8 +413,9 @@ describe("doctor command", () => { value === "/Users/steipete/openclaw" || value === legacyPath || value === legacyAgentsPath - ) + ) { return true; + } return realExists(value as never); }); diff --git a/src/commands/doctor.warns-state-directory-is-missing.test.ts b/src/commands/doctor.warns-state-directory-is-missing.test.ts index 8719fd7351..6f90d67aa5 100644 --- a/src/commands/doctor.warns-state-directory-is-missing.test.ts +++ b/src/commands/doctor.warns-state-directory-is-missing.test.ts @@ -421,8 +421,11 @@ describe("doctor command", () => { { nonInteractive: true, workspaceSuggestions: false }, ); } finally { - if (prevToken === undefined) delete process.env.OPENCLAW_GATEWAY_TOKEN; - else process.env.OPENCLAW_GATEWAY_TOKEN = prevToken; + if (prevToken === undefined) { + delete process.env.OPENCLAW_GATEWAY_TOKEN; + } else { + process.env.OPENCLAW_GATEWAY_TOKEN = prevToken; + } } const warned = note.mock.calls.some(([message]) => diff --git a/src/commands/gateway-status.ts b/src/commands/gateway-status.ts index 536a6f6ffc..d935d966e8 100644 --- a/src/commands/gateway-status.ts +++ b/src/commands/gateway-status.ts @@ -68,7 +68,9 @@ export async function gatewayStatusCommand( const resolved = await resolveSshTarget(sshTarget, sshIdentity, overallTimeoutMs); if (resolved) { sshTarget = resolved.target; - if (!sshIdentity && resolved.identity) sshIdentity = resolved.identity; + if (!sshIdentity && resolved.identity) { + sshIdentity = resolved.identity; + } } } @@ -80,7 +82,9 @@ export async function gatewayStatusCommand( }, async () => { const tryStartTunnel = async () => { - if (!sshTarget) return null; + if (!sshTarget) { + return null; + } try { const tunnel = await startSshPortForward({ target: sshTarget, @@ -107,7 +111,9 @@ export async function gatewayStatusCommand( const candidates = discovery .map((b) => { const host = b.tailnetDns || b.lanHost || b.host; - if (!host?.trim()) return null; + if (!host?.trim()) { + return null; + } const sshPort = typeof b.sshPort === "number" && b.sshPort > 0 ? b.sshPort : 22; const base = user ? `${user}@${host.trim()}` : host.trim(); return sshPort !== 22 ? `${base}:${sshPort}` : base; @@ -115,7 +121,9 @@ export async function gatewayStatusCommand( .filter((candidate): candidate is string => Boolean(candidate && parseSshTarget(candidate)), ); - if (candidates.length > 0) sshTarget = candidates[0] ?? null; + if (candidates.length > 0) { + sshTarget = candidates[0] ?? null; + } } const tunnel = @@ -261,7 +269,9 @@ export async function gatewayStatusCommand( 2, ), ); - if (!ok) runtime.exit(1); + if (!ok) { + runtime.exit(1); + } return; } @@ -276,7 +286,9 @@ export async function gatewayStatusCommand( if (warnings.length > 0) { runtime.log(""); runtime.log(colorize(rich, theme.warn, "Warning:")); - for (const w of warnings) runtime.log(`- ${w.message}`); + for (const w of warnings) { + runtime.log(`- ${w.message}`); + } } runtime.log(""); @@ -327,31 +339,43 @@ export async function gatewayStatusCommand( runtime.log(""); } - if (!ok) runtime.exit(1); + if (!ok) { + runtime.exit(1); + } } function inferSshTargetFromRemoteUrl(rawUrl?: string | null): string | null { - if (typeof rawUrl !== "string") return null; + if (typeof rawUrl !== "string") { + return null; + } const trimmed = rawUrl.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } let host: string | null = null; try { host = new URL(trimmed).hostname || null; } catch { return null; } - if (!host) return null; + if (!host) { + return null; + } const user = process.env.USER?.trim() || ""; return user ? `${user}@${host}` : host; } function buildSshTarget(input: { user?: string; host?: string; port?: number }): string | null { const host = input.host?.trim() ?? ""; - if (!host) return null; + if (!host) { + return null; + } const user = input.user?.trim() ?? ""; const base = user ? `${user}@${host}` : host; const port = input.port ?? 22; - if (port && port !== 22) return `${base}:${port}`; + if (port && port !== 22) { + return `${base}:${port}`; + } return base; } @@ -361,18 +385,24 @@ async function resolveSshTarget( overallTimeoutMs: number, ): Promise<{ target: string; identity?: string } | null> { const parsed = parseSshTarget(rawTarget); - if (!parsed) return null; + if (!parsed) { + return null; + } const config = await resolveSshConfig(parsed, { identity: identity ?? undefined, timeoutMs: Math.min(800, overallTimeoutMs), }); - if (!config) return { target: rawTarget, identity: identity ?? undefined }; + if (!config) { + return { target: rawTarget, identity: identity ?? undefined }; + } const target = buildSshTarget({ user: config.user ?? parsed.user, host: config.host ?? parsed.host, port: config.port ?? parsed.port, }); - if (!target) return { target: rawTarget, identity: identity ?? undefined }; + if (!target) { + return { target: rawTarget, identity: identity ?? undefined }; + } const identityFile = identity ?? config.identityFiles.find((entry) => entry.trim().length > 0)?.trim() ?? undefined; return { target, identity: identityFile }; diff --git a/src/commands/gateway-status/helpers.ts b/src/commands/gateway-status/helpers.ts index dfc0cb318e..2181d00628 100644 --- a/src/commands/gateway-status/helpers.ts +++ b/src/commands/gateway-status/helpers.ts @@ -52,7 +52,9 @@ function parseIntOrNull(value: unknown): number | null { : typeof value === "number" || typeof value === "bigint" ? String(value) : ""; - if (!s) return null; + if (!s) { + return null; + } const n = Number.parseInt(s, 10); return Number.isFinite(n) ? n : null; } @@ -64,7 +66,9 @@ export function parseTimeoutMs(raw: unknown, fallbackMs: number): number { : typeof raw === "number" || typeof raw === "bigint" ? String(raw) : ""; - if (!value) return fallbackMs; + if (!value) { + return fallbackMs; + } const parsed = Number.parseInt(value, 10); if (!Number.isFinite(parsed) || parsed <= 0) { throw new Error(`invalid --timeout: ${value}`); @@ -74,19 +78,27 @@ export function parseTimeoutMs(raw: unknown, fallbackMs: number): number { function normalizeWsUrl(value: string): string | null { const trimmed = value.trim(); - if (!trimmed) return null; - if (!trimmed.startsWith("ws://") && !trimmed.startsWith("wss://")) return null; + if (!trimmed) { + return null; + } + if (!trimmed.startsWith("ws://") && !trimmed.startsWith("wss://")) { + return null; + } return trimmed; } export function resolveTargets(cfg: OpenClawConfig, explicitUrl?: string): GatewayStatusTarget[] { const targets: GatewayStatusTarget[] = []; const add = (t: GatewayStatusTarget) => { - if (!targets.some((x) => x.url === t.url)) targets.push(t); + if (!targets.some((x) => x.url === t.url)) { + targets.push(t); + } }; const explicit = typeof explicitUrl === "string" ? normalizeWsUrl(explicitUrl) : null; - if (explicit) add({ id: "explicit", kind: "explicit", url: explicit, active: true }); + if (explicit) { + add({ id: "explicit", kind: "explicit", url: explicit, active: true }); + } const remoteUrl = typeof cfg.gateway?.remote?.url === "string" ? normalizeWsUrl(cfg.gateway.remote.url) : null; @@ -111,15 +123,23 @@ export function resolveTargets(cfg: OpenClawConfig, explicitUrl?: string): Gatew } export function resolveProbeBudgetMs(overallMs: number, kind: TargetKind): number { - if (kind === "localLoopback") return Math.min(800, overallMs); - if (kind === "sshTunnel") return Math.min(2000, overallMs); + if (kind === "localLoopback") { + return Math.min(800, overallMs); + } + if (kind === "sshTunnel") { + return Math.min(2000, overallMs); + } return Math.min(1500, overallMs); } export function sanitizeSshTarget(value: unknown): string | null { - if (typeof value !== "string") return null; + if (typeof value !== "string") { + return null; + } const trimmed = value.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } return trimmed.replace(/^ssh\\s+/, ""); } @@ -161,13 +181,17 @@ export function resolveAuthForTarget( export function pickGatewaySelfPresence( presence: unknown, ): { host?: string; ip?: string; version?: string; platform?: string } | null { - if (!Array.isArray(presence)) return null; + if (!Array.isArray(presence)) { + return null; + } const entries = presence as Array>; const self = entries.find((e) => e.mode === "gateway" && e.reason === "self") ?? entries.find((e) => typeof e.text === "string" && String(e.text).startsWith("Gateway:")) ?? null; - if (!self) return null; + if (!self) { + return null; + } return { host: typeof self.host === "string" ? self.host : undefined, ip: typeof self.ip === "string" ? self.ip : undefined, diff --git a/src/commands/google-gemini-model-default.ts b/src/commands/google-gemini-model-default.ts index d74e724218..a343a29fb3 100644 --- a/src/commands/google-gemini-model-default.ts +++ b/src/commands/google-gemini-model-default.ts @@ -4,7 +4,9 @@ import type { AgentModelListConfig } from "../config/types.js"; export const GOOGLE_GEMINI_DEFAULT_MODEL = "google/gemini-3-pro-preview"; function resolvePrimaryModel(model?: AgentModelListConfig | string): string | undefined { - if (typeof model === "string") return model; + if (typeof model === "string") { + return model; + } if (model && typeof model === "object" && typeof model.primary === "string") { return model.primary; } diff --git a/src/commands/health-format.ts b/src/commands/health-format.ts index 40be3e6a00..790ae293e4 100644 --- a/src/commands/health-format.ts +++ b/src/commands/health-format.ts @@ -2,7 +2,9 @@ import { colorize, isRich, theme } from "../terminal/theme.js"; const formatKv = (line: string, rich: boolean) => { const idx = line.indexOf(": "); - if (idx <= 0) return colorize(rich, theme.muted, line); + if (idx <= 0) { + return colorize(rich, theme.muted, line); + } const key = line.slice(0, idx); const value = line.slice(idx + 2); @@ -21,7 +23,9 @@ export function formatHealthCheckFailure(err: unknown, opts: { rich?: boolean } const raw = String(err); const message = err instanceof Error ? err.message : raw; - if (!rich) return `Health check failed: ${raw}`; + if (!rich) { + return `Health check failed: ${raw}`; + } const lines = message .split("\n") diff --git a/src/commands/health.ts b/src/commands/health.ts index d34637482e..8a54fabc20 100644 --- a/src/commands/health.ts +++ b/src/commands/health.ts @@ -79,8 +79,12 @@ const debugHealth = (...args: unknown[]) => { }; const formatDurationParts = (ms: number): string => { - if (!Number.isFinite(ms)) return "unknown"; - if (ms < 1000) return `${Math.max(0, Math.round(ms))}ms`; + if (!Number.isFinite(ms)) { + return "unknown"; + } + if (ms < 1000) { + return `${Math.max(0, Math.round(ms))}ms`; + } const units: Array<{ label: string; size: number }> = [ { label: "w", size: 7 * 24 * 60 * 60 * 1000 }, { label: "d", size: 24 * 60 * 60 * 1000 }, @@ -97,7 +101,9 @@ const formatDurationParts = (ms: number): string => { remaining -= value * unit.size; } } - if (parts.length === 0) return "0s"; + if (parts.length === 0) { + return "0s"; + } return parts.join(" "); }; @@ -111,10 +117,16 @@ const resolveAgentOrder = (cfg: ReturnType) => { const ordered: Array<{ id: string; name?: string }> = []; for (const entry of entries) { - if (!entry || typeof entry !== "object") continue; - if (typeof entry.id !== "string" || !entry.id.trim()) continue; + if (!entry || typeof entry !== "object") { + continue; + } + if (typeof entry.id !== "string" || !entry.id.trim()) { + continue; + } const id = normalizeAgentId(entry.id); - if (!id || seen.has(id)) continue; + if (!id || seen.has(id)) { + continue; + } seen.add(id); ordered.push({ id, name: typeof entry.name === "string" ? entry.name : undefined }); } @@ -149,7 +161,9 @@ const buildSessionSummary = (storePath: string) => { }; const isAccountEnabled = (account: unknown): boolean => { - if (!account || typeof account !== "object") return true; + if (!account || typeof account !== "object") { + return true; + } const enabled = (account as { enabled?: boolean }).enabled; return enabled !== false; }; @@ -159,9 +173,13 @@ const asRecord = (value: unknown): Record | null => const formatProbeLine = (probe: unknown, opts: { botUsernames?: string[] } = {}): string | null => { const record = asRecord(probe); - if (!record) return null; + if (!record) { + return null; + } const ok = typeof record.ok === "boolean" ? record.ok : undefined; - if (ok === undefined) return null; + if (ok === undefined) { + return null; + } const elapsedMs = typeof record.elapsedMs === "number" ? record.elapsedMs : null; const status = typeof record.status === "number" ? record.status : null; const error = typeof record.error === "string" ? record.error : null; @@ -171,9 +189,13 @@ const formatProbeLine = (probe: unknown, opts: { botUsernames?: string[] } = {}) const webhookUrl = webhook && typeof webhook.url === "string" ? webhook.url : null; const usernames = new Set(); - if (botUsername) usernames.add(botUsername); + if (botUsername) { + usernames.add(botUsername); + } for (const extra of opts.botUsernames ?? []) { - if (extra) usernames.add(extra); + if (extra) { + usernames.add(extra); + } } if (ok) { @@ -181,21 +203,31 @@ const formatProbeLine = (probe: unknown, opts: { botUsernames?: string[] } = {}) if (usernames.size > 0) { label += ` (@${Array.from(usernames).join(", @")})`; } - if (elapsedMs != null) label += ` (${elapsedMs}ms)`; - if (webhookUrl) label += ` - webhook ${webhookUrl}`; + if (elapsedMs != null) { + label += ` (${elapsedMs}ms)`; + } + if (webhookUrl) { + label += ` - webhook ${webhookUrl}`; + } return label; } let label = `failed (${status ?? "unknown"})`; - if (error) label += ` - ${error}`; + if (error) { + label += ` - ${error}`; + } return label; }; const formatAccountProbeTiming = (summary: ChannelAccountHealthSummary): string | null => { const probe = asRecord(summary.probe); - if (!probe) return null; + if (!probe) { + return null; + } const elapsedMs = typeof probe.elapsedMs === "number" ? Math.round(probe.elapsedMs) : null; const ok = typeof probe.ok === "boolean" ? probe.ok : null; - if (elapsedMs == null && ok !== true) return null; + if (elapsedMs == null && ok !== true) { + return null; + } const accountId = summary.accountId || "default"; const botRecord = asRecord(probe.bot); @@ -209,14 +241,18 @@ const formatAccountProbeTiming = (summary: ChannelAccountHealthSummary): string const isProbeFailure = (summary: ChannelAccountHealthSummary): boolean => { const probe = asRecord(summary.probe); - if (!probe) return false; + if (!probe) { + return false; + } const ok = typeof probe.ok === "boolean" ? probe.ok : null; return ok === false; }; function styleHealthChannelLine(line: string): string { const colon = line.indexOf(":"); - if (colon === -1) return line; + if (colon === -1) { + return line; + } const label = line.slice(0, colon + 1); const detail = line.slice(colon + 1).trimStart(); @@ -225,13 +261,27 @@ function styleHealthChannelLine(line: string): string { const applyPrefix = (prefix: string, color: (value: string) => string) => `${label} ${color(detail.slice(0, prefix.length))}${detail.slice(prefix.length)}`; - if (normalized.startsWith("failed")) return applyPrefix("failed", theme.error); - if (normalized.startsWith("ok")) return applyPrefix("ok", theme.success); - if (normalized.startsWith("linked")) return applyPrefix("linked", theme.success); - if (normalized.startsWith("configured")) return applyPrefix("configured", theme.success); - if (normalized.startsWith("not linked")) return applyPrefix("not linked", theme.warn); - if (normalized.startsWith("not configured")) return applyPrefix("not configured", theme.muted); - if (normalized.startsWith("unknown")) return applyPrefix("unknown", theme.warn); + if (normalized.startsWith("failed")) { + return applyPrefix("failed", theme.error); + } + if (normalized.startsWith("ok")) { + return applyPrefix("ok", theme.success); + } + if (normalized.startsWith("linked")) { + return applyPrefix("linked", theme.success); + } + if (normalized.startsWith("configured")) { + return applyPrefix("configured", theme.success); + } + if (normalized.startsWith("not linked")) { + return applyPrefix("not linked", theme.warn); + } + if (normalized.startsWith("not configured")) { + return applyPrefix("not configured", theme.muted); + } + if (normalized.startsWith("unknown")) { + return applyPrefix("unknown", theme.warn); + } return line; } @@ -251,7 +301,9 @@ export const formatHealthChannelLines = ( const lines: string[] = []; for (const channelId of channelOrder) { const channelSummary = channels[channelId]; - if (!channelSummary) continue; + if (!channelSummary) { + continue; + } const plugin = getChannelPlugin(channelId as never); const label = summary.channelLabels?.[channelId] ?? plugin?.meta.label ?? channelId; const accountSummaries = channelSummary.accounts ?? {}; @@ -440,8 +492,12 @@ export async function getHealthSnapshot(params?: { enabled, configured, }; - if (probe !== undefined) snapshot.probe = probe; - if (lastProbeAt) snapshot.lastProbeAt = lastProbeAt; + if (probe !== undefined) { + snapshot.probe = probe; + } + if (lastProbeAt) { + snapshot.lastProbeAt = lastProbeAt; + } const summary = plugin.status?.buildChannelSummary ? await plugin.status.buildChannelSummary({ @@ -460,7 +516,9 @@ export async function getHealthSnapshot(params?: { probe, lastProbeAt, } satisfies ChannelAccountHealthSummary); - if (record.configured === undefined) record.configured = configured; + if (record.configured === undefined) { + record.configured = configured; + } if (record.lastProbeAt === undefined && lastProbeAt) { record.lastProbeAt = lastProbeAt; } @@ -621,10 +679,14 @@ export async function healthCommand( for (const agent of entries) { const ids = byAgent.get(agent.agentId) ?? []; for (const id of ids) { - if (!accountIds.includes(id)) accountIds.push(id); + if (!accountIds.includes(id)) { + accountIds.push(id); + } } } - if (accountIds.length > 0) byChannel[channelId] = accountIds; + if (accountIds.length > 0) { + byChannel[channelId] = accountIds; + } } for (const [channelId, fallbackIds] of Object.entries(channelAccountFallbacks)) { if (!byChannel[channelId] || byChannel[channelId].length === 0) { @@ -647,8 +709,12 @@ export async function healthCommand( } for (const plugin of listChannelPlugins()) { const channelSummary = summary.channels?.[plugin.id]; - if (!channelSummary || channelSummary.linked !== true) continue; - if (!plugin.status?.logSelfId) continue; + if (!channelSummary || channelSummary.linked !== true) { + continue; + } + if (!plugin.status?.logSelfId) { + continue; + } const boundAccounts = channelBindings.get(plugin.id)?.get(defaultAgentId) ?? []; const accountIds = plugin.config.listAccountIds(cfg); const defaultAccountId = resolveChannelDefaultAccountId({ diff --git a/src/commands/message-format.ts b/src/commands/message-format.ts index ba9af32bc2..5554782690 100644 --- a/src/commands/message-format.ts +++ b/src/commands/message-format.ts @@ -9,7 +9,9 @@ import { isRich, theme } from "../terminal/theme.js"; const shortenText = (value: string, maxLen: number) => { const chars = Array.from(value); - if (chars.length <= maxLen) return value; + if (chars.length <= maxLen) { + return value; + } return `${chars.slice(0, Math.max(0, maxLen - 1)).join("")}…`; }; @@ -17,13 +19,19 @@ const resolveChannelLabel = (channel: ChannelId) => getChannelPlugin(channel)?.meta.label ?? channel; function extractMessageId(payload: unknown): string | null { - if (!payload || typeof payload !== "object") return null; + if (!payload || typeof payload !== "object") { + return null; + } const direct = (payload as { messageId?: unknown }).messageId; - if (typeof direct === "string" && direct.trim()) return direct.trim(); + if (typeof direct === "string" && direct.trim()) { + return direct.trim(); + } const result = (payload as { result?: unknown }).result; if (result && typeof result === "object") { const nested = (result as { messageId?: unknown }).messageId; - if (typeof nested === "string" && nested.trim()) return nested.trim(); + if (typeof nested === "string" && nested.trim()) { + return nested.trim(); + } } return null; } @@ -56,7 +64,9 @@ function renderObjectSummary(payload: unknown, opts: FormatOpts): string[] { } const obj = payload as Record; const keys = Object.keys(obj); - if (keys.length === 0) return [theme.muted("(empty)")]; + if (keys.length === 0) { + return [theme.muted("(empty)")]; + } const rows = keys.slice(0, 20).map((k) => { const v = obj[k]; @@ -145,23 +155,35 @@ function renderMessageList(messages: unknown[], opts: FormatOpts, emptyLabel: st } function renderMessagesFromPayload(payload: unknown, opts: FormatOpts): string[] | null { - if (!payload || typeof payload !== "object") return null; + if (!payload || typeof payload !== "object") { + return null; + } const messages = (payload as { messages?: unknown }).messages; - if (!Array.isArray(messages)) return null; + if (!Array.isArray(messages)) { + return null; + } return renderMessageList(messages, opts, "No messages."); } function renderPinsFromPayload(payload: unknown, opts: FormatOpts): string[] | null { - if (!payload || typeof payload !== "object") return null; + if (!payload || typeof payload !== "object") { + return null; + } const pins = (payload as { pins?: unknown }).pins; - if (!Array.isArray(pins)) return null; + if (!Array.isArray(pins)) { + return null; + } return renderMessageList(pins, opts, "No pins."); } function extractDiscordSearchResultsMessages(results: unknown): unknown[] | null { - if (!results || typeof results !== "object") return null; + if (!results || typeof results !== "object") { + return null; + } const raw = (results as { messages?: unknown }).messages; - if (!Array.isArray(raw)) return null; + if (!Array.isArray(raw)) { + return null; + } // Discord search returns messages as array-of-array; first element is the message. const flattened: unknown[] = []; for (const entry of raw) { @@ -175,9 +197,13 @@ function extractDiscordSearchResultsMessages(results: unknown): unknown[] | null } function renderReactions(payload: unknown, opts: FormatOpts): string[] | null { - if (!payload || typeof payload !== "object") return null; + if (!payload || typeof payload !== "object") { + return null; + } const reactions = (payload as { reactions?: unknown }).reactions; - if (!Array.isArray(reactions)) return null; + if (!Array.isArray(reactions)) { + return null; + } const rows = reactions.slice(0, 50).map((r) => { const entry = r as Record; @@ -192,8 +218,12 @@ function renderReactions(payload: unknown, opts: FormatOpts): string[] | null { ? (entry.users as unknown[]) .slice(0, 8) .map((u) => { - if (typeof u === "string") return u; - if (!u || typeof u !== "object") return ""; + if (typeof u === "string") { + return u; + } + if (!u || typeof u !== "object") { + return ""; + } const user = u as Record; return ( (typeof user.tag === "string" && user.tag) || @@ -211,7 +241,9 @@ function renderReactions(payload: unknown, opts: FormatOpts): string[] | null { }; }); - if (rows.length === 0) return [theme.muted("No reactions.")]; + if (rows.length === 0) { + return [theme.muted("No reactions.")]; + } return [ renderTable({ @@ -304,7 +336,9 @@ export function formatMessageCliText(result: MessageActionRunResult): string[] { }), ), ]; - if (pollId) lines.push(ok(`Poll id: ${pollId}`)); + if (pollId) { + lines.push(ok(`Poll id: ${pollId}`)); + } return lines; } diff --git a/src/commands/model-picker.ts b/src/commands/model-picker.ts index b3b620bec4..83c4ad7f9e 100644 --- a/src/commands/model-picker.ts +++ b/src/commands/model-picker.ts @@ -41,15 +41,23 @@ function hasAuthForProvider( cfg: OpenClawConfig, store: ReturnType, ) { - if (listProfilesForProvider(store, provider).length > 0) return true; - if (resolveEnvApiKey(provider)) return true; - if (getCustomProviderApiKey(cfg, provider)) return true; + if (listProfilesForProvider(store, provider).length > 0) { + return true; + } + if (resolveEnvApiKey(provider)) { + return true; + } + if (getCustomProviderApiKey(cfg, provider)) { + return true; + } return false; } function resolveConfiguredModelRaw(cfg: OpenClawConfig): string { const raw = cfg.agents?.defaults?.model as { primary?: string } | string | undefined; - if (typeof raw === "string") return raw.trim(); + if (typeof raw === "string") { + return raw.trim(); + } return raw?.primary?.trim() ?? ""; } @@ -65,7 +73,9 @@ function normalizeModelKeys(values: string[]): string[] { const next: string[] = []; for (const raw of values) { const value = String(raw ?? "").trim(); - if (!value || seen.has(value)) continue; + if (!value || seen.has(value)) { + continue; + } seen.add(value); next.push(value); } @@ -84,7 +94,9 @@ async function promptManualModel(params: { validate: params.allowBlank ? undefined : (value) => (value?.trim() ? undefined : "Required"), }); const model = String(modelInput ?? "").trim(); - if (!model) return {}; + if (!model) { + return {}; + } return { model }; } @@ -177,7 +189,9 @@ export async function promptDefaultModel( const authCache = new Map(); const hasAuth = (provider: string) => { const cached = authCache.get(provider); - if (cached !== undefined) return cached; + if (cached !== undefined) { + return cached; + } const value = hasAuthForProvider(provider, cfg, authStore); authCache.set(provider, value); return value; @@ -207,16 +221,30 @@ export async function promptDefaultModel( reasoning?: boolean; }) => { const key = modelKey(entry.provider, entry.id); - if (seen.has(key)) return; + if (seen.has(key)) { + return; + } // Skip internal router models that can't be directly called via API. - if (HIDDEN_ROUTER_MODELS.has(key)) return; + if (HIDDEN_ROUTER_MODELS.has(key)) { + return; + } const hints: string[] = []; - if (entry.name && entry.name !== entry.id) hints.push(entry.name); - if (entry.contextWindow) hints.push(`ctx ${formatTokenK(entry.contextWindow)}`); - if (entry.reasoning) hints.push("reasoning"); + if (entry.name && entry.name !== entry.id) { + hints.push(entry.name); + } + if (entry.contextWindow) { + hints.push(`ctx ${formatTokenK(entry.contextWindow)}`); + } + if (entry.reasoning) { + hints.push("reasoning"); + } const aliases = aliasIndex.byKey.get(key); - if (aliases?.length) hints.push(`alias: ${aliases.join(", ")}`); - if (!hasAuth(entry.provider)) hints.push("auth missing"); + if (aliases?.length) { + hints.push(`alias: ${aliases.join(", ")}`); + } + if (!hasAuth(entry.provider)) { + hints.push("auth missing"); + } options.push({ value: key, label: key, @@ -225,7 +253,9 @@ export async function promptDefaultModel( seen.add(key); }; - for (const entry of models) addModelOption(entry); + for (const entry of models) { + addModelOption(entry); + } if (configuredKey && !seen.has(configuredKey)) { options.push({ @@ -254,7 +284,9 @@ export async function promptDefaultModel( initialValue, }); - if (selection === KEEP_VALUE) return {}; + if (selection === KEEP_VALUE) { + return {}; + } if (selection === MANUAL_VALUE) { return promptManualModel({ prompter: params.prompter, @@ -305,7 +337,9 @@ export async function promptModelAllowlist(params: { .split(",") .map((value) => value.trim()) .filter((value) => value.length > 0); - if (parsed.length === 0) return {}; + if (parsed.length === 0) { + return {}; + } return { models: normalizeModelKeys(parsed) }; } @@ -319,7 +353,9 @@ export async function promptModelAllowlist(params: { const authCache = new Map(); const hasAuth = (provider: string) => { const cached = authCache.get(provider); - if (cached !== undefined) return cached; + if (cached !== undefined) { + return cached; + } const value = hasAuthForProvider(provider, cfg, authStore); authCache.set(provider, value); return value; @@ -335,15 +371,29 @@ export async function promptModelAllowlist(params: { reasoning?: boolean; }) => { const key = modelKey(entry.provider, entry.id); - if (seen.has(key)) return; - if (HIDDEN_ROUTER_MODELS.has(key)) return; + if (seen.has(key)) { + return; + } + if (HIDDEN_ROUTER_MODELS.has(key)) { + return; + } const hints: string[] = []; - if (entry.name && entry.name !== entry.id) hints.push(entry.name); - if (entry.contextWindow) hints.push(`ctx ${formatTokenK(entry.contextWindow)}`); - if (entry.reasoning) hints.push("reasoning"); + if (entry.name && entry.name !== entry.id) { + hints.push(entry.name); + } + if (entry.contextWindow) { + hints.push(`ctx ${formatTokenK(entry.contextWindow)}`); + } + if (entry.reasoning) { + hints.push("reasoning"); + } const aliases = aliasIndex.byKey.get(key); - if (aliases?.length) hints.push(`alias: ${aliases.join(", ")}`); - if (!hasAuth(entry.provider)) hints.push("auth missing"); + if (aliases?.length) { + hints.push(`alias: ${aliases.join(", ")}`); + } + if (!hasAuth(entry.provider)) { + hints.push("auth missing"); + } options.push({ value: key, label: key, @@ -356,11 +406,15 @@ export async function promptModelAllowlist(params: { ? catalog.filter((entry) => allowedKeySet.has(modelKey(entry.provider, entry.id))) : catalog; - for (const entry of filteredCatalog) addModelOption(entry); + for (const entry of filteredCatalog) { + addModelOption(entry); + } const supplementalKeys = allowedKeySet ? allowedKeys : existingKeys; for (const key of supplementalKeys) { - if (seen.has(key)) continue; + if (seen.has(key)) { + continue; + } options.push({ value: key, label: key, @@ -369,7 +423,9 @@ export async function promptModelAllowlist(params: { seen.add(key); } - if (options.length === 0) return {}; + if (options.length === 0) { + return {}; + } const selection = await params.prompter.multiselect({ message: params.message ?? "Models in /model picker (multi-select)", @@ -377,13 +433,19 @@ export async function promptModelAllowlist(params: { initialValues: initialKeys.length > 0 ? initialKeys : undefined, }); const selected = normalizeModelKeys(selection.map((value) => String(value))); - if (selected.length > 0) return { models: selected }; - if (existingKeys.length === 0) return { models: [] }; + if (selected.length > 0) { + return { models: selected }; + } + if (existingKeys.length === 0) { + return { models: [] }; + } const confirmClear = await params.prompter.confirm({ message: "Clear the model allowlist? (shows all models)", initialValue: false, }); - if (!confirmClear) return {}; + if (!confirmClear) { + return {}; + } return { models: [] }; } @@ -418,7 +480,9 @@ export function applyModelAllowlist(cfg: OpenClawConfig, models: string[]): Open const defaults = cfg.agents?.defaults; const normalized = normalizeModelKeys(models); if (normalized.length === 0) { - if (!defaults?.models) return cfg; + if (!defaults?.models) { + return cfg; + } const { models: _ignored, ...restDefaults } = defaults; return { ...cfg, @@ -452,7 +516,9 @@ export function applyModelFallbacksFromSelection( selection: string[], ): OpenClawConfig { const normalized = normalizeModelKeys(selection); - if (normalized.length <= 1) return cfg; + if (normalized.length <= 1) { + return cfg; + } const resolved = resolveConfiguredModelRef({ cfg, @@ -460,7 +526,9 @@ export function applyModelFallbacksFromSelection( defaultModel: DEFAULT_MODEL, }); const resolvedKey = modelKey(resolved.provider, resolved.model); - if (!normalized.includes(resolvedKey)) return cfg; + if (!normalized.includes(resolvedKey)) { + return cfg; + } const defaults = cfg.agents?.defaults; const existingModel = defaults?.model; diff --git a/src/commands/models/aliases.ts b/src/commands/models/aliases.ts index e243cccddb..5a84721d2d 100644 --- a/src/commands/models/aliases.ts +++ b/src/commands/models/aliases.ts @@ -18,7 +18,9 @@ export async function modelsAliasesListCommand( const aliases = Object.entries(models).reduce>( (acc, [modelKey, entry]) => { const alias = entry?.alias?.trim(); - if (alias) acc[alias] = modelKey; + if (alias) { + acc[alias] = modelKey; + } return acc; }, {}, diff --git a/src/commands/models/auth-order.ts b/src/commands/models/auth-order.ts index 4170ae33a1..2e4611a08c 100644 --- a/src/commands/models/auth-order.ts +++ b/src/commands/models/auth-order.ts @@ -33,7 +33,9 @@ export async function modelsAuthOrderGetCommand( runtime: RuntimeEnv, ) { const rawProvider = opts.provider?.trim(); - if (!rawProvider) throw new Error("Missing --provider."); + if (!rawProvider) { + throw new Error("Missing --provider."); + } const provider = normalizeProviderId(rawProvider); const cfg = loadConfig(); @@ -71,7 +73,9 @@ export async function modelsAuthOrderClearCommand( runtime: RuntimeEnv, ) { const rawProvider = opts.provider?.trim(); - if (!rawProvider) throw new Error("Missing --provider."); + if (!rawProvider) { + throw new Error("Missing --provider."); + } const provider = normalizeProviderId(rawProvider); const cfg = loadConfig(); @@ -81,7 +85,9 @@ export async function modelsAuthOrderClearCommand( provider, order: null, }); - if (!updated) throw new Error("Failed to update auth-profiles.json (lock busy?)."); + if (!updated) { + throw new Error("Failed to update auth-profiles.json (lock busy?)."); + } runtime.log(`Agent: ${agentId}`); runtime.log(`Provider: ${provider}`); @@ -93,7 +99,9 @@ export async function modelsAuthOrderSetCommand( runtime: RuntimeEnv, ) { const rawProvider = opts.provider?.trim(); - if (!rawProvider) throw new Error("Missing --provider."); + if (!rawProvider) { + throw new Error("Missing --provider."); + } const provider = normalizeProviderId(rawProvider); const cfg = loadConfig(); @@ -123,7 +131,9 @@ export async function modelsAuthOrderSetCommand( provider, order: requested, }); - if (!updated) throw new Error("Failed to update auth-profiles.json (lock busy?)."); + if (!updated) { + throw new Error("Failed to update auth-profiles.json (lock busy?)."); + } runtime.log(`Agent: ${agentId}`); runtime.log(`Provider: ${provider}`); diff --git a/src/commands/models/auth.ts b/src/commands/models/auth.ts index 9c973373c3..060c7835c0 100644 --- a/src/commands/models/auth.ts +++ b/src/commands/models/auth.ts @@ -52,9 +52,13 @@ type TokenProvider = "anthropic"; function resolveTokenProvider(raw?: string): TokenProvider | "custom" | null { const trimmed = raw?.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const normalized = normalizeProviderId(trimmed); - if (normalized === "anthropic") return "anthropic"; + if (normalized === "anthropic") { + return "anthropic"; + } return "custom"; } @@ -80,7 +84,9 @@ export async function modelsAuthSetupTokenCommand( message: "Have you run `claude setup-token` and copied the token?", initialValue: true, }); - if (!proceed) return; + if (!proceed) { + return; + } } const tokenInput = await text({ @@ -239,7 +245,9 @@ function resolveProviderMatch( rawProvider?: string, ): ProviderPlugin | null { const raw = rawProvider?.trim(); - if (!raw) return null; + if (!raw) { + return null; + } const normalized = normalizeProviderId(raw); return ( providers.find((provider) => normalizeProviderId(provider.id) === normalized) ?? @@ -253,7 +261,9 @@ function resolveProviderMatch( function pickAuthMethod(provider: ProviderPlugin, rawMethod?: string): ProviderAuthMethod | null { const raw = rawMethod?.trim(); - if (!raw) return null; + if (!raw) { + return null; + } const normalized = raw.toLowerCase(); return ( provider.auth.find((method) => method.id.toLowerCase() === normalized) ?? @@ -307,8 +317,12 @@ function applyDefaultModel(cfg: OpenClawConfig, model: string): OpenClawConfig { } function credentialMode(credential: AuthProfileCredential): "api_key" | "oauth" | "token" { - if (credential.type === "api_key") return "api_key"; - if (credential.type === "token") return "token"; + if (credential.type === "api_key") { + return "api_key"; + } + if (credential.type === "token") { + return "token"; + } return "oauth"; } diff --git a/src/commands/models/fallbacks.ts b/src/commands/models/fallbacks.ts index b34defa095..4311885b9e 100644 --- a/src/commands/models/fallbacks.ts +++ b/src/commands/models/fallbacks.ts @@ -23,7 +23,9 @@ export async function modelsFallbacksListCommand( return; } if (opts.plain) { - for (const entry of fallbacks) runtime.log(entry); + for (const entry of fallbacks) { + runtime.log(entry); + } return; } @@ -32,7 +34,9 @@ export async function modelsFallbacksListCommand( runtime.log("- none"); return; } - for (const entry of fallbacks) runtime.log(`- ${entry}`); + for (const entry of fallbacks) { + runtime.log(`- ${entry}`); + } } export async function modelsFallbacksAddCommand(modelRaw: string, runtime: RuntimeEnv) { @@ -40,7 +44,9 @@ export async function modelsFallbacksAddCommand(modelRaw: string, runtime: Runti const resolved = resolveModelTarget({ raw: modelRaw, cfg }); const targetKey = modelKey(resolved.provider, resolved.model); const nextModels = { ...cfg.agents?.defaults?.models }; - if (!nextModels[targetKey]) nextModels[targetKey] = {}; + if (!nextModels[targetKey]) { + nextModels[targetKey] = {}; + } const aliasIndex = buildModelAliasIndex({ cfg, defaultProvider: DEFAULT_PROVIDER, @@ -57,7 +63,9 @@ export async function modelsFallbacksAddCommand(modelRaw: string, runtime: Runti .filter((entry): entry is NonNullable => Boolean(entry)) .map((entry) => modelKey(entry.ref.provider, entry.ref.model)); - if (existingKeys.includes(targetKey)) return cfg; + if (existingKeys.includes(targetKey)) { + return cfg; + } const existingModel = cfg.agents?.defaults?.model as | { primary?: string; fallbacks?: string[] } @@ -98,7 +106,9 @@ export async function modelsFallbacksRemoveCommand(modelRaw: string, runtime: Ru defaultProvider: DEFAULT_PROVIDER, aliasIndex, }); - if (!resolvedEntry) return true; + if (!resolvedEntry) { + return true; + } return modelKey(resolvedEntry.ref.provider, resolvedEntry.ref.model) !== targetKey; }); diff --git a/src/commands/models/image-fallbacks.ts b/src/commands/models/image-fallbacks.ts index bffcfb1d86..19a9d98fbc 100644 --- a/src/commands/models/image-fallbacks.ts +++ b/src/commands/models/image-fallbacks.ts @@ -23,7 +23,9 @@ export async function modelsImageFallbacksListCommand( return; } if (opts.plain) { - for (const entry of fallbacks) runtime.log(entry); + for (const entry of fallbacks) { + runtime.log(entry); + } return; } @@ -32,7 +34,9 @@ export async function modelsImageFallbacksListCommand( runtime.log("- none"); return; } - for (const entry of fallbacks) runtime.log(`- ${entry}`); + for (const entry of fallbacks) { + runtime.log(`- ${entry}`); + } } export async function modelsImageFallbacksAddCommand(modelRaw: string, runtime: RuntimeEnv) { @@ -40,7 +44,9 @@ export async function modelsImageFallbacksAddCommand(modelRaw: string, runtime: const resolved = resolveModelTarget({ raw: modelRaw, cfg }); const targetKey = modelKey(resolved.provider, resolved.model); const nextModels = { ...cfg.agents?.defaults?.models }; - if (!nextModels[targetKey]) nextModels[targetKey] = {}; + if (!nextModels[targetKey]) { + nextModels[targetKey] = {}; + } const aliasIndex = buildModelAliasIndex({ cfg, defaultProvider: DEFAULT_PROVIDER, @@ -57,7 +63,9 @@ export async function modelsImageFallbacksAddCommand(modelRaw: string, runtime: .filter((entry): entry is NonNullable => Boolean(entry)) .map((entry) => modelKey(entry.ref.provider, entry.ref.model)); - if (existingKeys.includes(targetKey)) return cfg; + if (existingKeys.includes(targetKey)) { + return cfg; + } const existingModel = cfg.agents?.defaults?.imageModel as | { primary?: string; fallbacks?: string[] } @@ -100,7 +108,9 @@ export async function modelsImageFallbacksRemoveCommand(modelRaw: string, runtim defaultProvider: DEFAULT_PROVIDER, aliasIndex, }); - if (!resolvedEntry) return true; + if (!resolvedEntry) { + return true; + } return modelKey(resolvedEntry.ref.provider, resolvedEntry.ref.model) !== targetKey; }); diff --git a/src/commands/models/list.auth-overview.ts b/src/commands/models/list.auth-overview.ts index eca6085e1a..3a36d4bafc 100644 --- a/src/commands/models/list.auth-overview.ts +++ b/src/commands/models/list.auth-overview.ts @@ -23,7 +23,9 @@ export function resolveProviderAuthOverview(params: { const profiles = listProfilesForProvider(store, provider); const withUnusableSuffix = (base: string, profileId: string) => { const unusableUntil = resolveProfileUnusableUntilForDisplay(store, profileId); - if (!unusableUntil || now >= unusableUntil) return base; + if (!unusableUntil || now >= unusableUntil) { + return base; + } const stats = store.usageStats?.[profileId]; const kind = typeof stats?.disabledUntil === "number" && now < stats.disabledUntil @@ -34,7 +36,9 @@ export function resolveProviderAuthOverview(params: { }; const labels = profiles.map((profileId) => { const profile = store.profiles[profileId]; - if (!profile) return `${profileId}=missing`; + if (!profile) { + return `${profileId}=missing`; + } if (profile.type === "api_key") { return withUnusableSuffix(`${profileId}=${maskApiKey(profile.key)}`, profileId); } diff --git a/src/commands/models/list.configured.ts b/src/commands/models/list.configured.ts index ad3ea829ee..a4300ea563 100644 --- a/src/commands/models/list.configured.ts +++ b/src/commands/models/list.configured.ts @@ -54,7 +54,9 @@ export function resolveConfiguredEntries(cfg: OpenClawConfig) { defaultProvider: DEFAULT_PROVIDER, aliasIndex, }); - if (!resolved) return; + if (!resolved) { + return; + } addEntry(resolved.ref, `fallback#${idx + 1}`); }); @@ -64,7 +66,9 @@ export function resolveConfiguredEntries(cfg: OpenClawConfig) { defaultProvider: DEFAULT_PROVIDER, aliasIndex, }); - if (resolved) addEntry(resolved.ref, "image"); + if (resolved) { + addEntry(resolved.ref, "image"); + } } imageFallbacks.forEach((raw, idx) => { @@ -73,13 +77,17 @@ export function resolveConfiguredEntries(cfg: OpenClawConfig) { defaultProvider: DEFAULT_PROVIDER, aliasIndex, }); - if (!resolved) return; + if (!resolved) { + return; + } addEntry(resolved.ref, `img-fallback#${idx + 1}`); }); for (const key of Object.keys(cfg.agents?.defaults?.models ?? {})) { const parsed = parseModelRef(String(key ?? ""), DEFAULT_PROVIDER); - if (!parsed) continue; + if (!parsed) { + continue; + } addEntry(parsed, "configured"); } diff --git a/src/commands/models/list.format.ts b/src/commands/models/list.format.ts index eed531c42a..5e8c0e245a 100644 --- a/src/commands/models/list.format.ts +++ b/src/commands/models/list.format.ts @@ -19,26 +19,50 @@ export const formatKeyValue = ( export const formatSeparator = (rich: boolean) => colorize(rich, theme.muted, " | "); export const formatTag = (tag: string, rich: boolean) => { - if (!rich) return tag; - if (tag === "default") return theme.success(tag); - if (tag === "image") return theme.accentBright(tag); - if (tag === "configured") return theme.accent(tag); - if (tag === "missing") return theme.error(tag); - if (tag.startsWith("fallback#")) return theme.warn(tag); - if (tag.startsWith("img-fallback#")) return theme.warn(tag); - if (tag.startsWith("alias:")) return theme.accentDim(tag); + if (!rich) { + return tag; + } + if (tag === "default") { + return theme.success(tag); + } + if (tag === "image") { + return theme.accentBright(tag); + } + if (tag === "configured") { + return theme.accent(tag); + } + if (tag === "missing") { + return theme.error(tag); + } + if (tag.startsWith("fallback#")) { + return theme.warn(tag); + } + if (tag.startsWith("img-fallback#")) { + return theme.warn(tag); + } + if (tag.startsWith("alias:")) { + return theme.accentDim(tag); + } return theme.muted(tag); }; export const truncate = (value: string, max: number) => { - if (value.length <= max) return value; - if (max <= 3) return value.slice(0, max); + if (value.length <= max) { + return value; + } + if (max <= 3) { + return value.slice(0, max); + } return `${value.slice(0, max - 3)}...`; }; export const maskApiKey = (value: string): string => { const trimmed = value.trim(); - if (!trimmed) return "missing"; - if (trimmed.length <= 16) return trimmed; + if (!trimmed) { + return "missing"; + } + if (trimmed.length <= 16) { + return trimmed; + } return `${trimmed.slice(0, 8)}...${trimmed.slice(-8)}`; }; diff --git a/src/commands/models/list.list-command.ts b/src/commands/models/list.list-command.ts index 04bb1f9c85..b7c30e1d66 100644 --- a/src/commands/models/list.list-command.ts +++ b/src/commands/models/list.list-command.ts @@ -25,7 +25,9 @@ export async function modelsListCommand( const authStore = ensureAuthProfileStore(); const providerFilter = (() => { const raw = opts.provider?.trim(); - if (!raw) return undefined; + if (!raw) { + return undefined; + } const parsed = parseModelRef(`${raw}/_`, DEFAULT_PROVIDER); return parsed?.provider ?? raw.toLowerCase(); })(); @@ -66,7 +68,9 @@ export async function modelsListCommand( if (opts.all) { const sorted = [...models].toSorted((a, b) => { const p = a.provider.localeCompare(b.provider); - if (p !== 0) return p; + if (p !== 0) { + return p; + } return a.id.localeCompare(b.id); }); @@ -74,7 +78,9 @@ export async function modelsListCommand( if (providerFilter && model.provider.toLowerCase() !== providerFilter) { continue; } - if (opts.local && !isLocalBaseUrl(model.baseUrl)) continue; + if (opts.local && !isLocalBaseUrl(model.baseUrl)) { + continue; + } const key = modelKey(model.provider, model.id); const configured = configuredByKey.get(key); rows.push( @@ -95,8 +101,12 @@ export async function modelsListCommand( continue; } const model = modelByKey.get(entry.key); - if (opts.local && model && !isLocalBaseUrl(model.baseUrl)) continue; - if (opts.local && !model) continue; + if (opts.local && model && !isLocalBaseUrl(model.baseUrl)) { + continue; + } + if (opts.local && !model) { + continue; + } rows.push( toModelRow({ model, diff --git a/src/commands/models/list.probe.ts b/src/commands/models/list.probe.ts index 1dce145345..ad3c1c4d49 100644 --- a/src/commands/models/list.probe.ts +++ b/src/commands/models/list.probe.ts @@ -80,12 +80,24 @@ export type AuthProbeOptions = { }; const toStatus = (reason?: string | null): AuthProbeStatus => { - if (!reason) return "unknown"; - if (reason === "auth") return "auth"; - if (reason === "rate_limit") return "rate_limit"; - if (reason === "billing") return "billing"; - if (reason === "timeout") return "timeout"; - if (reason === "format") return "format"; + if (!reason) { + return "unknown"; + } + if (reason === "auth") { + return "auth"; + } + if (reason === "rate_limit") { + return "rate_limit"; + } + if (reason === "billing") { + return "billing"; + } + if (reason === "timeout") { + return "timeout"; + } + if (reason === "format") { + return "format"; + } return "unknown"; }; @@ -93,9 +105,13 @@ function buildCandidateMap(modelCandidates: string[]): Map { const map = new Map(); for (const raw of modelCandidates) { const parsed = parseModelRef(String(raw ?? ""), DEFAULT_PROVIDER); - if (!parsed) continue; + if (!parsed) { + continue; + } const list = map.get(parsed.provider) ?? []; - if (!list.includes(parsed.model)) list.push(parsed.model); + if (!list.includes(parsed.model)) { + list.push(parsed.model); + } map.set(parsed.provider, list); } return map; @@ -112,7 +128,9 @@ function selectProbeModel(params: { return { provider, model: direct[0] }; } const fromCatalog = catalog.find((entry) => entry.provider === provider); - if (fromCatalog) return { provider: fromCatalog.provider, model: fromCatalog.id }; + if (fromCatalog) { + return { provider: fromCatalog.provider, model: fromCatalog.id }; + } return null; } @@ -135,7 +153,9 @@ function buildProbeTargets(params: { for (const provider of providers) { const providerKey = normalizeProviderId(provider); - if (providerFilterKey && providerKey !== providerFilterKey) continue; + if (providerFilterKey && providerKey !== providerFilterKey) { + continue; + } const model = selectProbeModel({ provider: providerKey, @@ -148,13 +168,17 @@ function buildProbeTargets(params: { const order = store.order; if (order) { for (const [key, value] of Object.entries(order)) { - if (normalizeProviderId(key) === providerKey) return value; + if (normalizeProviderId(key) === providerKey) { + return value; + } } } const cfgOrder = cfg?.auth?.order; if (cfgOrder) { for (const [key, value] of Object.entries(cfgOrder)) { - if (normalizeProviderId(key) === providerKey) return value; + if (normalizeProviderId(key) === providerKey) { + return value; + } } } return undefined; @@ -223,11 +247,15 @@ function buildProbeTargets(params: { continue; } - if (profileFilter.size > 0) continue; + if (profileFilter.size > 0) { + continue; + } const envKey = resolveEnvApiKey(providerKey); const customKey = getCustomProviderApiKey(cfg, providerKey); - if (!envKey && !customKey) continue; + if (!envKey && !customKey) { + continue; + } const label = envKey ? "env" : "models.json"; const source = envKey ? "env" : "models.json"; @@ -360,7 +388,9 @@ async function runTargetsWithConcurrency(params: { while (true) { const index = cursor; cursor += 1; - if (index >= targets.length) return; + if (index >= targets.length) { + return; + } const target = targets[index]; onProgress?.({ completed, @@ -430,7 +460,9 @@ export async function runAuthProbes(params: { } export function formatProbeLatency(latencyMs?: number | null) { - if (!latencyMs && latencyMs !== 0) return "-"; + if (!latencyMs && latencyMs !== 0) { + return "-"; + } return formatMs(latencyMs); } @@ -447,7 +479,9 @@ export function groupProbeResults(results: AuthProbeResult[]): Map { const provider = a.provider.localeCompare(b.provider); - if (provider !== 0) return provider; + if (provider !== 0) { + return provider; + } const aLabel = a.label || a.profileId || ""; const bLabel = b.label || b.profileId || ""; return aLabel.localeCompare(bLabel); @@ -455,6 +489,8 @@ export function sortProbeResults(results: AuthProbeResult[]): AuthProbeResult[] } export function describeProbeSummary(summary: AuthProbeSummary): string { - if (summary.totalTargets === 0) return "No probe targets."; + if (summary.totalTargets === 0) { + return "No probe targets."; + } return `Probed ${summary.totalTargets} target${summary.totalTargets === 1 ? "" : "s"} in ${formatMs(summary.durationMs)}`; } diff --git a/src/commands/models/list.registry.ts b/src/commands/models/list.registry.ts index 841202aee1..3ca02d3127 100644 --- a/src/commands/models/list.registry.ts +++ b/src/commands/models/list.registry.ts @@ -31,10 +31,18 @@ const isLocalBaseUrl = (baseUrl: string) => { }; const hasAuthForProvider = (provider: string, cfg: OpenClawConfig, authStore: AuthProfileStore) => { - if (listProfilesForProvider(authStore, provider).length > 0) return true; - if (provider === "amazon-bedrock" && resolveAwsSdkEnvVarName()) return true; - if (resolveEnvApiKey(provider)) return true; - if (getCustomProviderApiKey(cfg, provider)) return true; + if (listProfilesForProvider(authStore, provider).length > 0) { + return true; + } + if (provider === "amazon-bedrock" && resolveAwsSdkEnvVarName()) { + return true; + } + if (resolveEnvApiKey(provider)) { + return true; + } + if (getCustomProviderApiKey(cfg, provider)) { + return true; + } return false; }; @@ -82,9 +90,13 @@ export function toModelRow(params: { const mergedTags = new Set(tags); if (aliasTags.length > 0) { for (const tag of mergedTags) { - if (tag === "alias" || tag.startsWith("alias:")) mergedTags.delete(tag); + if (tag === "alias" || tag.startsWith("alias:")) { + mergedTags.delete(tag); + } + } + for (const tag of aliasTags) { + mergedTags.add(tag); } - for (const tag of aliasTags) mergedTags.add(tag); } return { diff --git a/src/commands/models/list.status-command.ts b/src/commands/models/list.status-command.ts index c389f6ebf5..ccb68eaf70 100644 --- a/src/commands/models/list.status-command.ts +++ b/src/commands/models/list.status-command.ts @@ -108,7 +108,9 @@ export async function modelsStatusCommand( const aliases = Object.entries(cfg.agents?.defaults?.models ?? {}).reduce>( (acc, [key, entry]) => { const alias = entry?.alias?.trim(); - if (alias) acc[alias] = key; + if (alias) { + acc[alias] = key; + } return acc; }, {}, @@ -132,11 +134,15 @@ export async function modelsStatusCommand( const providersInUse = new Set(); for (const raw of [defaultLabel, ...fallbacks, imageModel, ...imageFallbacks, ...allowed]) { const parsed = parseModelRef(String(raw ?? ""), DEFAULT_PROVIDER); - if (parsed?.provider) providersFromModels.add(parsed.provider); + if (parsed?.provider) { + providersFromModels.add(parsed.provider); + } } for (const raw of [defaultLabel, ...fallbacks, imageModel, ...imageFallbacks]) { const parsed = parseModelRef(String(raw ?? ""), DEFAULT_PROVIDER); - if (parsed?.provider) providersInUse.add(parsed.provider); + if (parsed?.provider) { + providersInUse.add(parsed.provider); + } } const providersFromEnv = new Set(); @@ -157,7 +163,9 @@ export async function modelsStatusCommand( "synthetic", ]; for (const provider of envProbeProviders) { - if (resolveEnvApiKey(provider)) providersFromEnv.add(provider); + if (resolveEnvApiKey(provider)) { + providersFromEnv.add(provider); + } } const providers = Array.from( @@ -188,7 +196,9 @@ export async function modelsStatusCommand( .toSorted((a, b) => a.localeCompare(b)); const probeProfileIds = (() => { - if (!opts.probeProfile) return []; + if (!opts.probeProfile) { + return []; + } const raw = Array.isArray(opts.probeProfile) ? opts.probeProfile : [opts.probeProfile]; return raw .flatMap((value) => String(value ?? "").split(",")) @@ -283,7 +293,9 @@ export async function modelsStatusCommand( }> = []; for (const profileId of Object.keys(store.usageStats ?? {})) { const unusableUntil = resolveProfileUnusableUntilForDisplay(store, profileId); - if (!unusableUntil || now >= unusableUntil) continue; + if (!unusableUntil || now >= unusableUntil) { + continue; + } const stats = store.usageStats?.[profileId]; const kind = typeof stats?.disabledUntil === "number" && now < stats.disabledUntil @@ -306,8 +318,12 @@ export async function modelsStatusCommand( oauthProfiles.some((profile) => ["expired", "missing"].includes(profile.status)) || missingProvidersInUse.length > 0; const hasExpiring = oauthProfiles.some((profile) => profile.status === "expiring"); - if (hasExpiredOrMissing) return 1; - if (hasExpiring) return 2; + if (hasExpiredOrMissing) { + return 1; + } + if (hasExpiring) { + return 2; + } return 0; })(); @@ -355,13 +371,17 @@ export async function modelsStatusCommand( 2, ), ); - if (opts.check) runtime.exit(checkStatus); + if (opts.check) { + runtime.exit(checkStatus); + } return; } if (opts.plain) { runtime.log(resolvedLabel); - if (opts.check) runtime.exit(checkStatus); + if (opts.check) { + runtime.exit(checkStatus); + } return; } @@ -564,18 +584,29 @@ export async function modelsStatusCommand( } const formatStatus = (status: string) => { - if (status === "ok") return colorize(rich, theme.success, "ok"); - if (status === "static") return colorize(rich, theme.muted, "static"); - if (status === "expiring") return colorize(rich, theme.warn, "expiring"); - if (status === "missing") return colorize(rich, theme.warn, "unknown"); + if (status === "ok") { + return colorize(rich, theme.success, "ok"); + } + if (status === "static") { + return colorize(rich, theme.muted, "static"); + } + if (status === "expiring") { + return colorize(rich, theme.warn, "expiring"); + } + if (status === "missing") { + return colorize(rich, theme.warn, "unknown"); + } return colorize(rich, theme.error, "expired"); }; const profilesByProvider = new Map(); for (const profile of oauthProfiles) { const current = profilesByProvider.get(profile.provider); - if (current) current.push(profile); - else profilesByProvider.set(profile.provider, [profile]); + if (current) { + current.push(profile); + } else { + profilesByProvider.set(profile.provider, [profile]); + } } for (const [provider, profiles] of profilesByProvider) { @@ -607,11 +638,21 @@ export async function modelsStatusCommand( const tableWidth = Math.max(60, (process.stdout.columns ?? 120) - 1); const sorted = sortProbeResults(probeSummary.results); const statusColor = (status: string) => { - if (status === "ok") return theme.success; - if (status === "rate_limit") return theme.warn; - if (status === "timeout" || status === "billing") return theme.warn; - if (status === "auth" || status === "format") return theme.error; - if (status === "no_model") return theme.muted; + if (status === "ok") { + return theme.success; + } + if (status === "rate_limit") { + return theme.warn; + } + if (status === "timeout" || status === "billing") { + return theme.warn; + } + if (status === "auth" || status === "format") { + return theme.error; + } + if (status === "no_model") { + return theme.muted; + } return theme.muted; }; const rows = sorted.map((result) => { @@ -644,5 +685,7 @@ export async function modelsStatusCommand( } } - if (opts.check) runtime.exit(checkStatus); + if (opts.check) { + runtime.exit(checkStatus); + } } diff --git a/src/commands/models/list.table.ts b/src/commands/models/list.table.ts index 44ed0da265..3211ce57b1 100644 --- a/src/commands/models/list.table.ts +++ b/src/commands/models/list.table.ts @@ -30,7 +30,9 @@ export function printModelTable( } if (opts.plain) { - for (const row of rows) runtime.log(row.key); + for (const row of rows) { + runtime.log(row.key); + } return; } diff --git a/src/commands/models/scan.ts b/src/commands/models/scan.ts index 9831b04d9d..a4a402483d 100644 --- a/src/commands/models/scan.ts +++ b/src/commands/models/scan.ts @@ -27,8 +27,12 @@ const multiselect = (params: Parameters>[0]) => const pad = (value: string, size: number) => value.padEnd(size); const truncate = (value: string, max: number) => { - if (value.length <= max) return value; - if (max <= 3) return value.slice(0, max); + if (value.length <= max) { + return value; + } + if (max <= 3) { + return value.slice(0, max); + } return `${value.slice(0, max - 3)}...`; }; @@ -36,19 +40,27 @@ function sortScanResults(results: ModelScanResult[]): ModelScanResult[] { return results.slice().toSorted((a, b) => { const aImage = a.image.ok ? 1 : 0; const bImage = b.image.ok ? 1 : 0; - if (aImage !== bImage) return bImage - aImage; + if (aImage !== bImage) { + return bImage - aImage; + } const aToolLatency = a.tool.latencyMs ?? Number.POSITIVE_INFINITY; const bToolLatency = b.tool.latencyMs ?? Number.POSITIVE_INFINITY; - if (aToolLatency !== bToolLatency) return aToolLatency - bToolLatency; + if (aToolLatency !== bToolLatency) { + return aToolLatency - bToolLatency; + } const aCtx = a.contextLength ?? 0; const bCtx = b.contextLength ?? 0; - if (aCtx !== bCtx) return bCtx - aCtx; + if (aCtx !== bCtx) { + return bCtx - aCtx; + } const aParams = a.inferredParamB ?? 0; const bParams = b.inferredParamB ?? 0; - if (aParams !== bParams) return bParams - aParams; + if (aParams !== bParams) { + return bParams - aParams; + } return a.modelRef.localeCompare(b.modelRef); }); @@ -58,15 +70,21 @@ function sortImageResults(results: ModelScanResult[]): ModelScanResult[] { return results.slice().toSorted((a, b) => { const aLatency = a.image.latencyMs ?? Number.POSITIVE_INFINITY; const bLatency = b.image.latencyMs ?? Number.POSITIVE_INFINITY; - if (aLatency !== bLatency) return aLatency - bLatency; + if (aLatency !== bLatency) { + return aLatency - bLatency; + } const aCtx = a.contextLength ?? 0; const bCtx = b.contextLength ?? 0; - if (aCtx !== bCtx) return bCtx - aCtx; + if (aCtx !== bCtx) { + return bCtx - aCtx; + } const aParams = a.inferredParamB ?? 0; const bParams = b.inferredParamB ?? 0; - if (aParams !== bParams) return bParams - aParams; + if (aParams !== bParams) { + return bParams - aParams; + } return a.modelRef.localeCompare(b.modelRef); }); @@ -188,7 +206,9 @@ export async function modelsScanCommand( concurrency, probe, onProgress: ({ phase, completed, total }) => { - if (phase !== "probe") return; + if (phase !== "probe") { + return; + } const labelBase = probe ? "Probing models" : "Scanning models"; update({ completed, @@ -288,10 +308,14 @@ export async function modelsScanCommand( const _updated = await updateConfig((cfg) => { const nextModels = { ...cfg.agents?.defaults?.models }; for (const entry of selected) { - if (!nextModels[entry]) nextModels[entry] = {}; + if (!nextModels[entry]) { + nextModels[entry] = {}; + } } for (const entry of selectedImages) { - if (!nextModels[entry]) nextModels[entry] = {}; + if (!nextModels[entry]) { + nextModels[entry] = {}; + } } const existingImageModel = cfg.agents?.defaults?.imageModel as | { primary?: string; fallbacks?: string[] } diff --git a/src/commands/models/set-image.ts b/src/commands/models/set-image.ts index b0e6e3ecd8..0f8f9d641c 100644 --- a/src/commands/models/set-image.ts +++ b/src/commands/models/set-image.ts @@ -7,7 +7,9 @@ export async function modelsSetImageCommand(modelRaw: string, runtime: RuntimeEn const resolved = resolveModelTarget({ raw: modelRaw, cfg }); const key = `${resolved.provider}/${resolved.model}`; const nextModels = { ...cfg.agents?.defaults?.models }; - if (!nextModels[key]) nextModels[key] = {}; + if (!nextModels[key]) { + nextModels[key] = {}; + } const existingModel = cfg.agents?.defaults?.imageModel as | { primary?: string; fallbacks?: string[] } | undefined; diff --git a/src/commands/models/set.ts b/src/commands/models/set.ts index b6d74711e1..12e683c4e7 100644 --- a/src/commands/models/set.ts +++ b/src/commands/models/set.ts @@ -7,7 +7,9 @@ export async function modelsSetCommand(modelRaw: string, runtime: RuntimeEnv) { const resolved = resolveModelTarget({ raw: modelRaw, cfg }); const key = `${resolved.provider}/${resolved.model}`; const nextModels = { ...cfg.agents?.defaults?.models }; - if (!nextModels[key]) nextModels[key] = {}; + if (!nextModels[key]) { + nextModels[key] = {}; + } const existingModel = cfg.agents?.defaults?.model as | { primary?: string; fallbacks?: string[] } | undefined; diff --git a/src/commands/models/shared.ts b/src/commands/models/shared.ts index 4f4268361e..85f852c638 100644 --- a/src/commands/models/shared.ts +++ b/src/commands/models/shared.ts @@ -21,15 +21,25 @@ export const ensureFlagCompatibility = (opts: { json?: boolean; plain?: boolean }; export const formatTokenK = (value?: number | null) => { - if (!value || !Number.isFinite(value)) return "-"; - if (value < 1024) return `${Math.round(value)}`; + if (!value || !Number.isFinite(value)) { + return "-"; + } + if (value < 1024) { + return `${Math.round(value)}`; + } return `${Math.round(value / 1024)}k`; }; export const formatMs = (value?: number | null) => { - if (value === null || value === undefined) return "-"; - if (!Number.isFinite(value)) return "-"; - if (value < 1000) return `${Math.round(value)}ms`; + if (value === null || value === undefined) { + return "-"; + } + if (!Number.isFinite(value)) { + return "-"; + } + if (value < 1000) { + return `${Math.round(value)}ms`; + } return `${Math.round(value / 100) / 10}s`; }; @@ -70,7 +80,9 @@ export function buildAllowlistSet(cfg: OpenClawConfig): Set { const models = cfg.agents?.defaults?.models ?? {}; for (const raw of Object.keys(models)) { const parsed = parseModelRef(String(raw ?? ""), DEFAULT_PROVIDER); - if (!parsed) continue; + if (!parsed) { + continue; + } allowed.add(modelKey(parsed.provider, parsed.model)); } return allowed; @@ -78,7 +90,9 @@ export function buildAllowlistSet(cfg: OpenClawConfig): Set { export function normalizeAlias(alias: string): string { const trimmed = alias.trim(); - if (!trimmed) throw new Error("Alias cannot be empty."); + if (!trimmed) { + throw new Error("Alias cannot be empty."); + } if (!/^[A-Za-z0-9_.:-]+$/.test(trimmed)) { throw new Error("Alias must use letters, numbers, dots, underscores, colons, or dashes."); } @@ -90,7 +104,9 @@ export function resolveKnownAgentId(params: { rawAgentId?: string | null; }): string | undefined { const raw = params.rawAgentId?.trim(); - if (!raw) return undefined; + if (!raw) { + return undefined; + } const agentId = normalizeAgentId(raw); const knownAgents = listAgentIds(params.cfg); if (!knownAgents.includes(agentId)) { diff --git a/src/commands/node-daemon-install-helpers.ts b/src/commands/node-daemon-install-helpers.ts index 97ddfcd643..ef8007f8e5 100644 --- a/src/commands/node-daemon-install-helpers.ts +++ b/src/commands/node-daemon-install-helpers.ts @@ -53,7 +53,9 @@ export async function buildNodeInstallPlan(params: { if (params.runtime === "node") { const systemNode = await resolveSystemNodeInfo({ env: params.env }); const warning = renderSystemNodeWarning(systemNode, programArguments[0]); - if (warning) params.warn?.(warning, "Node daemon runtime"); + if (warning) { + params.warn?.(warning, "Node daemon runtime"); + } } const environment = buildNodeServiceEnvironment({ env: params.env }); diff --git a/src/commands/oauth-flow.ts b/src/commands/oauth-flow.ts index 897777c32b..77ea367c9a 100644 --- a/src/commands/oauth-flow.ts +++ b/src/commands/oauth-flow.ts @@ -40,7 +40,9 @@ export function createVpsAwareOAuthHandlers(params: { params.runtime.log(`Open: ${url}`); }, onPrompt: async (prompt) => { - if (manualCodePromise) return manualCodePromise; + if (manualCodePromise) { + return manualCodePromise; + } const code = await params.prompter.text({ message: prompt.message, placeholder: prompt.placeholder, diff --git a/src/commands/onboard-auth.test.ts b/src/commands/onboard-auth.test.ts index c29303a150..0aa080336c 100644 --- a/src/commands/onboard-auth.test.ts +++ b/src/commands/onboard-auth.test.ts @@ -27,7 +27,9 @@ import { const authProfilePathFor = (agentDir: string) => path.join(agentDir, "auth-profiles.json"); const requireAgentDir = () => { const agentDir = process.env.OPENCLAW_AGENT_DIR; - if (!agentDir) throw new Error("OPENCLAW_AGENT_DIR not set"); + if (!agentDir) { + throw new Error("OPENCLAW_AGENT_DIR not set"); + } return agentDir; }; diff --git a/src/commands/onboard-channels.test.ts b/src/commands/onboard-channels.test.ts index d6d281f96a..786f0a62f9 100644 --- a/src/commands/onboard-channels.test.ts +++ b/src/commands/onboard-channels.test.ts @@ -90,8 +90,12 @@ describe("setupChannels", () => { it("prompts for configured channel action and skips configuration when told to skip", async () => { const select = vi.fn(async ({ message }: { message: string }) => { - if (message === "Select channel (QuickStart)") return "telegram"; - if (message.includes("already configured")) return "skip"; + if (message === "Select channel (QuickStart)") { + return "telegram"; + } + if (message.includes("already configured")) { + return "skip"; + } throw new Error(`unexpected select prompt: ${message}`); }); const multiselect = vi.fn(async () => { @@ -156,7 +160,9 @@ describe("setupChannels", () => { expect(telegram?.hint).toContain("disabled"); return selectionCount === 1 ? "telegram" : "__done__"; } - if (message.includes("already configured")) return "skip"; + if (message.includes("already configured")) { + return "skip"; + } return "__done__"; }); const multiselect = vi.fn(async () => { diff --git a/src/commands/onboard-channels.ts b/src/commands/onboard-channels.ts index ffc63b814b..9628daa396 100644 --- a/src/commands/onboard-channels.ts +++ b/src/commands/onboard-channels.ts @@ -89,10 +89,14 @@ async function promptRemovalAccountId(params: { }): Promise { const { cfg, prompter, label, channel } = params; const plugin = getChannelPlugin(channel); - if (!plugin) return DEFAULT_ACCOUNT_ID; + if (!plugin) { + return DEFAULT_ACCOUNT_ID; + } const accountIds = plugin.config.listAccountIds(cfg).filter(Boolean); const defaultAccountId = resolveChannelDefaultAccountId({ plugin, cfg, accountIds }); - if (accountIds.length <= 1) return defaultAccountId; + if (accountIds.length <= 1) { + return defaultAccountId; + } const selected = await prompter.select({ message: `${label} account`, options: accountIds.map((accountId) => ({ @@ -204,7 +208,9 @@ function resolveQuickstartDefault( ): ChannelChoice | undefined { let best: { channel: ChannelChoice; score: number } | null = null; for (const [channel, status] of statusByChannel) { - if (status.quickstartScore == null) continue; + if (status.quickstartScore == null) { + continue; + } if (!best || status.quickstartScore > best.score) { best = { channel, score: status.quickstartScore }; } @@ -222,13 +228,17 @@ async function maybeConfigureDmPolicies(params: { const dmPolicies = selection .map((channel) => getChannelOnboardingAdapter(channel)?.dmPolicy) .filter(Boolean) as ChannelOnboardingDmPolicy[]; - if (dmPolicies.length === 0) return params.cfg; + if (dmPolicies.length === 0) { + return params.cfg; + } const wants = await prompter.confirm({ message: "Configure DM access policies now? (default: pairing)", initialValue: false, }); - if (!wants) return params.cfg; + if (!wants) { + return params.cfg; + } let cfg = params.cfg; const selectPolicy = async (policy: ChannelOnboardingDmPolicy) => { @@ -301,7 +311,9 @@ export async function setupChannels( message: "Configure chat channels now?", initialValue: true, }); - if (!shouldConfigure) return cfg; + if (!shouldConfigure) { + return cfg; + } const corePrimer = listChatChannels().map((meta) => ({ id: meta.id, @@ -342,14 +354,20 @@ export async function setupChannels( const selection: ChannelChoice[] = []; const addSelection = (channel: ChannelChoice) => { - if (!selection.includes(channel)) selection.push(channel); + if (!selection.includes(channel)) { + selection.push(channel); + } }; const resolveDisabledHint = (channel: ChannelChoice): string | undefined => { const plugin = getChannelPlugin(channel); if (!plugin) { - if (next.plugins?.entries?.[channel]?.enabled === false) return "plugin disabled"; - if (next.plugins?.enabled === false) return "plugins disabled"; + if (next.plugins?.entries?.[channel]?.enabled === false) { + return "plugin disabled"; + } + if (next.plugins?.enabled === false) { + return "plugins disabled"; + } return undefined; } const accountId = resolveChannelDefaultAccountId({ plugin, cfg: next }); @@ -418,13 +436,17 @@ export async function setupChannels( const refreshStatus = async (channel: ChannelChoice) => { const adapter = getChannelOnboardingAdapter(channel); - if (!adapter) return; + if (!adapter) { + return; + } const status = await adapter.getStatus({ cfg: next, options, accountOverrides }); statusByChannel.set(channel, status); }; const ensureBundledPluginEnabled = async (channel: ChannelChoice): Promise => { - if (getChannelPlugin(channel)) return true; + if (getChannelPlugin(channel)) { + return true; + } const result = enablePluginInConfig(next, channel); next = result.config; if (!result.enabled) { @@ -485,12 +507,16 @@ export async function setupChannels( supportsDelete, }); - if (action === "skip") return; + if (action === "skip") { + return; + } if (action === "update") { await configureChannel(channel); return; } - if (!options?.allowDisable) return; + if (!options?.allowDisable) { + return; + } if (action === "delete" && !supportsDelete) { await prompter.note(`${label} does not support deleting config entries.`, "Remove channel"); @@ -519,7 +545,9 @@ export async function setupChannels( message: `Delete ${label} account "${accountLabel}"?`, initialValue: false, }); - if (!confirmed) return; + if (!confirmed) { + return; + } if (plugin?.config.deleteAccount) { next = plugin.config.deleteAccount({ cfg: next, accountId: resolvedAccountId }); } @@ -552,7 +580,9 @@ export async function setupChannels( workspaceDir, }); next = result.cfg; - if (!result.installed) return; + if (!result.installed) { + return; + } reloadOnboardingPluginRegistry({ cfg: next, runtime, @@ -561,7 +591,9 @@ export async function setupChannels( await refreshStatus(channel); } else { const enabled = await ensureBundledPluginEnabled(channel); - if (!enabled) return; + if (!enabled) { + return; + } } const plugin = getChannelPlugin(channel); @@ -609,7 +641,9 @@ export async function setupChannels( ], initialValue, })) as ChannelChoice | typeof doneValue; - if (choice === doneValue) break; + if (choice === doneValue) { + break; + } await handleChannelChoice(choice); } } diff --git a/src/commands/onboard-helpers.ts b/src/commands/onboard-helpers.ts index 8703d0259d..ff2a8ce7b3 100644 --- a/src/commands/onboard-helpers.ts +++ b/src/commands/onboard-helpers.ts @@ -39,16 +39,24 @@ export function guardCancel(value: T | symbol, runtime: RuntimeEnv): T { export function summarizeExistingConfig(config: OpenClawConfig): string { const rows: string[] = []; const defaults = config.agents?.defaults; - if (defaults?.workspace) rows.push(shortenHomeInString(`workspace: ${defaults.workspace}`)); + if (defaults?.workspace) { + rows.push(shortenHomeInString(`workspace: ${defaults.workspace}`)); + } if (defaults?.model) { const model = typeof defaults.model === "string" ? defaults.model : defaults.model.primary; - if (model) rows.push(shortenHomeInString(`model: ${model}`)); + if (model) { + rows.push(shortenHomeInString(`model: ${model}`)); + } + } + if (config.gateway?.mode) { + rows.push(shortenHomeInString(`gateway.mode: ${config.gateway.mode}`)); } - if (config.gateway?.mode) rows.push(shortenHomeInString(`gateway.mode: ${config.gateway.mode}`)); if (typeof config.gateway?.port === "number") { rows.push(shortenHomeInString(`gateway.port: ${config.gateway.port}`)); } - if (config.gateway?.bind) rows.push(shortenHomeInString(`gateway.bind: ${config.gateway.bind}`)); + if (config.gateway?.bind) { + rows.push(shortenHomeInString(`gateway.bind: ${config.gateway.bind}`)); + } if (config.gateway?.remote?.url) { rows.push(shortenHomeInString(`gateway.remote.url: ${config.gateway.remote.url}`)); } @@ -63,7 +71,9 @@ export function randomToken(): string { } export function normalizeGatewayTokenInput(value: unknown): string { - if (typeof value !== "string") return ""; + if (typeof value !== "string") { + return ""; + } return value.trim(); } @@ -147,8 +157,12 @@ export async function resolveBrowserOpenCommand(): Promise { } if (wsl) { const hasWslview = await detectBinary("wslview"); - if (hasWslview) return { argv: ["wslview"], command: "wslview" }; - if (!hasDisplay) return { argv: null, reason: "wsl-no-wslview" }; + if (hasWslview) { + return { argv: ["wslview"], command: "wslview" }; + } + if (!hasDisplay) { + return { argv: null, reason: "wsl-no-wslview" }; + } } const hasXdgOpen = await detectBinary("xdg-open"); return hasXdgOpen @@ -161,7 +175,9 @@ export async function resolveBrowserOpenCommand(): Promise { export async function detectBrowserOpenSupport(): Promise { const resolved = await resolveBrowserOpenCommand(); - if (!resolved.argv) return { ok: false, reason: resolved.reason }; + if (!resolved.argv) { + return { ok: false, reason: resolved.reason }; + } return { ok: true, command: resolved.command }; } @@ -198,9 +214,13 @@ function resolveSshTargetHint(): string { } export async function openUrl(url: string): Promise { - if (shouldSkipBrowserOpenInTests()) return false; + if (shouldSkipBrowserOpenInTests()) { + return false; + } const resolved = await resolveBrowserOpenCommand(); - if (!resolved.argv) return false; + if (!resolved.argv) { + return false; + } const quoteUrl = resolved.quoteUrl === true; const command = [...resolved.argv]; if (quoteUrl) { @@ -225,10 +245,16 @@ export async function openUrl(url: string): Promise { } export async function openUrlInBackground(url: string): Promise { - if (shouldSkipBrowserOpenInTests()) return false; - if (process.platform !== "darwin") return false; + if (shouldSkipBrowserOpenInTests()) { + return false; + } + if (process.platform !== "darwin") { + return false; + } const resolved = await resolveBrowserOpenCommand(); - if (!resolved.argv || resolved.command !== "open") return false; + if (!resolved.argv || resolved.command !== "open") { + return false; + } const command = ["open", "-g", url]; try { await runCommandWithTimeout(command, { timeoutMs: 5_000 }); @@ -265,7 +291,9 @@ export function resolveNodeManagerOptions(): Array<{ } export async function moveToTrash(pathname: string, runtime: RuntimeEnv): Promise { - if (!pathname) return; + if (!pathname) { + return; + } try { await fs.access(pathname); } catch { @@ -281,7 +309,9 @@ export async function moveToTrash(pathname: string, runtime: RuntimeEnv): Promis export async function handleReset(scope: ResetScope, workspaceDir: string, runtime: RuntimeEnv) { await moveToTrash(CONFIG_PATH, runtime); - if (scope === "config") return; + if (scope === "config") { + return; + } await moveToTrash(path.join(CONFIG_DIR, "credentials"), runtime); await moveToTrash(resolveSessionTranscriptsDirForAgent(), runtime); if (scope === "full") { @@ -290,8 +320,12 @@ export async function handleReset(scope: ResetScope, workspaceDir: string, runti } export async function detectBinary(name: string): Promise { - if (!name?.trim()) return false; - if (!isSafeExecutableValue(name)) return false; + if (!name?.trim()) { + return false; + } + if (!isSafeExecutableValue(name)) { + return false; + } const resolved = name.startsWith("~") ? resolveUserPath(name) : name; if ( path.isAbsolute(resolved) || @@ -317,7 +351,9 @@ export async function detectBinary(name: string): Promise { } function shouldSkipBrowserOpenInTests(): boolean { - if (process.env.VITEST) return true; + if (process.env.VITEST) { + return true; + } return process.env.NODE_ENV === "test"; } @@ -369,7 +405,9 @@ export async function waitForGatewayReachable(params: { password: params.password, timeoutMs: probeTimeoutMs, }); - if (probe.ok) return probe; + if (probe.ok) { + return probe; + } lastDetail = probe.detail; await sleep(pollMs); } @@ -410,7 +448,9 @@ export function resolveControlUiLinks(params: { if (bind === "custom" && customBindHost && isValidIPv4(customBindHost)) { return customBindHost; } - if (bind === "tailnet" && tailnetIPv4) return tailnetIPv4 ?? "127.0.0.1"; + if (bind === "tailnet" && tailnetIPv4) { + return tailnetIPv4 ?? "127.0.0.1"; + } return "127.0.0.1"; })(); const basePath = normalizeControlUiBasePath(params.basePath); @@ -424,7 +464,9 @@ export function resolveControlUiLinks(params: { function isValidIPv4(host: string): boolean { const parts = host.split("."); - if (parts.length !== 4) return false; + if (parts.length !== 4) { + return false; + } return parts.every((part) => { const n = Number.parseInt(part, 10); return !Number.isNaN(n) && n >= 0 && n <= 255 && part === String(n); diff --git a/src/commands/onboard-non-interactive.gateway.test.ts b/src/commands/onboard-non-interactive.gateway.test.ts index 27170a1771..bd5c46ed57 100644 --- a/src/commands/onboard-non-interactive.gateway.test.ts +++ b/src/commands/onboard-non-interactive.gateway.test.ts @@ -55,8 +55,11 @@ async function getFreePort(): Promise { } const port = addr.port; srv.close((err) => { - if (err) reject(err); - else resolve(port); + if (err) { + reject(err); + } else { + resolve(port); + } }); }); }); diff --git a/src/commands/onboard-non-interactive/api-keys.ts b/src/commands/onboard-non-interactive/api-keys.ts index f29a7a154c..43c73f0825 100644 --- a/src/commands/onboard-non-interactive/api-keys.ts +++ b/src/commands/onboard-non-interactive/api-keys.ts @@ -22,14 +22,18 @@ async function resolveApiKeyFromProfiles(params: { }); for (const profileId of order) { const cred = store.profiles[profileId]; - if (cred?.type !== "api_key") continue; + if (cred?.type !== "api_key") { + continue; + } const resolved = await resolveApiKeyForProfile({ cfg: params.cfg, store, profileId, agentDir: params.agentDir, }); - if (resolved?.apiKey) return resolved.apiKey; + if (resolved?.apiKey) { + return resolved.apiKey; + } } return null; } @@ -45,10 +49,14 @@ export async function resolveNonInteractiveApiKey(params: { allowProfile?: boolean; }): Promise<{ key: string; source: NonInteractiveApiKeySource } | null> { const flagKey = params.flagValue?.trim(); - if (flagKey) return { key: flagKey, source: "flag" }; + if (flagKey) { + return { key: flagKey, source: "flag" }; + } const envResolved = resolveEnvApiKey(params.provider); - if (envResolved?.apiKey) return { key: envResolved.apiKey, source: "env" }; + if (envResolved?.apiKey) { + return { key: envResolved.apiKey, source: "env" }; + } if (params.allowProfile ?? true) { const profileKey = await resolveApiKeyFromProfiles({ @@ -56,7 +64,9 @@ export async function resolveNonInteractiveApiKey(params: { cfg: params.cfg, agentDir: params.agentDir, }); - if (profileKey) return { key: profileKey, source: "profile" }; + if (profileKey) { + return { key: profileKey, source: "profile" }; + } } const profileHint = diff --git a/src/commands/onboard-non-interactive/local.ts b/src/commands/onboard-non-interactive/local.ts index 04bbad832c..5a4edf6856 100644 --- a/src/commands/onboard-non-interactive/local.ts +++ b/src/commands/onboard-non-interactive/local.ts @@ -58,7 +58,9 @@ export async function runNonInteractiveOnboardingLocal(params: { runtime, baseConfig, }); - if (!nextConfigAfterAuth) return; + if (!nextConfigAfterAuth) { + return; + } nextConfig = nextConfigAfterAuth; const gatewayBasePort = resolveGatewayPort(baseConfig); @@ -68,7 +70,9 @@ export async function runNonInteractiveOnboardingLocal(params: { runtime, defaultPort: gatewayBasePort, }); - if (!gatewayResult) return; + if (!gatewayResult) { + return; + } nextConfig = gatewayResult.nextConfig; nextConfig = applyNonInteractiveSkillsConfig({ nextConfig, opts, runtime }); diff --git a/src/commands/onboard-non-interactive/local/auth-choice.ts b/src/commands/onboard-non-interactive/local/auth-choice.ts index 9ad97bac27..f930a7c3d3 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice.ts @@ -77,8 +77,12 @@ export async function applyNonInteractiveAuthChoice(params: { envVar: "ANTHROPIC_API_KEY", runtime, }); - if (!resolved) return null; - if (resolved.source !== "profile") await setAnthropicApiKey(resolved.key); + if (!resolved) { + return null; + } + if (resolved.source !== "profile") { + await setAnthropicApiKey(resolved.key); + } return applyAuthProfileConfig(nextConfig, { profileId: "anthropic:default", provider: "anthropic", @@ -150,8 +154,12 @@ export async function applyNonInteractiveAuthChoice(params: { envVar: "GEMINI_API_KEY", runtime, }); - if (!resolved) return null; - if (resolved.source !== "profile") await setGeminiApiKey(resolved.key); + if (!resolved) { + return null; + } + if (resolved.source !== "profile") { + await setGeminiApiKey(resolved.key); + } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "google:default", provider: "google", @@ -169,8 +177,12 @@ export async function applyNonInteractiveAuthChoice(params: { envVar: "ZAI_API_KEY", runtime, }); - if (!resolved) return null; - if (resolved.source !== "profile") await setZaiApiKey(resolved.key); + if (!resolved) { + return null; + } + if (resolved.source !== "profile") { + await setZaiApiKey(resolved.key); + } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "zai:default", provider: "zai", @@ -188,8 +200,12 @@ export async function applyNonInteractiveAuthChoice(params: { envVar: "XIAOMI_API_KEY", runtime, }); - if (!resolved) return null; - if (resolved.source !== "profile") await setXiaomiApiKey(resolved.key); + if (!resolved) { + return null; + } + if (resolved.source !== "profile") { + await setXiaomiApiKey(resolved.key); + } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "xiaomi:default", provider: "xiaomi", @@ -208,7 +224,9 @@ export async function applyNonInteractiveAuthChoice(params: { runtime, allowProfile: false, }); - if (!resolved) return null; + if (!resolved) { + return null; + } const key = resolved.key; const result = upsertSharedEnvVar({ key: "OPENAI_API_KEY", value: key }); process.env.OPENAI_API_KEY = key; @@ -225,8 +243,12 @@ export async function applyNonInteractiveAuthChoice(params: { envVar: "OPENROUTER_API_KEY", runtime, }); - if (!resolved) return null; - if (resolved.source !== "profile") await setOpenrouterApiKey(resolved.key); + if (!resolved) { + return null; + } + if (resolved.source !== "profile") { + await setOpenrouterApiKey(resolved.key); + } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "openrouter:default", provider: "openrouter", @@ -244,8 +266,12 @@ export async function applyNonInteractiveAuthChoice(params: { envVar: "AI_GATEWAY_API_KEY", runtime, }); - if (!resolved) return null; - if (resolved.source !== "profile") await setVercelAiGatewayApiKey(resolved.key); + if (!resolved) { + return null; + } + if (resolved.source !== "profile") { + await setVercelAiGatewayApiKey(resolved.key); + } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "vercel-ai-gateway:default", provider: "vercel-ai-gateway", @@ -263,8 +289,12 @@ export async function applyNonInteractiveAuthChoice(params: { envVar: "MOONSHOT_API_KEY", runtime, }); - if (!resolved) return null; - if (resolved.source !== "profile") await setMoonshotApiKey(resolved.key); + if (!resolved) { + return null; + } + if (resolved.source !== "profile") { + await setMoonshotApiKey(resolved.key); + } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "moonshot:default", provider: "moonshot", @@ -282,8 +312,12 @@ export async function applyNonInteractiveAuthChoice(params: { envVar: "KIMI_API_KEY", runtime, }); - if (!resolved) return null; - if (resolved.source !== "profile") await setKimiCodingApiKey(resolved.key); + if (!resolved) { + return null; + } + if (resolved.source !== "profile") { + await setKimiCodingApiKey(resolved.key); + } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "kimi-coding:default", provider: "kimi-coding", @@ -301,8 +335,12 @@ export async function applyNonInteractiveAuthChoice(params: { envVar: "SYNTHETIC_API_KEY", runtime, }); - if (!resolved) return null; - if (resolved.source !== "profile") await setSyntheticApiKey(resolved.key); + if (!resolved) { + return null; + } + if (resolved.source !== "profile") { + await setSyntheticApiKey(resolved.key); + } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "synthetic:default", provider: "synthetic", @@ -320,8 +358,12 @@ export async function applyNonInteractiveAuthChoice(params: { envVar: "VENICE_API_KEY", runtime, }); - if (!resolved) return null; - if (resolved.source !== "profile") await setVeniceApiKey(resolved.key); + if (!resolved) { + return null; + } + if (resolved.source !== "profile") { + await setVeniceApiKey(resolved.key); + } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "venice:default", provider: "venice", @@ -343,8 +385,12 @@ export async function applyNonInteractiveAuthChoice(params: { envVar: "MINIMAX_API_KEY", runtime, }); - if (!resolved) return null; - if (resolved.source !== "profile") await setMinimaxApiKey(resolved.key); + if (!resolved) { + return null; + } + if (resolved.source !== "profile") { + await setMinimaxApiKey(resolved.key); + } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "minimax:default", provider: "minimax", @@ -355,7 +401,9 @@ export async function applyNonInteractiveAuthChoice(params: { return applyMinimaxApiConfig(nextConfig, modelId); } - if (authChoice === "minimax") return applyMinimaxConfig(nextConfig); + if (authChoice === "minimax") { + return applyMinimaxConfig(nextConfig); + } if (authChoice === "opencode-zen") { const resolved = await resolveNonInteractiveApiKey({ @@ -366,8 +414,12 @@ export async function applyNonInteractiveAuthChoice(params: { envVar: "OPENCODE_API_KEY (or OPENCODE_ZEN_API_KEY)", runtime, }); - if (!resolved) return null; - if (resolved.source !== "profile") await setOpencodeZenApiKey(resolved.key); + if (!resolved) { + return null; + } + if (resolved.source !== "profile") { + await setOpencodeZenApiKey(resolved.key); + } nextConfig = applyAuthProfileConfig(nextConfig, { profileId: "opencode:default", provider: "opencode", diff --git a/src/commands/onboard-non-interactive/local/daemon-install.ts b/src/commands/onboard-non-interactive/local/daemon-install.ts index 77fccac59e..9d994bfed5 100644 --- a/src/commands/onboard-non-interactive/local/daemon-install.ts +++ b/src/commands/onboard-non-interactive/local/daemon-install.ts @@ -15,7 +15,9 @@ export async function installGatewayDaemonNonInteractive(params: { gatewayToken?: string; }) { const { opts, runtime, port, gatewayToken } = params; - if (!opts.installDaemon) return; + if (!opts.installDaemon) { + return; + } const daemonRuntimeRaw = opts.daemonRuntime ?? DEFAULT_GATEWAY_DAEMON_RUNTIME; const systemdAvailable = diff --git a/src/commands/onboard-non-interactive/local/gateway-config.ts b/src/commands/onboard-non-interactive/local/gateway-config.ts index 9e66fd63a5..a786838cef 100644 --- a/src/commands/onboard-non-interactive/local/gateway-config.ts +++ b/src/commands/onboard-non-interactive/local/gateway-config.ts @@ -41,14 +41,20 @@ export function applyNonInteractiveGatewayConfig(params: { // Tighten config to safe combos: // - If Tailscale is on, force loopback bind (the tunnel handles external access). // - If using Tailscale Funnel, require password auth. - if (tailscaleMode !== "off" && bind !== "loopback") bind = "loopback"; - if (tailscaleMode === "funnel" && authMode !== "password") authMode = "password"; + if (tailscaleMode !== "off" && bind !== "loopback") { + bind = "loopback"; + } + if (tailscaleMode === "funnel" && authMode !== "password") { + authMode = "password"; + } let nextConfig = params.nextConfig; let gatewayToken = opts.gatewayToken?.trim() || undefined; if (authMode === "token") { - if (!gatewayToken) gatewayToken = randomToken(); + if (!gatewayToken) { + gatewayToken = randomToken(); + } nextConfig = { ...nextConfig, gateway: { diff --git a/src/commands/onboard-non-interactive/local/output.ts b/src/commands/onboard-non-interactive/local/output.ts index e8f79c8c28..d4296e3500 100644 --- a/src/commands/onboard-non-interactive/local/output.ts +++ b/src/commands/onboard-non-interactive/local/output.ts @@ -18,7 +18,9 @@ export function logNonInteractiveOnboardingJson(params: { skipSkills?: boolean; skipHealth?: boolean; }) { - if (!params.opts.json) return; + if (!params.opts.json) { + return; + } params.runtime.log( JSON.stringify( { diff --git a/src/commands/onboard-non-interactive/local/skills-config.ts b/src/commands/onboard-non-interactive/local/skills-config.ts index 2b187a3e71..c71de0845f 100644 --- a/src/commands/onboard-non-interactive/local/skills-config.ts +++ b/src/commands/onboard-non-interactive/local/skills-config.ts @@ -8,7 +8,9 @@ export function applyNonInteractiveSkillsConfig(params: { runtime: RuntimeEnv; }) { const { nextConfig, opts, runtime } = params; - if (opts.skipSkills) return nextConfig; + if (opts.skipSkills) { + return nextConfig; + } const nodeManager = opts.nodeManager ?? "npm"; if (!["npm", "pnpm", "bun"].includes(nodeManager)) { diff --git a/src/commands/onboard-remote.ts b/src/commands/onboard-remote.ts index d471f6d825..57b93a8270 100644 --- a/src/commands/onboard-remote.ts +++ b/src/commands/onboard-remote.ts @@ -21,7 +21,9 @@ function buildLabel(beacon: GatewayBonjourBeacon): string { function ensureWsUrl(value: string): string { const trimmed = value.trim(); - if (!trimmed) return DEFAULT_GATEWAY_URL; + if (!trimmed) { + return DEFAULT_GATEWAY_URL; + } return trimmed; } diff --git a/src/commands/onboard-skills.ts b/src/commands/onboard-skills.ts index 82a13b36bb..6432de0f85 100644 --- a/src/commands/onboard-skills.ts +++ b/src/commands/onboard-skills.ts @@ -8,7 +8,9 @@ import { detectBinary, resolveNodeManagerOptions } from "./onboard-helpers.js"; function summarizeInstallFailure(message: string): string | undefined { const cleaned = message.replace(/^Install failed(?:\s*\([^)]*\))?\s*:?\s*/i, "").trim(); - if (!cleaned) return undefined; + if (!cleaned) { + return undefined; + } const maxLen = 140; return cleaned.length > maxLen ? `${cleaned.slice(0, maxLen - 1)}…` : cleaned; } @@ -20,7 +22,9 @@ function formatSkillHint(skill: { const desc = skill.description?.trim(); const installLabel = skill.install[0]?.label?.trim(); const combined = desc && installLabel ? `${desc} — ${installLabel}` : desc || installLabel; - if (!combined) return "install"; + if (!combined) { + return "install"; + } const maxLen = 90; return combined.length > maxLen ? `${combined.slice(0, maxLen - 1)}…` : combined; } @@ -71,7 +75,9 @@ export async function setupSkills( message: "Configure skills now? (recommended)", initialValue: true, }); - if (!shouldConfigure) return cfg; + if (!shouldConfigure) { + return cfg; + } if (needsBrewPrompt) { await prompter.note( @@ -135,9 +141,13 @@ export async function setupSkills( const selected = toInstall.filter((name) => name !== "__skip__"); for (const name of selected) { const target = installable.find((s) => s.name === name); - if (!target || target.install.length === 0) continue; + if (!target || target.install.length === 0) { + continue; + } const installId = target.install[0]?.id; - if (!installId) continue; + if (!installId) { + continue; + } const spin = prompter.progress(`Installing ${name}…`); const result = await installSkill({ workspaceDir, @@ -151,8 +161,11 @@ export async function setupSkills( const code = result.code == null ? "" : ` (exit ${result.code})`; const detail = summarizeInstallFailure(result.message); spin.stop(`Install failed: ${name}${code}${detail ? ` — ${detail}` : ""}`); - if (result.stderr) runtime.log(result.stderr.trim()); - else if (result.stdout) runtime.log(result.stdout.trim()); + if (result.stderr) { + runtime.log(result.stderr.trim()); + } else if (result.stdout) { + runtime.log(result.stdout.trim()); + } runtime.log( `Tip: run \`${formatCliCommand("openclaw doctor")}\` to review skills + requirements.`, ); @@ -162,12 +175,16 @@ export async function setupSkills( } for (const skill of missing) { - if (!skill.primaryEnv || skill.missing.env.length === 0) continue; + if (!skill.primaryEnv || skill.missing.env.length === 0) { + continue; + } const wantsKey = await prompter.confirm({ message: `Set ${skill.primaryEnv} for ${skill.name}?`, initialValue: false, }); - if (!wantsKey) continue; + if (!wantsKey) { + continue; + } const apiKey = String( await prompter.text({ message: `Enter ${skill.primaryEnv}`, diff --git a/src/commands/onboarding/plugin-install.ts b/src/commands/onboarding/plugin-install.ts index a44ec85bec..3e55657349 100644 --- a/src/commands/onboarding/plugin-install.ts +++ b/src/commands/onboarding/plugin-install.ts @@ -25,7 +25,9 @@ function hasGitWorkspace(workspaceDir?: string): boolean { candidates.add(path.join(workspaceDir, ".git")); } for (const candidate of candidates) { - if (fs.existsSync(candidate)) return true; + if (fs.existsSync(candidate)) { + return true; + } } return false; } @@ -35,16 +37,22 @@ function resolveLocalPath( workspaceDir: string | undefined, allowLocal: boolean, ): string | null { - if (!allowLocal) return null; + if (!allowLocal) { + return null; + } const raw = entry.install.localPath?.trim(); - if (!raw) return null; + if (!raw) { + return null; + } const candidates = new Set(); candidates.add(path.resolve(process.cwd(), raw)); if (workspaceDir && workspaceDir !== process.cwd()) { candidates.add(path.resolve(workspaceDir, raw)); } for (const candidate of candidates) { - if (fs.existsSync(candidate)) return candidate; + if (fs.existsSync(candidate)) { + return candidate; + } } return null; } @@ -108,8 +116,12 @@ function resolveInstallDefaultChoice(params: { return "npm"; } const entryDefault = entry.install.defaultChoice; - if (entryDefault === "local") return localPath ? "local" : "npm"; - if (entryDefault === "npm") return "npm"; + if (entryDefault === "local") { + return localPath ? "local" : "npm"; + } + if (entryDefault === "npm") { + return "npm"; + } return localPath ? "local" : "npm"; } diff --git a/src/commands/openai-codex-model-default.ts b/src/commands/openai-codex-model-default.ts index 97f7a5c459..08ff72ac6d 100644 --- a/src/commands/openai-codex-model-default.ts +++ b/src/commands/openai-codex-model-default.ts @@ -5,15 +5,23 @@ export const OPENAI_CODEX_DEFAULT_MODEL = "openai-codex/gpt-5.2"; function shouldSetOpenAICodexModel(model?: string): boolean { const trimmed = model?.trim(); - if (!trimmed) return true; + if (!trimmed) { + return true; + } const normalized = trimmed.toLowerCase(); - if (normalized.startsWith("openai-codex/")) return false; - if (normalized.startsWith("openai/")) return true; + if (normalized.startsWith("openai-codex/")) { + return false; + } + if (normalized.startsWith("openai/")) { + return true; + } return normalized === "gpt" || normalized === "gpt-mini"; } function resolvePrimaryModel(model?: AgentModelListConfig | string): string | undefined { - if (typeof model === "string") return model; + if (typeof model === "string") { + return model; + } if (model && typeof model === "object" && typeof model.primary === "string") { return model.primary; } diff --git a/src/commands/opencode-zen-model-default.ts b/src/commands/opencode-zen-model-default.ts index ef9aab2980..b3813fb5c8 100644 --- a/src/commands/opencode-zen-model-default.ts +++ b/src/commands/opencode-zen-model-default.ts @@ -5,7 +5,9 @@ export const OPENCODE_ZEN_DEFAULT_MODEL = "opencode/claude-opus-4-5"; const LEGACY_OPENCODE_ZEN_DEFAULT_MODEL = "opencode-zen/claude-opus-4-5"; function resolvePrimaryModel(model?: AgentModelListConfig | string): string | undefined { - if (typeof model === "string") return model; + if (typeof model === "string") { + return model; + } if (model && typeof model === "object" && typeof model.primary === "string") { return model.primary; } diff --git a/src/commands/reset.ts b/src/commands/reset.ts index d4681b2eb3..3b49867d70 100644 --- a/src/commands/reset.ts +++ b/src/commands/reset.ts @@ -37,7 +37,9 @@ const selectStyled = (params: Parameters>[0]) => }); async function stopGatewayIfRunning(runtime: RuntimeEnv) { - if (isNixMode) return; + if (isNixMode) { + return; + } const service = resolveGatewayService(); let loaded = false; try { @@ -46,7 +48,9 @@ async function stopGatewayIfRunning(runtime: RuntimeEnv) { runtime.error(`Gateway service check failed: ${String(err)}`); return; } - if (!loaded) return; + if (!loaded) { + return; + } try { await service.stop({ env: process.env, stdout: process.stdout }); } catch (err) { diff --git a/src/commands/sandbox-explain.ts b/src/commands/sandbox-explain.ts index 33948441bf..f91ab07c81 100644 --- a/src/commands/sandbox-explain.ts +++ b/src/commands/sandbox-explain.ts @@ -44,8 +44,12 @@ function normalizeExplainSessionKey(params: { agentId: params.agentId, }); } - if (raw.includes(":")) return raw; - if (raw === "global") return "global"; + if (raw.includes(":")) { + return raw; + } + if (raw === "global") { + return "global"; + } return buildAgentMainSessionKey({ agentId: params.agentId, mainKey: normalizeMainKey(raw), @@ -57,16 +61,28 @@ function inferProviderFromSessionKey(params: { sessionKey: string; }): string | undefined { const parsed = parseAgentSessionKey(params.sessionKey); - if (!parsed) return undefined; + if (!parsed) { + return undefined; + } const rest = parsed.rest.trim(); - if (!rest) return undefined; + if (!rest) { + return undefined; + } const parts = rest.split(":").filter(Boolean); - if (parts.length === 0) return undefined; + if (parts.length === 0) { + return undefined; + } const configuredMainKey = normalizeMainKey(params.cfg.session?.mainKey); - if (parts[0] === configuredMainKey) return undefined; + if (parts[0] === configuredMainKey) { + return undefined; + } const candidate = parts[0]?.trim().toLowerCase(); - if (!candidate) return undefined; - if (candidate === INTERNAL_MESSAGE_CHANNEL) return INTERNAL_MESSAGE_CHANNEL; + if (!candidate) { + return undefined; + } + if (candidate === INTERNAL_MESSAGE_CHANNEL) { + return INTERNAL_MESSAGE_CHANNEL; + } return normalizeAnyChannelId(candidate) ?? undefined; } @@ -97,9 +113,13 @@ function resolveActiveChannel(params: { ) .trim() .toLowerCase(); - if (candidate === INTERNAL_MESSAGE_CHANNEL) return INTERNAL_MESSAGE_CHANNEL; + if (candidate === INTERNAL_MESSAGE_CHANNEL) { + return INTERNAL_MESSAGE_CHANNEL; + } const normalized = normalizeAnyChannelId(candidate); - if (normalized) return normalized; + if (normalized) { + return normalized; + } return inferProviderFromSessionKey({ cfg: params.cfg, sessionKey: params.sessionKey, @@ -205,7 +225,9 @@ export async function sandboxExplainCommand( fixIt.push("agents.list[].tools.sandbox.tools.allow"); fixIt.push("agents.list[].tools.sandbox.tools.deny"); fixIt.push("tools.elevated.enabled"); - if (channel) fixIt.push(`tools.elevated.allowFrom.${channel}`); + if (channel) { + fixIt.push(`tools.elevated.allowFrom.${channel}`); + } const payload = { docsUrl: SANDBOX_DOCS_URL, @@ -305,7 +327,9 @@ export async function sandboxExplainCommand( } lines.push(""); lines.push(heading("Fix-it:")); - for (const key of payload.fixIt) lines.push(` - ${key}`); + for (const key of payload.fixIt) { + lines.push(` - ${key}`); + } lines.push(""); lines.push(`${key("Docs:")} ${formatDocsLink("/sandbox", "docs.openclaw.ai/sandbox")}`); diff --git a/src/commands/sandbox-formatters.ts b/src/commands/sandbox-formatters.ts index cc3eef865a..915017d191 100644 --- a/src/commands/sandbox-formatters.ts +++ b/src/commands/sandbox-formatters.ts @@ -20,9 +20,15 @@ export function formatAge(ms: number): string { const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); - if (days > 0) return `${days}d ${hours % 24}h`; - if (hours > 0) return `${hours}h ${minutes % 60}m`; - if (minutes > 0) return `${minutes}m`; + if (days > 0) { + return `${days}d ${hours % 24}h`; + } + if (hours > 0) { + return `${hours}h ${minutes % 60}m`; + } + if (minutes > 0) { + return `${minutes}m`; + } return `${seconds}s`; } diff --git a/src/commands/sessions.ts b/src/commands/sessions.ts index bd7232811f..0c091267c9 100644 --- a/src/commands/sessions.ts +++ b/src/commands/sessions.ts @@ -37,21 +37,33 @@ const TOKENS_PAD = 20; const formatKTokens = (value: number) => `${(value / 1000).toFixed(value >= 10_000 ? 0 : 1)}k`; const truncateKey = (key: string) => { - if (key.length <= KEY_PAD) return key; + if (key.length <= KEY_PAD) { + return key; + } const head = Math.max(4, KEY_PAD - 10); return `${key.slice(0, head)}...${key.slice(-6)}`; }; const colorByPct = (label: string, pct: number | null, rich: boolean) => { - if (!rich || pct === null) return label; - if (pct >= 95) return theme.error(label); - if (pct >= 80) return theme.warn(label); - if (pct >= 60) return theme.success(label); + if (!rich || pct === null) { + return label; + } + if (pct >= 95) { + return theme.error(label); + } + if (pct >= 80) { + return theme.warn(label); + } + if (pct >= 60) { + return theme.success(label); + } return theme.muted(label); }; const formatTokensCell = (total: number, contextTokens: number | null, rich: boolean) => { - if (!total) return "-".padEnd(TOKENS_PAD); + if (!total) { + return "-".padEnd(TOKENS_PAD); + } const totalLabel = formatKTokens(total); const ctxLabel = contextTokens ? formatKTokens(contextTokens) : "?"; const pct = contextTokens ? Math.min(999, Math.round((total / contextTokens) * 100)) : null; @@ -62,10 +74,18 @@ const formatTokensCell = (total: number, contextTokens: number | null, rich: boo const formatKindCell = (kind: SessionRow["kind"], rich: boolean) => { const label = kind.padEnd(KIND_PAD); - if (!rich) return label; - if (kind === "group") return theme.accentBright(label); - if (kind === "global") return theme.warn(label); - if (kind === "direct") return theme.accent(label); + if (!rich) { + return label; + } + if (kind === "group") { + return theme.accentBright(label); + } + if (kind === "global") { + return theme.warn(label); + } + if (kind === "direct") { + return theme.accent(label); + } return theme.muted(label); }; @@ -97,19 +117,31 @@ const formatFlagsCell = (row: SessionRow, rich: boolean) => { }; const formatAge = (ms: number | null | undefined) => { - if (!ms || ms < 0) return "unknown"; + if (!ms || ms < 0) { + return "unknown"; + } const minutes = Math.round(ms / 60_000); - if (minutes < 1) return "just now"; - if (minutes < 60) return `${minutes}m ago`; + if (minutes < 1) { + return "just now"; + } + if (minutes < 60) { + return `${minutes}m ago`; + } const hours = Math.round(minutes / 60); - if (hours < 48) return `${hours}h ago`; + if (hours < 48) { + return `${hours}h ago`; + } const days = Math.round(hours / 24); return `${days}d ago`; }; function classifyKey(key: string, entry?: SessionEntry): SessionRow["kind"] { - if (key === "global") return "global"; - if (key === "unknown") return "unknown"; + if (key === "global") { + return "global"; + } + if (key === "unknown") { + return "unknown"; + } if (entry?.chatType === "group" || entry?.chatType === "channel") { return "group"; } @@ -177,8 +209,12 @@ export async function sessionsCommand( } const rows = toRows(store).filter((row) => { - if (activeMinutes === undefined) return true; - if (!row.updatedAt) return false; + if (activeMinutes === undefined) { + return true; + } + if (!row.updatedAt) { + return false; + } return Date.now() - row.updatedAt <= activeMinutes * 60_000; }); diff --git a/src/commands/signal-install.ts b/src/commands/signal-install.ts index f314dbcda7..b795a3085a 100644 --- a/src/commands/signal-install.ts +++ b/src/commands/signal-install.ts @@ -94,7 +94,9 @@ async function downloadToFile(url: string, dest: string, maxRedirects = 5): Prom async function findSignalCliBinary(root: string): Promise { const candidates: string[] = []; const enqueue = async (dir: string, depth: number) => { - if (depth > 3) return; + if (depth > 3) { + return; + } const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => []); for (const entry of entries) { const full = path.join(dir, entry.name); diff --git a/src/commands/status-all.ts b/src/commands/status-all.ts index 6c52b00278..611caddb76 100644 --- a/src/commands/status-all.ts +++ b/src/commands/status-all.ts @@ -197,7 +197,9 @@ export async function statusAllCommand( progress.tick(); const connectionDetailsForReport = (() => { - if (!remoteUrlMissing) return connection.message; + if (!remoteUrlMissing) { + return connection.message; + } const bindMode = cfg.gateway?.bind ?? "loopback"; const configPath = snap?.path?.trim() ? snap.path.trim() : "(unknown config path)"; return [ @@ -277,31 +279,50 @@ export async function statusAllCommand( if (update.installKind === "git" && update.git) { const parts: string[] = []; parts.push(update.git.branch ? `git ${update.git.branch}` : "git"); - if (update.git.upstream) parts.push(`↔ ${update.git.upstream}`); - if (update.git.dirty) parts.push("dirty"); - if (update.git.behind != null && update.git.ahead != null) { - if (update.git.behind === 0 && update.git.ahead === 0) parts.push("up to date"); - else if (update.git.behind > 0 && update.git.ahead === 0) - parts.push(`behind ${update.git.behind}`); - else if (update.git.behind === 0 && update.git.ahead > 0) - parts.push(`ahead ${update.git.ahead}`); - else parts.push(`diverged (ahead ${update.git.ahead}, behind ${update.git.behind})`); + if (update.git.upstream) { + parts.push(`↔ ${update.git.upstream}`); + } + if (update.git.dirty) { + parts.push("dirty"); + } + if (update.git.behind != null && update.git.ahead != null) { + if (update.git.behind === 0 && update.git.ahead === 0) { + parts.push("up to date"); + } else if (update.git.behind > 0 && update.git.ahead === 0) { + parts.push(`behind ${update.git.behind}`); + } else if (update.git.behind === 0 && update.git.ahead > 0) { + parts.push(`ahead ${update.git.ahead}`); + } else { + parts.push(`diverged (ahead ${update.git.ahead}, behind ${update.git.behind})`); + } + } + if (update.git.fetchOk === false) { + parts.push("fetch failed"); } - if (update.git.fetchOk === false) parts.push("fetch failed"); const latest = update.registry?.latestVersion; if (latest) { const cmp = compareSemverStrings(VERSION, latest); - if (cmp === 0) parts.push(`npm latest ${latest}`); - else if (cmp != null && cmp < 0) parts.push(`npm update ${latest}`); - else parts.push(`npm latest ${latest} (local newer)`); + if (cmp === 0) { + parts.push(`npm latest ${latest}`); + } else if (cmp != null && cmp < 0) { + parts.push(`npm update ${latest}`); + } else { + parts.push(`npm latest ${latest} (local newer)`); + } } else if (update.registry?.error) { parts.push("npm latest unknown"); } - if (update.deps?.status === "ok") parts.push("deps ok"); - if (update.deps?.status === "stale") parts.push("deps stale"); - if (update.deps?.status === "missing") parts.push("deps missing"); + if (update.deps?.status === "ok") { + parts.push("deps ok"); + } + if (update.deps?.status === "stale") { + parts.push("deps stale"); + } + if (update.deps?.status === "missing") { + parts.push("deps missing"); + } return parts.join(" · "); } const parts: string[] = []; @@ -309,15 +330,25 @@ export async function statusAllCommand( const latest = update.registry?.latestVersion; if (latest) { const cmp = compareSemverStrings(VERSION, latest); - if (cmp === 0) parts.push(`npm latest ${latest}`); - else if (cmp != null && cmp < 0) parts.push(`npm update ${latest}`); - else parts.push(`npm latest ${latest} (local newer)`); + if (cmp === 0) { + parts.push(`npm latest ${latest}`); + } else if (cmp != null && cmp < 0) { + parts.push(`npm update ${latest}`); + } else { + parts.push(`npm latest ${latest} (local newer)`); + } } else if (update.registry?.error) { parts.push("npm latest unknown"); } - if (update.deps?.status === "ok") parts.push("deps ok"); - if (update.deps?.status === "stale") parts.push("deps stale"); - if (update.deps?.status === "missing") parts.push("deps missing"); + if (update.deps?.status === "ok") { + parts.push("deps ok"); + } + if (update.deps?.status === "stale") { + parts.push("deps stale"); + } + if (update.deps?.status === "missing") { + parts.push("deps missing"); + } return parts.join(" · "); })(); diff --git a/src/commands/status-all/channels.ts b/src/commands/status-all/channels.ts index 7229e1aa36..8b7d22fe45 100644 --- a/src/commands/status-all/channels.ts +++ b/src/commands/status-all/channels.ts @@ -47,7 +47,9 @@ function summarizeSources(sources: Array): { function existsSyncMaybe(p: string | undefined): boolean | null { const path = p?.trim() || ""; - if (!path) return null; + if (!path) { + return null; + } try { return fs.existsSync(path); } catch { @@ -61,17 +63,25 @@ function sha256HexPrefix(value: string, len = 8): string { function formatTokenHint(token: string, opts: { showSecrets: boolean }): string { const t = token.trim(); - if (!t) return "empty"; - if (!opts.showSecrets) return `sha256:${sha256HexPrefix(t)} · len ${t.length}`; + if (!t) { + return "empty"; + } + if (!opts.showSecrets) { + return `sha256:${sha256HexPrefix(t)} · len ${t.length}`; + } const head = t.slice(0, 4); const tail = t.slice(-4); - if (t.length <= 10) return `${t} · len ${t.length}`; + if (t.length <= 10) { + return `${t} · len ${t.length}`; + } return `${head}…${tail} · len ${t.length}`; } const formatAccountLabel = (params: { accountId: string; name?: string }) => { const base = params.accountId || "default"; - if (params.name?.trim()) return `${base} (${params.name.trim()})`; + if (params.name?.trim()) { + return `${base} (${params.name.trim()})`; + } return base; }; @@ -80,7 +90,9 @@ const resolveAccountEnabled = ( account: unknown, cfg: OpenClawConfig, ): boolean => { - if (plugin.config.isEnabled) return plugin.config.isEnabled(account, cfg); + if (plugin.config.isEnabled) { + return plugin.config.isEnabled(account, cfg); + } const enabled = asRecord(account).enabled; return enabled !== false; }; @@ -138,8 +150,12 @@ const buildAccountNotes = (params: { const { plugin, cfg, entry } = params; const notes: string[] = []; const snapshot = entry.snapshot; - if (snapshot.enabled === false) notes.push("disabled"); - if (snapshot.dmPolicy) notes.push(`dm:${snapshot.dmPolicy}`); + if (snapshot.enabled === false) { + notes.push("disabled"); + } + if (snapshot.dmPolicy) { + notes.push(`dm:${snapshot.dmPolicy}`); + } if (snapshot.tokenSource && snapshot.tokenSource !== "none") { notes.push(`token:${snapshot.tokenSource}`); } @@ -149,10 +165,18 @@ const buildAccountNotes = (params: { if (snapshot.appTokenSource && snapshot.appTokenSource !== "none") { notes.push(`app:${snapshot.appTokenSource}`); } - if (snapshot.baseUrl) notes.push(snapshot.baseUrl); - if (snapshot.port != null) notes.push(`port:${snapshot.port}`); - if (snapshot.cliPath) notes.push(`cli:${snapshot.cliPath}`); - if (snapshot.dbPath) notes.push(`db:${snapshot.dbPath}`); + if (snapshot.baseUrl) { + notes.push(snapshot.baseUrl); + } + if (snapshot.port != null) { + notes.push(`port:${snapshot.port}`); + } + if (snapshot.cliPath) { + notes.push(`cli:${snapshot.cliPath}`); + } + if (snapshot.dbPath) { + notes.push(`db:${snapshot.dbPath}`); + } const allowFrom = plugin.config.resolveAllowFrom?.({ cfg, accountId: snapshot.accountId }) ?? snapshot.allowFrom; @@ -163,7 +187,9 @@ const buildAccountNotes = (params: { accountId: snapshot.accountId, allowFrom, }).slice(0, 3); - if (formatted.length > 0) notes.push(`allow:${formatted.join(",")}`); + if (formatted.length > 0) { + notes.push(`allow:${formatted.join(",")}`); + } } return notes; @@ -198,7 +224,9 @@ function collectMissingPaths(accounts: ChannelAccountRow[]): string[] { const raw = (accountRec[key] as string | undefined) ?? (snapshotRec[key] as string | undefined); const ok = existsSyncMaybe(raw); - if (ok === false) missing.push(String(raw)); + if (ok === false) { + missing.push(String(raw)); + } } } return missing; @@ -211,7 +239,9 @@ function summarizeTokenConfig(params: { showSecrets: boolean; }): { state: "ok" | "setup" | "warn" | null; detail: string | null } { const enabled = params.accounts.filter((a) => a.enabled); - if (enabled.length === 0) return { state: null, detail: null }; + if (enabled.length === 0) { + return { state: null, detail: null }; + } const accountRecs = enabled.map((a) => asRecord(a.account)); const hasBotOrAppTokenFields = accountRecs.some((r) => "botToken" in r || "appToken" in r); @@ -365,28 +395,50 @@ export async function buildChannelsTable( const label = plugin.meta.label ?? plugin.id; const state = (() => { - if (!anyEnabled) return "off"; - if (missingPaths.length > 0) return "warn"; - if (issues.length > 0) return "warn"; - if (link.linked === false) return "setup"; - if (tokenSummary.state) return tokenSummary.state; - if (link.linked === true) return "ok"; - if (configuredAccounts.length > 0) return "ok"; + if (!anyEnabled) { + return "off"; + } + if (missingPaths.length > 0) { + return "warn"; + } + if (issues.length > 0) { + return "warn"; + } + if (link.linked === false) { + return "setup"; + } + if (tokenSummary.state) { + return tokenSummary.state; + } + if (link.linked === true) { + return "ok"; + } + if (configuredAccounts.length > 0) { + return "ok"; + } return "setup"; })(); const detail = (() => { if (!anyEnabled) { - if (!defaultEntry) return "disabled"; + if (!defaultEntry) { + return "disabled"; + } return plugin.config.disabledReason?.(defaultEntry.account, cfg) ?? "disabled"; } - if (missingPaths.length > 0) return `missing file (${missingPaths[0]})`; - if (issues.length > 0) return issues[0]?.message ?? "misconfigured"; + if (missingPaths.length > 0) { + return `missing file (${missingPaths[0]})`; + } + if (issues.length > 0) { + return issues[0]?.message ?? "misconfigured"; + } if (link.linked !== null) { const base = link.linked ? "linked" : "not linked"; const extra: string[] = []; - if (link.linked && link.selfE164) extra.push(link.selfE164); + if (link.linked && link.selfE164) { + extra.push(link.selfE164); + } if (link.linked && link.authAgeMs != null && link.authAgeMs >= 0) { extra.push(`auth ${formatAge(link.authAgeMs)}`); } @@ -396,11 +448,15 @@ export async function buildChannelsTable( return extra.length > 0 ? `${base} · ${extra.join(" · ")}` : base; } - if (tokenSummary.detail) return tokenSummary.detail; + if (tokenSummary.detail) { + return tokenSummary.detail; + } if (configuredAccounts.length > 0) { const head = "configured"; - if (accounts.length <= 1 && !plugin.meta.forceAccountBinding) return head; + if (accounts.length <= 1 && !plugin.meta.forceAccountBinding) { + return head; + } return `${head} · accounts ${configuredAccounts.length}/${enabledAccounts.length || 1}`; } diff --git a/src/commands/status-all/diagnosis.ts b/src/commands/status-all/diagnosis.ts index 1b0ac21981..6c34336ffc 100644 --- a/src/commands/status-all/diagnosis.ts +++ b/src/commands/status-all/diagnosis.ts @@ -214,12 +214,20 @@ export async function appendStatusAllDiagnosis(params: { } const healthErr = (() => { - if (!params.health || typeof params.health !== "object") return ""; + if (!params.health || typeof params.health !== "object") { + return ""; + } const record = params.health as Record; - if (!("error" in record)) return ""; + if (!("error" in record)) { + return ""; + } const value = record.error; - if (!value) return ""; - if (typeof value === "string") return value; + if (!value) { + return ""; + } + if (typeof value === "string") { + return value; + } try { return JSON.stringify(value, null, 2); } catch { diff --git a/src/commands/status-all/format.ts b/src/commands/status-all/format.ts index ad636d14ed..979fa4e6db 100644 --- a/src/commands/status-all/format.ts +++ b/src/commands/status-all/format.ts @@ -1,17 +1,29 @@ export const formatAge = (ms: number | null | undefined) => { - if (!ms || ms < 0) return "unknown"; + if (!ms || ms < 0) { + return "unknown"; + } const minutes = Math.round(ms / 60_000); - if (minutes < 1) return "just now"; - if (minutes < 60) return `${minutes}m ago`; + if (minutes < 1) { + return "just now"; + } + if (minutes < 60) { + return `${minutes}m ago`; + } const hours = Math.round(minutes / 60); - if (hours < 48) return `${hours}h ago`; + if (hours < 48) { + return `${hours}h ago`; + } const days = Math.round(hours / 24); return `${days}d ago`; }; export const formatDuration = (ms: number | null | undefined) => { - if (ms == null || !Number.isFinite(ms)) return "unknown"; - if (ms < 1000) return `${Math.round(ms)}ms`; + if (ms == null || !Number.isFinite(ms)) { + return "unknown"; + } + if (ms < 1000) { + return `${Math.round(ms)}ms`; + } return `${(ms / 1000).toFixed(1)}s`; }; @@ -23,14 +35,22 @@ export function formatGatewayAuthUsed( ): "token" | "password" | "token+password" | "none" { const hasToken = Boolean(auth?.token?.trim()); const hasPassword = Boolean(auth?.password?.trim()); - if (hasToken && hasPassword) return "token+password"; - if (hasToken) return "token"; - if (hasPassword) return "password"; + if (hasToken && hasPassword) { + return "token+password"; + } + if (hasToken) { + return "token"; + } + if (hasPassword) { + return "password"; + } return "none"; } export function redactSecrets(text: string): string { - if (!text) return text; + if (!text) { + return text; + } let out = text; out = out.replace( /(\b(?:access[_-]?token|refresh[_-]?token|token|password|secret|api[_-]?key)\b\s*[:=]\s*)("?)([^"\\s]+)("?)/gi, diff --git a/src/commands/status-all/gateway.ts b/src/commands/status-all/gateway.ts index a7dcc34ebb..6d764376a4 100644 --- a/src/commands/status-all/gateway.ts +++ b/src/commands/status-all/gateway.ts @@ -2,20 +2,26 @@ import fs from "node:fs/promises"; export async function readFileTailLines(filePath: string, maxLines: number): Promise { const raw = await fs.readFile(filePath, "utf8").catch(() => ""); - if (!raw.trim()) return []; + if (!raw.trim()) { + return []; + } const lines = raw.replace(/\r/g, "").split("\n"); const out = lines.slice(Math.max(0, lines.length - maxLines)); return out.map((line) => line.trimEnd()).filter((line) => line.trim().length > 0); } function countMatches(haystack: string, needle: string): number { - if (!haystack || !needle) return 0; + if (!haystack || !needle) { + return 0; + } return haystack.split(needle).length - 1; } function shorten(message: string, maxLen: number): string { const cleaned = message.replace(/\s+/g, " ").trim(); - if (cleaned.length <= maxLen) return cleaned; + if (cleaned.length <= maxLen) { + return cleaned; + } return `${cleaned.slice(0, Math.max(0, maxLen - 1))}…`; } @@ -34,7 +40,9 @@ function consumeJsonBlock( ): { json: string; endIndex: number } | null { const startLine = lines[startIndex] ?? ""; const braceAt = startLine.indexOf("{"); - if (braceAt < 0) return null; + if (braceAt < 0) { + return null; + } const parts: string[] = [startLine.slice(braceAt)]; let depth = countMatches(parts[0] ?? "", "{") - countMatches(parts[0] ?? "", "}"); @@ -66,7 +74,9 @@ export function summarizeLogTail(rawLines: string[], opts?: { maxLines?: number const addLine = (line: string) => { const trimmed = line.trimEnd(); - if (!trimmed) return; + if (!trimmed) { + return; + } out.push(trimmed); }; @@ -142,17 +152,23 @@ export function summarizeLogTail(rawLines: string[], opts?: { maxLines?: number } for (const g of groups.values()) { - if (g.count <= 1) continue; + if (g.count <= 1) { + continue; + } out[g.index] = `${g.base} ×${g.count}`; } const deduped: string[] = []; for (const line of out) { - if (deduped[deduped.length - 1] === line) continue; + if (deduped[deduped.length - 1] === line) { + continue; + } deduped.push(line); } - if (deduped.length <= maxLines) return deduped; + if (deduped.length <= maxLines) { + return deduped; + } const head = Math.min(6, Math.floor(maxLines / 3)); const tail = Math.max(1, maxLines - head - 1); @@ -170,10 +186,14 @@ export function pickGatewaySelfPresence(presence: unknown): { version?: string; platform?: string; } | null { - if (!Array.isArray(presence)) return null; + if (!Array.isArray(presence)) { + return null; + } const entries = presence as Array>; const self = entries.find((e) => e.mode === "gateway" && e.reason === "self") ?? null; - if (!self) return null; + if (!self) { + return null; + } return { host: typeof self.host === "string" ? self.host : undefined, ip: typeof self.ip === "string" ? self.ip : undefined, diff --git a/src/commands/status-all/report-lines.ts b/src/commands/status-all/report-lines.ts index ff90d6239d..84d2656dd2 100644 --- a/src/commands/status-all/report-lines.ts +++ b/src/commands/status-all/report-lines.ts @@ -86,14 +86,19 @@ export async function buildStatusAllReportLines(params: { for (const issue of params.channelIssues) { const key = issue.channel; const list = map.get(key); - if (list) list.push(issue); - else map.set(key, [issue]); + if (list) { + list.push(issue); + } else { + map.set(key, [issue]); + } } return map; })(); const channelRowsWithIssues = channelRows.map((row) => { const issues = channelIssuesByChannel.get(row.channelId) ?? []; - if (issues.length === 0) return row; + if (issues.length === 0) { + return row; + } const issue = issues[0]; const suffix = ` · ${warn(`gateway: ${String(issue.message).slice(0, 90)}`)}`; return { diff --git a/src/commands/status.command.ts b/src/commands/status.command.ts index d016e154d9..af0305d75f 100644 --- a/src/commands/status.command.ts +++ b/src/commands/status.command.ts @@ -174,7 +174,9 @@ export async function statusCommand( if (opts.verbose) { const details = buildGatewayConnectionDetails(); runtime.log(info("Gateway connection:")); - for (const line of details.message.split("\n")) runtime.log(` ${line}`); + for (const line of details.message.split("\n")) { + runtime.log(` ${line}`); + } runtime.log(""); } @@ -182,7 +184,9 @@ export async function statusCommand( const dashboard = (() => { const controlUiEnabled = cfg.gateway?.controlUi?.enabled ?? true; - if (!controlUiEnabled) return "disabled"; + if (!controlUiEnabled) { + return "disabled"; + } const links = resolveControlUiLinks({ port: resolveGatewayPort(cfg), bind: cfg.gateway?.bind, @@ -236,12 +240,16 @@ export async function statusCommand( getNodeDaemonStatusSummary(), ]); const daemonValue = (() => { - if (daemon.installed === false) return `${daemon.label} not installed`; + if (daemon.installed === false) { + return `${daemon.label} not installed`; + } const installedPrefix = daemon.installed === true ? "installed · " : ""; return `${daemon.label} ${installedPrefix}${daemon.loadedText}${daemon.runtimeShort ? ` · ${daemon.runtimeShort}` : ""}`; })(); const nodeDaemonValue = (() => { - if (nodeDaemon.installed === false) return `${nodeDaemon.label} not installed`; + if (nodeDaemon.installed === false) { + return `${nodeDaemon.label} not installed`; + } const installedPrefix = nodeDaemon.installed === true ? "installed · " : ""; return `${nodeDaemon.label} ${installedPrefix}${nodeDaemon.loadedText}${nodeDaemon.runtimeShort ? ` · ${nodeDaemon.runtimeShort}` : ""}`; })(); @@ -258,7 +266,9 @@ export async function statusCommand( const heartbeatValue = (() => { const parts = summary.heartbeat.agents .map((agent) => { - if (!agent.enabled || !agent.everyMs) return `disabled (${agent.agentId})`; + if (!agent.enabled || !agent.everyMs) { + return `disabled (${agent.agentId})`; + } const everyLabel = agent.every; return `${everyLabel} (${agent.agentId})`; }) @@ -283,8 +293,12 @@ export async function statusCommand( const parts: string[] = []; const dirtySuffix = memory.dirty ? ` · ${warn("dirty")}` : ""; parts.push(`${memory.files} files · ${memory.chunks} chunks${dirtySuffix}`); - if (memory.sources?.length) parts.push(`sources ${memory.sources.join(", ")}`); - if (memoryPlugin.slot) parts.push(`plugin ${memoryPlugin.slot}`); + if (memory.sources?.length) { + parts.push(`sources ${memory.sources.join(", ")}`); + } + if (memoryPlugin.slot) { + parts.push(`plugin ${memoryPlugin.slot}`); + } const colorByTone = (tone: Tone, text: string) => tone === "ok" ? ok(text) : tone === "warn" ? warn(text) : muted(text); const vector = memory.vector; @@ -395,8 +409,12 @@ export async function statusCommand( runtime.log(theme.muted("No critical or warn findings detected.")); } else { const severityLabel = (sev: "critical" | "warn" | "info") => { - if (sev === "critical") return theme.error("CRITICAL"); - if (sev === "warn") return theme.warn("WARN"); + if (sev === "critical") { + return theme.error("CRITICAL"); + } + if (sev === "warn") { + return theme.warn("WARN"); + } return theme.muted("INFO"); }; const sevRank = (sev: "critical" | "warn" | "info") => @@ -408,7 +426,9 @@ export async function statusCommand( for (const f of shown) { runtime.log(` ${severityLabel(f.severity)} ${f.title}`); runtime.log(` ${shortenText(f.detail.replaceAll("\n", " "), 160)}`); - if (f.remediation?.trim()) runtime.log(` ${theme.muted(`Fix: ${f.remediation.trim()}`)}`); + if (f.remediation?.trim()) { + runtime.log(` ${theme.muted(`Fix: ${f.remediation.trim()}`)}`); + } } if (sorted.length > shown.length) { runtime.log(theme.muted(`… +${sorted.length - shown.length} more`)); @@ -424,8 +444,11 @@ export async function statusCommand( for (const issue of channelIssues) { const key = issue.channel; const list = map.get(key); - if (list) list.push(issue); - else map.set(key, [issue]); + if (list) { + list.push(issue); + } else { + map.set(key, [issue]); + } } return map; })(); @@ -524,17 +547,31 @@ export async function statusCommand( for (const line of formatHealthChannelLines(health, { accountMode: "all" })) { const colon = line.indexOf(":"); - if (colon === -1) continue; + if (colon === -1) { + continue; + } const item = line.slice(0, colon).trim(); const detail = line.slice(colon + 1).trim(); const normalized = detail.toLowerCase(); const status = (() => { - if (normalized.startsWith("ok")) return ok("OK"); - if (normalized.startsWith("failed")) return warn("WARN"); - if (normalized.startsWith("not configured")) return muted("OFF"); - if (normalized.startsWith("configured")) return ok("OK"); - if (normalized.startsWith("linked")) return ok("LINKED"); - if (normalized.startsWith("not linked")) return warn("UNLINKED"); + if (normalized.startsWith("ok")) { + return ok("OK"); + } + if (normalized.startsWith("failed")) { + return warn("WARN"); + } + if (normalized.startsWith("not configured")) { + return muted("OFF"); + } + if (normalized.startsWith("configured")) { + return ok("OK"); + } + if (normalized.startsWith("linked")) { + return ok("LINKED"); + } + if (normalized.startsWith("not linked")) { + return warn("UNLINKED"); + } return warn("WARN"); })(); rows.push({ Item: item, Status: status, Detail: detail }); diff --git a/src/commands/status.format.ts b/src/commands/status.format.ts index c0040ec75b..7572ca18cb 100644 --- a/src/commands/status.format.ts +++ b/src/commands/status.format.ts @@ -4,25 +4,39 @@ export const formatKTokens = (value: number) => `${(value / 1000).toFixed(value >= 10_000 ? 0 : 1)}k`; export const formatAge = (ms: number | null | undefined) => { - if (!ms || ms < 0) return "unknown"; + if (!ms || ms < 0) { + return "unknown"; + } const minutes = Math.round(ms / 60_000); - if (minutes < 1) return "just now"; - if (minutes < 60) return `${minutes}m ago`; + if (minutes < 1) { + return "just now"; + } + if (minutes < 60) { + return `${minutes}m ago`; + } const hours = Math.round(minutes / 60); - if (hours < 48) return `${hours}h ago`; + if (hours < 48) { + return `${hours}h ago`; + } const days = Math.round(hours / 24); return `${days}d ago`; }; export const formatDuration = (ms: number | null | undefined) => { - if (ms == null || !Number.isFinite(ms)) return "unknown"; - if (ms < 1000) return `${Math.round(ms)}ms`; + if (ms == null || !Number.isFinite(ms)) { + return "unknown"; + } + if (ms < 1000) { + return `${Math.round(ms)}ms`; + } return `${(ms / 1000).toFixed(1)}s`; }; export const shortenText = (value: string, maxLen: number) => { const chars = Array.from(value); - if (chars.length <= maxLen) return value; + if (chars.length <= maxLen) { + return value; + } return `${chars.slice(0, Math.max(0, maxLen - 1)).join("")}…`; }; @@ -31,7 +45,9 @@ export const formatTokensCompact = ( ) => { const used = sess.totalTokens ?? 0; const ctx = sess.contextTokens; - if (!ctx) return `${formatKTokens(used)} used`; + if (!ctx) { + return `${formatKTokens(used)} used`; + } const pctLabel = sess.percentUsed != null ? `${sess.percentUsed}%` : "?%"; return `${formatKTokens(used)}/${formatKTokens(ctx)} (${pctLabel})`; }; @@ -43,16 +59,22 @@ export const formatDaemonRuntimeShort = (runtime?: { detail?: string; missingUnit?: boolean; }) => { - if (!runtime) return null; + if (!runtime) { + return null; + } const status = runtime.status ?? "unknown"; const details: string[] = []; - if (runtime.pid) details.push(`pid ${runtime.pid}`); + if (runtime.pid) { + details.push(`pid ${runtime.pid}`); + } if (runtime.state && runtime.state.toLowerCase() !== status) { details.push(`state ${runtime.state}`); } const detail = runtime.detail?.replace(/\s+/g, " ").trim() || ""; const noisyLaunchctlDetail = runtime.missingUnit === true && detail.toLowerCase().includes("could not find service"); - if (detail && !noisyLaunchctlDetail) details.push(detail); + if (detail && !noisyLaunchctlDetail) { + details.push(detail); + } return details.length > 0 ? `${status} (${details.join(", ")})` : status; }; diff --git a/src/commands/status.gateway-probe.ts b/src/commands/status.gateway-probe.ts index b670b3a114..c0c00465ea 100644 --- a/src/commands/status.gateway-probe.ts +++ b/src/commands/status.gateway-probe.ts @@ -32,10 +32,14 @@ export function pickGatewaySelfPresence(presence: unknown): { version?: string; platform?: string; } | null { - if (!Array.isArray(presence)) return null; + if (!Array.isArray(presence)) { + return null; + } const entries = presence as Array>; const self = entries.find((e) => e.mode === "gateway" && e.reason === "self") ?? null; - if (!self) return null; + if (!self) { + return null; + } return { host: typeof self.host === "string" ? self.host : undefined, ip: typeof self.ip === "string" ? self.ip : undefined, diff --git a/src/commands/status.link-channel.ts b/src/commands/status.link-channel.ts index dcbcc227c7..cea7b8feb9 100644 --- a/src/commands/status.link-channel.ts +++ b/src/commands/status.link-channel.ts @@ -44,7 +44,9 @@ export async function resolveLinkChannelContext( const summaryRecord = summary; const linked = summaryRecord && typeof summaryRecord.linked === "boolean" ? summaryRecord.linked : null; - if (linked === null) continue; + if (linked === null) { + continue; + } const authAgeMs = summaryRecord && typeof summaryRecord.authAgeMs === "number" ? summaryRecord.authAgeMs : null; return { linked, authAgeMs, account, accountId: defaultAccountId, plugin }; diff --git a/src/commands/status.scan.ts b/src/commands/status.scan.ts index 743c5cfe1e..1bc5bd0bcc 100644 --- a/src/commands/status.scan.ts +++ b/src/commands/status.scan.ts @@ -27,7 +27,9 @@ type MemoryPluginStatus = { function resolveMemoryPluginStatus(cfg: ReturnType): MemoryPluginStatus { const pluginsEnabled = cfg.plugins?.enabled !== false; - if (!pluginsEnabled) return { enabled: false, slot: null, reason: "plugins disabled" }; + if (!pluginsEnabled) { + return { enabled: false, slot: null, reason: "plugins disabled" }; + } const raw = typeof cfg.plugins?.slots?.memory === "string" ? cfg.plugins.slots.memory.trim() : ""; if (raw && raw.toLowerCase() === "none") { return { enabled: false, slot: null, reason: 'plugins.slots.memory="none"' }; @@ -148,12 +150,18 @@ export async function scanStatus( progress.setLabel("Checking memory…"); const memoryPlugin = resolveMemoryPluginStatus(cfg); const memory = await (async (): Promise => { - if (!memoryPlugin.enabled) return null; - if (memoryPlugin.slot !== "memory-core") return null; + if (!memoryPlugin.enabled) { + return null; + } + if (memoryPlugin.slot !== "memory-core") { + return null; + } const agentId = agentStatus.defaultId ?? "main"; const { MemoryIndexManager } = await import("../memory/manager.js"); const manager = await MemoryIndexManager.get({ cfg, agentId }).catch(() => null); - if (!manager) return null; + if (!manager) { + return null; + } try { await manager.probeVectorAvailability(); } catch {} diff --git a/src/commands/status.summary.ts b/src/commands/status.summary.ts index 96b12df7f4..ec326b32f5 100644 --- a/src/commands/status.summary.ts +++ b/src/commands/status.summary.ts @@ -17,8 +17,12 @@ import { resolveLinkChannelContext } from "./status.link-channel.js"; import type { HeartbeatStatus, SessionStatus, StatusSummary } from "./status.types.js"; const classifyKey = (key: string, entry?: SessionEntry): SessionStatus["kind"] => { - if (key === "global") return "global"; - if (key === "unknown") return "unknown"; + if (key === "global") { + return "global"; + } + if (key === "unknown") { + return "unknown"; + } if (entry?.chatType === "group" || entry?.chatType === "channel") { return "group"; } @@ -29,20 +33,36 @@ const classifyKey = (key: string, entry?: SessionEntry): SessionStatus["kind"] = }; const buildFlags = (entry?: SessionEntry): string[] => { - if (!entry) return []; + if (!entry) { + return []; + } const flags: string[] = []; const think = entry?.thinkingLevel; - if (typeof think === "string" && think.length > 0) flags.push(`think:${think}`); + if (typeof think === "string" && think.length > 0) { + flags.push(`think:${think}`); + } const verbose = entry?.verboseLevel; - if (typeof verbose === "string" && verbose.length > 0) flags.push(`verbose:${verbose}`); + if (typeof verbose === "string" && verbose.length > 0) { + flags.push(`verbose:${verbose}`); + } const reasoning = entry?.reasoningLevel; - if (typeof reasoning === "string" && reasoning.length > 0) flags.push(`reasoning:${reasoning}`); + if (typeof reasoning === "string" && reasoning.length > 0) { + flags.push(`reasoning:${reasoning}`); + } const elevated = entry?.elevatedLevel; - if (typeof elevated === "string" && elevated.length > 0) flags.push(`elevated:${elevated}`); - if (entry?.systemSent) flags.push("system"); - if (entry?.abortedLastRun) flags.push("aborted"); + if (typeof elevated === "string" && elevated.length > 0) { + flags.push(`elevated:${elevated}`); + } + if (entry?.systemSent) { + flags.push("system"); + } + if (entry?.abortedLastRun) { + flags.push("aborted"); + } const sessionId = entry?.sessionId as unknown; - if (typeof sessionId === "string" && sessionId.length > 0) flags.push(`id:${sessionId}`); + if (typeof sessionId === "string" && sessionId.length > 0) { + flags.push(`id:${sessionId}`); + } return flags; }; @@ -81,7 +101,9 @@ export async function getStatusSummary(): Promise { const storeCache = new Map>(); const loadStore = (storePath: string) => { const cached = storeCache.get(storePath); - if (cached) return cached; + if (cached) { + return cached; + } const store = loadSessionStore(storePath); storeCache.set(storePath, store); return store; diff --git a/src/commands/status.test.ts b/src/commands/status.test.ts index e88ab65373..7cc3f617f3 100644 --- a/src/commands/status.test.ts +++ b/src/commands/status.test.ts @@ -358,8 +358,11 @@ describe("statusCommand", () => { const logs = (runtime.log as vi.Mock).mock.calls.map((c) => String(c[0])); expect(logs.some((l) => l.includes("auth token"))).toBe(true); } finally { - if (prevToken === undefined) delete process.env.OPENCLAW_GATEWAY_TOKEN; - else process.env.OPENCLAW_GATEWAY_TOKEN = prevToken; + if (prevToken === undefined) { + delete process.env.OPENCLAW_GATEWAY_TOKEN; + } else { + process.env.OPENCLAW_GATEWAY_TOKEN = prevToken; + } } }); @@ -459,10 +462,14 @@ describe("statusCommand", () => { payload.sessions.recent.some((sess: { key?: string }) => sess.key === "agent:ops:main"), ).toBe(true); - if (originalAgents) mocks.listAgentsForGateway.mockImplementation(originalAgents); - if (originalResolveStorePath) + if (originalAgents) { + mocks.listAgentsForGateway.mockImplementation(originalAgents); + } + if (originalResolveStorePath) { mocks.resolveStorePath.mockImplementation(originalResolveStorePath); - if (originalLoadSessionStore) + } + if (originalLoadSessionStore) { mocks.loadSessionStore.mockImplementation(originalLoadSessionStore); + } }); }); diff --git a/src/commands/status.update.ts b/src/commands/status.update.ts index 7cdfc6937b..6010f2fe6b 100644 --- a/src/commands/status.update.ts +++ b/src/commands/status.update.ts @@ -54,7 +54,9 @@ export function resolveUpdateAvailability(update: UpdateCheckResult): UpdateAvai export function formatUpdateAvailableHint(update: UpdateCheckResult): string | null { const availability = resolveUpdateAvailability(update); - if (!availability.available) return null; + if (!availability.available) { + return null; + } const details: string[] = []; if (availability.hasGitUpdate && availability.gitBehind != null) { @@ -72,8 +74,12 @@ export function formatUpdateOneLiner(update: UpdateCheckResult): string { if (update.installKind === "git" && update.git) { const branch = update.git.branch ? `git ${update.git.branch}` : "git"; parts.push(branch); - if (update.git.upstream) parts.push(`↔ ${update.git.upstream}`); - if (update.git.dirty === true) parts.push("dirty"); + if (update.git.upstream) { + parts.push(`↔ ${update.git.upstream}`); + } + if (update.git.dirty === true) { + parts.push("dirty"); + } if (update.git.behind != null && update.git.ahead != null) { if (update.git.behind === 0 && update.git.ahead === 0) { parts.push("up to date"); @@ -85,13 +91,19 @@ export function formatUpdateOneLiner(update: UpdateCheckResult): string { parts.push(`diverged (ahead ${update.git.ahead}, behind ${update.git.behind})`); } } - if (update.git.fetchOk === false) parts.push("fetch failed"); + if (update.git.fetchOk === false) { + parts.push("fetch failed"); + } if (update.registry?.latestVersion) { const cmp = compareSemverStrings(VERSION, update.registry.latestVersion); - if (cmp === 0) parts.push(`npm latest ${update.registry.latestVersion}`); - else if (cmp != null && cmp < 0) parts.push(`npm update ${update.registry.latestVersion}`); - else parts.push(`npm latest ${update.registry.latestVersion} (local newer)`); + if (cmp === 0) { + parts.push(`npm latest ${update.registry.latestVersion}`); + } else if (cmp != null && cmp < 0) { + parts.push(`npm update ${update.registry.latestVersion}`); + } else { + parts.push(`npm latest ${update.registry.latestVersion} (local newer)`); + } } else if (update.registry?.error) { parts.push("npm latest unknown"); } @@ -99,8 +111,9 @@ export function formatUpdateOneLiner(update: UpdateCheckResult): string { parts.push(update.packageManager !== "unknown" ? update.packageManager : "pkg"); if (update.registry?.latestVersion) { const cmp = compareSemverStrings(VERSION, update.registry.latestVersion); - if (cmp === 0) parts.push(`npm latest ${update.registry.latestVersion}`); - else if (cmp != null && cmp < 0) { + if (cmp === 0) { + parts.push(`npm latest ${update.registry.latestVersion}`); + } else if (cmp != null && cmp < 0) { parts.push(`npm update ${update.registry.latestVersion}`); } else { parts.push(`npm latest ${update.registry.latestVersion} (local newer)`); @@ -111,9 +124,15 @@ export function formatUpdateOneLiner(update: UpdateCheckResult): string { } if (update.deps) { - if (update.deps.status === "ok") parts.push("deps ok"); - if (update.deps.status === "missing") parts.push("deps missing"); - if (update.deps.status === "stale") parts.push("deps stale"); + if (update.deps.status === "ok") { + parts.push("deps ok"); + } + if (update.deps.status === "missing") { + parts.push("deps missing"); + } + if (update.deps.status === "stale") { + parts.push("deps stale"); + } } return `Update: ${parts.join(" · ")}`; } diff --git a/src/commands/systemd-linger.ts b/src/commands/systemd-linger.ts index fe840493cf..f810f5c250 100644 --- a/src/commands/systemd-linger.ts +++ b/src/commands/systemd-linger.ts @@ -20,8 +20,12 @@ export async function ensureSystemdUserLingerInteractive(params: { prompt?: boolean; requireConfirm?: boolean; }): Promise { - if (process.platform !== "linux") return; - if (params.prompt === false) return; + if (process.platform !== "linux") { + return; + } + if (params.prompt === false) { + return; + } const env = params.env ?? process.env; const prompter = params.prompter ?? { note }; const title = params.title ?? "Systemd"; @@ -37,7 +41,9 @@ export async function ensureSystemdUserLingerInteractive(params: { ); return; } - if (status.linger === "yes") return; + if (status.linger === "yes") { + return; + } const reason = params.reason ?? @@ -87,11 +93,17 @@ export async function ensureSystemdUserLingerNonInteractive(params: { runtime: RuntimeEnv; env?: NodeJS.ProcessEnv; }): Promise { - if (process.platform !== "linux") return; + if (process.platform !== "linux") { + return; + } const env = params.env ?? process.env; - if (!(await isSystemdUserServiceAvailable())) return; + if (!(await isSystemdUserServiceAvailable())) { + return; + } const status = await readSystemdUserLingerStatus(env); - if (!status || status.linger === "yes") return; + if (!status || status.linger === "yes") { + return; + } const result = await enableSystemdUserLinger({ env, diff --git a/src/commands/uninstall.ts b/src/commands/uninstall.ts index dfeaeb24e5..1843edc6e1 100644 --- a/src/commands/uninstall.ts +++ b/src/commands/uninstall.ts @@ -42,10 +42,18 @@ function buildScopeSelection(opts: UninstallOptions): { } { const hadExplicit = Boolean(opts.all || opts.service || opts.state || opts.workspace || opts.app); const scopes = new Set(); - if (opts.all || opts.service) scopes.add("service"); - if (opts.all || opts.state) scopes.add("state"); - if (opts.all || opts.workspace) scopes.add("workspace"); - if (opts.all || opts.app) scopes.add("app"); + if (opts.all || opts.service) { + scopes.add("service"); + } + if (opts.all || opts.state) { + scopes.add("state"); + } + if (opts.all || opts.workspace) { + scopes.add("workspace"); + } + if (opts.all || opts.app) { + scopes.add("app"); + } return { scopes, hadExplicit }; } @@ -81,7 +89,9 @@ async function stopAndUninstallService(runtime: RuntimeEnv): Promise { } async function removeMacApp(runtime: RuntimeEnv, dryRun?: boolean) { - if (process.platform !== "darwin") return; + if (process.platform !== "darwin") { + return; + } await removePath("/Applications/OpenClaw.app", runtime, { dryRun, label: "/Applications/OpenClaw.app", @@ -126,7 +136,9 @@ export async function uninstallCommand(runtime: RuntimeEnv, opts: UninstallOptio runtime.exit(0); return; } - for (const value of selection) scopes.add(value); + for (const value of selection) { + scopes.add(value); + } } if (scopes.size === 0) { diff --git a/src/config/agent-dirs.ts b/src/config/agent-dirs.ts index 79ca7f7438..1860230587 100644 --- a/src/config/agent-dirs.ts +++ b/src/config/agent-dirs.ts @@ -37,7 +37,9 @@ function collectReferencedAgentIds(cfg: OpenClawConfig): string[] { ids.add(normalizeAgentId(defaultAgentId)); for (const entry of agents) { - if (entry?.id) ids.add(normalizeAgentId(entry.id)); + if (entry?.id) { + ids.add(normalizeAgentId(entry.id)); + } } const bindings = cfg.bindings; @@ -63,7 +65,9 @@ function resolveEffectiveAgentDir( ? cfg.agents?.list.find((agent) => normalizeAgentId(agent.id) === id)?.agentDir : undefined; const trimmed = configured?.trim(); - if (trimmed) return resolveUserPath(trimmed); + if (trimmed) { + return resolveUserPath(trimmed); + } const root = resolveStateDir(deps?.env ?? process.env, deps?.homedir ?? os.homedir); return path.join(root, "agents", id, "agent"); } diff --git a/src/config/channel-capabilities.ts b/src/config/channel-capabilities.ts index b4acfc55a0..7e5bd75461 100644 --- a/src/config/channel-capabilities.ts +++ b/src/config/channel-capabilities.ts @@ -11,7 +11,9 @@ const isStringArray = (value: unknown): value is string[] => function normalizeCapabilities(capabilities: CapabilitiesConfig | undefined): string[] | undefined { // Handle object-format capabilities (e.g., { inlineButtons: "dm" }) gracefully. // Channel-specific handlers (like resolveTelegramInlineButtonsScope) process these separately. - if (!isStringArray(capabilities)) return undefined; + if (!isStringArray(capabilities)) { + return undefined; + } const normalized = capabilities.map((entry) => entry.trim()).filter(Boolean); return normalized.length > 0 ? normalized : undefined; } @@ -23,7 +25,9 @@ function resolveAccountCapabilities(params: { accountId?: string | null; }): string[] | undefined { const cfg = params.cfg; - if (!cfg) return undefined; + if (!cfg) { + return undefined; + } const normalizedAccountId = normalizeAccountId(params.accountId); const accounts = cfg.accounts; @@ -51,7 +55,9 @@ export function resolveChannelCapabilities(params: { }): string[] | undefined { const cfg = params.cfg; const channel = normalizeChannelId(params.channel); - if (!cfg || !channel) return undefined; + if (!cfg || !channel) { + return undefined; + } const channelsConfig = cfg.channels as Record | undefined; const channelConfig = (channelsConfig?.[channel] ?? (cfg as Record)[channel]) as diff --git a/src/config/commands.ts b/src/config/commands.ts index 4e3074cf2a..1271a09e41 100644 --- a/src/config/commands.ts +++ b/src/config/commands.ts @@ -4,9 +4,15 @@ import type { NativeCommandsSetting } from "./types.js"; function resolveAutoDefault(providerId?: ChannelId): boolean { const id = normalizeChannelId(providerId); - if (!id) return false; - if (id === "discord" || id === "telegram") return true; - if (id === "slack") return false; + if (!id) { + return false; + } + if (id === "discord" || id === "telegram") { + return true; + } + if (id === "slack") { + return false; + } return false; } @@ -17,8 +23,12 @@ export function resolveNativeSkillsEnabled(params: { }): boolean { const { providerId, providerSetting, globalSetting } = params; const setting = providerSetting === undefined ? globalSetting : providerSetting; - if (setting === true) return true; - if (setting === false) return false; + if (setting === true) { + return true; + } + if (setting === false) { + return false; + } return resolveAutoDefault(providerId); } @@ -29,8 +39,12 @@ export function resolveNativeCommandsEnabled(params: { }): boolean { const { providerId, providerSetting, globalSetting } = params; const setting = providerSetting === undefined ? globalSetting : providerSetting; - if (setting === true) return true; - if (setting === false) return false; + if (setting === true) { + return true; + } + if (setting === false) { + return false; + } // auto or undefined -> heuristic return resolveAutoDefault(providerId); } @@ -40,7 +54,11 @@ export function isNativeCommandsExplicitlyDisabled(params: { globalSetting?: NativeCommandsSetting; }): boolean { const { providerSetting, globalSetting } = params; - if (providerSetting === false) return true; - if (providerSetting === undefined) return globalSetting === false; + if (providerSetting === false) { + return true; + } + if (providerSetting === undefined) { + return globalSetting === false; + } return false; } diff --git a/src/config/config-paths.test.ts b/src/config/config-paths.test.ts index fafa06d043..61d613f256 100644 --- a/src/config/config-paths.test.ts +++ b/src/config/config-paths.test.ts @@ -21,7 +21,9 @@ describe("config paths", () => { it("sets, gets, and unsets nested values", () => { const root: Record = {}; const parsed = parseConfigPath("foo.bar"); - if (!parsed.ok || !parsed.path) throw new Error("path parse failed"); + if (!parsed.ok || !parsed.path) { + throw new Error("path parse failed"); + } setConfigValueAtPath(root, parsed.path, 123); expect(getConfigValueAtPath(root, parsed.path)).toBe(123); expect(unsetConfigValueAtPath(root, parsed.path)).toBe(true); diff --git a/src/config/config-paths.ts b/src/config/config-paths.ts index 07effa9c34..24e8095dc0 100644 --- a/src/config/config-paths.ts +++ b/src/config/config-paths.ts @@ -46,12 +46,16 @@ export function unsetConfigValueAtPath(root: PathNode, path: string[]): boolean for (let idx = 0; idx < path.length - 1; idx += 1) { const key = path[idx]; const next = cursor[key]; - if (!isPlainObject(next)) return false; + if (!isPlainObject(next)) { + return false; + } stack.push({ node: cursor, key }); cursor = next; } const leafKey = path[path.length - 1]; - if (!(leafKey in cursor)) return false; + if (!(leafKey in cursor)) { + return false; + } delete cursor[leafKey]; for (let idx = stack.length - 1; idx >= 0; idx -= 1) { const { node, key } = stack[idx]; @@ -68,7 +72,9 @@ export function unsetConfigValueAtPath(root: PathNode, path: string[]): boolean export function getConfigValueAtPath(root: PathNode, path: string[]): unknown { let cursor: unknown = root; for (const key of path) { - if (!isPlainObject(cursor)) return undefined; + if (!isPlainObject(cursor)) { + return undefined; + } cursor = cursor[key]; } return cursor; diff --git a/src/config/config.skills-entries-config.test.ts b/src/config/config.skills-entries-config.test.ts index 14d6a9630f..3295281e6d 100644 --- a/src/config/config.skills-entries-config.test.ts +++ b/src/config/config.skills-entries-config.test.ts @@ -33,7 +33,9 @@ describe("skills entries config schema", () => { }); expect(res.success).toBe(false); - if (res.success) return; + if (res.success) { + return; + } expect( res.error.issues.some( diff --git a/src/config/config.telegram-custom-commands.test.ts b/src/config/config.telegram-custom-commands.test.ts index d83457d13d..cb21cd86dc 100644 --- a/src/config/config.telegram-custom-commands.test.ts +++ b/src/config/config.telegram-custom-commands.test.ts @@ -13,7 +13,9 @@ describe("telegram custom commands schema", () => { }); expect(res.success).toBe(true); - if (!res.success) return; + if (!res.success) { + return; + } expect(res.data.channels?.telegram?.customCommands).toEqual([ { command: "backup", description: "Git backup" }, @@ -30,7 +32,9 @@ describe("telegram custom commands schema", () => { }); expect(res.success).toBe(false); - if (res.success) return; + if (res.success) { + return; + } expect( res.error.issues.some( diff --git a/src/config/defaults.ts b/src/config/defaults.ts index d68be922fb..686b4c09c8 100644 --- a/src/config/defaults.ts +++ b/src/config/defaults.ts @@ -62,27 +62,45 @@ function resolveAnthropicDefaultAuthMode(cfg: OpenClawConfig): AnthropicAuthDefa const order = cfg.auth?.order?.anthropic ?? []; for (const profileId of order) { const entry = profiles[profileId]; - if (!entry || entry.provider !== "anthropic") continue; - if (entry.mode === "api_key") return "api_key"; - if (entry.mode === "oauth" || entry.mode === "token") return "oauth"; + if (!entry || entry.provider !== "anthropic") { + continue; + } + if (entry.mode === "api_key") { + return "api_key"; + } + if (entry.mode === "oauth" || entry.mode === "token") { + return "oauth"; + } } const hasApiKey = anthropicProfiles.some(([, profile]) => profile?.mode === "api_key"); const hasOauth = anthropicProfiles.some( ([, profile]) => profile?.mode === "oauth" || profile?.mode === "token", ); - if (hasApiKey && !hasOauth) return "api_key"; - if (hasOauth && !hasApiKey) return "oauth"; + if (hasApiKey && !hasOauth) { + return "api_key"; + } + if (hasOauth && !hasApiKey) { + return "oauth"; + } - if (process.env.ANTHROPIC_OAUTH_TOKEN?.trim()) return "oauth"; - if (process.env.ANTHROPIC_API_KEY?.trim()) return "api_key"; + if (process.env.ANTHROPIC_OAUTH_TOKEN?.trim()) { + return "oauth"; + } + if (process.env.ANTHROPIC_API_KEY?.trim()) { + return "api_key"; + } return null; } function resolvePrimaryModelRef(raw?: string): string | null { - if (!raw || typeof raw !== "string") return null; + if (!raw || typeof raw !== "string") { + return null; + } const trimmed = raw.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const aliasKey = trimmed.toLowerCase(); return DEFAULT_MODEL_ALIASES[aliasKey] ?? trimmed; } @@ -95,7 +113,9 @@ export type SessionDefaultsOptions = { export function applyMessageDefaults(cfg: OpenClawConfig): OpenClawConfig { const messages = cfg.messages; const hasAckScope = messages?.ackReactionScope !== undefined; - if (hasAckScope) return cfg; + if (hasAckScope) { + return cfg; + } const nextMessages = messages ? { ...messages } : {}; nextMessages.ackReactionScope = "group-mentions"; @@ -110,7 +130,9 @@ export function applySessionDefaults( options: SessionDefaultsOptions = {}, ): OpenClawConfig { const session = cfg.session; - if (!session || session.mainKey === undefined) return cfg; + if (!session || session.mainKey === undefined) { + return cfg; + } const trimmed = session.mainKey.trim(); const warn = options.warn ?? console.warn; @@ -131,9 +153,13 @@ export function applySessionDefaults( export function applyTalkApiKey(config: OpenClawConfig): OpenClawConfig { const resolved = resolveTalkApiKey(); - if (!resolved) return config; + if (!resolved) { + return config; + } const existing = config.talk?.apiKey?.trim(); - if (existing) return config; + if (existing) { + return config; + } return { ...config, talk: { @@ -152,17 +178,23 @@ export function applyModelDefaults(cfg: OpenClawConfig): OpenClawConfig { const nextProviders = { ...providerConfig }; for (const [providerId, provider] of Object.entries(providerConfig)) { const models = provider.models; - if (!Array.isArray(models) || models.length === 0) continue; + if (!Array.isArray(models) || models.length === 0) { + continue; + } let providerMutated = false; const nextModels = models.map((model) => { const raw = model as ModelDefinitionLike; let modelMutated = false; const reasoning = typeof raw.reasoning === "boolean" ? raw.reasoning : false; - if (raw.reasoning !== reasoning) modelMutated = true; + if (raw.reasoning !== reasoning) { + modelMutated = true; + } const input = raw.input ?? [...DEFAULT_MODEL_INPUT]; - if (raw.input === undefined) modelMutated = true; + if (raw.input === undefined) { + modelMutated = true; + } const cost = resolveModelCost(raw.cost); const costMutated = @@ -171,18 +203,26 @@ export function applyModelDefaults(cfg: OpenClawConfig): OpenClawConfig { raw.cost.output !== cost.output || raw.cost.cacheRead !== cost.cacheRead || raw.cost.cacheWrite !== cost.cacheWrite; - if (costMutated) modelMutated = true; + if (costMutated) { + modelMutated = true; + } const contextWindow = isPositiveNumber(raw.contextWindow) ? raw.contextWindow : DEFAULT_CONTEXT_TOKENS; - if (raw.contextWindow !== contextWindow) modelMutated = true; + if (raw.contextWindow !== contextWindow) { + modelMutated = true; + } const defaultMaxTokens = Math.min(DEFAULT_MODEL_MAX_TOKENS, contextWindow); const maxTokens = isPositiveNumber(raw.maxTokens) ? raw.maxTokens : defaultMaxTokens; - if (raw.maxTokens !== maxTokens) modelMutated = true; + if (raw.maxTokens !== maxTokens) { + modelMutated = true; + } - if (!modelMutated) return model; + if (!modelMutated) { + return model; + } providerMutated = true; return { ...raw, @@ -194,7 +234,9 @@ export function applyModelDefaults(cfg: OpenClawConfig): OpenClawConfig { } as ModelDefinitionConfig; }); - if (!providerMutated) continue; + if (!providerMutated) { + continue; + } nextProviders[providerId] = { ...provider, models: nextModels }; mutated = true; } @@ -211,9 +253,13 @@ export function applyModelDefaults(cfg: OpenClawConfig): OpenClawConfig { } const existingAgent = nextCfg.agents?.defaults; - if (!existingAgent) return mutated ? nextCfg : cfg; + if (!existingAgent) { + return mutated ? nextCfg : cfg; + } const existingModels = existingAgent.models ?? {}; - if (Object.keys(existingModels).length === 0) return mutated ? nextCfg : cfg; + if (Object.keys(existingModels).length === 0) { + return mutated ? nextCfg : cfg; + } const nextModels: Record = { ...existingModels, @@ -221,13 +267,19 @@ export function applyModelDefaults(cfg: OpenClawConfig): OpenClawConfig { for (const [alias, target] of Object.entries(DEFAULT_MODEL_ALIASES)) { const entry = nextModels[target]; - if (!entry) continue; - if (entry.alias !== undefined) continue; + if (!entry) { + continue; + } + if (entry.alias !== undefined) { + continue; + } nextModels[target] = { ...entry, alias }; mutated = true; } - if (!mutated) return cfg; + if (!mutated) { + return cfg; + } return { ...nextCfg, @@ -246,7 +298,9 @@ export function applyAgentDefaults(cfg: OpenClawConfig): OpenClawConfig { const hasSubMax = typeof defaults?.subagents?.maxConcurrent === "number" && Number.isFinite(defaults.subagents.maxConcurrent); - if (hasMax && hasSubMax) return cfg; + if (hasMax && hasSubMax) { + return cfg; + } let mutated = false; const nextDefaults = defaults ? { ...defaults } : {}; @@ -261,7 +315,9 @@ export function applyAgentDefaults(cfg: OpenClawConfig): OpenClawConfig { mutated = true; } - if (!mutated) return cfg; + if (!mutated) { + return cfg; + } return { ...cfg, @@ -277,8 +333,12 @@ export function applyAgentDefaults(cfg: OpenClawConfig): OpenClawConfig { export function applyLoggingDefaults(cfg: OpenClawConfig): OpenClawConfig { const logging = cfg.logging; - if (!logging) return cfg; - if (logging.redactSensitive) return cfg; + if (!logging) { + return cfg; + } + if (logging.redactSensitive) { + return cfg; + } return { ...cfg, logging: { @@ -290,10 +350,14 @@ export function applyLoggingDefaults(cfg: OpenClawConfig): OpenClawConfig { export function applyContextPruningDefaults(cfg: OpenClawConfig): OpenClawConfig { const defaults = cfg.agents?.defaults; - if (!defaults) return cfg; + if (!defaults) { + return cfg; + } const authMode = resolveAnthropicDefaultAuthMode(cfg); - if (!authMode) return cfg; + if (!authMode) { + return cfg; + } let mutated = false; const nextDefaults = { ...defaults }; @@ -323,10 +387,14 @@ export function applyContextPruningDefaults(cfg: OpenClawConfig): OpenClawConfig for (const [key, entry] of Object.entries(nextModels)) { const parsed = parseModelRef(key, "anthropic"); - if (!parsed || parsed.provider !== "anthropic") continue; + if (!parsed || parsed.provider !== "anthropic") { + continue; + } const current = entry ?? {}; const params = (current as { params?: Record }).params ?? {}; - if (typeof params.cacheControlTtl === "string") continue; + if (typeof params.cacheControlTtl === "string") { + continue; + } nextModels[key] = { ...(current as Record), params: { ...params, cacheControlTtl: "1h" }, @@ -358,7 +426,9 @@ export function applyContextPruningDefaults(cfg: OpenClawConfig): OpenClawConfig } } - if (!mutated) return cfg; + if (!mutated) { + return cfg; + } return { ...cfg, @@ -371,9 +441,13 @@ export function applyContextPruningDefaults(cfg: OpenClawConfig): OpenClawConfig export function applyCompactionDefaults(cfg: OpenClawConfig): OpenClawConfig { const defaults = cfg.agents?.defaults; - if (!defaults) return cfg; + if (!defaults) { + return cfg; + } const compaction = defaults?.compaction; - if (compaction?.mode) return cfg; + if (compaction?.mode) { + return cfg; + } return { ...cfg, diff --git a/src/config/env-vars.ts b/src/config/env-vars.ts index 34b916e9fc..54f9fceee9 100644 --- a/src/config/env-vars.ts +++ b/src/config/env-vars.ts @@ -2,20 +2,28 @@ import type { OpenClawConfig } from "./types.js"; export function collectConfigEnvVars(cfg?: OpenClawConfig): Record { const envConfig = cfg?.env; - if (!envConfig) return {}; + if (!envConfig) { + return {}; + } const entries: Record = {}; if (envConfig.vars) { for (const [key, value] of Object.entries(envConfig.vars)) { - if (!value) continue; + if (!value) { + continue; + } entries[key] = value; } } for (const [key, value] of Object.entries(envConfig)) { - if (key === "shellEnv" || key === "vars") continue; - if (typeof value !== "string" || !value.trim()) continue; + if (key === "shellEnv" || key === "vars") { + continue; + } + if (typeof value !== "string" || !value.trim()) { + continue; + } entries[key] = value; } diff --git a/src/config/group-policy.ts b/src/config/group-policy.ts index 9550c9d14c..7472d39081 100644 --- a/src/config/group-policy.ts +++ b/src/config/group-policy.ts @@ -29,7 +29,9 @@ export type GroupToolPolicySender = { function normalizeSenderKey(value: string): string { const trimmed = value.trim(); - if (!trimmed) return ""; + if (!trimmed) { + return ""; + } const withoutAt = trimmed.startsWith("@") ? trimmed.slice(1) : trimmed; return withoutAt.toLowerCase(); } @@ -40,16 +42,24 @@ export function resolveToolsBySender( } & GroupToolPolicySender, ): GroupToolPolicyConfig | undefined { const toolsBySender = params.toolsBySender; - if (!toolsBySender) return undefined; + if (!toolsBySender) { + return undefined; + } const entries = Object.entries(toolsBySender); - if (entries.length === 0) return undefined; + if (entries.length === 0) { + return undefined; + } const normalized = new Map(); let wildcard: GroupToolPolicyConfig | undefined; for (const [rawKey, policy] of entries) { - if (!policy) continue; + if (!policy) { + continue; + } const key = normalizeSenderKey(rawKey); - if (!key) continue; + if (!key) { + continue; + } if (key === "*") { wildcard = policy; continue; @@ -62,7 +72,9 @@ export function resolveToolsBySender( const candidates: string[] = []; const pushCandidate = (value?: string | null) => { const trimmed = value?.trim(); - if (!trimmed) return; + if (!trimmed) { + return; + } candidates.push(trimmed); }; pushCandidate(params.senderId); @@ -72,9 +84,13 @@ export function resolveToolsBySender( for (const candidate of candidates) { const key = normalizeSenderKey(candidate); - if (!key) continue; + if (!key) { + continue; + } const match = normalized.get(key); - if (match) return match; + if (match) { + return match; + } } return wildcard; } @@ -91,7 +107,9 @@ function resolveChannelGroups( groups?: ChannelGroups; } | undefined; - if (!channelConfig) return undefined; + if (!channelConfig) { + return undefined; + } const accountGroups = channelConfig.accounts?.[normalizedAccountId]?.groups ?? channelConfig.accounts?.[ @@ -147,7 +165,9 @@ export function resolveChannelGroupRequireMention(params: { if (overrideOrder === "before-config" && typeof requireMentionOverride === "boolean") { return requireMentionOverride; } - if (typeof configMention === "boolean") return configMention; + if (typeof configMention === "boolean") { + return configMention; + } if (overrideOrder !== "before-config" && typeof requireMentionOverride === "boolean") { return requireMentionOverride; } @@ -170,8 +190,12 @@ export function resolveChannelGroupToolsPolicy( senderUsername: params.senderUsername, senderE164: params.senderE164, }); - if (groupSenderPolicy) return groupSenderPolicy; - if (groupConfig?.tools) return groupConfig.tools; + if (groupSenderPolicy) { + return groupSenderPolicy; + } + if (groupConfig?.tools) { + return groupConfig.tools; + } const defaultSenderPolicy = resolveToolsBySender({ toolsBySender: defaultConfig?.toolsBySender, senderId: params.senderId, @@ -179,7 +203,11 @@ export function resolveChannelGroupToolsPolicy( senderUsername: params.senderUsername, senderE164: params.senderE164, }); - if (defaultSenderPolicy) return defaultSenderPolicy; - if (defaultConfig?.tools) return defaultConfig.tools; + if (defaultSenderPolicy) { + return defaultSenderPolicy; + } + if (defaultConfig?.tools) { + return defaultConfig.tools; + } return undefined; } diff --git a/src/config/io.ts b/src/config/io.ts index 372dc066d2..bb9956a0ab 100644 --- a/src/config/io.ts +++ b/src/config/io.ts @@ -75,9 +75,13 @@ export function resolveConfigSnapshotHash(snapshot: { }): string | null { if (typeof snapshot.hash === "string") { const trimmed = snapshot.hash.trim(); - if (trimmed) return trimmed; + if (trimmed) { + return trimmed; + } + } + if (typeof snapshot.raw !== "string") { + return null; } - if (typeof snapshot.raw !== "string") return null; return hashConfigRaw(snapshot.raw); } @@ -89,7 +93,9 @@ function coerceConfig(value: unknown): OpenClawConfig { } async function rotateConfigBackups(configPath: string, ioFs: typeof fs.promises): Promise { - if (CONFIG_BACKUP_COUNT <= 1) return; + if (CONFIG_BACKUP_COUNT <= 1) { + return; + } const backupBase = `${configPath}.bak`; const maxIndex = CONFIG_BACKUP_COUNT - 1; await ioFs.unlink(`${backupBase}.${maxIndex}`).catch(() => { @@ -115,9 +121,13 @@ export type ConfigIoDeps = { }; function warnOnConfigMiskeys(raw: unknown, logger: Pick): void { - if (!raw || typeof raw !== "object") return; + if (!raw || typeof raw !== "object") { + return; + } const gateway = (raw as Record).gateway; - if (!gateway || typeof gateway !== "object") return; + if (!gateway || typeof gateway !== "object") { + return; + } if ("token" in (gateway as Record)) { logger.warn( 'Config uses "gateway.token". This key is ignored; use "gateway.auth.token" instead.', @@ -139,9 +149,13 @@ function stampConfigVersion(cfg: OpenClawConfig): OpenClawConfig { function warnIfConfigFromFuture(cfg: OpenClawConfig, logger: Pick): void { const touched = cfg.meta?.lastTouchedVersion; - if (!touched) return; + if (!touched) { + return; + } const cmp = compareOpenClawVersions(VERSION, touched); - if (cmp === null) return; + if (cmp === null) { + return; + } if (cmp < 0) { logger.warn( `Config was last written by a newer OpenClaw (${touched}); current version is ${VERSION}.`, @@ -152,13 +166,17 @@ function warnIfConfigFromFuture(cfg: OpenClawConfig, logger: Pick): string { - if (deps.configPath) return deps.configPath; + if (deps.configPath) { + return deps.configPath; + } return resolveConfigPath(deps.env, resolveStateDir(deps.env, deps.homedir)); } @@ -226,7 +244,9 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) { const resolvedConfig = substituted; warnOnConfigMiskeys(resolvedConfig, deps.logger); - if (typeof resolvedConfig !== "object" || resolvedConfig === null) return {}; + if (typeof resolvedConfig !== "object" || resolvedConfig === null) { + return {}; + } const preValidationDuplicates = findDuplicateAgentDirs(resolvedConfig as OpenClawConfig, { env: deps.env, homedir: deps.homedir, @@ -538,15 +558,23 @@ let configCache: { function resolveConfigCacheMs(env: NodeJS.ProcessEnv): number { const raw = env.OPENCLAW_CONFIG_CACHE_MS?.trim(); - if (raw === "" || raw === "0") return 0; - if (!raw) return DEFAULT_CONFIG_CACHE_MS; + if (raw === "" || raw === "0") { + return 0; + } + if (!raw) { + return DEFAULT_CONFIG_CACHE_MS; + } const parsed = Number.parseInt(raw, 10); - if (!Number.isFinite(parsed)) return DEFAULT_CONFIG_CACHE_MS; + if (!Number.isFinite(parsed)) { + return DEFAULT_CONFIG_CACHE_MS; + } return Math.max(0, parsed); } function shouldUseConfigCache(env: NodeJS.ProcessEnv): boolean { - if (env.OPENCLAW_DISABLE_CONFIG_CACHE?.trim()) return false; + if (env.OPENCLAW_DISABLE_CONFIG_CACHE?.trim()) { + return false; + } return resolveConfigCacheMs(env) > 0; } diff --git a/src/config/legacy-migrate.ts b/src/config/legacy-migrate.ts index 8385416b77..fedb59c2d6 100644 --- a/src/config/legacy-migrate.ts +++ b/src/config/legacy-migrate.ts @@ -7,7 +7,9 @@ export function migrateLegacyConfig(raw: unknown): { changes: string[]; } { const { next, changes } = applyLegacyMigrations(raw); - if (!next) return { config: null, changes: [] }; + if (!next) { + return { config: null, changes: [] }; + } const validated = validateConfigObjectWithPlugins(next); if (!validated.ok) { changes.push("Migration applied, but config still invalid; fix remaining issues manually."); diff --git a/src/config/legacy.migrations.part-1.ts b/src/config/legacy.migrations.part-1.ts index f537c3ce8b..036a0c5a22 100644 --- a/src/config/legacy.migrations.part-1.ts +++ b/src/config/legacy.migrations.part-1.ts @@ -12,16 +12,26 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_1: LegacyConfigMigration[] = [ describe: "Move bindings[].match.provider to bindings[].match.channel", apply: (raw, changes) => { const bindings = Array.isArray(raw.bindings) ? raw.bindings : null; - if (!bindings) return; + if (!bindings) { + return; + } let touched = false; for (const entry of bindings) { - if (!isRecord(entry)) continue; + if (!isRecord(entry)) { + continue; + } const match = getRecord(entry.match); - if (!match) continue; - if (typeof match.channel === "string" && match.channel.trim()) continue; + if (!match) { + continue; + } + if (typeof match.channel === "string" && match.channel.trim()) { + continue; + } const provider = typeof match.provider === "string" ? match.provider.trim() : ""; - if (!provider) continue; + if (!provider) { + continue; + } match.channel = provider; delete match.provider; entry.match = match; @@ -39,17 +49,27 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_1: LegacyConfigMigration[] = [ describe: "Move bindings[].match.accountID to bindings[].match.accountId", apply: (raw, changes) => { const bindings = Array.isArray(raw.bindings) ? raw.bindings : null; - if (!bindings) return; + if (!bindings) { + return; + } let touched = false; for (const entry of bindings) { - if (!isRecord(entry)) continue; + if (!isRecord(entry)) { + continue; + } const match = getRecord(entry.match); - if (!match) continue; - if (match.accountId !== undefined) continue; + if (!match) { + continue; + } + if (match.accountId !== undefined) { + continue; + } const accountID = typeof match.accountID === "string" ? match.accountID.trim() : match.accountID; - if (!accountID) continue; + if (!accountID) { + continue; + } match.accountId = accountID; delete match.accountID; entry.match = match; @@ -67,20 +87,34 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_1: LegacyConfigMigration[] = [ describe: "Move session.sendPolicy.rules[].match.provider to match.channel", apply: (raw, changes) => { const session = getRecord(raw.session); - if (!session) return; + if (!session) { + return; + } const sendPolicy = getRecord(session.sendPolicy); - if (!sendPolicy) return; + if (!sendPolicy) { + return; + } const rules = Array.isArray(sendPolicy.rules) ? sendPolicy.rules : null; - if (!rules) return; + if (!rules) { + return; + } let touched = false; for (const rule of rules) { - if (!isRecord(rule)) continue; + if (!isRecord(rule)) { + continue; + } const match = getRecord(rule.match); - if (!match) continue; - if (typeof match.channel === "string" && match.channel.trim()) continue; + if (!match) { + continue; + } + if (typeof match.channel === "string" && match.channel.trim()) { + continue; + } const provider = typeof match.provider === "string" ? match.provider.trim() : ""; - if (!provider) continue; + if (!provider) { + continue; + } match.channel = provider; delete match.provider; rule.match = match; @@ -100,10 +134,16 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_1: LegacyConfigMigration[] = [ describe: "Move messages.queue.byProvider to messages.queue.byChannel", apply: (raw, changes) => { const messages = getRecord(raw.messages); - if (!messages) return; + if (!messages) { + return; + } const queue = getRecord(messages.queue); - if (!queue) return; - if (queue.byProvider === undefined) return; + if (!queue) { + return; + } + if (queue.byProvider === undefined) { + return; + } if (queue.byChannel === undefined) { queue.byChannel = queue.byProvider; changes.push("Moved messages.queue.byProvider → messages.queue.byChannel."); @@ -129,12 +169,16 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_1: LegacyConfigMigration[] = [ "msteams", ]; const legacyEntries = legacyKeys.filter((key) => isRecord(raw[key])); - if (legacyEntries.length === 0) return; + if (legacyEntries.length === 0) { + return; + } const channels = ensureRecord(raw, "channels"); for (const key of legacyEntries) { const legacy = getRecord(raw[key]); - if (!legacy) continue; + if (!legacy) { + continue; + } const channelEntry = ensureRecord(channels, key); const hadEntries = Object.keys(channelEntry).length > 0; mergeMissing(channelEntry, legacy); @@ -152,9 +196,13 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_1: LegacyConfigMigration[] = [ describe: "Move routing.allowFrom to channels.whatsapp.allowFrom", apply: (raw, changes) => { const routing = raw.routing; - if (!routing || typeof routing !== "object") return; + if (!routing || typeof routing !== "object") { + return; + } const allowFrom = (routing as Record).allowFrom; - if (allowFrom === undefined) return; + if (allowFrom === undefined) { + return; + } const channels = getRecord(raw.channels); const whatsapp = channels ? getRecord(channels.whatsapp) : null; @@ -187,22 +235,30 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_1: LegacyConfigMigration[] = [ describe: "Move routing.groupChat.requireMention to channels.whatsapp/telegram/imessage groups", apply: (raw, changes) => { const routing = raw.routing; - if (!routing || typeof routing !== "object") return; + if (!routing || typeof routing !== "object") { + return; + } const groupChat = (routing as Record).groupChat && typeof (routing as Record).groupChat === "object" ? ((routing as Record).groupChat as Record) : null; - if (!groupChat) return; + if (!groupChat) { + return; + } const requireMention = groupChat.requireMention; - if (requireMention === undefined) return; + if (requireMention === undefined) { + return; + } const channels = ensureRecord(raw, "channels"); const applyTo = ( key: "whatsapp" | "telegram" | "imessage", options?: { requireExisting?: boolean }, ) => { - if (options?.requireExisting && !isRecord(channels[key])) return; + if (options?.requireExisting && !isRecord(channels[key])) { + return; + } const section = channels[key] && typeof channels[key] === "object" ? (channels[key] as Record) @@ -250,9 +306,13 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_1: LegacyConfigMigration[] = [ describe: "Move gateway.token to gateway.auth.token", apply: (raw, changes) => { const gateway = raw.gateway; - if (!gateway || typeof gateway !== "object") return; + if (!gateway || typeof gateway !== "object") { + return; + } const token = (gateway as Record).token; - if (token === undefined) return; + if (token === undefined) { + return; + } const gatewayObj = gateway as Record; const auth = @@ -261,7 +321,9 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_1: LegacyConfigMigration[] = [ : {}; if (auth.token === undefined) { auth.token = token; - if (!auth.mode) auth.mode = "token"; + if (!auth.mode) { + auth.mode = "token"; + } changes.push("Moved gateway.token → gateway.auth.token."); } else { changes.push("Removed gateway.token (gateway.auth.token already set)."); @@ -279,9 +341,13 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_1: LegacyConfigMigration[] = [ apply: (raw, changes) => { const channels = ensureRecord(raw, "channels"); const telegram = channels.telegram; - if (!telegram || typeof telegram !== "object") return; + if (!telegram || typeof telegram !== "object") { + return; + } const requireMention = (telegram as Record).requireMention; - if (requireMention === undefined) return; + if (requireMention === undefined) { + return; + } const groups = (telegram as Record).groups && diff --git a/src/config/legacy.migrations.part-2.ts b/src/config/legacy.migrations.part-2.ts index cd04da6f1f..c08020b146 100644 --- a/src/config/legacy.migrations.part-2.ts +++ b/src/config/legacy.migrations.part-2.ts @@ -18,7 +18,9 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_2: LegacyConfigMigration[] = [ const agentRoot = getRecord(raw.agent); const defaults = getRecord(getRecord(raw.agents)?.defaults); const agent = agentRoot ?? defaults; - if (!agent) return; + if (!agent) { + return; + } const label = agentRoot ? "agent" : "agents.defaults"; const legacyModel = typeof agent.model === "string" ? String(agent.model) : undefined; @@ -45,7 +47,9 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_2: LegacyConfigMigration[] = [ legacyModelFallbacks.length > 0 || legacyImageModelFallbacks.length > 0 || Object.keys(legacyAliases).length > 0; - if (!hasLegacy) return; + if (!hasLegacy) { + return; + } const models = agent.models && typeof agent.models === "object" @@ -53,26 +57,44 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_2: LegacyConfigMigration[] = [ : {}; const ensureModel = (rawKey?: string) => { - if (typeof rawKey !== "string") return; + if (typeof rawKey !== "string") { + return; + } const key = rawKey.trim(); - if (!key) return; - if (!models[key]) models[key] = {}; + if (!key) { + return; + } + if (!models[key]) { + models[key] = {}; + } }; ensureModel(legacyModel); ensureModel(legacyImageModel); - for (const key of legacyAllowed) ensureModel(key); - for (const key of legacyModelFallbacks) ensureModel(key); - for (const key of legacyImageModelFallbacks) ensureModel(key); + for (const key of legacyAllowed) { + ensureModel(key); + } + for (const key of legacyModelFallbacks) { + ensureModel(key); + } + for (const key of legacyImageModelFallbacks) { + ensureModel(key); + } for (const target of Object.values(legacyAliases)) { - if (typeof target !== "string") continue; + if (typeof target !== "string") { + continue; + } ensureModel(target); } for (const [alias, targetRaw] of Object.entries(legacyAliases)) { - if (typeof targetRaw !== "string") continue; + if (typeof targetRaw !== "string") { + continue; + } const target = targetRaw.trim(); - if (!target) continue; + if (!target) { + continue; + } const entry = models[target] && typeof models[target] === "object" ? (models[target] as Record) @@ -159,7 +181,9 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_2: LegacyConfigMigration[] = [ describe: "Move routing.agents/defaultAgentId to agents.list", apply: (raw, changes) => { const routing = getRecord(raw.routing); - if (!routing) return; + if (!routing) { + return; + } const routingAgents = getRecord(routing.agents); const agents = ensureRecord(raw, "agents"); @@ -169,7 +193,9 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_2: LegacyConfigMigration[] = [ for (const [rawId, entryRaw] of Object.entries(routingAgents)) { const agentId = String(rawId ?? "").trim(); const entry = getRecord(entryRaw); - if (!agentId || !entry) continue; + if (!agentId || !entry) { + continue; + } const target = ensureAgentEntry(list, agentId); const entryCopy: Record = { ...entry }; @@ -251,7 +277,9 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_2: LegacyConfigMigration[] = [ describe: "Move routing bindings/groupChat/queue/agentToAgent/transcribeAudio", apply: (raw, changes) => { const routing = getRecord(raw.routing); - if (!routing) return; + if (!routing) { + return; + } if (routing.bindings !== undefined) { if (raw.bindings === undefined) { @@ -362,13 +390,19 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_2: LegacyConfigMigration[] = [ changes.push("Removed audio.transcription (tools.media.audio.models already set)."); } delete audio.transcription; - if (Object.keys(audio).length === 0) delete raw.audio; - else raw.audio = audio; + if (Object.keys(audio).length === 0) { + delete raw.audio; + } else { + raw.audio = audio; + } } else { delete audio.transcription; changes.push("Removed audio.transcription (unsupported transcription CLI)."); - if (Object.keys(audio).length === 0) delete raw.audio; - else raw.audio = audio; + if (Object.keys(audio).length === 0) { + delete raw.audio; + } else { + raw.audio = audio; + } } } diff --git a/src/config/legacy.migrations.part-3.ts b/src/config/legacy.migrations.part-3.ts index 21589e4fa5..bb1ae80879 100644 --- a/src/config/legacy.migrations.part-3.ts +++ b/src/config/legacy.migrations.part-3.ts @@ -20,10 +20,16 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_3: LegacyConfigMigration[] = [ apply: (raw, changes) => { const auth = getRecord(raw.auth); const profiles = getRecord(auth?.profiles); - if (!profiles) return; + if (!profiles) { + return; + } const claudeCli = getRecord(profiles["anthropic:claude-cli"]); - if (!claudeCli) return; - if (claudeCli.mode !== "token") return; + if (!claudeCli) { + return; + } + if (claudeCli.mode !== "token") { + return; + } claudeCli.mode = "oauth"; changes.push('Updated auth.profiles["anthropic:claude-cli"].mode → "oauth".'); }, @@ -35,7 +41,9 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_3: LegacyConfigMigration[] = [ apply: (raw, changes) => { const tools = ensureRecord(raw, "tools"); const bash = getRecord(tools.bash); - if (!bash) return; + if (!bash) { + return; + } if (tools.exec === undefined) { tools.exec = bash; changes.push("Moved tools.bash → tools.exec."); @@ -51,7 +59,9 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_3: LegacyConfigMigration[] = [ apply: (raw, changes) => { const messages = getRecord(raw.messages); const tts = getRecord(messages?.tts); - if (!tts) return; + if (!tts) { + return; + } if (tts.auto !== undefined) { if ("enabled" in tts) { delete tts.enabled; @@ -59,7 +69,9 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_3: LegacyConfigMigration[] = [ } return; } - if (typeof tts.enabled !== "boolean") return; + if (typeof tts.enabled !== "boolean") { + return; + } tts.auto = tts.enabled ? "always" : "off"; delete tts.enabled; changes.push(`Moved messages.tts.enabled → messages.tts.auto (${String(tts.auto)}).`); @@ -70,7 +82,9 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_3: LegacyConfigMigration[] = [ describe: "Move agent config to agents.defaults and tools", apply: (raw, changes) => { const agent = getRecord(raw.agent); - if (!agent) return; + if (!agent) { + return; + } const agents = ensureRecord(raw, "agents"); const defaults = getRecord(agents.defaults) ?? {}; @@ -136,8 +150,12 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_3: LegacyConfigMigration[] = [ delete agentCopy.tools; delete agentCopy.elevated; delete agentCopy.bash; - if (isRecord(agentCopy.sandbox)) delete agentCopy.sandbox.tools; - if (isRecord(agentCopy.subagents)) delete agentCopy.subagents.tools; + if (isRecord(agentCopy.sandbox)) { + delete agentCopy.sandbox.tools; + } + if (isRecord(agentCopy.subagents)) { + delete agentCopy.subagents.tools; + } mergeMissing(defaults, agentCopy); agents.defaults = defaults; @@ -151,7 +169,9 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_3: LegacyConfigMigration[] = [ describe: "Move identity to agents.list[].identity", apply: (raw, changes) => { const identity = getRecord(raw.identity); - if (!identity) return; + if (!identity) { + return; + } const agents = ensureRecord(raw, "agents"); const list = getAgentsList(agents); diff --git a/src/config/legacy.shared.ts b/src/config/legacy.shared.ts index d2d361f006..bd978b2287 100644 --- a/src/config/legacy.shared.ts +++ b/src/config/legacy.shared.ts @@ -21,7 +21,9 @@ export const ensureRecord = ( key: string, ): Record => { const existing = root[key]; - if (isRecord(existing)) return existing; + if (isRecord(existing)) { + return existing; + } const next: Record = {}; root[key] = next; return next; @@ -29,7 +31,9 @@ export const ensureRecord = ( export const mergeMissing = (target: Record, source: Record) => { for (const [key, value] of Object.entries(source)) { - if (value === undefined) continue; + if (value === undefined) { + continue; + } const existing = target[key]; if (existing === undefined) { target[key] = value; @@ -46,19 +50,29 @@ const AUDIO_TRANSCRIPTION_CLI_ALLOWLIST = new Set(["whisper"]); export const mapLegacyAudioTranscription = (value: unknown): Record | null => { const transcriber = getRecord(value); const command = Array.isArray(transcriber?.command) ? transcriber?.command : null; - if (!command || command.length === 0) return null; + if (!command || command.length === 0) { + return null; + } const rawExecutable = String(command[0] ?? "").trim(); - if (!rawExecutable) return null; + if (!rawExecutable) { + return null; + } const executableName = rawExecutable.split(/[\\/]/).pop() ?? rawExecutable; - if (!AUDIO_TRANSCRIPTION_CLI_ALLOWLIST.has(executableName)) return null; + if (!AUDIO_TRANSCRIPTION_CLI_ALLOWLIST.has(executableName)) { + return null; + } const args = command.slice(1).map((part) => String(part)); const timeoutSeconds = typeof transcriber?.timeoutSeconds === "number" ? transcriber?.timeoutSeconds : undefined; const result: Record = { command: rawExecutable, type: "cli" }; - if (args.length > 0) result.args = args; - if (timeoutSeconds !== undefined) result.timeoutSeconds = timeoutSeconds; + if (args.length > 0) { + result.args = args; + } + if (timeoutSeconds !== undefined) { + result.timeoutSeconds = timeoutSeconds; + } return result; }; @@ -77,16 +91,22 @@ export const resolveDefaultAgentIdFromRaw = (raw: Record) => { typeof entry.id === "string" && entry.id.trim() !== "", ); - if (defaultEntry) return defaultEntry.id.trim(); + if (defaultEntry) { + return defaultEntry.id.trim(); + } const routing = getRecord(raw.routing); const routingDefault = typeof routing?.defaultAgentId === "string" ? routing.defaultAgentId.trim() : ""; - if (routingDefault) return routingDefault; + if (routingDefault) { + return routingDefault; + } const firstEntry = list.find( (entry): entry is { id: string } => isRecord(entry) && typeof entry.id === "string" && entry.id.trim() !== "", ); - if (firstEntry) return firstEntry.id.trim(); + if (firstEntry) { + return firstEntry.id.trim(); + } return "main"; }; @@ -96,7 +116,9 @@ export const ensureAgentEntry = (list: unknown[], id: string): Record => isRecord(entry) && typeof entry.id === "string" && entry.id.trim() === normalized, ); - if (existing) return existing; + if (existing) { + return existing; + } const created: Record = { id: normalized }; list.push(created); return created; diff --git a/src/config/legacy.ts b/src/config/legacy.ts index c918fed4f9..4f34fb9563 100644 --- a/src/config/legacy.ts +++ b/src/config/legacy.ts @@ -3,7 +3,9 @@ import { LEGACY_CONFIG_RULES } from "./legacy.rules.js"; import type { LegacyConfigIssue } from "./types.js"; export function findLegacyConfigIssues(raw: unknown): LegacyConfigIssue[] { - if (!raw || typeof raw !== "object") return []; + if (!raw || typeof raw !== "object") { + return []; + } const root = raw as Record; const issues: LegacyConfigIssue[] = []; for (const rule of LEGACY_CONFIG_RULES) { @@ -26,12 +28,16 @@ export function applyLegacyMigrations(raw: unknown): { next: Record | null; changes: string[]; } { - if (!raw || typeof raw !== "object") return { next: null, changes: [] }; + if (!raw || typeof raw !== "object") { + return { next: null, changes: [] }; + } const next = structuredClone(raw) as Record; const changes: string[] = []; for (const migration of LEGACY_CONFIG_MIGRATIONS) { migration.apply(next, changes); } - if (changes.length === 0) return { next: null, changes: [] }; + if (changes.length === 0) { + return { next: null, changes: [] }; + } return { next, changes }; } diff --git a/src/config/markdown-tables.ts b/src/config/markdown-tables.ts index bdeddef0ad..8815a90b13 100644 --- a/src/config/markdown-tables.ts +++ b/src/config/markdown-tables.ts @@ -25,19 +25,25 @@ function resolveMarkdownModeFromSection( section: MarkdownConfigSection | undefined, accountId?: string | null, ): MarkdownTableMode | undefined { - if (!section) return undefined; + if (!section) { + return undefined; + } const normalizedAccountId = normalizeAccountId(accountId); const accounts = section.accounts; if (accounts && typeof accounts === "object") { const direct = accounts[normalizedAccountId]; const directMode = direct?.markdown?.tables; - if (isMarkdownTableMode(directMode)) return directMode; + if (isMarkdownTableMode(directMode)) { + return directMode; + } const matchKey = Object.keys(accounts).find( (key) => key.toLowerCase() === normalizedAccountId.toLowerCase(), ); const match = matchKey ? accounts[matchKey] : undefined; const matchMode = match?.markdown?.tables; - if (isMarkdownTableMode(matchMode)) return matchMode; + if (isMarkdownTableMode(matchMode)) { + return matchMode; + } } const sectionMode = section.markdown?.tables; return isMarkdownTableMode(sectionMode) ? sectionMode : undefined; @@ -50,7 +56,9 @@ export function resolveMarkdownTableMode(params: { }): MarkdownTableMode { const channel = normalizeChannelId(params.channel); const defaultMode = channel ? (DEFAULT_TABLE_MODES.get(channel) ?? "code") : "code"; - if (!channel || !params.cfg) return defaultMode; + if (!channel || !params.cfg) { + return defaultMode; + } const channelsConfig = params.cfg.channels as Record | undefined; const section = (channelsConfig?.[channel] ?? (params.cfg as Record | undefined)?.[channel]) as diff --git a/src/config/normalize-paths.ts b/src/config/normalize-paths.ts index 12ccd6df5f..187815f5da 100644 --- a/src/config/normalize-paths.ts +++ b/src/config/normalize-paths.ts @@ -11,8 +11,12 @@ function isPlainObject(value: unknown): value is Record { } function normalizeStringValue(key: string | undefined, value: string): string { - if (!PATH_VALUE_RE.test(value.trim())) return value; - if (!key) return value; + if (!PATH_VALUE_RE.test(value.trim())) { + return value; + } + if (!key) { + return value; + } if (PATH_KEY_RE.test(key) || PATH_LIST_KEYS.has(key)) { return resolveUserPath(value); } @@ -20,7 +24,9 @@ function normalizeStringValue(key: string | undefined, value: string): string { } function normalizeAny(key: string | undefined, value: unknown): unknown { - if (typeof value === "string") return normalizeStringValue(key, value); + if (typeof value === "string") { + return normalizeStringValue(key, value); + } if (Array.isArray(value)) { const normalizeChildren = Boolean(key && PATH_LIST_KEYS.has(key)); @@ -28,17 +34,25 @@ function normalizeAny(key: string | undefined, value: unknown): unknown { if (typeof entry === "string") { return normalizeChildren ? normalizeStringValue(key, entry) : entry; } - if (Array.isArray(entry)) return normalizeAny(undefined, entry); - if (isPlainObject(entry)) return normalizeAny(undefined, entry); + if (Array.isArray(entry)) { + return normalizeAny(undefined, entry); + } + if (isPlainObject(entry)) { + return normalizeAny(undefined, entry); + } return entry; }); } - if (!isPlainObject(value)) return value; + if (!isPlainObject(value)) { + return value; + } for (const [childKey, childValue] of Object.entries(value)) { const next = normalizeAny(childKey, childValue); - if (next !== childValue) value[childKey] = next; + if (next !== childValue) { + value[childKey] = next; + } } return value; @@ -51,7 +65,9 @@ function normalizeAny(key: string | undefined, value: unknown): unknown { * keeping the surface area small and predictable. */ export function normalizeConfigPaths(cfg: OpenClawConfig): OpenClawConfig { - if (!cfg || typeof cfg !== "object") return cfg; + if (!cfg || typeof cfg !== "object") { + return cfg; + } normalizeAny(undefined, cfg); return cfg; } diff --git a/src/config/paths.test.ts b/src/config/paths.test.ts index d06f6612d4..247cdeb510 100644 --- a/src/config/paths.test.ts +++ b/src/config/paths.test.ts @@ -114,20 +114,41 @@ describe("state + config path candidates", () => { } else { process.env.HOME = previousHome; } - if (previousUserProfile === undefined) delete process.env.USERPROFILE; - else process.env.USERPROFILE = previousUserProfile; - if (previousHomeDrive === undefined) delete process.env.HOMEDRIVE; - else process.env.HOMEDRIVE = previousHomeDrive; - if (previousHomePath === undefined) delete process.env.HOMEPATH; - else process.env.HOMEPATH = previousHomePath; - if (previousOpenClawConfig === undefined) delete process.env.OPENCLAW_CONFIG_PATH; - else process.env.OPENCLAW_CONFIG_PATH = previousOpenClawConfig; - if (previousOpenClawConfig === undefined) delete process.env.OPENCLAW_CONFIG_PATH; - else process.env.OPENCLAW_CONFIG_PATH = previousOpenClawConfig; - if (previousOpenClawState === undefined) delete process.env.OPENCLAW_STATE_DIR; - else process.env.OPENCLAW_STATE_DIR = previousOpenClawState; - if (previousOpenClawState === undefined) delete process.env.OPENCLAW_STATE_DIR; - else process.env.OPENCLAW_STATE_DIR = previousOpenClawState; + if (previousUserProfile === undefined) { + delete process.env.USERPROFILE; + } else { + process.env.USERPROFILE = previousUserProfile; + } + if (previousHomeDrive === undefined) { + delete process.env.HOMEDRIVE; + } else { + process.env.HOMEDRIVE = previousHomeDrive; + } + if (previousHomePath === undefined) { + delete process.env.HOMEPATH; + } else { + process.env.HOMEPATH = previousHomePath; + } + if (previousOpenClawConfig === undefined) { + delete process.env.OPENCLAW_CONFIG_PATH; + } else { + process.env.OPENCLAW_CONFIG_PATH = previousOpenClawConfig; + } + if (previousOpenClawConfig === undefined) { + delete process.env.OPENCLAW_CONFIG_PATH; + } else { + process.env.OPENCLAW_CONFIG_PATH = previousOpenClawConfig; + } + if (previousOpenClawState === undefined) { + delete process.env.OPENCLAW_STATE_DIR; + } else { + process.env.OPENCLAW_STATE_DIR = previousOpenClawState; + } + if (previousOpenClawState === undefined) { + delete process.env.OPENCLAW_STATE_DIR; + } else { + process.env.OPENCLAW_STATE_DIR = previousOpenClawState; + } await fs.rm(root, { recursive: true, force: true }); vi.resetModules(); } diff --git a/src/config/paths.ts b/src/config/paths.ts index 4f10c277f2..ba9dce7563 100644 --- a/src/config/paths.ts +++ b/src/config/paths.ts @@ -51,11 +51,15 @@ export function resolveStateDir( homedir: () => string = os.homedir, ): string { const override = env.OPENCLAW_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim(); - if (override) return resolveUserPath(override); + if (override) { + return resolveUserPath(override); + } const newDir = newStateDir(homedir); const legacyDirs = legacyStateDirs(homedir); const hasNew = fs.existsSync(newDir); - if (hasNew) return newDir; + if (hasNew) { + return newDir; + } const existingLegacy = legacyDirs.find((dir) => { try { return fs.existsSync(dir); @@ -63,13 +67,17 @@ export function resolveStateDir( return false; } }); - if (existingLegacy) return existingLegacy; + if (existingLegacy) { + return existingLegacy; + } return newDir; } function resolveUserPath(input: string): string { const trimmed = input.trim(); - if (!trimmed) return trimmed; + if (!trimmed) { + return trimmed; + } if (trimmed.startsWith("~")) { const expanded = trimmed.replace(/^~(?=$|[\\/])/, os.homedir()); return path.resolve(expanded); @@ -89,7 +97,9 @@ export function resolveCanonicalConfigPath( stateDir: string = resolveStateDir(env, os.homedir), ): string { const override = env.OPENCLAW_CONFIG_PATH?.trim() || env.CLAWDBOT_CONFIG_PATH?.trim(); - if (override) return resolveUserPath(override); + if (override) { + return resolveUserPath(override); + } return path.join(stateDir, CONFIG_FILENAME); } @@ -109,7 +119,9 @@ export function resolveConfigPathCandidate( return false; } }); - if (existing) return existing; + if (existing) { + return existing; + } return resolveCanonicalConfigPath(env, resolveStateDir(env, homedir)); } @@ -122,7 +134,9 @@ export function resolveConfigPath( homedir: () => string = os.homedir, ): string { const override = env.OPENCLAW_CONFIG_PATH?.trim(); - if (override) return resolveUserPath(override); + if (override) { + return resolveUserPath(override); + } const stateOverride = env.OPENCLAW_STATE_DIR?.trim(); const candidates = [ path.join(stateDir, CONFIG_FILENAME), @@ -135,8 +149,12 @@ export function resolveConfigPath( return false; } }); - if (existing) return existing; - if (stateOverride) return path.join(stateDir, CONFIG_FILENAME); + if (existing) { + return existing; + } + if (stateOverride) { + return path.join(stateDir, CONFIG_FILENAME); + } const defaultStateDir = resolveStateDir(env, homedir); if (path.resolve(stateDir) === path.resolve(defaultStateDir)) { return resolveConfigPathCandidate(env, homedir); @@ -155,7 +173,9 @@ export function resolveDefaultConfigCandidates( homedir: () => string = os.homedir, ): string[] { const explicit = env.OPENCLAW_CONFIG_PATH?.trim() || env.CLAWDBOT_CONFIG_PATH?.trim(); - if (explicit) return [resolveUserPath(explicit)]; + if (explicit) { + return [resolveUserPath(explicit)]; + } const candidates: string[] = []; const openclawStateDir = env.OPENCLAW_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim(); @@ -200,7 +220,9 @@ export function resolveOAuthDir( stateDir: string = resolveStateDir(env, os.homedir), ): string { const override = env.OPENCLAW_OAUTH_DIR?.trim(); - if (override) return resolveUserPath(override); + if (override) { + return resolveUserPath(override); + } return path.join(stateDir, "credentials"); } @@ -218,11 +240,15 @@ export function resolveGatewayPort( const envRaw = env.OPENCLAW_GATEWAY_PORT?.trim() || env.CLAWDBOT_GATEWAY_PORT?.trim(); if (envRaw) { const parsed = Number.parseInt(envRaw, 10); - if (Number.isFinite(parsed) && parsed > 0) return parsed; + if (Number.isFinite(parsed) && parsed > 0) { + return parsed; + } } const configPort = cfg?.gateway?.port; if (typeof configPort === "number" && Number.isFinite(configPort)) { - if (configPort > 0) return configPort; + if (configPort > 0) { + return configPort; + } } return DEFAULT_GATEWAY_PORT; } diff --git a/src/config/plugin-auto-enable.ts b/src/config/plugin-auto-enable.ts index c9c106597a..19b422bde0 100644 --- a/src/config/plugin-auto-enable.ts +++ b/src/config/plugin-auto-enable.ts @@ -48,11 +48,17 @@ function recordHasKeys(value: unknown): boolean { } function accountsHaveKeys(value: unknown, keys: string[]): boolean { - if (!isRecord(value)) return false; + if (!isRecord(value)) { + return false; + } for (const account of Object.values(value)) { - if (!isRecord(account)) continue; + if (!isRecord(account)) { + continue; + } for (const key of keys) { - if (hasNonEmptyString(account[key])) return true; + if (hasNonEmptyString(account[key])) { + return true; + } } } return false; @@ -68,20 +74,36 @@ function resolveChannelConfig( } function isTelegramConfigured(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): boolean { - if (hasNonEmptyString(env.TELEGRAM_BOT_TOKEN)) return true; + if (hasNonEmptyString(env.TELEGRAM_BOT_TOKEN)) { + return true; + } const entry = resolveChannelConfig(cfg, "telegram"); - if (!entry) return false; - if (hasNonEmptyString(entry.botToken) || hasNonEmptyString(entry.tokenFile)) return true; - if (accountsHaveKeys(entry.accounts, ["botToken", "tokenFile"])) return true; + if (!entry) { + return false; + } + if (hasNonEmptyString(entry.botToken) || hasNonEmptyString(entry.tokenFile)) { + return true; + } + if (accountsHaveKeys(entry.accounts, ["botToken", "tokenFile"])) { + return true; + } return recordHasKeys(entry); } function isDiscordConfigured(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): boolean { - if (hasNonEmptyString(env.DISCORD_BOT_TOKEN)) return true; + if (hasNonEmptyString(env.DISCORD_BOT_TOKEN)) { + return true; + } const entry = resolveChannelConfig(cfg, "discord"); - if (!entry) return false; - if (hasNonEmptyString(entry.token)) return true; - if (accountsHaveKeys(entry.accounts, ["token"])) return true; + if (!entry) { + return false; + } + if (hasNonEmptyString(entry.token)) { + return true; + } + if (accountsHaveKeys(entry.accounts, ["token"])) { + return true; + } return recordHasKeys(entry); } @@ -94,7 +116,9 @@ function isSlackConfigured(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): boolean return true; } const entry = resolveChannelConfig(cfg, "slack"); - if (!entry) return false; + if (!entry) { + return false; + } if ( hasNonEmptyString(entry.botToken) || hasNonEmptyString(entry.appToken) || @@ -102,13 +126,17 @@ function isSlackConfigured(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): boolean ) { return true; } - if (accountsHaveKeys(entry.accounts, ["botToken", "appToken", "userToken"])) return true; + if (accountsHaveKeys(entry.accounts, ["botToken", "appToken", "userToken"])) { + return true; + } return recordHasKeys(entry); } function isSignalConfigured(cfg: OpenClawConfig): boolean { const entry = resolveChannelConfig(cfg, "signal"); - if (!entry) return false; + if (!entry) { + return false; + } if ( hasNonEmptyString(entry.account) || hasNonEmptyString(entry.httpUrl) || @@ -118,21 +146,31 @@ function isSignalConfigured(cfg: OpenClawConfig): boolean { ) { return true; } - if (accountsHaveKeys(entry.accounts, ["account", "httpUrl", "httpHost", "cliPath"])) return true; + if (accountsHaveKeys(entry.accounts, ["account", "httpUrl", "httpHost", "cliPath"])) { + return true; + } return recordHasKeys(entry); } function isIMessageConfigured(cfg: OpenClawConfig): boolean { const entry = resolveChannelConfig(cfg, "imessage"); - if (!entry) return false; - if (hasNonEmptyString(entry.cliPath)) return true; + if (!entry) { + return false; + } + if (hasNonEmptyString(entry.cliPath)) { + return true; + } return recordHasKeys(entry); } function isWhatsAppConfigured(cfg: OpenClawConfig): boolean { - if (hasAnyWhatsAppAuth(cfg)) return true; + if (hasAnyWhatsAppAuth(cfg)) { + return true; + } const entry = resolveChannelConfig(cfg, "whatsapp"); - if (!entry) return false; + if (!entry) { + return false; + } return recordHasKeys(entry); } @@ -167,10 +205,14 @@ export function isChannelConfigured( function collectModelRefs(cfg: OpenClawConfig): string[] { const refs: string[] = []; const pushModelRef = (value: unknown) => { - if (typeof value === "string" && value.trim()) refs.push(value.trim()); + if (typeof value === "string" && value.trim()) { + refs.push(value.trim()); + } }; const collectFromAgent = (agent: Record | null | undefined) => { - if (!agent) return; + if (!agent) { + return; + } const model = agent.model; if (typeof model === "string") { pushModelRef(model); @@ -178,7 +220,9 @@ function collectModelRefs(cfg: OpenClawConfig): string[] { pushModelRef(model.primary); const fallbacks = model.fallbacks; if (Array.isArray(fallbacks)) { - for (const entry of fallbacks) pushModelRef(entry); + for (const entry of fallbacks) { + pushModelRef(entry); + } } } const models = agent.models; @@ -195,7 +239,9 @@ function collectModelRefs(cfg: OpenClawConfig): string[] { const list = cfg.agents?.list; if (Array.isArray(list)) { for (const entry of list) { - if (isRecord(entry)) collectFromAgent(entry); + if (isRecord(entry)) { + collectFromAgent(entry); + } } } return refs; @@ -204,7 +250,9 @@ function collectModelRefs(cfg: OpenClawConfig): string[] { function extractProviderFromModelRef(value: string): string | null { const trimmed = value.trim(); const slash = trimmed.indexOf("/"); - if (slash <= 0) return null; + if (slash <= 0) { + return null; + } return normalizeProviderId(trimmed.slice(0, slash)); } @@ -214,23 +262,31 @@ function isProviderConfigured(cfg: OpenClawConfig, providerId: string): boolean const profiles = cfg.auth?.profiles; if (profiles && typeof profiles === "object") { for (const profile of Object.values(profiles)) { - if (!isRecord(profile)) continue; + if (!isRecord(profile)) { + continue; + } const provider = normalizeProviderId(String(profile.provider ?? "")); - if (provider === normalized) return true; + if (provider === normalized) { + return true; + } } } const providerConfig = cfg.models?.providers; if (providerConfig && typeof providerConfig === "object") { for (const key of Object.keys(providerConfig)) { - if (normalizeProviderId(key) === normalized) return true; + if (normalizeProviderId(key) === normalized) { + return true; + } } } const modelRefs = collectModelRefs(cfg); for (const ref of modelRefs) { const provider = extractProviderFromModelRef(ref); - if (provider && provider === normalized) return true; + if (provider && provider === normalized) { + return true; + } } return false; @@ -245,12 +301,16 @@ function resolveConfiguredPlugins( const configuredChannels = cfg.channels as Record | undefined; if (configuredChannels && typeof configuredChannels === "object") { for (const key of Object.keys(configuredChannels)) { - if (key === "defaults") continue; + if (key === "defaults") { + continue; + } channelIds.add(key); } } for (const channelId of channelIds) { - if (!channelId) continue; + if (!channelId) { + continue; + } if (isChannelConfigured(cfg, channelId, env)) { changes.push({ pluginId: channelId, @@ -294,9 +354,15 @@ function shouldSkipPreferredPluginAutoEnable( configured: PluginEnableChange[], ): boolean { for (const other of configured) { - if (other.pluginId === entry.pluginId) continue; - if (isPluginDenied(cfg, other.pluginId)) continue; - if (isPluginExplicitlyDisabled(cfg, other.pluginId)) continue; + if (other.pluginId === entry.pluginId) { + continue; + } + if (isPluginDenied(cfg, other.pluginId)) { + continue; + } + if (isPluginExplicitlyDisabled(cfg, other.pluginId)) { + continue; + } const preferOver = resolvePreferredOverIds(other.pluginId); if (preferOver.includes(entry.pluginId)) { return true; @@ -307,7 +373,9 @@ function shouldSkipPreferredPluginAutoEnable( function ensureAllowlisted(cfg: OpenClawConfig, pluginId: string): OpenClawConfig { const allow = cfg.plugins?.allow; - if (!Array.isArray(allow) || allow.includes(pluginId)) return cfg; + if (!Array.isArray(allow) || allow.includes(pluginId)) { + return cfg; + } return { ...cfg, plugins: { @@ -363,13 +431,21 @@ export function applyPluginAutoEnable(params: { } for (const entry of configured) { - if (isPluginDenied(next, entry.pluginId)) continue; - if (isPluginExplicitlyDisabled(next, entry.pluginId)) continue; - if (shouldSkipPreferredPluginAutoEnable(next, entry, configured)) continue; + if (isPluginDenied(next, entry.pluginId)) { + continue; + } + if (isPluginExplicitlyDisabled(next, entry.pluginId)) { + continue; + } + if (shouldSkipPreferredPluginAutoEnable(next, entry, configured)) { + continue; + } const allow = next.plugins?.allow; const allowMissing = Array.isArray(allow) && !allow.includes(entry.pluginId); const alreadyEnabled = next.plugins?.entries?.[entry.pluginId]?.enabled === true; - if (alreadyEnabled && !allowMissing) continue; + if (alreadyEnabled && !allowMissing) { + continue; + } next = enablePluginEntry(next, entry.pluginId); next = ensureAllowlisted(next, entry.pluginId); changes.push(formatAutoEnableChange(entry)); diff --git a/src/config/port-defaults.ts b/src/config/port-defaults.ts index 48421b7c46..8e5321cb8d 100644 --- a/src/config/port-defaults.ts +++ b/src/config/port-defaults.ts @@ -36,6 +36,8 @@ export function deriveDefaultBrowserCdpPortRange(browserControlPort: number): Po start + (DEFAULT_BROWSER_CDP_PORT_RANGE_END - DEFAULT_BROWSER_CDP_PORT_RANGE_START), DEFAULT_BROWSER_CDP_PORT_RANGE_END, ); - if (end < start) return { start, end: start }; + if (end < start) { + return { start, end: start }; + } return { start, end }; } diff --git a/src/config/runtime-overrides.ts b/src/config/runtime-overrides.ts index 15f44a5167..5bfae75af3 100644 --- a/src/config/runtime-overrides.ts +++ b/src/config/runtime-overrides.ts @@ -6,10 +6,14 @@ type OverrideTree = Record; let overrides: OverrideTree = {}; function mergeOverrides(base: unknown, override: unknown): unknown { - if (!isPlainObject(base) || !isPlainObject(override)) return override; + if (!isPlainObject(base) || !isPlainObject(override)) { + return override; + } const next: OverrideTree = { ...base }; for (const [key, value] of Object.entries(override)) { - if (value === undefined) continue; + if (value === undefined) { + continue; + } next[key] = mergeOverrides((base as OverrideTree)[key], value); } return next; @@ -65,6 +69,8 @@ export function unsetConfigOverride(pathRaw: string): { } export function applyConfigOverrides(cfg: OpenClawConfig): OpenClawConfig { - if (!overrides || Object.keys(overrides).length === 0) return cfg; + if (!overrides || Object.keys(overrides).length === 0) { + return cfg; + } return mergeOverrides(cfg, overrides) as OpenClawConfig; } diff --git a/src/config/schema.ts b/src/config/schema.ts index 1401b0574c..7e9b643c05 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -701,19 +701,27 @@ type JsonSchemaObject = JsonSchemaNode & { }; function cloneSchema(value: T): T { - if (typeof structuredClone === "function") return structuredClone(value); + if (typeof structuredClone === "function") { + return structuredClone(value); + } return JSON.parse(JSON.stringify(value)) as T; } function asSchemaObject(value: unknown): JsonSchemaObject | null { - if (!value || typeof value !== "object" || Array.isArray(value)) return null; + if (!value || typeof value !== "object" || Array.isArray(value)) { + return null; + } return value as JsonSchemaObject; } function isObjectSchema(schema: JsonSchemaObject): boolean { const type = schema.type; - if (type === "object") return true; - if (Array.isArray(type) && type.includes("object")) return true; + if (type === "object") { + return true; + } + if (Array.isArray(type) && type.includes("object")) { + return true; + } return Boolean(schema.properties || schema.additionalProperties); } @@ -731,7 +739,9 @@ function mergeObjectSchema(base: JsonSchemaObject, extension: JsonSchemaObject): merged.required = Array.from(mergedRequired); } const additional = extension.additionalProperties ?? base.additionalProperties; - if (additional !== undefined) merged.additionalProperties = additional; + if (additional !== undefined) { + merged.additionalProperties = additional; + } return merged; } @@ -773,7 +783,9 @@ function applyPluginHints(hints: ConfigUiHints, plugins: PluginUiMetadata[]): Co const next: ConfigUiHints = { ...hints }; for (const plugin of plugins) { const id = plugin.id.trim(); - if (!id) continue; + if (!id) { + continue; + } const name = (plugin.name ?? id).trim() || id; const basePath = `plugins.entries.${id}`; @@ -797,7 +809,9 @@ function applyPluginHints(hints: ConfigUiHints, plugins: PluginUiMetadata[]): Co const uiHints = plugin.configUiHints ?? {}; for (const [relPathRaw, hint] of Object.entries(uiHints)) { const relPath = relPathRaw.trim().replace(/^\./, ""); - if (!relPath) continue; + if (!relPath) { + continue; + } const key = `${basePath}.config.${relPath}`; next[key] = { ...next[key], @@ -812,7 +826,9 @@ function applyChannelHints(hints: ConfigUiHints, channels: ChannelUiMetadata[]): const next: ConfigUiHints = { ...hints }; for (const channel of channels) { const id = channel.id.trim(); - if (!id) continue; + if (!id) { + continue; + } const basePath = `channels.${id}`; const current = next[basePath] ?? {}; const label = channel.label?.trim(); @@ -826,7 +842,9 @@ function applyChannelHints(hints: ConfigUiHints, channels: ChannelUiMetadata[]): const uiHints = channel.configUiHints ?? {}; for (const [relPathRaw, hint] of Object.entries(uiHints)) { const relPath = relPathRaw.trim().replace(/^\./, ""); - if (!relPath) continue; + if (!relPath) { + continue; + } const key = `${basePath}.${relPath}`; next[key] = { ...next[key], @@ -842,13 +860,17 @@ function listHeartbeatTargetChannels(channels: ChannelUiMetadata[]): string[] { const ordered: string[] = []; for (const id of CHANNEL_IDS) { const normalized = id.trim().toLowerCase(); - if (!normalized || seen.has(normalized)) continue; + if (!normalized || seen.has(normalized)) { + continue; + } seen.add(normalized); ordered.push(normalized); } for (const channel of channels) { const normalized = channel.id.trim().toLowerCase(); - if (!normalized || seen.has(normalized)) continue; + if (!normalized || seen.has(normalized)) { + continue; + } seen.add(normalized); ordered.push(normalized); } @@ -880,14 +902,18 @@ function applyPluginSchemas(schema: ConfigSchema, plugins: PluginUiMetadata[]): const root = asSchemaObject(next); const pluginsNode = asSchemaObject(root?.properties?.plugins); const entriesNode = asSchemaObject(pluginsNode?.properties?.entries); - if (!entriesNode) return next; + if (!entriesNode) { + return next; + } const entryBase = asSchemaObject(entriesNode.additionalProperties); const entryProperties = entriesNode.properties ?? {}; entriesNode.properties = entryProperties; for (const plugin of plugins) { - if (!plugin.configSchema) continue; + if (!plugin.configSchema) { + continue; + } const entrySchema = entryBase ? cloneSchema(entryBase) : ({ type: "object" } as JsonSchemaObject); @@ -916,12 +942,16 @@ function applyChannelSchemas(schema: ConfigSchema, channels: ChannelUiMetadata[] const next = cloneSchema(schema); const root = asSchemaObject(next); const channelsNode = asSchemaObject(root?.properties?.channels); - if (!channelsNode) return next; + if (!channelsNode) { + return next; + } const channelProps = channelsNode.properties ?? {}; channelsNode.properties = channelProps; for (const channel of channels) { - if (!channel.configSchema) continue; + if (!channel.configSchema) { + continue; + } const existing = asSchemaObject(channelProps[channel.id]); const incoming = asSchemaObject(channel.configSchema); if (existing && incoming && isObjectSchema(existing) && isObjectSchema(incoming)) { @@ -939,7 +969,9 @@ let cachedBase: ConfigSchemaResponse | null = null; function stripChannelSchema(schema: ConfigSchema): ConfigSchema { const next = cloneSchema(schema); const root = asSchemaObject(next); - if (!root || !root.properties) return next; + if (!root || !root.properties) { + return next; + } const channelsNode = asSchemaObject(root.properties.channels); if (channelsNode) { channelsNode.properties = {}; @@ -950,7 +982,9 @@ function stripChannelSchema(schema: ConfigSchema): ConfigSchema { } function buildBaseConfigSchema(): ConfigSchemaResponse { - if (cachedBase) return cachedBase; + if (cachedBase) { + return cachedBase; + } const schema = OpenClawSchema.toJSONSchema({ target: "draft-07", unrepresentable: "any", @@ -974,7 +1008,9 @@ export function buildConfigSchema(params?: { const base = buildBaseConfigSchema(); const plugins = params?.plugins ?? []; const channels = params?.channels ?? []; - if (plugins.length === 0 && channels.length === 0) return base; + if (plugins.length === 0 && channels.length === 0) { + return base; + } const mergedHints = applySensitiveHints( applyHeartbeatTargetHints( applyChannelHints(applyPluginHints(base.uiHints, plugins), channels), diff --git a/src/config/sessions/group.ts b/src/config/sessions/group.ts index fb0e341041..d02726a004 100644 --- a/src/config/sessions/group.ts +++ b/src/config/sessions/group.ts @@ -6,7 +6,9 @@ const getGroupSurfaces = () => new Set([...listDeliverableMessageChannel function normalizeGroupLabel(raw?: string) { const trimmed = raw?.trim().toLowerCase() ?? ""; - if (!trimmed) return ""; + if (!trimmed) { + return ""; + } const dashed = trimmed.replace(/\s+/g, "-"); const cleaned = dashed.replace(/[^a-z0-9#@._+-]+/g, "-"); return cleaned.replace(/-{2,}/g, "-").replace(/^[-.]+|[-.]+$/g, ""); @@ -14,8 +16,12 @@ function normalizeGroupLabel(raw?: string) { function shortenGroupId(value?: string) { const trimmed = value?.trim() ?? ""; - if (!trimmed) return ""; - if (trimmed.length <= 14) return trimmed; + if (!trimmed) { + return ""; + } + if (trimmed.length <= 14) { + return trimmed; + } return `${trimmed.slice(0, 6)}...${trimmed.slice(-4)}`; } @@ -63,7 +69,9 @@ export function resolveGroupSessionKey(ctx: MsgContext): GroupKeyResolution | nu from.includes(":group:") || from.includes(":channel:") || isWhatsAppGroupId; - if (!looksLikeGroup) return null; + if (!looksLikeGroup) { + return null; + } const providerHint = ctx.Provider?.trim().toLowerCase(); @@ -74,7 +82,9 @@ export function resolveGroupSessionKey(ctx: MsgContext): GroupKeyResolution | nu const provider = headIsSurface ? head : (providerHint ?? (isWhatsAppGroupId ? "whatsapp" : undefined)); - if (!provider) return null; + if (!provider) { + return null; + } const second = parts[1]?.trim().toLowerCase(); const secondIsKind = second === "group" || second === "channel"; @@ -89,7 +99,9 @@ export function resolveGroupSessionKey(ctx: MsgContext): GroupKeyResolution | nu : parts.slice(1).join(":") : from; const finalId = id.trim().toLowerCase(); - if (!finalId) return null; + if (!finalId) { + return null; + } return { key: `${provider}:${kind}:${finalId}`, diff --git a/src/config/sessions/main-session.ts b/src/config/sessions/main-session.ts index bda6035946..b9e4ef1642 100644 --- a/src/config/sessions/main-session.ts +++ b/src/config/sessions/main-session.ts @@ -12,7 +12,9 @@ export function resolveMainSessionKey(cfg?: { session?: { scope?: SessionScope; mainKey?: string }; agents?: { list?: Array<{ id?: string; default?: boolean }> }; }): string { - if (cfg?.session?.scope === "global") return "global"; + if (cfg?.session?.scope === "global") { + return "global"; + } const agents = cfg?.agents?.list ?? []; const defaultAgentId = agents.find((agent) => agent?.default)?.id ?? agents[0]?.id ?? DEFAULT_AGENT_ID; @@ -40,7 +42,9 @@ export function resolveExplicitAgentSessionKey(params: { agentId?: string | null; }): string | undefined { const agentId = params.agentId?.trim(); - if (!agentId) return undefined; + if (!agentId) { + return undefined; + } return resolveAgentMainSessionKey({ cfg: params.cfg, agentId }); } @@ -50,7 +54,9 @@ export function canonicalizeMainSessionAlias(params: { sessionKey: string; }): string { const raw = params.sessionKey.trim(); - if (!raw) return raw; + if (!raw) { + return raw; + } const agentId = normalizeAgentId(params.agentId); const mainKey = normalizeMainKey(params.cfg?.session?.mainKey); @@ -63,7 +69,11 @@ export function canonicalizeMainSessionAlias(params: { const isMainAlias = raw === "main" || raw === mainKey || raw === agentMainSessionKey || raw === agentMainAliasKey; - if (params.cfg?.session?.scope === "global" && isMainAlias) return "global"; - if (isMainAlias) return agentMainSessionKey; + if (params.cfg?.session?.scope === "global" && isMainAlias) { + return "global"; + } + if (isMainAlias) { + return agentMainSessionKey; + } return raw; } diff --git a/src/config/sessions/metadata.ts b/src/config/sessions/metadata.ts index 1b70177d42..c438fd60f2 100644 --- a/src/config/sessions/metadata.ts +++ b/src/config/sessions/metadata.ts @@ -11,16 +11,34 @@ const mergeOrigin = ( existing: SessionOrigin | undefined, next: SessionOrigin | undefined, ): SessionOrigin | undefined => { - if (!existing && !next) return undefined; + if (!existing && !next) { + return undefined; + } const merged: SessionOrigin = existing ? { ...existing } : {}; - if (next?.label) merged.label = next.label; - if (next?.provider) merged.provider = next.provider; - if (next?.surface) merged.surface = next.surface; - if (next?.chatType) merged.chatType = next.chatType; - if (next?.from) merged.from = next.from; - if (next?.to) merged.to = next.to; - if (next?.accountId) merged.accountId = next.accountId; - if (next?.threadId != null && next.threadId !== "") merged.threadId = next.threadId; + if (next?.label) { + merged.label = next.label; + } + if (next?.provider) { + merged.provider = next.provider; + } + if (next?.surface) { + merged.surface = next.surface; + } + if (next?.chatType) { + merged.chatType = next.chatType; + } + if (next?.from) { + merged.from = next.from; + } + if (next?.to) { + merged.to = next.to; + } + if (next?.accountId) { + merged.accountId = next.accountId; + } + if (next?.threadId != null && next.threadId !== "") { + merged.threadId = next.threadId; + } return Object.keys(merged).length > 0 ? merged : undefined; }; @@ -40,20 +58,38 @@ export function deriveSessionOrigin(ctx: MsgContext): SessionOrigin | undefined const threadId = ctx.MessageThreadId ?? undefined; const origin: SessionOrigin = {}; - if (label) origin.label = label; - if (provider) origin.provider = provider; - if (surface) origin.surface = surface; - if (chatType) origin.chatType = chatType; - if (from) origin.from = from; - if (to) origin.to = to; - if (accountId) origin.accountId = accountId; - if (threadId != null && threadId !== "") origin.threadId = threadId; + if (label) { + origin.label = label; + } + if (provider) { + origin.provider = provider; + } + if (surface) { + origin.surface = surface; + } + if (chatType) { + origin.chatType = chatType; + } + if (from) { + origin.from = from; + } + if (to) { + origin.to = to; + } + if (accountId) { + origin.accountId = accountId; + } + if (threadId != null && threadId !== "") { + origin.threadId = threadId; + } return Object.keys(origin).length > 0 ? origin : undefined; } export function snapshotSessionOrigin(entry?: SessionEntry): SessionOrigin | undefined { - if (!entry?.origin) return undefined; + if (!entry?.origin) { + return undefined; + } return { ...entry.origin }; } @@ -64,7 +100,9 @@ export function deriveGroupSessionPatch(params: { groupResolution?: GroupKeyResolution | null; }): Partial | null { const resolution = params.groupResolution ?? resolveGroupSessionKey(params.ctx); - if (!resolution?.channel) return null; + if (!resolution?.channel) { + return null; + } const channel = resolution.channel; const subject = params.ctx.GroupSubject?.trim(); @@ -87,9 +125,15 @@ export function deriveGroupSessionPatch(params: { channel, groupId: resolution.id, }; - if (nextSubject) patch.subject = nextSubject; - if (nextGroupChannel) patch.groupChannel = nextGroupChannel; - if (space) patch.space = space; + if (nextSubject) { + patch.subject = nextSubject; + } + if (nextGroupChannel) { + patch.groupChannel = nextGroupChannel; + } + if (space) { + patch.space = space; + } const displayName = buildGroupDisplayName({ provider: channel, @@ -99,7 +143,9 @@ export function deriveGroupSessionPatch(params: { id: resolution.id, key: params.sessionKey, }); - if (displayName) patch.displayName = displayName; + if (displayName) { + patch.displayName = displayName; + } return patch; } @@ -112,11 +158,15 @@ export function deriveSessionMetaPatch(params: { }): Partial | null { const groupPatch = deriveGroupSessionPatch(params); const origin = deriveSessionOrigin(params.ctx); - if (!groupPatch && !origin) return null; + if (!groupPatch && !origin) { + return null; + } const patch: Partial = groupPatch ? { ...groupPatch } : {}; const mergedOrigin = mergeOrigin(params.existing?.origin, origin); - if (mergedOrigin) patch.origin = mergedOrigin; + if (mergedOrigin) { + patch.origin = mergedOrigin; + } return Object.keys(patch).length > 0 ? patch : null; } diff --git a/src/config/sessions/paths.ts b/src/config/sessions/paths.ts index 63d2f68348..7987631971 100644 --- a/src/config/sessions/paths.ts +++ b/src/config/sessions/paths.ts @@ -60,7 +60,9 @@ export function resolveSessionFilePath( export function resolveStorePath(store?: string, opts?: { agentId?: string }) { const agentId = normalizeAgentId(opts?.agentId ?? DEFAULT_AGENT_ID); - if (!store) return resolveDefaultSessionStorePath(agentId); + if (!store) { + return resolveDefaultSessionStorePath(agentId); + } if (store.includes("{agentId}")) { const expanded = store.replaceAll("{agentId}", agentId); if (expanded.startsWith("~")) { @@ -68,6 +70,8 @@ export function resolveStorePath(store?: string, opts?: { agentId?: string }) { } return path.resolve(expanded); } - if (store.startsWith("~")) return path.resolve(store.replace(/^~(?=$|[\\/])/, os.homedir())); + if (store.startsWith("~")) { + return path.resolve(store.replace(/^~(?=$|[\\/])/, os.homedir())); + } return path.resolve(store); } diff --git a/src/config/sessions/reset.ts b/src/config/sessions/reset.ts index 45d54ad4f4..5ae0ca680a 100644 --- a/src/config/sessions/reset.ts +++ b/src/config/sessions/reset.ts @@ -25,7 +25,9 @@ const GROUP_SESSION_MARKERS = [":group:", ":channel:"]; export function isThreadSessionKey(sessionKey?: string | null): boolean { const normalized = (sessionKey ?? "").toLowerCase(); - if (!normalized) return false; + if (!normalized) { + return false; + } return THREAD_SESSION_MARKERS.some((marker) => normalized.includes(marker)); } @@ -34,10 +36,16 @@ export function resolveSessionResetType(params: { isGroup?: boolean; isThread?: boolean; }): SessionResetType { - if (params.isThread || isThreadSessionKey(params.sessionKey)) return "thread"; - if (params.isGroup) return "group"; + if (params.isThread || isThreadSessionKey(params.sessionKey)) { + return "thread"; + } + if (params.isGroup) { + return "group"; + } const normalized = (params.sessionKey ?? "").toLowerCase(); - if (GROUP_SESSION_MARKERS.some((marker) => normalized.includes(marker))) return "group"; + if (GROUP_SESSION_MARKERS.some((marker) => normalized.includes(marker))) { + return "group"; + } return "dm"; } @@ -48,10 +56,18 @@ export function resolveThreadFlag(params: { threadStarterBody?: string | null; parentSessionKey?: string | null; }): boolean { - if (params.messageThreadId != null) return true; - if (params.threadLabel?.trim()) return true; - if (params.threadStarterBody?.trim()) return true; - if (params.parentSessionKey?.trim()) return true; + if (params.messageThreadId != null) { + return true; + } + if (params.threadLabel?.trim()) { + return true; + } + if (params.threadStarterBody?.trim()) { + return true; + } + if (params.parentSessionKey?.trim()) { + return true; + } return isThreadSessionKey(params.sessionKey); } @@ -102,11 +118,15 @@ export function resolveChannelResetConfig(params: { channel?: string | null; }): SessionResetConfig | undefined { const resetByChannel = params.sessionCfg?.resetByChannel; - if (!resetByChannel) return undefined; + if (!resetByChannel) { + return undefined; + } const normalized = normalizeMessageChannel(params.channel); const fallback = params.channel?.trim().toLowerCase(); const key = normalized ?? fallback; - if (!key) return undefined; + if (!key) { + return undefined; + } return resetByChannel[key] ?? resetByChannel[key.toLowerCase()]; } @@ -133,10 +153,18 @@ export function evaluateSessionFreshness(params: { } function normalizeResetAtHour(value: number | undefined): number { - if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_RESET_AT_HOUR; + if (typeof value !== "number" || !Number.isFinite(value)) { + return DEFAULT_RESET_AT_HOUR; + } const normalized = Math.floor(value); - if (!Number.isFinite(normalized)) return DEFAULT_RESET_AT_HOUR; - if (normalized < 0) return 0; - if (normalized > 23) return 23; + if (!Number.isFinite(normalized)) { + return DEFAULT_RESET_AT_HOUR; + } + if (normalized < 0) { + return 0; + } + if (normalized > 23) { + return 23; + } return normalized; } diff --git a/src/config/sessions/session-key.ts b/src/config/sessions/session-key.ts index 7cec6f646f..3244f5c7c6 100644 --- a/src/config/sessions/session-key.ts +++ b/src/config/sessions/session-key.ts @@ -10,9 +10,13 @@ import type { SessionScope } from "./types.js"; // Decide which session bucket to use (per-sender vs global). export function deriveSessionKey(scope: SessionScope, ctx: MsgContext) { - if (scope === "global") return "global"; + if (scope === "global") { + return "global"; + } const resolvedGroup = resolveGroupSessionKey(ctx); - if (resolvedGroup) return resolvedGroup.key; + if (resolvedGroup) { + return resolvedGroup.key; + } const from = ctx.From ? normalizeE164(ctx.From) : ""; return from || "unknown"; } @@ -23,15 +27,21 @@ export function deriveSessionKey(scope: SessionScope, ctx: MsgContext) { */ export function resolveSessionKey(scope: SessionScope, ctx: MsgContext, mainKey?: string) { const explicit = ctx.SessionKey?.trim(); - if (explicit) return explicit.toLowerCase(); + if (explicit) { + return explicit.toLowerCase(); + } const raw = deriveSessionKey(scope, ctx); - if (scope === "global") return raw; + if (scope === "global") { + return raw; + } const canonicalMainKey = normalizeMainKey(mainKey); const canonical = buildAgentMainSessionKey({ agentId: DEFAULT_AGENT_ID, mainKey: canonicalMainKey, }); const isGroup = raw.includes(":group:") || raw.includes(":channel:"); - if (!isGroup) return canonical; + if (!isGroup) { + return canonical; + } return `agent:${DEFAULT_AGENT_ID}:${raw}`; } diff --git a/src/config/sessions/store.ts b/src/config/sessions/store.ts index cd438bc053..8f1833257e 100644 --- a/src/config/sessions/store.ts +++ b/src/config/sessions/store.ts @@ -74,7 +74,9 @@ function normalizeSessionEntryDelivery(entry: SessionEntry): SessionEntry { entry.lastTo === normalized.lastTo && entry.lastAccountId === normalized.lastAccountId && entry.lastThreadId === normalized.lastThreadId; - if (sameDelivery && sameLast) return entry; + if (sameDelivery && sameLast) { + return entry; + } return { ...entry, deliveryContext: nextDelivery, @@ -87,7 +89,9 @@ function normalizeSessionEntryDelivery(entry: SessionEntry): SessionEntry { function normalizeSessionStore(store: Record): void { for (const [key, entry] of Object.entries(store)) { - if (!entry) continue; + if (!entry) { + continue; + } const normalized = normalizeSessionEntryDelivery(entry); if (normalized !== entry) { store[key] = normalized; @@ -136,7 +140,9 @@ export function loadSessionStore( // Best-effort migration: message provider → channel naming. for (const entry of Object.values(store)) { - if (!entry || typeof entry !== "object") continue; + if (!entry || typeof entry !== "object") { + continue; + } const rec = entry as unknown as Record; if (typeof rec.channel !== "string" && typeof rec.provider === "string") { rec.channel = rec.provider; @@ -203,7 +209,9 @@ async function saveSessionStoreUnlocked( err && typeof err === "object" && "code" in err ? String((err as { code?: unknown }).code) : null; - if (code === "ENOENT") return; + if (code === "ENOENT") { + return; + } throw err; } return; @@ -233,7 +241,9 @@ async function saveSessionStoreUnlocked( err2 && typeof err2 === "object" && "code" in err2 ? String((err2 as { code?: unknown }).code) : null; - if (code2 === "ENOENT") return; + if (code2 === "ENOENT") { + return; + } throw err2; } return; @@ -313,7 +323,9 @@ async function withSessionStoreLock( await new Promise((r) => setTimeout(r, pollIntervalMs)); continue; } - if (code !== "EEXIST") throw err; + if (code !== "EEXIST") { + throw err; + } const now = Date.now(); if (now - startedAt > timeoutMs) { @@ -352,9 +364,13 @@ export async function updateSessionStoreEntry(params: { return await withSessionStoreLock(storePath, async () => { const store = loadSessionStore(storePath); const existing = store[sessionKey]; - if (!existing) return null; + if (!existing) { + return null; + } const patch = await update(existing); - if (!patch) return existing; + if (!patch) { + return existing; + } const next = mergeSessionEntry(existing, patch); store[sessionKey] = next; await saveSessionStoreUnlocked(storePath, store); @@ -379,8 +395,12 @@ export async function recordSessionMetaFromInbound(params: { existing, groupResolution: params.groupResolution, }); - if (!patch) return existing ?? null; - if (!existing && !createIfMissing) return null; + if (!patch) { + return existing ?? null; + } + if (!existing && !createIfMissing) { + return null; + } const next = mergeSessionEntry(existing, patch); store[sessionKey] = next; return next; diff --git a/src/config/sessions/transcript.ts b/src/config/sessions/transcript.ts index 4107875b73..0f67a7f2c9 100644 --- a/src/config/sessions/transcript.ts +++ b/src/config/sessions/transcript.ts @@ -15,12 +15,16 @@ function stripQuery(value: string): string { function extractFileNameFromMediaUrl(value: string): string | null { const trimmed = value.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const cleaned = stripQuery(trimmed); try { const parsed = new URL(cleaned); const base = path.basename(parsed.pathname); - if (!base) return null; + if (!base) { + return null; + } try { return decodeURIComponent(base); } catch { @@ -28,7 +32,9 @@ function extractFileNameFromMediaUrl(value: string): string | null { } } catch { const base = path.basename(cleaned); - if (!base || base === "/" || base === ".") return null; + if (!base || base === "/" || base === ".") { + return null; + } return base; } } @@ -42,7 +48,9 @@ export function resolveMirroredTranscriptText(params: { const names = mediaUrls .map((url) => extractFileNameFromMediaUrl(url)) .filter((name): name is string => Boolean(name && name.trim())); - if (names.length > 0) return names.join(", "); + if (names.length > 0) { + return names.join(", "); + } return "media"; } @@ -55,7 +63,9 @@ async function ensureSessionHeader(params: { sessionFile: string; sessionId: string; }): Promise { - if (fs.existsSync(params.sessionFile)) return; + if (fs.existsSync(params.sessionFile)) { + return; + } await fs.promises.mkdir(path.dirname(params.sessionFile), { recursive: true }); const header = { type: "session", @@ -76,18 +86,24 @@ export async function appendAssistantMessageToSessionTranscript(params: { storePath?: string; }): Promise<{ ok: true; sessionFile: string } | { ok: false; reason: string }> { const sessionKey = params.sessionKey.trim(); - if (!sessionKey) return { ok: false, reason: "missing sessionKey" }; + if (!sessionKey) { + return { ok: false, reason: "missing sessionKey" }; + } const mirrorText = resolveMirroredTranscriptText({ text: params.text, mediaUrls: params.mediaUrls, }); - if (!mirrorText) return { ok: false, reason: "empty text" }; + if (!mirrorText) { + return { ok: false, reason: "empty text" }; + } const storePath = params.storePath ?? resolveDefaultSessionStorePath(params.agentId); const store = loadSessionStore(storePath, { skipCache: true }); const entry = store[sessionKey] as SessionEntry | undefined; - if (!entry?.sessionId) return { ok: false, reason: `unknown sessionKey: ${sessionKey}` }; + if (!entry?.sessionId) { + return { ok: false, reason: `unknown sessionKey: ${sessionKey}` }; + } const sessionFile = entry.sessionFile?.trim() || resolveSessionTranscriptPath(entry.sessionId, params.agentId); diff --git a/src/config/sessions/types.ts b/src/config/sessions/types.ts index 48ce428c11..5ec81a8ab4 100644 --- a/src/config/sessions/types.ts +++ b/src/config/sessions/types.ts @@ -102,7 +102,9 @@ export function mergeSessionEntry( ): SessionEntry { const sessionId = patch.sessionId ?? existing?.sessionId ?? crypto.randomUUID(); const updatedAt = Math.max(existing?.updatedAt ?? 0, patch.updatedAt ?? 0, Date.now()); - if (!existing) return { ...patch, sessionId, updatedAt }; + if (!existing) { + return { ...patch, sessionId, updatedAt }; + } return { ...existing, ...patch, sessionId, updatedAt }; } diff --git a/src/config/talk.ts b/src/config/talk.ts index b01e02942e..f7856dc679 100644 --- a/src/config/talk.ts +++ b/src/config/talk.ts @@ -18,14 +18,18 @@ export function readTalkApiKeyFromProfile(deps: TalkApiKeyDeps = {}): string | n pathImpl.join(home, name), ); for (const candidate of candidates) { - if (!fsImpl.existsSync(candidate)) continue; + if (!fsImpl.existsSync(candidate)) { + continue; + } try { const text = fsImpl.readFileSync(candidate, "utf-8"); const match = text.match( /(?:^|\n)\s*(?:export\s+)?ELEVENLABS_API_KEY\s*=\s*["']?([^\n"']+)["']?/, ); const value = match?.[1]?.trim(); - if (value) return value; + if (value) { + return value; + } } catch { // Ignore profile read errors. } @@ -38,6 +42,8 @@ export function resolveTalkApiKey( deps: TalkApiKeyDeps = {}, ): string | null { const envValue = (env.ELEVENLABS_API_KEY ?? "").trim(); - if (envValue) return envValue; + if (envValue) { + return envValue; + } return readTalkApiKeyFromProfile(deps); } diff --git a/src/config/telegram-custom-commands.ts b/src/config/telegram-custom-commands.ts index 381cc1e55c..579707b591 100644 --- a/src/config/telegram-custom-commands.ts +++ b/src/config/telegram-custom-commands.ts @@ -13,7 +13,9 @@ export type TelegramCustomCommandIssue = { export function normalizeTelegramCommandName(value: string): string { const trimmed = value.trim(); - if (!trimmed) return ""; + if (!trimmed) { + return ""; + } const withoutSlash = trimmed.startsWith("/") ? trimmed.slice(1) : trimmed; return withoutSlash.trim().toLowerCase(); } diff --git a/src/config/validation.ts b/src/config/validation.ts index b9758b65a1..2a5946f729 100644 --- a/src/config/validation.ts +++ b/src/config/validation.ts @@ -24,22 +24,36 @@ function isWorkspaceAvatarPath(value: string, workspaceDir: string): boolean { const workspaceRoot = path.resolve(workspaceDir); const resolved = path.resolve(workspaceRoot, value); const relative = path.relative(workspaceRoot, resolved); - if (relative === "") return true; - if (relative.startsWith("..")) return false; + if (relative === "") { + return true; + } + if (relative.startsWith("..")) { + return false; + } return !path.isAbsolute(relative); } function validateIdentityAvatar(config: OpenClawConfig): ConfigValidationIssue[] { const agents = config.agents?.list; - if (!Array.isArray(agents) || agents.length === 0) return []; + if (!Array.isArray(agents) || agents.length === 0) { + return []; + } const issues: ConfigValidationIssue[] = []; for (const [index, entry] of agents.entries()) { - if (!entry || typeof entry !== "object") continue; + if (!entry || typeof entry !== "object") { + continue; + } const avatarRaw = entry.identity?.avatar; - if (typeof avatarRaw !== "string") continue; + if (typeof avatarRaw !== "string") { + continue; + } const avatar = avatarRaw.trim(); - if (!avatar) continue; - if (AVATAR_DATA_RE.test(avatar) || AVATAR_HTTP_RE.test(avatar)) continue; + if (!avatar) { + continue; + } + if (AVATAR_DATA_RE.test(avatar) || AVATAR_HTTP_RE.test(avatar)) { + continue; + } if (avatar.startsWith("~")) { issues.push({ path: `agents.list.${index}.identity.avatar`, @@ -178,7 +192,9 @@ export function validateConfigObjectWithPlugins(raw: unknown): const allow = pluginsConfig?.allow ?? []; for (const pluginId of allow) { - if (typeof pluginId !== "string" || !pluginId.trim()) continue; + if (typeof pluginId !== "string" || !pluginId.trim()) { + continue; + } if (!knownIds.has(pluginId)) { issues.push({ path: "plugins.allow", @@ -189,7 +205,9 @@ export function validateConfigObjectWithPlugins(raw: unknown): const deny = pluginsConfig?.deny ?? []; for (const pluginId of deny) { - if (typeof pluginId !== "string" || !pluginId.trim()) continue; + if (typeof pluginId !== "string" || !pluginId.trim()) { + continue; + } if (!knownIds.has(pluginId)) { issues.push({ path: "plugins.deny", @@ -216,7 +234,9 @@ export function validateConfigObjectWithPlugins(raw: unknown): if (config.channels && isRecord(config.channels)) { for (const key of Object.keys(config.channels)) { const trimmed = key.trim(); - if (!trimmed) continue; + if (!trimmed) { + continue; + } if (!allowedChannels.has(trimmed)) { issues.push({ path: `channels.${trimmed}`, @@ -233,21 +253,31 @@ export function validateConfigObjectWithPlugins(raw: unknown): for (const record of registry.plugins) { for (const channelId of record.channels) { const trimmed = channelId.trim(); - if (trimmed) heartbeatChannelIds.add(trimmed.toLowerCase()); + if (trimmed) { + heartbeatChannelIds.add(trimmed.toLowerCase()); + } } } const validateHeartbeatTarget = (target: string | undefined, path: string) => { - if (typeof target !== "string") return; + if (typeof target !== "string") { + return; + } const trimmed = target.trim(); if (!trimmed) { issues.push({ path, message: "heartbeat target must not be empty" }); return; } const normalized = trimmed.toLowerCase(); - if (normalized === "last" || normalized === "none") return; - if (normalizeChatChannelId(trimmed)) return; - if (heartbeatChannelIds.has(normalized)) return; + if (normalized === "last" || normalized === "none") { + return; + } + if (normalizeChatChannelId(trimmed)) { + return; + } + if (heartbeatChannelIds.has(normalized)) { + return; + } issues.push({ path, message: `unknown heartbeat target: ${target}` }); }; diff --git a/src/config/version.ts b/src/config/version.ts index b57be78a96..bfa3be90be 100644 --- a/src/config/version.ts +++ b/src/config/version.ts @@ -8,9 +8,13 @@ export type OpenClawVersion = { const VERSION_RE = /^v?(\d+)\.(\d+)\.(\d+)(?:-(\d+))?/; export function parseOpenClawVersion(raw: string | null | undefined): OpenClawVersion | null { - if (!raw) return null; + if (!raw) { + return null; + } const match = raw.trim().match(VERSION_RE); - if (!match) return null; + if (!match) { + return null; + } const [, major, minor, patch, revision] = match; return { major: Number.parseInt(major, 10), @@ -26,10 +30,20 @@ export function compareOpenClawVersions( ): number | null { const parsedA = parseOpenClawVersion(a); const parsedB = parseOpenClawVersion(b); - if (!parsedA || !parsedB) return null; - if (parsedA.major !== parsedB.major) return parsedA.major < parsedB.major ? -1 : 1; - if (parsedA.minor !== parsedB.minor) return parsedA.minor < parsedB.minor ? -1 : 1; - if (parsedA.patch !== parsedB.patch) return parsedA.patch < parsedB.patch ? -1 : 1; - if (parsedA.revision !== parsedB.revision) return parsedA.revision < parsedB.revision ? -1 : 1; + if (!parsedA || !parsedB) { + return null; + } + if (parsedA.major !== parsedB.major) { + return parsedA.major < parsedB.major ? -1 : 1; + } + if (parsedA.minor !== parsedB.minor) { + return parsedA.minor < parsedB.minor ? -1 : 1; + } + if (parsedA.patch !== parsedB.patch) { + return parsedA.patch < parsedB.patch ? -1 : 1; + } + if (parsedA.revision !== parsedB.revision) { + return parsedA.revision < parsedB.revision ? -1 : 1; + } return 0; } diff --git a/src/config/zod-schema.agent-runtime.ts b/src/config/zod-schema.agent-runtime.ts index 7e95c3538b..228bfc70ac 100644 --- a/src/config/zod-schema.agent-runtime.ts +++ b/src/config/zod-schema.agent-runtime.ts @@ -30,7 +30,9 @@ export const HeartbeatSchema = z }) .strict() .superRefine((val, ctx) => { - if (!val.every) return; + if (!val.every) { + return; + } try { parseDurationMs(val.every, { defaultUnit: "m" }); } catch { @@ -42,10 +44,14 @@ export const HeartbeatSchema = z } const active = val.activeHours; - if (!active) return; + if (!active) { + return; + } const timePattern = /^([01]\d|2[0-3]|24):([0-5]\d)$/; const validateTime = (raw: string | undefined, opts: { allow24: boolean }, path: string) => { - if (!raw) return; + if (!raw) { + return; + } if (!timePattern.test(raw)) { ctx.addIssue({ code: z.ZodIssueCode.custom, diff --git a/src/config/zod-schema.core.ts b/src/config/zod-schema.core.ts index 4a8c80bccc..8f2fcf6f11 100644 --- a/src/config/zod-schema.core.ts +++ b/src/config/zod-schema.core.ts @@ -279,9 +279,13 @@ export const requireOpenAllowFrom = (params: { path: Array; message: string; }) => { - if (params.policy !== "open") return; + if (params.policy !== "open") { + return; + } const allow = normalizeAllowFrom(params.allowFrom); - if (allow.includes("*")) return; + if (allow.includes("*")) { + return; + } params.ctx.addIssue({ code: z.ZodIssueCode.custom, path: params.path, diff --git a/src/config/zod-schema.providers-core.ts b/src/config/zod-schema.providers-core.ts index ed7dda22a1..a1ca83f7de 100644 --- a/src/config/zod-schema.providers-core.ts +++ b/src/config/zod-schema.providers-core.ts @@ -69,7 +69,9 @@ const validateTelegramCustomCommands = ( value: { customCommands?: Array<{ command?: string; description?: string }> }, ctx: z.RefinementCtx, ) => { - if (!value.customCommands || value.customCommands.length === 0) return; + if (!value.customCommands || value.customCommands.length === 0) { + return; + } const { issues } = resolveTelegramCustomCommands({ commands: value.customCommands, checkReserved: false, @@ -476,12 +478,20 @@ export const SlackConfigSchema = SlackAccountSchema.extend({ path: ["signingSecret"], }); } - if (!value.accounts) return; + if (!value.accounts) { + return; + } for (const [accountId, account] of Object.entries(value.accounts)) { - if (!account) continue; - if (account.enabled === false) continue; + if (!account) { + continue; + } + if (account.enabled === false) { + continue; + } const accountMode = account.mode ?? baseMode; - if (accountMode !== "http") continue; + if (accountMode !== "http") { + continue; + } const accountSecret = account.signingSecret ?? value.signingSecret; if (!accountSecret) { ctx.addIssue({ diff --git a/src/config/zod-schema.providers-whatsapp.ts b/src/config/zod-schema.providers-whatsapp.ts index f9f6c6d26e..108a731ee4 100644 --- a/src/config/zod-schema.providers-whatsapp.ts +++ b/src/config/zod-schema.providers-whatsapp.ts @@ -62,9 +62,13 @@ export const WhatsAppAccountSchema = z }) .strict() .superRefine((value, ctx) => { - if (value.dmPolicy !== "open") return; + if (value.dmPolicy !== "open") { + return; + } const allow = (value.allowFrom ?? []).map((v) => String(v).trim()).filter(Boolean); - if (allow.includes("*")) return; + if (allow.includes("*")) { + return; + } ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["allowFrom"], @@ -127,9 +131,13 @@ export const WhatsAppConfigSchema = z }) .strict() .superRefine((value, ctx) => { - if (value.dmPolicy !== "open") return; + if (value.dmPolicy !== "open") { + return; + } const allow = (value.allowFrom ?? []).map((v) => String(v).trim()).filter(Boolean); - if (allow.includes("*")) return; + if (allow.includes("*")) { + return; + } ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["allowFrom"], diff --git a/src/config/zod-schema.ts b/src/config/zod-schema.ts index 961ba8ecb0..044dbf54d3 100644 --- a/src/config/zod-schema.ts +++ b/src/config/zod-schema.ts @@ -532,15 +532,23 @@ export const OpenClawSchema = z .strict() .superRefine((cfg, ctx) => { const agents = cfg.agents?.list ?? []; - if (agents.length === 0) return; + if (agents.length === 0) { + return; + } const agentIds = new Set(agents.map((agent) => agent.id)); const broadcast = cfg.broadcast; - if (!broadcast) return; + if (!broadcast) { + return; + } for (const [peerId, ids] of Object.entries(broadcast)) { - if (peerId === "strategy") continue; - if (!Array.isArray(ids)) continue; + if (peerId === "strategy") { + continue; + } + if (!Array.isArray(ids)) { + continue; + } for (let idx = 0; idx < ids.length; idx += 1) { const agentId = ids[idx]; if (!agentIds.has(agentId)) { diff --git a/src/cron/isolated-agent/helpers.ts b/src/cron/isolated-agent/helpers.ts index cd257618d5..ddc72d6456 100644 --- a/src/cron/isolated-agent/helpers.ts +++ b/src/cron/isolated-agent/helpers.ts @@ -12,7 +12,9 @@ type DeliveryPayload = { export function pickSummaryFromOutput(text: string | undefined) { const clean = (text ?? "").trim(); - if (!clean) return undefined; + if (!clean) { + return undefined; + } const limit = 2000; return clean.length > limit ? `${truncateUtf16Safe(clean, limit)}…` : clean; } @@ -20,7 +22,9 @@ export function pickSummaryFromOutput(text: string | undefined) { export function pickSummaryFromPayloads(payloads: Array<{ text?: string | undefined }>) { for (let i = payloads.length - 1; i >= 0; i--) { const summary = pickSummaryFromOutput(payloads[i]?.text); - if (summary) return summary; + if (summary) { + return summary; + } } return undefined; } @@ -28,7 +32,9 @@ export function pickSummaryFromPayloads(payloads: Array<{ text?: string | undefi export function pickLastNonEmptyTextFromPayloads(payloads: Array<{ text?: string | undefined }>) { for (let i = payloads.length - 1; i >= 0; i--) { const clean = (payloads[i]?.text ?? "").trim(); - if (clean) return clean; + if (clean) { + return clean; + } } return undefined; } @@ -38,11 +44,15 @@ export function pickLastNonEmptyTextFromPayloads(payloads: Array<{ text?: string * Returns true if delivery should be skipped because there's no real content. */ export function isHeartbeatOnlyResponse(payloads: DeliveryPayload[], ackMaxChars: number) { - if (payloads.length === 0) return true; + if (payloads.length === 0) { + return true; + } return payloads.every((payload) => { // If there's media, we should deliver regardless of text content. const hasMedia = (payload.mediaUrls?.length ?? 0) > 0 || Boolean(payload.mediaUrl); - if (hasMedia) return false; + if (hasMedia) { + return false; + } // Use heartbeat mode to check if text is just HEARTBEAT_OK or short ack. const result = stripHeartbeatToken(payload.text, { mode: "heartbeat", diff --git a/src/cron/isolated-agent/run.ts b/src/cron/isolated-agent/run.ts index 82864add41..88bee00da6 100644 --- a/src/cron/isolated-agent/run.ts +++ b/src/cron/isolated-agent/run.ts @@ -67,10 +67,14 @@ function matchesMessagingToolDeliveryTarget( target: MessagingToolSend, delivery: { channel: string; to?: string; accountId?: string }, ): boolean { - if (!delivery.to || !target.to) return false; + if (!delivery.to || !target.to) { + return false; + } const channel = delivery.channel.trim().toLowerCase(); const provider = target.provider?.trim().toLowerCase(); - if (provider && provider !== "message" && provider !== channel) return false; + if (provider && provider !== "message" && provider !== channel) { + return false; + } if (target.accountId && delivery.accountId && target.accountId !== delivery.accountId) { return false; } diff --git a/src/cron/normalize.ts b/src/cron/normalize.ts index 91de138112..427ba02831 100644 --- a/src/cron/normalize.ts +++ b/src/cron/normalize.ts @@ -34,17 +34,22 @@ function coerceSchedule(schedule: UnknownRecord) { typeof schedule.atMs === "number" || typeof schedule.at === "string" || typeof schedule.atMs === "string" - ) + ) { next.kind = "at"; - else if (typeof schedule.everyMs === "number") next.kind = "every"; - else if (typeof schedule.expr === "string") next.kind = "cron"; + } else if (typeof schedule.everyMs === "number") { + next.kind = "every"; + } else if (typeof schedule.expr === "string") { + next.kind = "cron"; + } } if (typeof schedule.atMs !== "number" && parsedAtMs !== null) { next.atMs = parsedAtMs; } - if ("at" in next) delete next.at; + if ("at" in next) { + delete next.at; + } return next; } @@ -57,8 +62,12 @@ function coercePayload(payload: UnknownRecord) { } function unwrapJob(raw: UnknownRecord) { - if (isRecord(raw.data)) return raw.data; - if (isRecord(raw.job)) return raw.job; + if (isRecord(raw.data)) { + return raw.data; + } + if (isRecord(raw.job)) { + return raw.job; + } return raw; } @@ -66,7 +75,9 @@ export function normalizeCronJobInput( raw: unknown, options: NormalizeOptions = DEFAULT_OPTIONS, ): UnknownRecord | null { - if (!isRecord(raw)) return null; + if (!isRecord(raw)) { + return null; + } const base = unwrapJob(raw); const next: UnknownRecord = { ...base }; @@ -76,8 +87,11 @@ export function normalizeCronJobInput( next.agentId = null; } else if (typeof agentId === "string") { const trimmed = agentId.trim(); - if (trimmed) next.agentId = sanitizeAgentId(trimmed); - else delete next.agentId; + if (trimmed) { + next.agentId = sanitizeAgentId(trimmed); + } else { + delete next.agentId; + } } } @@ -87,8 +101,12 @@ export function normalizeCronJobInput( next.enabled = enabled; } else if (typeof enabled === "string") { const trimmed = enabled.trim().toLowerCase(); - if (trimmed === "true") next.enabled = true; - if (trimmed === "false") next.enabled = false; + if (trimmed === "true") { + next.enabled = true; + } + if (trimmed === "false") { + next.enabled = false; + } } } @@ -101,11 +119,17 @@ export function normalizeCronJobInput( } if (options.applyDefaults) { - if (!next.wakeMode) next.wakeMode = "next-heartbeat"; + if (!next.wakeMode) { + next.wakeMode = "next-heartbeat"; + } if (!next.sessionTarget && isRecord(next.payload)) { const kind = typeof next.payload.kind === "string" ? next.payload.kind : ""; - if (kind === "systemEvent") next.sessionTarget = "main"; - if (kind === "agentTurn") next.sessionTarget = "isolated"; + if (kind === "systemEvent") { + next.sessionTarget = "main"; + } + if (kind === "agentTurn") { + next.sessionTarget = "isolated"; + } } } diff --git a/src/cron/parse.ts b/src/cron/parse.ts index e42dd775e3..3954969400 100644 --- a/src/cron/parse.ts +++ b/src/cron/parse.ts @@ -3,18 +3,28 @@ const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/; const ISO_DATE_TIME_RE = /^\d{4}-\d{2}-\d{2}T/; function normalizeUtcIso(raw: string) { - if (ISO_TZ_RE.test(raw)) return raw; - if (ISO_DATE_RE.test(raw)) return `${raw}T00:00:00Z`; - if (ISO_DATE_TIME_RE.test(raw)) return `${raw}Z`; + if (ISO_TZ_RE.test(raw)) { + return raw; + } + if (ISO_DATE_RE.test(raw)) { + return `${raw}T00:00:00Z`; + } + if (ISO_DATE_TIME_RE.test(raw)) { + return `${raw}Z`; + } return raw; } export function parseAbsoluteTimeMs(input: string): number | null { const raw = input.trim(); - if (!raw) return null; + if (!raw) { + return null; + } if (/^\d+$/.test(raw)) { const n = Number(raw); - if (Number.isFinite(n) && n > 0) return Math.floor(n); + if (Number.isFinite(n) && n > 0) { + return Math.floor(n); + } } const parsed = Date.parse(normalizeUtcIso(raw)); return Number.isFinite(parsed) ? parsed : null; diff --git a/src/cron/payload-migration.ts b/src/cron/payload-migration.ts index 05bf02cb09..318925a3f9 100644 --- a/src/cron/payload-migration.ts +++ b/src/cron/payload-migration.ts @@ -1,7 +1,9 @@ type UnknownRecord = Record; function readString(value: unknown): string | undefined { - if (typeof value !== "string") return undefined; + if (typeof value !== "string") { + return undefined; + } return value; } diff --git a/src/cron/run-log.ts b/src/cron/run-log.ts index 6949ba941a..744b023e53 100644 --- a/src/cron/run-log.ts +++ b/src/cron/run-log.ts @@ -23,7 +23,9 @@ const writesByPath = new Map>(); async function pruneIfNeeded(filePath: string, opts: { maxBytes: number; keepLines: number }) { const stat = await fs.stat(filePath).catch(() => null); - if (!stat || stat.size <= opts.maxBytes) return; + if (!stat || stat.size <= opts.maxBytes) { + return; + } const raw = await fs.readFile(filePath, "utf-8").catch(() => ""); const lines = raw @@ -64,19 +66,33 @@ export async function readCronRunLogEntries( const limit = Math.max(1, Math.min(5000, Math.floor(opts?.limit ?? 200))); const jobId = opts?.jobId?.trim() || undefined; const raw = await fs.readFile(path.resolve(filePath), "utf-8").catch(() => ""); - if (!raw.trim()) return []; + if (!raw.trim()) { + return []; + } const parsed: CronRunLogEntry[] = []; const lines = raw.split("\n"); for (let i = lines.length - 1; i >= 0 && parsed.length < limit; i--) { const line = lines[i]?.trim(); - if (!line) continue; + if (!line) { + continue; + } try { const obj = JSON.parse(line) as Partial | null; - if (!obj || typeof obj !== "object") continue; - if (obj.action !== "finished") continue; - if (typeof obj.jobId !== "string" || obj.jobId.trim().length === 0) continue; - if (typeof obj.ts !== "number" || !Number.isFinite(obj.ts)) continue; - if (jobId && obj.jobId !== jobId) continue; + if (!obj || typeof obj !== "object") { + continue; + } + if (obj.action !== "finished") { + continue; + } + if (typeof obj.jobId !== "string" || obj.jobId.trim().length === 0) { + continue; + } + if (typeof obj.ts !== "number" || !Number.isFinite(obj.ts)) { + continue; + } + if (jobId && obj.jobId !== jobId) { + continue; + } parsed.push(obj as CronRunLogEntry); } catch { // ignore invalid lines diff --git a/src/cron/schedule.ts b/src/cron/schedule.ts index 5d70d55c5e..814ba751c2 100644 --- a/src/cron/schedule.ts +++ b/src/cron/schedule.ts @@ -9,14 +9,18 @@ export function computeNextRunAtMs(schedule: CronSchedule, nowMs: number): numbe if (schedule.kind === "every") { const everyMs = Math.max(1, Math.floor(schedule.everyMs)); const anchor = Math.max(0, Math.floor(schedule.anchorMs ?? nowMs)); - if (nowMs < anchor) return anchor; + if (nowMs < anchor) { + return anchor; + } const elapsed = nowMs - anchor; const steps = Math.max(1, Math.floor((elapsed + everyMs - 1) / everyMs)); return anchor + steps * everyMs; } const expr = schedule.expr.trim(); - if (!expr) return undefined; + if (!expr) { + return undefined; + } const cron = new Cron(expr, { timezone: schedule.tz?.trim() || undefined, catch: false, diff --git a/src/cron/service.runs-one-shot-main-job-disables-it.test.ts b/src/cron/service.runs-one-shot-main-job-disables-it.test.ts index 9e6b83ca57..44aa7b682f 100644 --- a/src/cron/service.runs-one-shot-main-job-disables-it.test.ts +++ b/src/cron/service.runs-one-shot-main-job-disables-it.test.ts @@ -163,7 +163,9 @@ describe("CronService", () => { const runPromise = cron.run(job.id, "force"); for (let i = 0; i < 10; i++) { - if (runHeartbeatOnce.mock.calls.length > 0) break; + if (runHeartbeatOnce.mock.calls.length > 0) { + break; + } // Let the locked() chain progress. await Promise.resolve(); } diff --git a/src/cron/service/jobs.ts b/src/cron/service/jobs.ts index eb3fe34694..74e23a4128 100644 --- a/src/cron/service/jobs.ts +++ b/src/cron/service/jobs.ts @@ -29,25 +29,35 @@ export function assertSupportedJobSpec(job: Pick j.id === id); - if (!job) throw new Error(`unknown cron job id: ${id}`); + if (!job) { + throw new Error(`unknown cron job id: ${id}`); + } return job; } export function computeJobNextRunAtMs(job: CronJob, nowMs: number): number | undefined { - if (!job.enabled) return undefined; + if (!job.enabled) { + return undefined; + } if (job.schedule.kind === "at") { // One-shot jobs stay due until they successfully finish. - if (job.state.lastStatus === "ok" && job.state.lastRunAtMs) return undefined; + if (job.state.lastStatus === "ok" && job.state.lastRunAtMs) { + return undefined; + } return job.schedule.atMs; } return computeNextRunAtMs(job.schedule, nowMs); } export function recomputeNextRuns(state: CronServiceState) { - if (!state.store) return; + if (!state.store) { + return; + } const now = state.deps.nowMs(); for (const job of state.store.jobs) { - if (!job.state) job.state = {}; + if (!job.state) { + job.state = {}; + } if (!job.enabled) { job.state.nextRunAtMs = undefined; job.state.runningAtMs = undefined; @@ -68,7 +78,9 @@ export function recomputeNextRuns(state: CronServiceState) { export function nextWakeAtMs(state: CronServiceState) { const jobs = state.store?.jobs ?? []; const enabled = jobs.filter((j) => j.enabled && typeof j.state.nextRunAtMs === "number"); - if (enabled.length === 0) return undefined; + if (enabled.length === 0) { + return undefined; + } return enabled.reduce( (min, j) => Math.min(min, j.state.nextRunAtMs as number), enabled[0].state.nextRunAtMs as number, @@ -102,16 +114,36 @@ export function createJob(state: CronServiceState, input: CronJobCreate): CronJo } export function applyJobPatch(job: CronJob, patch: CronJobPatch) { - if ("name" in patch) job.name = normalizeRequiredName(patch.name); - if ("description" in patch) job.description = normalizeOptionalText(patch.description); - if (typeof patch.enabled === "boolean") job.enabled = patch.enabled; - if (typeof patch.deleteAfterRun === "boolean") job.deleteAfterRun = patch.deleteAfterRun; - if (patch.schedule) job.schedule = patch.schedule; - if (patch.sessionTarget) job.sessionTarget = patch.sessionTarget; - if (patch.wakeMode) job.wakeMode = patch.wakeMode; - if (patch.payload) job.payload = mergeCronPayload(job.payload, patch.payload); - if (patch.isolation) job.isolation = patch.isolation; - if (patch.state) job.state = { ...job.state, ...patch.state }; + if ("name" in patch) { + job.name = normalizeRequiredName(patch.name); + } + if ("description" in patch) { + job.description = normalizeOptionalText(patch.description); + } + if (typeof patch.enabled === "boolean") { + job.enabled = patch.enabled; + } + if (typeof patch.deleteAfterRun === "boolean") { + job.deleteAfterRun = patch.deleteAfterRun; + } + if (patch.schedule) { + job.schedule = patch.schedule; + } + if (patch.sessionTarget) { + job.sessionTarget = patch.sessionTarget; + } + if (patch.wakeMode) { + job.wakeMode = patch.wakeMode; + } + if (patch.payload) { + job.payload = mergeCronPayload(job.payload, patch.payload); + } + if (patch.isolation) { + job.isolation = patch.isolation; + } + if (patch.state) { + job.state = { ...job.state, ...patch.state }; + } if ("agentId" in patch) { job.agentId = normalizeOptionalAgentId((patch as { agentId?: unknown }).agentId); } @@ -136,13 +168,27 @@ function mergeCronPayload(existing: CronPayload, patch: CronPayloadPatch): CronP } const next: Extract = { ...existing }; - if (typeof patch.message === "string") next.message = patch.message; - if (typeof patch.model === "string") next.model = patch.model; - if (typeof patch.thinking === "string") next.thinking = patch.thinking; - if (typeof patch.timeoutSeconds === "number") next.timeoutSeconds = patch.timeoutSeconds; - if (typeof patch.deliver === "boolean") next.deliver = patch.deliver; - if (typeof patch.channel === "string") next.channel = patch.channel; - if (typeof patch.to === "string") next.to = patch.to; + if (typeof patch.message === "string") { + next.message = patch.message; + } + if (typeof patch.model === "string") { + next.model = patch.model; + } + if (typeof patch.thinking === "string") { + next.thinking = patch.thinking; + } + if (typeof patch.timeoutSeconds === "number") { + next.timeoutSeconds = patch.timeoutSeconds; + } + if (typeof patch.deliver === "boolean") { + next.deliver = patch.deliver; + } + if (typeof patch.channel === "string") { + next.channel = patch.channel; + } + if (typeof patch.to === "string") { + next.to = patch.to; + } if (typeof patch.bestEffortDeliver === "boolean") { next.bestEffortDeliver = patch.bestEffortDeliver; } @@ -175,12 +221,16 @@ function buildPayloadFromPatch(patch: CronPayloadPatch): CronPayload { } export function isJobDue(job: CronJob, nowMs: number, opts: { forced: boolean }) { - if (opts.forced) return true; + if (opts.forced) { + return true; + } return job.enabled && typeof job.state.nextRunAtMs === "number" && nowMs >= job.state.nextRunAtMs; } export function resolveJobPayloadTextForMain(job: CronJob): string | undefined { - if (job.payload.kind !== "systemEvent") return undefined; + if (job.payload.kind !== "systemEvent") { + return undefined; + } const text = normalizePayloadToSystemText(job.payload); return text.trim() ? text : undefined; } diff --git a/src/cron/service/normalize.ts b/src/cron/service/normalize.ts index 161b118fa8..2712a9f86b 100644 --- a/src/cron/service/normalize.ts +++ b/src/cron/service/normalize.ts @@ -3,27 +3,39 @@ import { truncateUtf16Safe } from "../../utils.js"; import type { CronPayload } from "../types.js"; export function normalizeRequiredName(raw: unknown) { - if (typeof raw !== "string") throw new Error("cron job name is required"); + if (typeof raw !== "string") { + throw new Error("cron job name is required"); + } const name = raw.trim(); - if (!name) throw new Error("cron job name is required"); + if (!name) { + throw new Error("cron job name is required"); + } return name; } export function normalizeOptionalText(raw: unknown) { - if (typeof raw !== "string") return undefined; + if (typeof raw !== "string") { + return undefined; + } const trimmed = raw.trim(); return trimmed ? trimmed : undefined; } function truncateText(input: string, maxLen: number) { - if (input.length <= maxLen) return input; + if (input.length <= maxLen) { + return input; + } return `${truncateUtf16Safe(input, Math.max(0, maxLen - 1)).trimEnd()}…`; } export function normalizeOptionalAgentId(raw: unknown) { - if (typeof raw !== "string") return undefined; + if (typeof raw !== "string") { + return undefined; + } const trimmed = raw.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } return normalizeAgentId(trimmed); } @@ -42,18 +54,26 @@ export function inferLegacyName(job: { .split("\n") .map((l) => l.trim()) .find(Boolean) ?? ""; - if (firstLine) return truncateText(firstLine, 60); + if (firstLine) { + return truncateText(firstLine, 60); + } const kind = typeof job?.schedule?.kind === "string" ? job.schedule.kind : ""; - if (kind === "cron" && typeof job?.schedule?.expr === "string") + if (kind === "cron" && typeof job?.schedule?.expr === "string") { return `Cron: ${truncateText(job.schedule.expr, 52)}`; - if (kind === "every" && typeof job?.schedule?.everyMs === "number") + } + if (kind === "every" && typeof job?.schedule?.everyMs === "number") { return `Every: ${job.schedule.everyMs}ms`; - if (kind === "at") return "One-shot"; + } + if (kind === "at") { + return "One-shot"; + } return "Cron job"; } export function normalizePayloadToSystemText(payload: CronPayload) { - if (payload.kind === "systemEvent") return payload.text.trim(); + if (payload.kind === "systemEvent") { + return payload.text.trim(); + } return payload.message.trim(); } diff --git a/src/cron/service/ops.ts b/src/cron/service/ops.ts index a084c2ba44..8e734ea18d 100644 --- a/src/cron/service/ops.ts +++ b/src/cron/service/ops.ts @@ -107,12 +107,16 @@ export async function remove(state: CronServiceState, id: string) { warnIfDisabled(state, "remove"); await ensureLoaded(state); const before = state.store?.jobs.length ?? 0; - if (!state.store) return { ok: false, removed: false } as const; + if (!state.store) { + return { ok: false, removed: false } as const; + } state.store.jobs = state.store.jobs.filter((j) => j.id !== id); const removed = (state.store.jobs.length ?? 0) !== before; await persist(state); armTimer(state); - if (removed) emit(state, { jobId: id, action: "removed" }); + if (removed) { + emit(state, { jobId: id, action: "removed" }); + } return { ok: true, removed } as const; }); } @@ -124,7 +128,9 @@ export async function run(state: CronServiceState, id: string, mode?: "due" | "f const job = findJobOrThrow(state, id); const now = state.deps.nowMs(); const due = isJobDue(job, now, { forced: mode === "force" }); - if (!due) return { ok: true, ran: false, reason: "not-due" as const }; + if (!due) { + return { ok: true, ran: false, reason: "not-due" as const }; + } await executeJob(state, job, now, { forced: mode === "force" }); await persist(state); armTimer(state); diff --git a/src/cron/service/store.ts b/src/cron/service/store.ts index 814786ac6c..6b2f041332 100644 --- a/src/cron/service/store.ts +++ b/src/cron/service/store.ts @@ -7,7 +7,9 @@ import type { CronServiceState } from "./state.js"; const storeCache = new Map(); export async function ensureLoaded(state: CronServiceState) { - if (state.store) return; + if (state.store) { + return; + } const cached = storeCache.get(state.deps.storePath); if (cached) { state.store = cached; @@ -43,12 +45,18 @@ export async function ensureLoaded(state: CronServiceState) { } state.store = { version: 1, jobs: jobs as unknown as CronJob[] }; storeCache.set(state.deps.storePath, state.store); - if (mutated) await persist(state); + if (mutated) { + await persist(state); + } } export function warnIfDisabled(state: CronServiceState, action: string) { - if (state.deps.cronEnabled) return; - if (state.warnedDisabled) return; + if (state.deps.cronEnabled) { + return; + } + if (state.warnedDisabled) { + return; + } state.warnedDisabled = true; state.deps.log.warn( { enabled: false, action, storePath: state.deps.storePath }, @@ -57,6 +65,8 @@ export function warnIfDisabled(state: CronServiceState, action: string) { } export async function persist(state: CronServiceState) { - if (!state.store) return; + if (!state.store) { + return; + } await saveCronStore(state.deps.storePath, state.store); } diff --git a/src/cron/service/timer.ts b/src/cron/service/timer.ts index 370f5d116b..9344cdf73e 100644 --- a/src/cron/service/timer.ts +++ b/src/cron/service/timer.ts @@ -8,11 +8,17 @@ import { ensureLoaded, persist } from "./store.js"; const MAX_TIMEOUT_MS = 2 ** 31 - 1; export function armTimer(state: CronServiceState) { - if (state.timer) clearTimeout(state.timer); + if (state.timer) { + clearTimeout(state.timer); + } state.timer = null; - if (!state.deps.cronEnabled) return; + if (!state.deps.cronEnabled) { + return; + } const nextAt = nextWakeAtMs(state); - if (!nextAt) return; + if (!nextAt) { + return; + } const delay = Math.max(nextAt - state.deps.nowMs(), 0); // Avoid TimeoutOverflowWarning when a job is far in the future. const clampedDelay = Math.min(delay, MAX_TIMEOUT_MS); @@ -25,7 +31,9 @@ export function armTimer(state: CronServiceState) { } export async function onTimer(state: CronServiceState) { - if (state.running) return; + if (state.running) { + return; + } state.running = true; try { await locked(state, async () => { @@ -40,11 +48,17 @@ export async function onTimer(state: CronServiceState) { } export async function runDueJobs(state: CronServiceState) { - if (!state.store) return; + if (!state.store) { + return; + } const now = state.deps.nowMs(); const due = state.store.jobs.filter((j) => { - if (!j.enabled) return false; - if (typeof j.state.runningAtMs === "number") return false; + if (!j.enabled) { + return false; + } + if (typeof j.state.runningAtMs === "number") { + return false; + } const next = j.state.nextRunAtMs; return typeof next === "number" && now >= next; }); @@ -199,10 +213,13 @@ export async function executeJob( job, message: job.payload.message, }); - if (res.status === "ok") await finish("ok", undefined, res.summary, res.outputText); - else if (res.status === "skipped") + if (res.status === "ok") { + await finish("ok", undefined, res.summary, res.outputText); + } else if (res.status === "skipped") { await finish("skipped", undefined, res.summary, res.outputText); - else await finish("error", res.error ?? "cron job failed", res.summary, res.outputText); + } else { + await finish("error", res.error ?? "cron job failed", res.summary, res.outputText); + } } catch (err) { await finish("error", String(err)); } finally { @@ -219,7 +236,9 @@ export function wake( opts: { mode: "now" | "next-heartbeat"; text: string }, ) { const text = opts.text.trim(); - if (!text) return { ok: false } as const; + if (!text) { + return { ok: false } as const; + } state.deps.enqueueSystemEvent(text); if (opts.mode === "now") { state.deps.requestHeartbeatNow({ reason: "wake" }); @@ -228,7 +247,9 @@ export function wake( } export function stopTimer(state: CronServiceState) { - if (state.timer) clearTimeout(state.timer); + if (state.timer) { + clearTimeout(state.timer); + } state.timer = null; } diff --git a/src/cron/store.ts b/src/cron/store.ts index e50d020e4a..f74308156f 100644 --- a/src/cron/store.ts +++ b/src/cron/store.ts @@ -12,7 +12,9 @@ export const DEFAULT_CRON_STORE_PATH = path.join(DEFAULT_CRON_DIR, "jobs.json"); export function resolveCronStorePath(storePath?: string) { if (storePath?.trim()) { const raw = storePath.trim(); - if (raw.startsWith("~")) return path.resolve(raw.replace("~", os.homedir())); + if (raw.startsWith("~")) { + return path.resolve(raw.replace("~", os.homedir())); + } return path.resolve(raw); } return DEFAULT_CRON_STORE_PATH; diff --git a/src/daemon/constants.ts b/src/daemon/constants.ts index ff1ec2036a..212eb93a2a 100644 --- a/src/daemon/constants.ts +++ b/src/daemon/constants.ts @@ -16,7 +16,9 @@ export const LEGACY_GATEWAY_WINDOWS_TASK_NAMES: string[] = []; export function normalizeGatewayProfile(profile?: string): string | null { const trimmed = profile?.trim(); - if (!trimmed || trimmed.toLowerCase() === "default") return null; + if (!trimmed || trimmed.toLowerCase() === "default") { + return null; + } return trimmed; } @@ -40,13 +42,17 @@ export function resolveLegacyGatewayLaunchAgentLabels(profile?: string): string[ export function resolveGatewaySystemdServiceName(profile?: string): string { const suffix = resolveGatewayProfileSuffix(profile); - if (!suffix) return GATEWAY_SYSTEMD_SERVICE_NAME; + if (!suffix) { + return GATEWAY_SYSTEMD_SERVICE_NAME; + } return `openclaw-gateway${suffix}`; } export function resolveGatewayWindowsTaskName(profile?: string): string { const normalized = normalizeGatewayProfile(profile); - if (!normalized) return GATEWAY_WINDOWS_TASK_NAME; + if (!normalized) { + return GATEWAY_WINDOWS_TASK_NAME; + } return `OpenClaw Gateway (${normalized})`; } @@ -57,9 +63,15 @@ export function formatGatewayServiceDescription(params?: { const profile = normalizeGatewayProfile(params?.profile); const version = params?.version?.trim(); const parts: string[] = []; - if (profile) parts.push(`profile: ${profile}`); - if (version) parts.push(`v${version}`); - if (parts.length === 0) return "OpenClaw Gateway"; + if (profile) { + parts.push(`profile: ${profile}`); + } + if (version) { + parts.push(`v${version}`); + } + if (parts.length === 0) { + return "OpenClaw Gateway"; + } return `OpenClaw Gateway (${parts.join(", ")})`; } @@ -77,6 +89,8 @@ export function resolveNodeWindowsTaskName(): string { export function formatNodeServiceDescription(params?: { version?: string }): string { const version = params?.version?.trim(); - if (!version) return "OpenClaw Node Host"; + if (!version) { + return "OpenClaw Node Host"; + } return `OpenClaw Node Host (v${version})`; } diff --git a/src/daemon/diagnostics.ts b/src/daemon/diagnostics.ts index 4b25fae264..9592347c23 100644 --- a/src/daemon/diagnostics.ts +++ b/src/daemon/diagnostics.ts @@ -15,7 +15,9 @@ async function readLastLogLine(filePath: string): Promise { const raw = await fs.readFile(filePath, "utf8"); const lines = raw.split(/\r?\n/).map((line) => line.trim()); for (let i = lines.length - 1; i >= 0; i -= 1) { - if (lines[i]) return lines[i]; + if (lines[i]) { + return lines[i]; + } } return null; } catch { @@ -32,7 +34,9 @@ export async function readLastGatewayErrorLine(env: NodeJS.ProcessEnv): Promise< ); for (let i = lines.length - 1; i >= 0; i -= 1) { const line = lines[i]; - if (!line) continue; + if (!line) { + continue; + } if (GATEWAY_LOG_ERROR_PATTERNS.some((pattern) => pattern.test(line))) { return line; } diff --git a/src/daemon/inspect.ts b/src/daemon/inspect.ts index c1e83305c7..e8a4d9a3a8 100644 --- a/src/daemon/inspect.ts +++ b/src/daemon/inspect.ts @@ -54,7 +54,9 @@ export function renderGatewayServiceCleanupHints( function resolveHomeDir(env: Record): string { const home = env.HOME?.trim() || env.USERPROFILE?.trim(); - if (!home) throw new Error("Missing HOME"); + if (!home) { + throw new Error("Missing HOME"); + } return home; } @@ -63,7 +65,9 @@ type Marker = (typeof EXTRA_MARKERS)[number]; function detectMarker(content: string): Marker | null { const lower = content.toLowerCase(); for (const marker of EXTRA_MARKERS) { - if (lower.includes(marker)) return marker; + if (lower.includes(marker)) { + return marker; + } } return null; } @@ -85,28 +89,40 @@ function hasGatewayServiceMarker(content: string): boolean { } function isOpenClawGatewayLaunchdService(label: string, contents: string): boolean { - if (hasGatewayServiceMarker(contents)) return true; + if (hasGatewayServiceMarker(contents)) { + return true; + } const lowerContents = contents.toLowerCase(); - if (!lowerContents.includes("gateway")) return false; + if (!lowerContents.includes("gateway")) { + return false; + } return label.startsWith("ai.openclaw."); } function isOpenClawGatewaySystemdService(name: string, contents: string): boolean { - if (hasGatewayServiceMarker(contents)) return true; - if (!name.startsWith("openclaw-gateway")) return false; + if (hasGatewayServiceMarker(contents)) { + return true; + } + if (!name.startsWith("openclaw-gateway")) { + return false; + } return contents.toLowerCase().includes("gateway"); } function isOpenClawGatewayTaskName(name: string): boolean { const normalized = name.trim().toLowerCase(); - if (!normalized) return false; + if (!normalized) { + return false; + } const defaultName = resolveGatewayWindowsTaskName().toLowerCase(); return normalized === defaultName || normalized.startsWith("openclaw gateway"); } function tryExtractPlistLabel(contents: string): string | null { const match = contents.match(/Label<\/key>\s*([\s\S]*?)<\/string>/i); - if (!match) return null; + if (!match) { + return null; + } return match[1]?.trim() || null; } @@ -136,9 +152,13 @@ async function scanLaunchdDir(params: { } for (const entry of entries) { - if (!entry.endsWith(".plist")) continue; + if (!entry.endsWith(".plist")) { + continue; + } const labelFromName = entry.replace(/\.plist$/, ""); - if (isIgnoredLaunchdLabel(labelFromName)) continue; + if (isIgnoredLaunchdLabel(labelFromName)) { + continue; + } const fullPath = path.join(params.dir, entry); let contents = ""; try { @@ -150,7 +170,9 @@ async function scanLaunchdDir(params: { const label = tryExtractPlistLabel(contents) ?? labelFromName; if (!marker) { const legacyLabel = isLegacyLabel(labelFromName) || isLegacyLabel(label); - if (!legacyLabel) continue; + if (!legacyLabel) { + continue; + } results.push({ platform: "darwin", label, @@ -161,8 +183,12 @@ async function scanLaunchdDir(params: { }); continue; } - if (isIgnoredLaunchdLabel(label)) continue; - if (marker === "openclaw" && isOpenClawGatewayLaunchdService(label, contents)) continue; + if (isIgnoredLaunchdLabel(label)) { + continue; + } + if (marker === "openclaw" && isOpenClawGatewayLaunchdService(label, contents)) { + continue; + } results.push({ platform: "darwin", label, @@ -189,9 +215,13 @@ async function scanSystemdDir(params: { } for (const entry of entries) { - if (!entry.endsWith(".service")) continue; + if (!entry.endsWith(".service")) { + continue; + } const name = entry.replace(/\.service$/, ""); - if (isIgnoredSystemdName(name)) continue; + if (isIgnoredSystemdName(name)) { + continue; + } const fullPath = path.join(params.dir, entry); let contents = ""; try { @@ -200,8 +230,12 @@ async function scanSystemdDir(params: { continue; } const marker = detectMarker(contents); - if (!marker) continue; - if (marker === "openclaw" && isOpenClawGatewaySystemdService(name, contents)) continue; + if (!marker) { + continue; + } + if (marker === "openclaw" && isOpenClawGatewaySystemdService(name, contents)) { + continue; + } results.push({ platform: "linux", label: entry, @@ -234,22 +268,32 @@ function parseSchtasksList(output: string): ScheduledTaskInfo[] { continue; } const idx = line.indexOf(":"); - if (idx <= 0) continue; + if (idx <= 0) { + continue; + } const key = line.slice(0, idx).trim().toLowerCase(); const value = line.slice(idx + 1).trim(); - if (!value) continue; + if (!value) { + continue; + } if (key === "taskname") { - if (current) tasks.push(current); + if (current) { + tasks.push(current); + } current = { name: value }; continue; } - if (!current) continue; + if (!current) { + continue; + } if (key === "task to run") { current.taskToRun = value; } } - if (current) tasks.push(current); + if (current) { + tasks.push(current); + } return tasks; } @@ -290,7 +334,9 @@ export async function findExtraGatewayServices( const seen = new Set(); const push = (svc: ExtraGatewayService) => { const key = `${svc.platform}:${svc.label}:${svc.detail}:${svc.scope}`; - if (seen.has(key)) return; + if (seen.has(key)) { + return; + } seen.add(key); results.push(svc); }; @@ -356,14 +402,22 @@ export async function findExtraGatewayServices( } if (process.platform === "win32") { - if (!opts.deep) return results; + if (!opts.deep) { + return results; + } const res = await execSchtasks(["/Query", "/FO", "LIST", "/V"]); - if (res.code !== 0) return results; + if (res.code !== 0) { + return results; + } const tasks = parseSchtasksList(res.stdout); for (const task of tasks) { const name = task.name.trim(); - if (!name) continue; - if (isOpenClawGatewayTaskName(name)) continue; + if (!name) { + continue; + } + if (isOpenClawGatewayTaskName(name)) { + continue; + } const lowerName = name.toLowerCase(); const lowerCommand = task.taskToRun?.toLowerCase() ?? ""; let marker: Marker | null = null; @@ -373,7 +427,9 @@ export async function findExtraGatewayServices( break; } } - if (!marker) continue; + if (!marker) { + continue; + } push({ platform: "win32", label: name, diff --git a/src/daemon/launchd-plist.ts b/src/daemon/launchd-plist.ts index 55ba5112e2..e685cd9941 100644 --- a/src/daemon/launchd-plist.ts +++ b/src/daemon/launchd-plist.ts @@ -17,11 +17,15 @@ const plistUnescape = (value: string): string => .replaceAll("&", "&"); const renderEnvDict = (env: Record | undefined): string => { - if (!env) return ""; + if (!env) { + return ""; + } const entries = Object.entries(env).filter( ([, value]) => typeof value === "string" && value.trim(), ); - if (entries.length === 0) return ""; + if (entries.length === 0) { + return ""; + } const items = entries .map( ([key, value]) => @@ -40,7 +44,9 @@ export async function readLaunchAgentProgramArgumentsFromFile(plistPath: string) try { const plist = await fs.readFile(plistPath, "utf8"); const programMatch = plist.match(/ProgramArguments<\/key>\s*([\s\S]*?)<\/array>/i); - if (!programMatch) return null; + if (!programMatch) { + return null; + } const args = Array.from(programMatch[1].matchAll(/([\s\S]*?)<\/string>/gi)).map( (match) => plistUnescape(match[1] ?? "").trim(), ); @@ -55,7 +61,9 @@ export async function readLaunchAgentProgramArgumentsFromFile(plistPath: string) /([\s\S]*?)<\/key>\s*([\s\S]*?)<\/string>/gi, )) { const key = plistUnescape(pair[1] ?? "").trim(); - if (!key) continue; + if (!key) { + continue; + } const value = plistUnescape(pair[2] ?? "").trim(); environment[key] = value; } diff --git a/src/daemon/launchd.ts b/src/daemon/launchd.ts index ab0819cc0c..5832bde7f7 100644 --- a/src/daemon/launchd.ts +++ b/src/daemon/launchd.ts @@ -28,7 +28,9 @@ const formatLine = (label: string, value: string) => { function resolveLaunchAgentLabel(args?: { env?: Record }): string { const envLabel = args?.env?.OPENCLAW_LAUNCHD_LABEL?.trim(); - if (envLabel) return envLabel; + if (envLabel) { + return envLabel; + } return resolveGatewayLaunchAgentLabel(args?.env?.OPENCLAW_PROFILE); } @@ -130,7 +132,9 @@ async function execLaunchctl( } function resolveGuiDomain(): string { - if (typeof process.getuid !== "function") return "gui/501"; + if (typeof process.getuid !== "function") { + return "gui/501"; + } return `gui/${process.getuid()}`; } @@ -145,19 +149,27 @@ export function parseLaunchctlPrint(output: string): LaunchctlPrintInfo { const entries = parseKeyValueOutput(output, "="); const info: LaunchctlPrintInfo = {}; const state = entries.state; - if (state) info.state = state; + if (state) { + info.state = state; + } const pidValue = entries.pid; if (pidValue) { const pid = Number.parseInt(pidValue, 10); - if (Number.isFinite(pid)) info.pid = pid; + if (Number.isFinite(pid)) { + info.pid = pid; + } } const exitStatusValue = entries["last exit status"]; if (exitStatusValue) { const status = Number.parseInt(exitStatusValue, 10); - if (Number.isFinite(status)) info.lastExitStatus = status; + if (Number.isFinite(status)) { + info.lastExitStatus = status; + } } const exitReason = entries["last exit reason"]; - if (exitReason) info.lastExitReason = exitReason; + if (exitReason) { + info.lastExitReason = exitReason; + } return info; } @@ -175,7 +187,9 @@ export async function isLaunchAgentListed(args: { }): Promise { const label = resolveLaunchAgentLabel({ env: args.env }); const res = await execLaunchctl(["list"]); - if (res.code !== 0) return false; + if (res.code !== 0) { + return false; + } return res.stdout.split(/\r?\n/).some((line) => line.trim().split(/\s+/).at(-1) === label); } @@ -275,7 +289,9 @@ export async function uninstallLegacyLaunchAgents({ }): Promise { const domain = resolveGuiDomain(); const agents = await findLegacyLaunchAgents(env); - if (agents.length === 0) return agents; + if (agents.length === 0) { + return agents; + } const home = resolveHomeDir(env); const trashDir = path.join(home, ".Trash"); diff --git a/src/daemon/paths.ts b/src/daemon/paths.ts index 70d01085d2..fd8895f7ca 100644 --- a/src/daemon/paths.ts +++ b/src/daemon/paths.ts @@ -7,15 +7,21 @@ const windowsUncPath = /^\\\\/; export function resolveHomeDir(env: Record): string { const home = env.HOME?.trim() || env.USERPROFILE?.trim(); - if (!home) throw new Error("Missing HOME"); + if (!home) { + throw new Error("Missing HOME"); + } return home; } export function resolveUserPathWithHome(input: string, home?: string): string { const trimmed = input.trim(); - if (!trimmed) return trimmed; + if (!trimmed) { + return trimmed; + } if (trimmed.startsWith("~")) { - if (!home) throw new Error("Missing HOME"); + if (!home) { + throw new Error("Missing HOME"); + } const expanded = trimmed.replace(/^~(?=$|[\\/])/, home); return path.resolve(expanded); } diff --git a/src/daemon/program-args.ts b/src/daemon/program-args.ts index 080398b471..0e3c903987 100644 --- a/src/daemon/program-args.ts +++ b/src/daemon/program-args.ts @@ -20,7 +20,9 @@ function isBunRuntime(execPath: string): boolean { async function resolveCliEntrypointPathForService(): Promise { const argv1 = process.argv[1]; - if (!argv1) throw new Error("Unable to resolve CLI entrypoint path"); + if (!argv1) { + throw new Error("Unable to resolve CLI entrypoint path"); + } const normalized = path.resolve(argv1); const resolvedPath = await resolveRealpathSafe(normalized); @@ -73,7 +75,9 @@ function buildDistCandidates(...inputs: string[]): string[] { const seen = new Set(); for (const inputPath of inputs) { - if (!inputPath) continue; + if (!inputPath) { + continue; + } const baseDir = path.dirname(inputPath); appendDistCandidates(candidates, seen, path.resolve(baseDir, "..")); appendDistCandidates(candidates, seen, baseDir); @@ -92,7 +96,9 @@ function appendDistCandidates(candidates: string[], seen: Set, baseDir: path.join(distDir, "entry.mjs"), ]; for (const entry of distEntries) { - if (seen.has(entry)) continue; + if (seen.has(entry)) { + continue; + } seen.add(entry); candidates.push(entry); } @@ -105,8 +111,12 @@ function appendNodeModulesBinCandidates( ): void { const parts = inputPath.split(path.sep); const binIndex = parts.lastIndexOf(".bin"); - if (binIndex <= 0) return; - if (parts[binIndex - 1] !== "node_modules") return; + if (binIndex <= 0) { + return; + } + if (parts[binIndex - 1] !== "node_modules") { + return; + } const binName = path.basename(inputPath); const nodeModulesDir = parts.slice(0, binIndex).join(path.sep); const packageRoot = path.join(nodeModulesDir, binName); @@ -115,7 +125,9 @@ function appendNodeModulesBinCandidates( function resolveRepoRootForDev(): string { const argv1 = process.argv[1]; - if (!argv1) throw new Error("Unable to resolve repo root"); + if (!argv1) { + throw new Error("Unable to resolve repo root"); + } const normalized = path.resolve(argv1); const parts = normalized.split(path.sep); const srcIndex = parts.lastIndexOf("src"); @@ -141,7 +153,9 @@ async function resolveBinaryPath(binary: string): Promise { try { const output = execSync(`${cmd} ${binary}`, { encoding: "utf8" }).trim(); const resolved = output.split(/\r?\n/)[0]?.trim(); - if (!resolved) throw new Error("empty"); + if (!resolved) { + throw new Error("empty"); + } await fs.access(resolved); return resolved; } catch { @@ -252,10 +266,18 @@ export async function resolveNodeProgramArguments(params: { nodePath?: string; }): Promise { const args = ["node", "run", "--host", params.host, "--port", String(params.port)]; - if (params.tls || params.tlsFingerprint) args.push("--tls"); - if (params.tlsFingerprint) args.push("--tls-fingerprint", params.tlsFingerprint); - if (params.nodeId) args.push("--node-id", params.nodeId); - if (params.displayName) args.push("--display-name", params.displayName); + if (params.tls || params.tlsFingerprint) { + args.push("--tls"); + } + if (params.tlsFingerprint) { + args.push("--tls-fingerprint", params.tlsFingerprint); + } + if (params.nodeId) { + args.push("--node-id", params.nodeId); + } + if (params.displayName) { + args.push("--display-name", params.displayName); + } return resolveCliProgramArguments({ args, dev: params.dev, diff --git a/src/daemon/runtime-parse.ts b/src/daemon/runtime-parse.ts index bd1833f11c..439667e250 100644 --- a/src/daemon/runtime-parse.ts +++ b/src/daemon/runtime-parse.ts @@ -2,11 +2,17 @@ export function parseKeyValueOutput(output: string, separator: string): Record = {}; for (const rawLine of output.split(/\r?\n/)) { const line = rawLine.trim(); - if (!line) continue; + if (!line) { + continue; + } const idx = line.indexOf(separator); - if (idx <= 0) continue; + if (idx <= 0) { + continue; + } const key = line.slice(0, idx).trim().toLowerCase(); - if (!key) continue; + if (!key) { + continue; + } const value = line.slice(idx + separator.length).trim(); entries[key] = value; } diff --git a/src/daemon/runtime-paths.test.ts b/src/daemon/runtime-paths.test.ts index d505be38c5..d7ec4d6048 100644 --- a/src/daemon/runtime-paths.test.ts +++ b/src/daemon/runtime-paths.test.ts @@ -24,7 +24,9 @@ describe("resolvePreferredNodePath", () => { it("uses system node when it meets the minimum version", async () => { fsMocks.access.mockImplementation(async (target: string) => { - if (target === darwinNode) return; + if (target === darwinNode) { + return; + } throw new Error("missing"); }); @@ -43,7 +45,9 @@ describe("resolvePreferredNodePath", () => { it("skips system node when it is too old", async () => { fsMocks.access.mockImplementation(async (target: string) => { - if (target === darwinNode) return; + if (target === darwinNode) { + return; + } throw new Error("missing"); }); @@ -82,7 +86,9 @@ describe("resolveSystemNodeInfo", () => { it("returns supported info when version is new enough", async () => { fsMocks.access.mockImplementation(async (target: string) => { - if (target === darwinNode) return; + if (target === darwinNode) { + return; + } throw new Error("missing"); }); diff --git a/src/daemon/runtime-paths.ts b/src/daemon/runtime-paths.ts index f49b445ea9..1d3d31a8df 100644 --- a/src/daemon/runtime-paths.ts +++ b/src/daemon/runtime-paths.ts @@ -124,7 +124,9 @@ export async function resolveSystemNodeInfo(params: { const env = params.env ?? process.env; const platform = params.platform ?? process.platform; const systemNode = await resolveSystemNodePath(env, platform); - if (!systemNode) return null; + if (!systemNode) { + return null; + } const version = await resolveNodeVersion(systemNode, params.execFile ?? execFileAsync); return { @@ -138,7 +140,9 @@ export function renderSystemNodeWarning( systemNode: SystemNodeInfo | null, selectedNodePath?: string, ): string | null { - if (!systemNode || systemNode.supported) return null; + if (!systemNode || systemNode.supported) { + return null; + } const versionLabel = systemNode.version ?? "unknown"; const selectedLabel = selectedNodePath ? ` Using ${selectedNodePath} for the daemon.` : ""; return `System Node ${versionLabel} at ${systemNode.path} is below the required Node 22+.${selectedLabel} Install Node 22+ from nodejs.org or Homebrew.`; @@ -150,8 +154,12 @@ export async function resolvePreferredNodePath(params: { platform?: NodeJS.Platform; execFile?: ExecFileAsync; }): Promise { - if (params.runtime !== "node") return undefined; + if (params.runtime !== "node") { + return undefined; + } const systemNode = await resolveSystemNodeInfo(params); - if (!systemNode?.supported) return undefined; + if (!systemNode?.supported) { + return undefined; + } return systemNode.path; } diff --git a/src/daemon/schtasks.ts b/src/daemon/schtasks.ts index ebf2112aab..41766accb2 100644 --- a/src/daemon/schtasks.ts +++ b/src/daemon/schtasks.ts @@ -18,29 +18,41 @@ const formatLine = (label: string, value: string) => { function resolveTaskName(env: Record): string { const override = env.OPENCLAW_WINDOWS_TASK_NAME?.trim(); - if (override) return override; + if (override) { + return override; + } return resolveGatewayWindowsTaskName(env.OPENCLAW_PROFILE); } export function resolveTaskScriptPath(env: Record): string { const override = env.OPENCLAW_TASK_SCRIPT?.trim(); - if (override) return override; + if (override) { + return override; + } const scriptName = env.OPENCLAW_TASK_SCRIPT_NAME?.trim() || "gateway.cmd"; const stateDir = resolveGatewayStateDir(env); return path.join(stateDir, scriptName); } function quoteCmdArg(value: string): string { - if (!/[ \t"]/g.test(value)) return value; + if (!/[ \t"]/g.test(value)) { + return value; + } return `"${value.replace(/"/g, '\\"')}"`; } function resolveTaskUser(env: Record): string | null { const username = env.USERNAME || env.USER || env.LOGNAME; - if (!username) return null; - if (username.includes("\\")) return username; + if (!username) { + return null; + } + if (username.includes("\\")) { + return username; + } const domain = env.USERDOMAIN; - if (domain) return `${domain}\\${username}`; + if (domain) { + return `${domain}\\${username}`; + } return username; } @@ -73,7 +85,9 @@ function parseCommandLine(value: string): string[] { } current += char; } - if (current) args.push(current); + if (current) { + args.push(current); + } return args; } @@ -90,16 +104,24 @@ export async function readScheduledTaskCommand(env: Record = {}; for (const rawLine of content.split(/\r?\n/)) { const line = rawLine.trim(); - if (!line) continue; - if (line.startsWith("@echo")) continue; - if (line.toLowerCase().startsWith("rem ")) continue; + if (!line) { + continue; + } + if (line.startsWith("@echo")) { + continue; + } + if (line.toLowerCase().startsWith("rem ")) { + continue; + } if (line.toLowerCase().startsWith("set ")) { const assignment = line.slice(4).trim(); const index = assignment.indexOf("="); if (index > 0) { const key = assignment.slice(0, index).trim(); const value = assignment.slice(index + 1).trim(); - if (key) environment[key] = value; + if (key) { + environment[key] = value; + } } continue; } @@ -110,7 +132,9 @@ export async function readScheduledTaskCommand(env: Record, platform: NodeJS.Platform, ) { - if (platform === "win32") return; + if (platform === "win32") { + return; + } const servicePath = command?.environment?.PATH; if (!servicePath) { issues.push({ @@ -276,7 +298,9 @@ async function auditGatewayRuntime( platform: NodeJS.Platform, ) { const execPath = command?.programArguments?.[0]; - if (!execPath) return; + if (!execPath) { + return; + } if (isBunRuntime(execPath)) { issues.push({ @@ -288,7 +312,9 @@ async function auditGatewayRuntime( return; } - if (!isNodeRuntime(execPath)) return; + if (!isNodeRuntime(execPath)) { + return; + } if (isVersionManagedNodePath(execPath, platform)) { issues.push({ diff --git a/src/daemon/service-env.ts b/src/daemon/service-env.ts index 2afdcfc6a8..7a83f3259e 100644 --- a/src/daemon/service-env.ts +++ b/src/daemon/service-env.ts @@ -43,15 +43,21 @@ export function resolveLinuxUserBinDirs( home: string | undefined, env?: Record, ): string[] { - if (!home) return []; + if (!home) { + return []; + } const dirs: string[] = []; const add = (dir: string | undefined) => { - if (dir) dirs.push(dir); + if (dir) { + dirs.push(dir); + } }; const appendSubdir = (base: string | undefined, subdir: string) => { - if (!base) return undefined; + if (!base) { + return undefined; + } return base.endsWith(`/${subdir}`) ? base : path.posix.join(base, subdir); }; @@ -82,7 +88,9 @@ export function resolveLinuxUserBinDirs( export function getMinimalServicePathParts(options: MinimalServicePathOptions = {}): string[] { const platform = options.platform ?? process.platform; - if (platform === "win32") return []; + if (platform === "win32") { + return []; + } const parts: string[] = []; const extraDirs = options.extraDirs ?? []; @@ -93,14 +101,24 @@ export function getMinimalServicePathParts(options: MinimalServicePathOptions = platform === "linux" ? resolveLinuxUserBinDirs(options.home, options.env) : []; const add = (dir: string) => { - if (!dir) return; - if (!parts.includes(dir)) parts.push(dir); + if (!dir) { + return; + } + if (!parts.includes(dir)) { + parts.push(dir); + } }; - for (const dir of extraDirs) add(dir); + for (const dir of extraDirs) { + add(dir); + } // User dirs first so user-installed binaries take precedence - for (const dir of linuxUserDirs) add(dir); - for (const dir of systemDirs) add(dir); + for (const dir of linuxUserDirs) { + add(dir); + } + for (const dir of systemDirs) { + add(dir); + } return parts; } diff --git a/src/daemon/systemd-hints.ts b/src/daemon/systemd-hints.ts index e14159d6f9..49cd2a9016 100644 --- a/src/daemon/systemd-hints.ts +++ b/src/daemon/systemd-hints.ts @@ -1,7 +1,9 @@ import { formatCliCommand } from "../cli/command-format.js"; export function isSystemdUnavailableDetail(detail?: string): boolean { - if (!detail) return false; + if (!detail) { + return false; + } const normalized = detail.toLowerCase(); return ( normalized.includes("systemctl --user unavailable") || diff --git a/src/daemon/systemd-linger.ts b/src/daemon/systemd-linger.ts index 0fb8e020bd..60cfb4e301 100644 --- a/src/daemon/systemd-linger.ts +++ b/src/daemon/systemd-linger.ts @@ -3,7 +3,9 @@ import { runCommandWithTimeout, runExec } from "../process/exec.js"; function resolveLoginctlUser(env: Record): string | null { const fromEnv = env.USER?.trim() || env.LOGNAME?.trim(); - if (fromEnv) return fromEnv; + if (fromEnv) { + return fromEnv; + } try { return os.userInfo().username; } catch { @@ -20,7 +22,9 @@ export async function readSystemdUserLingerStatus( env: Record, ): Promise { const user = resolveLoginctlUser(env); - if (!user) return null; + if (!user) { + return null; + } try { const { stdout } = await runExec("loginctl", ["show-user", user, "-p", "Linger"], { timeoutMs: 5_000, diff --git a/src/daemon/systemd-unit.ts b/src/daemon/systemd-unit.ts index 39900f275c..e7a8d09488 100644 --- a/src/daemon/systemd-unit.ts +++ b/src/daemon/systemd-unit.ts @@ -1,14 +1,20 @@ function systemdEscapeArg(value: string): string { - if (!/[\\s"\\\\]/.test(value)) return value; + if (!/[\\s"\\\\]/.test(value)) { + return value; + } return `"${value.replace(/\\\\/g, "\\\\\\\\").replace(/"/g, '\\\\"')}"`; } function renderEnvLines(env: Record | undefined): string[] { - if (!env) return []; + if (!env) { + return []; + } const entries = Object.entries(env).filter( ([, value]) => typeof value === "string" && value.trim(), ); - if (entries.length === 0) return []; + if (entries.length === 0) { + return []; + } return entries.map( ([key, value]) => `Environment=${systemdEscapeArg(`${key}=${value?.trim() ?? ""}`)}`, ); @@ -85,16 +91,22 @@ export function parseSystemdExecStart(value: string): string[] { } current += char; } - if (current) args.push(current); + if (current) { + args.push(current); + } return args; } export function parseSystemdEnvAssignment(raw: string): { key: string; value: string } | null { const trimmed = raw.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const unquoted = (() => { - if (!(trimmed.startsWith('"') && trimmed.endsWith('"'))) return trimmed; + if (!(trimmed.startsWith('"') && trimmed.endsWith('"'))) { + return trimmed; + } let out = ""; let escapeNext = false; for (const ch of trimmed.slice(1, -1)) { @@ -113,9 +125,13 @@ export function parseSystemdEnvAssignment(raw: string): { key: string; value: st })(); const eq = unquoted.indexOf("="); - if (eq <= 0) return null; + if (eq <= 0) { + return null; + } const key = unquoted.slice(0, eq).trim(); - if (!key) return null; + if (!key) { + return null; + } const value = unquoted.slice(eq + 1); return { key, value }; } diff --git a/src/daemon/systemd.ts b/src/daemon/systemd.ts index d1afae8c8b..2c2d3bbc95 100644 --- a/src/daemon/systemd.ts +++ b/src/daemon/systemd.ts @@ -75,7 +75,9 @@ export async function readSystemdServiceExecStart( const environment: Record = {}; for (const rawLine of content.split("\n")) { const line = rawLine.trim(); - if (!line || line.startsWith("#")) continue; + if (!line || line.startsWith("#")) { + continue; + } if (line.startsWith("ExecStart=")) { execStart = line.slice("ExecStart=".length).trim(); } else if (line.startsWith("WorkingDirectory=")) { @@ -83,10 +85,14 @@ export async function readSystemdServiceExecStart( } else if (line.startsWith("Environment=")) { const raw = line.slice("Environment=".length).trim(); const parsed = parseSystemdEnvAssignment(raw); - if (parsed) environment[parsed.key] = parsed.value; + if (parsed) { + environment[parsed.key] = parsed.value; + } } } - if (!execStart) return null; + if (!execStart) { + return null; + } const programArguments = parseSystemdExecStart(execStart); return { programArguments, @@ -111,21 +117,31 @@ export function parseSystemdShow(output: string): SystemdServiceInfo { const entries = parseKeyValueOutput(output, "="); const info: SystemdServiceInfo = {}; const activeState = entries.activestate; - if (activeState) info.activeState = activeState; + if (activeState) { + info.activeState = activeState; + } const subState = entries.substate; - if (subState) info.subState = subState; + if (subState) { + info.subState = subState; + } const mainPidValue = entries.mainpid; if (mainPidValue) { const pid = Number.parseInt(mainPidValue, 10); - if (Number.isFinite(pid) && pid > 0) info.mainPid = pid; + if (Number.isFinite(pid) && pid > 0) { + info.mainPid = pid; + } } const execMainStatusValue = entries.execmainstatus; if (execMainStatusValue) { const status = Number.parseInt(execMainStatusValue, 10); - if (Number.isFinite(status)) info.execMainStatus = status; + if (Number.isFinite(status)) { + info.execMainStatus = status; + } } const execMainCode = entries.execmaincode; - if (execMainCode) info.execMainCode = execMainCode; + if (execMainCode) { + info.execMainCode = execMainCode; + } return info; } @@ -159,20 +175,36 @@ async function execSystemctl( export async function isSystemdUserServiceAvailable(): Promise { const res = await execSystemctl(["--user", "status"]); - if (res.code === 0) return true; + if (res.code === 0) { + return true; + } const detail = `${res.stderr} ${res.stdout}`.toLowerCase(); - if (!detail) return false; - if (detail.includes("not found")) return false; - if (detail.includes("failed to connect")) return false; - if (detail.includes("not been booted")) return false; - if (detail.includes("no such file or directory")) return false; - if (detail.includes("not supported")) return false; + if (!detail) { + return false; + } + if (detail.includes("not found")) { + return false; + } + if (detail.includes("failed to connect")) { + return false; + } + if (detail.includes("not been booted")) { + return false; + } + if (detail.includes("no such file or directory")) { + return false; + } + if (detail.includes("not supported")) { + return false; + } return false; } async function assertSystemdAvailable() { const res = await execSystemctl(["--user", "status"]); - if (res.code === 0) return; + if (res.code === 0) { + return; + } const detail = res.stderr || res.stdout; if (detail.toLowerCase().includes("not found")) { throw new Error("systemctl not available; systemd user services are required on Linux."); @@ -352,7 +384,9 @@ export type LegacySystemdUnit = { async function isSystemctlAvailable(): Promise { const res = await execSystemctl(["--user", "status"]); - if (res.code === 0) return true; + if (res.code === 0) { + return true; + } const detail = `${res.stderr || res.stdout}`.toLowerCase(); return !detail.includes("not found"); } @@ -391,7 +425,9 @@ export async function uninstallLegacySystemdUnits({ stdout: NodeJS.WritableStream; }): Promise { const units = await findLegacySystemdUnits(env); - if (units.length === 0) return units; + if (units.length === 0) { + return units; + } const systemctlAvailable = await isSystemctlAvailable(); for (const unit of units) { diff --git a/src/discord/accounts.ts b/src/discord/accounts.ts index a041a649a9..6692820a2d 100644 --- a/src/discord/accounts.ts +++ b/src/discord/accounts.ts @@ -14,19 +14,25 @@ export type ResolvedDiscordAccount = { function listConfiguredAccountIds(cfg: OpenClawConfig): string[] { const accounts = cfg.channels?.discord?.accounts; - if (!accounts || typeof accounts !== "object") return []; + if (!accounts || typeof accounts !== "object") { + return []; + } return Object.keys(accounts).filter(Boolean); } export function listDiscordAccountIds(cfg: OpenClawConfig): string[] { const ids = listConfiguredAccountIds(cfg); - if (ids.length === 0) return [DEFAULT_ACCOUNT_ID]; + if (ids.length === 0) { + return [DEFAULT_ACCOUNT_ID]; + } return ids.toSorted((a, b) => a.localeCompare(b)); } export function resolveDefaultDiscordAccountId(cfg: OpenClawConfig): string { const ids = listDiscordAccountIds(cfg); - if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID; + if (ids.includes(DEFAULT_ACCOUNT_ID)) { + return DEFAULT_ACCOUNT_ID; + } return ids[0] ?? DEFAULT_ACCOUNT_ID; } @@ -35,7 +41,9 @@ function resolveAccountConfig( accountId: string, ): DiscordAccountConfig | undefined { const accounts = cfg.channels?.discord?.accounts; - if (!accounts || typeof accounts !== "object") return undefined; + if (!accounts || typeof accounts !== "object") { + return undefined; + } return accounts[accountId] as DiscordAccountConfig | undefined; } diff --git a/src/discord/api.ts b/src/discord/api.ts index 7743420201..f8a88a5025 100644 --- a/src/discord/api.ts +++ b/src/discord/api.ts @@ -18,10 +18,14 @@ type DiscordApiErrorPayload = { function parseDiscordApiErrorPayload(text: string): DiscordApiErrorPayload | null { const trimmed = text.trim(); - if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return null; + if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) { + return null; + } try { const payload = JSON.parse(trimmed); - if (payload && typeof payload === "object") return payload as DiscordApiErrorPayload; + if (payload && typeof payload === "object") { + return payload as DiscordApiErrorPayload; + } } catch { return null; } @@ -34,22 +38,30 @@ function parseRetryAfterSeconds(text: string, response: Response): number | unde payload && typeof payload.retry_after === "number" && Number.isFinite(payload.retry_after) ? payload.retry_after : undefined; - if (retryAfter !== undefined) return retryAfter; + if (retryAfter !== undefined) { + return retryAfter; + } const header = response.headers.get("Retry-After"); - if (!header) return undefined; + if (!header) { + return undefined; + } const parsed = Number(header); return Number.isFinite(parsed) ? parsed : undefined; } function formatRetryAfterSeconds(value: number | undefined): string | undefined { - if (value === undefined || !Number.isFinite(value) || value < 0) return undefined; + if (value === undefined || !Number.isFinite(value) || value < 0) { + return undefined; + } const rounded = value < 10 ? value.toFixed(1) : Math.round(value).toString(); return `${rounded}s`; } function formatDiscordApiErrorText(text: string): string | undefined { const trimmed = text.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } const payload = parseDiscordApiErrorPayload(trimmed); if (!payload) { const looksJson = trimmed.startsWith("{") && trimmed.endsWith("}"); diff --git a/src/discord/audit.ts b/src/discord/audit.ts index 056ad43f32..9dfd1986ca 100644 --- a/src/discord/audit.ts +++ b/src/discord/audit.ts @@ -27,25 +27,41 @@ function isRecord(value: unknown): value is Record { } function shouldAuditChannelConfig(config: DiscordGuildChannelConfig | undefined) { - if (!config) return true; - if (config.allow === false) return false; - if (config.enabled === false) return false; + if (!config) { + return true; + } + if (config.allow === false) { + return false; + } + if (config.enabled === false) { + return false; + } return true; } function listConfiguredGuildChannelKeys( guilds: Record | undefined, ): string[] { - if (!guilds) return []; + if (!guilds) { + return []; + } const ids = new Set(); for (const entry of Object.values(guilds)) { - if (!entry || typeof entry !== "object") continue; + if (!entry || typeof entry !== "object") { + continue; + } const channelsRaw = (entry as { channels?: unknown }).channels; - if (!isRecord(channelsRaw)) continue; + if (!isRecord(channelsRaw)) { + continue; + } for (const [key, value] of Object.entries(channelsRaw)) { const channelId = String(key).trim(); - if (!channelId) continue; - if (!shouldAuditChannelConfig(value as DiscordGuildChannelConfig | undefined)) continue; + if (!channelId) { + continue; + } + if (!shouldAuditChannelConfig(value as DiscordGuildChannelConfig | undefined)) { + continue; + } ids.add(channelId); } } diff --git a/src/discord/chunk.test.ts b/src/discord/chunk.test.ts index 13ec1b8e75..c35b86e63f 100644 --- a/src/discord/chunk.test.ts +++ b/src/discord/chunk.test.ts @@ -10,7 +10,9 @@ function hasBalancedFences(chunk: string) { let open: { markerChar: string; markerLen: number } | null = null; for (const line of chunk.split("\n")) { const match = line.match(/^( {0,3})(`{3,}|~{3,})(.*)$/); - if (!match) continue; + if (!match) { + continue; + } const marker = match[2]; if (!open) { open = { markerChar: marker[0], markerLen: marker.length }; diff --git a/src/discord/chunk.ts b/src/discord/chunk.ts index 8f3980d0fc..242d5c74c2 100644 --- a/src/discord/chunk.ts +++ b/src/discord/chunk.ts @@ -24,13 +24,17 @@ const DEFAULT_MAX_LINES = 17; const FENCE_RE = /^( {0,3})(`{3,}|~{3,})(.*)$/; function countLines(text: string) { - if (!text) return 0; + if (!text) { + return 0; + } return text.split("\n").length; } function parseFenceLine(line: string): OpenFence | null { const match = line.match(FENCE_RE); - if (!match) return null; + if (!match) { + return null; + } const indent = match[1] ?? ""; const marker = match[2] ?? ""; return { @@ -46,10 +50,16 @@ function closeFenceLine(openFence: OpenFence) { } function closeFenceIfNeeded(text: string, openFence: OpenFence | null) { - if (!openFence) return text; + if (!openFence) { + return text; + } const closeLine = closeFenceLine(openFence); - if (!text) return closeLine; - if (!text.endsWith("\n")) return `${text}\n${closeLine}`; + if (!text) { + return closeLine; + } + if (!text.endsWith("\n")) { + return `${text}\n${closeLine}`; + } return `${text}${closeLine}`; } @@ -59,7 +69,9 @@ function splitLongLine( opts: { preserveWhitespace: boolean }, ): string[] { const limit = Math.max(1, Math.floor(maxChars)); - if (line.length <= limit) return [line]; + if (line.length <= limit) { + return [line]; + } const out: string[] = []; let remaining = line; while (remaining.length > limit) { @@ -76,12 +88,16 @@ function splitLongLine( break; } } - if (breakIdx <= 0) breakIdx = limit; + if (breakIdx <= 0) { + breakIdx = limit; + } out.push(remaining.slice(0, breakIdx)); // Keep the separator for the next segment so words don't get glued together. remaining = remaining.slice(breakIdx); } - if (remaining.length) out.push(remaining); + if (remaining.length) { + out.push(remaining); + } return out; } @@ -94,10 +110,14 @@ export function chunkDiscordText(text: string, opts: ChunkDiscordTextOpts = {}): const maxLines = Math.max(1, Math.floor(opts.maxLines ?? DEFAULT_MAX_LINES)); const body = text ?? ""; - if (!body) return []; + if (!body) { + return []; + } const alreadyOk = body.length <= maxChars && countLines(body) <= maxLines; - if (alreadyOk) return [body]; + if (alreadyOk) { + return [body]; + } const lines = body.split("\n"); const chunks: string[] = []; @@ -107,9 +127,13 @@ export function chunkDiscordText(text: string, opts: ChunkDiscordTextOpts = {}): let openFence: OpenFence | null = null; const flush = () => { - if (!current) return; + if (!current) { + return; + } const payload = closeFenceIfNeeded(current, openFence); - if (payload.trim().length) chunks.push(payload); + if (payload.trim().length) { + chunks.push(payload); + } current = ""; currentLines = 0; if (openFence) { @@ -162,7 +186,9 @@ export function chunkDiscordText(text: string, opts: ChunkDiscordTextOpts = {}): if (current.length > 0) { current += addition; - if (!isLineContinuation) currentLines += 1; + if (!isLineContinuation) { + currentLines += 1; + } } else { current = segment; currentLines = 1; @@ -174,7 +200,9 @@ export function chunkDiscordText(text: string, opts: ChunkDiscordTextOpts = {}): if (current.length) { const payload = closeFenceIfNeeded(current, openFence); - if (payload.trim().length) chunks.push(payload); + if (payload.trim().length) { + chunks.push(payload); + } } return rebalanceReasoningItalics(text, chunks); @@ -210,11 +238,15 @@ export function chunkDiscordTextWithMode( // each chunk and reopen at the start of the next so every chunk renders // consistently. function rebalanceReasoningItalics(source: string, chunks: string[]): string[] { - if (chunks.length <= 1) return chunks; + if (chunks.length <= 1) { + return chunks; + } const opensWithReasoningItalics = source.startsWith("Reasoning:\n_") && source.trimEnd().endsWith("_"); - if (!opensWithReasoningItalics) return chunks; + if (!opensWithReasoningItalics) { + return chunks; + } const adjusted = [...chunks]; for (let i = 0; i < adjusted.length; i++) { @@ -227,7 +259,9 @@ function rebalanceReasoningItalics(source: string, chunks: string[]): string[] { adjusted[i] = `${current}_`; } - if (isLast) break; + if (isLast) { + break; + } // Re-open italics on the next chunk if needed. const next = adjusted[i + 1]; diff --git a/src/discord/directory-live.ts b/src/discord/directory-live.ts index 4c466627a1..3797f70e08 100644 --- a/src/discord/directory-live.ts +++ b/src/discord/directory-live.ts @@ -23,7 +23,9 @@ export async function listDiscordDirectoryGroupsLive( ): Promise { const account = resolveDiscordAccount({ cfg: params.cfg, accountId: params.accountId }); const token = normalizeDiscordToken(account.token); - if (!token) return []; + if (!token) { + return []; + } const query = normalizeQuery(params.query); const guilds = await fetchDiscord("/users/@me/guilds", token); const rows: ChannelDirectoryEntry[] = []; @@ -32,8 +34,12 @@ export async function listDiscordDirectoryGroupsLive( const channels = await fetchDiscord(`/guilds/${guild.id}/channels`, token); for (const channel of channels) { const name = channel.name?.trim(); - if (!name) continue; - if (query && !normalizeDiscordSlug(name).includes(normalizeDiscordSlug(query))) continue; + if (!name) { + continue; + } + if (query && !normalizeDiscordSlug(name).includes(normalizeDiscordSlug(query))) { + continue; + } rows.push({ kind: "group", id: `channel:${channel.id}`, @@ -55,9 +61,13 @@ export async function listDiscordDirectoryPeersLive( ): Promise { const account = resolveDiscordAccount({ cfg: params.cfg, accountId: params.accountId }); const token = normalizeDiscordToken(account.token); - if (!token) return []; + if (!token) { + return []; + } const query = normalizeQuery(params.query); - if (!query) return []; + if (!query) { + return []; + } const guilds = await fetchDiscord("/users/@me/guilds", token); const rows: ChannelDirectoryEntry[] = []; @@ -74,7 +84,9 @@ export async function listDiscordDirectoryPeersLive( ); for (const member of members) { const user = member.user; - if (!user?.id) continue; + if (!user?.id) { + continue; + } const name = member.nick?.trim() || user.global_name?.trim() || user.username?.trim(); rows.push({ kind: "user", @@ -84,7 +96,9 @@ export async function listDiscordDirectoryPeersLive( rank: buildUserRank(user), raw: member, }); - if (rows.length >= limit) return rows; + if (rows.length >= limit) { + return rows; + } } } diff --git a/src/discord/gateway-logging.ts b/src/discord/gateway-logging.ts index 2c5b37609c..027ea1df89 100644 --- a/src/discord/gateway-logging.ts +++ b/src/discord/gateway-logging.ts @@ -15,8 +15,12 @@ const shouldPromoteGatewayDebug = (message: string) => INFO_DEBUG_MARKERS.some((marker) => message.includes(marker)); const formatGatewayMetrics = (metrics: unknown) => { - if (metrics === null || metrics === undefined) return String(metrics); - if (typeof metrics === "string") return metrics; + if (metrics === null || metrics === undefined) { + return String(metrics); + } + if (typeof metrics === "string") { + return metrics; + } if (typeof metrics === "number" || typeof metrics === "boolean" || typeof metrics === "bigint") { return String(metrics); } @@ -32,7 +36,9 @@ export function attachDiscordGatewayLogging(params: { runtime: RuntimeEnv; }) { const { emitter, runtime } = params; - if (!emitter) return () => {}; + if (!emitter) { + return () => {}; + } const onGatewayDebug = (msg: unknown) => { const message = String(msg); diff --git a/src/discord/monitor.gateway.ts b/src/discord/monitor.gateway.ts index 53c40950c6..5cb190e8d5 100644 --- a/src/discord/monitor.gateway.ts +++ b/src/discord/monitor.gateway.ts @@ -24,7 +24,9 @@ export async function waitForDiscordGatewayStop(params: { emitter?.removeListener("error", onGatewayErrorEvent); }; const finishResolve = () => { - if (settled) return; + if (settled) { + return; + } settled = true; cleanup(); try { @@ -34,7 +36,9 @@ export async function waitForDiscordGatewayStop(params: { } }; const finishReject = (err: unknown) => { - if (settled) return; + if (settled) { + return; + } settled = true; cleanup(); try { diff --git a/src/discord/monitor/allow-list.ts b/src/discord/monitor/allow-list.ts index a0954ec2f5..66fb8fd83f 100644 --- a/src/discord/monitor/allow-list.ts +++ b/src/discord/monitor/allow-list.ts @@ -53,13 +53,17 @@ export function normalizeDiscordAllowList( raw: Array | undefined, prefixes: string[], ) { - if (!raw || raw.length === 0) return null; + if (!raw || raw.length === 0) { + return null; + } const ids = new Set(); const names = new Set(); const allowAll = raw.some((entry) => String(entry).trim() === "*"); for (const entry of raw) { const text = String(entry).trim(); - if (!text || text === "*") continue; + if (!text || text === "*") { + continue; + } const normalized = normalizeDiscordSlug(text); const maybeId = text.replace(/^<@!?/, "").replace(/>$/, ""); if (/^\d+$/.test(maybeId)) { @@ -69,7 +73,9 @@ export function normalizeDiscordAllowList( const prefix = prefixes.find((entry) => text.startsWith(entry)); if (prefix) { const candidate = text.slice(prefix.length); - if (candidate) ids.add(candidate); + if (candidate) { + ids.add(candidate); + } continue; } if (normalized) { @@ -92,11 +98,19 @@ export function allowListMatches( list: DiscordAllowList, candidate: { id?: string; name?: string; tag?: string }, ) { - if (list.allowAll) return true; - if (candidate.id && list.ids.has(candidate.id)) return true; + if (list.allowAll) { + return true; + } + if (candidate.id && list.ids.has(candidate.id)) { + return true; + } const slug = candidate.name ? normalizeDiscordSlug(candidate.name) : ""; - if (slug && list.names.has(slug)) return true; - if (candidate.tag && list.names.has(normalizeDiscordSlug(candidate.tag))) return true; + if (slug && list.names.has(slug)) { + return true; + } + if (candidate.tag && list.names.has(normalizeDiscordSlug(candidate.tag))) { + return true; + } return false; } @@ -129,7 +143,9 @@ export function resolveDiscordUserAllowed(params: { userTag?: string; }) { const allowList = normalizeDiscordAllowList(params.allowList, ["discord:", "user:"]); - if (!allowList) return true; + if (!allowList) { + return true; + } return allowListMatches(allowList, { id: params.userId, name: params.userName, @@ -143,9 +159,13 @@ export function resolveDiscordCommandAuthorized(params: { guildInfo?: DiscordGuildEntryResolved | null; author: User; }) { - if (!params.isDirectMessage) return true; + if (!params.isDirectMessage) { + return true; + } const allowList = normalizeDiscordAllowList(params.allowFrom, ["discord:", "user:"]); - if (!allowList) return true; + if (!allowList) { + return true; + } return allowListMatches(allowList, { id: params.author.id, name: params.author.username, @@ -159,14 +179,22 @@ export function resolveDiscordGuildEntry(params: { }): DiscordGuildEntryResolved | null { const guild = params.guild; const entries = params.guildEntries; - if (!guild || !entries) return null; + if (!guild || !entries) { + return null; + } const byId = entries[guild.id]; - if (byId) return { ...byId, id: guild.id }; + if (byId) { + return { ...byId, id: guild.id }; + } const slug = normalizeDiscordSlug(guild.name ?? ""); const bySlug = entries[slug]; - if (bySlug) return { ...bySlug, id: guild.id, slug: slug || bySlug.slug }; + if (bySlug) { + return { ...bySlug, id: guild.id, slug: slug || bySlug.slug }; + } const wildcard = entries["*"]; - if (wildcard) return { ...wildcard, id: guild.id, slug: slug || wildcard.slug }; + if (wildcard) { + return { ...wildcard, id: guild.id, slug: slug || wildcard.slug }; + } return null; } @@ -227,7 +255,9 @@ export function resolveDiscordChannelConfig(params: { }): DiscordChannelConfigResolved | null { const { guildInfo, channelId, channelName, channelSlug } = params; const channels = guildInfo?.channels; - if (!channels) return null; + if (!channels) { + return null; + } const match = resolveDiscordChannelEntryMatch(channels, { id: channelId, name: channelName, @@ -258,7 +288,9 @@ export function resolveDiscordChannelConfigWithFallback(params: { scope, } = params; const channels = guildInfo?.channels; - if (!channels) return null; + if (!channels) { + return null; + } const resolvedParentSlug = parentSlug ?? (parentName ? normalizeDiscordSlug(parentName) : ""); const match = resolveDiscordChannelEntryMatch( channels, @@ -289,10 +321,14 @@ export function resolveDiscordShouldRequireMention(params: { /** Pass pre-computed value to avoid redundant checks. */ isAutoThreadOwnedByBot?: boolean; }): boolean { - if (!params.isGuildMessage) return false; + if (!params.isGuildMessage) { + return false; + } // Only skip mention requirement in threads created by the bot (when autoThread is enabled). const isBotThread = params.isAutoThreadOwnedByBot ?? isDiscordAutoThreadOwnedByBot(params); - if (isBotThread) return false; + if (isBotThread) { + return false; + } return params.channelConfig?.requireMention ?? params.guildInfo?.requireMention ?? true; } @@ -302,8 +338,12 @@ export function isDiscordAutoThreadOwnedByBot(params: { botId?: string | null; threadOwnerId?: string | null; }): boolean { - if (!params.isThread) return false; - if (!params.channelConfig?.autoThread) return false; + if (!params.isThread) { + return false; + } + if (!params.channelConfig?.autoThread) { + return false; + } const botId = params.botId?.trim(); const threadOwnerId = params.threadOwnerId?.trim(); return Boolean(botId && threadOwnerId && botId === threadOwnerId); @@ -316,10 +356,18 @@ export function isDiscordGroupAllowedByPolicy(params: { channelAllowed: boolean; }): boolean { const { groupPolicy, guildAllowlisted, channelAllowlistConfigured, channelAllowed } = params; - if (groupPolicy === "disabled") return false; - if (groupPolicy === "open") return true; - if (!guildAllowlisted) return false; - if (!channelAllowlistConfigured) return true; + if (groupPolicy === "disabled") { + return false; + } + if (groupPolicy === "open") { + return true; + } + if (!guildAllowlisted) { + return false; + } + if (!channelAllowlistConfigured) { + return true; + } return channelAllowed; } @@ -330,7 +378,9 @@ export function resolveGroupDmAllow(params: { channelSlug: string; }) { const { channels, channelId, channelName, channelSlug } = params; - if (!channels || channels.length === 0) return true; + if (!channels || channels.length === 0) { + return true; + } const allowList = new Set(channels.map((entry) => normalizeDiscordSlug(String(entry)))); const candidates = [ normalizeDiscordSlug(channelId), @@ -350,14 +400,20 @@ export function shouldEmitDiscordReactionNotification(params: { allowlist?: Array; }) { const mode = params.mode ?? "own"; - if (mode === "off") return false; - if (mode === "all") return true; + if (mode === "off") { + return false; + } + if (mode === "all") { + return true; + } if (mode === "own") { return Boolean(params.botId && params.messageAuthorId === params.botId); } if (mode === "allowlist") { const list = normalizeDiscordAllowList(params.allowlist, ["discord:", "user:"]); - if (!list) return false; + if (!list) { + return false; + } return allowListMatches(list, { id: params.userId, name: params.userName, diff --git a/src/discord/monitor/exec-approvals.ts b/src/discord/monitor/exec-approvals.ts index ed47e1fa79..014b35b1ed 100644 --- a/src/discord/monitor/exec-approvals.ts +++ b/src/discord/monitor/exec-approvals.ts @@ -65,12 +65,16 @@ export function buildExecApprovalCustomId( export function parseExecApprovalData( data: ComponentData, ): { approvalId: string; action: ExecApprovalDecision } | null { - if (!data || typeof data !== "object") return null; + if (!data || typeof data !== "object") { + return null; + } const coerce = (value: unknown) => typeof value === "string" || typeof value === "number" ? String(value) : ""; const rawId = coerce(data.id); const rawAction = coerce(data.action); - if (!rawId || !rawAction) return null; + if (!rawId || !rawAction) { + return null; + } const action = rawAction as ExecApprovalDecision; if (action !== "allow-once" && action !== "allow-always" && action !== "deny") { return null; @@ -205,19 +209,29 @@ export class DiscordExecApprovalHandler { shouldHandle(request: ExecApprovalRequest): boolean { const config = this.opts.config; - if (!config.enabled) return false; - if (!config.approvers || config.approvers.length === 0) return false; + if (!config.enabled) { + return false; + } + if (!config.approvers || config.approvers.length === 0) { + return false; + } // Check agent filter if (config.agentFilter?.length) { - if (!request.request.agentId) return false; - if (!config.agentFilter.includes(request.request.agentId)) return false; + if (!request.request.agentId) { + return false; + } + if (!config.agentFilter.includes(request.request.agentId)) { + return false; + } } // Check session filter (substring match) if (config.sessionFilter?.length) { const session = request.request.sessionKey; - if (!session) return false; + if (!session) { + return false; + } const matches = config.sessionFilter.some((p) => { try { return session.includes(p) || new RegExp(p).test(session); @@ -225,14 +239,18 @@ export class DiscordExecApprovalHandler { return session.includes(p); } }); - if (!matches) return false; + if (!matches) { + return false; + } } return true; } async start(): Promise { - if (this.started) return; + if (this.started) { + return; + } this.started = true; const config = this.opts.config; @@ -270,7 +288,9 @@ export class DiscordExecApprovalHandler { } async stop(): Promise { - if (!this.started) return; + if (!this.started) { + return; + } this.started = false; // Clear all pending timeouts @@ -297,7 +317,9 @@ export class DiscordExecApprovalHandler { } private async handleApprovalRequested(request: ExecApprovalRequest): Promise { - if (!this.shouldHandle(request)) return; + if (!this.shouldHandle(request)) { + return; + } logDebug(`discord exec approvals: received request ${request.id}`); @@ -394,7 +416,9 @@ export class DiscordExecApprovalHandler { private async handleApprovalResolved(resolved: ExecApprovalResolved): Promise { const pending = this.pending.get(resolved.id); - if (!pending) return; + if (!pending) { + return; + } clearTimeout(pending.timeoutId); this.pending.delete(resolved.id); @@ -402,7 +426,9 @@ export class DiscordExecApprovalHandler { const request = this.requestCache.get(resolved.id); this.requestCache.delete(resolved.id); - if (!request) return; + if (!request) { + return; + } logDebug(`discord exec approvals: resolved ${resolved.id} with ${resolved.decision}`); @@ -415,14 +441,18 @@ export class DiscordExecApprovalHandler { private async handleApprovalTimeout(approvalId: string): Promise { const pending = this.pending.get(approvalId); - if (!pending) return; + if (!pending) { + return; + } this.pending.delete(approvalId); const request = this.requestCache.get(approvalId); this.requestCache.delete(approvalId); - if (!request) return; + if (!request) { + return; + } logDebug(`discord exec approvals: timeout for ${approvalId}`); diff --git a/src/discord/monitor/format.ts b/src/discord/monitor/format.ts index fe4e5c59ea..2b47f00269 100644 --- a/src/discord/monitor/format.ts +++ b/src/discord/monitor/format.ts @@ -7,8 +7,12 @@ export function resolveDiscordSystemLocation(params: { channelName: string; }) { const { isDirectMessage, isGroupDm, guild, channelName } = params; - if (isDirectMessage) return "DM"; - if (isGroupDm) return `Group DM #${channelName}`; + if (isDirectMessage) { + return "DM"; + } + if (isGroupDm) { + return `Group DM #${channelName}`; + } return guild?.name ? `${guild.name} #${channelName}` : `#${channelName}`; } @@ -28,7 +32,9 @@ export function formatDiscordUserTag(user: User) { } export function resolveTimestampMs(timestamp?: string | null) { - if (!timestamp) return undefined; + if (!timestamp) { + return undefined; + } const parsed = Date.parse(timestamp); return Number.isNaN(parsed) ? undefined : parsed; } diff --git a/src/discord/monitor/listeners.ts b/src/discord/monitor/listeners.ts index 770ae6d6c6..14e7d86e21 100644 --- a/src/discord/monitor/listeners.ts +++ b/src/discord/monitor/listeners.ts @@ -41,7 +41,9 @@ function logSlowDiscordListener(params: { event: string; durationMs: number; }) { - if (params.durationMs < DISCORD_SLOW_LISTENER_THRESHOLD_MS) return; + if (params.durationMs < DISCORD_SLOW_LISTENER_THRESHOLD_MS) { + return; + } const duration = formatDurationSeconds(params.durationMs, { decimals: 1, unit: "seconds", @@ -180,10 +182,16 @@ async function handleDiscordReactionEvent(params: { }) { try { const { data, client, action, botUserId, guildEntries } = params; - if (!("user" in data)) return; + if (!("user" in data)) { + return; + } const user = data.user; - if (!user || user.bot) return; - if (!data.guild_id) return; + if (!user || user.bot) { + return; + } + if (!data.guild_id) { + return; + } const guildInfo = resolveDiscordGuildEntry({ guild: data.guild ?? undefined, @@ -194,7 +202,9 @@ async function handleDiscordReactionEvent(params: { } const channel = await client.fetchChannel(data.channel_id); - if (!channel) return; + if (!channel) { + return; + } const channelName = "name" in channel ? (channel.name ?? undefined) : undefined; const channelSlug = channelName ? normalizeDiscordSlug(channelName) : ""; const channelType = "type" in channel ? channel.type : undefined; @@ -226,9 +236,13 @@ async function handleDiscordReactionEvent(params: { parentSlug, scope: isThreadChannel ? "thread" : "channel", }); - if (channelConfig?.allowed === false) return; + if (channelConfig?.allowed === false) { + return; + } - if (botUserId && user.id === botUserId) return; + if (botUserId && user.id === botUserId) { + return; + } const reactionMode = guildInfo?.reactionNotifications ?? "own"; const message = await data.message.fetch().catch(() => null); @@ -242,7 +256,9 @@ async function handleDiscordReactionEvent(params: { userTag: formatDiscordUserTag(user), allowlist: guildInfo?.users, }); - if (!shouldNotify) return; + if (!shouldNotify) { + return; + } const emojiLabel = formatDiscordReactionEmoji(data.emoji); const actorLabel = formatDiscordUserTag(user); @@ -290,7 +306,9 @@ export class DiscordPresenceListener extends PresenceUpdateListener { "user" in data && data.user && typeof data.user === "object" && "id" in data.user ? String(data.user.id) : undefined; - if (!userId) return; + if (!userId) { + return; + } setPresence( this.accountId, userId, diff --git a/src/discord/monitor/message-handler.preflight.ts b/src/discord/monitor/message-handler.preflight.ts index 098533aedb..1f9ec0b725 100644 --- a/src/discord/monitor/message-handler.preflight.ts +++ b/src/discord/monitor/message-handler.preflight.ts @@ -61,12 +61,16 @@ export async function preflightDiscordMessage( const logger = getChildLogger({ module: "discord-auto-reply" }); const message = params.data.message; const author = params.data.author; - if (!author) return null; + if (!author) { + return null; + } const allowBots = params.discordConfig?.allowBots ?? false; if (author.bot) { // Always ignore own messages to prevent self-reply loops - if (params.botUserId && author.id === params.botUserId) return null; + if (params.botUserId && author.id === params.botUserId) { + return null; + } if (!allowBots) { logVerbose("discord: drop bot message (allowBots=false)"); return null; @@ -300,7 +304,9 @@ export async function preflightDiscordMessage( channelName: displayChannelName, channelSlug: displayChannelSlug, }); - if (isGroupDm && !groupDmAllowed) return null; + if (isGroupDm && !groupDmAllowed) { + return null; + } const channelAllowlistConfigured = Boolean(guildInfo?.channels) && Object.keys(guildInfo?.channels ?? {}).length > 0; diff --git a/src/discord/monitor/message-handler.ts b/src/discord/monitor/message-handler.ts index 2f839207b9..ab04ab051d 100644 --- a/src/discord/monitor/message-handler.ts +++ b/src/discord/monitor/message-handler.ts @@ -47,22 +47,34 @@ export function createDiscordMessageHandler(params: { buildKey: (entry) => { const message = entry.data.message; const authorId = entry.data.author?.id; - if (!message || !authorId) return null; + if (!message || !authorId) { + return null; + } const channelId = message.channelId; - if (!channelId) return null; + if (!channelId) { + return null; + } return `discord:${params.accountId}:${channelId}:${authorId}`; }, shouldDebounce: (entry) => { const message = entry.data.message; - if (!message) return false; - if (message.attachments && message.attachments.length > 0) return false; + if (!message) { + return false; + } + if (message.attachments && message.attachments.length > 0) { + return false; + } const baseText = resolveDiscordMessageText(message, { includeForwarded: false }); - if (!baseText.trim()) return false; + if (!baseText.trim()) { + return false; + } return !hasControlCommand(baseText, params.cfg); }, onFlush: async (entries) => { const last = entries.at(-1); - if (!last) return; + if (!last) { + return; + } if (entries.length === 1) { const ctx = await preflightDiscordMessage({ ...params, @@ -71,7 +83,9 @@ export function createDiscordMessageHandler(params: { data: last.data, client: last.client, }); - if (!ctx) return; + if (!ctx) { + return; + } await processDiscordMessage(ctx); return; } @@ -100,7 +114,9 @@ export function createDiscordMessageHandler(params: { data: syntheticData, client: last.client, }); - if (!ctx) return; + if (!ctx) { + return; + } if (entries.length > 1) { const ids = entries.map((entry) => entry.data.message?.id).filter(Boolean) as string[]; if (ids.length > 0) { diff --git a/src/discord/monitor/message-utils.ts b/src/discord/monitor/message-utils.ts index 2647e51131..1e6ad76603 100644 --- a/src/discord/monitor/message-utils.ts +++ b/src/discord/monitor/message-utils.ts @@ -55,7 +55,9 @@ export async function resolveDiscordChannelInfo( ): Promise { const cached = DISCORD_CHANNEL_INFO_CACHE.get(channelId); if (cached) { - if (cached.expiresAt > Date.now()) return cached.value; + if (cached.expiresAt > Date.now()) { + return cached.value; + } DISCORD_CHANNEL_INFO_CACHE.delete(channelId); } try { @@ -98,7 +100,9 @@ export async function resolveMediaList( maxBytes: number, ): Promise { const attachments = message.attachments ?? []; - if (attachments.length === 0) return []; + if (attachments.length === 0) { + return []; + } const out: DiscordMediaInfo[] = []; for (const attachment of attachments) { try { @@ -127,22 +131,34 @@ export async function resolveMediaList( function inferPlaceholder(attachment: APIAttachment): string { const mime = attachment.content_type ?? ""; - if (mime.startsWith("image/")) return ""; - if (mime.startsWith("video/")) return ""; - if (mime.startsWith("audio/")) return ""; + if (mime.startsWith("image/")) { + return ""; + } + if (mime.startsWith("video/")) { + return ""; + } + if (mime.startsWith("audio/")) { + return ""; + } return ""; } function isImageAttachment(attachment: APIAttachment): boolean { const mime = attachment.content_type ?? ""; - if (mime.startsWith("image/")) return true; + if (mime.startsWith("image/")) { + return true; + } const name = attachment.filename?.toLowerCase() ?? ""; - if (!name) return false; + if (!name) { + return false; + } return /\.(avif|bmp|gif|heic|heif|jpe?g|png|tiff?|webp)$/.test(name); } function buildDiscordAttachmentPlaceholder(attachments?: APIAttachment[]): string { - if (!attachments || attachments.length === 0) return ""; + if (!attachments || attachments.length === 0) { + return ""; + } const count = attachments.length; const allImages = attachments.every(isImageAttachment); const label = allImages ? "image" : "file"; @@ -161,22 +177,34 @@ export function resolveDiscordMessageText( message.embeds?.[0]?.description || options?.fallbackText?.trim() || ""; - if (!options?.includeForwarded) return baseText; + if (!options?.includeForwarded) { + return baseText; + } const forwardedText = resolveDiscordForwardedMessagesText(message); - if (!forwardedText) return baseText; - if (!baseText) return forwardedText; + if (!forwardedText) { + return baseText; + } + if (!baseText) { + return forwardedText; + } return `${baseText}\n${forwardedText}`; } function resolveDiscordForwardedMessagesText(message: Message): string { const snapshots = resolveDiscordMessageSnapshots(message); - if (snapshots.length === 0) return ""; + if (snapshots.length === 0) { + return ""; + } const forwardedBlocks = snapshots .map((snapshot) => { const snapshotMessage = snapshot.message; - if (!snapshotMessage) return null; + if (!snapshotMessage) { + return null; + } const text = resolveDiscordSnapshotMessageText(snapshotMessage); - if (!text) return null; + if (!text) { + return null; + } const authorLabel = formatDiscordSnapshotAuthor(snapshotMessage.author); const heading = authorLabel ? `[Forwarded message from ${authorLabel}]` @@ -184,7 +212,9 @@ function resolveDiscordForwardedMessagesText(message: Message): string { return `${heading}\n${text}`; }) .filter((entry): entry is string => Boolean(entry)); - if (forwardedBlocks.length === 0) return ""; + if (forwardedBlocks.length === 0) { + return ""; + } return forwardedBlocks.join("\n\n"); } @@ -194,7 +224,9 @@ function resolveDiscordMessageSnapshots(message: Message): DiscordMessageSnapsho rawData?.message_snapshots ?? (message as { message_snapshots?: unknown }).message_snapshots ?? (message as { messageSnapshots?: unknown }).messageSnapshots; - if (!Array.isArray(snapshots)) return []; + if (!Array.isArray(snapshots)) { + return []; + } return snapshots.filter( (entry): entry is DiscordMessageSnapshot => Boolean(entry) && typeof entry === "object", ); @@ -211,7 +243,9 @@ function resolveDiscordSnapshotMessageText(snapshot: DiscordSnapshotMessage): st function formatDiscordSnapshotAuthor( author: DiscordSnapshotAuthor | null | undefined, ): string | undefined { - if (!author) return undefined; + if (!author) { + return undefined; + } const globalName = author.global_name ?? undefined; const username = author.username ?? undefined; const name = author.name ?? undefined; @@ -220,8 +254,12 @@ function formatDiscordSnapshotAuthor( if (username && discriminator && discriminator !== "0") { return `@${username}#${discriminator}`; } - if (base) return `@${base}`; - if (author.id) return `@${author.id}`; + if (base) { + return `@${base}`; + } + if (author.id) { + return `@${author.id}`; + } return undefined; } diff --git a/src/discord/monitor/native-command.ts b/src/discord/monitor/native-command.ts index e53ff328ad..227a48f03d 100644 --- a/src/discord/monitor/native-command.ts +++ b/src/discord/monitor/native-command.ts @@ -63,7 +63,9 @@ function buildDiscordCommandOptions(params: { }): CommandOptions | undefined { const { command, cfg } = params; const args = command.args; - if (!args || args.length === 0) return undefined; + if (!args || args.length === 0) { + return undefined; + } return args.map((arg) => { const required = arg.required ?? false; if (arg.type === "number") { @@ -121,7 +123,9 @@ function readDiscordCommandArgs( interaction: CommandInteraction, definitions?: CommandArgDefinition[], ): CommandArgs | undefined { - if (!definitions || definitions.length === 0) return undefined; + if (!definitions || definitions.length === 0) { + return undefined; + } const values: CommandArgValues = {}; for (const definition of definitions) { let value: string | number | boolean | null | undefined; @@ -140,7 +144,9 @@ function readDiscordCommandArgs( } function chunkItems(items: T[], size: number): T[][] { - if (size <= 0) return [items]; + if (size <= 0) { + return [items]; + } const rows: T[][] = []; for (let i = 0; i < items.length; i += size) { rows.push(items.slice(i, i + size)); @@ -168,16 +174,24 @@ function decodeDiscordCommandArgValue(value: string): string { } function isDiscordUnknownInteraction(error: unknown): boolean { - if (!error || typeof error !== "object") return false; + if (!error || typeof error !== "object") { + return false; + } const err = error as { discordCode?: number; status?: number; message?: string; rawBody?: { code?: number; message?: string }; }; - if (err.discordCode === 10062 || err.rawBody?.code === 10062) return true; - if (err.status === 404 && /Unknown interaction/i.test(err.message ?? "")) return true; - if (/Unknown interaction/i.test(err.rawBody?.message ?? "")) return true; + if (err.discordCode === 10062 || err.rawBody?.code === 10062) { + return true; + } + if (err.status === 404 && /Unknown interaction/i.test(err.message ?? "")) { + return true; + } + if (/Unknown interaction/i.test(err.rawBody?.message ?? "")) { + return true; + } return false; } @@ -213,14 +227,18 @@ function buildDiscordCommandArgCustomId(params: { function parseDiscordCommandArgData( data: ComponentData, ): { command: string; arg: string; value: string; userId: string } | null { - if (!data || typeof data !== "object") return null; + if (!data || typeof data !== "object") { + return null; + } const coerce = (value: unknown) => typeof value === "string" || typeof value === "number" ? String(value) : ""; const rawCommand = coerce(data.command); const rawArg = coerce(data.arg); const rawValue = coerce(data.value); const rawUser = coerce(data.user); - if (!rawCommand || !rawArg || !rawValue || !rawUser) return null; + if (!rawCommand || !rawArg || !rawValue || !rawUser) { + return null; + } return { command: decodeDiscordCommandArgValue(rawCommand), arg: decodeDiscordCommandArgValue(rawArg), @@ -273,7 +291,9 @@ async function handleDiscordCommandArgInteraction( components: [], }), ); - if (!updated) return; + if (!updated) { + return; + } const commandArgs = createCommandArgsWithValue({ argName: parsed.arg, value: parsed.value, @@ -502,7 +522,9 @@ async function dispatchDiscordCommandInteraction(params: { const useAccessGroups = cfg.commands?.useAccessGroups !== false; const user = interaction.user; - if (!user) return; + if (!user) { + return; + } const channel = interaction.channel; const channelType = channel?.type; const isDirectMessage = channelType === ChannelType.DM; @@ -851,25 +873,35 @@ async function deliverDiscordInteractionReply(params: { maxLines: maxLinesPerMessage, chunkMode, }); - if (!chunks.length && text) chunks.push(text); + if (!chunks.length && text) { + chunks.push(text); + } const caption = chunks[0] ?? ""; await sendMessage(caption, media); for (const chunk of chunks.slice(1)) { - if (!chunk.trim()) continue; + if (!chunk.trim()) { + continue; + } await interaction.followUp({ content: chunk }); } return; } - if (!text.trim()) return; + if (!text.trim()) { + return; + } const chunks = chunkDiscordTextWithMode(text, { maxChars: textLimit, maxLines: maxLinesPerMessage, chunkMode, }); - if (!chunks.length && text) chunks.push(text); + if (!chunks.length && text) { + chunks.push(text); + } for (const chunk of chunks) { - if (!chunk.trim()) continue; + if (!chunk.trim()) { + continue; + } await sendMessage(chunk); } } diff --git a/src/discord/monitor/provider.ts b/src/discord/monitor/provider.ts index f9dfecdb1a..dbc2ee1e14 100644 --- a/src/discord/monitor/provider.ts +++ b/src/discord/monitor/provider.ts @@ -52,14 +52,18 @@ export type MonitorDiscordOpts = { }; function summarizeAllowList(list?: Array) { - if (!list || list.length === 0) return "any"; + if (!list || list.length === 0) { + return "any"; + } const sample = list.slice(0, 4).map((entry) => String(entry)); const suffix = list.length > sample.length ? ` (+${list.length - sample.length})` : ""; return `${sample.join(", ")}${suffix}`; } function summarizeGuilds(entries?: Record) { - if (!entries || Object.keys(entries).length === 0) return "any"; + if (!entries || Object.keys(entries).length === 0) { + return "any"; + } const keys = Object.keys(entries); const sample = keys.slice(0, 4); const suffix = keys.length > sample.length ? ` (+${keys.length - sample.length})` : ""; @@ -71,7 +75,9 @@ async function deployDiscordCommands(params: { runtime: RuntimeEnv; enabled: boolean; }) { - if (!params.enabled) return; + if (!params.enabled) { + return; + } const runWithRetry = createDiscordRetryRunner({ verbose: shouldLogVerbose() }); try { await runWithRetry(() => params.client.handleDeployRequest(), "command deploy"); @@ -84,12 +90,16 @@ async function deployDiscordCommands(params: { } function formatDiscordDeployErrorDetails(err: unknown): string { - if (!err || typeof err !== "object") return ""; + if (!err || typeof err !== "object") { + return ""; + } const status = (err as { status?: unknown }).status; const discordCode = (err as { discordCode?: unknown }).discordCode; const rawBody = (err as { rawBody?: unknown }).rawBody; const details: string[] = []; - if (typeof status === "number") details.push(`status=${status}`); + if (typeof status === "number") { + details.push(`status=${status}`); + } if (typeof discordCode === "number" || typeof discordCode === "string") { details.push(`code=${discordCode}`); } @@ -204,7 +214,9 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) { try { const entries: Array<{ input: string; guildKey: string; channelKey?: string }> = []; for (const [guildKey, guildCfg] of Object.entries(guildEntries)) { - if (guildKey === "*") continue; + if (guildKey === "*") { + continue; + } const channels = guildCfg?.channels ?? {}; const channelKeys = Object.keys(channels).filter((key) => key !== "*"); if (channelKeys.length === 0) { @@ -229,7 +241,9 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) { const unresolved: string[] = []; for (const entry of resolved) { const source = entries.find((item) => item.input === entry.input); - if (!source) continue; + if (!source) { + continue; + } const sourceGuild = guildEntries?.[source.guildKey] ?? {}; if (!entry.resolved || !entry.guildId) { unresolved.push(entry.input); @@ -301,22 +315,32 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) { if (guildEntries && Object.keys(guildEntries).length > 0) { const userEntries = new Set(); for (const guild of Object.values(guildEntries)) { - if (!guild || typeof guild !== "object") continue; + if (!guild || typeof guild !== "object") { + continue; + } const users = (guild as { users?: Array }).users; if (Array.isArray(users)) { for (const entry of users) { const trimmed = String(entry).trim(); - if (trimmed && trimmed !== "*") userEntries.add(trimmed); + if (trimmed && trimmed !== "*") { + userEntries.add(trimmed); + } } } const channels = (guild as { channels?: Record }).channels ?? {}; for (const channel of Object.values(channels)) { - if (!channel || typeof channel !== "object") continue; + if (!channel || typeof channel !== "object") { + continue; + } const channelUsers = (channel as { users?: Array }).users; - if (!Array.isArray(channelUsers)) continue; + if (!Array.isArray(channelUsers)) { + continue; + } for (const entry of channelUsers) { const trimmed = String(entry).trim(); - if (trimmed && trimmed !== "*") userEntries.add(trimmed); + if (trimmed && trimmed !== "*") { + userEntries.add(trimmed); + } } } } @@ -337,7 +361,9 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) { const nextGuilds = { ...guildEntries }; for (const [guildKey, guildConfig] of Object.entries(guildEntries ?? {})) { - if (!guildConfig || typeof guildConfig !== "object") continue; + if (!guildConfig || typeof guildConfig !== "object") { + continue; + } const nextGuild = { ...guildConfig } as Record; const users = (guildConfig as { users?: Array }).users; if (Array.isArray(users) && users.length > 0) { @@ -345,7 +371,9 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) { for (const entry of users) { const trimmed = String(entry).trim(); const resolved = resolvedMap.get(trimmed); - if (resolved?.resolved && resolved.id) additions.push(resolved.id); + if (resolved?.resolved && resolved.id) { + additions.push(resolved.id); + } } nextGuild.users = mergeAllowlist({ existing: users, additions }); } @@ -353,14 +381,20 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) { if (channels && typeof channels === "object") { const nextChannels: Record = { ...channels }; for (const [channelKey, channelConfig] of Object.entries(channels)) { - if (!channelConfig || typeof channelConfig !== "object") continue; + if (!channelConfig || typeof channelConfig !== "object") { + continue; + } const channelUsers = (channelConfig as { users?: Array }).users; - if (!Array.isArray(channelUsers) || channelUsers.length === 0) continue; + if (!Array.isArray(channelUsers) || channelUsers.length === 0) { + continue; + } const additions: string[] = []; for (const entry of channelUsers) { const trimmed = String(entry).trim(); const resolved = resolvedMap.get(trimmed); - if (resolved?.resolved && resolved.id) additions.push(resolved.id); + if (resolved?.resolved && resolved.id) { + additions.push(resolved.id); + } } nextChannels[channelKey] = { ...channelConfig, @@ -564,7 +598,9 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) { }); const abortSignal = opts.abortSignal; const onAbort = () => { - if (!gateway) return; + if (!gateway) { + return; + } // Carbon emits an error when maxAttempts is 0; keep a one-shot listener to avoid // an unhandled error after we tear down listeners during abort. gatewayEmitter?.once("error", () => {}); @@ -581,8 +617,12 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) { let helloTimeoutId: ReturnType | undefined; const onGatewayDebug = (msg: unknown) => { const message = String(msg); - if (!message.includes("WebSocket connection opened")) return; - if (helloTimeoutId) clearTimeout(helloTimeoutId); + if (!message.includes("WebSocket connection opened")) { + return; + } + if (helloTimeoutId) { + clearTimeout(helloTimeoutId); + } helloTimeoutId = setTimeout(() => { if (!gateway?.isConnected) { runtime.log?.( @@ -618,7 +658,9 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) { }); } finally { stopGatewayLogging(); - if (helloTimeoutId) clearTimeout(helloTimeoutId); + if (helloTimeoutId) { + clearTimeout(helloTimeoutId); + } gatewayEmitter?.removeListener("debug", onGatewayDebug); abortSignal?.removeEventListener("abort", onAbort); if (execApprovalsHandler) { diff --git a/src/discord/monitor/reply-context.ts b/src/discord/monitor/reply-context.ts index a3095050fd..6a1a5635b7 100644 --- a/src/discord/monitor/reply-context.ts +++ b/src/discord/monitor/reply-context.ts @@ -9,11 +9,15 @@ export function resolveReplyContext( options?: { envelope?: EnvelopeFormatOptions }, ): string | null { const referenced = message.referencedMessage; - if (!referenced?.author) return null; + if (!referenced?.author) { + return null; + } const referencedText = resolveDiscordMessageText(referenced, { includeForwarded: true, }); - if (!referencedText) return null; + if (!referencedText) { + return null; + } const fromLabel = referenced.author ? buildDirectLabel(referenced.author) : "Unknown"; const body = `${referencedText}\n[discord message id: ${referenced.id} channel: ${referenced.channelId} from: ${formatDiscordUserTag(referenced.author)} user id:${referenced.author?.id ?? "unknown"}]`; return formatAgentEnvelope({ diff --git a/src/discord/monitor/reply-delivery.ts b/src/discord/monitor/reply-delivery.ts index 3b63f88420..e2b01beaef 100644 --- a/src/discord/monitor/reply-delivery.ts +++ b/src/discord/monitor/reply-delivery.ts @@ -27,7 +27,9 @@ export async function deliverDiscordReply(params: { const rawText = payload.text ?? ""; const tableMode = params.tableMode ?? "code"; const text = convertMarkdownTables(rawText, tableMode); - if (!text && mediaList.length === 0) continue; + if (!text && mediaList.length === 0) { + continue; + } const replyTo = params.replyToId?.trim() || undefined; if (mediaList.length === 0) { @@ -38,10 +40,14 @@ export async function deliverDiscordReply(params: { maxLines: params.maxLinesPerMessage, chunkMode: mode, }); - if (!chunks.length && text) chunks.push(text); + if (!chunks.length && text) { + chunks.push(text); + } for (const chunk of chunks) { const trimmed = chunk.trim(); - if (!trimmed) continue; + if (!trimmed) { + continue; + } await sendMessageDiscord(params.target, trimmed, { token: params.token, rest: params.rest, @@ -54,7 +60,9 @@ export async function deliverDiscordReply(params: { } const firstMedia = mediaList[0]; - if (!firstMedia) continue; + if (!firstMedia) { + continue; + } await sendMessageDiscord(params.target, text, { token: params.token, rest: params.rest, diff --git a/src/discord/monitor/threading.ts b/src/discord/monitor/threading.ts index 71af6408fa..8ffa264fda 100644 --- a/src/discord/monitor/threading.ts +++ b/src/discord/monitor/threading.ts @@ -48,7 +48,9 @@ export function resolveDiscordThreadChannel(params: { message: DiscordMessageEvent["message"]; channelInfo: import("./message-utils.js").DiscordChannelInfo | null; }): DiscordThreadChannel | null { - if (!params.isGuildMessage) return null; + if (!params.isGuildMessage) { + return null; + } const { message, channelInfo } = params; const channel = "channel" in message ? (message as { channel?: unknown }).channel : undefined; const isThreadChannel = @@ -57,8 +59,12 @@ export function resolveDiscordThreadChannel(params: { "isThread" in channel && typeof (channel as { isThread?: unknown }).isThread === "function" && (channel as { isThread: () => boolean }).isThread(); - if (isThreadChannel) return channel as unknown as DiscordThreadChannel; - if (!isDiscordThreadType(channelInfo?.type)) return null; + if (isThreadChannel) { + return channel as unknown as DiscordThreadChannel; + } + if (!isDiscordThreadType(channelInfo?.type)) { + return null; + } return { id: message.channelId, name: channelInfo?.name ?? undefined, @@ -76,7 +82,9 @@ export async function resolveDiscordThreadParentInfo(params: { const { threadChannel, channelInfo, client } = params; const parentId = threadChannel.parentId ?? threadChannel.parent?.id ?? channelInfo?.parentId ?? undefined; - if (!parentId) return {}; + if (!parentId) { + return {}; + } let parentName = threadChannel.parent?.name; const parentInfo = await resolveDiscordChannelInfo(client, parentId); parentName = parentName ?? parentInfo?.name; @@ -93,13 +101,17 @@ export async function resolveDiscordThreadStarter(params: { }): Promise { const cacheKey = params.channel.id; const cached = DISCORD_THREAD_STARTER_CACHE.get(cacheKey); - if (cached) return cached; + if (cached) { + return cached; + } try { const parentType = params.parentType; const isForumParent = parentType === ChannelType.GuildForum || parentType === ChannelType.GuildMedia; const messageChannelId = isForumParent ? params.channel.id : params.parentId; - if (!messageChannelId) return null; + if (!messageChannelId) { + return null; + } const starter = (await params.client.rest.get( Routes.channelMessage(messageChannelId, params.channel.id), )) as { @@ -113,9 +125,13 @@ export async function resolveDiscordThreadStarter(params: { }; timestamp?: string | null; }; - if (!starter) return null; + if (!starter) { + return null; + } const text = starter.content?.trim() ?? starter.embeds?.[0]?.description?.trim() ?? ""; - if (!text) return null; + if (!text) { + return null; + } const author = starter.member?.nick ?? starter.member?.displayName ?? @@ -142,10 +158,16 @@ export function resolveDiscordReplyTarget(opts: { replyToId?: string; hasReplied: boolean; }): string | undefined { - if (opts.replyToMode === "off") return undefined; + if (opts.replyToMode === "off") { + return undefined; + } const replyToId = opts.replyToId?.trim(); - if (!replyToId) return undefined; - if (opts.replyToMode === "all") return replyToId; + if (!replyToId) { + return undefined; + } + if (opts.replyToMode === "all") { + return replyToId; + } return opts.hasReplied ? undefined : replyToId; } @@ -183,9 +205,13 @@ export function resolveDiscordAutoThreadContext(params: { createdThreadId?: string | null; }): DiscordAutoThreadContext | null { const createdThreadId = String(params.createdThreadId ?? "").trim(); - if (!createdThreadId) return null; + if (!createdThreadId) { + return null; + } const messageChannelId = params.messageChannelId.trim(); - if (!messageChannelId) return null; + if (!messageChannelId) { + return null; + } const threadSessionKey = buildAgentSessionKey({ agentId: params.agentId, @@ -262,9 +288,15 @@ export async function maybeCreateDiscordAutoThread(params: { baseText: string; combinedBody: string; }): Promise { - if (!params.isGuildMessage) return undefined; - if (!params.channelConfig?.autoThread) return undefined; - if (params.threadChannel) return undefined; + if (!params.isGuildMessage) { + return undefined; + } + if (!params.channelConfig?.autoThread) { + return undefined; + } + if (params.threadChannel) { + return undefined; + } try { const threadName = sanitizeDiscordThreadName( params.baseText || params.combinedBody || "Thread", diff --git a/src/discord/monitor/typing.ts b/src/discord/monitor/typing.ts index e9ce734d4e..9c53612277 100644 --- a/src/discord/monitor/typing.ts +++ b/src/discord/monitor/typing.ts @@ -2,7 +2,9 @@ import type { Client } from "@buape/carbon"; export async function sendTyping(params: { client: Client; channelId: string }) { const channel = await params.client.fetchChannel(params.channelId); - if (!channel) return; + if (!channel) { + return; + } if ("triggerTyping" in channel && typeof channel.triggerTyping === "function") { await channel.triggerTyping(); } diff --git a/src/discord/probe.ts b/src/discord/probe.ts index 78175c3b98..f50bccc0f2 100644 --- a/src/discord/probe.ts +++ b/src/discord/probe.ts @@ -37,8 +37,12 @@ export function resolveDiscordPrivilegedIntentsFromFlags( flags: number, ): DiscordPrivilegedIntentsSummary { const resolve = (enabledBit: number, limitedBit: number) => { - if ((flags & enabledBit) !== 0) return "enabled"; - if ((flags & limitedBit) !== 0) return "limited"; + if ((flags & enabledBit) !== 0) { + return "enabled"; + } + if ((flags & limitedBit) !== 0) { + return "limited"; + } return "disabled"; }; return { @@ -60,7 +64,9 @@ export async function fetchDiscordApplicationSummary( fetcher: typeof fetch = fetch, ): Promise { const normalized = normalizeDiscordToken(token); - if (!normalized) return undefined; + if (!normalized) { + return undefined; + } try { const res = await fetchWithTimeout( `${DISCORD_API_BASE}/oauth2/applications/@me`, @@ -70,7 +76,9 @@ export async function fetchDiscordApplicationSummary( Authorization: `Bot ${normalized}`, }, ); - if (!res.ok) return undefined; + if (!res.ok) { + return undefined; + } const json = (await res.json()) as { id?: string; flags?: number }; const flags = typeof json.flags === "number" && Number.isFinite(json.flags) ? json.flags : undefined; @@ -162,7 +170,9 @@ export async function fetchDiscordApplicationId( fetcher: typeof fetch = fetch, ): Promise { const normalized = normalizeDiscordToken(token); - if (!normalized) return undefined; + if (!normalized) { + return undefined; + } try { const res = await fetchWithTimeout( `${DISCORD_API_BASE}/oauth2/applications/@me`, @@ -172,7 +182,9 @@ export async function fetchDiscordApplicationId( Authorization: `Bot ${normalized}`, }, ); - if (!res.ok) return undefined; + if (!res.ok) { + return undefined; + } const json = (await res.json()) as { id?: string }; return json.id ?? undefined; } catch { diff --git a/src/discord/resolve-channels.ts b/src/discord/resolve-channels.ts index 6035d21207..c59cb73239 100644 --- a/src/discord/resolve-channels.ts +++ b/src/discord/resolve-channels.ts @@ -35,11 +35,17 @@ function parseDiscordChannelInput(raw: string): { guildOnly?: boolean; } { const trimmed = raw.trim(); - if (!trimmed) return {}; + if (!trimmed) { + return {}; + } const mention = trimmed.match(/^<#(\d+)>$/); - if (mention) return { channelId: mention[1] }; + if (mention) { + return { channelId: mention[1] }; + } const channelPrefix = trimmed.match(/^(?:channel:|discord:)?(\d+)$/i); - if (channelPrefix) return { channelId: channelPrefix[1] }; + if (channelPrefix) { + return { channelId: channelPrefix[1] }; + } const guildPrefix = trimmed.match(/^(?:guild:|server:)?(\d+)$/i); if (guildPrefix && !trimmed.includes("/") && !trimmed.includes("#")) { return { guildId: guildPrefix[1], guildOnly: true }; @@ -51,7 +57,9 @@ function parseDiscordChannelInput(raw: string): { if (!channel) { return guild ? { guild: guild.trim(), guildOnly: true } : {}; } - if (guild && /^\d+$/.test(guild)) return { guildId: guild, channel }; + if (guild && /^\d+$/.test(guild)) { + return { guildId: guild, channel }; + } return { guild, channel }; } return { guild: trimmed, guildOnly: true }; @@ -100,7 +108,9 @@ async function fetchChannel( channelId: string, ): Promise { const raw = await fetchDiscord(`/channels/${channelId}`, token, fetcher); - if (!raw || !("guild_id" in raw)) return null; + if (!raw || !("guild_id" in raw)) { + return null; + } return { id: raw.id, name: "name" in raw ? (raw.name ?? "") : "", @@ -110,7 +120,9 @@ async function fetchChannel( } function preferActiveMatch(candidates: DiscordChannelSummary[]): DiscordChannelSummary | undefined { - if (candidates.length === 0) return undefined; + if (candidates.length === 0) { + return undefined; + } const scored = candidates.map((channel) => { const isThread = channel.type === 11 || channel.type === 12; const archived = Boolean(channel.archived); @@ -126,7 +138,9 @@ function resolveGuildByName( input: string, ): DiscordGuildSummary | undefined { const slug = normalizeDiscordSlug(input); - if (!slug) return undefined; + if (!slug) { + return undefined; + } return guilds.find((guild) => guild.slug === slug); } @@ -136,17 +150,20 @@ export async function resolveDiscordChannelAllowlist(params: { fetcher?: typeof fetch; }): Promise { const token = normalizeDiscordToken(params.token); - if (!token) + if (!token) { return params.entries.map((input) => ({ input, resolved: false, })); + } const fetcher = params.fetcher ?? fetch; const guilds = await listGuilds(token, fetcher); const channelsByGuild = new Map>(); const getChannels = (guildId: string) => { const existing = channelsByGuild.get(guildId); - if (existing) return existing; + if (existing) { + return existing; + } const promise = listGuildChannels(token, fetcher, guildId); channelsByGuild.set(guildId, promise); return promise; diff --git a/src/discord/resolve-users.ts b/src/discord/resolve-users.ts index 1956ee8625..bb3dd42de9 100644 --- a/src/discord/resolve-users.ts +++ b/src/discord/resolve-users.ts @@ -38,16 +38,24 @@ function parseDiscordUserInput(raw: string): { userName?: string; } { const trimmed = raw.trim(); - if (!trimmed) return {}; + if (!trimmed) { + return {}; + } const mention = trimmed.match(/^<@!?(\d+)>$/); - if (mention) return { userId: mention[1] }; + if (mention) { + return { userId: mention[1] }; + } const prefixed = trimmed.match(/^(?:user:|discord:)?(\d+)$/i); - if (prefixed) return { userId: prefixed[1] }; + if (prefixed) { + return { userId: prefixed[1] }; + } const split = trimmed.includes("/") ? trimmed.split("/") : trimmed.split("#"); if (split.length >= 2) { const guild = split[0]?.trim(); const user = split.slice(1).join("#").trim(); - if (guild && /^\d+$/.test(guild)) return { guildId: guild, userName: user }; + if (guild && /^\d+$/.test(guild)) { + return { guildId: guild, userName: user }; + } return { guildName: guild, userName: user }; } return { userName: trimmed.replace(/^@/, "") }; @@ -73,9 +81,15 @@ function scoreDiscordMember(member: DiscordMember, query: string): number { .map((value) => value?.toLowerCase()) .filter(Boolean) as string[]; let score = 0; - if (candidates.some((value) => value === q)) score += 3; - if (candidates.some((value) => value?.includes(q))) score += 1; - if (!user.bot) score += 1; + if (candidates.some((value) => value === q)) { + score += 3; + } + if (candidates.some((value) => value?.includes(q))) { + score += 1; + } + if (!user.bot) { + score += 1; + } return score; } @@ -85,11 +99,12 @@ export async function resolveDiscordUserAllowlist(params: { fetcher?: typeof fetch; }): Promise { const token = normalizeDiscordToken(params.token); - if (!token) + if (!token) { return params.entries.map((input) => ({ input, resolved: false, })); + } const fetcher = params.fetcher ?? fetch; const guilds = await listGuilds(token, fetcher); const results: DiscordUserResolution[] = []; @@ -133,7 +148,9 @@ export async function resolveDiscordUserAllowlist(params: { ); for (const member of members) { const score = scoreDiscordMember(member, query); - if (score === 0) continue; + if (score === 0) { + continue; + } matches += 1; if (!best || score > best.score) { best = { member, guild, score }; diff --git a/src/discord/send.channels.ts b/src/discord/send.channels.ts index 25f665f19a..7b13e1445d 100644 --- a/src/discord/send.channels.ts +++ b/src/discord/send.channels.ts @@ -17,11 +17,21 @@ export async function createChannelDiscord( const body: Record = { name: payload.name, }; - if (payload.type !== undefined) body.type = payload.type; - if (payload.parentId) body.parent_id = payload.parentId; - if (payload.topic) body.topic = payload.topic; - if (payload.position !== undefined) body.position = payload.position; - if (payload.nsfw !== undefined) body.nsfw = payload.nsfw; + if (payload.type !== undefined) { + body.type = payload.type; + } + if (payload.parentId) { + body.parent_id = payload.parentId; + } + if (payload.topic) { + body.topic = payload.topic; + } + if (payload.position !== undefined) { + body.position = payload.position; + } + if (payload.nsfw !== undefined) { + body.nsfw = payload.nsfw; + } return (await rest.post(Routes.guildChannels(payload.guildId), { body, })) as APIChannel; @@ -33,12 +43,24 @@ export async function editChannelDiscord( ): Promise { const rest = resolveDiscordRest(opts); const body: Record = {}; - if (payload.name !== undefined) body.name = payload.name; - if (payload.topic !== undefined) body.topic = payload.topic; - if (payload.position !== undefined) body.position = payload.position; - if (payload.parentId !== undefined) body.parent_id = payload.parentId; - if (payload.nsfw !== undefined) body.nsfw = payload.nsfw; - if (payload.rateLimitPerUser !== undefined) body.rate_limit_per_user = payload.rateLimitPerUser; + if (payload.name !== undefined) { + body.name = payload.name; + } + if (payload.topic !== undefined) { + body.topic = payload.topic; + } + if (payload.position !== undefined) { + body.position = payload.position; + } + if (payload.parentId !== undefined) { + body.parent_id = payload.parentId; + } + if (payload.nsfw !== undefined) { + body.nsfw = payload.nsfw; + } + if (payload.rateLimitPerUser !== undefined) { + body.rate_limit_per_user = payload.rateLimitPerUser; + } return (await rest.patch(Routes.channel(payload.channelId), { body, })) as APIChannel; @@ -71,8 +93,12 @@ export async function setChannelPermissionDiscord( const body: Record = { type: payload.targetType, }; - if (payload.allow !== undefined) body.allow = payload.allow; - if (payload.deny !== undefined) body.deny = payload.deny; + if (payload.allow !== undefined) { + body.allow = payload.allow; + } + if (payload.deny !== undefined) { + body.deny = payload.deny; + } await rest.put(`/channels/${payload.channelId}/permissions/${payload.targetId}`, { body }); return { ok: true }; } diff --git a/src/discord/send.messages.ts b/src/discord/send.messages.ts index 93bc378d70..0a2d065815 100644 --- a/src/discord/send.messages.ts +++ b/src/discord/send.messages.ts @@ -21,10 +21,18 @@ export async function readMessagesDiscord( ? Math.min(Math.max(Math.floor(query.limit), 1), 100) : undefined; const params: Record = {}; - if (limit) params.limit = limit; - if (query.before) params.before = query.before; - if (query.after) params.after = query.after; - if (query.around) params.around = query.around; + if (limit) { + params.limit = limit; + } + if (query.before) { + params.before = query.before; + } + if (query.after) { + params.after = query.after; + } + if (query.around) { + params.around = query.around; + } return (await rest.get(Routes.channelMessages(channelId), params)) as APIMessage[]; } @@ -108,8 +116,12 @@ export async function listThreadsDiscord(payload: DiscordThreadList, opts: Disco throw new Error("channelId required to list archived threads"); } const params: Record = {}; - if (payload.before) params.before = payload.before; - if (payload.limit) params.limit = payload.limit; + if (payload.before) { + params.before = payload.before; + } + if (payload.limit) { + params.limit = payload.limit; + } return await rest.get(Routes.channelThreads(payload.channelId, "public"), params); } return await rest.get(Routes.guildActiveThreads(payload.guildId)); diff --git a/src/discord/send.permissions.ts b/src/discord/send.permissions.ts index 2f67b2e84e..23ca5ac069 100644 --- a/src/discord/send.permissions.ts +++ b/src/discord/send.permissions.ts @@ -22,7 +22,9 @@ type DiscordClientOpts = { function resolveToken(params: { explicit?: string; accountId: string; fallbackToken?: string }) { const explicit = normalizeDiscordToken(params.explicit); - if (explicit) return explicit; + if (explicit) { + return explicit; + } const fallback = normalizeDiscordToken(params.fallbackToken); if (!fallback) { throw new Error( @@ -48,12 +50,16 @@ function resolveDiscordRest(opts: DiscordClientOpts) { } function addPermissionBits(base: bigint, add?: string) { - if (!add) return base; + if (!add) { + return base; + } return base | BigInt(add); } function removePermissionBits(base: bigint, deny?: string) { - if (!deny) return base; + if (!deny) { + return base; + } return base & ~BigInt(deny); } diff --git a/src/discord/send.reactions.ts b/src/discord/send.reactions.ts index 83f878be29..4ec224b29a 100644 --- a/src/discord/send.reactions.ts +++ b/src/discord/send.reactions.ts @@ -50,9 +50,13 @@ export async function removeOwnReactionsDiscord( const identifiers = new Set(); for (const reaction of message.reactions ?? []) { const identifier = buildReactionIdentifier(reaction.emoji); - if (identifier) identifiers.add(identifier); + if (identifier) { + identifiers.add(identifier); + } + } + if (identifiers.size === 0) { + return { ok: true, removed: [] }; } - if (identifiers.size === 0) return { ok: true, removed: [] }; const removed: string[] = []; await Promise.allSettled( Array.from(identifiers, (identifier) => { @@ -78,7 +82,9 @@ export async function fetchReactionsDiscord( }>; }; const reactions = message.reactions ?? []; - if (reactions.length === 0) return []; + if (reactions.length === 0) { + return []; + } const limit = typeof opts.limit === "number" && Number.isFinite(opts.limit) ? Math.min(Math.max(Math.floor(opts.limit), 1), 100) @@ -87,7 +93,9 @@ export async function fetchReactionsDiscord( const summaries: DiscordReactionSummary[] = []; for (const reaction of reactions) { const identifier = buildReactionIdentifier(reaction.emoji); - if (!identifier) continue; + if (!identifier) { + continue; + } const encoded = encodeURIComponent(identifier); const users = (await rest.get(Routes.channelMessageReaction(channelId, messageId, encoded), { limit, diff --git a/src/discord/send.shared.ts b/src/discord/send.shared.ts index e247300ee5..7b88c9c405 100644 --- a/src/discord/send.shared.ts +++ b/src/discord/send.shared.ts @@ -45,7 +45,9 @@ type DiscordClientOpts = { function resolveToken(params: { explicit?: string; accountId: string; fallbackToken?: string }) { const explicit = normalizeDiscordToken(params.explicit); - if (explicit) return explicit; + if (explicit) { + return explicit; + } const fallback = normalizeDiscordToken(params.fallbackToken); if (!fallback) { throw new Error( @@ -183,14 +185,18 @@ function normalizeDiscordPollInput(input: PollInput): RESTAPIPoll { } function getDiscordErrorCode(err: unknown) { - if (!err || typeof err !== "object") return undefined; + if (!err || typeof err !== "object") { + return undefined; + } const candidate = "code" in err && err.code !== undefined ? err.code : "rawError" in err && err.rawError && typeof err.rawError === "object" ? (err.rawError as { code?: unknown }).code : undefined; - if (typeof candidate === "number") return candidate; + if (typeof candidate === "number") { + return candidate; + } if (typeof candidate === "string" && /^\d+$/.test(candidate)) { return Number(candidate); } @@ -206,7 +212,9 @@ async function buildDiscordSendError( hasMedia: boolean; }, ) { - if (err instanceof DiscordSendError) return err; + if (err instanceof DiscordSendError) { + return err; + } const code = getDiscordErrorCode(err); if (code === DISCORD_CANNOT_DM) { return new DiscordSendError( @@ -214,7 +222,9 @@ async function buildDiscordSendError( { kind: "dm-blocked" }, ); } - if (code !== DISCORD_MISSING_PERMISSIONS) return err; + if (code !== DISCORD_MISSING_PERMISSIONS) { + return err; + } let missing: string[] = []; try { @@ -288,7 +298,9 @@ async function sendDiscordText( maxLines: maxLinesPerMessage, chunkMode, }); - if (!chunks.length && text) chunks.push(text); + if (!chunks.length && text) { + chunks.push(text); + } if (chunks.length === 1) { const res = (await request( () => @@ -344,7 +356,9 @@ async function sendDiscordMedia( chunkMode, }) : []; - if (!chunks.length && text) chunks.push(text); + if (!chunks.length && text) { + chunks.push(text); + } const caption = chunks[0] ?? ""; const messageReference = replyTo ? { message_id: replyTo, fail_if_not_exists: false } : undefined; const res = (await request( @@ -365,7 +379,9 @@ async function sendDiscordMedia( "media", )) as { id: string; channel_id: string }; for (const chunk of chunks.slice(1)) { - if (!chunk.trim()) continue; + if (!chunk.trim()) { + continue; + } await sendDiscordText( rest, channelId, diff --git a/src/discord/send.types.ts b/src/discord/send.types.ts index 5ea63366a0..c199a24be6 100644 --- a/src/discord/send.types.ts +++ b/src/discord/send.types.ts @@ -10,7 +10,9 @@ export class DiscordSendError extends Error { constructor(message: string, opts?: Partial) { super(message); this.name = "DiscordSendError"; - if (opts) Object.assign(this, opts); + if (opts) { + Object.assign(this, opts); + } } override toString() { diff --git a/src/discord/targets.ts b/src/discord/targets.ts index 5ea6f5b1b6..e4e6cdd522 100644 --- a/src/discord/targets.ts +++ b/src/discord/targets.ts @@ -22,7 +22,9 @@ export function parseDiscordTarget( options: DiscordTargetParseOptions = {}, ): DiscordTarget | undefined { const trimmed = raw.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } const mentionMatch = trimmed.match(/^<@!?(\d+)>$/); if (mentionMatch) { return buildMessagingTarget("user", mentionMatch[1], trimmed); @@ -80,7 +82,9 @@ export async function resolveDiscordTarget( parseOptions: DiscordTargetParseOptions = {}, ): Promise { const trimmed = raw.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } const likelyUsername = isLikelyUsername(trimmed); const shouldLookup = isExplicitUserLookup(trimmed, parseOptions) || likelyUsername; diff --git a/src/discord/token.ts b/src/discord/token.ts index e8ec2addca..2187fbc32b 100644 --- a/src/discord/token.ts +++ b/src/discord/token.ts @@ -9,9 +9,13 @@ export type DiscordTokenResolution = { }; export function normalizeDiscordToken(raw?: string | null): string | undefined { - if (!raw) return undefined; + if (!raw) { + return undefined; + } const trimmed = raw.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } return trimmed.replace(/^Bot\s+/i, ""); } @@ -26,16 +30,22 @@ export function resolveDiscordToken( ? discordCfg?.accounts?.[accountId] : discordCfg?.accounts?.[DEFAULT_ACCOUNT_ID]; const accountToken = normalizeDiscordToken(accountCfg?.token ?? undefined); - if (accountToken) return { token: accountToken, source: "config" }; + if (accountToken) { + return { token: accountToken, source: "config" }; + } const allowEnv = accountId === DEFAULT_ACCOUNT_ID; const configToken = allowEnv ? normalizeDiscordToken(discordCfg?.token ?? undefined) : undefined; - if (configToken) return { token: configToken, source: "config" }; + if (configToken) { + return { token: configToken, source: "config" }; + } const envToken = allowEnv ? normalizeDiscordToken(opts.envToken ?? process.env.DISCORD_BOT_TOKEN) : undefined; - if (envToken) return { token: envToken, source: "env" }; + if (envToken) { + return { token: envToken, source: "env" }; + } return { token: "", source: "none" }; } diff --git a/src/entry.ts b/src/entry.ts index fa45cc7252..151c890548 100644 --- a/src/entry.ts +++ b/src/entry.ts @@ -20,15 +20,23 @@ if (process.argv.includes("--no-color")) { const EXPERIMENTAL_WARNING_FLAG = "--disable-warning=ExperimentalWarning"; function hasExperimentalWarningSuppressed(nodeOptions: string): boolean { - if (!nodeOptions) return false; + if (!nodeOptions) { + return false; + } return nodeOptions.includes(EXPERIMENTAL_WARNING_FLAG) || nodeOptions.includes("--no-warnings"); } function ensureExperimentalWarningSuppressed(): boolean { - if (isTruthyEnvValue(process.env.OPENCLAW_NO_RESPAWN)) return false; - if (isTruthyEnvValue(process.env.OPENCLAW_NODE_OPTIONS_READY)) return false; + if (isTruthyEnvValue(process.env.OPENCLAW_NO_RESPAWN)) { + return false; + } + if (isTruthyEnvValue(process.env.OPENCLAW_NODE_OPTIONS_READY)) { + return false; + } const nodeOptions = process.env.NODE_OPTIONS ?? ""; - if (hasExperimentalWarningSuppressed(nodeOptions)) return false; + if (hasExperimentalWarningSuppressed(nodeOptions)) { + return false; + } process.env.OPENCLAW_NODE_OPTIONS_READY = "1"; process.env.NODE_OPTIONS = `${nodeOptions} ${EXPERIMENTAL_WARNING_FLAG}`.trim(); @@ -61,8 +69,12 @@ function ensureExperimentalWarningSuppressed(): boolean { } function normalizeWindowsArgv(argv: string[]): string[] { - if (process.platform !== "win32") return argv; - if (argv.length < 2) return argv; + if (process.platform !== "win32") { + return argv; + } + if (argv.length < 2) { + return argv; + } const stripControlChars = (value: string): string => { let out = ""; for (let i = 0; i < value.length; i += 1) { @@ -83,7 +95,9 @@ function normalizeWindowsArgv(argv: string[]): string[] { const execPathLower = execPath.toLowerCase(); const execBase = path.basename(execPath).toLowerCase(); const isExecPath = (value: string | undefined): boolean => { - if (!value) return false; + if (!value) { + return false; + } const lower = normalizeCandidate(value).toLowerCase(); return ( lower === execPathLower || @@ -102,7 +116,9 @@ function normalizeWindowsArgv(argv: string[]): string[] { i += 1; } const filtered = next.filter((arg, index) => index === 0 || !isExecPath(arg)); - if (filtered.length < 3) return filtered; + if (filtered.length < 3) { + return filtered; + } const cleaned = [...filtered]; for (let i = 2; i < cleaned.length; ) { const arg = cleaned[i]; diff --git a/src/gateway/assistant-identity.ts b/src/gateway/assistant-identity.ts index f474a33cb7..812a090d5c 100644 --- a/src/gateway/assistant-identity.ts +++ b/src/gateway/assistant-identity.ts @@ -20,10 +20,16 @@ export type AssistantIdentity = { }; function coerceIdentityValue(value: string | undefined, maxLength: number): string | undefined { - if (typeof value !== "string") return undefined; + if (typeof value !== "string") { + return undefined; + } const trimmed = value.trim(); - if (!trimmed) return undefined; - if (trimmed.length <= maxLength) return trimmed; + if (!trimmed) { + return undefined; + } + if (trimmed.length <= maxLength) { + return trimmed; + } return trimmed.slice(0, maxLength); } @@ -32,17 +38,29 @@ function isAvatarUrl(value: string): boolean { } function looksLikeAvatarPath(value: string): boolean { - if (/[\\/]/.test(value)) return true; + if (/[\\/]/.test(value)) { + return true; + } return /\.(png|jpe?g|gif|webp|svg|ico)$/i.test(value); } function normalizeAvatarValue(value: string | undefined): string | undefined { - if (!value) return undefined; + if (!value) { + return undefined; + } const trimmed = value.trim(); - if (!trimmed) return undefined; - if (isAvatarUrl(trimmed)) return trimmed; - if (looksLikeAvatarPath(trimmed)) return trimmed; - if (!/\s/.test(trimmed) && trimmed.length <= 4) return trimmed; + if (!trimmed) { + return undefined; + } + if (isAvatarUrl(trimmed)) { + return trimmed; + } + if (looksLikeAvatarPath(trimmed)) { + return trimmed; + } + if (!/\s/.test(trimmed) && trimmed.length <= 4) { + return trimmed; + } return undefined; } diff --git a/src/gateway/auth.ts b/src/gateway/auth.ts index c57eef3226..98d603686f 100644 --- a/src/gateway/auth.ts +++ b/src/gateway/auth.ts @@ -33,7 +33,9 @@ type TailscaleUser = { type TailscaleWhoisLookup = (ip: string) => Promise; function safeEqual(a: string, b: string): boolean { - if (a.length !== b.length) return false; + if (a.length !== b.length) { + return false; + } return timingSafeEqual(Buffer.from(a), Buffer.from(b)); } @@ -42,20 +44,34 @@ function normalizeLogin(login: string): string { } function isLoopbackAddress(ip: string | undefined): boolean { - if (!ip) return false; - if (ip === "127.0.0.1") return true; - if (ip.startsWith("127.")) return true; - if (ip === "::1") return true; - if (ip.startsWith("::ffff:127.")) return true; + if (!ip) { + return false; + } + if (ip === "127.0.0.1") { + return true; + } + if (ip.startsWith("127.")) { + return true; + } + if (ip === "::1") { + return true; + } + if (ip.startsWith("::ffff:127.")) { + return true; + } return false; } function getHostName(hostHeader?: string): string { const host = (hostHeader ?? "").trim().toLowerCase(); - if (!host) return ""; + if (!host) { + return ""; + } if (host.startsWith("[")) { const end = host.indexOf("]"); - if (end !== -1) return host.slice(1, end); + if (end !== -1) { + return host.slice(1, end); + } } const [name] = host.split(":"); return name ?? ""; @@ -66,7 +82,9 @@ function headerValue(value: string | string[] | undefined): string | undefined { } function resolveTailscaleClientIp(req?: IncomingMessage): string | undefined { - if (!req) return undefined; + if (!req) { + return undefined; + } const forwardedFor = headerValue(req.headers?.["x-forwarded-for"]); return forwardedFor ? parseForwardedForClientIp(forwardedFor) : undefined; } @@ -75,7 +93,9 @@ function resolveRequestClientIp( req?: IncomingMessage, trustedProxies?: string[], ): string | undefined { - if (!req) return undefined; + if (!req) { + return undefined; + } return resolveGatewayClientIp({ remoteAddr: req.socket?.remoteAddress ?? "", forwardedFor: headerValue(req.headers?.["x-forwarded-for"]), @@ -85,9 +105,13 @@ function resolveRequestClientIp( } export function isLocalDirectRequest(req?: IncomingMessage, trustedProxies?: string[]): boolean { - if (!req) return false; + if (!req) { + return false; + } const clientIp = resolveRequestClientIp(req, trustedProxies) ?? ""; - if (!isLoopbackAddress(clientIp)) return false; + if (!isLoopbackAddress(clientIp)) { + return false; + } const host = getHostName(req.headers?.host); const hostIsLocal = host === "localhost" || host === "127.0.0.1" || host === "::1"; @@ -104,9 +128,13 @@ export function isLocalDirectRequest(req?: IncomingMessage, trustedProxies?: str } function getTailscaleUser(req?: IncomingMessage): TailscaleUser | null { - if (!req) return null; + if (!req) { + return null; + } const login = req.headers["tailscale-user-login"]; - if (typeof login !== "string" || !login.trim()) return null; + if (typeof login !== "string" || !login.trim()) { + return null; + } const nameRaw = req.headers["tailscale-user-name"]; const profilePic = req.headers["tailscale-user-profile-pic"]; const name = typeof nameRaw === "string" && nameRaw.trim() ? nameRaw.trim() : login.trim(); @@ -118,7 +146,9 @@ function getTailscaleUser(req?: IncomingMessage): TailscaleUser | null { } function hasTailscaleProxyHeaders(req?: IncomingMessage): boolean { - if (!req) return false; + if (!req) { + return false; + } return Boolean( req.headers["x-forwarded-for"] && req.headers["x-forwarded-proto"] && @@ -127,7 +157,9 @@ function hasTailscaleProxyHeaders(req?: IncomingMessage): boolean { } function isTailscaleProxyRequest(req?: IncomingMessage): boolean { - if (!req) return false; + if (!req) { + return false; + } return isLoopbackAddress(req.socket?.remoteAddress) && hasTailscaleProxyHeaders(req); } @@ -191,7 +223,9 @@ export function resolveGatewayAuth(params: { export function assertGatewayAuthConfigured(auth: ResolvedGatewayAuth): void { if (auth.mode === "token" && !auth.token) { - if (auth.allowTailscale) return; + if (auth.allowTailscale) { + return; + } throw new Error( "gateway auth mode is token, but no token was configured (set gateway.auth.token or OPENCLAW_GATEWAY_TOKEN)", ); diff --git a/src/gateway/boot.ts b/src/gateway/boot.ts index dc2c5ecb43..32cb6ec861 100644 --- a/src/gateway/boot.ts +++ b/src/gateway/boot.ts @@ -38,11 +38,15 @@ async function loadBootFile( try { const content = await fs.readFile(bootPath, "utf-8"); const trimmed = content.trim(); - if (!trimmed) return { status: "empty" }; + if (!trimmed) { + return { status: "empty" }; + } return { status: "ok", content: trimmed }; } catch (err) { const anyErr = err as { code?: string }; - if (anyErr.code === "ENOENT") return { status: "missing" }; + if (anyErr.code === "ENOENT") { + return { status: "missing" }; + } throw err; } } diff --git a/src/gateway/call.test.ts b/src/gateway/call.test.ts index 9ae8c54ce1..7bb8ae95d9 100644 --- a/src/gateway/call.test.ts +++ b/src/gateway/call.test.ts @@ -31,8 +31,12 @@ vi.mock("../infra/tailnet.js", () => ({ vi.mock("./client.js", () => ({ describeGatewayCloseCode: (code: number) => { - if (code === 1000) return "normal closure"; - if (code === 1006) return "abnormal closure (no close frame)"; + if (code === 1000) { + return "normal closure"; + } + if (code === 1006) { + return "abnormal closure (no close frame)"; + } return undefined; }, GatewayClient: class { diff --git a/src/gateway/call.ts b/src/gateway/call.ts index 4ca9b68bc4..e229ac561c 100644 --- a/src/gateway/call.ts +++ b/src/gateway/call.ts @@ -190,11 +190,16 @@ export async function callGateway(opts: CallGatewayOptions): Promis let settled = false; let ignoreClose = false; const stop = (err?: Error, value?: T) => { - if (settled) return; + if (settled) { + return; + } settled = true; clearTimeout(timer); - if (err) reject(err); - else resolve(value as T); + if (err) { + reject(err); + } else { + resolve(value as T); + } }; const client = new GatewayClient({ @@ -228,7 +233,9 @@ export async function callGateway(opts: CallGatewayOptions): Promis } }, onClose: (code, reason) => { - if (settled || ignoreClose) return; + if (settled || ignoreClose) { + return; + } ignoreClose = true; client.stop(); stop(new Error(formatCloseError(code, reason))); diff --git a/src/gateway/chat-abort.ts b/src/gateway/chat-abort.ts index 5e673f0ad4..d1dd2ec87d 100644 --- a/src/gateway/chat-abort.ts +++ b/src/gateway/chat-abort.ts @@ -10,7 +10,9 @@ export type ChatAbortControllerEntry = { export function isChatStopCommandText(text: string): boolean { const trimmed = text.trim(); - if (!trimmed) return false; + if (!trimmed) { + return false; + } return trimmed.toLowerCase() === "/stop" || isAbortTrigger(trimmed); } @@ -74,8 +76,12 @@ export function abortChatRunById( ): { aborted: boolean } { const { runId, sessionKey, stopReason } = params; const active = ops.chatAbortControllers.get(runId); - if (!active) return { aborted: false }; - if (active.sessionKey !== sessionKey) return { aborted: false }; + if (!active) { + return { aborted: false }; + } + if (active.sessionKey !== sessionKey) { + return { aborted: false }; + } ops.chatAbortedRuns.set(runId, Date.now()); active.controller.abort(); @@ -97,9 +103,13 @@ export function abortChatRunsForSessionKey( const { sessionKey, stopReason } = params; const runIds: string[] = []; for (const [runId, active] of ops.chatAbortControllers) { - if (active.sessionKey !== sessionKey) continue; + if (active.sessionKey !== sessionKey) { + continue; + } const res = abortChatRunById(ops, { runId, sessionKey, stopReason }); - if (res.aborted) runIds.push(runId); + if (res.aborted) { + runIds.push(runId); + } } return { aborted: runIds.length > 0, runIds }; } diff --git a/src/gateway/chat-attachments.ts b/src/gateway/chat-attachments.ts index 1b31b8ab58..0ff3181ee8 100644 --- a/src/gateway/chat-attachments.ts +++ b/src/gateway/chat-attachments.ts @@ -23,18 +23,24 @@ type AttachmentLog = { }; function normalizeMime(mime?: string): string | undefined { - if (!mime) return undefined; + if (!mime) { + return undefined; + } const cleaned = mime.split(";")[0]?.trim().toLowerCase(); return cleaned || undefined; } async function sniffMimeFromBase64(base64: string): Promise { const trimmed = base64.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } const take = Math.min(256, trimmed.length); const sliceLen = take - (take % 4); - if (sliceLen < 8) return undefined; + if (sliceLen < 8) { + return undefined; + } try { const head = Buffer.from(trimmed.slice(0, sliceLen), "base64"); @@ -67,7 +73,9 @@ export async function parseMessageWithAttachments( const images: ChatImageContent[] = []; for (const [idx, att] of attachments.entries()) { - if (!att) continue; + if (!att) { + continue; + } const mime = att.mimeType ?? ""; const content = att.content; const label = att.fileName || att.type || `attachment-${idx + 1}`; @@ -132,12 +140,16 @@ export function buildMessageWithAttachments( opts?: { maxBytes?: number }, ): string { const maxBytes = opts?.maxBytes ?? 2_000_000; // 2 MB - if (!attachments || attachments.length === 0) return message; + if (!attachments || attachments.length === 0) { + return message; + } const blocks: string[] = []; for (const [idx, att] of attachments.entries()) { - if (!att) continue; + if (!att) { + continue; + } const mime = att.mimeType ?? ""; const content = att.content; const label = att.fileName || att.type || `attachment-${idx + 1}`; @@ -169,7 +181,9 @@ export function buildMessageWithAttachments( blocks.push(dataUrl); } - if (blocks.length === 0) return message; + if (blocks.length === 0) { + return message; + } const separator = message.trim().length > 0 ? "\n\n" : ""; return `${message}${separator}${blocks.join("\n\n")}`; } diff --git a/src/gateway/chat-sanitize.ts b/src/gateway/chat-sanitize.ts index 53b1d94032..d175c2e169 100644 --- a/src/gateway/chat-sanitize.ts +++ b/src/gateway/chat-sanitize.ts @@ -18,21 +18,31 @@ const ENVELOPE_CHANNELS = [ const MESSAGE_ID_LINE = /^\s*\[message_id:\s*[^\]]+\]\s*$/i; function looksLikeEnvelopeHeader(header: string): boolean { - if (/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}Z\b/.test(header)) return true; - if (/\d{4}-\d{2}-\d{2} \d{2}:\d{2}\b/.test(header)) return true; + if (/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}Z\b/.test(header)) { + return true; + } + if (/\d{4}-\d{2}-\d{2} \d{2}:\d{2}\b/.test(header)) { + return true; + } return ENVELOPE_CHANNELS.some((label) => header.startsWith(`${label} `)); } export function stripEnvelope(text: string): string { const match = text.match(ENVELOPE_PREFIX); - if (!match) return text; + if (!match) { + return text; + } const header = match[1] ?? ""; - if (!looksLikeEnvelopeHeader(header)) return text; + if (!looksLikeEnvelopeHeader(header)) { + return text; + } return text.slice(match[0].length); } function stripMessageIdHints(text: string): string { - if (!text.includes("[message_id:")) return text; + if (!text.includes("[message_id:")) { + return text; + } const lines = text.split(/\r?\n/); const filtered = lines.filter((line) => !MESSAGE_ID_LINE.test(line)); return filtered.length === lines.length ? text : filtered.join("\n"); @@ -41,11 +51,17 @@ function stripMessageIdHints(text: string): string { function stripEnvelopeFromContent(content: unknown[]): { content: unknown[]; changed: boolean } { let changed = false; const next = content.map((item) => { - if (!item || typeof item !== "object") return item; + if (!item || typeof item !== "object") { + return item; + } const entry = item as Record; - if (entry.type !== "text" || typeof entry.text !== "string") return item; + if (entry.type !== "text" || typeof entry.text !== "string") { + return item; + } const stripped = stripMessageIdHints(stripEnvelope(entry.text)); - if (stripped === entry.text) return item; + if (stripped === entry.text) { + return item; + } changed = true; return { ...entry, @@ -56,10 +72,14 @@ function stripEnvelopeFromContent(content: unknown[]): { content: unknown[]; cha } export function stripEnvelopeFromMessage(message: unknown): unknown { - if (!message || typeof message !== "object") return message; + if (!message || typeof message !== "object") { + return message; + } const entry = message as Record; const role = typeof entry.role === "string" ? entry.role.toLowerCase() : ""; - if (role !== "user") return message; + if (role !== "user") { + return message; + } let changed = false; const next: Record = { ...entry }; @@ -88,11 +108,15 @@ export function stripEnvelopeFromMessage(message: unknown): unknown { } export function stripEnvelopeFromMessages(messages: unknown[]): unknown[] { - if (messages.length === 0) return messages; + if (messages.length === 0) { + return messages; + } let changed = false; const next = messages.map((message) => { const stripped = stripEnvelopeFromMessage(message); - if (stripped !== message) changed = true; + if (stripped !== message) { + changed = true; + } return stripped; }); return changed ? next : messages; diff --git a/src/gateway/client.test.ts b/src/gateway/client.test.ts index 00fadfa2fc..88498bcad7 100644 --- a/src/gateway/client.test.ts +++ b/src/gateway/client.test.ts @@ -146,7 +146,9 @@ r1USnb+wUdA7Zoj/mQ== const error = await new Promise((resolve) => { let settled = false; const finish = (err: Error) => { - if (settled) return; + if (settled) { + return; + } settled = true; resolve(err); }; diff --git a/src/gateway/client.ts b/src/gateway/client.ts index f296d9524f..5ad57eaf0c 100644 --- a/src/gateway/client.ts +++ b/src/gateway/client.ts @@ -99,7 +99,9 @@ export class GatewayClient { } start() { - if (this.closed) return; + if (this.closed) { + return; + } const url = this.opts.url ?? "ws://127.0.0.1:18789"; if (this.opts.tlsFingerprint && !url.startsWith("wss://")) { this.opts.onConnectError?.(new Error("gateway tls fingerprint requires wss:// gateway url")); @@ -173,7 +175,9 @@ export class GatewayClient { } private sendConnect() { - if (this.connectSent) return; + if (this.connectSent) { + return; + } this.connectSent = true; if (this.connectTimer) { clearTimeout(this.connectTimer); @@ -196,7 +200,9 @@ export class GatewayClient { const nonce = this.connectNonce ?? undefined; const scopes = this.opts.scopes ?? ["operator.admin"]; const device = (() => { - if (!this.opts.deviceIdentity) return undefined; + if (!this.opts.deviceIdentity) { + return undefined; + } const payload = buildDeviceAuthPayload({ deviceId: this.opts.deviceIdentity.deviceId, clientId: this.opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT, @@ -269,8 +275,11 @@ export class GatewayClient { } this.opts.onConnectError?.(err instanceof Error ? err : new Error(String(err))); const msg = `gateway connect failed: ${String(err)}`; - if (this.opts.mode === GATEWAY_CLIENT_MODES.PROBE) logDebug(msg); - else logError(msg); + if (this.opts.mode === GATEWAY_CLIENT_MODES.PROBE) { + logDebug(msg); + } else { + logError(msg); + } this.ws?.close(1008, "connect failed"); }); } @@ -304,7 +313,9 @@ export class GatewayClient { } if (validateResponseFrame(parsed)) { const pending = this.pending.get(parsed.id); - if (!pending) return; + if (!pending) { + return; + } // If the payload is an ack with status accepted, keep waiting for final. const payload = parsed.payload as { status?: unknown } | undefined; const status = payload?.status; @@ -312,8 +323,11 @@ export class GatewayClient { return; } this.pending.delete(parsed.id); - if (parsed.ok) pending.resolve(parsed.payload); - else pending.reject(new Error(parsed.error?.message ?? "unknown error")); + if (parsed.ok) { + pending.resolve(parsed.payload); + } else { + pending.reject(new Error(parsed.error?.message ?? "unknown error")); + } } } catch (err) { logDebug(`gateway client parse error: ${String(err)}`); @@ -323,14 +337,18 @@ export class GatewayClient { private queueConnect() { this.connectNonce = null; this.connectSent = false; - if (this.connectTimer) clearTimeout(this.connectTimer); + if (this.connectTimer) { + clearTimeout(this.connectTimer); + } this.connectTimer = setTimeout(() => { this.sendConnect(); }, 750); } private scheduleReconnect() { - if (this.closed) return; + if (this.closed) { + return; + } if (this.tickTimer) { clearInterval(this.tickTimer); this.tickTimer = null; @@ -348,11 +366,17 @@ export class GatewayClient { } private startTickWatch() { - if (this.tickTimer) clearInterval(this.tickTimer); + if (this.tickTimer) { + clearInterval(this.tickTimer); + } const interval = Math.max(this.tickIntervalMs, 1000); this.tickTimer = setInterval(() => { - if (this.closed) return; - if (!this.lastTick) return; + if (this.closed) { + return; + } + if (!this.lastTick) { + return; + } const gap = Date.now() - this.lastTick; if (gap > this.tickIntervalMs * 2) { this.ws?.close(4000, "tick timeout"); @@ -361,9 +385,13 @@ export class GatewayClient { } private validateTlsFingerprint(): Error | null { - if (!this.opts.tlsFingerprint || !this.ws) return null; + if (!this.opts.tlsFingerprint || !this.ws) { + return null; + } const expected = normalizeFingerprint(this.opts.tlsFingerprint); - if (!expected) return new Error("gateway tls fingerprint missing"); + if (!expected) { + return new Error("gateway tls fingerprint missing"); + } const socket = ( this.ws as WebSocket & { _socket?: { getPeerCertificate?: () => { fingerprint256?: string } }; @@ -374,8 +402,12 @@ export class GatewayClient { } const cert = socket.getPeerCertificate(); const fingerprint = normalizeFingerprint(cert?.fingerprint256 ?? ""); - if (!fingerprint) return new Error("gateway tls fingerprint unavailable"); - if (fingerprint !== expected) return new Error("gateway tls fingerprint mismatch"); + if (!fingerprint) { + return new Error("gateway tls fingerprint unavailable"); + } + if (fingerprint !== expected) { + return new Error("gateway tls fingerprint mismatch"); + } return null; } diff --git a/src/gateway/config-reload.ts b/src/gateway/config-reload.ts index 8579dc4a06..b31e504cd5 100644 --- a/src/gateway/config-reload.ts +++ b/src/gateway/config-reload.ts @@ -93,7 +93,9 @@ function listReloadRules(): ReloadRule[] { cachedReloadRules = null; cachedRegistry = registry; } - if (cachedReloadRules) return cachedReloadRules; + if (cachedReloadRules) { + return cachedReloadRules; + } // Channel docking: plugins contribute hot reload/no-op prefixes here. const channelReloadRules: ReloadRule[] = listChannelPlugins().flatMap((plugin) => [ ...(plugin.reload?.configPrefixes ?? []).map( @@ -117,7 +119,9 @@ function listReloadRules(): ReloadRule[] { function matchRule(path: string): ReloadRule | null { for (const rule of listReloadRules()) { - if (path === rule.prefix || path.startsWith(`${rule.prefix}.`)) return rule; + if (path === rule.prefix || path.startsWith(`${rule.prefix}.`)) { + return rule; + } } return null; } @@ -132,14 +136,18 @@ function isPlainObject(value: unknown): value is Record { } export function diffConfigPaths(prev: unknown, next: unknown, prefix = ""): string[] { - if (prev === next) return []; + if (prev === next) { + return []; + } if (isPlainObject(prev) && isPlainObject(next)) { const keys = new Set([...Object.keys(prev), ...Object.keys(next)]); const paths: string[] = []; for (const key of keys) { const prevValue = prev[key]; const nextValue = next[key]; - if (prevValue === undefined && nextValue === undefined) continue; + if (prevValue === undefined && nextValue === undefined) { + continue; + } const childPrefix = prefix ? `${prefix}.${key}` : key; const childPaths = diffConfigPaths(prevValue, nextValue, childPrefix); if (childPaths.length > 0) { @@ -266,8 +274,12 @@ export function startGatewayConfigReloader(opts: { let restartQueued = false; const schedule = () => { - if (stopped) return; - if (debounceTimer) clearTimeout(debounceTimer); + if (stopped) { + return; + } + if (debounceTimer) { + clearTimeout(debounceTimer); + } const wait = settings.debounceMs; debounceTimer = setTimeout(() => { void runReload(); @@ -275,7 +287,9 @@ export function startGatewayConfigReloader(opts: { }; const runReload = async () => { - if (stopped) return; + if (stopped) { + return; + } if (running) { pending = true; return; @@ -296,7 +310,9 @@ export function startGatewayConfigReloader(opts: { const changedPaths = diffConfigPaths(currentConfig, nextConfig); currentConfig = nextConfig; settings = resolveGatewayReloadSettings(nextConfig); - if (changedPaths.length === 0) return; + if (changedPaths.length === 0) { + return; + } opts.log.info(`config change detected; evaluating reload (${changedPaths.join(", ")})`); const plan = buildGatewayReloadPlan(changedPaths); @@ -350,7 +366,9 @@ export function startGatewayConfigReloader(opts: { watcher.on("unlink", schedule); let watcherClosed = false; watcher.on("error", (err) => { - if (watcherClosed) return; + if (watcherClosed) { + return; + } watcherClosed = true; opts.log.warn(`config watcher error: ${String(err)}`); void watcher.close().catch(() => {}); @@ -359,7 +377,9 @@ export function startGatewayConfigReloader(opts: { return { stop: async () => { stopped = true; - if (debounceTimer) clearTimeout(debounceTimer); + if (debounceTimer) { + clearTimeout(debounceTimer); + } debounceTimer = null; watcherClosed = true; await watcher.close().catch(() => {}); diff --git a/src/gateway/control-ui-shared.ts b/src/gateway/control-ui-shared.ts index b29de0a036..8f27411f52 100644 --- a/src/gateway/control-ui-shared.ts +++ b/src/gateway/control-ui-shared.ts @@ -1,12 +1,22 @@ const CONTROL_UI_AVATAR_PREFIX = "/avatar"; export function normalizeControlUiBasePath(basePath?: string): string { - if (!basePath) return ""; + if (!basePath) { + return ""; + } let normalized = basePath.trim(); - if (!normalized) return ""; - if (!normalized.startsWith("/")) normalized = `/${normalized}`; - if (normalized === "/") return ""; - if (normalized.endsWith("/")) normalized = normalized.slice(0, -1); + if (!normalized) { + return ""; + } + if (!normalized.startsWith("/")) { + normalized = `/${normalized}`; + } + if (normalized === "/") { + return ""; + } + if (normalized.endsWith("/")) { + normalized = normalized.slice(0, -1); + } return normalized; } @@ -17,7 +27,9 @@ export function buildControlUiAvatarUrl(basePath: string, agentId: string): stri } function looksLikeLocalAvatarPath(value: string): boolean { - if (/[\\/]/.test(value)) return true; + if (/[\\/]/.test(value)) { + return true; + } return /\.(png|jpe?g|gif|webp|svg|ico)$/i.test(value); } @@ -27,8 +39,12 @@ export function resolveAssistantAvatarUrl(params: { basePath?: string; }): string | undefined { const avatar = params.avatar?.trim(); - if (!avatar) return undefined; - if (/^https?:\/\//i.test(avatar) || /^data:image\//i.test(avatar)) return avatar; + if (!avatar) { + return undefined; + } + if (/^https?:\/\//i.test(avatar) || /^data:image\//i.test(avatar)) { + return avatar; + } const basePath = normalizeControlUiBasePath(params.basePath); const baseAvatarPrefix = basePath @@ -37,9 +53,13 @@ export function resolveAssistantAvatarUrl(params: { if (basePath && avatar.startsWith(`${CONTROL_UI_AVATAR_PREFIX}/`)) { return `${basePath}${avatar}`; } - if (avatar.startsWith(baseAvatarPrefix)) return avatar; + if (avatar.startsWith(baseAvatarPrefix)) { + return avatar; + } - if (!params.agentId) return avatar; + if (!params.agentId) { + return avatar; + } if (looksLikeLocalAvatarPath(avatar)) { return buildControlUiAvatarUrl(basePath, params.agentId); } diff --git a/src/gateway/control-ui.ts b/src/gateway/control-ui.ts index 79d1abdfca..fde3218f72 100644 --- a/src/gateway/control-ui.ts +++ b/src/gateway/control-ui.ts @@ -40,7 +40,9 @@ function resolveControlUiRoot(): string | null { path.resolve(process.cwd(), "dist", "control-ui"), ].filter((dir): dir is string => Boolean(dir)); for (const dir of candidates) { - if (fs.existsSync(path.join(dir, "index.html"))) return dir; + if (fs.existsSync(path.join(dir, "index.html"))) { + return dir; + } } return null; } @@ -103,8 +105,12 @@ export function handleControlUiAvatarRequest( opts: { basePath?: string; resolveAvatar: (agentId: string) => ControlUiAvatarResolution }, ): boolean { const urlRaw = req.url; - if (!urlRaw) return false; - if (req.method !== "GET" && req.method !== "HEAD") return false; + if (!urlRaw) { + return false; + } + if (req.method !== "GET" && req.method !== "HEAD") { + return false; + } const url = new URL(urlRaw, "http://localhost"); const basePath = normalizeControlUiBasePath(opts.basePath); @@ -112,7 +118,9 @@ export function handleControlUiAvatarRequest( const pathWithBase = basePath ? `${basePath}${CONTROL_UI_AVATAR_PREFIX}/` : `${CONTROL_UI_AVATAR_PREFIX}/`; - if (!pathname.startsWith(pathWithBase)) return false; + if (!pathname.startsWith(pathWithBase)) { + return false; + } const agentIdParts = pathname.slice(pathWithBase.length).split("/").filter(Boolean); const agentId = agentIdParts[0] ?? ""; @@ -185,7 +193,9 @@ function injectControlUiConfig(html: string, opts: ControlUiInjectionOpts): stri )};` + ``; // Check if already injected - if (html.includes("__OPENCLAW_ASSISTANT_NAME__")) return html; + if (html.includes("__OPENCLAW_ASSISTANT_NAME__")) { + return html; + } const headClose = html.indexOf(""); if (headClose !== -1) { return `${html.slice(0, headClose)}${script}${html.slice(headClose)}`; @@ -227,10 +237,16 @@ function serveIndexHtml(res: ServerResponse, indexPath: string, opts: ServeIndex } function isSafeRelativePath(relPath: string) { - if (!relPath) return false; + if (!relPath) { + return false; + } const normalized = path.posix.normalize(relPath); - if (normalized.startsWith("../") || normalized === "..") return false; - if (normalized.includes("\0")) return false; + if (normalized.startsWith("../") || normalized === "..") { + return false; + } + if (normalized.includes("\0")) { + return false; + } return true; } @@ -240,7 +256,9 @@ export function handleControlUiHttpRequest( opts?: ControlUiRequestOptions, ): boolean { const urlRaw = req.url; - if (!urlRaw) return false; + if (!urlRaw) { + return false; + } if (req.method !== "GET" && req.method !== "HEAD") { res.statusCode = 405; res.setHeader("Content-Type", "text/plain; charset=utf-8"); @@ -266,7 +284,9 @@ export function handleControlUiHttpRequest( res.end(); return true; } - if (!pathname.startsWith(`${basePath}/`)) return false; + if (!pathname.startsWith(`${basePath}/`)) { + return false; + } } const root = resolveControlUiRoot(); @@ -282,9 +302,13 @@ export function handleControlUiHttpRequest( const uiPath = basePath && pathname.startsWith(`${basePath}/`) ? pathname.slice(basePath.length) : pathname; const rel = (() => { - if (uiPath === ROOT_PREFIX) return ""; + if (uiPath === ROOT_PREFIX) { + return ""; + } const assetsIndex = uiPath.indexOf("/assets/"); - if (assetsIndex >= 0) return uiPath.slice(assetsIndex + 1); + if (assetsIndex >= 0) { + return uiPath.slice(assetsIndex + 1); + } return uiPath.slice(1); })(); const requested = rel && !rel.endsWith("/") ? rel : `${rel}index.html`; diff --git a/src/gateway/exec-approval-manager.ts b/src/gateway/exec-approval-manager.ts index 5e51f2abc3..2eb32d84ab 100644 --- a/src/gateway/exec-approval-manager.ts +++ b/src/gateway/exec-approval-manager.ts @@ -64,7 +64,9 @@ export class ExecApprovalManager { resolve(recordId: string, decision: ExecApprovalDecision, resolvedBy?: string | null): boolean { const pending = this.pending.get(recordId); - if (!pending) return false; + if (!pending) { + return false; + } clearTimeout(pending.timer); pending.record.resolvedAtMs = Date.now(); pending.record.decision = decision; diff --git a/src/gateway/gateway-cli-backend.live.test.ts b/src/gateway/gateway-cli-backend.live.test.ts index 7d547c0af9..a47bada5de 100644 --- a/src/gateway/gateway-cli-backend.live.test.ts +++ b/src/gateway/gateway-cli-backend.live.test.ts @@ -45,11 +45,17 @@ function randomImageProbeCode(len = 6): string { } function editDistance(a: string, b: string): number { - if (a === b) return 0; + if (a === b) { + return 0; + } const aLen = a.length; const bLen = b.length; - if (aLen === 0) return bLen; - if (bLen === 0) return aLen; + if (aLen === 0) { + return bLen; + } + if (bLen === 0) { + return aLen; + } let prev = Array.from({ length: bLen + 1 }, (_v, idx) => idx); let curr = Array.from({ length: bLen + 1 }, () => 0); @@ -82,7 +88,9 @@ function extractPayloadText(result: unknown): string { function parseJsonStringArray(name: string, raw?: string): string[] | undefined { const trimmed = raw?.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } const parsed = JSON.parse(trimmed); if (!Array.isArray(parsed) || !parsed.every((entry) => typeof entry === "string")) { throw new Error(`${name} must be a JSON array of strings.`); @@ -92,8 +100,12 @@ function parseJsonStringArray(name: string, raw?: string): string[] | undefined function parseImageMode(raw?: string): "list" | "repeat" | undefined { const trimmed = raw?.trim(); - if (!trimmed) return undefined; - if (trimmed === "list" || trimmed === "repeat") return trimmed; + if (!trimmed) { + return undefined; + } + if (trimmed === "list" || trimmed === "repeat") { + return trimmed; + } throw new Error("OPENCLAW_LIVE_CLI_BACKEND_IMAGE_MODE must be 'list' or 'repeat'."); } @@ -121,15 +133,20 @@ async function getFreePort(): Promise { } const port = addr.port; srv.close((err) => { - if (err) reject(err); - else resolve(port); + if (err) { + reject(err); + } else { + resolve(port); + } }); }); }); } async function isPortFree(port: number): Promise { - if (!Number.isFinite(port) || port <= 0 || port > 65535) return false; + if (!Number.isFinite(port) || port <= 0 || port > 65535) { + return false; + } return await new Promise((resolve) => { const srv = createServer(); srv.once("error", () => resolve(false)); @@ -146,7 +163,9 @@ async function getFreeGatewayPort(): Promise { const ok = (await Promise.all(candidates.map((candidate) => isPortFree(candidate)))).every( Boolean, ); - if (ok) return port; + if (ok) { + return port; + } } throw new Error("failed to acquire a free gateway port block"); } @@ -155,11 +174,16 @@ async function connectClient(params: { url: string; token: string }) { return await new Promise((resolve, reject) => { let settled = false; const stop = (err?: Error, client?: GatewayClient) => { - if (settled) return; + if (settled) { + return; + } settled = true; clearTimeout(timer); - if (err) reject(err); - else resolve(client as GatewayClient); + if (err) { + reject(err); + } else { + resolve(client as GatewayClient); + } }; const client = new GatewayClient({ url: params.url, @@ -388,7 +412,9 @@ describeLive("gateway live (cli backend)", () => { } const candidates = imageText.toUpperCase().match(/[A-Z0-9]{6,20}/g) ?? []; const bestDistance = candidates.reduce((best, cand) => { - if (Math.abs(cand.length - imageCode.length) > 2) return best; + if (Math.abs(cand.length - imageCode.length) > 2) { + return best; + } return Math.min(best, editDistance(cand, imageCode)); }, Number.POSITIVE_INFINITY); if (!(bestDistance <= 5)) { @@ -399,22 +425,46 @@ describeLive("gateway live (cli backend)", () => { client.stop(); await server.close(); await fs.rm(tempDir, { recursive: true, force: true }); - if (previous.configPath === undefined) delete process.env.OPENCLAW_CONFIG_PATH; - else process.env.OPENCLAW_CONFIG_PATH = previous.configPath; - if (previous.token === undefined) delete process.env.OPENCLAW_GATEWAY_TOKEN; - else process.env.OPENCLAW_GATEWAY_TOKEN = previous.token; - if (previous.skipChannels === undefined) delete process.env.OPENCLAW_SKIP_CHANNELS; - else process.env.OPENCLAW_SKIP_CHANNELS = previous.skipChannels; - if (previous.skipGmail === undefined) delete process.env.OPENCLAW_SKIP_GMAIL_WATCHER; - else process.env.OPENCLAW_SKIP_GMAIL_WATCHER = previous.skipGmail; - if (previous.skipCron === undefined) delete process.env.OPENCLAW_SKIP_CRON; - else process.env.OPENCLAW_SKIP_CRON = previous.skipCron; - if (previous.skipCanvas === undefined) delete process.env.OPENCLAW_SKIP_CANVAS_HOST; - else process.env.OPENCLAW_SKIP_CANVAS_HOST = previous.skipCanvas; - if (previous.anthropicApiKey === undefined) delete process.env.ANTHROPIC_API_KEY; - else process.env.ANTHROPIC_API_KEY = previous.anthropicApiKey; - if (previous.anthropicApiKeyOld === undefined) delete process.env.ANTHROPIC_API_KEY_OLD; - else process.env.ANTHROPIC_API_KEY_OLD = previous.anthropicApiKeyOld; + if (previous.configPath === undefined) { + delete process.env.OPENCLAW_CONFIG_PATH; + } else { + process.env.OPENCLAW_CONFIG_PATH = previous.configPath; + } + if (previous.token === undefined) { + delete process.env.OPENCLAW_GATEWAY_TOKEN; + } else { + process.env.OPENCLAW_GATEWAY_TOKEN = previous.token; + } + if (previous.skipChannels === undefined) { + delete process.env.OPENCLAW_SKIP_CHANNELS; + } else { + process.env.OPENCLAW_SKIP_CHANNELS = previous.skipChannels; + } + if (previous.skipGmail === undefined) { + delete process.env.OPENCLAW_SKIP_GMAIL_WATCHER; + } else { + process.env.OPENCLAW_SKIP_GMAIL_WATCHER = previous.skipGmail; + } + if (previous.skipCron === undefined) { + delete process.env.OPENCLAW_SKIP_CRON; + } else { + process.env.OPENCLAW_SKIP_CRON = previous.skipCron; + } + if (previous.skipCanvas === undefined) { + delete process.env.OPENCLAW_SKIP_CANVAS_HOST; + } else { + process.env.OPENCLAW_SKIP_CANVAS_HOST = previous.skipCanvas; + } + if (previous.anthropicApiKey === undefined) { + delete process.env.ANTHROPIC_API_KEY; + } else { + process.env.ANTHROPIC_API_KEY = previous.anthropicApiKey; + } + if (previous.anthropicApiKeyOld === undefined) { + delete process.env.ANTHROPIC_API_KEY_OLD; + } else { + process.env.ANTHROPIC_API_KEY_OLD = previous.anthropicApiKeyOld; + } } }, 60_000); }); diff --git a/src/gateway/gateway-models.profiles.live.test.ts b/src/gateway/gateway-models.profiles.live.test.ts index 90d1ecefcc..6d8640a5c7 100644 --- a/src/gateway/gateway-models.profiles.live.test.ts +++ b/src/gateway/gateway-models.profiles.live.test.ts @@ -44,7 +44,9 @@ const describeLive = LIVE || GATEWAY_LIVE ? describe : describe.skip; function parseFilter(raw?: string): Set | null { const trimmed = raw?.trim(); - if (!trimmed || trimmed === "all") return null; + if (!trimmed || trimmed === "all") { + return null; + } const ids = trimmed .split(",") .map((s) => s.trim()) @@ -62,7 +64,9 @@ function assertNoReasoningTags(params: { phase: string; label: string; }): void { - if (!params.text) return; + if (!params.text) { + return; + } if (THINKING_TAG_RE.test(params.text) || FINAL_TAG_RE.test(params.text)) { const snippet = params.text.length > 200 ? `${params.text.slice(0, 200)}…` : params.text; throw new Error( @@ -81,22 +85,40 @@ function extractPayloadText(result: unknown): string { } function isMeaningful(text: string): boolean { - if (!text) return false; + if (!text) { + return false; + } const trimmed = text.trim(); - if (trimmed.toLowerCase() === "ok") return false; - if (trimmed.length < 60) return false; + if (trimmed.toLowerCase() === "ok") { + return false; + } + if (trimmed.length < 60) { + return false; + } const words = trimmed.split(/\s+/g).filter(Boolean); - if (words.length < 12) return false; + if (words.length < 12) { + return false; + } return true; } function isGoogleModelNotFoundText(text: string): boolean { const trimmed = text.trim(); - if (!trimmed) return false; - if (!/not found/i.test(trimmed)) return false; - if (/models\/.+ is not found for api version/i.test(trimmed)) return true; - if (/"status"\s*:\s*"NOT_FOUND"/.test(trimmed)) return true; - if (/"code"\s*:\s*404/.test(trimmed)) return true; + if (!trimmed) { + return false; + } + if (!/not found/i.test(trimmed)) { + return false; + } + if (/models\/.+ is not found for api version/i.test(trimmed)) { + return true; + } + if (/"status"\s*:\s*"NOT_FOUND"/.test(trimmed)) { + return true; + } + if (/"code"\s*:\s*404/.test(trimmed)) { + return true; + } return false; } @@ -124,7 +146,9 @@ function isOpenAIReasoningSequenceError(error: string): boolean { function isToolNonceRefusal(error: string): boolean { const msg = error.toLowerCase(); - if (!msg.includes("nonce")) return false; + if (!msg.includes("nonce")) { + return false; + } return ( msg.includes("token") || msg.includes("secret") || @@ -226,11 +250,17 @@ function randomImageProbeCode(len = 6): string { } function editDistance(a: string, b: string): number { - if (a === b) return 0; + if (a === b) { + return 0; + } const aLen = a.length; const bLen = b.length; - if (aLen === 0) return bLen; - if (bLen === 0) return aLen; + if (aLen === 0) { + return bLen; + } + if (bLen === 0) { + return aLen; + } let prev = Array.from({ length: bLen + 1 }, (_v, idx) => idx); let curr = Array.from({ length: bLen + 1 }, () => 0); @@ -264,15 +294,20 @@ async function getFreePort(): Promise { } const port = addr.port; srv.close((err) => { - if (err) reject(err); - else resolve(port); + if (err) { + reject(err); + } else { + resolve(port); + } }); }); }); } async function isPortFree(port: number): Promise { - if (!Number.isFinite(port) || port <= 0 || port > 65535) return false; + if (!Number.isFinite(port) || port <= 0 || port > 65535) { + return false; + } return await new Promise((resolve) => { const srv = createServer(); srv.once("error", () => resolve(false)); @@ -291,7 +326,9 @@ async function getFreeGatewayPort(): Promise { const ok = (await Promise.all(candidates.map((candidate) => isPortFree(candidate)))).every( Boolean, ); - if (ok) return port; + if (ok) { + return port; + } } throw new Error("failed to acquire a free gateway port block"); } @@ -305,11 +342,16 @@ async function connectClient(params: { url: string; token: string }) { return await new Promise((resolve, reject) => { let settled = false; const stop = (err?: Error, client?: GatewayClient) => { - if (settled) return; + if (settled) { + return; + } settled = true; clearTimeout(timer); - if (err) reject(err); - else resolve(client as GatewayClient); + if (err) { + reject(err); + } else { + resolve(client as GatewayClient); + } }; const client = new GatewayClient({ url: params.url, @@ -386,7 +428,9 @@ function sanitizeAuthConfig(params: { agentDir: string; }): OpenClawConfig["auth"] | undefined { const auth = params.cfg.auth; - if (!auth) return auth; + if (!auth) { + return auth; + } const store = ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false, }); @@ -395,10 +439,14 @@ function sanitizeAuthConfig(params: { if (auth.profiles) { profiles = {}; for (const [profileId, profile] of Object.entries(auth.profiles)) { - if (!store.profiles[profileId]) continue; + if (!store.profiles[profileId]) { + continue; + } profiles[profileId] = profile; } - if (Object.keys(profiles).length === 0) profiles = undefined; + if (Object.keys(profiles).length === 0) { + profiles = undefined; + } } let order: Record | undefined; @@ -406,13 +454,19 @@ function sanitizeAuthConfig(params: { order = {}; for (const [provider, ids] of Object.entries(auth.order)) { const filtered = ids.filter((id) => Boolean(store.profiles[id])); - if (filtered.length === 0) continue; + if (filtered.length === 0) { + continue; + } order[provider] = filtered; } - if (Object.keys(order).length === 0) order = undefined; + if (Object.keys(order).length === 0) { + order = undefined; + } } - if (!profiles && !order && !auth.cooldowns) return undefined; + if (!profiles && !order && !auth.cooldowns) { + return undefined; + } return { ...auth, profiles, @@ -426,7 +480,9 @@ function buildMinimaxProviderOverride(params: { baseUrl: string; }): ModelProviderConfig | null { const existing = params.cfg.models?.providers?.minimax; - if (!existing || !Array.isArray(existing.models) || existing.models.length === 0) return null; + if (!existing || !Array.isArray(existing.models) || existing.models.length === 0) { + return null; + } return { ...existing, api: params.api, @@ -761,7 +817,9 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) { } else { const candidates = imageText.toUpperCase().match(/[A-Z0-9]{6,20}/g) ?? []; const bestDistance = candidates.reduce((best, cand) => { - if (Math.abs(cand.length - imageCode.length) > 2) return best; + if (Math.abs(cand.length - imageCode.length) > 2) { + return best; + } return Math.min(best, editDistance(cand, imageCode)); }, Number.POSITIVE_INFINITY); // OCR / image-read flake: allow a small edit distance, but still require the "cat" token above. @@ -977,7 +1035,9 @@ describeLive("gateway live (dev agent, profile keys)", () => { const candidates: Array> = []; for (const model of wanted) { - if (PROVIDERS && !PROVIDERS.has(model.provider)) continue; + if (PROVIDERS && !PROVIDERS.has(model.provider)) { + continue; + } try { // eslint-disable-next-line no-await-in-loop const apiKeyInfo = await getApiKeyForModel({ @@ -1042,7 +1102,9 @@ describeLive("gateway live (dev agent, profile keys)", () => { ); it("z.ai fallback handles anthropic tool history", async () => { - if (!ZAI_FALLBACK) return; + if (!ZAI_FALLBACK) { + return; + } const previous = { configPath: process.env.OPENCLAW_CONFIG_PATH, token: process.env.OPENCLAW_GATEWAY_TOKEN, @@ -1069,7 +1131,9 @@ describeLive("gateway live (dev agent, profile keys)", () => { const anthropic = modelRegistry.find("anthropic", "claude-opus-4-5") as Model | null; const zai = modelRegistry.find("zai", "glm-4.7") as Model | null; - if (!anthropic || !zai) return; + if (!anthropic || !zai) { + return; + } try { await getApiKeyForModel({ model: anthropic, cfg }); await getApiKeyForModel({ model: zai, cfg }); diff --git a/src/gateway/gateway.e2e.test.ts b/src/gateway/gateway.e2e.test.ts index eb643a9b8f..eb06b7acb0 100644 --- a/src/gateway/gateway.e2e.test.ts +++ b/src/gateway/gateway.e2e.test.ts @@ -219,9 +219,13 @@ describe("gateway e2e", () => { let didSendToken = false; while (!next.done) { const step = next.step; - if (!step) throw new Error("wizard missing step"); + if (!step) { + throw new Error("wizard missing step"); + } const value = step.type === "text" ? wizardToken : null; - if (step.type === "text") didSendToken = true; + if (step.type === "text") { + didSendToken = true; + } next = await client.request("wizard.next", { sessionId, answer: { stepId: step.id, value }, diff --git a/src/gateway/hooks-mapping.ts b/src/gateway/hooks-mapping.ts index e6972134f7..d3a14b47ab 100644 --- a/src/gateway/hooks-mapping.ts +++ b/src/gateway/hooks-mapping.ts @@ -104,10 +104,14 @@ export function resolveHookMappings(hooks?: HooksConfig): HookMappingResolved[] const presets = hooks?.presets ?? []; const gmailAllowUnsafe = hooks?.gmail?.allowUnsafeExternalContent; const mappings: HookMappingConfig[] = []; - if (hooks?.mappings) mappings.push(...hooks.mappings); + if (hooks?.mappings) { + mappings.push(...hooks.mappings); + } for (const preset of presets) { const presetMappings = hookPresetMappings[preset]; - if (!presetMappings) continue; + if (!presetMappings) { + continue; + } if (preset === "gmail" && typeof gmailAllowUnsafe === "boolean") { mappings.push( ...presetMappings.map((mapping) => ({ @@ -119,7 +123,9 @@ export function resolveHookMappings(hooks?: HooksConfig): HookMappingResolved[] } mappings.push(...presetMappings); } - if (mappings.length === 0) return []; + if (mappings.length === 0) { + return []; + } const configDir = path.dirname(CONFIG_PATH); const transformsDir = hooks?.transformsDir @@ -133,12 +139,18 @@ export async function applyHookMappings( mappings: HookMappingResolved[], ctx: HookMappingContext, ): Promise { - if (mappings.length === 0) return null; + if (mappings.length === 0) { + return null; + } for (const mapping of mappings) { - if (!mappingMatches(mapping, ctx)) continue; + if (!mappingMatches(mapping, ctx)) { + continue; + } const base = buildActionFromMapping(mapping, ctx); - if (!base.ok) return base; + if (!base.ok) { + return base; + } let override: HookTransformResult = null; if (mapping.transform) { @@ -149,9 +161,13 @@ export async function applyHookMappings( } } - if (!base.action) return { ok: true, action: null, skipped: true }; + if (!base.action) { + return { ok: true, action: null, skipped: true }; + } const merged = mergeAction(base.action, override, mapping.action); - if (!merged.ok) return merged; + if (!merged.ok) { + return merged; + } return merged; } return null; @@ -197,11 +213,15 @@ function normalizeHookMapping( function mappingMatches(mapping: HookMappingResolved, ctx: HookMappingContext) { if (mapping.matchPath) { - if (mapping.matchPath !== normalizeMatchPath(ctx.path)) return false; + if (mapping.matchPath !== normalizeMatchPath(ctx.path)) { + return false; + } } if (mapping.matchSource) { const source = typeof ctx.payload.source === "string" ? ctx.payload.source : undefined; - if (!source || source !== mapping.matchSource) return false; + if (!source || source !== mapping.matchSource) { + return false; + } } return true; } @@ -295,7 +315,9 @@ function validateAction(action: HookAction): HookMappingResult { async function loadTransform(transform: HookMappingTransformResolved): Promise { const cached = transformCache.get(transform.modulePath); - if (cached) return cached; + if (cached) { + return cached; + } const url = pathToFileURL(transform.modulePath).href; const mod = (await import(url)) as Record; const fn = resolveTransformFn(mod, transform.exportName); @@ -312,38 +334,60 @@ function resolveTransformFn(mod: Record, exportName?: string): } function resolvePath(baseDir: string, target: string): string { - if (!target) return baseDir; - if (path.isAbsolute(target)) return target; + if (!target) { + return baseDir; + } + if (path.isAbsolute(target)) { + return target; + } return path.join(baseDir, target); } function normalizeMatchPath(raw?: string): string | undefined { - if (!raw) return undefined; + if (!raw) { + return undefined; + } const trimmed = raw.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } return trimmed.replace(/^\/+/, "").replace(/\/+$/, ""); } function renderOptional(value: string | undefined, ctx: HookMappingContext) { - if (!value) return undefined; + if (!value) { + return undefined; + } const rendered = renderTemplate(value, ctx).trim(); return rendered ? rendered : undefined; } function renderTemplate(template: string, ctx: HookMappingContext) { - if (!template) return ""; + if (!template) { + return ""; + } return template.replace(/\{\{\s*([^}]+)\s*\}\}/g, (_, expr: string) => { const value = resolveTemplateExpr(expr.trim(), ctx); - if (value === undefined || value === null) return ""; - if (typeof value === "string") return value; - if (typeof value === "number" || typeof value === "boolean") return String(value); + if (value === undefined || value === null) { + return ""; + } + if (typeof value === "string") { + return value; + } + if (typeof value === "number" || typeof value === "boolean") { + return String(value); + } return JSON.stringify(value); }); } function resolveTemplateExpr(expr: string, ctx: HookMappingContext) { - if (expr === "path") return ctx.path; - if (expr === "now") return new Date().toISOString(); + if (expr === "path") { + return ctx.path; + } + if (expr === "now") { + return new Date().toISOString(); + } if (expr.startsWith("headers.")) { return getByPath(ctx.headers, expr.slice("headers.".length)); } @@ -360,7 +404,9 @@ function resolveTemplateExpr(expr: string, ctx: HookMappingContext) { } function getByPath(input: Record, pathExpr: string): unknown { - if (!pathExpr) return undefined; + if (!pathExpr) { + return undefined; + } const parts: Array = []; const re = /([^.[\]]+)|(\[(\d+)\])/g; let match = re.exec(pathExpr); @@ -374,13 +420,19 @@ function getByPath(input: Record, pathExpr: string): unknown { } let current: unknown = input; for (const part of parts) { - if (current === null || current === undefined) return undefined; + if (current === null || current === undefined) { + return undefined; + } if (typeof part === "number") { - if (!Array.isArray(current)) return undefined; + if (!Array.isArray(current)) { + return undefined; + } current = current[part] as unknown; continue; } - if (typeof current !== "object") return undefined; + if (typeof current !== "object") { + return undefined; + } current = (current as Record)[part]; } return current; diff --git a/src/gateway/hooks.ts b/src/gateway/hooks.ts index cddbb97303..26b44c8de0 100644 --- a/src/gateway/hooks.ts +++ b/src/gateway/hooks.ts @@ -17,7 +17,9 @@ export type HooksConfigResolved = { }; export function resolveHooksConfig(cfg: OpenClawConfig): HooksConfigResolved | null { - if (cfg.hooks?.enabled !== true) return null; + if (cfg.hooks?.enabled !== true) { + return null; + } const token = cfg.hooks?.token?.trim(); if (!token) { throw new Error("hooks.enabled requires hooks.token"); @@ -51,15 +53,21 @@ export function extractHookToken(req: IncomingMessage, url: URL): HookTokenResul typeof req.headers.authorization === "string" ? req.headers.authorization.trim() : ""; if (auth.toLowerCase().startsWith("bearer ")) { const token = auth.slice(7).trim(); - if (token) return { token, fromQuery: false }; + if (token) { + return { token, fromQuery: false }; + } } const headerToken = typeof req.headers["x-openclaw-token"] === "string" ? req.headers["x-openclaw-token"].trim() : ""; - if (headerToken) return { token: headerToken, fromQuery: false }; + if (headerToken) { + return { token: headerToken, fromQuery: false }; + } const queryToken = url.searchParams.get("token"); - if (queryToken) return { token: queryToken.trim(), fromQuery: true }; + if (queryToken) { + return { token: queryToken.trim(), fromQuery: true }; + } return { token: undefined, fromQuery: false }; } @@ -72,7 +80,9 @@ export async function readJsonBody( let total = 0; const chunks: Buffer[] = []; req.on("data", (chunk: Buffer) => { - if (done) return; + if (done) { + return; + } total += chunk.length; if (total > maxBytes) { done = true; @@ -83,7 +93,9 @@ export async function readJsonBody( chunks.push(chunk); }); req.on("end", () => { - if (done) return; + if (done) { + return; + } done = true; const raw = Buffer.concat(chunks).toString("utf-8").trim(); if (!raw) { @@ -98,7 +110,9 @@ export async function readJsonBody( } }); req.on("error", (err) => { - if (done) return; + if (done) { + return; + } done = true; resolve({ ok: false, error: String(err) }); }); @@ -123,7 +137,9 @@ export function normalizeWakePayload( | { ok: true; value: { text: string; mode: "now" | "next-heartbeat" } } | { ok: false; error: string } { const text = typeof payload.text === "string" ? payload.text.trim() : ""; - if (!text) return { ok: false, error: "text required" }; + if (!text) { + return { ok: false, error: "text required" }; + } const mode = payload.mode === "next-heartbeat" ? "next-heartbeat" : "now"; return { ok: true, value: { text, mode } }; } @@ -149,10 +165,16 @@ const getHookChannelSet = () => new Set(listHookChannelValues()); export const getHookChannelError = () => `channel must be ${listHookChannelValues().join("|")}`; export function resolveHookChannel(raw: unknown): HookMessageChannel | null { - if (raw === undefined) return "last"; - if (typeof raw !== "string") return null; + if (raw === undefined) { + return "last"; + } + if (typeof raw !== "string") { + return null; + } const normalized = normalizeMessageChannel(raw); - if (!normalized || !getHookChannelSet().has(normalized)) return null; + if (!normalized || !getHookChannelSet().has(normalized)) { + return null; + } return normalized as HookMessageChannel; } @@ -170,7 +192,9 @@ export function normalizeAgentPayload( } | { ok: false; error: string } { const message = typeof payload.message === "string" ? payload.message.trim() : ""; - if (!message) return { ok: false, error: "message required" }; + if (!message) { + return { ok: false, error: "message required" }; + } const nameRaw = payload.name; const name = typeof nameRaw === "string" && nameRaw.trim() ? nameRaw.trim() : "Hook"; const wakeMode = payload.wakeMode === "next-heartbeat" ? "next-heartbeat" : "now"; @@ -181,7 +205,9 @@ export function normalizeAgentPayload( ? sessionKeyRaw.trim() : `hook:${idFactory()}`; const channel = resolveHookChannel(payload.channel); - if (!channel) return { ok: false, error: getHookChannelError() }; + if (!channel) { + return { ok: false, error: getHookChannelError() }; + } const toRaw = payload.to; const to = typeof toRaw === "string" && toRaw.trim() ? toRaw.trim() : undefined; const modelRaw = payload.model; diff --git a/src/gateway/http-utils.ts b/src/gateway/http-utils.ts index f6a6e725a5..f857a6caac 100644 --- a/src/gateway/http-utils.ts +++ b/src/gateway/http-utils.ts @@ -5,14 +5,20 @@ import { buildAgentMainSessionKey, normalizeAgentId } from "../routing/session-k export function getHeader(req: IncomingMessage, name: string): string | undefined { const raw = req.headers[name.toLowerCase()]; - if (typeof raw === "string") return raw; - if (Array.isArray(raw)) return raw[0]; + if (typeof raw === "string") { + return raw; + } + if (Array.isArray(raw)) { + return raw[0]; + } return undefined; } export function getBearerToken(req: IncomingMessage): string | undefined { const raw = getHeader(req, "authorization")?.trim() ?? ""; - if (!raw.toLowerCase().startsWith("bearer ")) return undefined; + if (!raw.toLowerCase().startsWith("bearer ")) { + return undefined; + } const token = raw.slice(7).trim(); return token || undefined; } @@ -22,19 +28,25 @@ export function resolveAgentIdFromHeader(req: IncomingMessage): string | undefin getHeader(req, "x-openclaw-agent-id")?.trim() || getHeader(req, "x-openclaw-agent")?.trim() || ""; - if (!raw) return undefined; + if (!raw) { + return undefined; + } return normalizeAgentId(raw); } export function resolveAgentIdFromModel(model: string | undefined): string | undefined { const raw = model?.trim(); - if (!raw) return undefined; + if (!raw) { + return undefined; + } const m = raw.match(/^openclaw[:/](?[a-z0-9][a-z0-9_-]{0,63})$/i) ?? raw.match(/^agent:(?[a-z0-9][a-z0-9_-]{0,63})$/i); const agentId = m?.groups?.agentId; - if (!agentId) return undefined; + if (!agentId) { + return undefined; + } return normalizeAgentId(agentId); } @@ -43,7 +55,9 @@ export function resolveAgentIdForRequest(params: { model: string | undefined; }): string { const fromHeader = resolveAgentIdFromHeader(params.req); - if (fromHeader) return fromHeader; + if (fromHeader) { + return fromHeader; + } const fromModel = resolveAgentIdFromModel(params.model); return fromModel ?? "main"; @@ -56,7 +70,9 @@ export function resolveSessionKey(params: { prefix: string; }): string { const explicit = getHeader(params.req, "x-openclaw-session-key")?.trim(); - if (explicit) return explicit; + if (explicit) { + return explicit; + } const user = params.user?.trim(); const mainKey = user ? `${params.prefix}-user:${user}` : `${params.prefix}:${randomUUID()}`; diff --git a/src/gateway/live-image-probe.ts b/src/gateway/live-image-probe.ts index 77e33e5f24..883d0ac41e 100644 --- a/src/gateway/live-image-probe.ts +++ b/src/gateway/live-image-probe.ts @@ -68,10 +68,16 @@ function fillPixel( b: number, a = 255, ) { - if (x < 0 || y < 0) return; - if (x >= width) return; + if (x < 0 || y < 0) { + return; + } + if (x >= width) { + return; + } const idx = (y * width + x) * 4; - if (idx < 0 || idx + 3 >= buf.length) return; + if (idx < 0 || idx + 3 >= buf.length) { + return; + } buf[idx] = r; buf[idx + 1] = g; buf[idx + 2] = b; @@ -109,12 +115,16 @@ function drawGlyph5x7(params: { color: { r: number; g: number; b: number; a?: number }; }) { const rows = GLYPH_ROWS_5X7[params.char]; - if (!rows) return; + if (!rows) { + return; + } for (let row = 0; row < 7; row += 1) { const bits = rows[row] ?? 0; for (let col = 0; col < 5; col += 1) { const on = (bits & (1 << (4 - col))) !== 0; - if (!on) continue; + if (!on) { + continue; + } for (let dy = 0; dy < params.scale; dy += 1) { for (let dx = 0; dx < params.scale; dx += 1) { fillPixel( diff --git a/src/gateway/net.ts b/src/gateway/net.ts index 6702e0e8b8..dd9c2ccc3d 100644 --- a/src/gateway/net.ts +++ b/src/gateway/net.ts @@ -3,54 +3,80 @@ import net from "node:net"; import { pickPrimaryTailnetIPv4, pickPrimaryTailnetIPv6 } from "../infra/tailnet.js"; export function isLoopbackAddress(ip: string | undefined): boolean { - if (!ip) return false; - if (ip === "127.0.0.1") return true; - if (ip.startsWith("127.")) return true; - if (ip === "::1") return true; - if (ip.startsWith("::ffff:127.")) return true; + if (!ip) { + return false; + } + if (ip === "127.0.0.1") { + return true; + } + if (ip.startsWith("127.")) { + return true; + } + if (ip === "::1") { + return true; + } + if (ip.startsWith("::ffff:127.")) { + return true; + } return false; } function normalizeIPv4MappedAddress(ip: string): string { - if (ip.startsWith("::ffff:")) return ip.slice("::ffff:".length); + if (ip.startsWith("::ffff:")) { + return ip.slice("::ffff:".length); + } return ip; } function normalizeIp(ip: string | undefined): string | undefined { const trimmed = ip?.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } return normalizeIPv4MappedAddress(trimmed.toLowerCase()); } function stripOptionalPort(ip: string): string { if (ip.startsWith("[")) { const end = ip.indexOf("]"); - if (end !== -1) return ip.slice(1, end); + if (end !== -1) { + return ip.slice(1, end); + } + } + if (net.isIP(ip)) { + return ip; } - if (net.isIP(ip)) return ip; const lastColon = ip.lastIndexOf(":"); if (lastColon > -1 && ip.includes(".") && ip.indexOf(":") === lastColon) { const candidate = ip.slice(0, lastColon); - if (net.isIP(candidate) === 4) return candidate; + if (net.isIP(candidate) === 4) { + return candidate; + } } return ip; } export function parseForwardedForClientIp(forwardedFor?: string): string | undefined { const raw = forwardedFor?.split(",")[0]?.trim(); - if (!raw) return undefined; + if (!raw) { + return undefined; + } return normalizeIp(stripOptionalPort(raw)); } function parseRealIp(realIp?: string): string | undefined { const raw = realIp?.trim(); - if (!raw) return undefined; + if (!raw) { + return undefined; + } return normalizeIp(stripOptionalPort(raw)); } export function isTrustedProxyAddress(ip: string | undefined, trustedProxies?: string[]): boolean { const normalized = normalizeIp(ip); - if (!normalized || !trustedProxies || trustedProxies.length === 0) return false; + if (!normalized || !trustedProxies || trustedProxies.length === 0) { + return false; + } return trustedProxies.some((proxy) => normalizeIp(proxy) === normalized); } @@ -61,19 +87,31 @@ export function resolveGatewayClientIp(params: { trustedProxies?: string[]; }): string | undefined { const remote = normalizeIp(params.remoteAddr); - if (!remote) return undefined; - if (!isTrustedProxyAddress(remote, params.trustedProxies)) return remote; + if (!remote) { + return undefined; + } + if (!isTrustedProxyAddress(remote, params.trustedProxies)) { + return remote; + } return parseForwardedForClientIp(params.forwardedFor) ?? parseRealIp(params.realIp) ?? remote; } export function isLocalGatewayAddress(ip: string | undefined): boolean { - if (isLoopbackAddress(ip)) return true; - if (!ip) return false; + if (isLoopbackAddress(ip)) { + return true; + } + if (!ip) { + return false; + } const normalized = normalizeIPv4MappedAddress(ip.trim().toLowerCase()); const tailnetIPv4 = pickPrimaryTailnetIPv4(); - if (tailnetIPv4 && normalized === tailnetIPv4.toLowerCase()) return true; + if (tailnetIPv4 && normalized === tailnetIPv4.toLowerCase()) { + return true; + } const tailnetIPv6 = pickPrimaryTailnetIPv6(); - if (tailnetIPv6 && ip.trim().toLowerCase() === tailnetIPv6.toLowerCase()) return true; + if (tailnetIPv6 && ip.trim().toLowerCase() === tailnetIPv6.toLowerCase()) { + return true; + } return false; } @@ -97,14 +135,20 @@ export async function resolveGatewayBindHost( if (mode === "loopback") { // 127.0.0.1 rarely fails, but handle gracefully - if (await canBindToHost("127.0.0.1")) return "127.0.0.1"; + if (await canBindToHost("127.0.0.1")) { + return "127.0.0.1"; + } return "0.0.0.0"; // extreme fallback } if (mode === "tailnet") { const tailnetIP = pickPrimaryTailnetIPv4(); - if (tailnetIP && (await canBindToHost(tailnetIP))) return tailnetIP; - if (await canBindToHost("127.0.0.1")) return "127.0.0.1"; + if (tailnetIP && (await canBindToHost(tailnetIP))) { + return tailnetIP; + } + if (await canBindToHost("127.0.0.1")) { + return "127.0.0.1"; + } return "0.0.0.0"; } @@ -114,15 +158,21 @@ export async function resolveGatewayBindHost( if (mode === "custom") { const host = customHost?.trim(); - if (!host) return "0.0.0.0"; // invalid config → fall back to all + if (!host) { + return "0.0.0.0"; + } // invalid config → fall back to all - if (isValidIPv4(host) && (await canBindToHost(host))) return host; + if (isValidIPv4(host) && (await canBindToHost(host))) { + return host; + } // Custom IP failed → fall back to LAN return "0.0.0.0"; } if (mode === "auto") { - if (await canBindToHost("127.0.0.1")) return "127.0.0.1"; + if (await canBindToHost("127.0.0.1")) { + return "127.0.0.1"; + } return "0.0.0.0"; } @@ -155,9 +205,13 @@ export async function resolveGatewayListenHosts( bindHost: string, opts?: { canBindToHost?: (host: string) => Promise }, ): Promise { - if (bindHost !== "127.0.0.1") return [bindHost]; + if (bindHost !== "127.0.0.1") { + return [bindHost]; + } const canBind = opts?.canBindToHost ?? canBindToHost; - if (await canBind("::1")) return [bindHost, "::1"]; + if (await canBind("::1")) { + return [bindHost, "::1"]; + } return [bindHost]; } @@ -169,7 +223,9 @@ export async function resolveGatewayListenHosts( */ function isValidIPv4(host: string): boolean { const parts = host.split("."); - if (parts.length !== 4) return false; + if (parts.length !== 4) { + return false; + } return parts.every((part) => { const n = parseInt(part, 10); return !Number.isNaN(n) && n >= 0 && n <= 255 && part === String(n); diff --git a/src/gateway/node-command-policy.ts b/src/gateway/node-command-policy.ts index 2e34a6a130..f22611404c 100644 --- a/src/gateway/node-command-policy.ts +++ b/src/gateway/node-command-policy.ts @@ -59,18 +59,40 @@ const PLATFORM_DEFAULTS: Record = { function normalizePlatformId(platform?: string, deviceFamily?: string): string { const raw = (platform ?? "").trim().toLowerCase(); - if (raw.startsWith("ios")) return "ios"; - if (raw.startsWith("android")) return "android"; - if (raw.startsWith("mac")) return "macos"; - if (raw.startsWith("darwin")) return "macos"; - if (raw.startsWith("win")) return "windows"; - if (raw.startsWith("linux")) return "linux"; + if (raw.startsWith("ios")) { + return "ios"; + } + if (raw.startsWith("android")) { + return "android"; + } + if (raw.startsWith("mac")) { + return "macos"; + } + if (raw.startsWith("darwin")) { + return "macos"; + } + if (raw.startsWith("win")) { + return "windows"; + } + if (raw.startsWith("linux")) { + return "linux"; + } const family = (deviceFamily ?? "").trim().toLowerCase(); - if (family.includes("iphone") || family.includes("ipad") || family.includes("ios")) return "ios"; - if (family.includes("android")) return "android"; - if (family.includes("mac")) return "macos"; - if (family.includes("windows")) return "windows"; - if (family.includes("linux")) return "linux"; + if (family.includes("iphone") || family.includes("ipad") || family.includes("ios")) { + return "ios"; + } + if (family.includes("android")) { + return "android"; + } + if (family.includes("mac")) { + return "macos"; + } + if (family.includes("windows")) { + return "windows"; + } + if (family.includes("linux")) { + return "linux"; + } return "unknown"; } @@ -85,7 +107,9 @@ export function resolveNodeCommandAllowlist( const allow = new Set([...base, ...extra].map((cmd) => cmd.trim()).filter(Boolean)); for (const blocked of deny) { const trimmed = blocked.trim(); - if (trimmed) allow.delete(trimmed); + if (trimmed) { + allow.delete(trimmed); + } } return allow; } @@ -96,7 +120,9 @@ export function isNodeCommandAllowed(params: { allowlist: Set; }): { ok: true } | { ok: false; reason: string } { const command = params.command.trim(); - if (!command) return { ok: false, reason: "command required" }; + if (!command) { + return { ok: false, reason: "command required" }; + } if (!params.allowlist.has(command)) { return { ok: false, reason: "command not allowlisted" }; } diff --git a/src/gateway/node-registry.ts b/src/gateway/node-registry.ts index af881364f0..e29b55fe53 100644 --- a/src/gateway/node-registry.ts +++ b/src/gateway/node-registry.ts @@ -81,11 +81,15 @@ export class NodeRegistry { unregister(connId: string): string | null { const nodeId = this.nodesByConn.get(connId); - if (!nodeId) return null; + if (!nodeId) { + return null; + } this.nodesByConn.delete(connId); this.nodesById.delete(nodeId); for (const [id, pending] of this.pendingInvokes.entries()) { - if (pending.nodeId !== nodeId) continue; + if (pending.nodeId !== nodeId) { + continue; + } clearTimeout(pending.timer); pending.reject(new Error(`node disconnected (${pending.command})`)); this.pendingInvokes.delete(id); @@ -160,8 +164,12 @@ export class NodeRegistry { error?: { code?: string; message?: string } | null; }): boolean { const pending = this.pendingInvokes.get(params.id); - if (!pending) return false; - if (pending.nodeId !== params.nodeId) return false; + if (!pending) { + return false; + } + if (pending.nodeId !== params.nodeId) { + return false; + } clearTimeout(pending.timer); this.pendingInvokes.delete(params.id); pending.resolve({ @@ -175,7 +183,9 @@ export class NodeRegistry { sendEvent(nodeId: string, event: string, payload?: unknown): boolean { const node = this.nodesById.get(nodeId); - if (!node) return false; + if (!node) { + return false; + } return this.sendEventToSession(node, event, payload); } diff --git a/src/gateway/openai-http.ts b/src/gateway/openai-http.ts index 2cbcb7c1ff..15f361ab33 100644 --- a/src/gateway/openai-http.ts +++ b/src/gateway/openai-http.ts @@ -45,17 +45,27 @@ function asMessages(val: unknown): OpenAiChatMessage[] { } function extractTextContent(content: unknown): string { - if (typeof content === "string") return content; + if (typeof content === "string") { + return content; + } if (Array.isArray(content)) { return content .map((part) => { - if (!part || typeof part !== "object") return ""; + if (!part || typeof part !== "object") { + return ""; + } const type = (part as { type?: unknown }).type; const text = (part as { text?: unknown }).text; const inputText = (part as { input_text?: unknown }).input_text; - if (type === "text" && typeof text === "string") return text; - if (type === "input_text" && typeof text === "string") return text; - if (typeof inputText === "string") return inputText; + if (type === "text" && typeof text === "string") { + return text; + } + if (type === "input_text" && typeof text === "string") { + return text; + } + if (typeof inputText === "string") { + return inputText; + } return ""; }) .filter(Boolean) @@ -75,10 +85,14 @@ function buildAgentPrompt(messagesUnknown: unknown): { []; for (const msg of messages) { - if (!msg || typeof msg !== "object") continue; + if (!msg || typeof msg !== "object") { + continue; + } const role = typeof msg.role === "string" ? msg.role.trim() : ""; const content = extractTextContent(msg.content).trim(); - if (!role || !content) continue; + if (!role || !content) { + continue; + } if (role === "system" || role === "developer") { systemParts.push(content); continue; @@ -115,7 +129,9 @@ function buildAgentPrompt(messagesUnknown: unknown): { break; } } - if (currentIndex < 0) currentIndex = conversationEntries.length - 1; + if (currentIndex < 0) { + currentIndex = conversationEntries.length - 1; + } const currentEntry = conversationEntries[currentIndex]?.entry; if (currentEntry) { const historyEntries = conversationEntries.slice(0, currentIndex).map((entry) => entry.entry); @@ -147,7 +163,9 @@ function resolveOpenAiSessionKey(params: { } function coerceRequest(val: unknown): OpenAiChatCompletionRequest { - if (!val || typeof val !== "object") return {}; + if (!val || typeof val !== "object") { + return {}; + } return val as OpenAiChatCompletionRequest; } @@ -157,7 +175,9 @@ export async function handleOpenAiHttpRequest( opts: OpenAiHttpOptions, ): Promise { const url = new URL(req.url ?? "/", `http://${req.headers.host || "localhost"}`); - if (url.pathname !== "/v1/chat/completions") return false; + if (url.pathname !== "/v1/chat/completions") { + return false; + } if (req.method !== "POST") { sendMethodNotAllowed(res); @@ -177,7 +197,9 @@ export async function handleOpenAiHttpRequest( } const body = await readJsonBodyOrError(req, res, opts.maxBodyBytes ?? 1024 * 1024); - if (body === undefined) return true; + if (body === undefined) { + return true; + } const payload = coerceRequest(body); const stream = Boolean(payload.stream); @@ -254,14 +276,20 @@ export async function handleOpenAiHttpRequest( let closed = false; const unsubscribe = onAgentEvent((evt) => { - if (evt.runId !== runId) return; - if (closed) return; + if (evt.runId !== runId) { + return; + } + if (closed) { + return; + } if (evt.stream === "assistant") { const delta = evt.data?.delta; const text = evt.data?.text; const content = typeof delta === "string" ? delta : typeof text === "string" ? text : ""; - if (!content) return; + if (!content) { + return; + } if (!wroteRole) { wroteRole = true; @@ -323,7 +351,9 @@ export async function handleOpenAiHttpRequest( deps, ); - if (closed) return; + if (closed) { + return; + } if (!sawAssistantDelta) { if (!wroteRole) { @@ -362,7 +392,9 @@ export async function handleOpenAiHttpRequest( }); } } catch (err) { - if (closed) return; + if (closed) { + return; + } writeSse(res, { id: runId, object: "chat.completion.chunk", diff --git a/src/gateway/openresponses-http.e2e.test.ts b/src/gateway/openresponses-http.e2e.test.ts index 9ae6c2f025..548fa179f4 100644 --- a/src/gateway/openresponses-http.e2e.test.ts +++ b/src/gateway/openresponses-http.e2e.test.ts @@ -73,7 +73,9 @@ function parseSseEvents(text: string): Array<{ event?: string; data: string }> { } async function ensureResponseConsumed(res: Response) { - if (res.bodyUsed) return; + if (res.bodyUsed) { + return; + } try { await res.text(); } catch { @@ -493,7 +495,9 @@ describe("OpenResponses HTTP API (e2e)", () => { const typeText = await resTypeMatch.text(); const typeEvents = parseSseEvents(typeText); for (const event of typeEvents) { - if (event.data === "[DONE]") continue; + if (event.data === "[DONE]") { + continue; + } const parsed = JSON.parse(event.data) as { type?: string }; expect(event.event).toBe(parsed.type); } diff --git a/src/gateway/openresponses-http.ts b/src/gateway/openresponses-http.ts index 82fef800e6..be8f5bbd5a 100644 --- a/src/gateway/openresponses-http.ts +++ b/src/gateway/openresponses-http.ts @@ -71,11 +71,17 @@ function writeSseEvent(res: ServerResponse, event: StreamingEvent) { } function extractTextContent(content: string | ContentPart[]): string { - if (typeof content === "string") return content; + if (typeof content === "string") { + return content; + } return content .map((part) => { - if (part.type === "input_text") return part.text; - if (part.type === "output_text") return part.text; + if (part.type === "input_text") { + return part.text; + } + if (part.type === "output_text") { + return part.text; + } return ""; }) .filter(Boolean) @@ -127,7 +133,9 @@ function applyToolChoice(params: { toolChoice: CreateResponseBody["tool_choice"]; }): { tools: ClientToolDefinition[]; extraSystemPrompt?: string } { const { tools, toolChoice } = params; - if (!toolChoice) return { tools }; + if (!toolChoice) { + return { tools }; + } if (toolChoice === "none") { return { tools: [] }; @@ -176,7 +184,9 @@ export function buildAgentPrompt(input: string | ItemParam[]): { for (const item of input) { if (item.type === "message") { const content = extractTextContent(item.content).trim(); - if (!content) continue; + if (!content) { + continue; + } if (item.role === "system" || item.role === "developer") { systemParts.push(content); @@ -210,7 +220,9 @@ export function buildAgentPrompt(input: string | ItemParam[]): { break; } } - if (currentIndex < 0) currentIndex = conversationEntries.length - 1; + if (currentIndex < 0) { + currentIndex = conversationEntries.length - 1; + } const currentEntry = conversationEntries[currentIndex]?.entry; if (currentEntry) { @@ -257,7 +269,9 @@ function toUsage( } | undefined, ): Usage { - if (!value) return createEmptyUsage(); + if (!value) { + return createEmptyUsage(); + } const input = value.input ?? 0; const output = value.output ?? 0; const cacheRead = value.cacheRead ?? 0; @@ -320,7 +334,9 @@ export async function handleOpenResponsesHttpRequest( opts: OpenResponsesHttpOptions, ): Promise { const url = new URL(req.url ?? "/", `http://${req.headers.host || "localhost"}`); - if (url.pathname !== "/v1/responses") return false; + if (url.pathname !== "/v1/responses") { + return false; + } if (req.method !== "POST") { sendMethodNotAllowed(res); @@ -346,7 +362,9 @@ export async function handleOpenResponsesHttpRequest( ? limits.maxBodyBytes : Math.max(limits.maxBodyBytes, limits.files.maxBytes * 2, limits.images.maxBytes * 2)); const body = await readJsonBodyOrError(req, res, maxBodyBytes); - if (body === undefined) return true; + if (body === undefined) { + return true; + } // Validate request body with Zod const parseResult = CreateResponseBodySchema.safeParse(body); @@ -592,9 +610,15 @@ export async function handleOpenResponsesHttpRequest( let finalizeRequested: { status: ResponseResource["status"]; text: string } | null = null; const maybeFinalize = () => { - if (closed) return; - if (!finalizeRequested) return; - if (!finalUsage) return; + if (closed) { + return; + } + if (!finalizeRequested) { + return; + } + if (!finalUsage) { + return; + } const usage = finalUsage; closed = true; @@ -642,7 +666,9 @@ export async function handleOpenResponsesHttpRequest( }; const requestFinalize = (status: ResponseResource["status"], text: string) => { - if (finalizeRequested) return; + if (finalizeRequested) { + return; + } finalizeRequested = { status, text }; maybeFinalize(); }; @@ -681,14 +707,20 @@ export async function handleOpenResponsesHttpRequest( }); unsubscribe = onAgentEvent((evt) => { - if (evt.runId !== responseId) return; - if (closed) return; + if (evt.runId !== responseId) { + return; + } + if (closed) { + return; + } if (evt.stream === "assistant") { const delta = evt.data?.delta; const text = evt.data?.text; const content = typeof delta === "string" ? delta : typeof text === "string" ? text : ""; - if (!content) return; + if (!content) { + return; + } sawAssistantDelta = true; accumulatedText += content; @@ -740,7 +772,9 @@ export async function handleOpenResponsesHttpRequest( finalUsage = extractUsageFromResult(result); maybeFinalize(); - if (closed) return; + if (closed) { + return; + } // Fallback: if no streaming deltas were received, send the full response if (!sawAssistantDelta) { @@ -845,7 +879,9 @@ export async function handleOpenResponsesHttpRequest( }); } } catch (err) { - if (closed) return; + if (closed) { + return; + } finalUsage = finalUsage ?? createEmptyUsage(); const errorResponse = createResponseResource({ diff --git a/src/gateway/probe.ts b/src/gateway/probe.ts index 39be5f7ea1..a57e23774d 100644 --- a/src/gateway/probe.ts +++ b/src/gateway/probe.ts @@ -28,7 +28,9 @@ export type GatewayProbeResult = { }; function formatError(err: unknown): string { - if (err instanceof Error) return err.message; + if (err instanceof Error) { + return err.message; + } return String(err); } @@ -46,7 +48,9 @@ export async function probeGateway(opts: { return await new Promise((resolve) => { let settled = false; const settle = (result: Omit) => { - if (settled) return; + if (settled) { + return; + } settled = true; clearTimeout(timer); client.stop(); diff --git a/src/gateway/protocol/client-info.ts b/src/gateway/protocol/client-info.ts index 9fc39ff119..7bb30f9170 100644 --- a/src/gateway/protocol/client-info.ts +++ b/src/gateway/protocol/client-info.ts @@ -47,7 +47,9 @@ const GATEWAY_CLIENT_MODE_SET = new Set(Object.values(GATEWAY export function normalizeGatewayClientId(raw?: string | null): GatewayClientId | undefined { const normalized = raw?.trim().toLowerCase(); - if (!normalized) return undefined; + if (!normalized) { + return undefined; + } return GATEWAY_CLIENT_ID_SET.has(normalized as GatewayClientId) ? (normalized as GatewayClientId) : undefined; @@ -59,7 +61,9 @@ export function normalizeGatewayClientName(raw?: string | null): GatewayClientNa export function normalizeGatewayClientMode(raw?: string | null): GatewayClientMode | undefined { const normalized = raw?.trim().toLowerCase(); - if (!normalized) return undefined; + if (!normalized) { + return undefined; + } return GATEWAY_CLIENT_MODE_SET.has(normalized as GatewayClientMode) ? (normalized as GatewayClientMode) : undefined; diff --git a/src/gateway/protocol/index.ts b/src/gateway/protocol/index.ts index 6e5a862d11..9bd0b40543 100644 --- a/src/gateway/protocol/index.ts +++ b/src/gateway/protocol/index.ts @@ -321,7 +321,9 @@ export const validateWebLoginStartParams = export const validateWebLoginWaitParams = ajv.compile(WebLoginWaitParamsSchema); export function formatValidationErrors(errors: ErrorObject[] | null | undefined) { - if (!errors?.length) return "unknown validation error"; + if (!errors?.length) { + return "unknown validation error"; + } const parts: string[] = []; diff --git a/src/gateway/server-broadcast.ts b/src/gateway/server-broadcast.ts index 61df310979..abbc3d87e3 100644 --- a/src/gateway/server-broadcast.ts +++ b/src/gateway/server-broadcast.ts @@ -17,11 +17,17 @@ const EVENT_SCOPE_GUARDS: Record = { function hasEventScope(client: GatewayWsClient, event: string): boolean { const required = EVENT_SCOPE_GUARDS[event]; - if (!required) return true; + if (!required) { + return true; + } const role = client.connect.role ?? "operator"; - if (role !== "operator") return false; + if (role !== "operator") { + return false; + } const scopes = Array.isArray(client.connect.scopes) ? client.connect.scopes : []; - if (scopes.includes(ADMIN_SCOPE)) return true; + if (scopes.includes(ADMIN_SCOPE)) { + return true; + } return required.some((scope) => scopes.includes(scope)); } @@ -56,9 +62,13 @@ export function createGatewayBroadcaster(params: { clients: Set } logWs("out", "event", logMeta); for (const c of params.clients) { - if (!hasEventScope(c, event)) continue; + if (!hasEventScope(c, event)) { + continue; + } const slow = c.socket.bufferedAmount > MAX_BUFFERED_BYTES; - if (slow && opts?.dropIfSlow) continue; + if (slow && opts?.dropIfSlow) { + continue; + } if (slow) { try { c.socket.close(1008, "slow consumer"); diff --git a/src/gateway/server-browser.ts b/src/gateway/server-browser.ts index bbc5060342..02f3659de3 100644 --- a/src/gateway/server-browser.ts +++ b/src/gateway/server-browser.ts @@ -5,7 +5,9 @@ export type BrowserControlServer = { }; export async function startBrowserControlServerIfEnabled(): Promise { - if (isTruthyEnvValue(process.env.OPENCLAW_SKIP_BROWSER_CONTROL_SERVER)) return null; + if (isTruthyEnvValue(process.env.OPENCLAW_SKIP_BROWSER_CONTROL_SERVER)) { + return null; + } // Lazy import: keeps startup fast, but still bundles for the embedded // gateway (bun --compile) via the static specifier path. const override = process.env.OPENCLAW_BROWSER_CONTROL_MODULE?.trim(); @@ -21,7 +23,9 @@ export async function startBrowserControlServerIfEnabled(): Promise Promise }).stopBrowserControlService : (mod as { stopBrowserControlServer?: () => Promise }).stopBrowserControlServer; - if (!start) return null; + if (!start) { + return null; + } await start(); return { stop: stop ?? (async () => {}) }; } diff --git a/src/gateway/server-channels.ts b/src/gateway/server-channels.ts index 43c21f0828..7b1567b43b 100644 --- a/src/gateway/server-channels.ts +++ b/src/gateway/server-channels.ts @@ -30,7 +30,9 @@ function createRuntimeStore(): ChannelRuntimeStore { } function isAccountEnabled(account: unknown): boolean { - if (!account || typeof account !== "object") return true; + if (!account || typeof account !== "object") { + return true; + } const enabled = (account as { enabled?: boolean }).enabled; return enabled !== false; } @@ -66,7 +68,9 @@ export function createChannelManager(opts: ChannelManagerOptions): ChannelManage const getStore = (channelId: ChannelId): ChannelRuntimeStore => { const existing = channelStores.get(channelId); - if (existing) return existing; + if (existing) { + return existing; + } const next = createRuntimeStore(); channelStores.set(channelId, next); return next; @@ -92,16 +96,22 @@ export function createChannelManager(opts: ChannelManagerOptions): ChannelManage const startChannel = async (channelId: ChannelId, accountId?: string) => { const plugin = getChannelPlugin(channelId); const startAccount = plugin?.gateway?.startAccount; - if (!startAccount) return; + if (!startAccount) { + return; + } const cfg = loadConfig(); resetDirectoryCache({ channel: channelId, accountId }); const store = getStore(channelId); const accountIds = accountId ? [accountId] : plugin.config.listAccountIds(cfg); - if (accountIds.length === 0) return; + if (accountIds.length === 0) { + return; + } await Promise.all( accountIds.map(async (id) => { - if (store.tasks.has(id)) return; + if (store.tasks.has(id)) { + return; + } const account = plugin.config.resolveAccount(cfg, id); const enabled = plugin.config.isEnabled ? plugin.config.isEnabled(account, cfg) @@ -186,7 +196,9 @@ export function createChannelManager(opts: ChannelManagerOptions): ChannelManage Array.from(knownIds.values()).map(async (id) => { const abort = store.aborts.get(id); const task = store.tasks.get(id); - if (!abort && !task && !plugin?.gateway?.stopAccount) return; + if (!abort && !task && !plugin?.gateway?.stopAccount) { + return; + } abort?.abort(); if (plugin?.gateway?.stopAccount) { const account = plugin.config.resolveAccount(cfg, id); @@ -225,7 +237,9 @@ export function createChannelManager(opts: ChannelManagerOptions): ChannelManage const markChannelLoggedOut = (channelId: ChannelId, cleared: boolean, accountId?: string) => { const plugin = getChannelPlugin(channelId); - if (!plugin) return; + if (!plugin) { + return; + } const cfg = loadConfig(); const resolvedId = accountId ?? diff --git a/src/gateway/server-chat.ts b/src/gateway/server-chat.ts index 8c67767a6b..953b26268a 100644 --- a/src/gateway/server-chat.ts +++ b/src/gateway/server-chat.ts @@ -11,7 +11,9 @@ import { formatForLog } from "./ws-log.js"; */ function shouldSuppressHeartbeatBroadcast(runId: string): boolean { const runContext = getAgentRunContext(runId); - if (!runContext?.isHeartbeat) return false; + if (!runContext?.isHeartbeat) { + return false; + } try { const cfg = loadConfig(); @@ -52,22 +54,32 @@ export function createChatRunRegistry(): ChatRunRegistry { const shift = (sessionId: string) => { const queue = chatRunSessions.get(sessionId); - if (!queue || queue.length === 0) return undefined; + if (!queue || queue.length === 0) { + return undefined; + } const entry = queue.shift(); - if (!queue.length) chatRunSessions.delete(sessionId); + if (!queue.length) { + chatRunSessions.delete(sessionId); + } return entry; }; const remove = (sessionId: string, clientRunId: string, sessionKey?: string) => { const queue = chatRunSessions.get(sessionId); - if (!queue || queue.length === 0) return undefined; + if (!queue || queue.length === 0) { + return undefined; + } const idx = queue.findIndex( (entry) => entry.clientRunId === clientRunId && (sessionKey ? entry.sessionKey === sessionKey : true), ); - if (idx < 0) return undefined; + if (idx < 0) { + return undefined; + } const [entry] = queue.splice(idx, 1); - if (!queue.length) chatRunSessions.delete(sessionId); + if (!queue.length) { + chatRunSessions.delete(sessionId); + } return entry; }; @@ -137,7 +149,9 @@ export function createAgentEventHandler({ chatRunState.buffers.set(clientRunId, text); const now = Date.now(); const last = chatRunState.deltaSentAt.get(clientRunId) ?? 0; - if (now - last < 150) return; + if (now - last < 150) { + return; + } chatRunState.deltaSentAt.set(clientRunId, now); const payload = { runId: clientRunId, @@ -202,12 +216,18 @@ export function createAgentEventHandler({ const shouldEmitToolEvents = (runId: string, sessionKey?: string) => { const runContext = getAgentRunContext(runId); const runVerbose = normalizeVerboseLevel(runContext?.verboseLevel); - if (runVerbose) return runVerbose === "on"; - if (!sessionKey) return false; + if (runVerbose) { + return runVerbose === "on"; + } + if (!sessionKey) { + return false; + } try { const { cfg, entry } = loadSessionEntry(sessionKey); const sessionVerbose = normalizeVerboseLevel(entry?.verboseLevel); - if (sessionVerbose) return sessionVerbose === "on"; + if (sessionVerbose) { + return sessionVerbose === "on"; + } const defaultVerbose = normalizeVerboseLevel(cfg.agents?.defaults?.verboseDefault); return defaultVerbose === "on"; } catch { diff --git a/src/gateway/server-constants.ts b/src/gateway/server-constants.ts index 103b55accf..996ea63585 100644 --- a/src/gateway/server-constants.ts +++ b/src/gateway/server-constants.ts @@ -7,7 +7,9 @@ let maxChatHistoryMessagesBytes = DEFAULT_MAX_CHAT_HISTORY_MESSAGES_BYTES; export const getMaxChatHistoryMessagesBytes = () => maxChatHistoryMessagesBytes; export const __setMaxChatHistoryMessagesBytesForTest = (value?: number) => { - if (!process.env.VITEST && process.env.NODE_ENV !== "test") return; + if (!process.env.VITEST && process.env.NODE_ENV !== "test") { + return; + } if (value === undefined) { maxChatHistoryMessagesBytes = DEFAULT_MAX_CHAT_HISTORY_MESSAGES_BYTES; return; @@ -20,7 +22,9 @@ export const DEFAULT_HANDSHAKE_TIMEOUT_MS = 10_000; export const getHandshakeTimeoutMs = () => { if (process.env.VITEST && process.env.OPENCLAW_TEST_HANDSHAKE_TIMEOUT_MS) { const parsed = Number(process.env.OPENCLAW_TEST_HANDSHAKE_TIMEOUT_MS); - if (Number.isFinite(parsed) && parsed > 0) return parsed; + if (Number.isFinite(parsed) && parsed > 0) { + return parsed; + } } return DEFAULT_HANDSHAKE_TIMEOUT_MS; }; diff --git a/src/gateway/server-discovery.ts b/src/gateway/server-discovery.ts index 519ab3ba0a..98554d81de 100644 --- a/src/gateway/server-discovery.ts +++ b/src/gateway/server-discovery.ts @@ -13,15 +13,21 @@ export type ResolveBonjourCliPathOptions = { export function formatBonjourInstanceName(displayName: string) { const trimmed = displayName.trim(); - if (!trimmed) return "OpenClaw"; - if (/openclaw/i.test(trimmed)) return trimmed; + if (!trimmed) { + return "OpenClaw"; + } + if (/openclaw/i.test(trimmed)) { + return trimmed; + } return `${trimmed} (OpenClaw)`; } export function resolveBonjourCliPath(opts: ResolveBonjourCliPathOptions = {}): string | undefined { const env = opts.env ?? process.env; const envPath = env.OPENCLAW_CLI_PATH?.trim(); - if (envPath) return envPath; + if (envPath) { + return envPath; + } const statSync = opts.statSync ?? fs.statSync; const isFile = (candidate: string) => { @@ -35,7 +41,9 @@ export function resolveBonjourCliPath(opts: ResolveBonjourCliPathOptions = {}): const execPath = opts.execPath ?? process.execPath; const execDir = path.dirname(execPath); const siblingCli = path.join(execDir, "openclaw"); - if (isFile(siblingCli)) return siblingCli; + if (isFile(siblingCli)) { + return siblingCli; + } const argv = opts.argv ?? process.argv; const argvPath = argv[1]; @@ -45,9 +53,13 @@ export function resolveBonjourCliPath(opts: ResolveBonjourCliPathOptions = {}): const cwd = opts.cwd ?? process.cwd(); const distCli = path.join(cwd, "dist", "index.js"); - if (isFile(distCli)) return distCli; + if (isFile(distCli)) { + return distCli; + } const binCli = path.join(cwd, "bin", "openclaw"); - if (isFile(binCli)) return binCli; + if (isFile(binCli)) { + return binCli; + } return undefined; } @@ -60,8 +72,12 @@ export async function resolveTailnetDnsHint(opts?: { const env = opts?.env ?? process.env; const envRaw = env.OPENCLAW_TAILNET_DNS?.trim(); const envValue = envRaw && envRaw.length > 0 ? envRaw.replace(/\.$/, "") : ""; - if (envValue) return envValue; - if (opts?.enabled === false) return undefined; + if (envValue) { + return envValue; + } + if (opts?.enabled === false) { + return undefined; + } const exec = opts?.exec ?? diff --git a/src/gateway/server-http.ts b/src/gateway/server-http.ts index e84c0ed431..f725c2abf5 100644 --- a/src/gateway/server-http.ts +++ b/src/gateway/server-http.ts @@ -69,7 +69,9 @@ export function createHooksRequestHandler( const { getHooksConfig, bindHost, port, logHooks, dispatchAgentHook, dispatchWakeHook } = opts; return async (req, res) => { const hooksConfig = getHooksConfig(); - if (!hooksConfig) return false; + if (!hooksConfig) { + return false; + } const url = new URL(req.url ?? "/", `http://${bindHost}:${port}`); const basePath = hooksConfig.basePath; if (url.pathname !== basePath && !url.pathname.startsWith(`${basePath}/`)) { @@ -233,21 +235,30 @@ export function createGatewayHttpServer(opts: { async function handleRequest(req: IncomingMessage, res: ServerResponse) { // Don't interfere with WebSocket upgrades; ws handles the 'upgrade' event. - if (String(req.headers.upgrade ?? "").toLowerCase() === "websocket") return; + if (String(req.headers.upgrade ?? "").toLowerCase() === "websocket") { + return; + } try { const configSnapshot = loadConfig(); const trustedProxies = configSnapshot.gateway?.trustedProxies ?? []; - if (await handleHooksRequest(req, res)) return; + if (await handleHooksRequest(req, res)) { + return; + } if ( await handleToolsInvokeHttpRequest(req, res, { auth: resolvedAuth, trustedProxies, }) - ) + ) { return; - if (await handleSlackHttpRequest(req, res)) return; - if (handlePluginRequest && (await handlePluginRequest(req, res))) return; + } + if (await handleSlackHttpRequest(req, res)) { + return; + } + if (handlePluginRequest && (await handlePluginRequest(req, res))) { + return; + } if (openResponsesEnabled) { if ( await handleOpenResponsesHttpRequest(req, res, { @@ -255,8 +266,9 @@ export function createGatewayHttpServer(opts: { config: openResponsesConfig, trustedProxies, }) - ) + ) { return; + } } if (openAiChatCompletionsEnabled) { if ( @@ -264,12 +276,17 @@ export function createGatewayHttpServer(opts: { auth: resolvedAuth, trustedProxies, }) - ) + ) { return; + } } if (canvasHost) { - if (await handleA2uiHttpRequest(req, res)) return; - if (await canvasHost.handleHttpRequest(req, res)) return; + if (await handleA2uiHttpRequest(req, res)) { + return; + } + if (await canvasHost.handleHttpRequest(req, res)) { + return; + } } if (controlUiEnabled) { if ( @@ -277,15 +294,17 @@ export function createGatewayHttpServer(opts: { basePath: controlUiBasePath, resolveAvatar: (agentId) => resolveAgentAvatar(configSnapshot, agentId), }) - ) + ) { return; + } if ( handleControlUiHttpRequest(req, res, { basePath: controlUiBasePath, config: configSnapshot, }) - ) + ) { return; + } } res.statusCode = 404; @@ -308,7 +327,9 @@ export function attachGatewayUpgradeHandler(opts: { }) { const { httpServer, wss, canvasHost } = opts; httpServer.on("upgrade", (req, socket, head) => { - if (canvasHost?.handleUpgrade(req, socket, head)) return; + if (canvasHost?.handleUpgrade(req, socket, head)) { + return; + } wss.handleUpgrade(req, socket, head, (ws) => { wss.emit("connection", ws, req); }); diff --git a/src/gateway/server-maintenance.ts b/src/gateway/server-maintenance.ts index 027103950e..0c440eee87 100644 --- a/src/gateway/server-maintenance.ts +++ b/src/gateway/server-maintenance.ts @@ -75,7 +75,9 @@ export function startGatewayMaintenanceTimers(params: { const dedupeCleanup = setInterval(() => { const now = Date.now(); for (const [k, v] of params.dedupe) { - if (now - v.ts > DEDUPE_TTL_MS) params.dedupe.delete(k); + if (now - v.ts > DEDUPE_TTL_MS) { + params.dedupe.delete(k); + } } if (params.dedupe.size > DEDUPE_MAX) { const entries = [...params.dedupe.entries()].toSorted((a, b) => a[1].ts - b[1].ts); @@ -85,7 +87,9 @@ export function startGatewayMaintenanceTimers(params: { } for (const [runId, entry] of params.chatAbortControllers) { - if (now <= entry.expiresAtMs) continue; + if (now <= entry.expiresAtMs) { + continue; + } abortChatRunById( { chatAbortControllers: params.chatAbortControllers, @@ -103,7 +107,9 @@ export function startGatewayMaintenanceTimers(params: { const ABORTED_RUN_TTL_MS = 60 * 60_000; for (const [runId, abortedAt] of params.chatRunState.abortedRuns) { - if (now - abortedAt <= ABORTED_RUN_TTL_MS) continue; + if (now - abortedAt <= ABORTED_RUN_TTL_MS) { + continue; + } params.chatRunState.abortedRuns.delete(runId); params.chatRunBuffers.delete(runId); params.chatDeltaSentAt.delete(runId); diff --git a/src/gateway/server-methods.ts b/src/gateway/server-methods.ts index 66492b9765..fac38858be 100644 --- a/src/gateway/server-methods.ts +++ b/src/gateway/server-methods.ts @@ -91,11 +91,15 @@ const WRITE_METHODS = new Set([ ]); function authorizeGatewayMethod(method: string, client: GatewayRequestOptions["client"]) { - if (!client?.connect) return null; + if (!client?.connect) { + return null; + } const role = client.connect.role ?? "operator"; const scopes = client.connect.scopes ?? []; if (NODE_ROLE_METHODS.has(method)) { - if (role === "node") return null; + if (role === "node") { + return null; + } return errorShape(ErrorCodes.INVALID_REQUEST, `unauthorized role: ${role}`); } if (role === "node") { @@ -104,7 +108,9 @@ function authorizeGatewayMethod(method: string, client: GatewayRequestOptions["c if (role !== "operator") { return errorShape(ErrorCodes.INVALID_REQUEST, `unauthorized role: ${role}`); } - if (scopes.includes(ADMIN_SCOPE)) return null; + if (scopes.includes(ADMIN_SCOPE)) { + return null; + } if (APPROVAL_METHODS.has(method) && !scopes.includes(APPROVALS_SCOPE)) { return errorShape(ErrorCodes.INVALID_REQUEST, "missing scope: operator.approvals"); } @@ -117,10 +123,18 @@ function authorizeGatewayMethod(method: string, client: GatewayRequestOptions["c if (WRITE_METHODS.has(method) && !scopes.includes(WRITE_SCOPE)) { return errorShape(ErrorCodes.INVALID_REQUEST, "missing scope: operator.write"); } - if (APPROVAL_METHODS.has(method)) return null; - if (PAIRING_METHODS.has(method)) return null; - if (READ_METHODS.has(method)) return null; - if (WRITE_METHODS.has(method)) return null; + if (APPROVAL_METHODS.has(method)) { + return null; + } + if (PAIRING_METHODS.has(method)) { + return null; + } + if (READ_METHODS.has(method)) { + return null; + } + if (WRITE_METHODS.has(method)) { + return null; + } if (ADMIN_METHOD_PREFIXES.some((prefix) => method.startsWith(prefix))) { return errorShape(ErrorCodes.INVALID_REQUEST, "missing scope: operator.admin"); } diff --git a/src/gateway/server-methods/agent-job.ts b/src/gateway/server-methods/agent-job.ts index 6469b14d18..872d887226 100644 --- a/src/gateway/server-methods/agent-job.ts +++ b/src/gateway/server-methods/agent-job.ts @@ -28,18 +28,26 @@ function recordAgentRunSnapshot(entry: AgentRunSnapshot) { } function ensureAgentRunListener() { - if (agentRunListenerStarted) return; + if (agentRunListenerStarted) { + return; + } agentRunListenerStarted = true; onAgentEvent((evt) => { - if (!evt) return; - if (evt.stream !== "lifecycle") return; + if (!evt) { + return; + } + if (evt.stream !== "lifecycle") { + return; + } const phase = evt.data?.phase; if (phase === "start") { const startedAt = typeof evt.data?.startedAt === "number" ? evt.data.startedAt : undefined; agentRunStarts.set(evt.runId, startedAt ?? Date.now()); return; } - if (phase !== "end" && phase !== "error") return; + if (phase !== "end" && phase !== "error") { + return; + } const startedAt = typeof evt.data?.startedAt === "number" ? evt.data.startedAt : agentRunStarts.get(evt.runId); const endedAt = typeof evt.data?.endedAt === "number" ? evt.data.endedAt : undefined; @@ -68,23 +76,35 @@ export async function waitForAgentJob(params: { const { runId, timeoutMs } = params; ensureAgentRunListener(); const cached = getCachedAgentRun(runId); - if (cached) return cached; - if (timeoutMs <= 0) return null; + if (cached) { + return cached; + } + if (timeoutMs <= 0) { + return null; + } return await new Promise((resolve) => { let settled = false; const finish = (entry: AgentRunSnapshot | null) => { - if (settled) return; + if (settled) { + return; + } settled = true; clearTimeout(timer); unsubscribe(); resolve(entry); }; const unsubscribe = onAgentEvent((evt) => { - if (!evt || evt.stream !== "lifecycle") return; - if (evt.runId !== runId) return; + if (!evt || evt.stream !== "lifecycle") { + return; + } + if (evt.runId !== runId) { + return; + } const phase = evt.data?.phase; - if (phase !== "end" && phase !== "error") return; + if (phase !== "end" && phase !== "error") { + return; + } const cached = getCachedAgentRun(runId); if (cached) { finish(cached); diff --git a/src/gateway/server-methods/browser.ts b/src/gateway/server-methods/browser.ts index 16811fbcfc..24adf8af0e 100644 --- a/src/gateway/server-methods/browser.ts +++ b/src/gateway/server-methods/browser.ts @@ -46,18 +46,32 @@ function normalizeNodeKey(value: string) { function resolveBrowserNode(nodes: NodeSession[], query: string): NodeSession | null { const q = query.trim(); - if (!q) return null; + if (!q) { + return null; + } const qNorm = normalizeNodeKey(q); const matches = nodes.filter((node) => { - if (node.nodeId === q) return true; - if (typeof node.remoteIp === "string" && node.remoteIp === q) return true; + if (node.nodeId === q) { + return true; + } + if (typeof node.remoteIp === "string" && node.remoteIp === q) { + return true; + } const name = typeof node.displayName === "string" ? node.displayName : ""; - if (name && normalizeNodeKey(name) === qNorm) return true; - if (q.length >= 6 && node.nodeId.startsWith(q)) return true; + if (name && normalizeNodeKey(name) === qNorm) { + return true; + } + if (q.length >= 6 && node.nodeId.startsWith(q)) { + return true; + } return false; }); - if (matches.length === 1) return matches[0] ?? null; - if (matches.length === 0) return null; + if (matches.length === 1) { + return matches[0] ?? null; + } + if (matches.length === 0) { + return null; + } throw new Error( `ambiguous node: ${q} (matches: ${matches .map((node) => node.displayName || node.remoteIp || node.nodeId) @@ -71,7 +85,9 @@ function resolveBrowserNodeTarget(params: { }): NodeSession | null { const policy = params.cfg.gateway?.nodes?.browser; const mode = policy?.mode ?? "auto"; - if (mode === "off") return null; + if (mode === "off") { + return null; + } const browserNodes = params.nodes.filter((node) => isBrowserNode(node)); if (browserNodes.length === 0) { if (policy?.node?.trim()) { @@ -87,13 +103,19 @@ function resolveBrowserNodeTarget(params: { } return resolved; } - if (mode === "manual") return null; - if (browserNodes.length === 1) return browserNodes[0] ?? null; + if (mode === "manual") { + return null; + } + if (browserNodes.length === 1) { + return browserNodes[0] ?? null; + } return null; } async function persistProxyFiles(files: BrowserProxyFile[] | undefined) { - if (!files || files.length === 0) return new Map(); + if (!files || files.length === 0) { + return new Map(); + } const mapping = new Map(); for (const file of files) { const buffer = Buffer.from(file.base64, "base64"); @@ -104,7 +126,9 @@ async function persistProxyFiles(files: BrowserProxyFile[] | undefined) { } function applyProxyPaths(result: unknown, mapping: Map) { - if (!result || typeof result !== "object") return; + if (!result || typeof result !== "object") { + return; + } const obj = result as Record; if (typeof obj.path === "string" && mapping.has(obj.path)) { obj.path = mapping.get(obj.path); diff --git a/src/gateway/server-methods/channels.ts b/src/gateway/server-methods/channels.ts index f0b71761e7..ad71d593af 100644 --- a/src/gateway/server-methods/channels.ts +++ b/src/gateway/server-methods/channels.ts @@ -98,7 +98,9 @@ export const channelsHandlers: GatewayRequestHandlers = { const defaultRuntime = runtime.channels[channelId]; const raw = accounts?.[accountId] ?? (accountId === defaultAccountId ? defaultRuntime : undefined); - if (!raw) return undefined; + if (!raw) { + return undefined; + } return raw; }; @@ -171,7 +173,9 @@ export const channelsHandlers: GatewayRequestHandlers = { probe: probeResult, audit: auditResult, }); - if (lastProbeAt) snapshot.lastProbeAt = lastProbeAt; + if (lastProbeAt) { + snapshot.lastProbeAt = lastProbeAt; + } const activity = getChannelActivity({ channel: channelId as never, accountId, diff --git a/src/gateway/server-methods/chat.ts b/src/gateway/server-methods/chat.ts index 9010a6f210..0e10e8df08 100644 --- a/src/gateway/server-methods/chat.ts +++ b/src/gateway/server-methods/chat.ts @@ -56,8 +56,12 @@ function resolveTranscriptPath(params: { sessionFile?: string; }): string | null { const { sessionId, storePath, sessionFile } = params; - if (sessionFile) return sessionFile; - if (!storePath) return null; + if (sessionFile) { + return sessionFile; + } + if (!storePath) { + return null; + } return path.join(path.dirname(storePath), `${sessionId}.jsonl`); } @@ -65,7 +69,9 @@ function ensureTranscriptFile(params: { transcriptPath: string; sessionId: strin ok: boolean; error?: string; } { - if (fs.existsSync(params.transcriptPath)) return { ok: true }; + if (fs.existsSync(params.transcriptPath)) { + return { ok: true }; + } try { fs.mkdirSync(path.dirname(params.transcriptPath), { recursive: true }); const header = { @@ -476,9 +482,13 @@ export const chatHandlers: GatewayRequestHandlers = { context.logGateway.warn(`webchat dispatch failed: ${formatForLog(err)}`); }, deliver: async (payload, info) => { - if (info.kind !== "final") return; + if (info.kind !== "final") { + return; + } const text = payload.text?.trim() ?? ""; - if (!text) return; + if (!text) { + return; + } finalReplyParts.push(text); }, }); diff --git a/src/gateway/server-methods/config.ts b/src/gateway/server-methods/config.ts index 833ce7c489..8984a5ca7a 100644 --- a/src/gateway/server-methods/config.ts +++ b/src/gateway/server-methods/config.ts @@ -33,7 +33,9 @@ import type { GatewayRequestHandlers, RespondFn } from "./types.js"; function resolveBaseHash(params: unknown): string | null { const raw = (params as { baseHash?: unknown })?.baseHash; - if (typeof raw !== "string") return null; + if (typeof raw !== "string") { + return null; + } const trimmed = raw.trim(); return trimmed ? trimmed : null; } @@ -43,7 +45,9 @@ function requireConfigBaseHash( snapshot: Awaited>, respond: RespondFn, ): boolean { - if (!snapshot.exists) return true; + if (!snapshot.exists) { + return true; + } const snapshotHash = resolveConfigSnapshotHash(snapshot); if (!snapshotHash) { respond( diff --git a/src/gateway/server-methods/exec-approval.test.ts b/src/gateway/server-methods/exec-approval.test.ts index 71a63e5a36..6a9fdb720a 100644 --- a/src/gateway/server-methods/exec-approval.test.ts +++ b/src/gateway/server-methods/exec-approval.test.ts @@ -117,7 +117,9 @@ describe("exec approval handlers", () => { const context = { broadcast: (event: string, payload: unknown) => { - if (event !== "exec.approval.requested") return; + if (event !== "exec.approval.requested") { + return; + } const id = (payload as { id?: string })?.id ?? ""; void handlers["exec.approval.resolve"]({ params: { id, decision: "allow-once" }, diff --git a/src/gateway/server-methods/exec-approvals.ts b/src/gateway/server-methods/exec-approvals.ts index 71821b7d0a..7fdda35570 100644 --- a/src/gateway/server-methods/exec-approvals.ts +++ b/src/gateway/server-methods/exec-approvals.ts @@ -21,7 +21,9 @@ import type { GatewayRequestHandlers, RespondFn } from "./types.js"; function resolveBaseHash(params: unknown): string | null { const raw = (params as { baseHash?: unknown })?.baseHash; - if (typeof raw !== "string") return null; + if (typeof raw !== "string") { + return null; + } const trimmed = raw.trim(); return trimmed ? trimmed : null; } @@ -31,7 +33,9 @@ function requireApprovalsBaseHash( snapshot: ExecApprovalsSnapshot, respond: RespondFn, ): boolean { - if (!snapshot.exists) return true; + if (!snapshot.exists) { + return true; + } if (!snapshot.hash) { respond( false, diff --git a/src/gateway/server-methods/logs.ts b/src/gateway/server-methods/logs.ts index 1f8c07c88b..97091386d6 100644 --- a/src/gateway/server-methods/logs.ts +++ b/src/gateway/server-methods/logs.ts @@ -25,12 +25,18 @@ function isRollingLogFile(file: string): boolean { async function resolveLogFile(file: string): Promise { const stat = await fs.stat(file).catch(() => null); - if (stat) return file; - if (!isRollingLogFile(file)) return file; + if (stat) { + return file; + } + if (!isRollingLogFile(file)) { + return file; + } const dir = path.dirname(file); const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => null); - if (!entries) return file; + if (!entries) { + return file; + } const candidates = await Promise.all( entries diff --git a/src/gateway/server-methods/nodes.helpers.ts b/src/gateway/server-methods/nodes.helpers.ts index a4c66b0293..2c1ffae4ef 100644 --- a/src/gateway/server-methods/nodes.helpers.ts +++ b/src/gateway/server-methods/nodes.helpers.ts @@ -38,9 +38,13 @@ export function uniqueSortedStrings(values: unknown[]) { } export function safeParseJson(value: string | null | undefined): unknown { - if (typeof value !== "string") return undefined; + if (typeof value !== "string") { + return undefined; + } const trimmed = value.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } try { return JSON.parse(trimmed) as unknown; } catch { diff --git a/src/gateway/server-methods/nodes.ts b/src/gateway/server-methods/nodes.ts index 0ae80dd9cf..dd39784938 100644 --- a/src/gateway/server-methods/nodes.ts +++ b/src/gateway/server-methods/nodes.ts @@ -33,13 +33,19 @@ import { isNodeCommandAllowed, resolveNodeCommandAllowlist } from "../node-comma import type { GatewayRequestHandlers } from "./types.js"; function isNodeEntry(entry: { role?: string; roles?: string[] }) { - if (entry.role === "node") return true; - if (Array.isArray(entry.roles) && entry.roles.includes("node")) return true; + if (entry.role === "node") { + return true; + } + if (Array.isArray(entry.roles) && entry.roles.includes("node")) { + return true; + } return false; } function normalizeNodeInvokeResultParams(params: unknown): unknown { - if (!params || typeof params !== "object") return params; + if (!params || typeof params !== "object") { + return params; + } const raw = params as Record; const normalized: Record = { ...raw }; if (normalized.payloadJSON === null) { @@ -284,11 +290,17 @@ export const nodeHandlers: GatewayRequestHandlers = { }); nodes.sort((a, b) => { - if (a.connected !== b.connected) return a.connected ? -1 : 1; + if (a.connected !== b.connected) { + return a.connected ? -1 : 1; + } const an = (a.displayName ?? a.nodeId).toLowerCase(); const bn = (b.displayName ?? b.nodeId).toLowerCase(); - if (an < bn) return -1; - if (an > bn) return 1; + if (an < bn) { + return -1; + } + if (an > bn) { + return 1; + } return a.nodeId.localeCompare(b.nodeId); }); diff --git a/src/gateway/server-methods/send.ts b/src/gateway/server-methods/send.ts index 58807bccf7..8981acc8cf 100644 --- a/src/gateway/server-methods/send.ts +++ b/src/gateway/server-methods/send.ts @@ -199,9 +199,15 @@ export const sendHandlers: GatewayRequestHandlers = { messageId: result.messageId, channel, }; - if ("chatId" in result) payload.chatId = result.chatId; - if ("channelId" in result) payload.channelId = result.channelId; - if ("toJid" in result) payload.toJid = result.toJid; + if ("chatId" in result) { + payload.chatId = result.chatId; + } + if ("channelId" in result) { + payload.channelId = result.channelId; + } + if ("toJid" in result) { + payload.toJid = result.toJid; + } if ("conversationId" in result) { payload.conversationId = result.conversationId; } @@ -324,10 +330,18 @@ export const sendHandlers: GatewayRequestHandlers = { messageId: result.messageId, channel, }; - if (result.toJid) payload.toJid = result.toJid; - if (result.channelId) payload.channelId = result.channelId; - if (result.conversationId) payload.conversationId = result.conversationId; - if (result.pollId) payload.pollId = result.pollId; + if (result.toJid) { + payload.toJid = result.toJid; + } + if (result.channelId) { + payload.channelId = result.channelId; + } + if (result.conversationId) { + payload.conversationId = result.conversationId; + } + if (result.pollId) { + payload.pollId = result.pollId; + } context.dedupe.set(`poll:${idem}`, { ts: Date.now(), ok: true, diff --git a/src/gateway/server-methods/sessions.ts b/src/gateway/server-methods/sessions.ts index f50e1ef492..cb71accfb2 100644 --- a/src/gateway/server-methods/sessions.ts +++ b/src/gateway/server-methods/sessions.ts @@ -300,7 +300,9 @@ export const sessionsHandlers: GatewayRequestHandlers = { const existed = Boolean(entry); const queueKeys = new Set(target.storeKeys); queueKeys.add(target.canonicalKey); - if (sessionId) queueKeys.add(sessionId); + if (sessionId) { + queueKeys.add(sessionId); + } clearSessionQueues([...queueKeys]); stopSubagentsForRequester({ cfg, requesterSessionKey: target.canonicalKey }); if (sessionId) { @@ -325,7 +327,9 @@ export const sessionsHandlers: GatewayRequestHandlers = { store[primaryKey] = store[existingKey]; delete store[existingKey]; } - if (store[primaryKey]) delete store[primaryKey]; + if (store[primaryKey]) { + delete store[primaryKey]; + } }); const archived: string[] = []; @@ -336,7 +340,9 @@ export const sessionsHandlers: GatewayRequestHandlers = { entry?.sessionFile, target.agentId, )) { - if (!fs.existsSync(candidate)) continue; + if (!fs.existsSync(candidate)) { + continue; + } try { archived.push(archiveFileOnDisk(candidate, "deleted")); } catch { @@ -443,7 +449,9 @@ export const sessionsHandlers: GatewayRequestHandlers = { await updateSessionStore(storePath, (store) => { const entryKey = compactTarget.primaryKey; const entryToUpdate = store[entryKey]; - if (!entryToUpdate) return; + if (!entryToUpdate) { + return; + } delete entryToUpdate.inputTokens; delete entryToUpdate.outputTokens; delete entryToUpdate.totalTokens; diff --git a/src/gateway/server-methods/skills.ts b/src/gateway/server-methods/skills.ts index d5e872fdbf..d9ecb3a8c2 100644 --- a/src/gateway/server-methods/skills.ts +++ b/src/gateway/server-methods/skills.ts @@ -38,17 +38,23 @@ function collectSkillBins(entries: SkillEntry[]): string[] { const install = entry.metadata?.install ?? []; for (const bin of required) { const trimmed = bin.trim(); - if (trimmed) bins.add(trimmed); + if (trimmed) { + bins.add(trimmed); + } } for (const bin of anyBins) { const trimmed = bin.trim(); - if (trimmed) bins.add(trimmed); + if (trimmed) { + bins.add(trimmed); + } } for (const spec of install) { const specBins = spec?.bins ?? []; for (const bin of specBins) { const trimmed = String(bin).trim(); - if (trimmed) bins.add(trimmed); + if (trimmed) { + bins.add(trimmed); + } } } } @@ -93,7 +99,9 @@ export const skillsHandlers: GatewayRequestHandlers = { const bins = new Set(); for (const workspaceDir of workspaceDirs) { const entries = loadWorkspaceSkillEntries(workspaceDir, { config: cfg }); - for (const bin of collectSkillBins(entries)) bins.add(bin); + for (const bin of collectSkillBins(entries)) { + bins.add(bin); + } } respond(true, { bins: [...bins].toSorted() }, undefined); }, @@ -156,17 +164,25 @@ export const skillsHandlers: GatewayRequestHandlers = { } if (typeof p.apiKey === "string") { const trimmed = p.apiKey.trim(); - if (trimmed) current.apiKey = trimmed; - else delete current.apiKey; + if (trimmed) { + current.apiKey = trimmed; + } else { + delete current.apiKey; + } } if (p.env && typeof p.env === "object") { const nextEnv = current.env ? { ...current.env } : {}; for (const [key, value] of Object.entries(p.env)) { const trimmedKey = key.trim(); - if (!trimmedKey) continue; + if (!trimmedKey) { + continue; + } const trimmedVal = value.trim(); - if (!trimmedVal) delete nextEnv[trimmedKey]; - else nextEnv[trimmedKey] = trimmedVal; + if (!trimmedVal) { + delete nextEnv[trimmedKey]; + } else { + nextEnv[trimmedKey] = trimmedVal; + } } current.env = nextEnv; } diff --git a/src/gateway/server-methods/usage.ts b/src/gateway/server-methods/usage.ts index dcdd897422..1774e9d8fc 100644 --- a/src/gateway/server-methods/usage.ts +++ b/src/gateway/server-methods/usage.ts @@ -15,10 +15,14 @@ type CostUsageCacheEntry = { const costUsageCache = new Map(); const parseDays = (raw: unknown): number => { - if (typeof raw === "number" && Number.isFinite(raw)) return Math.floor(raw); + if (typeof raw === "number" && Number.isFinite(raw)) { + return Math.floor(raw); + } if (typeof raw === "string" && raw.trim() !== "") { const parsed = Number(raw); - if (Number.isFinite(parsed)) return Math.floor(parsed); + if (Number.isFinite(parsed)) { + return Math.floor(parsed); + } } return 30; }; @@ -35,7 +39,9 @@ async function loadCostUsageSummaryCached(params: { } if (cached?.inFlight) { - if (cached.summary) return cached.summary; + if (cached.summary) { + return cached.summary; + } return await cached.inFlight; } @@ -46,7 +52,9 @@ async function loadCostUsageSummaryCached(params: { return summary; }) .catch((err) => { - if (entry.summary) return entry.summary; + if (entry.summary) { + return entry.summary; + } throw err; }) .finally(() => { @@ -60,7 +68,9 @@ async function loadCostUsageSummaryCached(params: { entry.inFlight = inFlight; costUsageCache.set(days, entry); - if (entry.summary) return entry.summary; + if (entry.summary) { + return entry.summary; + } return await inFlight; } diff --git a/src/gateway/server-mobile-nodes.ts b/src/gateway/server-mobile-nodes.ts index c9271f15cb..c4baeaf143 100644 --- a/src/gateway/server-mobile-nodes.ts +++ b/src/gateway/server-mobile-nodes.ts @@ -2,7 +2,9 @@ import type { NodeRegistry } from "./node-registry.js"; const isMobilePlatform = (platform: unknown): boolean => { const p = typeof platform === "string" ? platform.trim().toLowerCase() : ""; - if (!p) return false; + if (!p) { + return false; + } return p.startsWith("ios") || p.startsWith("ipados") || p.startsWith("android"); }; diff --git a/src/gateway/server-node-events.ts b/src/gateway/server-node-events.ts index 6870f8a0f2..2913f263b6 100644 --- a/src/gateway/server-node-events.ts +++ b/src/gateway/server-node-events.ts @@ -14,7 +14,9 @@ import { formatForLog } from "./ws-log.js"; export const handleNodeEvent = async (ctx: NodeEventContext, nodeId: string, evt: NodeEvent) => { switch (evt.event) { case "voice.transcript": { - if (!evt.payloadJSON) return; + if (!evt.payloadJSON) { + return; + } let payload: unknown; try { payload = JSON.parse(evt.payloadJSON) as unknown; @@ -24,8 +26,12 @@ export const handleNodeEvent = async (ctx: NodeEventContext, nodeId: string, evt const obj = typeof payload === "object" && payload !== null ? (payload as Record) : {}; const text = typeof obj.text === "string" ? obj.text.trim() : ""; - if (!text) return; - if (text.length > 20_000) return; + if (!text) { + return; + } + if (text.length > 20_000) { + return; + } const sessionKeyRaw = typeof obj.sessionKey === "string" ? obj.sessionKey.trim() : ""; const cfg = loadConfig(); const rawMainKey = normalizeMainKey(cfg.session?.mainKey); @@ -73,7 +79,9 @@ export const handleNodeEvent = async (ctx: NodeEventContext, nodeId: string, evt return; } case "agent.request": { - if (!evt.payloadJSON) return; + if (!evt.payloadJSON) { + return; + } type AgentDeepLink = { message?: string; sessionKey?: string | null; @@ -91,8 +99,12 @@ export const handleNodeEvent = async (ctx: NodeEventContext, nodeId: string, evt return; } const message = (link?.message ?? "").trim(); - if (!message) return; - if (message.length > 20_000) return; + if (!message) { + return; + } + if (message.length > 20_000) { + return; + } const channelRaw = typeof link?.channel === "string" ? link.channel.trim() : ""; const channel = normalizeChannelId(channelRaw) ?? undefined; @@ -141,7 +153,9 @@ export const handleNodeEvent = async (ctx: NodeEventContext, nodeId: string, evt return; } case "chat.subscribe": { - if (!evt.payloadJSON) return; + if (!evt.payloadJSON) { + return; + } let payload: unknown; try { payload = JSON.parse(evt.payloadJSON) as unknown; @@ -151,12 +165,16 @@ export const handleNodeEvent = async (ctx: NodeEventContext, nodeId: string, evt const obj = typeof payload === "object" && payload !== null ? (payload as Record) : {}; const sessionKey = typeof obj.sessionKey === "string" ? obj.sessionKey.trim() : ""; - if (!sessionKey) return; + if (!sessionKey) { + return; + } ctx.nodeSubscribe(nodeId, sessionKey); return; } case "chat.unsubscribe": { - if (!evt.payloadJSON) return; + if (!evt.payloadJSON) { + return; + } let payload: unknown; try { payload = JSON.parse(evt.payloadJSON) as unknown; @@ -166,14 +184,18 @@ export const handleNodeEvent = async (ctx: NodeEventContext, nodeId: string, evt const obj = typeof payload === "object" && payload !== null ? (payload as Record) : {}; const sessionKey = typeof obj.sessionKey === "string" ? obj.sessionKey.trim() : ""; - if (!sessionKey) return; + if (!sessionKey) { + return; + } ctx.nodeUnsubscribe(nodeId, sessionKey); return; } case "exec.started": case "exec.finished": case "exec.denied": { - if (!evt.payloadJSON) return; + if (!evt.payloadJSON) { + return; + } let payload: unknown; try { payload = JSON.parse(evt.payloadJSON) as unknown; @@ -184,7 +206,9 @@ export const handleNodeEvent = async (ctx: NodeEventContext, nodeId: string, evt typeof payload === "object" && payload !== null ? (payload as Record) : {}; const sessionKey = typeof obj.sessionKey === "string" ? obj.sessionKey.trim() : `node-${nodeId}`; - if (!sessionKey) return; + if (!sessionKey) { + return; + } const runId = typeof obj.runId === "string" ? obj.runId.trim() : ""; const command = typeof obj.command === "string" ? obj.command.trim() : ""; const exitCode = @@ -198,14 +222,20 @@ export const handleNodeEvent = async (ctx: NodeEventContext, nodeId: string, evt let text = ""; if (evt.event === "exec.started") { text = `Exec started (node=${nodeId}${runId ? ` id=${runId}` : ""})`; - if (command) text += `: ${command}`; + if (command) { + text += `: ${command}`; + } } else if (evt.event === "exec.finished") { const exitLabel = timedOut ? "timeout" : `code ${exitCode ?? "?"}`; text = `Exec finished (node=${nodeId}${runId ? ` id=${runId}` : ""}, ${exitLabel})`; - if (output) text += `\n${output}`; + if (output) { + text += `\n${output}`; + } } else { text = `Exec denied (node=${nodeId}${runId ? ` id=${runId}` : ""}${reason ? `, ${reason}` : ""})`; - if (command) text += `: ${command}`; + if (command) { + text += `: ${command}`; + } } enqueueSystemEvent(text, { sessionKey, contextKey: runId ? `exec:${runId}` : "exec" }); diff --git a/src/gateway/server-node-subscriptions.ts b/src/gateway/server-node-subscriptions.ts index 33eecb0bed..e341ce30f2 100644 --- a/src/gateway/server-node-subscriptions.ts +++ b/src/gateway/server-node-subscriptions.ts @@ -39,14 +39,18 @@ export function createNodeSubscriptionManager(): NodeSubscriptionManager { const subscribe = (nodeId: string, sessionKey: string) => { const normalizedNodeId = nodeId.trim(); const normalizedSessionKey = sessionKey.trim(); - if (!normalizedNodeId || !normalizedSessionKey) return; + if (!normalizedNodeId || !normalizedSessionKey) { + return; + } let nodeSet = nodeSubscriptions.get(normalizedNodeId); if (!nodeSet) { nodeSet = new Set(); nodeSubscriptions.set(normalizedNodeId, nodeSet); } - if (nodeSet.has(normalizedSessionKey)) return; + if (nodeSet.has(normalizedSessionKey)) { + return; + } nodeSet.add(normalizedSessionKey); let sessionSet = sessionSubscribers.get(normalizedSessionKey); @@ -60,25 +64,35 @@ export function createNodeSubscriptionManager(): NodeSubscriptionManager { const unsubscribe = (nodeId: string, sessionKey: string) => { const normalizedNodeId = nodeId.trim(); const normalizedSessionKey = sessionKey.trim(); - if (!normalizedNodeId || !normalizedSessionKey) return; + if (!normalizedNodeId || !normalizedSessionKey) { + return; + } const nodeSet = nodeSubscriptions.get(normalizedNodeId); nodeSet?.delete(normalizedSessionKey); - if (nodeSet?.size === 0) nodeSubscriptions.delete(normalizedNodeId); + if (nodeSet?.size === 0) { + nodeSubscriptions.delete(normalizedNodeId); + } const sessionSet = sessionSubscribers.get(normalizedSessionKey); sessionSet?.delete(normalizedNodeId); - if (sessionSet?.size === 0) sessionSubscribers.delete(normalizedSessionKey); + if (sessionSet?.size === 0) { + sessionSubscribers.delete(normalizedSessionKey); + } }; const unsubscribeAll = (nodeId: string) => { const normalizedNodeId = nodeId.trim(); const nodeSet = nodeSubscriptions.get(normalizedNodeId); - if (!nodeSet) return; + if (!nodeSet) { + return; + } for (const sessionKey of nodeSet) { const sessionSet = sessionSubscribers.get(sessionKey); sessionSet?.delete(normalizedNodeId); - if (sessionSet?.size === 0) sessionSubscribers.delete(sessionKey); + if (sessionSet?.size === 0) { + sessionSubscribers.delete(sessionKey); + } } nodeSubscriptions.delete(normalizedNodeId); }; @@ -90,9 +104,13 @@ export function createNodeSubscriptionManager(): NodeSubscriptionManager { sendEvent?: NodeSendEventFn | null, ) => { const normalizedSessionKey = sessionKey.trim(); - if (!normalizedSessionKey || !sendEvent) return; + if (!normalizedSessionKey || !sendEvent) { + return; + } const subs = sessionSubscribers.get(normalizedSessionKey); - if (!subs || subs.size === 0) return; + if (!subs || subs.size === 0) { + return; + } const payloadJSON = toPayloadJSON(payload); for (const nodeId of subs) { @@ -105,7 +123,9 @@ export function createNodeSubscriptionManager(): NodeSubscriptionManager { payload: unknown, sendEvent?: NodeSendEventFn | null, ) => { - if (!sendEvent) return; + if (!sendEvent) { + return; + } const payloadJSON = toPayloadJSON(payload); for (const nodeId of nodeSubscriptions.keys()) { sendEvent({ nodeId, event, payloadJSON }); @@ -118,7 +138,9 @@ export function createNodeSubscriptionManager(): NodeSubscriptionManager { listConnected?: NodeListConnectedFn | null, sendEvent?: NodeSendEventFn | null, ) => { - if (!sendEvent || !listConnected) return; + if (!sendEvent || !listConnected) { + return; + } const payloadJSON = toPayloadJSON(payload); for (const node of listConnected()) { sendEvent({ nodeId: node.nodeId, event, payloadJSON }); diff --git a/src/gateway/server-restart-sentinel.ts b/src/gateway/server-restart-sentinel.ts index 28719290eb..969e0c577d 100644 --- a/src/gateway/server-restart-sentinel.ts +++ b/src/gateway/server-restart-sentinel.ts @@ -16,7 +16,9 @@ import { loadSessionEntry } from "./session-utils.js"; export async function scheduleRestartSentinelWake(params: { deps: CliDeps }) { const sentinel = await consumeRestartSentinel(); - if (!sentinel) return; + if (!sentinel) { + return; + } const payload = sentinel.payload; const sessionKey = payload.sessionKey?.trim(); const message = formatRestartSentinelMessage(payload); diff --git a/src/gateway/server-runtime-state.ts b/src/gateway/server-runtime-state.ts index 203383a0b1..24dd043f5b 100644 --- a/src/gateway/server-runtime-state.ts +++ b/src/gateway/server-runtime-state.ts @@ -129,7 +129,9 @@ export async function createGatewayRuntimeState(params: { httpServers.push(httpServer); httpBindHosts.push(host); } catch (err) { - if (host === bindHosts[0]) throw err; + if (host === bindHosts[0]) { + throw err; + } params.log.warn( `gateway: failed to bind loopback alias ${host}:${params.port} (${String(err)})`, ); diff --git a/src/gateway/server-session-key.ts b/src/gateway/server-session-key.ts index b1931c4bbe..4a9694f66b 100644 --- a/src/gateway/server-session-key.ts +++ b/src/gateway/server-session-key.ts @@ -5,7 +5,9 @@ import { toAgentRequestSessionKey } from "../routing/session-key.js"; export function resolveSessionKeyForRun(runId: string) { const cached = getAgentRunContext(runId)?.sessionKey; - if (cached) return cached; + if (cached) { + return cached; + } const cfg = loadConfig(); const storePath = resolveStorePath(cfg.session?.store); const store = loadSessionStore(storePath); diff --git a/src/gateway/server-utils.ts b/src/gateway/server-utils.ts index 43cbfa5a58..ed81275670 100644 --- a/src/gateway/server-utils.ts +++ b/src/gateway/server-utils.ts @@ -11,8 +11,12 @@ export function normalizeVoiceWakeTriggers(input: unknown): string[] { } export function formatError(err: unknown): string { - if (err instanceof Error) return err.message; - if (typeof err === "string") return err; + if (err instanceof Error) { + return err.message; + } + if (typeof err === "string") { + return err; + } const statusValue = (err as { status?: unknown })?.status; const codeValue = (err as { code?: unknown })?.code; const hasStatus = statusValue !== undefined; diff --git a/src/gateway/server-wizard-sessions.ts b/src/gateway/server-wizard-sessions.ts index bfa858874f..9b2c3de450 100644 --- a/src/gateway/server-wizard-sessions.ts +++ b/src/gateway/server-wizard-sessions.ts @@ -5,15 +5,21 @@ export function createWizardSessionTracker() { const findRunningWizard = (): string | null => { for (const [id, session] of wizardSessions) { - if (session.getStatus() === "running") return id; + if (session.getStatus() === "running") { + return id; + } } return null; }; const purgeWizardSession = (id: string) => { const session = wizardSessions.get(id); - if (!session) return; - if (session.getStatus() === "running") return; + if (!session) { + return; + } + if (session.getStatus() === "running") { + return; + } wizardSessions.delete(id); }; diff --git a/src/gateway/server.agent.gateway-server-agent-a.e2e.test.ts b/src/gateway/server.agent.gateway-server-agent-a.e2e.test.ts index 5c5be95716..b120939592 100644 --- a/src/gateway/server.agent.gateway-server-agent-a.e2e.test.ts +++ b/src/gateway/server.agent.gateway-server-agent-a.e2e.test.ts @@ -113,9 +113,13 @@ const createStubChannelPlugin = (params: { deliveryMode: "direct", resolveTarget: ({ to, allowFrom }) => { const trimmed = to?.trim() ?? ""; - if (trimmed) return { ok: true, to: trimmed }; + if (trimmed) { + return { ok: true, to: trimmed }; + } const first = allowFrom?.[0]; - if (first) return { ok: true, to: String(first) }; + if (first) { + return { ok: true, to: String(first) }; + } return { ok: false, error: new Error(`missing target for ${params.id}`), diff --git a/src/gateway/server.agent.gateway-server-agent-b.e2e.test.ts b/src/gateway/server.agent.gateway-server-agent-b.e2e.test.ts index 742117a08c..9e6511ef85 100644 --- a/src/gateway/server.agent.gateway-server-agent-b.e2e.test.ts +++ b/src/gateway/server.agent.gateway-server-agent-b.e2e.test.ts @@ -422,7 +422,9 @@ describe("gateway server agent", () => { const finalChatP = onceMessage( webchatWs, (o) => { - if (o.type !== "event" || o.event !== "chat") return false; + if (o.type !== "event" || o.event !== "chat") { + return false; + } const payload = o.payload as { state?: unknown; runId?: unknown } | undefined; return payload?.state === "final" && payload.runId === "run-auto-1"; }, diff --git a/src/gateway/server.auth.e2e.test.ts b/src/gateway/server.auth.e2e.test.ts index 3e2cdd4ce3..5adcf3ec8d 100644 --- a/src/gateway/server.auth.e2e.test.ts +++ b/src/gateway/server.auth.e2e.test.ts @@ -17,7 +17,9 @@ import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-cha installGatewayTestHooks({ scope: "suite" }); async function waitForWsClose(ws: WebSocket, timeoutMs: number): Promise { - if (ws.readyState === WebSocket.CLOSED) return true; + if (ws.readyState === WebSocket.CLOSED) { + return true; + } return await new Promise((resolve) => { const timer = setTimeout(() => resolve(ws.readyState === WebSocket.CLOSED), timeoutMs); ws.once("close", () => { diff --git a/src/gateway/server.chat.gateway-server-chat-b.e2e.test.ts b/src/gateway/server.chat.gateway-server-chat-b.e2e.test.ts index d6d1089380..dd6c39bcf8 100644 --- a/src/gateway/server.chat.gateway-server-chat-b.e2e.test.ts +++ b/src/gateway/server.chat.gateway-server-chat-b.e2e.test.ts @@ -19,7 +19,9 @@ installGatewayTestHooks({ scope: "suite" }); async function waitFor(condition: () => boolean, timeoutMs = 1500) { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { - if (condition()) return; + if (condition()) { + return; + } await new Promise((r) => setTimeout(r, 5)); } throw new Error("timeout waiting for condition"); @@ -127,8 +129,12 @@ describe("gateway server chat", () => { opts?.onAgentRunStart?.(opts.runId ?? "idem-abort-1"); const signal = opts?.abortSignal; await new Promise((resolve) => { - if (!signal) return resolve(); - if (signal.aborted) return resolve(); + if (!signal) { + return resolve(); + } + if (signal.aborted) { + return resolve(); + } signal.addEventListener("abort", () => resolve(), { once: true }); }); }); @@ -155,9 +161,12 @@ describe("gateway server chat", () => { await new Promise((resolve, reject) => { const deadline = Date.now() + 1000; const tick = () => { - if (spy.mock.calls.length > callsBefore) return resolve(); - if (Date.now() > deadline) + if (spy.mock.calls.length > callsBefore) { + return resolve(); + } + if (Date.now() > deadline) { return reject(new Error("timeout waiting for getReplyFromConfig")); + } setTimeout(tick, 5); }; tick(); @@ -183,8 +192,12 @@ describe("gateway server chat", () => { opts?.onAgentRunStart?.(opts.runId ?? "idem-abort-save-1"); const signal = opts?.abortSignal; await new Promise((resolve) => { - if (!signal) return resolve(); - if (signal.aborted) return resolve(); + if (!signal) { + return resolve(); + } + if (signal.aborted) { + return resolve(); + } signal.addEventListener("abort", () => resolve(), { once: true }); }); }); @@ -222,8 +235,12 @@ describe("gateway server chat", () => { opts?.onAgentRunStart?.(opts.runId ?? "idem-stop-1"); const signal = opts?.abortSignal; await new Promise((resolve) => { - if (!signal) return resolve(); - if (signal.aborted) return resolve(); + if (!signal) { + return resolve(); + } + if (signal.aborted) { + return resolve(); + } signal.addEventListener("abort", () => resolve(), { once: true }); }); }); @@ -303,8 +320,12 @@ describe("gateway server chat", () => { opts?.onAgentRunStart?.(opts.runId ?? "idem-abort-all-1"); const signal = opts?.abortSignal; await new Promise((resolve) => { - if (!signal) return resolve(); - if (signal.aborted) return resolve(); + if (!signal) { + return resolve(); + } + if (signal.aborted) { + return resolve(); + } signal.addEventListener("abort", () => resolve(), { once: true }); }); }); @@ -369,8 +390,12 @@ describe("gateway server chat", () => { agentStartedResolve?.(); const signal = opts?.abortSignal; await new Promise((resolve) => { - if (!signal) return resolve(); - if (signal.aborted) return resolve(); + if (!signal) { + return resolve(); + } + if (signal.aborted) { + return resolve(); + } signal.addEventListener("abort", () => resolve(), { once: true }); }); }); diff --git a/src/gateway/server.chat.gateway-server-chat.e2e.test.ts b/src/gateway/server.chat.gateway-server-chat.e2e.test.ts index 0cc9cee938..c3e77a7323 100644 --- a/src/gateway/server.chat.gateway-server-chat.e2e.test.ts +++ b/src/gateway/server.chat.gateway-server-chat.e2e.test.ts @@ -38,7 +38,9 @@ afterAll(async () => { async function waitFor(condition: () => boolean, timeoutMs = 1500) { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { - if (condition()) return; + if (condition()) { + return; + } await new Promise((r) => setTimeout(r, 5)); } throw new Error("timeout waiting for condition"); @@ -273,11 +275,17 @@ describe("gateway server chat", () => { expect(defaultRes.ok).toBe(true); const defaultMsgs = defaultRes.payload?.messages ?? []; const firstContentText = (msg: unknown): string | undefined => { - if (!msg || typeof msg !== "object") return undefined; + if (!msg || typeof msg !== "object") { + return undefined; + } const content = (msg as { content?: unknown }).content; - if (!Array.isArray(content) || content.length === 0) return undefined; + if (!Array.isArray(content) || content.length === 0) { + return undefined; + } const first = content[0]; - if (!first || typeof first !== "object") return undefined; + if (!first || typeof first !== "object") { + return undefined; + } const text = (first as { text?: unknown }).text; return typeof text === "string" ? text : undefined; }; @@ -287,7 +295,9 @@ describe("gateway server chat", () => { testState.agentConfig = undefined; testState.sessionStorePath = undefined; testState.sessionConfig = undefined; - if (webchatWs) webchatWs.close(); + if (webchatWs) { + webchatWs.close(); + } await Promise.all(tempDirs.map((dir) => fs.rm(dir, { recursive: true, force: true }))); } }); diff --git a/src/gateway/server.config-apply.e2e.test.ts b/src/gateway/server.config-apply.e2e.test.ts index aa7bfa0b72..4505ef7fbd 100644 --- a/src/gateway/server.config-apply.e2e.test.ts +++ b/src/gateway/server.config-apply.e2e.test.ts @@ -27,8 +27,11 @@ beforeAll(async () => { afterAll(async () => { await server.close(); - if (previousToken === undefined) delete process.env.OPENCLAW_GATEWAY_TOKEN; - else process.env.OPENCLAW_GATEWAY_TOKEN = previousToken; + if (previousToken === undefined) { + delete process.env.OPENCLAW_GATEWAY_TOKEN; + } else { + process.env.OPENCLAW_GATEWAY_TOKEN = previousToken; + } }); const openClient = async () => { diff --git a/src/gateway/server.cron.e2e.test.ts b/src/gateway/server.cron.e2e.test.ts index 927ce141e0..f7d8982997 100644 --- a/src/gateway/server.cron.e2e.test.ts +++ b/src/gateway/server.cron.e2e.test.ts @@ -39,7 +39,9 @@ async function waitForNonEmptyFile(pathname: string, timeoutMs = 2000) { const startedAt = process.hrtime.bigint(); for (;;) { const raw = await fs.readFile(pathname, "utf-8").catch(() => ""); - if (raw.trim().length > 0) return raw; + if (raw.trim().length > 0) { + return raw; + } const elapsedMs = Number(process.hrtime.bigint() - startedAt) / 1e6; if (elapsedMs >= timeoutMs) { throw new Error(`timeout waiting for file ${pathname}`); diff --git a/src/gateway/server.health.e2e.test.ts b/src/gateway/server.health.e2e.test.ts index 17b3abe56c..2faf7bb3f2 100644 --- a/src/gateway/server.health.e2e.test.ts +++ b/src/gateway/server.health.e2e.test.ts @@ -36,8 +36,11 @@ beforeAll(async () => { afterAll(async () => { await server.close(); - if (previousToken === undefined) delete process.env.OPENCLAW_GATEWAY_TOKEN; - else process.env.OPENCLAW_GATEWAY_TOKEN = previousToken; + if (previousToken === undefined) { + delete process.env.OPENCLAW_GATEWAY_TOKEN; + } else { + process.env.OPENCLAW_GATEWAY_TOKEN = previousToken; + } }); const openClient = async (opts?: Parameters[1]) => { @@ -210,7 +213,9 @@ describe("gateway server health/presence", () => { expect(evt.payload?.presence?.length).toBeGreaterThan(0); expect(typeof evt.seq).toBe("number"); } - for (const c of clients) c.close(); + for (const c of clients) { + c.close(); + } }); test("presence includes client fingerprint", async () => { diff --git a/src/gateway/server.impl.ts b/src/gateway/server.impl.ts index efa91be768..da190eb655 100644 --- a/src/gateway/server.impl.ts +++ b/src/gateway/server.impl.ts @@ -366,8 +366,12 @@ export async function startGatewayServer( let skillsRefreshTimer: ReturnType | null = null; const skillsRefreshDelayMs = 30_000; const skillsChangeUnsub = registerSkillsChangeListener((event) => { - if (event.reason === "remote-node") return; - if (skillsRefreshTimer) clearTimeout(skillsRefreshTimer); + if (event.reason === "remote-node") { + return; + } + if (skillsRefreshTimer) { + clearTimeout(skillsRefreshTimer); + } skillsRefreshTimer = setTimeout(() => { skillsRefreshTimer = null; const latest = loadConfig(); diff --git a/src/gateway/server.models-voicewake-misc.e2e.test.ts b/src/gateway/server.models-voicewake-misc.e2e.test.ts index 6c1ed968ce..828067da43 100644 --- a/src/gateway/server.models-voicewake-misc.e2e.test.ts +++ b/src/gateway/server.models-voicewake-misc.e2e.test.ts @@ -376,7 +376,9 @@ describe("gateway server misc", () => { test("auto-enables configured channel plugins on startup", async () => { const configPath = process.env.OPENCLAW_CONFIG_PATH; - if (!configPath) throw new Error("Missing OPENCLAW_CONFIG_PATH"); + if (!configPath) { + throw new Error("Missing OPENCLAW_CONFIG_PATH"); + } await fs.mkdir(path.dirname(configPath), { recursive: true }); await fs.writeFile( configPath, diff --git a/src/gateway/server.roles-allowlist-update.e2e.test.ts b/src/gateway/server.roles-allowlist-update.e2e.test.ts index e236cc7dd2..0fe6daec4e 100644 --- a/src/gateway/server.roles-allowlist-update.e2e.test.ts +++ b/src/gateway/server.roles-allowlist-update.e2e.test.ts @@ -73,17 +73,23 @@ const connectNodeClient = async (params: { commands: params.commands, onEvent: params.onEvent, onHelloOk: () => { - if (settled) return; + if (settled) { + return; + } settled = true; resolveReady?.(); }, onConnectError: (err) => { - if (settled) return; + if (settled) { + return; + } settled = true; rejectReady?.(err); }, onClose: (code, reason) => { - if (settled) return; + if (settled) { + return; + } settled = true; rejectReady?.(new Error(`gateway closed (${code}): ${reason}`)); }, @@ -101,7 +107,9 @@ const connectNodeClient = async (params: { async function waitForSignal(check: () => boolean, timeoutMs = 2000) { const start = Date.now(); while (Date.now() - start < timeoutMs) { - if (check()) return; + if (check()) { + return; + } await new Promise((resolve) => setTimeout(resolve, 10)); } throw new Error("timeout"); diff --git a/src/gateway/server.sessions-send.e2e.test.ts b/src/gateway/server.sessions-send.e2e.test.ts index c04709abde..52a3d380e1 100644 --- a/src/gateway/server.sessions-send.e2e.test.ts +++ b/src/gateway/server.sessions-send.e2e.test.ts @@ -87,7 +87,9 @@ describe("sessions_send gateway loopback", () => { }); const tool = createOpenClawTools().find((candidate) => candidate.name === "sessions_send"); - if (!tool) throw new Error("missing sessions_send tool"); + if (!tool) { + throw new Error("missing sessions_send tool"); + } const result = await tool.execute("call-loopback", { sessionKey: "main", @@ -152,7 +154,9 @@ describe("sessions_send label lookup", () => { }); const tool = createOpenClawTools().find((candidate) => candidate.name === "sessions_send"); - if (!tool) throw new Error("missing sessions_send tool"); + if (!tool) { + throw new Error("missing sessions_send tool"); + } // Send using label instead of sessionKey const result = await tool.execute("call-by-label", { @@ -172,7 +176,9 @@ describe("sessions_send label lookup", () => { it("returns error when label not found", { timeout: 60_000 }, async () => { const tool = createOpenClawTools().find((candidate) => candidate.name === "sessions_send"); - if (!tool) throw new Error("missing sessions_send tool"); + if (!tool) { + throw new Error("missing sessions_send tool"); + } const result = await tool.execute("call-missing-label", { label: "nonexistent-label", @@ -186,7 +192,9 @@ describe("sessions_send label lookup", () => { it("returns error when neither sessionKey nor label provided", { timeout: 60_000 }, async () => { const tool = createOpenClawTools().find((candidate) => candidate.name === "sessions_send"); - if (!tool) throw new Error("missing sessions_send tool"); + if (!tool) { + throw new Error("missing sessions_send tool"); + } const result = await tool.execute("call-no-key", { message: "hello", diff --git a/src/gateway/server.sessions.gateway-server-sessions-a.e2e.test.ts b/src/gateway/server.sessions.gateway-server-sessions-a.e2e.test.ts index 5a717a0ec5..c21472f345 100644 --- a/src/gateway/server.sessions.gateway-server-sessions-a.e2e.test.ts +++ b/src/gateway/server.sessions.gateway-server-sessions-a.e2e.test.ts @@ -56,8 +56,11 @@ beforeAll(async () => { afterAll(async () => { await server.close(); - if (previousToken === undefined) delete process.env.OPENCLAW_GATEWAY_TOKEN; - else process.env.OPENCLAW_GATEWAY_TOKEN = previousToken; + if (previousToken === undefined) { + delete process.env.OPENCLAW_GATEWAY_TOKEN; + } else { + process.env.OPENCLAW_GATEWAY_TOKEN = previousToken; + } }); const openClient = async (opts?: Parameters[1]) => { diff --git a/src/gateway/server/close-reason.ts b/src/gateway/server/close-reason.ts index c20ccae3dd..e530c07aae 100644 --- a/src/gateway/server/close-reason.ts +++ b/src/gateway/server/close-reason.ts @@ -3,8 +3,12 @@ import { Buffer } from "node:buffer"; const CLOSE_REASON_MAX_BYTES = 120; export function truncateCloseReason(reason: string, maxBytes = CLOSE_REASON_MAX_BYTES): string { - if (!reason) return "invalid handshake"; + if (!reason) { + return "invalid handshake"; + } const buf = Buffer.from(reason); - if (buf.length <= maxBytes) return reason; + if (buf.length <= maxBytes) { + return reason; + } return buf.subarray(0, maxBytes).toString(); } diff --git a/src/gateway/server/plugins-http.ts b/src/gateway/server/plugins-http.ts index f8a7f85fda..7d72adfc23 100644 --- a/src/gateway/server/plugins-http.ts +++ b/src/gateway/server/plugins-http.ts @@ -18,7 +18,9 @@ export function createGatewayPluginRequestHandler(params: { return async (req, res) => { const routes = registry.httpRoutes ?? []; const handlers = registry.httpHandlers ?? []; - if (routes.length === 0 && handlers.length === 0) return false; + if (routes.length === 0 && handlers.length === 0) { + return false; + } if (routes.length > 0) { const url = new URL(req.url ?? "/", "http://localhost"); @@ -42,7 +44,9 @@ export function createGatewayPluginRequestHandler(params: { for (const entry of handlers) { try { const handled = await entry.handler(req, res); - if (handled) return true; + if (handled) { + return true; + } } catch (err) { log.warn(`plugin http handler failed (${entry.pluginId}): ${String(err)}`); if (!res.headersSent) { diff --git a/src/gateway/server/ws-connection.ts b/src/gateway/server/ws-connection.ts index c413b3cec7..5bcf698fae 100644 --- a/src/gateway/server/ws-connection.ts +++ b/src/gateway/server/ws-connection.ts @@ -95,7 +95,9 @@ export function attachGatewayWsConnectionHandler(params: { let lastFrameId: string | undefined; const setCloseCause = (cause: string, meta?: Record) => { - if (!closeCause) closeCause = cause; + if (!closeCause) { + closeCause = cause; + } if (meta && Object.keys(meta).length > 0) { closeMeta = { ...closeMeta, ...meta }; } @@ -125,10 +127,14 @@ export function attachGatewayWsConnectionHandler(params: { }); const close = (code = 1000, reason?: string) => { - if (closed) return; + if (closed) { + return; + } closed = true; clearTimeout(handshakeTimer); - if (client) clients.delete(client); + if (client) { + clients.delete(client); + } try { socket.close(code, reason); } catch { diff --git a/src/gateway/server/ws-connection/message-handler.ts b/src/gateway/server/ws-connection/message-handler.ts index c6aa083b1d..070e996ef4 100644 --- a/src/gateway/server/ws-connection/message-handler.ts +++ b/src/gateway/server/ws-connection/message-handler.ts @@ -61,10 +61,14 @@ const DEVICE_SIGNATURE_SKEW_MS = 10 * 60 * 1000; function resolveHostName(hostHeader?: string): string { const host = (hostHeader ?? "").trim().toLowerCase(); - if (!host) return ""; + if (!host) { + return ""; + } if (host.startsWith("[")) { const end = host.indexOf("]"); - if (end !== -1) return host.slice(1, end); + if (end !== -1) { + return host.slice(1, end); + } } const [name] = host.split(":"); return name ?? ""; @@ -229,7 +233,9 @@ export function attachGatewayWsMessageHandler(params: { const isWebchatConnect = (p: ConnectParams | null | undefined) => isWebchatClient(p?.client); socket.on("message", async (data) => { - if (isClosed()) return; + if (isClosed()) { + return; + } const text = rawDataToString(data); try { const parsed = JSON.parse(text); @@ -681,30 +687,40 @@ export function attachGatewayWsMessageHandler(params: { const isPaired = paired?.publicKey === devicePublicKey; if (!isPaired) { const ok = await requirePairing("not-paired"); - if (!ok) return; + if (!ok) { + return; + } } else { const allowedRoles = new Set( Array.isArray(paired.roles) ? paired.roles : paired.role ? [paired.role] : [], ); if (allowedRoles.size === 0) { const ok = await requirePairing("role-upgrade", paired); - if (!ok) return; + if (!ok) { + return; + } } else if (!allowedRoles.has(role)) { const ok = await requirePairing("role-upgrade", paired); - if (!ok) return; + if (!ok) { + return; + } } const pairedScopes = Array.isArray(paired.scopes) ? paired.scopes : []; if (scopes.length > 0) { if (pairedScopes.length === 0) { const ok = await requirePairing("scope-upgrade", paired); - if (!ok) return; + if (!ok) { + return; + } } else { const allowedScopes = new Set(pairedScopes); const missingScope = scopes.find((scope) => !allowedScopes.has(scope)); if (missingScope) { const ok = await requirePairing("scope-upgrade", paired); - if (!ok) return; + if (!ok) { + return; + } } } } @@ -828,7 +844,9 @@ export function attachGatewayWsMessageHandler(params: { const instanceIdRaw = connectParams.client.instanceId; const instanceId = typeof instanceIdRaw === "string" ? instanceIdRaw.trim() : ""; const nodeIdsForPairing = new Set([nodeSession.nodeId]); - if (instanceId) nodeIdsForPairing.add(instanceId); + if (instanceId) { + nodeIdsForPairing.add(instanceId); + } for (const nodeId of nodeIdsForPairing) { void updatePairedNodeMetadata(nodeId, { lastConnectedAtMs: nodeSession.connectedAtMs, diff --git a/src/gateway/session-utils.fs.ts b/src/gateway/session-utils.fs.ts index 213583ad74..fbb8a33abc 100644 --- a/src/gateway/session-utils.fs.ts +++ b/src/gateway/session-utils.fs.ts @@ -14,12 +14,16 @@ export function readSessionMessages( const candidates = resolveSessionTranscriptCandidates(sessionId, storePath, sessionFile); const filePath = candidates.find((p) => fs.existsSync(p)); - if (!filePath) return []; + if (!filePath) { + return []; + } const lines = fs.readFileSync(filePath, "utf-8").split(/\r?\n/); const messages: unknown[] = []; for (const line of lines) { - if (!line.trim()) continue; + if (!line.trim()) { + continue; + } try { const parsed = JSON.parse(line); if (parsed?.message) { @@ -39,7 +43,9 @@ export function resolveSessionTranscriptCandidates( agentId?: string, ): string[] { const candidates: string[] = []; - if (sessionFile) candidates.push(sessionFile); + if (sessionFile) { + candidates.push(sessionFile); + } if (storePath) { const dir = path.dirname(storePath); candidates.push(path.join(dir, `${sessionId}.jsonl`)); @@ -71,7 +77,9 @@ export function capArrayByJsonBytes( items: T[], maxBytes: number, ): { items: T[]; bytes: number } { - if (items.length === 0) return { items, bytes: 2 }; + if (items.length === 0) { + return { items, bytes: 2 }; + } const parts = items.map((item) => jsonUtf8Bytes(item)); let bytes = 2 + parts.reduce((a, b) => a + b, 0) + (items.length - 1); let start = 0; @@ -91,13 +99,21 @@ type TranscriptMessage = { }; function extractTextFromContent(content: TranscriptMessage["content"]): string | null { - if (typeof content === "string") return content.trim() || null; - if (!Array.isArray(content)) return null; + if (typeof content === "string") { + return content.trim() || null; + } + if (!Array.isArray(content)) { + return null; + } for (const part of content) { - if (!part || typeof part.text !== "string") continue; + if (!part || typeof part.text !== "string") { + continue; + } if (part.type === "text" || part.type === "output_text" || part.type === "input_text") { const trimmed = part.text.trim(); - if (trimmed) return trimmed; + if (trimmed) { + return trimmed; + } } } return null; @@ -111,25 +127,33 @@ export function readFirstUserMessageFromTranscript( ): string | null { const candidates = resolveSessionTranscriptCandidates(sessionId, storePath, sessionFile, agentId); const filePath = candidates.find((p) => fs.existsSync(p)); - if (!filePath) return null; + if (!filePath) { + return null; + } let fd: number | null = null; try { fd = fs.openSync(filePath, "r"); const buf = Buffer.alloc(8192); const bytesRead = fs.readSync(fd, buf, 0, buf.length, 0); - if (bytesRead === 0) return null; + if (bytesRead === 0) { + return null; + } const chunk = buf.toString("utf-8", 0, bytesRead); const lines = chunk.split(/\r?\n/).slice(0, MAX_LINES_TO_SCAN); for (const line of lines) { - if (!line.trim()) continue; + if (!line.trim()) { + continue; + } try { const parsed = JSON.parse(line); const msg = parsed?.message as TranscriptMessage | undefined; if (msg?.role === "user") { const text = extractTextFromContent(msg.content); - if (text) return text; + if (text) { + return text; + } } } catch { // skip malformed lines @@ -138,7 +162,9 @@ export function readFirstUserMessageFromTranscript( } catch { // file read error } finally { - if (fd !== null) fs.closeSync(fd); + if (fd !== null) { + fs.closeSync(fd); + } } return null; } @@ -154,14 +180,18 @@ export function readLastMessagePreviewFromTranscript( ): string | null { const candidates = resolveSessionTranscriptCandidates(sessionId, storePath, sessionFile, agentId); const filePath = candidates.find((p) => fs.existsSync(p)); - if (!filePath) return null; + if (!filePath) { + return null; + } let fd: number | null = null; try { fd = fs.openSync(filePath, "r"); const stat = fs.fstatSync(fd); const size = stat.size; - if (size === 0) return null; + if (size === 0) { + return null; + } const readStart = Math.max(0, size - LAST_MSG_MAX_BYTES); const readLen = Math.min(size, LAST_MSG_MAX_BYTES); @@ -179,7 +209,9 @@ export function readLastMessagePreviewFromTranscript( const msg = parsed?.message as TranscriptMessage | undefined; if (msg?.role === "user" || msg?.role === "assistant") { const text = extractTextFromContent(msg.content); - if (text) return text; + if (text) { + return text; + } } } catch { // skip malformed @@ -188,7 +220,9 @@ export function readLastMessagePreviewFromTranscript( } catch { // file error } finally { - if (fd !== null) fs.closeSync(fd); + if (fd !== null) { + fs.closeSync(fd); + } } return null; } @@ -211,7 +245,9 @@ type TranscriptPreviewMessage = { }; function normalizeRole(role: string | undefined, isTool: boolean): SessionPreviewItem["role"] { - if (isTool) return "tool"; + if (isTool) { + return "tool"; + } switch ((role ?? "").toLowerCase()) { case "user": return "user"; @@ -227,8 +263,12 @@ function normalizeRole(role: string | undefined, isTool: boolean): SessionPrevie } function truncatePreviewText(text: string, maxChars: number): string { - if (maxChars <= 0 || text.length <= maxChars) return text; - if (maxChars <= 3) return text.slice(0, maxChars); + if (maxChars <= 0 || text.length <= maxChars) { + return text; + } + if (maxChars <= 3) { + return text.slice(0, maxChars); + } return `${text.slice(0, maxChars - 3)}...`; } @@ -253,10 +293,16 @@ function extractPreviewText(message: TranscriptPreviewMessage): string | null { } function isToolCall(message: TranscriptPreviewMessage): boolean { - if (message.toolName || message.tool_name) return true; - if (!Array.isArray(message.content)) return false; + if (message.toolName || message.tool_name) { + return true; + } + if (!Array.isArray(message.content)) { + return false; + } return message.content.some((entry) => { - if (entry?.name) return true; + if (entry?.name) { + return true; + } const raw = typeof entry?.type === "string" ? entry.type.toLowerCase() : ""; return raw === "toolcall" || raw === "tool_call"; }); @@ -279,10 +325,14 @@ function extractToolNames(message: TranscriptPreviewMessage): string[] { } function extractMediaSummary(message: TranscriptPreviewMessage): string | null { - if (!Array.isArray(message.content)) return null; + if (!Array.isArray(message.content)) { + return null; + } for (const entry of message.content) { const raw = typeof entry?.type === "string" ? entry.type.trim().toLowerCase() : ""; - if (!raw || raw === "text" || raw === "toolcall" || raw === "tool_call") continue; + if (!raw || raw === "text" || raw === "toolcall" || raw === "tool_call") { + continue; + } return `[${raw}]`; } return null; @@ -304,15 +354,21 @@ function buildPreviewItems( const shown = toolNames.slice(0, 2); const overflow = toolNames.length - shown.length; text = `call ${shown.join(", ")}`; - if (overflow > 0) text += ` +${overflow}`; + if (overflow > 0) { + text += ` +${overflow}`; + } } } if (!text) { text = extractMediaSummary(message); } - if (!text) continue; + if (!text) { + continue; + } let trimmed = text.trim(); - if (!trimmed) continue; + if (!trimmed) { + continue; + } if (role === "user") { trimmed = stripEnvelope(trimmed); } @@ -320,7 +376,9 @@ function buildPreviewItems( items.push({ role, text: trimmed }); } - if (items.length <= maxItems) return items; + if (items.length <= maxItems) { + return items; + } return items.slice(-maxItems); } @@ -334,7 +392,9 @@ function readRecentMessagesFromTranscript( fd = fs.openSync(filePath, "r"); const stat = fs.fstatSync(fd); const size = stat.size; - if (size === 0) return []; + if (size === 0) { + return []; + } const readStart = Math.max(0, size - readBytes); const readLen = Math.min(size, readBytes); @@ -353,7 +413,9 @@ function readRecentMessagesFromTranscript( const msg = parsed?.message as TranscriptPreviewMessage | undefined; if (msg && typeof msg === "object") { collected.push(msg); - if (collected.length >= maxMessages) break; + if (collected.length >= maxMessages) { + break; + } } } catch { // skip malformed lines @@ -363,7 +425,9 @@ function readRecentMessagesFromTranscript( } catch { return []; } finally { - if (fd !== null) fs.closeSync(fd); + if (fd !== null) { + fs.closeSync(fd); + } } } @@ -377,7 +441,9 @@ export function readSessionPreviewItemsFromTranscript( ): SessionPreviewItem[] { const candidates = resolveSessionTranscriptCandidates(sessionId, storePath, sessionFile, agentId); const filePath = candidates.find((p) => fs.existsSync(p)); - if (!filePath) return []; + if (!filePath) { + return []; + } const boundedItems = Math.max(1, Math.min(maxItems, 50)); const boundedChars = Math.max(20, Math.min(maxChars, 2000)); diff --git a/src/gateway/session-utils.ts b/src/gateway/session-utils.ts index a5d406b3e7..670742fda5 100644 --- a/src/gateway/session-utils.ts +++ b/src/gateway/session-utils.ts @@ -78,9 +78,15 @@ function resolveAvatarMime(filePath: string): string { } function isWorkspaceRelativePath(value: string): boolean { - if (!value) return false; - if (value.startsWith("~")) return false; - if (AVATAR_SCHEME_RE.test(value) && !WINDOWS_ABS_RE.test(value)) return false; + if (!value) { + return false; + } + if (value.startsWith("~")) { + return false; + } + if (AVATAR_SCHEME_RE.test(value) && !WINDOWS_ABS_RE.test(value)) { + return false; + } return true; } @@ -89,19 +95,31 @@ function resolveIdentityAvatarUrl( agentId: string, avatar: string | undefined, ): string | undefined { - if (!avatar) return undefined; + if (!avatar) { + return undefined; + } const trimmed = avatar.trim(); - if (!trimmed) return undefined; - if (AVATAR_DATA_RE.test(trimmed) || AVATAR_HTTP_RE.test(trimmed)) return trimmed; - if (!isWorkspaceRelativePath(trimmed)) return undefined; + if (!trimmed) { + return undefined; + } + if (AVATAR_DATA_RE.test(trimmed) || AVATAR_HTTP_RE.test(trimmed)) { + return trimmed; + } + if (!isWorkspaceRelativePath(trimmed)) { + return undefined; + } const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId); const workspaceRoot = path.resolve(workspaceDir); const resolved = path.resolve(workspaceRoot, trimmed); const relative = path.relative(workspaceRoot, resolved); - if (relative.startsWith("..") || path.isAbsolute(relative)) return undefined; + if (relative.startsWith("..") || path.isAbsolute(relative)) { + return undefined; + } try { const stat = fs.statSync(resolved); - if (!stat.isFile() || stat.size > AVATAR_MAX_BYTES) return undefined; + if (!stat.isFile() || stat.size > AVATAR_MAX_BYTES) { + return undefined; + } const buffer = fs.readFileSync(resolved); const mime = resolveAvatarMime(resolved); return `data:${mime};base64,${buffer.toString("base64")}`; @@ -121,10 +139,14 @@ function formatSessionIdPrefix(sessionId: string, updatedAt?: number | null): st } function truncateTitle(text: string, maxLen: number): string { - if (text.length <= maxLen) return text; + if (text.length <= maxLen) { + return text; + } const cut = text.slice(0, maxLen - 1); const lastSpace = cut.lastIndexOf(" "); - if (lastSpace > maxLen * 0.6) return cut.slice(0, lastSpace) + "…"; + if (lastSpace > maxLen * 0.6) { + return cut.slice(0, lastSpace) + "…"; + } return cut + "…"; } @@ -132,7 +154,9 @@ export function deriveSessionTitle( entry: SessionEntry | undefined, firstUserMessage?: string | null, ): string | undefined { - if (!entry) return undefined; + if (!entry) { + return undefined; + } if (entry.displayName?.trim()) { return entry.displayName.trim(); @@ -166,8 +190,12 @@ export function loadSessionEntry(sessionKey: string) { } export function classifySessionKey(key: string, entry?: SessionEntry): GatewaySessionRow["kind"] { - if (key === "global") return "global"; - if (key === "unknown") return "unknown"; + if (key === "global") { + return "global"; + } + if (key === "unknown") { + return "unknown"; + } if (entry?.chatType === "group" || entry?.chatType === "channel") { return "group"; } @@ -216,7 +244,9 @@ function listConfiguredAgentIds(cfg: OpenClawConfig): string[] { if (agents.length > 0) { const ids = new Set(); for (const entry of agents) { - if (entry?.id) ids.add(normalizeAgentId(entry.id)); + if (entry?.id) { + ids.add(normalizeAgentId(entry.id)); + } } const defaultId = normalizeAgentId(resolveDefaultAgentId(cfg)); ids.add(defaultId); @@ -230,7 +260,9 @@ function listConfiguredAgentIds(cfg: OpenClawConfig): string[] { const ids = new Set(); const defaultId = normalizeAgentId(resolveDefaultAgentId(cfg)); ids.add(defaultId); - for (const id of listExistingAgentIdsFromDisk()) ids.add(id); + for (const id of listExistingAgentIdsFromDisk()) { + ids.add(id); + } const sorted = Array.from(ids).filter(Boolean); sorted.sort((a, b) => a.localeCompare(b)); if (sorted.includes(defaultId)) { @@ -253,7 +285,9 @@ export function listAgentsForGateway(cfg: OpenClawConfig): { { name?: string; identity?: GatewayAgentRow["identity"] } >(); for (const entry of cfg.agents?.list ?? []) { - if (!entry?.id) continue; + if (!entry?.id) { + continue; + } const identity = entry.identity ? { name: entry.identity.name?.trim() || undefined, @@ -296,8 +330,12 @@ export function listAgentsForGateway(cfg: OpenClawConfig): { } function canonicalizeSessionKeyForAgent(agentId: string, key: string): string { - if (key === "global" || key === "unknown") return key; - if (key.startsWith("agent:")) return key; + if (key === "global" || key === "unknown") { + return key; + } + if (key.startsWith("agent:")) { + return key; + } return `agent:${normalizeAgentId(agentId)}:${key}`; } @@ -310,8 +348,12 @@ export function resolveSessionStoreKey(params: { sessionKey: string; }): string { const raw = params.sessionKey.trim(); - if (!raw) return raw; - if (raw === "global" || raw === "unknown") return raw; + if (!raw) { + return raw; + } + if (raw === "global" || raw === "unknown") { + return raw; + } const parsed = parseAgentSessionKey(raw); if (parsed) { @@ -321,7 +363,9 @@ export function resolveSessionStoreKey(params: { agentId, sessionKey: raw, }); - if (canonical !== raw) return canonical; + if (canonical !== raw) { + return canonical; + } return raw; } @@ -338,15 +382,23 @@ function resolveSessionStoreAgentId(cfg: OpenClawConfig, canonicalKey: string): return resolveDefaultStoreAgentId(cfg); } const parsed = parseAgentSessionKey(canonicalKey); - if (parsed?.agentId) return normalizeAgentId(parsed.agentId); + if (parsed?.agentId) { + return normalizeAgentId(parsed.agentId); + } return resolveDefaultStoreAgentId(cfg); } function canonicalizeSpawnedByForAgent(agentId: string, spawnedBy?: string): string | undefined { const raw = spawnedBy?.trim(); - if (!raw) return undefined; - if (raw === "global" || raw === "unknown") return raw; - if (raw.startsWith("agent:")) return raw; + if (!raw) { + return undefined; + } + if (raw === "global" || raw === "unknown") { + return raw; + } + if (raw.startsWith("agent:")) { + return raw; + } return `agent:${normalizeAgentId(agentId)}:${raw}`; } @@ -372,7 +424,9 @@ export function resolveGatewaySessionStoreTarget(params: { cfg: OpenClawConfig; const storeKeys = new Set(); storeKeys.add(canonicalKey); - if (key && key !== canonicalKey) storeKeys.add(key); + if (key && key !== canonicalKey) { + storeKeys.add(key); + } return { agentId, storePath, @@ -509,23 +563,37 @@ export function listSessionsFromStore(params: { let sessions = Object.entries(store) .filter(([key]) => { - if (!includeGlobal && key === "global") return false; - if (!includeUnknown && key === "unknown") return false; + if (!includeGlobal && key === "global") { + return false; + } + if (!includeUnknown && key === "unknown") { + return false; + } if (agentId) { - if (key === "global" || key === "unknown") return false; + if (key === "global" || key === "unknown") { + return false; + } const parsed = parseAgentSessionKey(key); - if (!parsed) return false; + if (!parsed) { + return false; + } return normalizeAgentId(parsed.agentId) === agentId; } return true; }) .filter(([key, entry]) => { - if (!spawnedBy) return true; - if (key === "unknown" || key === "global") return false; + if (!spawnedBy) { + return true; + } + if (key === "unknown" || key === "global") { + return false; + } return entry?.spawnedBy === spawnedBy; }) .filter(([, entry]) => { - if (!label) return true; + if (!label) { + return true; + } return entry?.label === label; }) .map(([key, entry]) => { @@ -628,7 +696,9 @@ export function listSessionsFromStore(params: { storePath, entry.sessionFile, ); - if (lastMsg) lastMessagePreview = lastMsg; + if (lastMsg) { + lastMessagePreview = lastMsg; + } } } return { ...rest, derivedTitle, lastMessagePreview } satisfies GatewaySessionRow; diff --git a/src/gateway/sessions-patch.test.ts b/src/gateway/sessions-patch.test.ts index ec4a1b7066..eb109601ab 100644 --- a/src/gateway/sessions-patch.test.ts +++ b/src/gateway/sessions-patch.test.ts @@ -13,7 +13,9 @@ describe("gateway sessions patch", () => { patch: { elevatedLevel: "off" }, }); expect(res.ok).toBe(true); - if (!res.ok) return; + if (!res.ok) { + return; + } expect(res.entry.elevatedLevel).toBe("off"); }); @@ -26,7 +28,9 @@ describe("gateway sessions patch", () => { patch: { elevatedLevel: "on" }, }); expect(res.ok).toBe(true); - if (!res.ok) return; + if (!res.ok) { + return; + } expect(res.entry.elevatedLevel).toBe("on"); }); @@ -41,7 +45,9 @@ describe("gateway sessions patch", () => { patch: { elevatedLevel: null }, }); expect(res.ok).toBe(true); - if (!res.ok) return; + if (!res.ok) { + return; + } expect(res.entry.elevatedLevel).toBeUndefined(); }); @@ -54,7 +60,9 @@ describe("gateway sessions patch", () => { patch: { elevatedLevel: "maybe" }, }); expect(res.ok).toBe(false); - if (res.ok) return; + if (res.ok) { + return; + } expect(res.error.message).toContain("invalid elevatedLevel"); }); @@ -78,7 +86,9 @@ describe("gateway sessions patch", () => { loadGatewayModelCatalog: async () => [{ provider: "openai", id: "gpt-5.2" }], }); expect(res.ok).toBe(true); - if (!res.ok) return; + if (!res.ok) { + return; + } expect(res.entry.providerOverride).toBe("openai"); expect(res.entry.modelOverride).toBe("gpt-5.2"); expect(res.entry.authProfileOverride).toBeUndefined(); diff --git a/src/gateway/sessions-patch.ts b/src/gateway/sessions-patch.ts index 3789cbae63..f2d693b342 100644 --- a/src/gateway/sessions-patch.ts +++ b/src/gateway/sessions-patch.ts @@ -76,10 +76,14 @@ export async function applySessionsPatchToStore(params: { if ("spawnedBy" in patch) { const raw = patch.spawnedBy; if (raw === null) { - if (existing?.spawnedBy) return invalid("spawnedBy cannot be cleared once set"); + if (existing?.spawnedBy) { + return invalid("spawnedBy cannot be cleared once set"); + } } else if (raw !== undefined) { const trimmed = String(raw).trim(); - if (!trimmed) return invalid("invalid spawnedBy: empty"); + if (!trimmed) { + return invalid("invalid spawnedBy: empty"); + } if (!isSubagentSessionKey(storeKey)) { return invalid("spawnedBy is only supported for subagent:* sessions"); } @@ -96,9 +100,13 @@ export async function applySessionsPatchToStore(params: { delete next.label; } else if (raw !== undefined) { const parsed = parseSessionLabel(raw); - if (!parsed.ok) return invalid(parsed.error); + if (!parsed.ok) { + return invalid(parsed.error); + } for (const [key, entry] of Object.entries(store)) { - if (key === storeKey) continue; + if (key === storeKey) { + continue; + } if (entry?.label === parsed.label) { return invalid(`label already in use: ${parsed.label}`); } @@ -125,15 +133,20 @@ export async function applySessionsPatchToStore(params: { `invalid thinkingLevel (use ${formatThinkingLevels(hintProvider, hintModel, "|")})`, ); } - if (normalized === "off") delete next.thinkingLevel; - else next.thinkingLevel = normalized; + if (normalized === "off") { + delete next.thinkingLevel; + } else { + next.thinkingLevel = normalized; + } } } if ("verboseLevel" in patch) { const raw = patch.verboseLevel; const parsed = parseVerboseOverride(raw); - if (!parsed.ok) return invalid(parsed.error); + if (!parsed.ok) { + return invalid(parsed.error); + } applyVerboseOverride(next, parsed.value); } @@ -146,8 +159,11 @@ export async function applySessionsPatchToStore(params: { if (!normalized) { return invalid('invalid reasoningLevel (use "on"|"off"|"stream")'); } - if (normalized === "off") delete next.reasoningLevel; - else next.reasoningLevel = normalized; + if (normalized === "off") { + delete next.reasoningLevel; + } else { + next.reasoningLevel = normalized; + } } } @@ -157,9 +173,14 @@ export async function applySessionsPatchToStore(params: { delete next.responseUsage; } else if (raw !== undefined) { const normalized = normalizeUsageDisplay(String(raw)); - if (!normalized) return invalid('invalid responseUsage (use "off"|"tokens"|"full")'); - if (normalized === "off") delete next.responseUsage; - else next.responseUsage = normalized; + if (!normalized) { + return invalid('invalid responseUsage (use "off"|"tokens"|"full")'); + } + if (normalized === "off") { + delete next.responseUsage; + } else { + next.responseUsage = normalized; + } } } @@ -169,7 +190,9 @@ export async function applySessionsPatchToStore(params: { delete next.elevatedLevel; } else if (raw !== undefined) { const normalized = normalizeElevatedLevel(String(raw)); - if (!normalized) return invalid('invalid elevatedLevel (use "on"|"off"|"ask"|"full")'); + if (!normalized) { + return invalid('invalid elevatedLevel (use "on"|"off"|"ask"|"full")'); + } // Persist "off" explicitly so patches can override defaults. next.elevatedLevel = normalized; } @@ -181,7 +204,9 @@ export async function applySessionsPatchToStore(params: { delete next.execHost; } else if (raw !== undefined) { const normalized = normalizeExecHost(String(raw)); - if (!normalized) return invalid('invalid execHost (use "sandbox"|"gateway"|"node")'); + if (!normalized) { + return invalid('invalid execHost (use "sandbox"|"gateway"|"node")'); + } next.execHost = normalized; } } @@ -192,7 +217,9 @@ export async function applySessionsPatchToStore(params: { delete next.execSecurity; } else if (raw !== undefined) { const normalized = normalizeExecSecurity(String(raw)); - if (!normalized) return invalid('invalid execSecurity (use "deny"|"allowlist"|"full")'); + if (!normalized) { + return invalid('invalid execSecurity (use "deny"|"allowlist"|"full")'); + } next.execSecurity = normalized; } } @@ -203,7 +230,9 @@ export async function applySessionsPatchToStore(params: { delete next.execAsk; } else if (raw !== undefined) { const normalized = normalizeExecAsk(String(raw)); - if (!normalized) return invalid('invalid execAsk (use "off"|"on-miss"|"always")'); + if (!normalized) { + return invalid('invalid execAsk (use "off"|"on-miss"|"always")'); + } next.execAsk = normalized; } } @@ -214,7 +243,9 @@ export async function applySessionsPatchToStore(params: { delete next.execNode; } else if (raw !== undefined) { const trimmed = String(raw).trim(); - if (!trimmed) return invalid("invalid execNode: empty"); + if (!trimmed) { + return invalid("invalid execNode: empty"); + } next.execNode = trimmed; } } @@ -237,7 +268,9 @@ export async function applySessionsPatchToStore(params: { }); } else if (raw !== undefined) { const trimmed = String(raw).trim(); - if (!trimmed) return invalid("invalid model: empty"); + if (!trimmed) { + return invalid("invalid model: empty"); + } if (!params.loadGatewayModelCatalog) { return { ok: false, @@ -291,7 +324,9 @@ export async function applySessionsPatchToStore(params: { delete next.sendPolicy; } else if (raw !== undefined) { const normalized = normalizeSendPolicy(String(raw)); - if (!normalized) return invalid('invalid sendPolicy (use "allow"|"deny")'); + if (!normalized) { + return invalid('invalid sendPolicy (use "allow"|"deny")'); + } next.sendPolicy = normalized; } } diff --git a/src/gateway/test-helpers.e2e.ts b/src/gateway/test-helpers.e2e.ts index ab124a83b1..62db8923f0 100644 --- a/src/gateway/test-helpers.e2e.ts +++ b/src/gateway/test-helpers.e2e.ts @@ -33,11 +33,16 @@ export async function connectGatewayClient(params: { return await new Promise>((resolve, reject) => { let settled = false; const stop = (err?: Error, client?: InstanceType) => { - if (settled) return; + if (settled) { + return; + } settled = true; clearTimeout(timer); - if (err) reject(err); - else resolve(client as InstanceType); + if (err) { + reject(err); + } else { + resolve(client as InstanceType); + } }; const client = new GatewayClient({ url: params.url, @@ -112,7 +117,9 @@ export async function connectDeviceAuthReq(params: { url: string; token?: string }; const handler = (data: WebSocket.RawData) => { const obj = JSON.parse(rawDataToString(data)) as { type?: unknown; id?: unknown }; - if (obj?.type !== "res" || obj?.id !== "c1") return; + if (obj?.type !== "res" || obj?.id !== "c1") { + return; + } clearTimeout(timer); ws.off("message", handler); ws.off("close", closeHandler); diff --git a/src/gateway/test-helpers.mocks.ts b/src/gateway/test-helpers.mocks.ts index d945d4dfc6..4a339efcdd 100644 --- a/src/gateway/test-helpers.mocks.ts +++ b/src/gateway/test-helpers.mocks.ts @@ -441,9 +441,12 @@ vi.mock("../config/config.js", async () => { ...fileSession, mainKey: fileSession.mainKey ?? "main", }; - if (typeof testState.sessionStorePath === "string") + if (typeof testState.sessionStorePath === "string") { session.store = testState.sessionStorePath; - if (testState.sessionConfig) Object.assign(session, testState.sessionConfig); + } + if (testState.sessionConfig) { + Object.assign(session, testState.sessionConfig); + } const fileGateway = fileConfig.gateway && @@ -451,9 +454,15 @@ vi.mock("../config/config.js", async () => { !Array.isArray(fileConfig.gateway) ? ({ ...(fileConfig.gateway as Record) } as Record) : {}; - if (testState.gatewayBind) fileGateway.bind = testState.gatewayBind; - if (testState.gatewayAuth) fileGateway.auth = testState.gatewayAuth; - if (testState.gatewayControlUi) fileGateway.controlUi = testState.gatewayControlUi; + if (testState.gatewayBind) { + fileGateway.bind = testState.gatewayBind; + } + if (testState.gatewayAuth) { + fileGateway.auth = testState.gatewayAuth; + } + if (testState.gatewayControlUi) { + fileGateway.controlUi = testState.gatewayControlUi; + } const gateway = Object.keys(fileGateway).length > 0 ? fileGateway : undefined; const fileCanvasHost = @@ -462,8 +471,9 @@ vi.mock("../config/config.js", async () => { !Array.isArray(fileConfig.canvasHost) ? ({ ...(fileConfig.canvasHost as Record) } as Record) : {}; - if (typeof testState.canvasHostPort === "number") + if (typeof testState.canvasHostPort === "number") { fileCanvasHost.port = testState.canvasHostPort; + } const canvasHost = Object.keys(fileCanvasHost).length > 0 ? fileCanvasHost : undefined; const hooks = testState.hooksConfig ?? (fileConfig.hooks as HooksConfig | undefined); @@ -472,8 +482,12 @@ vi.mock("../config/config.js", async () => { fileConfig.cron && typeof fileConfig.cron === "object" && !Array.isArray(fileConfig.cron) ? ({ ...(fileConfig.cron as Record) } as Record) : {}; - if (typeof testState.cronEnabled === "boolean") fileCron.enabled = testState.cronEnabled; - if (typeof testState.cronStorePath === "string") fileCron.store = testState.cronStorePath; + if (typeof testState.cronEnabled === "boolean") { + fileCron.enabled = testState.cronEnabled; + } + if (typeof testState.cronStorePath === "string") { + fileCron.store = testState.cronStorePath; + } const cron = Object.keys(fileCron).length > 0 ? fileCron : undefined; const config = { diff --git a/src/gateway/test-helpers.openai-mock.ts b/src/gateway/test-helpers.openai-mock.ts index ea5977c042..77e7abb1f1 100644 --- a/src/gateway/test-helpers.openai-mock.ts +++ b/src/gateway/test-helpers.openai-mock.ts @@ -22,7 +22,9 @@ type OpenAIResponseStreamEvent = function extractLastUserText(input: unknown[]): string { for (let i = input.length - 1; i >= 0; i -= 1) { const item = input[i] as Record | undefined; - if (!item || item.role !== "user") continue; + if (!item || item.role !== "user") { + continue; + } const content = item.content; if (Array.isArray(content)) { const text = content @@ -36,7 +38,9 @@ function extractLastUserText(input: unknown[]): string { .map((c) => c.text) .join("\n") .trim(); - if (text) return text; + if (text) { + return text; + } } } return ""; @@ -45,7 +49,9 @@ function extractLastUserText(input: unknown[]): string { function extractToolOutput(input: unknown[]): string { for (const itemRaw of input) { const item = itemRaw as Record | undefined; - if (!item || item.type !== "function_call_output") continue; + if (!item || item.type !== "function_call_output") { + continue; + } return typeof item.output === "string" ? item.output : ""; } return ""; @@ -128,10 +134,18 @@ async function* fakeOpenAIResponsesStream( } function decodeBodyText(body: unknown): string { - if (!body) return ""; - if (typeof body === "string") return body; - if (body instanceof Uint8Array) return Buffer.from(body).toString("utf8"); - if (body instanceof ArrayBuffer) return Buffer.from(new Uint8Array(body)).toString("utf8"); + if (!body) { + return ""; + } + if (typeof body === "string") { + return body; + } + if (body instanceof Uint8Array) { + return Buffer.from(body).toString("utf8"); + } + if (body instanceof ArrayBuffer) { + return Buffer.from(new Uint8Array(body)).toString("utf8"); + } return ""; } diff --git a/src/gateway/test-helpers.server.ts b/src/gateway/test-helpers.server.ts index 6b8f194718..a8a5ba2362 100644 --- a/src/gateway/test-helpers.server.ts +++ b/src/gateway/test-helpers.server.ts @@ -56,7 +56,9 @@ export async function writeSessionStore(params: { mainKey?: string; }): Promise { const storePath = params.storePath ?? testState.sessionStorePath; - if (!storePath) throw new Error("writeSessionStore requires testState.sessionStorePath"); + if (!storePath) { + throw new Error("writeSessionStore requires testState.sessionStorePath"); + } const agentId = params.agentId ?? DEFAULT_AGENT_ID; const store: Record> = {}; for (const [requestKey, entry] of Object.entries(params.entries)) { @@ -148,21 +150,41 @@ async function cleanupGatewayTestHome(options: { restoreEnv: boolean }) { vi.useRealTimers(); resetLogger(); if (options.restoreEnv) { - if (previousHome === undefined) delete process.env.HOME; - else process.env.HOME = previousHome; - if (previousUserProfile === undefined) delete process.env.USERPROFILE; - else process.env.USERPROFILE = previousUserProfile; - if (previousStateDir === undefined) delete process.env.OPENCLAW_STATE_DIR; - else process.env.OPENCLAW_STATE_DIR = previousStateDir; - if (previousConfigPath === undefined) delete process.env.OPENCLAW_CONFIG_PATH; - else process.env.OPENCLAW_CONFIG_PATH = previousConfigPath; - if (previousSkipBrowserControl === undefined) + if (previousHome === undefined) { + delete process.env.HOME; + } else { + process.env.HOME = previousHome; + } + if (previousUserProfile === undefined) { + delete process.env.USERPROFILE; + } else { + process.env.USERPROFILE = previousUserProfile; + } + if (previousStateDir === undefined) { + delete process.env.OPENCLAW_STATE_DIR; + } else { + process.env.OPENCLAW_STATE_DIR = previousStateDir; + } + if (previousConfigPath === undefined) { + delete process.env.OPENCLAW_CONFIG_PATH; + } else { + process.env.OPENCLAW_CONFIG_PATH = previousConfigPath; + } + if (previousSkipBrowserControl === undefined) { delete process.env.OPENCLAW_SKIP_BROWSER_CONTROL_SERVER; - else process.env.OPENCLAW_SKIP_BROWSER_CONTROL_SERVER = previousSkipBrowserControl; - if (previousSkipGmailWatcher === undefined) delete process.env.OPENCLAW_SKIP_GMAIL_WATCHER; - else process.env.OPENCLAW_SKIP_GMAIL_WATCHER = previousSkipGmailWatcher; - if (previousSkipCanvasHost === undefined) delete process.env.OPENCLAW_SKIP_CANVAS_HOST; - else process.env.OPENCLAW_SKIP_CANVAS_HOST = previousSkipCanvasHost; + } else { + process.env.OPENCLAW_SKIP_BROWSER_CONTROL_SERVER = previousSkipBrowserControl; + } + if (previousSkipGmailWatcher === undefined) { + delete process.env.OPENCLAW_SKIP_GMAIL_WATCHER; + } else { + process.env.OPENCLAW_SKIP_GMAIL_WATCHER = previousSkipGmailWatcher; + } + if (previousSkipCanvasHost === undefined) { + delete process.env.OPENCLAW_SKIP_CANVAS_HOST; + } else { + process.env.OPENCLAW_SKIP_CANVAS_HOST = previousSkipCanvasHost; + } } if (options.restoreEnv && tempHome) { await fs.rm(tempHome, { @@ -281,7 +303,9 @@ export async function startServerWithClient(token?: string, opts?: GatewayServer break; } catch (err) { const code = (err as { cause?: { code?: string } }).cause?.code; - if (code !== "EADDRINUSE") throw err; + if (code !== "EADDRINUSE") { + throw err; + } port = await getFreePort(); } } @@ -359,8 +383,12 @@ export async function connectReq( const password = opts?.password ?? defaultPassword; const requestedScopes = Array.isArray(opts?.scopes) ? opts?.scopes : []; const device = (() => { - if (opts?.device === null) return undefined; - if (opts?.device) return opts.device; + if (opts?.device === null) { + return undefined; + } + if (opts?.device) { + return opts.device; + } const identity = loadOrCreateDeviceIdentity(); const signedAtMs = Date.now(); const payload = buildDeviceAuthPayload({ @@ -406,7 +434,9 @@ export async function connectReq( }), ); const isResponseForId = (o: unknown): boolean => { - if (!o || typeof o !== "object" || Array.isArray(o)) return false; + if (!o || typeof o !== "object" || Array.isArray(o)) { + return false; + } const rec = o as Record; return rec.type === "res" && rec.id === id; }; @@ -438,7 +468,9 @@ export async function rpcReq( }>( ws, (o) => { - if (!o || typeof o !== "object" || Array.isArray(o)) return false; + if (!o || typeof o !== "object" || Array.isArray(o)) { + return false; + } const rec = o as Record; return rec.type === "res" && rec.id === id; }, @@ -451,7 +483,9 @@ export async function waitForSystemEvent(timeoutMs = 2000) { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { const events = peekSystemEvents(sessionKey); - if (events.length > 0) return events; + if (events.length > 0) { + return events; + } await new Promise((resolve) => setTimeout(resolve, 10)); } throw new Error("timeout waiting for system event"); diff --git a/src/gateway/tools-invoke-http.test.ts b/src/gateway/tools-invoke-http.test.ts index 84d536f4f7..e61cdff01b 100644 --- a/src/gateway/tools-invoke-http.test.ts +++ b/src/gateway/tools-invoke-http.test.ts @@ -19,7 +19,9 @@ beforeEach(() => { const resolveGatewayToken = (): string => { const token = (testState.gatewayAuth as { token?: string } | undefined)?.token; - if (!token) throw new Error("test gateway token missing"); + if (!token) { + throw new Error("test gateway token missing"); + } return token; }; diff --git a/src/gateway/tools-invoke-http.ts b/src/gateway/tools-invoke-http.ts index d7c8462ebb..0b97fe17af 100644 --- a/src/gateway/tools-invoke-http.ts +++ b/src/gateway/tools-invoke-http.ts @@ -45,12 +45,16 @@ type ToolsInvokeBody = { }; function resolveSessionKeyFromBody(body: ToolsInvokeBody): string | undefined { - if (typeof body.sessionKey === "string" && body.sessionKey.trim()) return body.sessionKey.trim(); + if (typeof body.sessionKey === "string" && body.sessionKey.trim()) { + return body.sessionKey.trim(); + } return undefined; } function resolveMemoryToolDisableReasons(cfg: ReturnType): string[] { - if (!process.env.VITEST) return []; + if (!process.env.VITEST) { + return []; + } const reasons: string[] = []; const plugins = cfg.plugins; const slotRaw = plugins?.slots?.memory; @@ -59,7 +63,9 @@ function resolveMemoryToolDisableReasons(cfg: ReturnType): st const pluginsDisabled = plugins?.enabled === false; const defaultDisabled = isTestDefaultMemorySlotDisabled(cfg); - if (pluginsDisabled) reasons.push("plugins.enabled=false"); + if (pluginsDisabled) { + reasons.push("plugins.enabled=false"); + } if (slotDisabled) { reasons.push(slotRaw === null ? "plugins.slots.memory=null" : 'plugins.slots.memory="none"'); } @@ -75,8 +81,12 @@ function mergeActionIntoArgsIfSupported(params: { args: Record; }): Record { const { toolSchema, action, args } = params; - if (!action) return args; - if (args.action !== undefined) return args; + if (!action) { + return args; + } + if (args.action !== undefined) { + return args; + } // TypeBox schemas are plain objects; many tools define an `action` property. const schemaObj = toolSchema as { properties?: Record } | null; const hasAction = Boolean( @@ -85,7 +95,9 @@ function mergeActionIntoArgsIfSupported(params: { schemaObj.properties && "action" in schemaObj.properties, ); - if (!hasAction) return args; + if (!hasAction) { + return args; + } return { ...args, action }; } @@ -95,7 +107,9 @@ export async function handleToolsInvokeHttpRequest( opts: { auth: ResolvedGatewayAuth; maxBodyBytes?: number; trustedProxies?: string[] }, ): Promise { const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`); - if (url.pathname !== "/tools/invoke") return false; + if (url.pathname !== "/tools/invoke") { + return false; + } if (req.method !== "POST") { sendMethodNotAllowed(res, "POST"); @@ -116,7 +130,9 @@ export async function handleToolsInvokeHttpRequest( } const bodyUnknown = await readJsonBodyOrError(req, res, opts.maxBodyBytes ?? DEFAULT_BODY_BYTES); - if (bodyUnknown === undefined) return true; + if (bodyUnknown === undefined) { + return true; + } const body = (bodyUnknown ?? {}) as ToolsInvokeBody; const toolName = typeof body.tool === "string" ? body.tool.trim() : ""; @@ -175,7 +191,9 @@ export async function handleToolsInvokeHttpRequest( const providerProfilePolicy = resolveToolProfilePolicy(providerProfile); const mergeAlsoAllow = (policy: typeof profilePolicy, alsoAllow?: string[]) => { - if (!policy?.allow || !Array.isArray(alsoAllow) || alsoAllow.length === 0) return policy; + if (!policy?.allow || !Array.isArray(alsoAllow) || alsoAllow.length === 0) { + return policy; + } return { ...policy, allow: Array.from(new Set([...policy.allow, ...alsoAllow])) }; }; diff --git a/src/gateway/ws-log.ts b/src/gateway/ws-log.ts index fccfc5b363..2716fe3a03 100644 --- a/src/gateway/ws-log.ts +++ b/src/gateway/ws-log.ts @@ -27,8 +27,12 @@ const wsLog = createSubsystemLogger("gateway/ws"); export function shortId(value: string): string { const s = value.trim(); - if (UUID_RE.test(s)) return `${s.slice(0, 8)}…${s.slice(-4)}`; - if (s.length <= 24) return s; + if (UUID_RE.test(s)) { + return `${s.slice(0, 8)}…${s.slice(-4)}`; + } + if (s.length <= 24) { + return s; + } return `${s.slice(0, 12)}…${s.slice(-4)}`; } @@ -36,13 +40,19 @@ export function formatForLog(value: unknown): string { try { if (value instanceof Error) { const parts: string[] = []; - if (value.name) parts.push(value.name); - if (value.message) parts.push(value.message); + if (value.name) { + parts.push(value.name); + } + if (value.message) { + parts.push(value.message); + } const code = "code" in value && (typeof value.code === "string" || typeof value.code === "number") ? String(value.code) : ""; - if (code) parts.push(`code=${code}`); + if (code) { + parts.push(`code=${code}`); + } const combined = parts.filter(Boolean).join(": ").trim(); if (combined) { return combined.length > LOG_VALUE_LIMIT @@ -57,7 +67,9 @@ export function formatForLog(value: unknown): string { const code = typeof rec.code === "string" || typeof rec.code === "number" ? String(rec.code) : ""; const parts = [name, rec.message.trim()].filter(Boolean); - if (code) parts.push(`code=${code}`); + if (code) { + parts.push(`code=${code}`); + } const combined = parts.join(": ").trim(); return combined.length > LOG_VALUE_LIMIT ? `${combined.slice(0, LOG_VALUE_LIMIT)}...` @@ -68,7 +80,9 @@ export function formatForLog(value: unknown): string { typeof value === "string" || typeof value === "number" ? String(value) : JSON.stringify(value); - if (!str) return ""; + if (!str) { + return ""; + } const redacted = redactSensitiveText(str, WS_LOG_REDACT_OPTIONS); return redacted.length > LOG_VALUE_LIMIT ? `${redacted.slice(0, LOG_VALUE_LIMIT)}...` @@ -80,12 +94,16 @@ export function formatForLog(value: unknown): string { function compactPreview(input: string, maxLen = 160): string { const oneLine = input.replace(/\s+/g, " ").trim(); - if (oneLine.length <= maxLen) return oneLine; + if (oneLine.length <= maxLen) { + return oneLine; + } return `${oneLine.slice(0, Math.max(0, maxLen - 1))}…`; } export function summarizeAgentEventForWsLog(payload: unknown): Record { - if (!payload || typeof payload !== "object") return {}; + if (!payload || typeof payload !== "object") { + return {}; + } const rec = payload as Record; const runId = typeof rec.runId === "string" ? rec.runId : undefined; const stream = typeof rec.stream === "string" ? rec.stream : undefined; @@ -95,7 +113,9 @@ export function summarizeAgentEventForWsLog(payload: unknown): Record) : undefined; const extra: Record = {}; - if (runId) extra.run = shortId(runId); + if (runId) { + extra.run = shortId(runId); + } if (sessionKey) { const parsed = parseAgentSessionKey(sessionKey); if (parsed) { @@ -105,47 +125,75 @@ export function summarizeAgentEventForWsLog(payload: unknown): Record 0) extra.media = mediaUrls.length; + if (mediaUrls && mediaUrls.length > 0) { + extra.media = mediaUrls.length; + } return extra; } if (stream === "tool") { const phase = typeof data.phase === "string" ? data.phase : undefined; const name = typeof data.name === "string" ? data.name : undefined; - if (phase || name) extra.tool = `${phase ?? "?"}:${name ?? "?"}`; + if (phase || name) { + extra.tool = `${phase ?? "?"}:${name ?? "?"}`; + } const toolCallId = typeof data.toolCallId === "string" ? data.toolCallId : undefined; - if (toolCallId) extra.call = shortId(toolCallId); + if (toolCallId) { + extra.call = shortId(toolCallId); + } const meta = typeof data.meta === "string" ? data.meta : undefined; - if (meta?.trim()) extra.meta = meta; - if (typeof data.isError === "boolean") extra.err = data.isError; + if (meta?.trim()) { + extra.meta = meta; + } + if (typeof data.isError === "boolean") { + extra.err = data.isError; + } return extra; } if (stream === "lifecycle") { const phase = typeof data.phase === "string" ? data.phase : undefined; - if (phase) extra.phase = phase; - if (typeof data.aborted === "boolean") extra.aborted = data.aborted; + if (phase) { + extra.phase = phase; + } + if (typeof data.aborted === "boolean") { + extra.aborted = data.aborted; + } const error = typeof data.error === "string" ? data.error : undefined; - if (error?.trim()) extra.error = compactPreview(error, 120); + if (error?.trim()) { + extra.error = compactPreview(error, 120); + } return extra; } const reason = typeof data.reason === "string" ? data.reason : undefined; - if (reason?.trim()) extra.reason = reason; + if (reason?.trim()) { + extra.reason = reason; + } return extra; } export function logWs(direction: "in" | "out", kind: string, meta?: Record) { - if (!shouldLogSubsystemToConsole("gateway/ws")) return; + if (!shouldLogSubsystemToConsole("gateway/ws")) { + return; + } const style = getGatewayWsLogStyle(); if (!isVerbose()) { logWsOptimized(direction, kind, meta); @@ -172,7 +220,9 @@ export function logWs(direction: "in" | "out", kind: string, meta?: Record { const startedAt = wsInflightSince.get(inflightKey); - if (startedAt === undefined) return undefined; + if (startedAt === undefined) { + return undefined; + } wsInflightSince.delete(inflightKey); return now - startedAt; })() @@ -201,10 +251,18 @@ export function logWs(direction: "in" | "out", kind: string, meta?: Record Boolean(t), @@ -232,7 +292,9 @@ function logWsOptimized(direction: "in" | "out", kind: string, meta?: Record 2000) wsInflightOptimized.clear(); + if (wsInflightOptimized.size > 2000) { + wsInflightOptimized.clear(); + } return; } @@ -250,15 +312,21 @@ function logWsOptimized(direction: "in" | "out", kind: string, meta?: Record= DEFAULT_WS_SLOW_MS); - if (!shouldLog) return; + if (!shouldLog) { + return; + } const statusToken = ok === undefined ? undefined : ok ? chalk.greenBright("✓") : chalk.redBright("✗"); @@ -267,9 +335,15 @@ function logWsOptimized(direction: "in" | "out", kind: string, meta?: Record { - if (kind === "req" || kind === "res") return "⇄"; + if (kind === "req" || kind === "res") { + return "⇄"; + } return direction === "in" ? "←" : "→"; })(); const arrowColor = @@ -340,10 +416,18 @@ function logWsCompact(direction: "in" | "out", kind: string, meta?: Record Boolean(t), diff --git a/src/git-hooks.test.ts b/src/git-hooks.test.ts index 424bec8472..b2a70f97dd 100644 --- a/src/git-hooks.test.ts +++ b/src/git-hooks.test.ts @@ -59,8 +59,12 @@ describe("setupGitHooks", () => { it("returns not-repo when not inside a work tree", () => { const runGit = vi.fn((args) => { - if (args[0] === "--version") return { status: 0, stdout: "git version" }; - if (args[0] === "rev-parse") return { status: 0, stdout: "false" }; + if (args[0] === "--version") { + return { status: 0, stdout: "git version" }; + } + if (args[0] === "rev-parse") { + return { status: 0, stdout: "false" }; + } return { status: 1, stdout: "" }; }); @@ -77,9 +81,15 @@ describe("setupGitHooks", () => { fs.chmodSync(hookPath, 0o644); const runGit = vi.fn((args) => { - if (args[0] === "--version") return { status: 0, stdout: "git version" }; - if (args[0] === "rev-parse") return { status: 0, stdout: "true" }; - if (args[0] === "config") return { status: 0, stdout: "" }; + if (args[0] === "--version") { + return { status: 0, stdout: "git version" }; + } + if (args[0] === "rev-parse") { + return { status: 0, stdout: "true" }; + } + if (args[0] === "config") { + return { status: 0, stdout: "" }; + } return { status: 1, stdout: "" }; }); diff --git a/src/globals.ts b/src/globals.ts index c3b9b2d1ff..5523c41ec7 100644 --- a/src/globals.ts +++ b/src/globals.ts @@ -17,18 +17,24 @@ export function shouldLogVerbose() { } export function logVerbose(message: string) { - if (!shouldLogVerbose()) return; + if (!shouldLogVerbose()) { + return; + } try { getLogger().debug({ message }, "verbose"); } catch { // ignore logger failures to avoid breaking verbose printing } - if (!globalVerbose) return; + if (!globalVerbose) { + return; + } console.log(theme.muted(message)); } export function logVerboseConsole(message: string) { - if (!globalVerbose) return; + if (!globalVerbose) { + return; + } console.log(theme.muted(message)); } diff --git a/src/hooks/bundled-dir.ts b/src/hooks/bundled-dir.ts index 1c3e70ca0e..9f2560751d 100644 --- a/src/hooks/bundled-dir.ts +++ b/src/hooks/bundled-dir.ts @@ -4,13 +4,17 @@ import { fileURLToPath } from "node:url"; export function resolveBundledHooksDir(): string | undefined { const override = process.env.OPENCLAW_BUNDLED_HOOKS_DIR?.trim(); - if (override) return override; + if (override) { + return override; + } // bun --compile: ship a sibling `hooks/bundled/` next to the executable. try { const execDir = path.dirname(process.execPath); const sibling = path.join(execDir, "hooks", "bundled"); - if (fs.existsSync(sibling)) return sibling; + if (fs.existsSync(sibling)) { + return sibling; + } } catch { // ignore } @@ -20,7 +24,9 @@ export function resolveBundledHooksDir(): string | undefined { try { const moduleDir = path.dirname(fileURLToPath(import.meta.url)); const distBundled = path.join(moduleDir, "bundled"); - if (fs.existsSync(distBundled)) return distBundled; + if (fs.existsSync(distBundled)) { + return distBundled; + } } catch { // ignore } @@ -31,7 +37,9 @@ export function resolveBundledHooksDir(): string | undefined { const moduleDir = path.dirname(fileURLToPath(import.meta.url)); const root = path.resolve(moduleDir, "..", ".."); const srcBundled = path.join(root, "src", "hooks", "bundled"); - if (fs.existsSync(srcBundled)) return srcBundled; + if (fs.existsSync(srcBundled)) { + return srcBundled; + } } catch { // ignore } diff --git a/src/hooks/bundled/soul-evil/handler.ts b/src/hooks/bundled/soul-evil/handler.ts index 71611bcab4..88e5f94a75 100644 --- a/src/hooks/bundled/soul-evil/handler.ts +++ b/src/hooks/bundled/soul-evil/handler.ts @@ -6,21 +6,31 @@ import { applySoulEvilOverride, resolveSoulEvilConfigFromHook } from "../../soul const HOOK_KEY = "soul-evil"; const soulEvilHook: HookHandler = async (event) => { - if (!isAgentBootstrapEvent(event)) return; + if (!isAgentBootstrapEvent(event)) { + return; + } const context = event.context; - if (context.sessionKey && isSubagentSessionKey(context.sessionKey)) return; + if (context.sessionKey && isSubagentSessionKey(context.sessionKey)) { + return; + } const cfg = context.cfg; const hookConfig = resolveHookConfig(cfg, HOOK_KEY); - if (!hookConfig || hookConfig.enabled === false) return; + if (!hookConfig || hookConfig.enabled === false) { + return; + } const soulConfig = resolveSoulEvilConfigFromHook(hookConfig as Record, { warn: (message) => console.warn(`[soul-evil] ${message}`), }); - if (!soulConfig) return; + if (!soulConfig) { + return; + } const workspaceDir = context.workspaceDir; - if (!workspaceDir || !Array.isArray(context.bootstrapFiles)) return; + if (!workspaceDir || !Array.isArray(context.bootstrapFiles)) { + return; + } const updated = await applySoulEvilOverride({ files: context.bootstrapFiles, diff --git a/src/hooks/config.ts b/src/hooks/config.ts index b15d3e1843..553ac68b0f 100644 --- a/src/hooks/config.ts +++ b/src/hooks/config.ts @@ -11,10 +11,18 @@ const DEFAULT_CONFIG_VALUES: Record = { }; function isTruthy(value: unknown): boolean { - if (value === undefined || value === null) return false; - if (typeof value === "boolean") return value; - if (typeof value === "number") return value !== 0; - if (typeof value === "string") return value.trim().length > 0; + if (value === undefined || value === null) { + return false; + } + if (typeof value === "boolean") { + return value; + } + if (typeof value === "number") { + return value !== 0; + } + if (typeof value === "string") { + return value.trim().length > 0; + } return true; } @@ -22,7 +30,9 @@ export function resolveConfigPath(config: OpenClawConfig | undefined, pathStr: s const parts = pathStr.split(".").filter(Boolean); let current: unknown = config; for (const part of parts) { - if (typeof current !== "object" || current === null) return undefined; + if (typeof current !== "object" || current === null) { + return undefined; + } current = (current as Record)[part]; } return current; @@ -41,9 +51,13 @@ export function resolveHookConfig( hookKey: string, ): HookConfig | undefined { const hooks = config?.hooks?.internal?.entries; - if (!hooks || typeof hooks !== "object") return undefined; + if (!hooks || typeof hooks !== "object") { + return undefined; + } const entry = (hooks as Record)[hookKey]; - if (!entry || typeof entry !== "object") return undefined; + if (!entry || typeof entry !== "object") { + return undefined; + } return entry; } @@ -79,7 +93,9 @@ export function shouldIncludeHook(params: { const remotePlatforms = eligibility?.remote?.platforms ?? []; // Check if explicitly disabled - if (!pluginManaged && hookConfig?.enabled === false) return false; + if (!pluginManaged && hookConfig?.enabled === false) { + return false; + } // Check OS requirement if ( @@ -99,8 +115,12 @@ export function shouldIncludeHook(params: { const requiredBins = entry.metadata?.requires?.bins ?? []; if (requiredBins.length > 0) { for (const bin of requiredBins) { - if (hasBinary(bin)) continue; - if (eligibility?.remote?.hasBin?.(bin)) continue; + if (hasBinary(bin)) { + continue; + } + if (eligibility?.remote?.hasBin?.(bin)) { + continue; + } return false; } } @@ -111,15 +131,21 @@ export function shouldIncludeHook(params: { const anyFound = requiredAnyBins.some((bin) => hasBinary(bin)) || eligibility?.remote?.hasAnyBin?.(requiredAnyBins); - if (!anyFound) return false; + if (!anyFound) { + return false; + } } // Check required environment variables const requiredEnv = entry.metadata?.requires?.env ?? []; if (requiredEnv.length > 0) { for (const envName of requiredEnv) { - if (process.env[envName]) continue; - if (hookConfig?.env?.[envName]) continue; + if (process.env[envName]) { + continue; + } + if (hookConfig?.env?.[envName]) { + continue; + } return false; } } @@ -128,7 +154,9 @@ export function shouldIncludeHook(params: { const requiredConfig = entry.metadata?.requires?.config ?? []; if (requiredConfig.length > 0) { for (const configPath of requiredConfig) { - if (!isConfigPathTruthy(config, configPath)) return false; + if (!isConfigPathTruthy(config, configPath)) { + return false; + } } } diff --git a/src/hooks/frontmatter.ts b/src/hooks/frontmatter.ts index 33a3445f6b..18a20bf647 100644 --- a/src/hooks/frontmatter.ts +++ b/src/hooks/frontmatter.ts @@ -16,7 +16,9 @@ export function parseFrontmatter(content: string): ParsedHookFrontmatter { } function normalizeStringList(input: unknown): string[] { - if (!input) return []; + if (!input) { + return []; + } if (Array.isArray(input)) { return input.map((value) => String(value).trim()).filter(Boolean); } @@ -30,7 +32,9 @@ function normalizeStringList(input: unknown): string[] { } function parseInstallSpec(input: unknown): HookInstallSpec | undefined { - if (!input || typeof input !== "object") return undefined; + if (!input || typeof input !== "object") { + return undefined; + } const raw = input as Record; const kindRaw = typeof raw.kind === "string" ? raw.kind : typeof raw.type === "string" ? raw.type : ""; @@ -43,12 +47,22 @@ function parseInstallSpec(input: unknown): HookInstallSpec | undefined { kind: kind, }; - if (typeof raw.id === "string") spec.id = raw.id; - if (typeof raw.label === "string") spec.label = raw.label; + if (typeof raw.id === "string") { + spec.id = raw.id; + } + if (typeof raw.label === "string") { + spec.label = raw.label; + } const bins = normalizeStringList(raw.bins); - if (bins.length > 0) spec.bins = bins; - if (typeof raw.package === "string") spec.package = raw.package; - if (typeof raw.repository === "string") spec.repository = raw.repository; + if (bins.length > 0) { + spec.bins = bins; + } + if (typeof raw.package === "string") { + spec.package = raw.package; + } + if (typeof raw.repository === "string") { + spec.repository = raw.repository; + } return spec; } @@ -67,10 +81,14 @@ export function resolveOpenClawMetadata( frontmatter: ParsedHookFrontmatter, ): OpenClawHookMetadata | undefined { const raw = getFrontmatterValue(frontmatter, "metadata"); - if (!raw) return undefined; + if (!raw) { + return undefined; + } try { 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]; let metadataRaw: unknown; for (const key of metadataRawCandidates) { @@ -80,7 +98,9 @@ export function resolveOpenClawMetadata( break; } } - if (!metadataRaw || typeof metadataRaw !== "object") return undefined; + if (!metadataRaw || typeof metadataRaw !== "object") { + return undefined; + } const metadataObj = metadataRaw as Record; const requiresRaw = typeof metadataObj.requires === "object" && metadataObj.requires !== null diff --git a/src/hooks/gmail-ops.ts b/src/hooks/gmail-ops.ts index 505e26d5f8..4922040764 100644 --- a/src/hooks/gmail-ops.ts +++ b/src/hooks/gmail-ops.ts @@ -332,7 +332,9 @@ export async function runGmailService(opts: GmailRunOptions) { }, renewMs); const shutdown = () => { - if (shuttingDown) return; + if (shuttingDown) { + return; + } shuttingDown = true; clearInterval(renewTimer); child.kill("SIGTERM"); @@ -342,10 +344,14 @@ export async function runGmailService(opts: GmailRunOptions) { process.on("SIGTERM", shutdown); child.on("exit", () => { - if (shuttingDown) return; + if (shuttingDown) { + return; + } defaultRuntime.log("gog watch serve exited; restarting in 2s"); setTimeout(() => { - if (shuttingDown) return; + if (shuttingDown) { + return; + } child = spawnGogServe(runtimeConfig); }, 2000); }); @@ -365,7 +371,9 @@ async function startGmailWatch( const result = await runCommandWithTimeout(args, { timeoutMs: 120_000 }); if (result.code !== 0) { const message = result.stderr || result.stdout || "gog watch start failed"; - if (fatal) throw new Error(message); + if (fatal) { + throw new Error(message); + } defaultRuntime.error(message); } } diff --git a/src/hooks/gmail-setup-utils.ts b/src/hooks/gmail-setup-utils.ts index 179097ac57..daa1a0e113 100644 --- a/src/hooks/gmail-setup-utils.ts +++ b/src/hooks/gmail-setup-utils.ts @@ -11,8 +11,12 @@ const MAX_OUTPUT_CHARS = 800; function trimOutput(value: string): string { const trimmed = value.trim(); - if (!trimmed) return ""; - if (trimmed.length <= MAX_OUTPUT_CHARS) return trimmed; + if (!trimmed) { + return ""; + } + if (trimmed.length <= MAX_OUTPUT_CHARS) { + return trimmed; + } return `${trimmed.slice(0, MAX_OUTPUT_CHARS)}…`; } @@ -23,8 +27,12 @@ function formatCommandFailure(command: string, result: SpawnResult): string { const stderr = trimOutput(result.stderr); const stdout = trimOutput(result.stdout); const lines = [`${command} failed (code=${code}${signal}${killed})`]; - if (stderr) lines.push(`stderr: ${stderr}`); - if (stdout) lines.push(`stdout: ${stdout}`); + if (stderr) { + lines.push(`stderr: ${stderr}`); + } + if (stdout) { + lines.push(`stdout: ${stdout}`); + } return lines.join("\n"); } @@ -35,8 +43,12 @@ function formatCommandResult(command: string, result: SpawnResult): string { const stderr = trimOutput(result.stderr); const stdout = trimOutput(result.stdout); const lines = [`${command} exited (code=${code}${signal}${killed})`]; - if (stderr) lines.push(`stderr: ${stderr}`); - if (stdout) lines.push(`stdout: ${stdout}`); + if (stderr) { + lines.push(`stderr: ${stderr}`); + } + if (stdout) { + lines.push(`stdout: ${stdout}`); + } return lines.join("\n"); } @@ -57,7 +69,9 @@ function findExecutablesOnPath(bins: string[]): string[] { for (const part of parts) { for (const bin of bins) { const candidate = path.join(part, bin); - if (seen.has(candidate)) continue; + if (seen.has(candidate)) { + continue; + } try { fs.accessSync(candidate, fs.constants.X_OK); matches.push(candidate); @@ -73,13 +87,17 @@ function findExecutablesOnPath(bins: string[]): string[] { function ensurePathIncludes(dirPath: string, position: "append" | "prepend") { const pathEnv = process.env.PATH ?? ""; const parts = pathEnv.split(path.delimiter).filter(Boolean); - if (parts.includes(dirPath)) return; + if (parts.includes(dirPath)) { + return; + } const next = position === "prepend" ? [dirPath, ...parts] : [...parts, dirPath]; process.env.PATH = next.join(path.delimiter); } function ensureGcloudOnPath(): boolean { - if (hasBinary("gcloud")) return true; + if (hasBinary("gcloud")) { + return true; + } const candidates = [ "/opt/homebrew/share/google-cloud-sdk/bin/gcloud", "/usr/local/share/google-cloud-sdk/bin/gcloud", @@ -108,9 +126,13 @@ export async function resolvePythonExecutablePath(): Promise [candidate, "-c", "import os, sys; print(os.path.realpath(sys.executable))"], { timeoutMs: 2_000 }, ); - if (res.code !== 0) continue; + if (res.code !== 0) { + continue; + } const resolved = res.stdout.trim().split(/\s+/)[0]; - if (!resolved) continue; + if (!resolved) { + continue; + } try { fs.accessSync(resolved, fs.constants.X_OK); cachedPythonPath = resolved; @@ -124,9 +146,13 @@ export async function resolvePythonExecutablePath(): Promise } async function gcloudEnv(): Promise { - if (process.env.CLOUDSDK_PYTHON) return undefined; + if (process.env.CLOUDSDK_PYTHON) { + return undefined; + } const pythonPath = await resolvePythonExecutablePath(); - if (!pythonPath) return undefined; + if (!pythonPath) { + return undefined; + } return { CLOUDSDK_PYTHON: pythonPath }; } @@ -141,8 +167,12 @@ async function runGcloudCommand( } export async function ensureDependency(bin: string, brewArgs: string[]) { - if (bin === "gcloud" && ensureGcloudOnPath()) return; - if (hasBinary(bin)) return; + if (bin === "gcloud" && ensureGcloudOnPath()) { + return; + } + if (hasBinary(bin)) { + return; + } if (process.platform !== "darwin") { throw new Error(`${bin} not installed; install it and retry`); } @@ -167,7 +197,9 @@ export async function ensureGcloudAuth() { ["auth", "list", "--filter", "status:ACTIVE", "--format", "value(account)"], 30_000, ); - if (res.code === 0 && res.stdout.trim()) return; + if (res.code === 0 && res.stdout.trim()) { + return; + } const login = await runGcloudCommand(["auth", "login"], 600_000); if (login.code !== 0) { throw new Error(login.stderr || "gcloud auth login failed"); @@ -187,7 +219,9 @@ export async function ensureTopic(projectId: string, topicName: string) { ["pubsub", "topics", "describe", topicName, "--project", projectId], 30_000, ); - if (describe.code === 0) return; + if (describe.code === 0) { + return; + } await runGcloud(["pubsub", "topics", "create", topicName, "--project", projectId]); } @@ -235,7 +269,9 @@ export async function ensureTailscaleEndpoint(params: { target?: string; token?: string; }): Promise { - if (params.mode === "off") return ""; + if (params.mode === "off") { + return ""; + } const statusArgs = ["status", "--json"]; const statusCommand = formatCommand("tailscale", statusArgs); @@ -283,13 +319,17 @@ export async function ensureTailscaleEndpoint(params: { export async function resolveProjectIdFromGogCredentials(): Promise { const candidates = gogCredentialsPaths(); for (const candidate of candidates) { - if (!fs.existsSync(candidate)) continue; + if (!fs.existsSync(candidate)) { + continue; + } try { const raw = fs.readFileSync(candidate, "utf-8"); const parsed = JSON.parse(raw) as Record; const clientId = extractGogClientId(parsed); const projectNumber = extractProjectNumber(clientId); - if (!projectNumber) continue; + if (!projectNumber) { + continue; + } const res = await runGcloudCommand( [ "projects", @@ -301,9 +341,13 @@ export async function resolveProjectIdFromGogCredentials(): Promise): string | null { } function extractProjectNumber(clientId: string | null): string | null { - if (!clientId) return null; + if (!clientId) { + return null; + } const match = clientId.match(/^(\d+)-/); return match?.[1] ?? null; } diff --git a/src/hooks/gmail-watcher.ts b/src/hooks/gmail-watcher.ts index 256608d5b4..67bb003e09 100644 --- a/src/hooks/gmail-watcher.ts +++ b/src/hooks/gmail-watcher.ts @@ -75,12 +75,16 @@ function spawnGogServe(cfg: GmailHookRuntimeConfig): ChildProcess { child.stdout?.on("data", (data: Buffer) => { const line = data.toString().trim(); - if (line) log.info(`[gog] ${line}`); + if (line) { + log.info(`[gog] ${line}`); + } }); child.stderr?.on("data", (data: Buffer) => { const line = data.toString().trim(); - if (!line) return; + if (!line) { + return; + } if (isAddressInUseError(line)) { addressInUse = true; } @@ -92,7 +96,9 @@ function spawnGogServe(cfg: GmailHookRuntimeConfig): ChildProcess { }); child.on("exit", (code, signal) => { - if (shuttingDown) return; + if (shuttingDown) { + return; + } if (addressInUse) { log.warn( "gog serve failed to bind (address already in use); stopping restarts. " + @@ -104,7 +110,9 @@ function spawnGogServe(cfg: GmailHookRuntimeConfig): ChildProcess { log.warn(`gog exited (code=${code}, signal=${signal}); restarting in 5s`); watcherProcess = null; setTimeout(() => { - if (shuttingDown || !currentConfig) return; + if (shuttingDown || !currentConfig) { + return; + } watcherProcess = spawnGogServe(currentConfig); }, 5000); }); @@ -180,7 +188,9 @@ export async function startGmailWatcher(cfg: OpenClawConfig): Promise { - if (shuttingDown) return; + if (shuttingDown) { + return; + } void startGmailWatch(runtimeConfig); }, renewMs); diff --git a/src/hooks/gmail.ts b/src/hooks/gmail.ts index 36b0b2045e..9907f802b8 100644 --- a/src/hooks/gmail.ts +++ b/src/hooks/gmail.ts @@ -71,7 +71,9 @@ export function mergeHookPresets(existing: string[] | undefined, preset: string) export function normalizeHooksPath(raw?: string): string { const base = raw?.trim() || DEFAULT_HOOKS_PATH; - if (base === "/") return DEFAULT_HOOKS_PATH; + if (base === "/") { + return DEFAULT_HOOKS_PATH; + } const withSlash = base.startsWith("/") ? base : `/${base}`; return withSlash.replace(/\/+$/, ""); } @@ -80,7 +82,9 @@ export function normalizeServePath(raw?: string): string { const base = raw?.trim() || DEFAULT_GMAIL_SERVE_PATH; // Tailscale funnel/serve strips the set-path prefix before proxying. // To accept requests at / externally, gog must listen on "/". - if (base === "/") return "/"; + if (base === "/") { + return "/"; + } const withSlash = base.startsWith("/") ? base : `/${base}`; return withSlash.replace(/\/+$/, ""); } @@ -253,7 +257,9 @@ export function buildTopicPath(projectId: string, topicName: string): string { export function parseTopicPath(topic: string): { projectId: string; topicName: string } | null { const match = topic.trim().match(/^projects\/([^/]+)\/topics\/([^/]+)$/i); - if (!match) return null; + if (!match) { + return null; + } return { projectId: match[1] ?? "", topicName: match[2] ?? "" }; } diff --git a/src/hooks/hooks-install.e2e.test.ts b/src/hooks/hooks-install.e2e.test.ts index 0ea6d8aca2..0bb0fcc637 100644 --- a/src/hooks/hooks-install.e2e.test.ts +++ b/src/hooks/hooks-install.e2e.test.ts @@ -95,7 +95,9 @@ describe("hooks install (e2e)", () => { const { installHooksFromPath } = await import("./install.js"); const installResult = await installHooksFromPath({ path: packDir }); expect(installResult.ok).toBe(true); - if (!installResult.ok) return; + if (!installResult.ok) { + return; + } const { clearInternalHooks, createInternalHookEvent, triggerInternalHook } = await import("./internal-hooks.js"); diff --git a/src/hooks/hooks-status.ts b/src/hooks/hooks-status.ts index a4bc0bb3bd..9ceef9972d 100644 --- a/src/hooks/hooks-status.ts +++ b/src/hooks/hooks-status.ts @@ -65,7 +65,9 @@ function resolveHookKey(entry: HookEntry): string { function normalizeInstallOptions(entry: HookEntry): HookInstallOption[] { const install = entry.metadata?.install ?? []; - if (install.length === 0) return []; + if (install.length === 0) { + return []; + } // For hooks, we just list all install options return install.map((spec, index) => { @@ -115,8 +117,12 @@ function buildHookStatus( const requiredOs = entry.metadata?.os ?? []; const missingBins = requiredBins.filter((bin) => { - if (hasBinary(bin)) return false; - if (eligibility?.remote?.hasBin?.(bin)) return false; + if (hasBinary(bin)) { + return false; + } + if (eligibility?.remote?.hasBin?.(bin)) { + return false; + } return true; }); @@ -138,8 +144,12 @@ function buildHookStatus( const missingEnv: string[] = []; for (const envName of requiredEnv) { - if (process.env[envName]) continue; - if (hookConfig?.env?.[envName]) continue; + if (process.env[envName]) { + continue; + } + if (hookConfig?.env?.[envName]) { + continue; + } missingEnv.push(envName); } diff --git a/src/hooks/install.test.ts b/src/hooks/install.test.ts index 4ac05e9238..212c15cc00 100644 --- a/src/hooks/install.test.ts +++ b/src/hooks/install.test.ts @@ -61,7 +61,9 @@ describe("installHooksFromArchive", () => { const result = await installHooksFromArchive({ archivePath, hooksDir }); expect(result.ok).toBe(true); - if (!result.ok) return; + if (!result.ok) { + return; + } expect(result.hookPackId).toBe("zip-hooks"); expect(result.hooks).toContain("zip-hook"); expect(result.targetDir).toBe(path.join(stateDir, "hooks", "zip-hooks")); @@ -109,7 +111,9 @@ describe("installHooksFromArchive", () => { const result = await installHooksFromArchive({ archivePath, hooksDir }); expect(result.ok).toBe(true); - if (!result.ok) return; + if (!result.ok) { + return; + } expect(result.hookPackId).toBe("tar-hooks"); expect(result.hooks).toContain("tar-hook"); expect(result.targetDir).toBe(path.join(stateDir, "hooks", "tar-hooks")); @@ -142,7 +146,9 @@ describe("installHooksFromPath", () => { const result = await installHooksFromPath({ path: hookDir, hooksDir }); expect(result.ok).toBe(true); - if (!result.ok) return; + if (!result.ok) { + return; + } expect(result.hookPackId).toBe("my-hook"); expect(result.hooks).toEqual(["my-hook"]); expect(result.targetDir).toBe(path.join(stateDir, "hooks", "my-hook")); diff --git a/src/hooks/install.ts b/src/hooks/install.ts index 32a6dac0b4..7fe7b5eaaa 100644 --- a/src/hooks/install.ts +++ b/src/hooks/install.ts @@ -39,13 +39,17 @@ const defaultLogger: HookInstallLogger = {}; function unscopedPackageName(name: string): string { const trimmed = name.trim(); - if (!trimmed) return trimmed; + if (!trimmed) { + return trimmed; + } return trimmed.includes("/") ? (trimmed.split("/").pop() ?? trimmed) : trimmed; } function safeDirName(input: string): string { const trimmed = input.trim(); - if (!trimmed) return trimmed; + if (!trimmed) { + return trimmed; + } return trimmed.replaceAll("/", "__"); } @@ -349,7 +353,9 @@ export async function installHooksFromNpmSpec(params: { const dryRun = params.dryRun ?? false; const expectedHookPackId = params.expectedHookPackId; const spec = params.spec.trim(); - if (!spec) return { ok: false, error: "missing npm spec" }; + if (!spec) { + return { ok: false, error: "missing npm spec" }; + } const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-hook-pack-")); logger.info?.(`Downloading ${spec}…`); diff --git a/src/hooks/internal-hooks.ts b/src/hooks/internal-hooks.ts index 136bd2fd3e..e92b193668 100644 --- a/src/hooks/internal-hooks.ts +++ b/src/hooks/internal-hooks.ts @@ -167,9 +167,15 @@ export function createInternalHookEvent( } export function isAgentBootstrapEvent(event: InternalHookEvent): event is AgentBootstrapHookEvent { - if (event.type !== "agent" || event.action !== "bootstrap") return false; + if (event.type !== "agent" || event.action !== "bootstrap") { + return false; + } const context = event.context as Partial | null; - if (!context || typeof context !== "object") return false; - if (typeof context.workspaceDir !== "string") return false; + if (!context || typeof context !== "object") { + return false; + } + if (typeof context.workspaceDir !== "string") { + return false; + } return Array.isArray(context.bootstrapFiles); } diff --git a/src/hooks/plugin-hooks.ts b/src/hooks/plugin-hooks.ts index 2136c55e69..5e98d130c4 100644 --- a/src/hooks/plugin-hooks.ts +++ b/src/hooks/plugin-hooks.ts @@ -15,7 +15,9 @@ export type PluginHookLoadResult = { }; function resolveHookDir(api: OpenClawPluginApi, dir: string): string { - if (path.isAbsolute(dir)) return dir; + if (path.isAbsolute(dir)) { + return dir; + } return path.resolve(path.dirname(api.source), dir); } diff --git a/src/hooks/soul-evil.ts b/src/hooks/soul-evil.ts index 934d0a48c8..0e60c92dc4 100644 --- a/src/hooks/soul-evil.ts +++ b/src/hooks/soul-evil.ts @@ -44,7 +44,9 @@ export function resolveSoulEvilConfigFromHook( entry: Record | undefined, log?: SoulEvilLog, ): SoulEvilConfig | null { - if (!entry) return null; + if (!entry) { + return null; + } const file = typeof entry.file === "string" ? entry.file : undefined; if (entry.file !== undefined && !file) { log?.warn?.("soul-evil config: file must be a string"); @@ -80,23 +82,33 @@ export function resolveSoulEvilConfigFromHook( log?.warn?.("soul-evil config: purge must be an object"); } - if (!file && chance === undefined && !purge) return null; + if (!file && chance === undefined && !purge) { + return null; + } return { file, chance, purge }; } function clampChance(value?: number): number { - if (typeof value !== "number" || !Number.isFinite(value)) return 0; + if (typeof value !== "number" || !Number.isFinite(value)) { + return 0; + } return Math.min(1, Math.max(0, value)); } function parsePurgeAt(raw?: string): number | null { - if (!raw) return null; + if (!raw) { + return null; + } const trimmed = raw.trim(); const match = /^([01]?\d|2[0-3]):([0-5]\d)$/.exec(trimmed); - if (!match) return null; + if (!match) { + return null; + } const hour = Number.parseInt(match[1] ?? "", 10); const minute = Number.parseInt(match[2] ?? "", 10); - if (!Number.isFinite(hour) || !Number.isFinite(minute)) return null; + if (!Number.isFinite(hour) || !Number.isFinite(minute)) { + return null; + } return hour * 60 + minute; } @@ -111,9 +123,13 @@ function timeOfDayMsInTimezone(date: Date, timeZone: string): number | null { }).formatToParts(date); const map: Record = {}; for (const part of parts) { - if (part.type !== "literal") map[part.type] = part.value; + if (part.type !== "literal") { + map[part.type] = part.value; + } + } + if (!map.hour || !map.minute || !map.second) { + return null; } - if (!map.hour || !map.minute || !map.second) return null; const hour = Number.parseInt(map.hour, 10); const minute = Number.parseInt(map.minute, 10); const second = Number.parseInt(map.second, 10); @@ -132,9 +148,13 @@ function isWithinDailyPurgeWindow(params: { now: Date; timeZone: string; }): boolean { - if (!params.at || !params.duration) return false; + if (!params.at || !params.duration) { + return false; + } const startMinutes = parsePurgeAt(params.at); - if (startMinutes === null) return false; + if (startMinutes === null) { + return false; + } let durationMs: number; try { @@ -142,13 +162,19 @@ function isWithinDailyPurgeWindow(params: { } catch { return false; } - if (!Number.isFinite(durationMs) || durationMs <= 0) return false; + if (!Number.isFinite(durationMs) || durationMs <= 0) { + return false; + } const dayMs = 24 * 60 * 60 * 1000; - if (durationMs >= dayMs) return true; + if (durationMs >= dayMs) { + return true; + } const nowMs = timeOfDayMsInTimezone(params.now, params.timeZone); - if (nowMs === null) return false; + if (nowMs === null) { + return false; + } const startMs = startMinutes * 60 * 1000; const endMs = startMs + durationMs; @@ -204,7 +230,9 @@ export async function applySoulEvilOverride(params: { now: params.now, random: params.random, }); - if (!decision.useEvil) return params.files; + if (!decision.useEvil) { + return params.files; + } const workspaceDir = resolveUserPath(params.workspaceDir); const evilPath = path.join(workspaceDir, decision.fileName); @@ -235,11 +263,15 @@ export async function applySoulEvilOverride(params: { let replaced = false; const updated = params.files.map((file) => { - if (file.name !== "SOUL.md") return file; + if (file.name !== "SOUL.md") { + return file; + } replaced = true; return { ...file, content: evilContent, missing: false }; }); - if (!replaced) return params.files; + if (!replaced) { + return params.files; + } params.log?.debug?.( `SOUL_EVIL active (${decision.reason ?? "unknown"}) using ${decision.fileName}`, diff --git a/src/hooks/workspace.ts b/src/hooks/workspace.ts index 84641abe13..3c75968af6 100644 --- a/src/hooks/workspace.ts +++ b/src/hooks/workspace.ts @@ -34,7 +34,9 @@ function filterHookEntries( function readHookPackageManifest(dir: string): HookPackageManifest | null { const manifestPath = path.join(dir, "package.json"); - if (!fs.existsSync(manifestPath)) return null; + if (!fs.existsSync(manifestPath)) { + return null; + } try { const raw = fs.readFileSync(manifestPath, "utf-8"); return JSON.parse(raw) as HookPackageManifest; @@ -45,7 +47,9 @@ function readHookPackageManifest(dir: string): HookPackageManifest | null { function resolvePackageHooks(manifest: HookPackageManifest): string[] { const raw = manifest[MANIFEST_KEY]?.hooks; - if (!Array.isArray(raw)) return []; + if (!Array.isArray(raw)) { + return []; + } return raw.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean); } @@ -56,7 +60,9 @@ function loadHookFromDir(params: { nameHint?: string; }): Hook | null { const hookMdPath = path.join(params.hookDir, "HOOK.md"); - if (!fs.existsSync(hookMdPath)) return null; + if (!fs.existsSync(hookMdPath)) { + return null; + } try { const content = fs.readFileSync(hookMdPath, "utf-8"); @@ -101,16 +107,22 @@ function loadHookFromDir(params: { function loadHooksFromDir(params: { dir: string; source: HookSource; pluginId?: string }): Hook[] { const { dir, source, pluginId } = params; - if (!fs.existsSync(dir)) return []; + if (!fs.existsSync(dir)) { + return []; + } const stat = fs.statSync(dir); - if (!stat.isDirectory()) return []; + if (!stat.isDirectory()) { + return []; + } const hooks: Hook[] = []; const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { - if (!entry.isDirectory()) continue; + if (!entry.isDirectory()) { + continue; + } const hookDir = path.join(dir, entry.name); const manifest = readHookPackageManifest(hookDir); @@ -125,7 +137,9 @@ function loadHooksFromDir(params: { dir: string; source: HookSource; pluginId?: pluginId, nameHint: path.basename(resolvedHookDir), }); - if (hook) hooks.push(hook); + if (hook) { + hooks.push(hook); + } } continue; } @@ -136,7 +150,9 @@ function loadHooksFromDir(params: { dir: string; source: HookSource; pluginId?: pluginId, nameHint: entry.name, }); - if (hook) hooks.push(hook); + if (hook) { + hooks.push(hook); + } } return hooks; @@ -214,10 +230,18 @@ function loadHookEntries( const merged = new Map(); // Precedence: extra < bundled < managed < workspace (workspace wins) - for (const hook of extraHooks) merged.set(hook.name, hook); - for (const hook of bundledHooks) merged.set(hook.name, hook); - for (const hook of managedHooks) merged.set(hook.name, hook); - for (const hook of workspaceHooks) merged.set(hook.name, hook); + for (const hook of extraHooks) { + merged.set(hook.name, hook); + } + for (const hook of bundledHooks) { + merged.set(hook.name, hook); + } + for (const hook of managedHooks) { + merged.set(hook.name, hook); + } + for (const hook of workspaceHooks) { + merged.set(hook.name, hook); + } return Array.from(merged.values()).map((hook) => { let frontmatter: ParsedHookFrontmatter = {}; diff --git a/src/imessage/accounts.ts b/src/imessage/accounts.ts index 07a06d7b9c..ed8ad886e8 100644 --- a/src/imessage/accounts.ts +++ b/src/imessage/accounts.ts @@ -12,19 +12,25 @@ export type ResolvedIMessageAccount = { function listConfiguredAccountIds(cfg: OpenClawConfig): string[] { const accounts = cfg.channels?.imessage?.accounts; - if (!accounts || typeof accounts !== "object") return []; + if (!accounts || typeof accounts !== "object") { + return []; + } return Object.keys(accounts).filter(Boolean); } export function listIMessageAccountIds(cfg: OpenClawConfig): string[] { const ids = listConfiguredAccountIds(cfg); - if (ids.length === 0) return [DEFAULT_ACCOUNT_ID]; + if (ids.length === 0) { + return [DEFAULT_ACCOUNT_ID]; + } return ids.toSorted((a, b) => a.localeCompare(b)); } export function resolveDefaultIMessageAccountId(cfg: OpenClawConfig): string { const ids = listIMessageAccountIds(cfg); - if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID; + if (ids.includes(DEFAULT_ACCOUNT_ID)) { + return DEFAULT_ACCOUNT_ID; + } return ids[0] ?? DEFAULT_ACCOUNT_ID; } @@ -33,7 +39,9 @@ function resolveAccountConfig( accountId: string, ): IMessageAccountConfig | undefined { const accounts = cfg.channels?.imessage?.accounts; - if (!accounts || typeof accounts !== "object") return undefined; + if (!accounts || typeof accounts !== "object") { + return undefined; + } return accounts[accountId] as IMessageAccountConfig | undefined; } diff --git a/src/imessage/client.ts b/src/imessage/client.ts index 52d255ad83..47ed058686 100644 --- a/src/imessage/client.ts +++ b/src/imessage/client.ts @@ -60,7 +60,9 @@ export class IMessageRpcClient { } async start(): Promise { - if (this.child) return; + if (this.child) { + return; + } const args = ["rpc"]; if (this.dbPath) { args.push("--db", this.dbPath); @@ -73,14 +75,18 @@ export class IMessageRpcClient { this.reader.on("line", (line) => { const trimmed = line.trim(); - if (!trimmed) return; + if (!trimmed) { + return; + } this.handleLine(trimmed); }); child.stderr?.on("data", (chunk) => { const lines = chunk.toString().split(/\r?\n/); for (const line of lines) { - if (!line.trim()) continue; + if (!line.trim()) { + continue; + } this.runtime?.error?.(`imsg rpc: ${line.trim()}`); } }); @@ -102,7 +108,9 @@ export class IMessageRpcClient { } async stop(): Promise { - if (!this.child) return; + if (!this.child) { + return; + } this.reader?.close(); this.reader = null; this.child.stdin?.end(); @@ -113,7 +121,9 @@ export class IMessageRpcClient { this.closed, new Promise((resolve) => { setTimeout(() => { - if (!child.killed) child.kill("SIGTERM"); + if (!child.killed) { + child.kill("SIGTERM"); + } resolve(); }, 500); }), @@ -175,8 +185,12 @@ export class IMessageRpcClient { if (parsed.id !== undefined && parsed.id !== null) { const key = String(parsed.id); const pending = this.pending.get(key); - if (!pending) return; - if (pending.timer) clearTimeout(pending.timer); + if (!pending) { + return; + } + if (pending.timer) { + clearTimeout(pending.timer); + } this.pending.delete(key); if (parsed.error) { @@ -184,11 +198,15 @@ export class IMessageRpcClient { const details = parsed.error.data; const code = parsed.error.code; const suffixes = [] as string[]; - if (typeof code === "number") suffixes.push(`code=${code}`); + if (typeof code === "number") { + suffixes.push(`code=${code}`); + } if (details !== undefined) { const detailText = typeof details === "string" ? details : JSON.stringify(details, null, 2); - if (detailText) suffixes.push(detailText); + if (detailText) { + suffixes.push(detailText); + } } const msg = suffixes.length > 0 ? `${baseMessage}: ${suffixes.join(" ")}` : baseMessage; pending.reject(new Error(msg)); @@ -208,7 +226,9 @@ export class IMessageRpcClient { private failAll(err: Error) { for (const [key, pending] of this.pending.entries()) { - if (pending.timer) clearTimeout(pending.timer); + if (pending.timer) { + clearTimeout(pending.timer); + } pending.reject(err); this.pending.delete(key); } diff --git a/src/imessage/monitor.skips-group-messages-without-mention-by-default.test.ts b/src/imessage/monitor.skips-group-messages-without-mention-by-default.test.ts index f066bb58f8..08656d482c 100644 --- a/src/imessage/monitor.skips-group-messages-without-mention-by-default.test.ts +++ b/src/imessage/monitor.skips-group-messages-without-mention-by-default.test.ts @@ -64,7 +64,9 @@ const flush = () => new Promise((resolve) => setTimeout(resolve, 0)); async function waitForSubscribe() { for (let i = 0; i < 5; i += 1) { - if (requestMock.mock.calls.some((call) => call[0] === "watch.subscribe")) return; + if (requestMock.mock.calls.some((call) => call[0] === "watch.subscribe")) { + return; + } await flush(); } } @@ -84,7 +86,9 @@ beforeEach(() => { }, }; requestMock.mockReset().mockImplementation((method: string) => { - if (method === "watch.subscribe") return Promise.resolve({ subscription: 1 }); + if (method === "watch.subscribe") { + return Promise.resolve({ subscription: 1 }); + } return Promise.resolve({}); }); stopMock.mockReset().mockResolvedValue(undefined); diff --git a/src/imessage/monitor.updates-last-route-chat-id-direct-messages.test.ts b/src/imessage/monitor.updates-last-route-chat-id-direct-messages.test.ts index 6aef30f8da..8573de0223 100644 --- a/src/imessage/monitor.updates-last-route-chat-id-direct-messages.test.ts +++ b/src/imessage/monitor.updates-last-route-chat-id-direct-messages.test.ts @@ -64,7 +64,9 @@ const flush = () => new Promise((resolve) => setTimeout(resolve, 0)); async function waitForSubscribe() { for (let i = 0; i < 5; i += 1) { - if (requestMock.mock.calls.some((call) => call[0] === "watch.subscribe")) return; + if (requestMock.mock.calls.some((call) => call[0] === "watch.subscribe")) { + return; + } await flush(); } } @@ -84,7 +86,9 @@ beforeEach(() => { }, }; requestMock.mockReset().mockImplementation((method: string) => { - if (method === "watch.subscribe") return Promise.resolve({ subscription: 1 }); + if (method === "watch.subscribe") { + return Promise.resolve({ subscription: 1 }); + } return Promise.resolve({}); }); stopMock.mockReset().mockResolvedValue(undefined); @@ -133,8 +137,12 @@ describe("monitorIMessageProvider", () => { it("does not trigger unhandledRejection when aborting during shutdown", async () => { requestMock.mockImplementation((method: string) => { - if (method === "watch.subscribe") return Promise.resolve({ subscription: 1 }); - if (method === "watch.unsubscribe") return Promise.reject(new Error("imsg rpc closed")); + if (method === "watch.subscribe") { + return Promise.resolve({ subscription: 1 }); + } + if (method === "watch.unsubscribe") { + return Promise.reject(new Error("imsg rpc closed")); + } return Promise.resolve({}); }); diff --git a/src/imessage/monitor/deliver.ts b/src/imessage/monitor/deliver.ts index c07bc2b089..84ee848e5a 100644 --- a/src/imessage/monitor/deliver.ts +++ b/src/imessage/monitor/deliver.ts @@ -28,7 +28,9 @@ export async function deliverReplies(params: { const mediaList = payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []); const rawText = payload.text ?? ""; const text = convertMarkdownTables(rawText, tableMode); - if (!text && mediaList.length === 0) continue; + if (!text && mediaList.length === 0) { + continue; + } if (mediaList.length === 0) { for (const chunk of chunkTextWithMode(text, textLimit, chunkMode)) { await sendMessageIMessage(target, chunk, { diff --git a/src/imessage/monitor/monitor-provider.ts b/src/imessage/monitor/monitor-provider.ts index 4910716993..7896650ace 100644 --- a/src/imessage/monitor/monitor-provider.ts +++ b/src/imessage/monitor/monitor-provider.ts @@ -72,7 +72,9 @@ async function detectRemoteHostFromCliPath(cliPath: string): Promise { const sender = entry.message.sender?.trim(); - if (!sender) return null; + if (!sender) { + return null; + } const conversationId = entry.message.chat_id != null ? `chat:${entry.message.chat_id}` @@ -158,13 +166,19 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P }, shouldDebounce: (entry) => { const text = entry.message.text?.trim() ?? ""; - if (!text) return false; - if (entry.message.attachments && entry.message.attachments.length > 0) return false; + if (!text) { + return false; + } + if (entry.message.attachments && entry.message.attachments.length > 0) { + return false; + } return !hasControlCommand(text, cfg); }, onFlush: async (entries) => { const last = entries.at(-1); - if (!last) return; + if (!last) { + return; + } if (entries.length === 1) { await handleMessageNow(last.message); return; @@ -188,9 +202,13 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P async function handleMessageNow(message: IMessagePayload) { const senderRaw = message.sender ?? ""; const sender = senderRaw.trim(); - if (!sender) return; + if (!sender) { + return; + } const senderNormalized = normalizeIMessageHandle(sender); - if (message.is_from_me) return; + if (message.is_from_me) { + return; + } const chatId = message.chat_id ?? undefined; const chatGuid = message.chat_guid ?? undefined; @@ -220,7 +238,9 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P ); const isGroup = Boolean(message.is_group) || treatAsGroupByConfig; - if (isGroup && !chatId) return; + if (isGroup && !chatId) { + return; + } const groupId = isGroup ? groupIdCandidate : undefined; const storeAllowFrom = await readChannelAllowFromStore("imessage").catch(() => []); @@ -273,7 +293,9 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P chatIdentifier, })); if (!isGroup) { - if (dmPolicy === "disabled") return; + if (dmPolicy === "disabled") { + return; + } if (!dmAuthorized) { if (dmPolicy === "pairing") { const senderId = normalizeIMessageHandle(sender); @@ -336,7 +358,9 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P const kind = mediaKindFromMime(mediaType ?? undefined); const placeholder = kind ? `` : attachments?.length ? "" : ""; const bodyText = messageText || placeholder; - if (!bodyText) return; + if (!bodyText) { + return; + } const replyContext = describeReplyContext(message); const createdAt = message.created_at ? Date.parse(message.created_at) : undefined; const historyKey = isGroup @@ -580,7 +604,9 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P const handleMessage = async (raw: unknown) => { const params = raw as { message?: IMessagePayload | null }; const message = params?.message ?? null; - if (!message) return; + if (!message) { + return; + } await inboundDebouncer.enqueue({ message }); }; @@ -594,7 +620,9 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P runtime, check: async () => { const probe = await probeIMessage(2000, { cliPath, dbPath, runtime }); - if (probe.ok) return { ok: true }; + if (probe.ok) { + return { ok: true }; + } if (probe.fatal) { throw new Error(probe.error ?? "imsg rpc unavailable"); } @@ -602,7 +630,9 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P }, }); - if (opts.abortSignal?.aborted) return; + if (opts.abortSignal?.aborted) { + return; + } const client = await createIMessageRpcClient({ cliPath, @@ -644,7 +674,9 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P subscriptionId = result?.subscription ?? null; await client.waitForClose(); } catch (err) { - if (abort?.aborted) return; + if (abort?.aborted) { + return; + } runtime.error?.(danger(`imessage: monitor failed: ${String(err)}`)); throw err; } finally { diff --git a/src/imessage/probe.ts b/src/imessage/probe.ts index b81a6f7f35..7eb37c1b5d 100644 --- a/src/imessage/probe.ts +++ b/src/imessage/probe.ts @@ -26,7 +26,9 @@ const rpcSupportCache = new Map(); async function probeRpcSupport(cliPath: string): Promise { const cached = rpcSupportCache.get(cliPath); - if (cached) return cached; + if (cached) { + return cached; + } try { const result = await runCommandWithTimeout([cliPath, "rpc", "--help"], { timeoutMs: 2000 }); const combined = `${result.stdout}\n${result.stderr}`.trim(); diff --git a/src/imessage/send.ts b/src/imessage/send.ts index a579385f6f..62d3422d9a 100644 --- a/src/imessage/send.ts +++ b/src/imessage/send.ts @@ -26,7 +26,9 @@ export type IMessageSendResult = { }; function resolveMessageId(result: Record | null | undefined): string | null { - if (!result) return null; + if (!result) { + return null; + } const raw = (typeof result.messageId === "string" && result.messageId.trim()) || (typeof result.message_id === "string" && result.message_id.trim()) || @@ -83,7 +85,9 @@ export async function sendMessageIMessage( filePath = resolved.path; if (!message.trim()) { const kind = mediaKindFromMime(resolved.contentType ?? undefined); - if (kind) message = kind === "image" ? "" : ``; + if (kind) { + message = kind === "image" ? "" : ``; + } } } @@ -104,7 +108,9 @@ export async function sendMessageIMessage( service: service || "auto", region, }; - if (filePath) params.file = filePath; + if (filePath) { + params.file = filePath; + } if (target.kind === "chat_id") { params.chat_id = target.chatId; diff --git a/src/imessage/targets.ts b/src/imessage/targets.ts index 03fdcf3064..3819e1f931 100644 --- a/src/imessage/targets.ts +++ b/src/imessage/targets.ts @@ -29,11 +29,19 @@ function stripPrefix(value: string, prefix: string): string { export function normalizeIMessageHandle(raw: string): string { const trimmed = raw.trim(); - if (!trimmed) return ""; + if (!trimmed) { + return ""; + } const lowered = trimmed.toLowerCase(); - if (lowered.startsWith("imessage:")) return normalizeIMessageHandle(trimmed.slice(9)); - if (lowered.startsWith("sms:")) return normalizeIMessageHandle(trimmed.slice(4)); - if (lowered.startsWith("auto:")) return normalizeIMessageHandle(trimmed.slice(5)); + if (lowered.startsWith("imessage:")) { + return normalizeIMessageHandle(trimmed.slice(9)); + } + if (lowered.startsWith("sms:")) { + return normalizeIMessageHandle(trimmed.slice(4)); + } + if (lowered.startsWith("auto:")) { + return normalizeIMessageHandle(trimmed.slice(5)); + } // Normalize chat_id/chat_guid/chat_identifier prefixes case-insensitively for (const prefix of CHAT_ID_PREFIXES) { @@ -55,21 +63,29 @@ export function normalizeIMessageHandle(raw: string): string { } } - if (trimmed.includes("@")) return trimmed.toLowerCase(); + if (trimmed.includes("@")) { + return trimmed.toLowerCase(); + } const normalized = normalizeE164(trimmed); - if (normalized) return normalized; + if (normalized) { + return normalized; + } return trimmed.replace(/\s+/g, ""); } export function parseIMessageTarget(raw: string): IMessageTarget { const trimmed = raw.trim(); - if (!trimmed) throw new Error("iMessage target is required"); + if (!trimmed) { + throw new Error("iMessage target is required"); + } const lower = trimmed.toLowerCase(); for (const { prefix, service } of SERVICE_PREFIXES) { if (lower.startsWith(prefix)) { const remainder = stripPrefix(trimmed, prefix); - if (!remainder) throw new Error(`${prefix} target is required`); + if (!remainder) { + throw new Error(`${prefix} target is required`); + } const remainderLower = remainder.toLowerCase(); const isChatTarget = CHAT_ID_PREFIXES.some((p) => remainderLower.startsWith(p)) || @@ -96,7 +112,9 @@ export function parseIMessageTarget(raw: string): IMessageTarget { for (const prefix of CHAT_GUID_PREFIXES) { if (lower.startsWith(prefix)) { const value = stripPrefix(trimmed, prefix); - if (!value) throw new Error("chat_guid is required"); + if (!value) { + throw new Error("chat_guid is required"); + } return { kind: "chat_guid", chatGuid: value }; } } @@ -104,7 +122,9 @@ export function parseIMessageTarget(raw: string): IMessageTarget { for (const prefix of CHAT_IDENTIFIER_PREFIXES) { if (lower.startsWith(prefix)) { const value = stripPrefix(trimmed, prefix); - if (!value) throw new Error("chat_identifier is required"); + if (!value) { + throw new Error("chat_identifier is required"); + } return { kind: "chat_identifier", chatIdentifier: value }; } } @@ -114,13 +134,17 @@ export function parseIMessageTarget(raw: string): IMessageTarget { export function parseIMessageAllowTarget(raw: string): IMessageAllowTarget { const trimmed = raw.trim(); - if (!trimmed) return { kind: "handle", handle: "" }; + if (!trimmed) { + return { kind: "handle", handle: "" }; + } const lower = trimmed.toLowerCase(); for (const { prefix } of SERVICE_PREFIXES) { if (lower.startsWith(prefix)) { const remainder = stripPrefix(trimmed, prefix); - if (!remainder) return { kind: "handle", handle: "" }; + if (!remainder) { + return { kind: "handle", handle: "" }; + } return parseIMessageAllowTarget(remainder); } } @@ -129,21 +153,27 @@ export function parseIMessageAllowTarget(raw: string): IMessageAllowTarget { if (lower.startsWith(prefix)) { const value = stripPrefix(trimmed, prefix); const chatId = Number.parseInt(value, 10); - if (Number.isFinite(chatId)) return { kind: "chat_id", chatId }; + if (Number.isFinite(chatId)) { + return { kind: "chat_id", chatId }; + } } } for (const prefix of CHAT_GUID_PREFIXES) { if (lower.startsWith(prefix)) { const value = stripPrefix(trimmed, prefix); - if (value) return { kind: "chat_guid", chatGuid: value }; + if (value) { + return { kind: "chat_guid", chatGuid: value }; + } } } for (const prefix of CHAT_IDENTIFIER_PREFIXES) { if (lower.startsWith(prefix)) { const value = stripPrefix(trimmed, prefix); - if (value) return { kind: "chat_identifier", chatIdentifier: value }; + if (value) { + return { kind: "chat_identifier", chatIdentifier: value }; + } } } @@ -158,8 +188,12 @@ export function isAllowedIMessageSender(params: { chatIdentifier?: string | null; }): boolean { const allowFrom = params.allowFrom.map((entry) => String(entry).trim()); - if (allowFrom.length === 0) return true; - if (allowFrom.includes("*")) return true; + if (allowFrom.length === 0) { + return true; + } + if (allowFrom.includes("*")) { + return true; + } const senderNormalized = normalizeIMessageHandle(params.sender); const chatId = params.chatId ?? undefined; @@ -167,22 +201,34 @@ export function isAllowedIMessageSender(params: { const chatIdentifier = params.chatIdentifier?.trim(); for (const entry of allowFrom) { - if (!entry) continue; + if (!entry) { + continue; + } const parsed = parseIMessageAllowTarget(entry); if (parsed.kind === "chat_id" && chatId !== undefined) { - if (parsed.chatId === chatId) return true; + if (parsed.chatId === chatId) { + return true; + } } else if (parsed.kind === "chat_guid" && chatGuid) { - if (parsed.chatGuid === chatGuid) return true; + if (parsed.chatGuid === chatGuid) { + return true; + } } else if (parsed.kind === "chat_identifier" && chatIdentifier) { - if (parsed.chatIdentifier === chatIdentifier) return true; + if (parsed.chatIdentifier === chatIdentifier) { + return true; + } } else if (parsed.kind === "handle" && senderNormalized) { - if (parsed.handle === senderNormalized) return true; + if (parsed.handle === senderNormalized) { + return true; + } } } return false; } export function formatIMessageChatTarget(chatId?: number | null): string { - if (!chatId || !Number.isFinite(chatId)) return ""; + if (!chatId || !Number.isFinite(chatId)) { + return ""; + } return `chat_id:${chatId}`; } diff --git a/src/infra/agent-events.test.ts b/src/infra/agent-events.test.ts index 1ce051bdb4..f86425894f 100644 --- a/src/infra/agent-events.test.ts +++ b/src/infra/agent-events.test.ts @@ -39,9 +39,15 @@ describe("agent-events sequencing", () => { test("preserves compaction ordering on the event bus", async () => { const phases: Array = []; const stop = onAgentEvent((evt) => { - if (evt.runId !== "run-1") return; - if (evt.stream !== "compaction") return; - if (typeof evt.data?.phase === "string") phases.push(evt.data.phase); + if (evt.runId !== "run-1") { + return; + } + if (evt.stream !== "compaction") { + return; + } + if (typeof evt.data?.phase === "string") { + phases.push(evt.data.phase); + } }); emitAgentEvent({ runId: "run-1", stream: "compaction", data: { phase: "start" } }); diff --git a/src/infra/agent-events.ts b/src/infra/agent-events.ts index 5c41c3c95c..23557cdda6 100644 --- a/src/infra/agent-events.ts +++ b/src/infra/agent-events.ts @@ -23,7 +23,9 @@ const listeners = new Set<(evt: AgentEventPayload) => void>(); const runContextById = new Map(); export function registerAgentRunContext(runId: string, context: AgentRunContext) { - if (!runId) return; + if (!runId) { + return; + } const existing = runContextById.get(runId); if (!existing) { runContextById.set(runId, { ...context }); diff --git a/src/infra/archive.ts b/src/infra/archive.ts index 35ad4fa042..64133b7200 100644 --- a/src/infra/archive.ts +++ b/src/infra/archive.ts @@ -14,8 +14,12 @@ const TAR_SUFFIXES = [".tgz", ".tar.gz", ".tar"]; export function resolveArchiveKind(filePath: string): ArchiveKind | null { const lower = filePath.toLowerCase(); - if (lower.endsWith(".zip")) return "zip"; - if (TAR_SUFFIXES.some((suffix) => lower.endsWith(suffix))) return "tar"; + if (lower.endsWith(".zip")) { + return "zip"; + } + if (TAR_SUFFIXES.some((suffix) => lower.endsWith(suffix))) { + return "tar"; + } return null; } @@ -23,7 +27,9 @@ export async function resolvePackedRootDir(extractDir: string): Promise const direct = path.join(extractDir, "package"); try { const stat = await fs.stat(direct); - if (stat.isDirectory()) return direct; + if (stat.isDirectory()) { + return direct; + } } catch { // ignore } @@ -57,7 +63,9 @@ export async function withTimeout( }), ]); } finally { - if (timeoutId) clearTimeout(timeoutId); + if (timeoutId) { + clearTimeout(timeoutId); + } } } diff --git a/src/infra/backoff.ts b/src/infra/backoff.ts index 215b66cb96..153eca1620 100644 --- a/src/infra/backoff.ts +++ b/src/infra/backoff.ts @@ -14,7 +14,9 @@ export function computeBackoff(policy: BackoffPolicy, attempt: number) { } export async function sleepWithAbort(ms: number, abortSignal?: AbortSignal) { - if (ms <= 0) return; + if (ms <= 0) { + return; + } try { await delay(ms, undefined, { signal: abortSignal }); } catch (err) { diff --git a/src/infra/bonjour-discovery.test.ts b/src/infra/bonjour-discovery.test.ts index 7d0df4c4fd..b3d859f53f 100644 --- a/src/infra/bonjour-discovery.test.ts +++ b/src/infra/bonjour-discovery.test.ts @@ -108,7 +108,9 @@ describe("bonjour-discovery", () => { it("decodes dns-sd octal escapes in TXT displayName", async () => { const run = vi.fn(async (argv: string[], options: { timeoutMs: number }) => { - if (options.timeoutMs < 0) throw new Error("invalid timeout"); + if (options.timeoutMs < 0) { + throw new Error("invalid timeout"); + } const domain = argv[3] ?? ""; if (argv[0] === "dns-sd" && argv[1] === "-B" && domain === "local.") { diff --git a/src/infra/bonjour-discovery.ts b/src/infra/bonjour-discovery.ts index 8f3a59d7b8..f0ee296156 100644 --- a/src/infra/bonjour-discovery.ts +++ b/src/infra/bonjour-discovery.ts @@ -36,7 +36,9 @@ function decodeDnsSdEscapes(value: string): string { let pending = ""; const flush = () => { - if (!pending) return; + if (!pending) { + return; + } bytes.push(...Buffer.from(pending, "utf8")); pending = ""; }; @@ -61,16 +63,22 @@ function decodeDnsSdEscapes(value: string): string { pending += ch; } - if (!decoded) return value; + if (!decoded) { + return value; + } flush(); return Buffer.from(bytes).toString("utf8"); } function isTailnetIPv4(address: string): boolean { const parts = address.split("."); - if (parts.length !== 4) return false; + if (parts.length !== 4) { + return false; + } const octets = parts.map((p) => Number.parseInt(p, 10)); - if (octets.some((n) => !Number.isFinite(n) || n < 0 || n > 255)) return false; + if (octets.some((n) => !Number.isFinite(n) || n < 0 || n > 255)) { + return false; + } // Tailscale IPv4 range: 100.64.0.0/10 const [a, b] = octets; return a === 100 && b >= 64 && b <= 127; @@ -89,7 +97,9 @@ function parseDigTxt(stdout: string): string[] { const tokens: string[] = []; for (const raw of stdout.split("\n")) { const line = raw.trim(); - if (!line) continue; + if (!line) { + continue; + } const matches = Array.from(line.matchAll(/"([^"]*)"/g), (m) => m[1] ?? ""); for (const m of matches) { const unescaped = m.replaceAll("\\\\", "\\").replaceAll('\\"', '"').replaceAll("\\n", "\n"); @@ -105,14 +115,22 @@ function parseDigSrv(stdout: string): { host: string; port: number } | null { .split("\n") .map((l) => l.trim()) .find(Boolean); - if (!line) return null; + if (!line) { + return null; + } const parts = line.split(/\s+/).filter(Boolean); - if (parts.length < 4) return null; + if (parts.length < 4) { + return null; + } const port = Number.parseInt(parts[2] ?? "", 10); const hostRaw = parts[3] ?? ""; - if (!Number.isFinite(port) || port <= 0) return null; + if (!Number.isFinite(port) || port <= 0) { + return null; + } const host = hostRaw.replace(/\.$/, ""); - if (!host) return null; + if (!host) { + return null; + } return { host, port }; } @@ -121,13 +139,21 @@ function parseTailscaleStatusIPv4s(stdout: string): string[] { const out: string[] = []; const addIps = (value: unknown) => { - if (!value || typeof value !== "object") return; + if (!value || typeof value !== "object") { + return; + } const ips = (value as { TailscaleIPs?: unknown }).TailscaleIPs; - if (!Array.isArray(ips)) return; + if (!Array.isArray(ips)) { + return; + } for (const ip of ips) { - if (typeof ip !== "string") continue; + if (typeof ip !== "string") { + continue; + } const trimmed = ip.trim(); - if (trimmed && isTailnetIPv4(trimmed)) out.push(trimmed); + if (trimmed && isTailnetIPv4(trimmed)) { + out.push(trimmed); + } } }; @@ -144,7 +170,9 @@ function parseTailscaleStatusIPv4s(stdout: string): string[] { } function parseIntOrNull(value: string | undefined): number | undefined { - if (!value) return undefined; + if (!value) { + return undefined; + } const parsed = Number.parseInt(value, 10); return Number.isFinite(parsed) ? parsed : undefined; } @@ -153,10 +181,14 @@ function parseTxtTokens(tokens: string[]): Record { const txt: Record = {}; for (const token of tokens) { const idx = token.indexOf("="); - if (idx <= 0) continue; + if (idx <= 0) { + continue; + } const key = token.slice(0, idx).trim(); const value = decodeDnsSdEscapes(token.slice(idx + 1).trim()); - if (!key) continue; + if (!key) { + continue; + } txt[key] = value; } return txt; @@ -166,8 +198,12 @@ function parseDnsSdBrowse(stdout: string): string[] { const instances = new Set(); for (const raw of stdout.split("\n")) { const line = raw.trim(); - if (!line || !line.includes(GATEWAY_SERVICE_TYPE)) continue; - if (!line.includes("Add")) continue; + if (!line || !line.includes(GATEWAY_SERVICE_TYPE)) { + continue; + } + if (!line.includes("Add")) { + continue; + } const match = line.match(/_openclaw-gw\._tcp\.?\s+(.+)$/); if (match?.[1]) { instances.add(decodeDnsSdEscapes(match[1].trim())); @@ -182,7 +218,9 @@ function parseDnsSdResolve(stdout: string, instanceName: string): GatewayBonjour let txt: Record = {}; for (const raw of stdout.split("\n")) { const line = raw.trim(); - if (!line) continue; + if (!line) { + continue; + } if (line.includes("can be reached at")) { const match = line.match(/can be reached at\s+([^\s:]+):(\d+)/i); @@ -202,21 +240,37 @@ function parseDnsSdResolve(stdout: string, instanceName: string): GatewayBonjour } beacon.txt = Object.keys(txt).length ? txt : undefined; - if (txt.displayName) beacon.displayName = decodeDnsSdEscapes(txt.displayName); - if (txt.lanHost) beacon.lanHost = txt.lanHost; - if (txt.tailnetDns) beacon.tailnetDns = txt.tailnetDns; - if (txt.cliPath) beacon.cliPath = txt.cliPath; + if (txt.displayName) { + beacon.displayName = decodeDnsSdEscapes(txt.displayName); + } + if (txt.lanHost) { + beacon.lanHost = txt.lanHost; + } + if (txt.tailnetDns) { + beacon.tailnetDns = txt.tailnetDns; + } + if (txt.cliPath) { + beacon.cliPath = txt.cliPath; + } beacon.gatewayPort = parseIntOrNull(txt.gatewayPort); beacon.sshPort = parseIntOrNull(txt.sshPort); if (txt.gatewayTls) { const raw = txt.gatewayTls.trim().toLowerCase(); beacon.gatewayTls = raw === "1" || raw === "true" || raw === "yes"; } - if (txt.gatewayTlsSha256) beacon.gatewayTlsFingerprintSha256 = txt.gatewayTlsSha256; - if (txt.role) beacon.role = txt.role; - if (txt.transport) beacon.transport = txt.transport; + if (txt.gatewayTlsSha256) { + beacon.gatewayTlsFingerprintSha256 = txt.gatewayTlsSha256; + } + if (txt.role) { + beacon.role = txt.role; + } + if (txt.transport) { + beacon.transport = txt.transport; + } - if (!beacon.displayName) beacon.displayName = decodedInstanceName; + if (!beacon.displayName) { + beacon.displayName = decodedInstanceName; + } return beacon; } @@ -235,7 +289,9 @@ async function discoverViaDnsSd( timeoutMs, }); const parsed = parseDnsSdResolve(resolved.stdout, instance); - if (parsed) results.push({ ...parsed, domain }); + if (parsed) { + results.push({ ...parsed, domain }); + } } return results; } @@ -245,7 +301,9 @@ async function discoverWideAreaViaTailnetDns( timeoutMs: number, run: typeof runCommandWithTimeout, ): Promise { - if (!domain || domain === "local.") return []; + if (!domain || domain === "local.") { + return []; + } const startedAt = Date.now(); const remainingMs = () => timeoutMs - (Date.now() - startedAt); @@ -257,13 +315,19 @@ async function discoverWideAreaViaTailnetDns( timeoutMs: Math.max(1, Math.min(700, remainingMs())), }); ips = parseTailscaleStatusIPv4s(res.stdout); - if (ips.length > 0) break; + if (ips.length > 0) { + break; + } } catch { // ignore } } - if (ips.length === 0) return []; - if (remainingMs() <= 0) return []; + if (ips.length === 0) { + return []; + } + if (remainingMs() <= 0) { + return []; + } // Keep scans bounded: this is a fallback and should not block long. ips = ips.slice(0, 40); @@ -278,19 +342,27 @@ async function discoverWideAreaViaTailnetDns( const worker = async () => { while (nameserver === null) { const budget = remainingMs(); - if (budget <= 0) return; + if (budget <= 0) { + return; + } const i = nextIndex; nextIndex += 1; - if (i >= ips.length) return; + if (i >= ips.length) { + return; + } const ip = ips[i] ?? ""; - if (!ip) continue; + if (!ip) { + continue; + } try { const probe = await run( ["dig", "+short", "+time=1", "+tries=1", `@${ip}`, probeName, "PTR"], { timeoutMs: Math.max(1, Math.min(250, budget)) }, ); const lines = parseDigShortLines(probe.stdout); - if (lines.length === 0) continue; + if (lines.length === 0) { + continue; + } nameserver = ip; ptrs = lines; return; @@ -302,23 +374,33 @@ async function discoverWideAreaViaTailnetDns( await Promise.all(Array.from({ length: Math.min(concurrency, ips.length) }, () => worker())); - if (!nameserver || ptrs.length === 0) return []; - if (remainingMs() <= 0) return []; + if (!nameserver || ptrs.length === 0) { + return []; + } + if (remainingMs() <= 0) { + return []; + } const nameserverArg = `@${String(nameserver)}`; const results: GatewayBonjourBeacon[] = []; for (const ptr of ptrs) { const budget = remainingMs(); - if (budget <= 0) break; + if (budget <= 0) { + break; + } const ptrName = ptr.trim().replace(/\.$/, ""); - if (!ptrName) continue; + if (!ptrName) { + continue; + } const instanceName = ptrName.replace(/\.?_openclaw-gw\._tcp\..*$/, ""); const srv = await run(["dig", "+short", "+time=1", "+tries=1", nameserverArg, ptrName, "SRV"], { timeoutMs: Math.max(1, Math.min(350, budget)), }).catch(() => null); const srvParsed = srv ? parseDigSrv(srv.stdout) : null; - if (!srvParsed) continue; + if (!srvParsed) { + continue; + } const txtBudget = remainingMs(); if (txtBudget <= 0) { @@ -354,9 +436,15 @@ async function discoverWideAreaViaTailnetDns( const raw = txtMap.gatewayTls.trim().toLowerCase(); beacon.gatewayTls = raw === "1" || raw === "true" || raw === "yes"; } - if (txtMap.gatewayTlsSha256) beacon.gatewayTlsFingerprintSha256 = txtMap.gatewayTlsSha256; - if (txtMap.role) beacon.role = txtMap.role; - if (txtMap.transport) beacon.transport = txtMap.transport; + if (txtMap.gatewayTlsSha256) { + beacon.gatewayTlsFingerprintSha256 = txtMap.gatewayTlsSha256; + } + if (txtMap.role) { + beacon.role = txtMap.role; + } + if (txtMap.transport) { + beacon.transport = txtMap.transport; + } results.push(beacon); } @@ -370,9 +458,13 @@ function parseAvahiBrowse(stdout: string): GatewayBonjourBeacon[] { for (const raw of stdout.split("\n")) { const line = raw.trimEnd(); - if (!line) continue; + if (!line) { + continue; + } if (line.startsWith("=") && line.includes(GATEWAY_SERVICE_TYPE)) { - if (current) results.push(current); + if (current) { + results.push(current); + } const marker = ` ${GATEWAY_SERVICE_TYPE}`; const idx = line.indexOf(marker); const left = idx >= 0 ? line.slice(0, idx).trim() : line; @@ -385,18 +477,24 @@ function parseAvahiBrowse(stdout: string): GatewayBonjourBeacon[] { continue; } - if (!current) continue; + if (!current) { + continue; + } const trimmed = line.trim(); if (trimmed.startsWith("hostname =")) { const match = trimmed.match(/hostname\s*=\s*\[([^\]]+)\]/); - if (match?.[1]) current.host = match[1]; + if (match?.[1]) { + current.host = match[1]; + } continue; } if (trimmed.startsWith("port =")) { const match = trimmed.match(/port\s*=\s*\[(\d+)\]/); - if (match?.[1]) current.port = parseIntOrNull(match[1]); + if (match?.[1]) { + current.port = parseIntOrNull(match[1]); + } continue; } @@ -404,23 +502,39 @@ function parseAvahiBrowse(stdout: string): GatewayBonjourBeacon[] { const tokens = Array.from(trimmed.matchAll(/"([^"]*)"/g), (m) => m[1]); const txt = parseTxtTokens(tokens); current.txt = Object.keys(txt).length ? txt : undefined; - if (txt.displayName) current.displayName = txt.displayName; - if (txt.lanHost) current.lanHost = txt.lanHost; - if (txt.tailnetDns) current.tailnetDns = txt.tailnetDns; - if (txt.cliPath) current.cliPath = txt.cliPath; + if (txt.displayName) { + current.displayName = txt.displayName; + } + if (txt.lanHost) { + current.lanHost = txt.lanHost; + } + if (txt.tailnetDns) { + current.tailnetDns = txt.tailnetDns; + } + if (txt.cliPath) { + current.cliPath = txt.cliPath; + } current.gatewayPort = parseIntOrNull(txt.gatewayPort); current.sshPort = parseIntOrNull(txt.sshPort); if (txt.gatewayTls) { const raw = txt.gatewayTls.trim().toLowerCase(); current.gatewayTls = raw === "1" || raw === "true" || raw === "yes"; } - if (txt.gatewayTlsSha256) current.gatewayTlsFingerprintSha256 = txt.gatewayTlsSha256; - if (txt.role) current.role = txt.role; - if (txt.transport) current.transport = txt.transport; + if (txt.gatewayTlsSha256) { + current.gatewayTlsFingerprintSha256 = txt.gatewayTlsSha256; + } + if (txt.role) { + current.role = txt.role; + } + if (txt.transport) { + current.transport = txt.transport; + } } } - if (current) results.push(current); + if (current) { + results.push(current); + } return results; } diff --git a/src/infra/bonjour.test.ts b/src/infra/bonjour.test.ts index 5f53e7a961..6057fb57c9 100644 --- a/src/infra/bonjour.test.ts +++ b/src/infra/bonjour.test.ts @@ -65,7 +65,9 @@ describe("gateway bonjour advertiser", () => { afterEach(() => { for (const key of Object.keys(process.env)) { - if (!(key in prevEnv)) delete process.env[key]; + if (!(key in prevEnv)) { + delete process.env[key]; + } } for (const [key, value] of Object.entries(prevEnv)) { process.env[key] = value; diff --git a/src/infra/bonjour.ts b/src/infra/bonjour.ts index 8efa4fb829..7d405741a0 100644 --- a/src/infra/bonjour.ts +++ b/src/infra/bonjour.ts @@ -26,9 +26,15 @@ export type GatewayBonjourAdvertiseOpts = { }; function isDisabledByEnv() { - if (isTruthyEnvValue(process.env.OPENCLAW_DISABLE_BONJOUR)) return true; - if (process.env.NODE_ENV === "test") return true; - if (process.env.VITEST) return true; + if (isTruthyEnvValue(process.env.OPENCLAW_DISABLE_BONJOUR)) { + return true; + } + if (process.env.NODE_ENV === "test") { + return true; + } + if (process.env.VITEST) { + return true; + } return false; } @@ -212,8 +218,12 @@ export async function startGatewayBonjourAdvertiser( const watchdog = setInterval(() => { for (const { label, svc } of services) { const stateUnknown = (svc as { serviceState?: unknown }).serviceState; - if (typeof stateUnknown !== "string") continue; - if (stateUnknown === "announced" || stateUnknown === "announcing") continue; + if (typeof stateUnknown !== "string") { + continue; + } + if (stateUnknown === "announced" || stateUnknown === "announcing") { + continue; + } let key = label; try { @@ -223,7 +233,9 @@ export async function startGatewayBonjourAdvertiser( } const now = Date.now(); const last = lastRepairAttempt.get(key) ?? 0; - if (now - last < 30_000) continue; + if (now - last < 30_000) { + continue; + } lastRepairAttempt.set(key, now); logWarn( diff --git a/src/infra/brew.ts b/src/infra/brew.ts index 9155729c24..0b600ee7cd 100644 --- a/src/infra/brew.ts +++ b/src/infra/brew.ts @@ -12,7 +12,9 @@ function isExecutable(filePath: string): boolean { } function normalizePathValue(value: unknown): string | undefined { - if (typeof value !== "string") return undefined; + if (typeof value !== "string") { + return undefined; + } const trimmed = value.trim(); return trimmed ? trimmed : undefined; } @@ -51,10 +53,14 @@ export function resolveBrewExecutable(opts?: { const candidates: string[] = []; const brewFile = normalizePathValue(env.HOMEBREW_BREW_FILE); - if (brewFile) candidates.push(brewFile); + if (brewFile) { + candidates.push(brewFile); + } const prefix = normalizePathValue(env.HOMEBREW_PREFIX); - if (prefix) candidates.push(path.join(prefix, "bin", "brew")); + if (prefix) { + candidates.push(path.join(prefix, "bin", "brew")); + } // Linuxbrew defaults. candidates.push(path.join(homeDir, ".linuxbrew", "bin", "brew")); @@ -64,7 +70,9 @@ export function resolveBrewExecutable(opts?: { candidates.push("/opt/homebrew/bin/brew", "/usr/local/bin/brew"); for (const candidate of candidates) { - if (isExecutable(candidate)) return candidate; + if (isExecutable(candidate)) { + return candidate; + } } return undefined; diff --git a/src/infra/canvas-host-url.ts b/src/infra/canvas-host-url.ts index 47fc50742d..fe537bb8ed 100644 --- a/src/infra/canvas-host-url.ts +++ b/src/infra/canvas-host-url.ts @@ -11,23 +11,39 @@ type CanvasHostUrlParams = { const isLoopbackHost = (value: string) => { const normalized = value.trim().toLowerCase(); - if (!normalized) return false; - if (normalized === "localhost") return true; - if (normalized === "::1") return true; - if (normalized === "0.0.0.0" || normalized === "::") return true; + if (!normalized) { + return false; + } + if (normalized === "localhost") { + return true; + } + if (normalized === "::1") { + return true; + } + if (normalized === "0.0.0.0" || normalized === "::") { + return true; + } return normalized.startsWith("127."); }; const normalizeHost = (value: HostSource, rejectLoopback: boolean) => { - if (!value) return ""; + if (!value) { + return ""; + } const trimmed = value.trim(); - if (!trimmed) return ""; - if (rejectLoopback && isLoopbackHost(trimmed)) return ""; + if (!trimmed) { + return ""; + } + if (rejectLoopback && isLoopbackHost(trimmed)) { + return ""; + } return trimmed; }; const parseHostHeader = (value: HostSource) => { - if (!value) return ""; + if (!value) { + return ""; + } try { return new URL(`http://${String(value).trim()}`).hostname; } catch { @@ -36,13 +52,17 @@ const parseHostHeader = (value: HostSource) => { }; const parseForwardedProto = (value: HostSource | HostSource[]) => { - if (Array.isArray(value)) return value[0]; + if (Array.isArray(value)) { + return value[0]; + } return value; }; export function resolveCanvasHostUrl(params: CanvasHostUrlParams) { const port = params.canvasPort; - if (!port) return undefined; + if (!port) { + return undefined; + } const scheme = params.scheme ?? @@ -53,7 +73,9 @@ export function resolveCanvasHostUrl(params: CanvasHostUrlParams) { const localAddress = normalizeHost(params.localAddress, Boolean(override || requestHost)); const host = override || requestHost || localAddress; - if (!host) return undefined; + if (!host) { + return undefined; + } const formatted = host.includes(":") ? `[${host}]` : host; return `${scheme}://${formatted}:${port}`; } diff --git a/src/infra/channel-activity.ts b/src/infra/channel-activity.ts index e56757256f..f1365548a9 100644 --- a/src/infra/channel-activity.ts +++ b/src/infra/channel-activity.ts @@ -15,7 +15,9 @@ function keyFor(channel: ChannelId, accountId: string) { function ensureEntry(channel: ChannelId, accountId: string): ActivityEntry { const key = keyFor(channel, accountId); const existing = activity.get(key); - if (existing) return existing; + if (existing) { + return existing; + } const created: ActivityEntry = { inboundAt: null, outboundAt: null }; activity.set(key, created); return created; @@ -30,8 +32,12 @@ export function recordChannelActivity(params: { const at = typeof params.at === "number" ? params.at : Date.now(); const accountId = params.accountId?.trim() || "default"; const entry = ensureEntry(params.channel, accountId); - if (params.direction === "inbound") entry.inboundAt = at; - if (params.direction === "outbound") entry.outboundAt = at; + if (params.direction === "inbound") { + entry.inboundAt = at; + } + if (params.direction === "outbound") { + entry.outboundAt = at; + } } export function getChannelActivity(params: { diff --git a/src/infra/channel-summary.ts b/src/infra/channel-summary.ts index 1d099868b5..8701cea400 100644 --- a/src/infra/channel-summary.ts +++ b/src/infra/channel-summary.ts @@ -24,7 +24,9 @@ type ChannelAccountEntry = { const formatAccountLabel = (params: { accountId: string; name?: string }) => { const base = params.accountId || DEFAULT_ACCOUNT_ID; - if (params.name?.trim()) return `${base} (${params.name.trim()})`; + if (params.name?.trim()) { + return `${base} (${params.name.trim()})`; + } return base; }; @@ -39,7 +41,9 @@ const resolveAccountEnabled = ( if (plugin.config.isEnabled) { return plugin.config.isEnabled(account, cfg); } - if (!account || typeof account !== "object") return true; + if (!account || typeof account !== "object") { + return true; + } const enabled = (account as { enabled?: boolean }).enabled; return enabled !== false; }; @@ -98,8 +102,12 @@ const buildAccountDetails = (params: { }): string[] => { const details: string[] = []; const snapshot = params.entry.snapshot; - if (snapshot.enabled === false) details.push("disabled"); - if (snapshot.dmPolicy) details.push(`dm:${snapshot.dmPolicy}`); + if (snapshot.enabled === false) { + details.push("disabled"); + } + if (snapshot.dmPolicy) { + details.push(`dm:${snapshot.dmPolicy}`); + } if (snapshot.tokenSource && snapshot.tokenSource !== "none") { details.push(`token:${snapshot.tokenSource}`); } @@ -109,10 +117,18 @@ const buildAccountDetails = (params: { if (snapshot.appTokenSource && snapshot.appTokenSource !== "none") { details.push(`app:${snapshot.appTokenSource}`); } - if (snapshot.baseUrl) details.push(snapshot.baseUrl); - if (snapshot.port != null) details.push(`port:${snapshot.port}`); - if (snapshot.cliPath) details.push(`cli:${snapshot.cliPath}`); - if (snapshot.dbPath) details.push(`db:${snapshot.dbPath}`); + if (snapshot.baseUrl) { + details.push(snapshot.baseUrl); + } + if (snapshot.port != null) { + details.push(`port:${snapshot.port}`); + } + if (snapshot.cliPath) { + details.push(`cli:${snapshot.cliPath}`); + } + if (snapshot.dbPath) { + details.push(`db:${snapshot.dbPath}`); + } if (params.includeAllowFrom && snapshot.allowFrom?.length) { const formatted = formatAllowFrom({ @@ -204,7 +220,9 @@ export async function buildChannelSummary( const authAgeMs = summaryRecord && typeof summaryRecord.authAgeMs === "number" ? summaryRecord.authAgeMs : null; const self = summaryRecord?.self as { e164?: string | null } | undefined; - if (self?.e164) line += ` ${self.e164}`; + if (self?.e164) { + line += ` ${self.e164}`; + } if (authAgeMs != null && authAgeMs >= 0) { line += ` auth ${formatAge(authAgeMs)}`; } @@ -236,12 +254,20 @@ export async function buildChannelSummary( } export function formatAge(ms: number): string { - if (ms < 0) return "unknown"; + if (ms < 0) { + return "unknown"; + } const minutes = Math.round(ms / 60_000); - if (minutes < 1) return "just now"; - if (minutes < 60) return `${minutes}m ago`; + if (minutes < 1) { + return "just now"; + } + if (minutes < 60) { + return `${minutes}m ago`; + } const hours = Math.round(minutes / 60); - if (hours < 48) return `${hours}h ago`; + if (hours < 48) { + return `${hours}h ago`; + } const days = Math.round(hours / 24); return `${days}d ago`; } diff --git a/src/infra/channels-status-issues.ts b/src/infra/channels-status-issues.ts index 61ed9d76ea..6ec5d19672 100644 --- a/src/infra/channels-status-issues.ts +++ b/src/infra/channels-status-issues.ts @@ -6,9 +6,13 @@ export function collectChannelStatusIssues(payload: Record): Ch const accountsByChannel = payload.channelAccounts as Record | undefined; for (const plugin of listChannelPlugins()) { const collect = plugin.status?.collectStatusIssues; - if (!collect) continue; + if (!collect) { + continue; + } const raw = accountsByChannel?.[plugin.id]; - if (!Array.isArray(raw)) continue; + if (!Array.isArray(raw)) { + continue; + } issues.push(...collect(raw as ChannelAccountSnapshot[])); } diff --git a/src/infra/clipboard.ts b/src/infra/clipboard.ts index dd8aaf5102..c7daebf22d 100644 --- a/src/infra/clipboard.ts +++ b/src/infra/clipboard.ts @@ -14,7 +14,9 @@ export async function copyToClipboard(value: string): Promise { timeoutMs: 3_000, input: value, }); - if (result.code === 0 && !result.killed) return true; + if (result.code === 0 && !result.killed) { + return true; + } } catch { // keep trying the next fallback } diff --git a/src/infra/control-ui-assets.ts b/src/infra/control-ui-assets.ts index 423be3d572..867485f5cd 100644 --- a/src/infra/control-ui-assets.ts +++ b/src/infra/control-ui-assets.ts @@ -8,13 +8,17 @@ import { resolveOpenClawPackageRoot } from "./openclaw-root.js"; export function resolveControlUiRepoRoot( argv1: string | undefined = process.argv[1], ): string | null { - if (!argv1) return null; + if (!argv1) { + return null; + } const normalized = path.resolve(argv1); const parts = normalized.split(path.sep); const srcIndex = parts.lastIndexOf("src"); if (srcIndex !== -1) { const root = parts.slice(0, srcIndex).join(path.sep); - if (fs.existsSync(path.join(root, "ui", "vite.config.ts"))) return root; + if (fs.existsSync(path.join(root, "ui", "vite.config.ts"))) { + return root; + } } let dir = path.dirname(normalized); @@ -26,7 +30,9 @@ export function resolveControlUiRepoRoot( return dir; } const parent = path.dirname(dir); - if (parent === dir) break; + if (parent === dir) { + break; + } dir = parent; } @@ -36,7 +42,9 @@ export function resolveControlUiRepoRoot( export async function resolveControlUiDistIndexPath( argv1: string | undefined = process.argv[1], ): Promise { - if (!argv1) return null; + if (!argv1) { + return null; + } const normalized = path.resolve(argv1); // Case 1: entrypoint is directly inside dist/ (e.g., dist/entry.js) @@ -46,7 +54,9 @@ export async function resolveControlUiDistIndexPath( } const packageRoot = await resolveOpenClawPackageRoot({ argv1: normalized }); - if (!packageRoot) return null; + if (!packageRoot) { + return null; + } return path.join(packageRoot, "dist", "control-ui", "index.html"); } @@ -61,9 +71,13 @@ function summarizeCommandOutput(text: string): string | undefined { .split(/\r?\n/g) .map((l) => l.trim()) .filter(Boolean); - if (!lines.length) return undefined; + if (!lines.length) { + return undefined; + } const last = lines.at(-1); - if (!last) return undefined; + if (!last) { + return undefined; + } return last.length > 240 ? `${last.slice(0, 239)}…` : last; } diff --git a/src/infra/dedupe.ts b/src/infra/dedupe.ts index dc5a61d427..850e2145a6 100644 --- a/src/infra/dedupe.ts +++ b/src/infra/dedupe.ts @@ -34,14 +34,18 @@ export function createDedupeCache(options: DedupeCacheOptions): DedupeCache { } while (cache.size > maxSize) { const oldestKey = cache.keys().next().value; - if (!oldestKey) break; + if (!oldestKey) { + break; + } cache.delete(oldestKey); } }; return { check: (key, now = Date.now()) => { - if (!key) return false; + if (!key) { + return false; + } const existing = cache.get(key); if (existing !== undefined && (ttlMs <= 0 || now - existing < ttlMs)) { touch(key, now); diff --git a/src/infra/device-auth-store.ts b/src/infra/device-auth-store.ts index a98c04fdfc..6776d23af2 100644 --- a/src/infra/device-auth-store.ts +++ b/src/infra/device-auth-store.ts @@ -27,22 +27,32 @@ function normalizeRole(role: string): string { } function normalizeScopes(scopes: string[] | undefined): string[] { - if (!Array.isArray(scopes)) return []; + if (!Array.isArray(scopes)) { + return []; + } const out = new Set(); for (const scope of scopes) { const trimmed = scope.trim(); - if (trimmed) out.add(trimmed); + if (trimmed) { + out.add(trimmed); + } } return [...out].toSorted(); } function readStore(filePath: string): DeviceAuthStore | null { try { - if (!fs.existsSync(filePath)) return null; + if (!fs.existsSync(filePath)) { + return null; + } const raw = fs.readFileSync(filePath, "utf8"); const parsed = JSON.parse(raw) as DeviceAuthStore; - if (parsed?.version !== 1 || typeof parsed.deviceId !== "string") return null; - if (!parsed.tokens || typeof parsed.tokens !== "object") return null; + if (parsed?.version !== 1 || typeof parsed.deviceId !== "string") { + return null; + } + if (!parsed.tokens || typeof parsed.tokens !== "object") { + return null; + } return parsed; } catch { return null; @@ -66,11 +76,17 @@ export function loadDeviceAuthToken(params: { }): DeviceAuthEntry | null { const filePath = resolveDeviceAuthPath(params.env); const store = readStore(filePath); - if (!store) return null; - if (store.deviceId !== params.deviceId) return null; + if (!store) { + return null; + } + if (store.deviceId !== params.deviceId) { + return null; + } const role = normalizeRole(params.role); const entry = store.tokens[role]; - if (!entry || typeof entry.token !== "string") return null; + if (!entry || typeof entry.token !== "string") { + return null; + } return entry; } @@ -110,9 +126,13 @@ export function clearDeviceAuthToken(params: { }): void { const filePath = resolveDeviceAuthPath(params.env); const store = readStore(filePath); - if (!store || store.deviceId !== params.deviceId) return; + if (!store || store.deviceId !== params.deviceId) { + return; + } const role = normalizeRole(params.role); - if (!store.tokens[role]) return; + if (!store.tokens[role]) { + return; + } const next: DeviceAuthStore = { version: 1, deviceId: store.deviceId, diff --git a/src/infra/device-pairing.ts b/src/infra/device-pairing.ts index e98cb7e70d..c2193af3f3 100644 --- a/src/infra/device-pairing.ts +++ b/src/infra/device-pairing.ts @@ -164,47 +164,69 @@ function normalizeRole(role: string | undefined): string | null { function mergeRoles(...items: Array): string[] | undefined { const roles = new Set(); for (const item of items) { - if (!item) continue; + if (!item) { + continue; + } if (Array.isArray(item)) { for (const role of item) { const trimmed = role.trim(); - if (trimmed) roles.add(trimmed); + if (trimmed) { + roles.add(trimmed); + } } } else { const trimmed = item.trim(); - if (trimmed) roles.add(trimmed); + if (trimmed) { + roles.add(trimmed); + } } } - if (roles.size === 0) return undefined; + if (roles.size === 0) { + return undefined; + } return [...roles]; } function mergeScopes(...items: Array): string[] | undefined { const scopes = new Set(); for (const item of items) { - if (!item) continue; + if (!item) { + continue; + } for (const scope of item) { const trimmed = scope.trim(); - if (trimmed) scopes.add(trimmed); + if (trimmed) { + scopes.add(trimmed); + } } } - if (scopes.size === 0) return undefined; + if (scopes.size === 0) { + return undefined; + } return [...scopes]; } function normalizeScopes(scopes: string[] | undefined): string[] { - if (!Array.isArray(scopes)) return []; + if (!Array.isArray(scopes)) { + return []; + } const out = new Set(); for (const scope of scopes) { const trimmed = scope.trim(); - if (trimmed) out.add(trimmed); + if (trimmed) { + out.add(trimmed); + } } return [...out].toSorted(); } function scopesAllow(requested: string[], allowed: string[]): boolean { - if (requested.length === 0) return true; - if (allowed.length === 0) return false; + if (requested.length === 0) { + return true; + } + if (allowed.length === 0) { + return false; + } const allowedSet = new Set(allowed); return requested.every((scope) => allowedSet.has(scope)); } @@ -278,7 +300,9 @@ export async function approveDevicePairing( return await withLock(async () => { const state = await loadState(baseDir); const pending = state.pendingById[requestId]; - if (!pending) return null; + if (!pending) { + return null; + } const now = Date.now(); const existing = state.pairedByDeviceId[pending.deviceId]; const roles = mergeRoles(existing?.roles, existing?.role, pending.roles, pending.role); @@ -328,7 +352,9 @@ export async function rejectDevicePairing( return await withLock(async () => { const state = await loadState(baseDir); const pending = state.pendingById[requestId]; - if (!pending) return null; + if (!pending) { + return null; + } delete state.pendingById[requestId]; await persistState(state, baseDir); return { requestId, deviceId: pending.deviceId }; @@ -343,7 +369,9 @@ export async function updatePairedDeviceMetadata( return await withLock(async () => { const state = await loadState(baseDir); const existing = state.pairedByDeviceId[normalizeDeviceId(deviceId)]; - if (!existing) return; + if (!existing) { + return; + } const roles = mergeRoles(existing.roles, existing.role, patch.role); const scopes = mergeScopes(existing.scopes, patch.scopes); state.pairedByDeviceId[deviceId] = { @@ -363,7 +391,9 @@ export async function updatePairedDeviceMetadata( export function summarizeDeviceTokens( tokens: Record | undefined, ): DeviceAuthTokenSummary[] | undefined { - if (!tokens) return undefined; + if (!tokens) { + return undefined; + } const summaries = Object.values(tokens) .map((token) => ({ role: token.role, @@ -387,13 +417,23 @@ export async function verifyDeviceToken(params: { return await withLock(async () => { const state = await loadState(params.baseDir); const device = state.pairedByDeviceId[normalizeDeviceId(params.deviceId)]; - if (!device) return { ok: false, reason: "device-not-paired" }; + if (!device) { + return { ok: false, reason: "device-not-paired" }; + } const role = normalizeRole(params.role); - if (!role) return { ok: false, reason: "role-missing" }; + if (!role) { + return { ok: false, reason: "role-missing" }; + } const entry = device.tokens?.[role]; - if (!entry) return { ok: false, reason: "token-missing" }; - if (entry.revokedAtMs) return { ok: false, reason: "token-revoked" }; - if (entry.token !== params.token) return { ok: false, reason: "token-mismatch" }; + if (!entry) { + return { ok: false, reason: "token-missing" }; + } + if (entry.revokedAtMs) { + return { ok: false, reason: "token-revoked" }; + } + if (entry.token !== params.token) { + return { ok: false, reason: "token-mismatch" }; + } const requestedScopes = normalizeScopes(params.scopes); if (!scopesAllow(requestedScopes, entry.scopes)) { return { ok: false, reason: "scope-mismatch" }; @@ -416,9 +456,13 @@ export async function ensureDeviceToken(params: { return await withLock(async () => { const state = await loadState(params.baseDir); const device = state.pairedByDeviceId[normalizeDeviceId(params.deviceId)]; - if (!device) return null; + if (!device) { + return null; + } const role = normalizeRole(params.role); - if (!role) return null; + if (!role) { + return null; + } const requestedScopes = normalizeScopes(params.scopes); const tokens = device.tokens ? { ...device.tokens } : {}; const existing = tokens[role]; @@ -454,9 +498,13 @@ export async function rotateDeviceToken(params: { return await withLock(async () => { const state = await loadState(params.baseDir); const device = state.pairedByDeviceId[normalizeDeviceId(params.deviceId)]; - if (!device) return null; + if (!device) { + return null; + } const role = normalizeRole(params.role); - if (!role) return null; + if (!role) { + return null; + } const tokens = device.tokens ? { ...device.tokens } : {}; const existing = tokens[role]; const requestedScopes = normalizeScopes(params.scopes ?? existing?.scopes ?? device.scopes); @@ -489,10 +537,16 @@ export async function revokeDeviceToken(params: { return await withLock(async () => { const state = await loadState(params.baseDir); const device = state.pairedByDeviceId[normalizeDeviceId(params.deviceId)]; - if (!device) return null; + if (!device) { + return null; + } const role = normalizeRole(params.role); - if (!role) return null; - if (!device.tokens?.[role]) return null; + if (!role) { + return null; + } + if (!device.tokens?.[role]) { + return null; + } const tokens = { ...device.tokens }; const entry = { ...tokens[role], revokedAtMs: Date.now() }; tokens[role] = entry; diff --git a/src/infra/diagnostic-flags.ts b/src/infra/diagnostic-flags.ts index eff29c4165..af02761928 100644 --- a/src/infra/diagnostic-flags.ts +++ b/src/infra/diagnostic-flags.ts @@ -7,12 +7,20 @@ function normalizeFlag(value: string): string { } function parseEnvFlags(raw?: string): string[] { - if (!raw) return []; + if (!raw) { + return []; + } const trimmed = raw.trim(); - if (!trimmed) return []; + if (!trimmed) { + return []; + } const lowered = trimmed.toLowerCase(); - if (["0", "false", "off", "none"].includes(lowered)) return []; - if (["1", "true", "all", "*"].includes(lowered)) return ["*"]; + if (["0", "false", "off", "none"].includes(lowered)) { + return []; + } + if (["1", "true", "all", "*"].includes(lowered)) { + return ["*"]; + } return trimmed .split(/[,\s]+/) .map(normalizeFlag) @@ -24,7 +32,9 @@ function uniqueFlags(flags: string[]): string[] { const out: string[] = []; for (const flag of flags) { const normalized = normalizeFlag(flag); - if (!normalized || seen.has(normalized)) continue; + if (!normalized || seen.has(normalized)) { + continue; + } seen.add(normalized); out.push(normalized); } @@ -42,20 +52,32 @@ export function resolveDiagnosticFlags( export function matchesDiagnosticFlag(flag: string, enabledFlags: string[]): boolean { const target = normalizeFlag(flag); - if (!target) return false; + if (!target) { + return false; + } for (const raw of enabledFlags) { const enabled = normalizeFlag(raw); - if (!enabled) continue; - if (enabled === "*" || enabled === "all") return true; + if (!enabled) { + continue; + } + if (enabled === "*" || enabled === "all") { + return true; + } if (enabled.endsWith(".*")) { const prefix = enabled.slice(0, -2); - if (target === prefix || target.startsWith(`${prefix}.`)) return true; + if (target === prefix || target.startsWith(`${prefix}.`)) { + return true; + } } if (enabled.endsWith("*")) { const prefix = enabled.slice(0, -1); - if (target.startsWith(prefix)) return true; + if (target.startsWith(prefix)) { + return true; + } + } + if (enabled === target) { + return true; } - if (enabled === target) return true; } return false; } diff --git a/src/infra/dotenv.test.ts b/src/infra/dotenv.test.ts index 58803ed9fd..5cd76416fe 100644 --- a/src/infra/dotenv.test.ts +++ b/src/infra/dotenv.test.ts @@ -36,11 +36,16 @@ describe("loadDotEnv", () => { process.chdir(prevCwd); for (const key of Object.keys(process.env)) { - if (!(key in prevEnv)) delete process.env[key]; + if (!(key in prevEnv)) { + delete process.env[key]; + } } for (const [key, value] of Object.entries(prevEnv)) { - if (value === undefined) delete process.env[key]; - else process.env[key] = value; + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } } }); @@ -66,11 +71,16 @@ describe("loadDotEnv", () => { process.chdir(prevCwd); for (const key of Object.keys(process.env)) { - if (!(key in prevEnv)) delete process.env[key]; + if (!(key in prevEnv)) { + delete process.env[key]; + } } for (const [key, value] of Object.entries(prevEnv)) { - if (value === undefined) delete process.env[key]; - else process.env[key] = value; + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } } }); }); diff --git a/src/infra/dotenv.ts b/src/infra/dotenv.ts index d031ad1377..b93c7a7af6 100644 --- a/src/infra/dotenv.ts +++ b/src/infra/dotenv.ts @@ -14,7 +14,9 @@ export function loadDotEnv(opts?: { quiet?: boolean }) { // Then load global fallback: ~/.openclaw/.env (or OPENCLAW_STATE_DIR/.env), // without overriding any env vars already present. const globalEnvPath = path.join(resolveConfigDir(process.env), ".env"); - if (!fs.existsSync(globalEnvPath)) return; + if (!fs.existsSync(globalEnvPath)) { + return; + } dotenv.config({ quiet, path: globalEnvPath, override: false }); } diff --git a/src/infra/env-file.ts b/src/infra/env-file.ts index de7a27f2da..55a5cd1825 100644 --- a/src/infra/env-file.ts +++ b/src/infra/env-file.ts @@ -30,11 +30,15 @@ export function upsertSharedEnvVar(params: { const nextLines = lines.map((line) => { const match = line.match(matcher); - if (!match) return line; + if (!match) { + return line; + } replaced = true; const prefix = match[1] ?? ""; const next = `${prefix}${key}=${value}`; - if (next !== line) updated = true; + if (next !== line) { + updated = true; + } return next; }); diff --git a/src/infra/env.test.ts b/src/infra/env.test.ts index dac8bc359a..97ba6440ac 100644 --- a/src/infra/env.test.ts +++ b/src/infra/env.test.ts @@ -13,10 +13,16 @@ describe("normalizeZaiEnv", () => { expect(process.env.ZAI_API_KEY).toBe("zai-legacy"); - if (prevZai === undefined) delete process.env.ZAI_API_KEY; - else process.env.ZAI_API_KEY = prevZai; - if (prevZAi === undefined) delete process.env.Z_AI_API_KEY; - else process.env.Z_AI_API_KEY = prevZAi; + if (prevZai === undefined) { + delete process.env.ZAI_API_KEY; + } else { + process.env.ZAI_API_KEY = prevZai; + } + if (prevZAi === undefined) { + delete process.env.Z_AI_API_KEY; + } else { + process.env.Z_AI_API_KEY = prevZAi; + } }); it("does not override existing ZAI_API_KEY", () => { @@ -29,10 +35,16 @@ describe("normalizeZaiEnv", () => { expect(process.env.ZAI_API_KEY).toBe("zai-current"); - if (prevZai === undefined) delete process.env.ZAI_API_KEY; - else process.env.ZAI_API_KEY = prevZai; - if (prevZAi === undefined) delete process.env.Z_AI_API_KEY; - else process.env.Z_AI_API_KEY = prevZAi; + if (prevZai === undefined) { + delete process.env.ZAI_API_KEY; + } else { + process.env.ZAI_API_KEY = prevZai; + } + if (prevZAi === undefined) { + delete process.env.Z_AI_API_KEY; + } else { + process.env.Z_AI_API_KEY = prevZAi; + } }); }); diff --git a/src/infra/env.ts b/src/infra/env.ts index 2139c65a72..47c16fed1d 100644 --- a/src/infra/env.ts +++ b/src/infra/env.ts @@ -12,17 +12,27 @@ type AcceptedEnvOption = { }; function formatEnvValue(value: string, redact?: boolean): string { - if (redact) return ""; + if (redact) { + return ""; + } const singleLine = value.replace(/\s+/g, " ").trim(); - if (singleLine.length <= 160) return singleLine; + if (singleLine.length <= 160) { + return singleLine; + } return `${singleLine.slice(0, 160)}…`; } export function logAcceptedEnvOption(option: AcceptedEnvOption): void { - if (process.env.VITEST || process.env.NODE_ENV === "test") return; - if (loggedEnv.has(option.key)) return; + if (process.env.VITEST || process.env.NODE_ENV === "test") { + return; + } + if (loggedEnv.has(option.key)) { + return; + } const rawValue = option.value ?? process.env[option.key]; - if (!rawValue || !rawValue.trim()) return; + if (!rawValue || !rawValue.trim()) { + return; + } loggedEnv.add(option.key); log.info(`env: ${option.key}=${formatEnvValue(rawValue, option.redact)} (${option.description})`); } diff --git a/src/infra/errors.ts b/src/infra/errors.ts index 9175b36227..9f41ee4e57 100644 --- a/src/infra/errors.ts +++ b/src/infra/errors.ts @@ -1,8 +1,14 @@ export function extractErrorCode(err: unknown): string | undefined { - if (!err || typeof err !== "object") return undefined; + if (!err || typeof err !== "object") { + return undefined; + } const code = (err as { code?: unknown }).code; - if (typeof code === "string") return code; - if (typeof code === "number") return String(code); + if (typeof code === "string") { + return code; + } + if (typeof code === "number") { + return String(code); + } return undefined; } @@ -10,7 +16,9 @@ export function formatErrorMessage(err: unknown): string { if (err instanceof Error) { return err.message || err.name || "Error"; } - if (typeof err === "string") return err; + if (typeof err === "string") { + return err; + } if (typeof err === "number" || typeof err === "boolean" || typeof err === "bigint") { return String(err); } diff --git a/src/infra/exec-approval-forwarder.ts b/src/infra/exec-approval-forwarder.ts index 8972b58fa7..c1b663ec89 100644 --- a/src/infra/exec-approval-forwarder.ts +++ b/src/infra/exec-approval-forwarder.ts @@ -82,18 +82,28 @@ function shouldForward(params: { request: ExecApprovalRequest; }): boolean { const config = params.config; - if (!config?.enabled) return false; + if (!config?.enabled) { + return false; + } if (config.agentFilter?.length) { const agentId = params.request.request.agentId ?? parseAgentSessionKey(params.request.request.sessionKey)?.agentId; - if (!agentId) return false; - if (!config.agentFilter.includes(agentId)) return false; + if (!agentId) { + return false; + } + if (!config.agentFilter.includes(agentId)) { + return false; + } } if (config.sessionFilter?.length) { const sessionKey = params.request.request.sessionKey; - if (!sessionKey) return false; - if (!matchSessionFilter(sessionKey, config.sessionFilter)) return false; + if (!sessionKey) { + return false; + } + if (!matchSessionFilter(sessionKey, config.sessionFilter)) { + return false; + } } return true; } @@ -108,11 +118,21 @@ function buildTargetKey(target: ExecApprovalForwardTarget): string { function buildRequestMessage(request: ExecApprovalRequest, nowMs: number) { const lines: string[] = ["🔒 Exec approval required", `ID: ${request.id}`]; lines.push(`Command: ${request.request.command}`); - if (request.request.cwd) lines.push(`CWD: ${request.request.cwd}`); - if (request.request.host) lines.push(`Host: ${request.request.host}`); - if (request.request.agentId) lines.push(`Agent: ${request.request.agentId}`); - if (request.request.security) lines.push(`Security: ${request.request.security}`); - if (request.request.ask) lines.push(`Ask: ${request.request.ask}`); + if (request.request.cwd) { + lines.push(`CWD: ${request.request.cwd}`); + } + if (request.request.host) { + lines.push(`Host: ${request.request.host}`); + } + if (request.request.agentId) { + lines.push(`Agent: ${request.request.agentId}`); + } + if (request.request.security) { + lines.push(`Security: ${request.request.security}`); + } + if (request.request.ask) { + lines.push(`Ask: ${request.request.ask}`); + } const expiresIn = Math.max(0, Math.round((request.expiresAtMs - nowMs) / 1000)); lines.push(`Expires in: ${expiresIn}s`); lines.push("Reply with: /approve allow-once|allow-always|deny"); @@ -120,8 +140,12 @@ function buildRequestMessage(request: ExecApprovalRequest, nowMs: number) { } function decisionLabel(decision: ExecApprovalDecision): string { - if (decision === "allow-once") return "allowed once"; - if (decision === "allow-always") return "allowed always"; + if (decision === "allow-once") { + return "allowed once"; + } + if (decision === "allow-always") { + return "allowed always"; + } return "denied"; } @@ -140,16 +164,24 @@ function defaultResolveSessionTarget(params: { request: ExecApprovalRequest; }): ExecApprovalForwardTarget | null { const sessionKey = params.request.request.sessionKey?.trim(); - if (!sessionKey) return null; + if (!sessionKey) { + return null; + } const parsed = parseAgentSessionKey(sessionKey); const agentId = parsed?.agentId ?? params.request.request.agentId ?? "main"; const storePath = resolveStorePath(params.cfg.session?.store, { agentId }); const store = loadSessionStore(storePath); const entry = store[sessionKey]; - if (!entry) return null; + if (!entry) { + return null; + } const target = resolveSessionDeliveryTarget({ entry, requestedChannel: "last" }); - if (!target.channel || !target.to) return null; - if (!isDeliverableMessageChannel(target.channel)) return null; + if (!target.channel || !target.to) { + return null; + } + if (!isDeliverableMessageChannel(target.channel)) { + return null; + } return { channel: target.channel, to: target.to, @@ -166,9 +198,13 @@ async function deliverToTargets(params: { shouldSend?: () => boolean; }) { const deliveries = params.targets.map(async (target) => { - if (params.shouldSend && !params.shouldSend()) return; + if (params.shouldSend && !params.shouldSend()) { + return; + } const channel = normalizeMessageChannel(target.channel) ?? target.channel; - if (!isDeliverableMessageChannel(channel)) return; + if (!isDeliverableMessageChannel(channel)) { + return; + } try { await params.deliver({ cfg: params.cfg, @@ -197,7 +233,9 @@ export function createExecApprovalForwarder( const handleRequested = async (request: ExecApprovalRequest) => { const cfg = getConfig(); const config = cfg.approvals?.exec; - if (!shouldForward({ config, request })) return; + if (!shouldForward({ config, request })) { + return; + } const mode = normalizeMode(config?.mode); const targets: ForwardTarget[] = []; @@ -218,19 +256,25 @@ export function createExecApprovalForwarder( const explicitTargets = config?.targets ?? []; for (const target of explicitTargets) { const key = buildTargetKey(target); - if (seen.has(key)) continue; + if (seen.has(key)) { + continue; + } seen.add(key); targets.push({ ...target, source: "target" }); } } - if (targets.length === 0) return; + if (targets.length === 0) { + return; + } const expiresInMs = Math.max(0, request.expiresAtMs - nowMs()); const timeoutId = setTimeout(() => { void (async () => { const entry = pending.get(request.id); - if (!entry) return; + if (!entry) { + return; + } pending.delete(request.id); const expiredText = buildExpiredMessage(request); await deliverToTargets({ cfg, targets: entry.targets, text: expiredText, deliver }); @@ -241,7 +285,9 @@ export function createExecApprovalForwarder( const pendingEntry: PendingApproval = { request, targets, timeoutId }; pending.set(request.id, pendingEntry); - if (pending.get(request.id) !== pendingEntry) return; + if (pending.get(request.id) !== pendingEntry) { + return; + } const text = buildRequestMessage(request, nowMs()); await deliverToTargets({ @@ -255,8 +301,12 @@ export function createExecApprovalForwarder( const handleResolved = async (resolved: ExecApprovalResolved) => { const entry = pending.get(resolved.id); - if (!entry) return; - if (entry.timeoutId) clearTimeout(entry.timeoutId); + if (!entry) { + return; + } + if (entry.timeoutId) { + clearTimeout(entry.timeoutId); + } pending.delete(resolved.id); const cfg = getConfig(); @@ -266,7 +316,9 @@ export function createExecApprovalForwarder( const stop = () => { for (const entry of pending.values()) { - if (entry.timeoutId) clearTimeout(entry.timeoutId); + if (entry.timeoutId) { + clearTimeout(entry.timeoutId); + } } pending.clear(); }; diff --git a/src/infra/exec-approvals.ts b/src/infra/exec-approvals.ts index 5c5622cb10..2254cba4b6 100644 --- a/src/infra/exec-approvals.ts +++ b/src/infra/exec-approvals.ts @@ -73,9 +73,15 @@ function hashExecApprovalsRaw(raw: string | null): string { } function expandHome(value: string): string { - if (!value) return value; - if (value === "~") return os.homedir(); - if (value.startsWith("~/")) return path.join(os.homedir(), value.slice(2)); + if (!value) { + return value; + } + if (value === "~") { + return os.homedir(); + } + if (value.startsWith("~/")) { + return path.join(os.homedir(), value.slice(2)); + } return value; } @@ -100,12 +106,18 @@ function mergeLegacyAgent( const seen = new Set(); const pushEntry = (entry: ExecAllowlistEntry) => { const key = normalizeAllowlistPattern(entry.pattern); - if (!key || seen.has(key)) return; + if (!key || seen.has(key)) { + return; + } seen.add(key); allowlist.push(entry); }; - for (const entry of current.allowlist ?? []) pushEntry(entry); - for (const entry of legacy.allowlist ?? []) pushEntry(entry); + for (const entry of current.allowlist ?? []) { + pushEntry(entry); + } + for (const entry of legacy.allowlist ?? []) { + pushEntry(entry); + } return { security: current.security ?? legacy.security, @@ -124,10 +136,14 @@ function ensureDir(filePath: string) { function ensureAllowlistIds( allowlist: ExecAllowlistEntry[] | undefined, ): ExecAllowlistEntry[] | undefined { - if (!Array.isArray(allowlist) || allowlist.length === 0) return allowlist; + if (!Array.isArray(allowlist) || allowlist.length === 0) { + return allowlist; + } let changed = false; const next = allowlist.map((entry) => { - if (entry.id) return entry; + if (entry.id) { + return entry; + } changed = true; return { ...entry, id: crypto.randomUUID() }; }); @@ -248,12 +264,16 @@ export function ensureExecApprovals(): ExecApprovalsFile { } function normalizeSecurity(value: ExecSecurity | undefined, fallback: ExecSecurity): ExecSecurity { - if (value === "allowlist" || value === "full" || value === "deny") return value; + if (value === "allowlist" || value === "full" || value === "deny") { + return value; + } return fallback; } function normalizeAsk(value: ExecAsk | undefined, fallback: ExecAsk): ExecAsk { - if (value === "always" || value === "off" || value === "on-miss") return value; + if (value === "always" || value === "off" || value === "on-miss") { + return value; + } return fallback; } @@ -345,7 +365,9 @@ type CommandResolution = { function isExecutableFile(filePath: string): boolean { try { const stat = fs.statSync(filePath); - if (!stat.isFile()) return false; + if (!stat.isFile()) { + return false; + } if (process.platform !== "win32") { fs.accessSync(filePath, fs.constants.X_OK); } @@ -357,11 +379,15 @@ function isExecutableFile(filePath: string): boolean { function parseFirstToken(command: string): string | null { const trimmed = command.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const first = trimmed[0]; if (first === '"' || first === "'") { const end = trimmed.indexOf(first, 1); - if (end > 1) return trimmed.slice(1, end); + if (end > 1) { + return trimmed.slice(1, end); + } return trimmed.slice(1); } const match = /^[^\s]+/.exec(trimmed); @@ -398,7 +424,9 @@ function resolveExecutablePath(rawExecutable: string, cwd?: string, env?: NodeJS for (const entry of entries) { for (const ext of extensions) { const candidate = path.join(entry, expanded + ext); - if (isExecutableFile(candidate)) return candidate; + if (isExecutableFile(candidate)) { + return candidate; + } } } return undefined; @@ -410,7 +438,9 @@ export function resolveCommandResolution( env?: NodeJS.ProcessEnv, ): CommandResolution | null { const rawExecutable = parseFirstToken(command); - if (!rawExecutable) return null; + if (!rawExecutable) { + return null; + } const resolvedPath = resolveExecutablePath(rawExecutable, cwd, env); const executableName = resolvedPath ? path.basename(resolvedPath) : rawExecutable; return { rawExecutable, resolvedPath, executableName }; @@ -422,7 +452,9 @@ export function resolveCommandResolutionFromArgv( env?: NodeJS.ProcessEnv, ): CommandResolution | null { const rawExecutable = argv[0]?.trim(); - if (!rawExecutable) return null; + if (!rawExecutable) { + return null; + } const resolvedPath = resolveExecutablePath(rawExecutable, cwd, env); const executableName = resolvedPath ? path.basename(resolvedPath) : rawExecutable; return { rawExecutable, resolvedPath, executableName }; @@ -474,7 +506,9 @@ function globToRegExp(pattern: string): RegExp { function matchesPattern(pattern: string, target: string): boolean { const trimmed = pattern.trim(); - if (!trimmed) return false; + if (!trimmed) { + return false; + } const expanded = trimmed.startsWith("~") ? expandHome(trimmed) : trimmed; const hasWildcard = /[*?]/.test(expanded); let normalizedPattern = expanded; @@ -493,13 +527,23 @@ function resolveAllowlistCandidatePath( resolution: CommandResolution | null, cwd?: string, ): string | undefined { - if (!resolution) return undefined; - if (resolution.resolvedPath) return resolution.resolvedPath; + if (!resolution) { + return undefined; + } + if (resolution.resolvedPath) { + return resolution.resolvedPath; + } const raw = resolution.rawExecutable?.trim(); - if (!raw) return undefined; + if (!raw) { + return undefined; + } const expanded = raw.startsWith("~") ? expandHome(raw) : raw; - if (!expanded.includes("/") && !expanded.includes("\\")) return undefined; - if (path.isAbsolute(expanded)) return expanded; + if (!expanded.includes("/") && !expanded.includes("\\")) { + return undefined; + } + if (path.isAbsolute(expanded)) { + return expanded; + } const base = cwd && cwd.trim() ? cwd.trim() : process.cwd(); return path.resolve(base, expanded); } @@ -508,14 +552,22 @@ export function matchAllowlist( entries: ExecAllowlistEntry[], resolution: CommandResolution | null, ): ExecAllowlistEntry | null { - if (!entries.length || !resolution?.resolvedPath) return null; + if (!entries.length || !resolution?.resolvedPath) { + return null; + } const resolvedPath = resolution.resolvedPath; for (const entry of entries) { const pattern = entry.pattern?.trim(); - if (!pattern) continue; + if (!pattern) { + continue; + } const hasPath = pattern.includes("/") || pattern.includes("\\") || pattern.includes("~"); - if (!hasPath) continue; - if (matchesPattern(pattern, resolvedPath)) return entry; + if (!hasPath) { + continue; + } + if (matchesPattern(pattern, resolvedPath)) { + return entry; + } } return null; } @@ -579,12 +631,16 @@ function iterateQuoteAware( continue; } if (inSingle) { - if (ch === "'") inSingle = false; + if (ch === "'") { + inSingle = false; + } buf += ch; continue; } if (inDouble) { - if (ch === '"') inDouble = false; + if (ch === '"') { + inDouble = false; + } buf += ch; continue; } @@ -805,10 +861,18 @@ export function analyzeArgvCommand(params: { function isPathLikeToken(value: string): boolean { const trimmed = value.trim(); - if (!trimmed) return false; - if (trimmed === "-") return false; - if (trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("~")) return true; - if (trimmed.startsWith("/")) return true; + if (!trimmed) { + return false; + } + if (trimmed === "-") { + return false; + } + if (trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("~")) { + return true; + } + if (trimmed.startsWith("/")) { + return true; + } return /^[A-Za-z]:[\\/]/.test(trimmed); } @@ -821,7 +885,9 @@ function defaultFileExists(filePath: string): boolean { } export function normalizeSafeBins(entries?: string[]): Set { - if (!Array.isArray(entries)) return new Set(); + if (!Array.isArray(entries)) { + return new Set(); + } const normalized = entries .map((entry) => entry.trim().toLowerCase()) .filter((entry) => entry.length > 0); @@ -829,7 +895,9 @@ export function normalizeSafeBins(entries?: string[]): Set { } export function resolveSafeBins(entries?: string[] | null): Set { - if (entries === undefined) return normalizeSafeBins(DEFAULT_SAFE_BINS); + if (entries === undefined) { + return normalizeSafeBins(DEFAULT_SAFE_BINS); + } return normalizeSafeBins(entries ?? []); } @@ -840,22 +908,34 @@ export function isSafeBinUsage(params: { cwd?: string; fileExists?: (filePath: string) => boolean; }): boolean { - if (params.safeBins.size === 0) return false; + if (params.safeBins.size === 0) { + return false; + } const resolution = params.resolution; const execName = resolution?.executableName?.toLowerCase(); - if (!execName) return false; + if (!execName) { + return false; + } const matchesSafeBin = params.safeBins.has(execName) || (process.platform === "win32" && params.safeBins.has(path.parse(execName).name)); - if (!matchesSafeBin) return false; - if (!resolution?.resolvedPath) return false; + if (!matchesSafeBin) { + return false; + } + if (!resolution?.resolvedPath) { + return false; + } const cwd = params.cwd ?? process.cwd(); const exists = params.fileExists ?? defaultFileExists; const argv = params.argv.slice(1); for (let i = 0; i < argv.length; i += 1) { const token = argv[i]; - if (!token) continue; - if (token === "-") continue; + if (!token) { + continue; + } + if (token === "-") { + continue; + } if (token.startsWith("-")) { const eqIndex = token.indexOf("="); if (eqIndex > 0) { @@ -866,8 +946,12 @@ export function isSafeBinUsage(params: { } continue; } - if (isPathLikeToken(token)) return false; - if (exists(path.resolve(cwd, token))) return false; + if (isPathLikeToken(token)) { + return false; + } + if (exists(path.resolve(cwd, token))) { + return false; + } } return true; } @@ -897,7 +981,9 @@ function evaluateSegments( ? { ...segment.resolution, resolvedPath: candidatePath } : segment.resolution; const match = matchAllowlist(params.allowlist, candidateResolution); - if (match) matches.push(match); + if (match) { + matches.push(match); + } const safe = isSafeBinUsage({ argv: segment.argv, resolution: segment.resolution, @@ -993,12 +1079,16 @@ function splitCommandChain(command: string): string[] | null { continue; } if (inSingle) { - if (ch === "'") inSingle = false; + if (ch === "'") { + inSingle = false; + } buf += ch; continue; } if (inDouble) { - if (ch === '"') inDouble = false; + if (ch === '"') { + inDouble = false; + } buf += ch; continue; } @@ -1014,19 +1104,25 @@ function splitCommandChain(command: string): string[] | null { } if (ch === "&" && command[i + 1] === "&") { - if (!pushPart()) invalidChain = true; + if (!pushPart()) { + invalidChain = true; + } i += 1; foundChain = true; continue; } if (ch === "|" && command[i + 1] === "|") { - if (!pushPart()) invalidChain = true; + if (!pushPart()) { + invalidChain = true; + } i += 1; foundChain = true; continue; } if (ch === ";") { - if (!pushPart()) invalidChain = true; + if (!pushPart()) { + invalidChain = true; + } foundChain = true; continue; } @@ -1035,8 +1131,12 @@ function splitCommandChain(command: string): string[] | null { } const pushedFinal = pushPart(); - if (!foundChain) return null; - if (invalidChain || !pushedFinal) return null; + if (!foundChain) { + return null; + } + if (invalidChain || !pushedFinal) { + return null; + } return parts.length > 0 ? parts : null; } @@ -1187,8 +1287,12 @@ export function addAllowlistEntry( const existing = agents[target] ?? {}; const allowlist = Array.isArray(existing.allowlist) ? existing.allowlist : []; const trimmed = pattern.trim(); - if (!trimmed) return; - if (allowlist.some((entry) => entry.pattern === trimmed)) return; + if (!trimmed) { + return; + } + if (allowlist.some((entry) => entry.pattern === trimmed)) { + return; + } allowlist.push({ id: crypto.randomUUID(), pattern: trimmed, lastUsedAt: Date.now() }); agents[target] = { ...existing, allowlist }; approvals.agents = agents; @@ -1214,14 +1318,18 @@ export async function requestExecApprovalViaSocket(params: { timeoutMs?: number; }): Promise { const { socketPath, token, request } = params; - if (!socketPath || !token) return null; + if (!socketPath || !token) { + return null; + } const timeoutMs = params.timeoutMs ?? 15_000; return await new Promise((resolve) => { const client = new net.Socket(); let settled = false; let buffer = ""; const finish = (value: ExecApprovalDecision | null) => { - if (settled) return; + if (settled) { + return; + } settled = true; try { client.destroy(); @@ -1250,7 +1358,9 @@ export async function requestExecApprovalViaSocket(params: { const line = buffer.slice(0, idx).trim(); buffer = buffer.slice(idx + 1); idx = buffer.indexOf("\n"); - if (!line) continue; + if (!line) { + continue; + } try { const msg = JSON.parse(line) as { type?: string; decision?: ExecApprovalDecision }; if (msg?.type === "decision" && msg.decision) { diff --git a/src/infra/exec-host.ts b/src/infra/exec-host.ts index 904b6503ae..d9d11aa927 100644 --- a/src/infra/exec-host.ts +++ b/src/infra/exec-host.ts @@ -39,14 +39,18 @@ export async function requestExecHostViaSocket(params: { timeoutMs?: number; }): Promise { const { socketPath, token, request } = params; - if (!socketPath || !token) return null; + if (!socketPath || !token) { + return null; + } const timeoutMs = params.timeoutMs ?? 20_000; return await new Promise((resolve) => { const client = new net.Socket(); let settled = false; let buffer = ""; const finish = (value: ExecHostResponse | null) => { - if (settled) return; + if (settled) { + return; + } settled = true; try { client.destroy(); @@ -85,7 +89,9 @@ export async function requestExecHostViaSocket(params: { const line = buffer.slice(0, idx).trim(); buffer = buffer.slice(idx + 1); idx = buffer.indexOf("\n"); - if (!line) continue; + if (!line) { + continue; + } try { const msg = JSON.parse(line) as { type?: string; diff --git a/src/infra/exec-safety.ts b/src/infra/exec-safety.ts index fe957d936f..f7ee77f014 100644 --- a/src/infra/exec-safety.ts +++ b/src/infra/exec-safety.ts @@ -4,21 +4,41 @@ const QUOTE_CHARS = /["']/; const BARE_NAME_PATTERN = /^[A-Za-z0-9._+-]+$/; function isLikelyPath(value: string): boolean { - if (value.startsWith(".") || value.startsWith("~")) return true; - if (value.includes("/") || value.includes("\\")) return true; + if (value.startsWith(".") || value.startsWith("~")) { + return true; + } + if (value.includes("/") || value.includes("\\")) { + return true; + } return /^[A-Za-z]:[\\/]/.test(value); } export function isSafeExecutableValue(value: string | null | undefined): boolean { - if (!value) return false; + if (!value) { + return false; + } const trimmed = value.trim(); - if (!trimmed) return false; - if (trimmed.includes("\0")) return false; - if (CONTROL_CHARS.test(trimmed)) return false; - if (SHELL_METACHARS.test(trimmed)) return false; - if (QUOTE_CHARS.test(trimmed)) return false; + if (!trimmed) { + return false; + } + if (trimmed.includes("\0")) { + return false; + } + if (CONTROL_CHARS.test(trimmed)) { + return false; + } + if (SHELL_METACHARS.test(trimmed)) { + return false; + } + if (QUOTE_CHARS.test(trimmed)) { + return false; + } - if (isLikelyPath(trimmed)) return true; - if (trimmed.startsWith("-")) return false; + if (isLikelyPath(trimmed)) { + return true; + } + if (trimmed.startsWith("-")) { + return false; + } return BARE_NAME_PATTERN.test(trimmed); } diff --git a/src/infra/fetch.test.ts b/src/infra/fetch.test.ts index 6a41f71f5b..f69bd601d1 100644 --- a/src/infra/fetch.test.ts +++ b/src/infra/fetch.test.ts @@ -30,10 +30,14 @@ describe("wrapFetchWithAbortSignal", () => { const fakeSignal = { aborted: false, addEventListener: (event: string, handler: () => void) => { - if (event === "abort") abortHandler = handler; + if (event === "abort") { + abortHandler = handler; + } }, removeEventListener: (event: string, handler: () => void) => { - if (event === "abort" && abortHandler === handler) abortHandler = null; + if (event === "abort" && abortHandler === handler) { + abortHandler = null; + } }, } as AbortSignal; diff --git a/src/infra/fetch.ts b/src/infra/fetch.ts index 61012e4856..86fd789dd9 100644 --- a/src/infra/fetch.ts +++ b/src/infra/fetch.ts @@ -14,8 +14,12 @@ function withDuplex( typeof Request !== "undefined" && input instanceof Request && input.body != null; - if (!hasInitBody && !hasRequestBody) return init; - if (init && "duplex" in (init as Record)) return init; + if (!hasInitBody && !hasRequestBody) { + return init; + } + if (init && "duplex" in (init as Record)) { + return init; + } return init ? ({ ...init, duplex: "half" as const } as RequestInitWithDuplex) : ({ duplex: "half" as const } as RequestInitWithDuplex); @@ -25,7 +29,9 @@ export function wrapFetchWithAbortSignal(fetchImpl: typeof fetch): typeof fetch const wrapped = ((input: RequestInfo | URL, init?: RequestInit) => { const patchedInit = withDuplex(init, input); const signal = patchedInit?.signal; - if (!signal) return fetchImpl(input, patchedInit); + if (!signal) { + return fetchImpl(input, patchedInit); + } if (typeof AbortSignal !== "undefined" && signal instanceof AbortSignal) { return fetchImpl(input, patchedInit); } @@ -62,6 +68,8 @@ export function wrapFetchWithAbortSignal(fetchImpl: typeof fetch): typeof fetch export function resolveFetch(fetchImpl?: typeof fetch): typeof fetch | undefined { const resolved = fetchImpl ?? globalThis.fetch; - if (!resolved) return undefined; + if (!resolved) { + return undefined; + } return wrapFetchWithAbortSignal(resolved); } diff --git a/src/infra/format-duration.ts b/src/infra/format-duration.ts index 04ff895936..b6cb694d75 100644 --- a/src/infra/format-duration.ts +++ b/src/infra/format-duration.ts @@ -7,7 +7,9 @@ export function formatDurationSeconds( ms: number, options: FormatDurationSecondsOptions = {}, ): string { - if (!Number.isFinite(ms)) return "unknown"; + if (!Number.isFinite(ms)) { + return "unknown"; + } const decimals = options.decimals ?? 1; const unit = options.unit ?? "s"; const seconds = Math.max(0, ms) / 1000; @@ -22,8 +24,12 @@ export type FormatDurationMsOptions = { }; export function formatDurationMs(ms: number, options: FormatDurationMsOptions = {}): string { - if (!Number.isFinite(ms)) return "unknown"; - if (ms < 1000) return `${ms}ms`; + if (!Number.isFinite(ms)) { + return "unknown"; + } + if (ms < 1000) { + return `${ms}ms`; + } return formatDurationSeconds(ms, { decimals: options.decimals ?? 2, unit: options.unit ?? "s", diff --git a/src/infra/fs-safe.ts b/src/infra/fs-safe.ts index 8e4f221980..2a6d073799 100644 --- a/src/infra/fs-safe.ts +++ b/src/infra/fs-safe.ts @@ -94,7 +94,9 @@ export async function openFileWithinRoot(params: { return { handle, realPath, stat }; } catch (err) { await handle.close().catch(() => {}); - if (err instanceof SafeOpenError) throw err; + if (err instanceof SafeOpenError) { + throw err; + } if (isNotFoundError(err)) { throw new SafeOpenError("not-found", "file not found"); } diff --git a/src/infra/gateway-lock.ts b/src/infra/gateway-lock.ts index e328f982d5..44e159f181 100644 --- a/src/infra/gateway-lock.ts +++ b/src/infra/gateway-lock.ts @@ -44,7 +44,9 @@ export class GatewayLockError extends Error { type LockOwnerStatus = "alive" | "dead" | "unknown"; function isAlive(pid: number): boolean { - if (!Number.isFinite(pid) || pid <= 0) return false; + if (!Number.isFinite(pid) || pid <= 0) { + return false; + } try { process.kill(pid, 0); return true; @@ -66,7 +68,9 @@ function parseProcCmdline(raw: string): string[] { function isGatewayArgv(args: string[]): boolean { const normalized = args.map(normalizeProcArg); - if (!normalized.includes("gateway")) return false; + if (!normalized.includes("gateway")) { + return false; + } const entryCandidates = [ "dist/index.mjs", @@ -98,7 +102,9 @@ function readLinuxStartTime(pid: number): number | null { try { const raw = fsSync.readFileSync(`/proc/${pid}/stat`, "utf8").trim(); const closeParen = raw.lastIndexOf(")"); - if (closeParen < 0) return null; + if (closeParen < 0) { + return null; + } const rest = raw.slice(closeParen + 1).trim(); const fields = rest.split(/\s+/); const startTime = Number.parseInt(fields[19] ?? "", 10); @@ -113,18 +119,26 @@ function resolveGatewayOwnerStatus( payload: LockPayload | null, platform: NodeJS.Platform, ): LockOwnerStatus { - if (!isAlive(pid)) return "dead"; - if (platform !== "linux") return "alive"; + if (!isAlive(pid)) { + return "dead"; + } + if (platform !== "linux") { + return "alive"; + } const payloadStartTime = payload?.startTime; if (Number.isFinite(payloadStartTime)) { const currentStartTime = readLinuxStartTime(pid); - if (currentStartTime == null) return "unknown"; + if (currentStartTime == null) { + return "unknown"; + } return currentStartTime === payloadStartTime ? "alive" : "dead"; } const args = readLinuxCmdline(pid); - if (!args) return "unknown"; + if (!args) { + return "unknown"; + } return isGatewayArgv(args) ? "alive" : "dead"; } @@ -132,9 +146,15 @@ async function readLockPayload(lockPath: string): Promise { try { const raw = await fs.readFile(lockPath, "utf8"); const parsed = JSON.parse(raw) as Partial; - if (typeof parsed.pid !== "number") return null; - if (typeof parsed.createdAt !== "string") return null; - if (typeof parsed.configPath !== "string") return null; + if (typeof parsed.pid !== "number") { + return null; + } + if (typeof parsed.createdAt !== "string") { + return null; + } + if (typeof parsed.configPath !== "string") { + return null; + } const startTime = typeof parsed.startTime === "number" ? parsed.startTime : undefined; return { pid: parsed.pid, diff --git a/src/infra/git-commit.ts b/src/infra/git-commit.ts index da351f17fc..996f8dad1f 100644 --- a/src/infra/git-commit.ts +++ b/src/infra/git-commit.ts @@ -3,9 +3,13 @@ import { createRequire } from "node:module"; import path from "node:path"; const formatCommit = (value?: string | null) => { - if (!value) return null; + if (!value) { + return null; + } const trimmed = value.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } return trimmed.length > 7 ? trimmed.slice(0, 7) : trimmed; }; @@ -30,7 +34,9 @@ const resolveGitHead = (startDir: string) => { // ignore missing .git at this level } const parent = path.dirname(current); - if (parent === current) break; + if (parent === current) { + break; + } current = parent; } return null; @@ -64,7 +70,9 @@ const readCommitFromBuildInfo = () => { }; export const resolveCommitHash = (options: { cwd?: string; env?: NodeJS.ProcessEnv } = {}) => { - if (cachedCommit !== undefined) return cachedCommit; + if (cachedCommit !== undefined) { + return cachedCommit; + } const env = options.env ?? process.env; const envCommit = env.GIT_COMMIT?.trim() || env.GIT_SHA?.trim(); const normalized = formatCommit(envCommit); diff --git a/src/infra/heartbeat-runner.ts b/src/infra/heartbeat-runner.ts index ac3471adf3..754b05eeaa 100644 --- a/src/infra/heartbeat-runner.ts +++ b/src/infra/heartbeat-runner.ts @@ -115,13 +115,19 @@ function resolveActiveHoursTimezone(cfg: OpenClawConfig, raw?: string): string { } function parseActiveHoursTime(opts: { allow24: boolean }, raw?: string): number | null { - if (!raw || !ACTIVE_HOURS_TIME_PATTERN.test(raw)) return null; + if (!raw || !ACTIVE_HOURS_TIME_PATTERN.test(raw)) { + return null; + } const [hourStr, minuteStr] = raw.split(":"); const hour = Number(hourStr); const minute = Number(minuteStr); - if (!Number.isFinite(hour) || !Number.isFinite(minute)) return null; + if (!Number.isFinite(hour) || !Number.isFinite(minute)) { + return null; + } if (hour === 24) { - if (!opts.allow24 || minute !== 0) return null; + if (!opts.allow24 || minute !== 0) { + return null; + } return 24 * 60; } return hour * 60 + minute; @@ -137,11 +143,15 @@ function resolveMinutesInTimeZone(nowMs: number, timeZone: string): number | nul }).formatToParts(new Date(nowMs)); const map: Record = {}; for (const part of parts) { - if (part.type !== "literal") map[part.type] = part.value; + if (part.type !== "literal") { + map[part.type] = part.value; + } } const hour = Number(map.hour); const minute = Number(map.minute); - if (!Number.isFinite(hour) || !Number.isFinite(minute)) return null; + if (!Number.isFinite(hour) || !Number.isFinite(minute)) { + return null; + } return hour * 60 + minute; } catch { return null; @@ -154,16 +164,24 @@ function isWithinActiveHours( nowMs?: number, ): boolean { const active = heartbeat?.activeHours; - if (!active) return true; + if (!active) { + return true; + } const startMin = parseActiveHoursTime({ allow24: false }, active.start); const endMin = parseActiveHoursTime({ allow24: true }, active.end); - if (startMin === null || endMin === null) return true; - if (startMin === endMin) return true; + if (startMin === null || endMin === null) { + return true; + } + if (startMin === endMin) { + return true; + } const timeZone = resolveActiveHoursTimezone(cfg, active.timezone); const currentMin = resolveMinutesInTimeZone(nowMs ?? Date.now(), timeZone); - if (currentMin === null) return true; + if (currentMin === null) { + return true; + } if (endMin > startMin) { return currentMin >= startMin && currentMin < endMin; @@ -206,9 +224,13 @@ function resolveHeartbeatConfig( agentId?: string, ): HeartbeatConfig | undefined { const defaults = cfg.agents?.defaults?.heartbeat; - if (!agentId) return defaults; + if (!agentId) { + return defaults; + } const overrides = resolveAgentConfig(cfg, agentId)?.heartbeat; - if (!defaults && !overrides) return overrides; + if (!defaults && !overrides) { + return overrides; + } return { ...defaults, ...overrides }; } @@ -285,16 +307,22 @@ export function resolveHeartbeatIntervalMs( heartbeat?.every ?? cfg.agents?.defaults?.heartbeat?.every ?? DEFAULT_HEARTBEAT_EVERY; - if (!raw) return null; + if (!raw) { + return null; + } const trimmed = String(raw).trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } let ms: number; try { ms = parseDurationMs(trimmed, { defaultUnit: "m" }); } catch { return null; } - if (ms <= 0) return null; + if (ms <= 0) { + return null; + } return ms; } @@ -363,11 +391,17 @@ function resolveHeartbeatSession( function resolveHeartbeatReplyPayload( replyResult: ReplyPayload | ReplyPayload[] | undefined, ): ReplyPayload | undefined { - if (!replyResult) return undefined; - if (!Array.isArray(replyResult)) return replyResult; + if (!replyResult) { + return undefined; + } + if (!Array.isArray(replyResult)) { + return replyResult; + } for (let idx = replyResult.length - 1; idx >= 0; idx -= 1) { const payload = replyResult[idx]; - if (!payload) continue; + if (!payload) { + continue; + } if (payload.text || payload.mediaUrl || (payload.mediaUrls && payload.mediaUrls.length > 0)) { return payload; } @@ -391,17 +425,27 @@ async function restoreHeartbeatUpdatedAt(params: { updatedAt?: number; }) { const { storePath, sessionKey, updatedAt } = params; - if (typeof updatedAt !== "number") return; + if (typeof updatedAt !== "number") { + return; + } const store = loadSessionStore(storePath); const entry = store[sessionKey]; - if (!entry) return; + if (!entry) { + return; + } const nextUpdatedAt = Math.max(entry.updatedAt ?? 0, updatedAt); - if (entry.updatedAt === nextUpdatedAt) return; + if (entry.updatedAt === nextUpdatedAt) { + return; + } await updateSessionStore(storePath, (nextStore) => { const nextEntry = nextStore[sessionKey] ?? entry; - if (!nextEntry) return; + if (!nextEntry) { + return; + } const resolvedUpdatedAt = Math.max(nextEntry.updatedAt ?? 0, updatedAt); - if (nextEntry.updatedAt === resolvedUpdatedAt) return; + if (nextEntry.updatedAt === resolvedUpdatedAt) { + return; + } nextStore[sessionKey] = { ...nextEntry, updatedAt: resolvedUpdatedAt }; }); } @@ -525,7 +569,9 @@ export async function runHeartbeatOnce(opts: { visibility.showOk && delivery.channel !== "none" && delivery.to, ); const maybeSendHeartbeatOk = async () => { - if (!canAttemptHeartbeatOk || delivery.channel === "none" || !delivery.to) return false; + if (!canAttemptHeartbeatOk || delivery.channel === "none" || !delivery.to) { + return false; + } const heartbeatPlugin = getChannelPlugin(delivery.channel); if (heartbeatPlugin?.heartbeat?.checkReady) { const readiness = await heartbeatPlugin.heartbeat.checkReady({ @@ -533,7 +579,9 @@ export async function runHeartbeatOnce(opts: { accountId: delivery.accountId, deps: opts.deps, }); - if (!readiness.ok) return false; + if (!readiness.ok) { + return false; + } } await deliverOutboundPayloads({ cfg, @@ -785,18 +833,26 @@ export function startHeartbeatRunner(opts: { }; const scheduleNext = () => { - if (state.stopped) return; + if (state.stopped) { + return; + } if (state.timer) { clearTimeout(state.timer); state.timer = null; } - if (state.agents.size === 0) return; + if (state.agents.size === 0) { + return; + } const now = Date.now(); let nextDue = Number.POSITIVE_INFINITY; for (const agent of state.agents.values()) { - if (agent.nextDueMs < nextDue) nextDue = agent.nextDueMs; + if (agent.nextDueMs < nextDue) { + nextDue = agent.nextDueMs; + } + } + if (!Number.isFinite(nextDue)) { + return; } - if (!Number.isFinite(nextDue)) return; const delay = Math.max(0, nextDue - now); state.timer = setTimeout(() => { requestHeartbeatNow({ reason: "interval", coalesceMs: 0 }); @@ -805,7 +861,9 @@ export function startHeartbeatRunner(opts: { }; const updateConfig = (cfg: OpenClawConfig) => { - if (state.stopped) return; + if (state.stopped) { + return; + } const now = Date.now(); const prevAgents = state.agents; const prevEnabled = prevAgents.size > 0; @@ -813,7 +871,9 @@ export function startHeartbeatRunner(opts: { const intervals: number[] = []; for (const agent of resolveHeartbeatAgents(cfg)) { const intervalMs = resolveHeartbeatIntervalMs(cfg, undefined, agent.heartbeat); - if (!intervalMs) continue; + if (!intervalMs) { + continue; + } intervals.push(intervalMs); const prevState = prevAgents.get(agent.agentId); const nextDueMs = resolveNextDue(now, intervalMs, prevState); @@ -880,11 +940,15 @@ export function startHeartbeatRunner(opts: { agent.lastRunMs = now; agent.nextDueMs = now + agent.intervalMs; } - if (res.status === "ran") ran = true; + if (res.status === "ran") { + ran = true; + } } scheduleNext(); - if (ran) return { status: "ran", durationMs: Date.now() - startedAt }; + if (ran) { + return { status: "ran", durationMs: Date.now() - startedAt }; + } return { status: "skipped", reason: isInterval ? "not-due" : "disabled" }; }; @@ -894,7 +958,9 @@ export function startHeartbeatRunner(opts: { const cleanup = () => { state.stopped = true; setHeartbeatWakeHandler(null); - if (state.timer) clearTimeout(state.timer); + if (state.timer) { + clearTimeout(state.timer); + } state.timer = null; }; diff --git a/src/infra/heartbeat-wake.ts b/src/infra/heartbeat-wake.ts index eb26bf499d..8e981ffc16 100644 --- a/src/infra/heartbeat-wake.ts +++ b/src/infra/heartbeat-wake.ts @@ -15,12 +15,16 @@ const DEFAULT_COALESCE_MS = 250; const DEFAULT_RETRY_MS = 1_000; function schedule(coalesceMs: number) { - if (timer) return; + if (timer) { + return; + } timer = setTimeout(async () => { timer = null; scheduled = false; const active = handler; - if (!active) return; + if (!active) { + return; + } if (running) { scheduled = true; schedule(coalesceMs); @@ -43,7 +47,9 @@ function schedule(coalesceMs: number) { schedule(DEFAULT_RETRY_MS); } finally { running = false; - if (pendingReason || scheduled) schedule(coalesceMs); + if (pendingReason || scheduled) { + schedule(coalesceMs); + } } }, coalesceMs); timer.unref?.(); diff --git a/src/infra/is-main.ts b/src/infra/is-main.ts index 15ccd38c48..23c036cc3d 100644 --- a/src/infra/is-main.ts +++ b/src/infra/is-main.ts @@ -9,7 +9,9 @@ type IsMainModuleOptions = { }; function normalizePathCandidate(candidate: string | undefined, cwd: string): string | undefined { - if (!candidate) return undefined; + if (!candidate) { + return undefined; + } const resolved = path.resolve(cwd, candidate); try { diff --git a/src/infra/json-file.ts b/src/infra/json-file.ts index 19c8169f7d..f34259df2e 100644 --- a/src/infra/json-file.ts +++ b/src/infra/json-file.ts @@ -3,7 +3,9 @@ import path from "node:path"; export function loadJsonFile(pathname: string): unknown { try { - if (!fs.existsSync(pathname)) return undefined; + if (!fs.existsSync(pathname)) { + return undefined; + } const raw = fs.readFileSync(pathname, "utf8"); return JSON.parse(raw) as unknown; } catch { diff --git a/src/infra/machine-name.ts b/src/infra/machine-name.ts index 684540f21c..4d31be7f23 100644 --- a/src/infra/machine-name.ts +++ b/src/infra/machine-name.ts @@ -29,16 +29,22 @@ function fallbackHostName() { } export async function getMachineDisplayName(): Promise { - if (cachedPromise) return cachedPromise; + if (cachedPromise) { + return cachedPromise; + } cachedPromise = (async () => { if (process.env.VITEST || process.env.NODE_ENV === "test") { return fallbackHostName(); } if (process.platform === "darwin") { const computerName = await tryScutil("ComputerName"); - if (computerName) return computerName; + if (computerName) { + return computerName; + } const localHostName = await tryScutil("LocalHostName"); - if (localHostName) return localHostName; + if (localHostName) { + return localHostName; + } } return fallbackHostName(); })(); diff --git a/src/infra/net/ssrf.pinning.test.ts b/src/infra/net/ssrf.pinning.test.ts index 2705fa1c96..954270e0c9 100644 --- a/src/infra/net/ssrf.pinning.test.ts +++ b/src/infra/net/ssrf.pinning.test.ts @@ -15,8 +15,11 @@ describe("ssrf pinning", () => { const first = await new Promise<{ address: string; family?: number }>((resolve, reject) => { pinned.lookup("example.com", (err, address, family) => { - if (err) reject(err); - else resolve({ address: address, family }); + if (err) { + reject(err); + } else { + resolve({ address: address, family }); + } }); }); expect(first.address).toBe("93.184.216.34"); @@ -24,8 +27,11 @@ describe("ssrf pinning", () => { const all = await new Promise((resolve, reject) => { pinned.lookup("example.com", { all: true }, (err, addresses) => { - if (err) reject(err); - else resolve(addresses); + if (err) { + reject(err); + } else { + resolve(addresses); + } }); }); expect(Array.isArray(all)).toBe(true); @@ -52,8 +58,11 @@ describe("ssrf pinning", () => { const result = await new Promise<{ address: string }>((resolve, reject) => { lookup("other.test", (err, address) => { - if (err) reject(err); - else resolve({ address: address }); + if (err) { + reject(err); + } else { + resolve({ address: address }); + } }); }); diff --git a/src/infra/net/ssrf.ts b/src/infra/net/ssrf.ts index 297df0f03a..49cb2e57c2 100644 --- a/src/infra/net/ssrf.ts +++ b/src/infra/net/ssrf.ts @@ -30,9 +30,13 @@ function normalizeHostname(hostname: string): string { function parseIpv4(address: string): number[] | null { const parts = address.split("."); - if (parts.length !== 4) return null; + if (parts.length !== 4) { + return null; + } const numbers = parts.map((part) => Number.parseInt(part, 10)); - if (numbers.some((value) => Number.isNaN(value) || value < 0 || value > 255)) return null; + if (numbers.some((value) => Number.isNaN(value) || value < 0 || value > 255)) { + return null; + } return numbers; } @@ -43,10 +47,14 @@ function parseIpv4FromMappedIpv6(mapped: string): number[] | null { const parts = mapped.split(":").filter(Boolean); if (parts.length === 1) { const value = Number.parseInt(parts[0], 16); - if (Number.isNaN(value) || value < 0 || value > 0xffff_ffff) return null; + if (Number.isNaN(value) || value < 0 || value > 0xffff_ffff) { + return null; + } return [(value >>> 24) & 0xff, (value >>> 16) & 0xff, (value >>> 8) & 0xff, value & 0xff]; } - if (parts.length !== 2) return null; + if (parts.length !== 2) { + return null; + } const high = Number.parseInt(parts[0], 16); const low = Number.parseInt(parts[1], 16); if ( @@ -65,13 +73,27 @@ function parseIpv4FromMappedIpv6(mapped: string): number[] | null { function isPrivateIpv4(parts: number[]): boolean { const [octet1, octet2] = parts; - if (octet1 === 0) return true; - if (octet1 === 10) return true; - if (octet1 === 127) return true; - if (octet1 === 169 && octet2 === 254) return true; - if (octet1 === 172 && octet2 >= 16 && octet2 <= 31) return true; - if (octet1 === 192 && octet2 === 168) return true; - if (octet1 === 100 && octet2 >= 64 && octet2 <= 127) return true; + if (octet1 === 0) { + return true; + } + if (octet1 === 10) { + return true; + } + if (octet1 === 127) { + return true; + } + if (octet1 === 169 && octet2 === 254) { + return true; + } + if (octet1 === 172 && octet2 >= 16 && octet2 <= 31) { + return true; + } + if (octet1 === 192 && octet2 === 168) { + return true; + } + if (octet1 === 100 && octet2 >= 64 && octet2 <= 127) { + return true; + } return false; } @@ -80,28 +102,40 @@ export function isPrivateIpAddress(address: string): boolean { if (normalized.startsWith("[") && normalized.endsWith("]")) { normalized = normalized.slice(1, -1); } - if (!normalized) return false; + if (!normalized) { + return false; + } if (normalized.startsWith("::ffff:")) { const mapped = normalized.slice("::ffff:".length); const ipv4 = parseIpv4FromMappedIpv6(mapped); - if (ipv4) return isPrivateIpv4(ipv4); + if (ipv4) { + return isPrivateIpv4(ipv4); + } } if (normalized.includes(":")) { - if (normalized === "::" || normalized === "::1") return true; + if (normalized === "::" || normalized === "::1") { + return true; + } return PRIVATE_IPV6_PREFIXES.some((prefix) => normalized.startsWith(prefix)); } const ipv4 = parseIpv4(normalized); - if (!ipv4) return false; + if (!ipv4) { + return false; + } return isPrivateIpv4(ipv4); } export function isBlockedHostname(hostname: string): boolean { const normalized = normalizeHostname(hostname); - if (!normalized) return false; - if (BLOCKED_HOSTNAMES.has(normalized)) return true; + if (!normalized) { + return false; + } + if (BLOCKED_HOSTNAMES.has(normalized)) { + return true; + } return ( normalized.endsWith(".localhost") || normalized.endsWith(".local") || @@ -134,7 +168,9 @@ export function createPinnedLookup(params: { return ((host: string, options?: unknown, callback?: unknown) => { const cb: LookupCallback = typeof options === "function" ? (options as LookupCallback) : (callback as LookupCallback); - if (!cb) return; + if (!cb) { + return; + } const normalized = normalizeHostname(host); if (!normalized || normalized !== normalizedHost) { if (typeof options === "function" || options === undefined) { @@ -219,7 +255,9 @@ export function createPinnedDispatcher(pinned: PinnedHostname): Dispatcher { } export async function closeDispatcher(dispatcher?: Dispatcher | null): Promise { - if (!dispatcher) return; + if (!dispatcher) { + return; + } const candidate = dispatcher as { close?: () => Promise | void; destroy?: () => void }; try { if (typeof candidate.close === "function") { diff --git a/src/infra/node-pairing.ts b/src/infra/node-pairing.ts index f6c86a492d..0d1089e824 100644 --- a/src/infra/node-pairing.ts +++ b/src/infra/node-pairing.ts @@ -216,7 +216,9 @@ export async function approveNodePairing( return await withLock(async () => { const state = await loadState(baseDir); const pending = state.pendingById[requestId]; - if (!pending) return null; + if (!pending) { + return null; + } const now = Date.now(); const existing = state.pairedByNodeId[pending.nodeId]; @@ -252,7 +254,9 @@ export async function rejectNodePairing( return await withLock(async () => { const state = await loadState(baseDir); const pending = state.pendingById[requestId]; - if (!pending) return null; + if (!pending) { + return null; + } delete state.pendingById[requestId]; await persistState(state, baseDir); return { requestId, nodeId: pending.nodeId }; @@ -267,7 +271,9 @@ export async function verifyNodeToken( const state = await loadState(baseDir); const normalized = normalizeNodeId(nodeId); const node = state.pairedByNodeId[normalized]; - if (!node) return { ok: false }; + if (!node) { + return { ok: false }; + } return node.token === token ? { ok: true, node } : { ok: false }; } @@ -280,7 +286,9 @@ export async function updatePairedNodeMetadata( const state = await loadState(baseDir); const normalized = normalizeNodeId(nodeId); const existing = state.pairedByNodeId[normalized]; - if (!existing) return; + if (!existing) { + return; + } const next: NodePairingPairedNode = { ...existing, @@ -313,9 +321,13 @@ export async function renamePairedNode( const state = await loadState(baseDir); const normalized = normalizeNodeId(nodeId); const existing = state.pairedByNodeId[normalized]; - if (!existing) return null; + if (!existing) { + return null; + } const trimmed = displayName.trim(); - if (!trimmed) throw new Error("displayName required"); + if (!trimmed) { + throw new Error("displayName required"); + } const next: NodePairingPairedNode = { ...existing, displayName: trimmed }; state.pairedByNodeId[normalized] = next; await persistState(state, baseDir); diff --git a/src/infra/openclaw-root.ts b/src/infra/openclaw-root.ts index 97ee4978c2..157ffbc300 100644 --- a/src/infra/openclaw-root.ts +++ b/src/infra/openclaw-root.ts @@ -18,9 +18,13 @@ async function findPackageRoot(startDir: string, maxDepth = 12): Promise { - if (platform === "darwin") return `macos ${macosVersion()} (${arch})`; - if (platform === "win32") return `windows ${release} (${arch})`; + if (platform === "darwin") { + return `macos ${macosVersion()} (${arch})`; + } + if (platform === "win32") { + return `windows ${release} (${arch})`; + } return `${platform} ${release} (${arch})`; })(); return { platform, arch, release, label }; diff --git a/src/infra/outbound/agent-delivery.ts b/src/infra/outbound/agent-delivery.ts index adc5ffe4cd..4091e61bc2 100644 --- a/src/infra/outbound/agent-delivery.ts +++ b/src/infra/outbound/agent-delivery.ts @@ -52,7 +52,9 @@ export function resolveAgentDeliveryPlan(params: { }); const resolvedChannel = (() => { - if (requestedChannel === INTERNAL_MESSAGE_CHANNEL) return INTERNAL_MESSAGE_CHANNEL; + if (requestedChannel === INTERNAL_MESSAGE_CHANNEL) { + return INTERNAL_MESSAGE_CHANNEL; + } if (requestedChannel === "last") { if (baseDelivery.channel && baseDelivery.channel !== INTERNAL_MESSAGE_CHANNEL) { return baseDelivery.channel; @@ -60,7 +62,9 @@ export function resolveAgentDeliveryPlan(params: { return params.wantsDelivery ? DEFAULT_CHAT_CHANNEL : INTERNAL_MESSAGE_CHANNEL; } - if (isGatewayMessageChannel(requestedChannel)) return requestedChannel; + if (isGatewayMessageChannel(requestedChannel)) { + return requestedChannel; + } if (baseDelivery.channel && baseDelivery.channel !== INTERNAL_MESSAGE_CHANNEL) { return baseDelivery.channel; diff --git a/src/infra/outbound/channel-adapters.ts b/src/infra/outbound/channel-adapters.ts index b66d1edbf7..c48fbb3959 100644 --- a/src/infra/outbound/channel-adapters.ts +++ b/src/infra/outbound/channel-adapters.ts @@ -19,6 +19,8 @@ const DISCORD_ADAPTER: ChannelMessageAdapter = { }; export function getChannelMessageAdapter(channel: ChannelId): ChannelMessageAdapter { - if (channel === "discord") return DISCORD_ADAPTER; + if (channel === "discord") { + return DISCORD_ADAPTER; + } return DEFAULT_ADAPTER; } diff --git a/src/infra/outbound/channel-selection.ts b/src/infra/outbound/channel-selection.ts index da740491ed..a8ba2b699e 100644 --- a/src/infra/outbound/channel-selection.ts +++ b/src/infra/outbound/channel-selection.ts @@ -16,24 +16,34 @@ function isKnownChannel(value: string): boolean { } function isAccountEnabled(account: unknown): boolean { - if (!account || typeof account !== "object") return true; + if (!account || typeof account !== "object") { + return true; + } const enabled = (account as { enabled?: boolean }).enabled; return enabled !== false; } async function isPluginConfigured(plugin: ChannelPlugin, cfg: OpenClawConfig): Promise { const accountIds = plugin.config.listAccountIds(cfg); - if (accountIds.length === 0) return false; + if (accountIds.length === 0) { + return false; + } for (const accountId of accountIds) { const account = plugin.config.resolveAccount(cfg, accountId); const enabled = plugin.config.isEnabled ? plugin.config.isEnabled(account, cfg) : isAccountEnabled(account); - if (!enabled) continue; - if (!plugin.config.isConfigured) return true; + if (!enabled) { + continue; + } + if (!plugin.config.isConfigured) { + return true; + } const configured = await plugin.config.isConfigured(account, cfg); - if (configured) return true; + if (configured) { + return true; + } } return false; @@ -44,7 +54,9 @@ export async function listConfiguredMessageChannels( ): Promise { const channels: MessageChannelId[] = []; for (const plugin of listChannelPlugins()) { - if (!isKnownChannel(plugin.id)) continue; + if (!isKnownChannel(plugin.id)) { + continue; + } if (await isPluginConfigured(plugin, cfg)) { channels.push(plugin.id); } diff --git a/src/infra/outbound/channel-target.ts b/src/infra/outbound/channel-target.ts index dae736abfc..21b577e7ca 100644 --- a/src/infra/outbound/channel-target.ts +++ b/src/infra/outbound/channel-target.ts @@ -24,7 +24,9 @@ export function applyTargetToParams(params: { throw new Error("Use `target` for actions that accept a destination."); } - if (!target) return; + if (!target) { + return; + } if (mode === "channelId") { params.args.channelId = target; return; diff --git a/src/infra/outbound/deliver.ts b/src/infra/outbound/deliver.ts index c9d260711d..ca3b5fd6a9 100644 --- a/src/infra/outbound/deliver.ts +++ b/src/infra/outbound/deliver.ts @@ -124,7 +124,9 @@ function createPluginHandler(params: { gifPlayback?: boolean; }): ChannelHandler | null { const outbound = params.outbound; - if (!outbound?.sendText || !outbound?.sendMedia) return null; + if (!outbound?.sendText || !outbound?.sendMedia) { + return null; + } const sendText = outbound.sendText; const sendMedia = outbound.sendMedia; const chunker = outbound.chunker ?? null; @@ -244,10 +246,14 @@ export async function deliverOutboundPayloads(params: { ? chunkMarkdownTextWithMode(text, textLimit, "newline") : chunkByParagraph(text, textLimit); - if (!blockChunks.length && text) blockChunks.push(text); + if (!blockChunks.length && text) { + blockChunks.push(text); + } for (const blockChunk of blockChunks) { const chunks = handler.chunker(blockChunk, textLimit); - if (!chunks.length && blockChunk) chunks.push(blockChunk); + if (!chunks.length && blockChunk) { + chunks.push(blockChunk); + } for (const chunk of chunks) { throwIfAborted(abortSignal); results.push(await handler.sendText(chunk)); @@ -346,7 +352,9 @@ export async function deliverOutboundPayloads(params: { } } } catch (err) { - if (!params.bestEffort) throw err; + if (!params.bestEffort) { + throw err; + } params.onError?.(err, payloadSummary); } } diff --git a/src/infra/outbound/directory-cache.ts b/src/infra/outbound/directory-cache.ts index 13e7f039ef..8dccac50ff 100644 --- a/src/infra/outbound/directory-cache.ts +++ b/src/infra/outbound/directory-cache.ts @@ -28,7 +28,9 @@ export class DirectoryCache { get(key: string, cfg: OpenClawConfig): T | undefined { this.resetIfConfigChanged(cfg); const entry = this.cache.get(key); - if (!entry) return undefined; + if (!entry) { + return undefined; + } if (Date.now() - entry.fetchedAt > this.ttlMs) { this.cache.delete(key); return undefined; @@ -43,13 +45,17 @@ export class DirectoryCache { clearMatching(match: (key: string) => boolean): void { for (const key of this.cache.keys()) { - if (match(key)) this.cache.delete(key); + if (match(key)) { + this.cache.delete(key); + } } } clear(cfg?: OpenClawConfig): void { this.cache.clear(); - if (cfg) this.lastConfigRef = cfg; + if (cfg) { + this.lastConfigRef = cfg; + } } private resetIfConfigChanged(cfg: OpenClawConfig): void { diff --git a/src/infra/outbound/format.ts b/src/infra/outbound/format.ts index 01391f8a2a..d74f5939a6 100644 --- a/src/infra/outbound/format.ts +++ b/src/infra/outbound/format.ts @@ -31,9 +31,13 @@ type OutboundDeliveryMeta = { const resolveChannelLabel = (channel: string) => { const pluginLabel = getChannelPlugin(channel as ChannelId)?.meta.label; - if (pluginLabel) return pluginLabel; + if (pluginLabel) { + return pluginLabel; + } const normalized = normalizeChatChannelId(channel); - if (normalized) return getChatChannelMeta(normalized).label; + if (normalized) { + return getChatChannelMeta(normalized).label; + } return channel; }; @@ -48,10 +52,18 @@ export function formatOutboundDeliverySummary( const label = resolveChannelLabel(result.channel); const base = `✅ Sent via ${label}. Message ID: ${result.messageId}`; - if ("chatId" in result) return `${base} (chat ${result.chatId})`; - if ("channelId" in result) return `${base} (channel ${result.channelId})`; - if ("roomId" in result) return `${base} (room ${result.roomId})`; - if ("conversationId" in result) return `${base} (conversation ${result.conversationId})`; + if ("chatId" in result) { + return `${base} (chat ${result.chatId})`; + } + if ("channelId" in result) { + return `${base} (channel ${result.channelId})`; + } + if ("roomId" in result) { + return `${base} (room ${result.roomId})`; + } + if ("conversationId" in result) { + return `${base} (conversation ${result.conversationId})`; + } return base; } diff --git a/src/infra/outbound/message-action-runner.ts b/src/infra/outbound/message-action-runner.ts index 51b08bcd23..16718adf47 100644 --- a/src/infra/outbound/message-action-runner.ts +++ b/src/infra/outbound/message-action-runner.ts @@ -123,7 +123,9 @@ export function getToolResult( } function extractToolPayload(result: AgentToolResult): unknown { - if (result.details !== undefined) return result.details; + if (result.details !== undefined) { + return result.details; + } const textBlock = Array.isArray(result.content) ? result.content.find( (block) => @@ -188,7 +190,9 @@ async function maybeApplyCrossContextMarker(params: { toolContext: params.toolContext, accountId: params.accountId ?? undefined, }); - if (!decoration) return params.message; + if (!decoration) { + return params.message; + } return applyCrossContextMessageDecoration({ params: params.args, message: params.message, @@ -199,11 +203,17 @@ async function maybeApplyCrossContextMarker(params: { function readBooleanParam(params: Record, key: string): boolean | undefined { const raw = params[key]; - if (typeof raw === "boolean") return raw; + if (typeof raw === "boolean") { + return raw; + } if (typeof raw === "string") { const trimmed = raw.trim().toLowerCase(); - if (trimmed === "true") return true; - if (trimmed === "false") return false; + if (trimmed === "true") { + return true; + } + if (trimmed === "false") { + return false; + } } return undefined; } @@ -213,13 +223,23 @@ function resolveSlackAutoThreadId(params: { toolContext?: ChannelThreadingToolContext; }): string | undefined { const context = params.toolContext; - if (!context?.currentThreadTs || !context.currentChannelId) return undefined; + if (!context?.currentThreadTs || !context.currentChannelId) { + return undefined; + } // Only mirror auto-threading when Slack would reply in the active thread for this channel. - if (context.replyToMode !== "all" && context.replyToMode !== "first") return undefined; + if (context.replyToMode !== "all" && context.replyToMode !== "first") { + return undefined; + } const parsedTarget = parseSlackTarget(params.to, { defaultKind: "channel" }); - if (!parsedTarget || parsedTarget.kind !== "channel") return undefined; - if (parsedTarget.id.toLowerCase() !== context.currentChannelId.toLowerCase()) return undefined; - if (context.replyToMode === "first" && context.hasRepliedRef?.value) return undefined; + if (!parsedTarget || parsedTarget.kind !== "channel") { + return undefined; + } + if (parsedTarget.id.toLowerCase() !== context.currentChannelId.toLowerCase()) { + return undefined; + } + if (context.replyToMode === "first" && context.hasRepliedRef?.value) { + return undefined; + } return context.currentThreadTs; } @@ -266,14 +286,20 @@ function inferAttachmentFilename(params: { if (mediaHint.startsWith("file://")) { const filePath = fileURLToPath(mediaHint); const base = path.basename(filePath); - if (base) return base; + if (base) { + return base; + } } else if (/^https?:\/\//i.test(mediaHint)) { const url = new URL(mediaHint); const base = path.basename(url.pathname); - if (base) return base; + if (base) { + return base; + } } else { const base = path.basename(mediaHint); - if (base) return base; + if (base) { + return base; + } } } catch { // fall through to content-type based default @@ -287,9 +313,13 @@ function normalizeBase64Payload(params: { base64?: string; contentType?: string base64?: string; contentType?: string; } { - if (!params.base64) return { base64: params.base64, contentType: params.contentType }; + if (!params.base64) { + return { base64: params.base64, contentType: params.contentType }; + } const match = /^data:([^;]+);base64,(.*)$/i.exec(params.base64.trim()); - if (!match) return { base64: params.base64, contentType: params.contentType }; + if (!match) { + return { base64: params.base64, contentType: params.contentType }; + } const [, mime, payload] = match; return { base64: payload, @@ -305,7 +335,9 @@ async function hydrateSetGroupIconParams(params: { action: ChannelMessageActionName; dryRun?: boolean; }): Promise { - if (params.action !== "setGroupIcon") return; + if (params.action !== "setGroupIcon") { + return; + } const mediaHint = readStringParam(params.args, "media", { trim: false }); const fileHint = @@ -362,7 +394,9 @@ async function hydrateSendAttachmentParams(params: { action: ChannelMessageActionName; dryRun?: boolean; }): Promise { - if (params.action !== "sendAttachment") return; + if (params.action !== "sendAttachment") { + return; + } const mediaHint = readStringParam(params.args, "media", { trim: false }); const fileHint = @@ -372,7 +406,9 @@ async function hydrateSendAttachmentParams(params: { readStringParam(params.args, "contentType") ?? readStringParam(params.args, "mimeType"); const caption = readStringParam(params.args, "caption", { allowEmpty: true })?.trim(); const message = readStringParam(params.args, "message", { allowEmpty: true })?.trim(); - if (!caption && message) params.args.caption = message; + if (!caption && message) { + params.args.caption = message; + } const rawBuffer = readStringParam(params.args, "buffer", { trim: false }); const normalized = normalizeBase64Payload({ @@ -416,7 +452,9 @@ async function hydrateSendAttachmentParams(params: { function parseButtonsParam(params: Record): void { const raw = params.buttons; - if (typeof raw !== "string") return; + if (typeof raw !== "string") { + return; + } const trimmed = raw.trim(); if (!trimmed) { delete params.buttons; @@ -431,7 +469,9 @@ function parseButtonsParam(params: Record): void { function parseCardParam(params: Record): void { const raw = params.card; - if (typeof raw !== "string") return; + if (typeof raw !== "string") { + return; + } const trimmed = raw.trim(); if (!trimmed) { delete params.card; @@ -511,7 +551,9 @@ type ResolvedActionContext = { abortSignal?: AbortSignal; }; function resolveGateway(input: RunMessageActionParams): MessageActionRunnerGateway | undefined { - if (!input.gateway) return undefined; + if (!input.gateway) { + return undefined; + } return { url: input.gateway.url, token: input.gateway.token, @@ -562,7 +604,9 @@ async function handleBroadcastAction( channel: targetChannel, input: target, }); - if (!resolved.ok) throw resolved.error; + if (!resolved.ok) { + throw resolved.error; + } const sendResult = await runMessageAction({ ...input, action: "send", @@ -579,7 +623,9 @@ async function handleBroadcastAction( result: sendResult.kind === "send" ? sendResult.sendResult : undefined, }); } catch (err) { - if (isAbortError(err)) throw err; + if (isAbortError(err)) { + throw err; + } results.push({ channel: targetChannel, to: target, @@ -640,17 +686,25 @@ async function handleSendAction(ctx: ResolvedActionContext): Promise(); const pushMedia = (value?: string | null) => { const trimmed = value?.trim(); - if (!trimmed) return; - if (seenMedia.has(trimmed)) return; + if (!trimmed) { + return; + } + if (seenMedia.has(trimmed)) { + return; + } seenMedia.add(trimmed); mergedMediaUrls.push(trimmed); }; pushMedia(mediaHint); - for (const url of parsed.mediaUrls ?? []) pushMedia(url); + for (const url of parsed.mediaUrls ?? []) { + pushMedia(url); + } pushMedia(parsed.mediaUrl); message = parsed.text; params.message = message; - if (!params.replyTo && parsed.replyToId) params.replyTo = parsed.replyToId; + if (!params.replyTo && parsed.replyToId) { + params.replyTo = parsed.replyToId; + } if (!params.media) { // Use path/filePath if media not set, then fall back to parsed directives params.media = mergedMediaUrls[0] || undefined; diff --git a/src/infra/outbound/message-action-spec.ts b/src/infra/outbound/message-action-spec.ts index 639e641d0b..12e5b0e714 100644 --- a/src/infra/outbound/message-action-spec.ts +++ b/src/infra/outbound/message-action-spec.ts @@ -75,15 +75,25 @@ export function actionHasTarget( params: Record, ): boolean { const to = typeof params.to === "string" ? params.to.trim() : ""; - if (to) return true; + if (to) { + return true; + } const channelId = typeof params.channelId === "string" ? params.channelId.trim() : ""; - if (channelId) return true; + if (channelId) { + return true; + } const aliases = ACTION_TARGET_ALIASES[action]; - if (!aliases) return false; + if (!aliases) { + return false; + } return aliases.some((alias) => { const value = params[alias]; - if (typeof value === "string") return value.trim().length > 0; - if (typeof value === "number") return Number.isFinite(value); + if (typeof value === "string") { + return value.trim().length > 0; + } + if (typeof value === "number") { + return Number.isFinite(value); + } return false; }); } diff --git a/src/infra/outbound/message.ts b/src/infra/outbound/message.ts index 10faea09f4..3175d251ce 100644 --- a/src/infra/outbound/message.ts +++ b/src/infra/outbound/message.ts @@ -155,7 +155,9 @@ export async function sendMessage(params: MessageSendParams): Promise, ): string | undefined { - if (!CONTEXT_GUARDED_ACTIONS.has(action)) return undefined; - - if (action === "thread-reply" || action === "thread-create") { - if (typeof params.channelId === "string") return params.channelId; - if (typeof params.to === "string") return params.to; + if (!CONTEXT_GUARDED_ACTIONS.has(action)) { return undefined; } - if (typeof params.to === "string") return params.to; - if (typeof params.channelId === "string") return params.channelId; + if (action === "thread-reply" || action === "thread-create") { + if (typeof params.channelId === "string") { + return params.channelId; + } + if (typeof params.to === "string") { + return params.to; + } + return undefined; + } + + if (typeof params.to === "string") { + return params.to; + } + if (typeof params.channelId === "string") { + return params.channelId; + } return undefined; } @@ -62,10 +72,14 @@ function isCrossContextTarget(params: { toolContext?: ChannelThreadingToolContext; }): boolean { const currentTarget = params.toolContext?.currentChannelId?.trim(); - if (!currentTarget) return false; + if (!currentTarget) { + return false; + } const normalizedTarget = normalizeTarget(params.channel, params.target); const normalizedCurrent = normalizeTarget(params.channel, currentTarget); - if (!normalizedTarget || !normalizedCurrent) return false; + if (!normalizedTarget || !normalizedCurrent) { + return false; + } return normalizedTarget !== normalizedCurrent; } @@ -77,10 +91,16 @@ export function enforceCrossContextPolicy(params: { cfg: OpenClawConfig; }): void { const currentTarget = params.toolContext?.currentChannelId?.trim(); - if (!currentTarget) return; - if (!CONTEXT_GUARDED_ACTIONS.has(params.action)) return; + if (!currentTarget) { + return; + } + if (!CONTEXT_GUARDED_ACTIONS.has(params.action)) { + return; + } - if (params.cfg.tools?.message?.allowCrossContextSend) return; + if (params.cfg.tools?.message?.allowCrossContextSend) { + return; + } const currentProvider = params.toolContext?.currentChannelProvider; const allowWithinProvider = @@ -97,10 +117,14 @@ export function enforceCrossContextPolicy(params: { return; } - if (allowWithinProvider) return; + if (allowWithinProvider) { + return; + } const target = resolveContextGuardTarget(params.action, params.args); - if (!target) return; + if (!target) { + return; + } if (!isCrossContextTarget({ channel: params.channel, target, toolContext: params.toolContext })) { return; @@ -118,13 +142,21 @@ export async function buildCrossContextDecoration(params: { toolContext?: ChannelThreadingToolContext; accountId?: string | null; }): Promise { - if (!params.toolContext?.currentChannelId) return null; + if (!params.toolContext?.currentChannelId) { + return null; + } // Skip decoration for direct tool sends (agent composing, not forwarding) - if (params.toolContext.skipCrossContextDecoration) return null; - if (!isCrossContextTarget(params)) return null; + if (params.toolContext.skipCrossContextDecoration) { + return null; + } + if (!isCrossContextTarget(params)) { + return null; + } const markerConfig = params.cfg.tools?.message?.crossContext?.marker; - if (markerConfig?.enabled === false) return null; + if (markerConfig?.enabled === false) { + return null; + } const currentName = (await lookupDirectoryDisplay({ diff --git a/src/infra/outbound/outbound-send-service.ts b/src/infra/outbound/outbound-send-service.ts index b296a6fad8..650eb62979 100644 --- a/src/infra/outbound/outbound-send-service.ts +++ b/src/infra/outbound/outbound-send-service.ts @@ -36,7 +36,9 @@ export type OutboundSendContext = { }; function extractToolPayload(result: AgentToolResult): unknown { - if (result.details !== undefined) return result.details; + if (result.details !== undefined) { + return result.details; + } const textBlock = Array.isArray(result.content) ? result.content.find( (block) => diff --git a/src/infra/outbound/outbound-session.ts b/src/infra/outbound/outbound-session.ts index 79a1295751..cd58048351 100644 --- a/src/infra/outbound/outbound-session.ts +++ b/src/infra/outbound/outbound-session.ts @@ -53,16 +53,24 @@ const UUID_COMPACT_RE = /^[0-9a-f]{32}$/i; const SLACK_CHANNEL_TYPE_CACHE = new Map(); function looksLikeUuid(value: string): boolean { - if (UUID_RE.test(value) || UUID_COMPACT_RE.test(value)) return true; + if (UUID_RE.test(value) || UUID_COMPACT_RE.test(value)) { + return true; + } const compact = value.replace(/-/g, ""); - if (!/^[0-9a-f]+$/i.test(compact)) return false; + if (!/^[0-9a-f]+$/i.test(compact)) { + return false; + } return /[a-f]/i.test(compact); } function normalizeThreadId(value?: string | number | null): string | undefined { - if (value == null) return undefined; + if (value == null) { + return undefined; + } if (typeof value === "number") { - if (!Number.isFinite(value)) return undefined; + if (!Number.isFinite(value)) { + return undefined; + } return String(Math.trunc(value)); } const trimmed = value.trim(); @@ -73,7 +81,9 @@ function stripProviderPrefix(raw: string, channel: string): string { const trimmed = raw.trim(); const lower = trimmed.toLowerCase(); const prefix = `${channel.toLowerCase()}:`; - if (lower.startsWith(prefix)) return trimmed.slice(prefix.length).trim(); + if (lower.startsWith(prefix)) { + return trimmed.slice(prefix.length).trim(); + } return trimmed; } @@ -86,14 +96,20 @@ function inferPeerKind(params: { resolvedTarget?: ResolvedMessagingTarget; }): RoutePeerKind { const resolvedKind = params.resolvedTarget?.kind; - if (resolvedKind === "user") return "dm"; - if (resolvedKind === "channel") return "channel"; + if (resolvedKind === "user") { + return "dm"; + } + if (resolvedKind === "channel") { + return "channel"; + } if (resolvedKind === "group") { const plugin = getChannelPlugin(params.channel); const chatTypes = plugin?.capabilities?.chatTypes ?? []; const supportsChannel = chatTypes.includes("channel"); const supportsGroup = chatTypes.includes("group"); - if (supportsChannel && !supportsGroup) return "channel"; + if (supportsChannel && !supportsGroup) { + return "channel"; + } return "group"; } return "dm"; @@ -123,9 +139,13 @@ async function resolveSlackChannelType(params: { channelId: string; }): Promise<"channel" | "group" | "dm" | "unknown"> { const channelId = params.channelId.trim(); - if (!channelId) return "unknown"; + if (!channelId) { + return "unknown"; + } const cached = SLACK_CHANNEL_TYPE_CACHE.get(`${params.accountId ?? "default"}:${channelId}`); - if (cached) return cached; + if (cached) { + return cached; + } const account = resolveSlackAccount({ cfg: params.cfg, accountId: params.accountId }); const groupChannels = normalizeAllowListLower(account.dm?.groupChannels); @@ -181,7 +201,9 @@ async function resolveSlackSession( params: ResolveOutboundSessionRouteParams, ): Promise { const parsed = parseSlackTarget(params.target, { defaultKind: "channel" }); - if (!parsed) return null; + if (!parsed) { + return null; + } const isDm = parsed.kind === "user"; let peerKind: RoutePeerKind = isDm ? "dm" : "channel"; if (!isDm && /^G/i.test(parsed.id)) { @@ -191,8 +213,12 @@ async function resolveSlackSession( accountId: params.accountId, channelId: parsed.id, }); - if (channelType === "group") peerKind = "group"; - if (channelType === "dm") peerKind = "dm"; + if (channelType === "group") { + peerKind = "group"; + } + if (channelType === "dm") { + peerKind = "dm"; + } } const peer: RoutePeer = { kind: peerKind, @@ -230,7 +256,9 @@ function resolveDiscordSession( params: ResolveOutboundSessionRouteParams, ): OutboundSessionRoute | null { const parsed = parseDiscordTarget(params.target, { defaultKind: "channel" }); - if (!parsed) return null; + if (!parsed) { + return null; + } const isDm = parsed.kind === "user"; const peer: RoutePeer = { kind: isDm ? "dm" : "channel", @@ -267,7 +295,9 @@ function resolveTelegramSession( ): OutboundSessionRoute | null { const parsed = parseTelegramTarget(params.target); const chatId = parsed.chatId.trim(); - if (!chatId) return null; + if (!chatId) { + return null; + } const parsedThreadId = parsed.messageThreadId; const fallbackThreadId = normalizeThreadId(params.threadId); const resolvedThreadId = @@ -307,7 +337,9 @@ function resolveWhatsAppSession( params: ResolveOutboundSessionRouteParams, ): OutboundSessionRoute | null { const normalized = normalizeWhatsAppTarget(params.target); - if (!normalized) return null; + if (!normalized) { + return null; + } const isGroup = isWhatsAppGroupJid(normalized); const peer: RoutePeer = { kind: isGroup ? "group" : "dm", @@ -337,7 +369,9 @@ function resolveSignalSession( const lowered = stripped.toLowerCase(); if (lowered.startsWith("group:")) { const groupId = stripped.slice("group:".length).trim(); - if (!groupId) return null; + if (!groupId) { + return null; + } const peer: RoutePeer = { kind: "group", id: groupId }; const baseSessionKey = buildBaseSessionKey({ cfg: params.cfg, @@ -362,7 +396,9 @@ function resolveSignalSession( } else if (lowered.startsWith("u:")) { recipient = stripped.slice("u:".length).trim(); } - if (!recipient) return null; + if (!recipient) { + return null; + } const uuidCandidate = recipient.toLowerCase().startsWith("uuid:") ? recipient.slice("uuid:".length) @@ -397,7 +433,9 @@ function resolveIMessageSession( const parsed = parseIMessageTarget(params.target); if (parsed.kind === "handle") { const handle = normalizeIMessageHandle(parsed.to); - if (!handle) return null; + if (!handle) { + return null; + } const peer: RoutePeer = { kind: "dm", id: handle }; const baseSessionKey = buildBaseSessionKey({ cfg: params.cfg, @@ -422,7 +460,9 @@ function resolveIMessageSession( : parsed.kind === "chat_guid" ? parsed.chatGuid : parsed.chatIdentifier; - if (!peerId) return null; + if (!peerId) { + return null; + } const peer: RoutePeer = { kind: "group", id: peerId }; const baseSessionKey = buildBaseSessionKey({ cfg: params.cfg, @@ -454,7 +494,9 @@ function resolveMatrixSession( const isUser = params.resolvedTarget?.kind === "user" || stripped.startsWith("@") || /^user:/i.test(stripped); const rawId = stripKindPrefix(stripped); - if (!rawId) return null; + if (!rawId) { + return null; + } const peer: RoutePeer = { kind: isUser ? "dm" : "channel", id: rawId }; const baseSessionKey = buildBaseSessionKey({ cfg: params.cfg, @@ -477,13 +519,17 @@ function resolveMSTeamsSession( params: ResolveOutboundSessionRouteParams, ): OutboundSessionRoute | null { let trimmed = params.target.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } trimmed = trimmed.replace(/^(msteams|teams):/i, "").trim(); const lower = trimmed.toLowerCase(); const isUser = lower.startsWith("user:"); const rawId = stripKindPrefix(trimmed); - if (!rawId) return null; + if (!rawId) { + return null; + } const conversationId = rawId.split(";")[0] ?? rawId; const isChannel = !isUser && /@thread\.tacv2/i.test(conversationId); const peer: RoutePeer = { @@ -515,7 +561,9 @@ function resolveMattermostSession( params: ResolveOutboundSessionRouteParams, ): OutboundSessionRoute | null { let trimmed = params.target.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } trimmed = trimmed.replace(/^mattermost:/i, "").trim(); const lower = trimmed.toLowerCase(); const isUser = lower.startsWith("user:") || trimmed.startsWith("@"); @@ -523,7 +571,9 @@ function resolveMattermostSession( trimmed = trimmed.slice(1).trim(); } const rawId = stripKindPrefix(trimmed); - if (!rawId) return null; + if (!rawId) { + return null; + } const peer: RoutePeer = { kind: isUser ? "dm" : "channel", id: rawId }; const baseSessionKey = buildBaseSessionKey({ cfg: params.cfg, @@ -565,7 +615,9 @@ function resolveBlueBubblesSession( const peerId = isGroup ? rawPeerId.replace(/^(chat_id|chat_guid|chat_identifier):/i, "") : rawPeerId; - if (!peerId) return null; + if (!peerId) { + return null; + } const peer: RoutePeer = { kind: isGroup ? "group" : "dm", id: peerId, @@ -591,10 +643,14 @@ function resolveNextcloudTalkSession( params: ResolveOutboundSessionRouteParams, ): OutboundSessionRoute | null { let trimmed = params.target.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } trimmed = trimmed.replace(/^(nextcloud-talk|nc-talk|nc):/i, "").trim(); trimmed = trimmed.replace(/^room:/i, "").trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const peer: RoutePeer = { kind: "group", id: trimmed }; const baseSessionKey = buildBaseSessionKey({ cfg: params.cfg, @@ -619,7 +675,9 @@ function resolveZaloSession( const trimmed = stripProviderPrefix(params.target, "zalo") .replace(/^(zl):/i, "") .trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const isGroup = trimmed.toLowerCase().startsWith("group:"); const peerId = stripKindPrefix(trimmed); const peer: RoutePeer = { kind: isGroup ? "group" : "dm", id: peerId }; @@ -646,7 +704,9 @@ function resolveZalouserSession( const trimmed = stripProviderPrefix(params.target, "zalouser") .replace(/^(zlu):/i, "") .trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const isGroup = trimmed.toLowerCase().startsWith("group:"); const peerId = stripKindPrefix(trimmed); // Keep DM vs group aligned with inbound sessions for Zalo Personal. @@ -672,7 +732,9 @@ function resolveNostrSession( params: ResolveOutboundSessionRouteParams, ): OutboundSessionRoute | null { const trimmed = stripProviderPrefix(params.target, "nostr").trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const peer: RoutePeer = { kind: "dm", id: trimmed }; const baseSessionKey = buildBaseSessionKey({ cfg: params.cfg, @@ -693,7 +755,9 @@ function resolveNostrSession( function normalizeTlonShip(raw: string): string { const trimmed = raw.trim(); - if (!trimmed) return trimmed; + if (!trimmed) { + return trimmed; + } return trimmed.startsWith("~") ? trimmed : `~${trimmed}`; } @@ -702,7 +766,9 @@ function resolveTlonSession( ): OutboundSessionRoute | null { let trimmed = stripProviderPrefix(params.target, "tlon"); trimmed = trimmed.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const lower = trimmed.toLowerCase(); let isGroup = lower.startsWith("group:") || lower.startsWith("room:") || lower.startsWith("chat/"); @@ -754,13 +820,17 @@ function resolveFallbackSession( params: ResolveOutboundSessionRouteParams, ): OutboundSessionRoute | null { const trimmed = stripProviderPrefix(params.target, params.channel).trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const peerKind = inferPeerKind({ channel: params.channel, resolvedTarget: params.resolvedTarget, }); const peerId = stripKindPrefix(trimmed); - if (!peerId) return null; + if (!peerId) { + return null; + } const peer: RoutePeer = { kind: peerKind, id: peerId }; const baseSessionKey = buildBaseSessionKey({ cfg: params.cfg, @@ -786,7 +856,9 @@ export async function resolveOutboundSessionRoute( params: ResolveOutboundSessionRouteParams, ): Promise { const target = params.target.trim(); - if (!target) return null; + if (!target) { + return null; + } switch (params.channel) { case "slack": return await resolveSlackSession({ ...params, target }); diff --git a/src/infra/outbound/payloads.ts b/src/infra/outbound/payloads.ts index 94eabb2bc3..888f3624e1 100644 --- a/src/infra/outbound/payloads.ts +++ b/src/infra/outbound/payloads.ts @@ -19,11 +19,17 @@ function mergeMediaUrls(...lists: Array | undefined>): const seen = new Set(); const merged: string[] = []; for (const list of lists) { - if (!list) continue; + if (!list) { + continue; + } for (const entry of list) { const trimmed = entry?.trim(); - if (!trimmed) continue; - if (seen.has(trimmed)) continue; + if (!trimmed) { + continue; + } + if (seen.has(trimmed)) { + continue; + } seen.add(trimmed); merged.push(trimmed); } @@ -52,8 +58,12 @@ export function normalizeReplyPayloadsForDelivery(payloads: ReplyPayload[]): Rep replyToCurrent: payload.replyToCurrent || parsed.replyToCurrent, audioAsVoice: Boolean(payload.audioAsVoice || parsed.audioAsVoice), }; - if (parsed.isSilent && mergedMedia.length === 0) return []; - if (!isRenderablePayload(next)) return []; + if (parsed.isSilent && mergedMedia.length === 0) { + return []; + } + if (!isRenderablePayload(next)) { + return []; + } return [next]; }); } @@ -90,7 +100,11 @@ export function normalizeOutboundPayloadsForJson(payloads: ReplyPayload[]): Outb export function formatOutboundPayloadLog(payload: NormalizedOutboundPayload): string { const lines: string[] = []; - if (payload.text) lines.push(payload.text.trimEnd()); - for (const url of payload.mediaUrls) lines.push(`MEDIA:${url}`); + if (payload.text) { + lines.push(payload.text.trimEnd()); + } + for (const url of payload.mediaUrls) { + lines.push(`MEDIA:${url}`); + } return lines.join("\n"); } diff --git a/src/infra/outbound/target-errors.ts b/src/infra/outbound/target-errors.ts index 76b12e985f..0bf589817a 100644 --- a/src/infra/outbound/target-errors.ts +++ b/src/infra/outbound/target-errors.ts @@ -23,6 +23,8 @@ export function unknownTargetError(provider: string, raw: string, hint?: string) } function formatTargetHint(hint?: string, withLabel = false): string { - if (!hint) return ""; + if (!hint) { + return ""; + } return withLabel ? ` Hint: ${hint}` : ` ${hint}`; } diff --git a/src/infra/outbound/target-normalization.ts b/src/infra/outbound/target-normalization.ts index dbab5e4e48..9964ff90e9 100644 --- a/src/infra/outbound/target-normalization.ts +++ b/src/infra/outbound/target-normalization.ts @@ -6,7 +6,9 @@ export function normalizeChannelTargetInput(raw: string): string { } export function normalizeTargetForProvider(provider: string, raw?: string): string | undefined { - if (!raw) return undefined; + if (!raw) { + return undefined; + } const providerId = normalizeChannelId(provider); const plugin = providerId ? getChannelPlugin(providerId) : undefined; const normalized = diff --git a/src/infra/outbound/target-resolver.ts b/src/infra/outbound/target-resolver.ts index ed9deec390..c8eb7de85b 100644 --- a/src/infra/outbound/target-resolver.ts +++ b/src/infra/outbound/target-resolver.ts @@ -51,8 +51,12 @@ export function resetDirectoryCache(params?: { channel?: ChannelId; accountId?: const channelKey = params.channel; const accountKey = params.accountId ?? "default"; directoryCache.clearMatching((key) => { - if (!key.startsWith(`${channelKey}:`)) return false; - if (!params.accountId) return true; + if (!key.startsWith(`${channelKey}:`)) { + return false; + } + if (!params.accountId) { + return true; + } return key.startsWith(`${channelKey}:${accountKey}:`); }); } @@ -91,14 +95,24 @@ export function formatTargetDisplay(params: { (lowered.startsWith("user:") ? "user" : lowered.startsWith("channel:") ? "group" : undefined); if (display) { - if (display.startsWith("#") || display.startsWith("@")) return display; - if (kind === "user") return `@${display}`; - if (kind === "group" || kind === "channel") return `#${display}`; + if (display.startsWith("#") || display.startsWith("@")) { + return display; + } + if (kind === "user") { + return `@${display}`; + } + if (kind === "group" || kind === "channel") { + return `#${display}`; + } return display; } - if (!trimmedTarget) return trimmedTarget; - if (trimmedTarget.startsWith("#") || trimmedTarget.startsWith("@")) return trimmedTarget; + if (!trimmedTarget) { + return trimmedTarget; + } + if (trimmedTarget.startsWith("#") || trimmedTarget.startsWith("@")) { + return trimmedTarget; + } const channelPrefix = `${params.channel}:`; const withoutProvider = trimmedTarget.toLowerCase().startsWith(channelPrefix) @@ -116,11 +130,19 @@ export function formatTargetDisplay(params: { } function preserveTargetCase(channel: ChannelId, raw: string, normalized: string): string { - if (channel !== "slack") return normalized; + if (channel !== "slack") { + return normalized; + } const trimmed = raw.trim(); - if (/^channel:/i.test(trimmed) || /^user:/i.test(trimmed)) return trimmed; - if (trimmed.startsWith("#")) return `channel:${trimmed.slice(1).trim()}`; - if (trimmed.startsWith("@")) return `user:${trimmed.slice(1).trim()}`; + if (/^channel:/i.test(trimmed) || /^user:/i.test(trimmed)) { + return trimmed; + } + if (trimmed.startsWith("#")) { + return `channel:${trimmed.slice(1).trim()}`; + } + if (trimmed.startsWith("@")) { + return `user:${trimmed.slice(1).trim()}`; + } return trimmed; } @@ -129,12 +151,20 @@ function detectTargetKind( raw: string, preferred?: TargetResolveKind, ): TargetResolveKind { - if (preferred) return preferred; + if (preferred) { + return preferred; + } const trimmed = raw.trim(); - if (!trimmed) return "group"; + if (!trimmed) { + return "group"; + } - if (trimmed.startsWith("@") || /^<@!?/.test(trimmed) || /^user:/i.test(trimmed)) return "user"; - if (trimmed.startsWith("#") || /^channel:/i.test(trimmed)) return "group"; + if (trimmed.startsWith("@") || /^<@!?/.test(trimmed) || /^user:/i.test(trimmed)) { + return "user"; + } + if (trimmed.startsWith("#") || /^channel:/i.test(trimmed)) { + return "group"; + } // For some channels (e.g., BlueBubbles/iMessage), bare phone numbers are almost always DM targets. if ((channel === "bluebubbles" || channel === "imessage") && /^\+?\d{6,}$/.test(trimmed)) { @@ -155,7 +185,9 @@ function matchesDirectoryEntry(params: { query: string; }): boolean { const query = normalizeQuery(params.query); - if (!query) return false; + if (!query) { + return false; + } const id = stripTargetPrefixes(normalizeDirectoryEntryId(params.channel, params.entry)); const name = params.entry.name ? stripTargetPrefixes(params.entry.name) : ""; const handle = params.entry.handle ? stripTargetPrefixes(params.entry.handle) : ""; @@ -171,8 +203,12 @@ function resolveMatch(params: { const matches = params.entries.filter((entry) => matchesDirectoryEntry({ channel: params.channel, entry, query: params.query }), ); - if (matches.length === 0) return { kind: "none" as const }; - if (matches.length === 1) return { kind: "single" as const, entry: matches[0] }; + if (matches.length === 0) { + return { kind: "none" as const }; + } + if (matches.length === 1) { + return { kind: "single" as const, entry: matches[0] }; + } return { kind: "ambiguous" as const, entries: matches }; } @@ -187,12 +223,16 @@ async function listDirectoryEntries(params: { }): Promise { const plugin = getChannelPlugin(params.channel); const directory = plugin?.directory; - if (!directory) return []; + if (!directory) { + return []; + } const runtime = params.runtime ?? defaultRuntime; const useLive = params.source === "live"; if (params.kind === "user") { const fn = useLive ? (directory.listPeersLive ?? directory.listPeers) : directory.listPeers; - if (!fn) return []; + if (!fn) { + return []; + } return await fn({ cfg: params.cfg, accountId: params.accountId ?? undefined, @@ -202,7 +242,9 @@ async function listDirectoryEntries(params: { }); } const fn = useLive ? (directory.listGroupsLive ?? directory.listGroups) : directory.listGroups; - if (!fn) return []; + if (!fn) { + return []; + } return await fn({ cfg: params.cfg, accountId: params.accountId ?? undefined, @@ -230,7 +272,9 @@ async function getDirectoryEntries(params: { signature, }); const cached = directoryCache.get(cacheKey, params.cfg); - if (cached) return cached; + if (cached) { + return cached; + } const entries = await listDirectoryEntries({ cfg: params.cfg, channel: params.channel, @@ -269,8 +313,12 @@ function pickAmbiguousMatch( entries: ChannelDirectoryEntry[], mode: ResolveAmbiguousMode, ): ChannelDirectoryEntry | null { - if (entries.length === 0) return null; - if (mode === "first") return entries[0] ?? null; + if (entries.length === 0) { + return null; + } + if (mode === "first") { + return entries[0] ?? null; + } const ranked = entries.map((entry) => ({ entry, rank: typeof entry.rank === "number" ? entry.rank : 0, @@ -300,19 +348,33 @@ export async function resolveMessagingTarget(params: { const normalized = normalizeTargetForProvider(params.channel, raw) ?? raw; const looksLikeTargetId = (): boolean => { const trimmed = raw.trim(); - if (!trimmed) return false; + if (!trimmed) { + return false; + } const lookup = plugin?.messaging?.targetResolver?.looksLikeId; - if (lookup) return lookup(trimmed, normalized); - if (/^(channel|group|user):/i.test(trimmed)) return true; - if (/^[@#]/.test(trimmed)) return true; + if (lookup) { + return lookup(trimmed, normalized); + } + if (/^(channel|group|user):/i.test(trimmed)) { + return true; + } + if (/^[@#]/.test(trimmed)) { + return true; + } if (/^\+?\d{6,}$/.test(trimmed)) { // BlueBubbles/iMessage phone numbers should usually resolve via the directory to a DM chat, // otherwise the provider may pick an existing group containing that handle. - if (params.channel === "bluebubbles" || params.channel === "imessage") return false; + if (params.channel === "bluebubbles" || params.channel === "imessage") { + return false; + } + return true; + } + if (trimmed.includes("@thread")) { + return true; + } + if (/^(conversation|user):/i.test(trimmed)) { return true; } - if (trimmed.includes("@thread")) return true; - if (/^(conversation|user):/i.test(trimmed)) return true; return false; }; if (looksLikeTargetId()) { diff --git a/src/infra/outbound/targets.ts b/src/infra/outbound/targets.ts index 824f27a570..8937896cf6 100644 --- a/src/infra/outbound/targets.ts +++ b/src/infra/outbound/targets.ts @@ -184,7 +184,9 @@ export function resolveHeartbeatDeliveryTarget(params: { target = rawTarget; } else if (typeof rawTarget === "string") { const normalized = normalizeChannelId(rawTarget); - if (normalized) target = normalized; + if (normalized) { + target = normalized; + } } if (target === "none") { @@ -279,12 +281,16 @@ function resolveHeartbeatSenderId(params: { } if (candidates.length > 0 && allowList.length > 0) { const matched = candidates.find((candidate) => allowList.includes(candidate)); - if (matched) return matched; + if (matched) { + return matched; + } } if (candidates.length > 0 && allowList.length === 0) { return candidates[0]; } - if (allowList.length > 0) return allowList[0]; + if (allowList.length > 0) { + return allowList[0]; + } return candidates[0] ?? "heartbeat"; } diff --git a/src/infra/path-env.test.ts b/src/infra/path-env.test.ts index 2b46ccf500..c731389f8e 100644 --- a/src/infra/path-env.test.ts +++ b/src/infra/path-env.test.ts @@ -31,8 +31,11 @@ describe("ensureOpenClawCliOnPath", () => { expect(updated.split(path.delimiter)[0]).toBe(appBinDir); } finally { process.env.PATH = originalPath; - if (originalFlag === undefined) delete process.env.OPENCLAW_PATH_BOOTSTRAPPED; - else process.env.OPENCLAW_PATH_BOOTSTRAPPED = originalFlag; + if (originalFlag === undefined) { + delete process.env.OPENCLAW_PATH_BOOTSTRAPPED; + } else { + process.env.OPENCLAW_PATH_BOOTSTRAPPED = originalFlag; + } } } finally { await fs.rm(tmp, { recursive: true, force: true }); @@ -54,8 +57,11 @@ describe("ensureOpenClawCliOnPath", () => { expect(process.env.PATH).toBe("/bin"); } finally { process.env.PATH = originalPath; - if (originalFlag === undefined) delete process.env.OPENCLAW_PATH_BOOTSTRAPPED; - else process.env.OPENCLAW_PATH_BOOTSTRAPPED = originalFlag; + if (originalFlag === undefined) { + delete process.env.OPENCLAW_PATH_BOOTSTRAPPED; + } else { + process.env.OPENCLAW_PATH_BOOTSTRAPPED = originalFlag; + } } }); @@ -101,10 +107,16 @@ describe("ensureOpenClawCliOnPath", () => { expect(shimsIndex).toBeGreaterThan(localIndex); } finally { process.env.PATH = originalPath; - if (originalFlag === undefined) delete process.env.OPENCLAW_PATH_BOOTSTRAPPED; - else process.env.OPENCLAW_PATH_BOOTSTRAPPED = originalFlag; - if (originalMiseDataDir === undefined) delete process.env.MISE_DATA_DIR; - else process.env.MISE_DATA_DIR = originalMiseDataDir; + if (originalFlag === undefined) { + delete process.env.OPENCLAW_PATH_BOOTSTRAPPED; + } else { + process.env.OPENCLAW_PATH_BOOTSTRAPPED = originalFlag; + } + if (originalMiseDataDir === undefined) { + delete process.env.MISE_DATA_DIR; + } else { + process.env.MISE_DATA_DIR = originalMiseDataDir; + } await fs.rm(tmp, { recursive: true, force: true }); } }); @@ -144,14 +156,26 @@ describe("ensureOpenClawCliOnPath", () => { expect(parts[1]).toBe(linuxbrewSbin); } finally { process.env.PATH = originalPath; - if (originalFlag === undefined) delete process.env.OPENCLAW_PATH_BOOTSTRAPPED; - else process.env.OPENCLAW_PATH_BOOTSTRAPPED = originalFlag; - if (originalHomebrewPrefix === undefined) delete process.env.HOMEBREW_PREFIX; - else process.env.HOMEBREW_PREFIX = originalHomebrewPrefix; - if (originalHomebrewBrewFile === undefined) delete process.env.HOMEBREW_BREW_FILE; - else process.env.HOMEBREW_BREW_FILE = originalHomebrewBrewFile; - if (originalXdgBinHome === undefined) delete process.env.XDG_BIN_HOME; - else process.env.XDG_BIN_HOME = originalXdgBinHome; + if (originalFlag === undefined) { + delete process.env.OPENCLAW_PATH_BOOTSTRAPPED; + } else { + process.env.OPENCLAW_PATH_BOOTSTRAPPED = originalFlag; + } + if (originalHomebrewPrefix === undefined) { + delete process.env.HOMEBREW_PREFIX; + } else { + process.env.HOMEBREW_PREFIX = originalHomebrewPrefix; + } + if (originalHomebrewBrewFile === undefined) { + delete process.env.HOMEBREW_BREW_FILE; + } else { + process.env.HOMEBREW_BREW_FILE = originalHomebrewBrewFile; + } + if (originalXdgBinHome === undefined) { + delete process.env.XDG_BIN_HOME; + } else { + process.env.XDG_BIN_HOME = originalXdgBinHome; + } await fs.rm(tmp, { recursive: true, force: true }); } }); diff --git a/src/infra/path-env.ts b/src/infra/path-env.ts index 24d0d82b91..469dca5da7 100644 --- a/src/infra/path-env.ts +++ b/src/infra/path-env.ts @@ -60,7 +60,9 @@ function candidateBinDirs(opts: EnsureOpenClawPathOpts): string[] { try { const execDir = path.dirname(execPath); const siblingCli = path.join(execDir, "openclaw"); - if (isExecutable(siblingCli)) candidates.push(execDir); + if (isExecutable(siblingCli)) { + candidates.push(execDir); + } } catch { // ignore } @@ -68,11 +70,15 @@ function candidateBinDirs(opts: EnsureOpenClawPathOpts): string[] { // Project-local installs (best effort): if a `node_modules/.bin/openclaw` exists near cwd, // include it. This helps when running under launchd or other minimal PATH environments. const localBinDir = path.join(cwd, "node_modules", ".bin"); - if (isExecutable(path.join(localBinDir, "openclaw"))) candidates.push(localBinDir); + if (isExecutable(path.join(localBinDir, "openclaw"))) { + candidates.push(localBinDir); + } const miseDataDir = process.env.MISE_DATA_DIR ?? path.join(homeDir, ".local", "share", "mise"); const miseShims = path.join(miseDataDir, "shims"); - if (isDirectory(miseShims)) candidates.push(miseShims); + if (isDirectory(miseShims)) { + candidates.push(miseShims); + } candidates.push(...resolveBrewPathDirs({ homeDir })); @@ -80,7 +86,9 @@ function candidateBinDirs(opts: EnsureOpenClawPathOpts): string[] { if (platform === "darwin") { candidates.push(path.join(homeDir, "Library", "pnpm")); } - if (process.env.XDG_BIN_HOME) candidates.push(process.env.XDG_BIN_HOME); + if (process.env.XDG_BIN_HOME) { + candidates.push(process.env.XDG_BIN_HOME); + } candidates.push(path.join(homeDir, ".local", "bin")); candidates.push(path.join(homeDir, ".local", "share", "pnpm")); candidates.push(path.join(homeDir, ".bun", "bin")); @@ -102,8 +110,12 @@ export function ensureOpenClawCliOnPath(opts: EnsureOpenClawPathOpts = {}) { const existing = opts.pathEnv ?? process.env.PATH ?? ""; const prepend = candidateBinDirs(opts); - if (prepend.length === 0) return; + if (prepend.length === 0) { + return; + } const merged = mergePath({ existing, prepend }); - if (merged) process.env.PATH = merged; + if (merged) { + process.env.PATH = merged; + } } diff --git a/src/infra/ports-format.ts b/src/infra/ports-format.ts index 3cada2879e..d8c45dfe27 100644 --- a/src/infra/ports-format.ts +++ b/src/infra/ports-format.ts @@ -11,14 +11,18 @@ export function classifyPortListener(listener: PortListener, port: number): Port const tunnelPattern = new RegExp( `-(l|r)\\s*${portToken}\\b|-(l|r)${portToken}\\b|:${portToken}\\b`, ); - if (!raw || tunnelPattern.test(raw)) return "ssh"; + if (!raw || tunnelPattern.test(raw)) { + return "ssh"; + } return "ssh"; } return "unknown"; } export function buildPortHints(listeners: PortListener[], port: number): string[] { - if (listeners.length === 0) return []; + if (listeners.length === 0) { + return []; + } const kinds = new Set(listeners.map((listener) => classifyPortListener(listener, port))); const hints: string[] = []; if (kinds.has("gateway")) { diff --git a/src/infra/ports-inspect.ts b/src/infra/ports-inspect.ts index 767480cede..4daf1c0cd4 100644 --- a/src/infra/ports-inspect.ts +++ b/src/infra/ports-inspect.ts @@ -39,7 +39,9 @@ function parseLsofFieldOutput(output: string): PortListener[] { let current: PortListener = {}; for (const line of lines) { if (line.startsWith("p")) { - if (current.pid || current.command) listeners.push(current); + if (current.pid || current.command) { + listeners.push(current); + } const pid = Number.parseInt(line.slice(1), 10); current = Number.isFinite(pid) ? { pid } : {}; } else if (line.startsWith("c")) { @@ -47,23 +49,31 @@ function parseLsofFieldOutput(output: string): PortListener[] { } else if (line.startsWith("n")) { // TCP 127.0.0.1:18789 (LISTEN) // TCP *:18789 (LISTEN) - if (!current.address) current.address = line.slice(1); + if (!current.address) { + current.address = line.slice(1); + } } } - if (current.pid || current.command) listeners.push(current); + if (current.pid || current.command) { + listeners.push(current); + } return listeners; } async function resolveUnixCommandLine(pid: number): Promise { const res = await runCommandSafe(["ps", "-p", String(pid), "-o", "command="]); - if (res.code !== 0) return undefined; + if (res.code !== 0) { + return undefined; + } const line = res.stdout.trim(); return line || undefined; } async function resolveUnixUser(pid: number): Promise { const res = await runCommandSafe(["ps", "-p", String(pid), "-o", "user="]); - if (res.code !== 0) return undefined; + if (res.code !== 0) { + return undefined; + } const line = res.stdout.trim(); return line || undefined; } @@ -78,13 +88,19 @@ async function readUnixListeners( const listeners = parseLsofFieldOutput(res.stdout); await Promise.all( listeners.map(async (listener) => { - if (!listener.pid) return; + if (!listener.pid) { + return; + } const [commandLine, user] = await Promise.all([ resolveUnixCommandLine(listener.pid), resolveUnixUser(listener.pid), ]); - if (commandLine) listener.commandLine = commandLine; - if (user) listener.user = user; + if (commandLine) { + listener.commandLine = commandLine; + } + if (user) { + listener.user = user; + } }), ); return { listeners, detail: res.stdout.trim() || undefined, errors }; @@ -93,9 +109,13 @@ async function readUnixListeners( if (res.code === 1 && !res.error && !stderr) { return { listeners: [], detail: undefined, errors }; } - if (res.error) errors.push(res.error); + if (res.error) { + errors.push(res.error); + } const detail = [stderr, res.stdout.trim()].filter(Boolean).join("\n"); - if (detail) errors.push(detail); + if (detail) { + errors.push(detail); + } return { listeners: [], detail: undefined, errors }; } @@ -104,17 +124,29 @@ function parseNetstatListeners(output: string, port: number): PortListener[] { const portToken = `:${port}`; for (const rawLine of output.split(/\r?\n/)) { const line = rawLine.trim(); - if (!line) continue; - if (!line.toLowerCase().includes("listen")) continue; - if (!line.includes(portToken)) continue; + if (!line) { + continue; + } + if (!line.toLowerCase().includes("listen")) { + continue; + } + if (!line.includes(portToken)) { + continue; + } const parts = line.split(/\s+/); - if (parts.length < 4) continue; + if (parts.length < 4) { + continue; + } const pidRaw = parts.at(-1); const pid = pidRaw ? Number.parseInt(pidRaw, 10) : NaN; const localAddr = parts[1]; const listener: PortListener = {}; - if (Number.isFinite(pid)) listener.pid = pid; - if (localAddr?.includes(portToken)) listener.address = localAddr; + if (Number.isFinite(pid)) { + listener.pid = pid; + } + if (localAddr?.includes(portToken)) { + listener.address = localAddr; + } listeners.push(listener); } return listeners; @@ -122,10 +154,14 @@ function parseNetstatListeners(output: string, port: number): PortListener[] { async function resolveWindowsImageName(pid: number): Promise { const res = await runCommandSafe(["tasklist", "/FI", `PID eq ${pid}`, "/FO", "LIST"]); - if (res.code !== 0) return undefined; + if (res.code !== 0) { + return undefined; + } for (const rawLine of res.stdout.split(/\r?\n/)) { const line = rawLine.trim(); - if (!line.toLowerCase().startsWith("image name:")) continue; + if (!line.toLowerCase().startsWith("image name:")) { + continue; + } const value = line.slice("image name:".length).trim(); return value || undefined; } @@ -142,10 +178,14 @@ async function resolveWindowsCommandLine(pid: number): Promise { - if (!listener.pid) return; + if (!listener.pid) { + return; + } const [imageName, commandLine] = await Promise.all([ resolveWindowsImageName(listener.pid), resolveWindowsCommandLine(listener.pid), ]); - if (imageName) listener.command = imageName; - if (commandLine) listener.commandLine = commandLine; + if (imageName) { + listener.command = imageName; + } + if (commandLine) { + listener.commandLine = commandLine; + } }), ); return { listeners, detail: res.stdout.trim() || undefined, errors }; @@ -191,7 +241,9 @@ async function tryListenOnHost(port: number, host: string): Promise { let sawUnknown = false; for (const host of hosts) { const result = await tryListenOnHost(port, host); - if (result === "busy") return "busy"; - if (result === "unknown") sawUnknown = true; + if (result === "busy") { + return "busy"; + } + if (result === "unknown") { + sawUnknown = true; + } } return sawUnknown ? "unknown" : "free"; } diff --git a/src/infra/ports-lsof.ts b/src/infra/ports-lsof.ts index 4b5f01a6a7..1409103e32 100644 --- a/src/infra/ports-lsof.ts +++ b/src/infra/ports-lsof.ts @@ -17,7 +17,9 @@ async function canExecute(path: string): Promise { export async function resolveLsofCommand(): Promise { for (const candidate of LSOF_CANDIDATES) { - if (await canExecute(candidate)) return candidate; + if (await canExecute(candidate)) { + return candidate; + } } return "lsof"; } diff --git a/src/infra/ports.ts b/src/infra/ports.ts index 37030ca9b8..697b74325f 100644 --- a/src/infra/ports.ts +++ b/src/infra/ports.ts @@ -25,7 +25,9 @@ function isErrno(err: unknown): err is NodeJS.ErrnoException { export async function describePortOwner(port: number): Promise { const diagnostics = await inspectPortUsage(port); - if (diagnostics.listeners.length === 0) return undefined; + if (diagnostics.listeners.length === 0) { + return undefined; + } return formatPortDiagnostics(diagnostics).join("\n"); } @@ -80,8 +82,12 @@ export async function handlePortError( if (shouldLogVerbose()) { const stdout = (err as { stdout?: string })?.stdout; const stderr = (err as { stderr?: string })?.stderr; - if (stdout?.trim()) logDebug(`stdout: ${stdout.trim()}`); - if (stderr?.trim()) logDebug(`stderr: ${stderr.trim()}`); + if (stdout?.trim()) { + logDebug(`stdout: ${stdout.trim()}`); + } + if (stderr?.trim()) { + logDebug(`stderr: ${stderr.trim()}`); + } } return runtime.exit(1); } diff --git a/src/infra/provider-usage.auth.ts b/src/infra/provider-usage.auth.ts index e0d9a6ef9b..53479f563d 100644 --- a/src/infra/provider-usage.auth.ts +++ b/src/infra/provider-usage.auth.ts @@ -20,7 +20,9 @@ export type ProviderAuth = { }; function parseGoogleToken(apiKey: string): { token: string } | null { - if (!apiKey) return null; + if (!apiKey) { + return null; + } try { const parsed = JSON.parse(apiKey) as { token?: unknown }; if (parsed && typeof parsed.token === "string") { @@ -34,14 +36,20 @@ function parseGoogleToken(apiKey: string): { token: string } | null { function resolveZaiApiKey(): string | undefined { const envDirect = process.env.ZAI_API_KEY?.trim() || process.env.Z_AI_API_KEY?.trim(); - if (envDirect) return envDirect; + if (envDirect) { + return envDirect; + } const envResolved = resolveEnvApiKey("zai"); - if (envResolved?.apiKey) return envResolved.apiKey; + if (envResolved?.apiKey) { + return envResolved.apiKey; + } const cfg = loadConfig(); const key = getCustomProviderApiKey(cfg, "zai") || getCustomProviderApiKey(cfg, "z-ai"); - if (key) return key; + if (key) { + return key; + } const store = ensureAuthProfileStore(); const apiProfile = [ @@ -57,7 +65,9 @@ function resolveZaiApiKey(): string | undefined { try { const authPath = path.join(os.homedir(), ".pi", "agent", "auth.json"); - if (!fs.existsSync(authPath)) return undefined; + if (!fs.existsSync(authPath)) { + return undefined; + } const data = JSON.parse(fs.readFileSync(authPath, "utf-8")) as Record< string, { access?: string } @@ -71,21 +81,29 @@ function resolveZaiApiKey(): string | undefined { function resolveMinimaxApiKey(): string | undefined { const envDirect = process.env.MINIMAX_CODE_PLAN_KEY?.trim() || process.env.MINIMAX_API_KEY?.trim(); - if (envDirect) return envDirect; + if (envDirect) { + return envDirect; + } const envResolved = resolveEnvApiKey("minimax"); - if (envResolved?.apiKey) return envResolved.apiKey; + if (envResolved?.apiKey) { + return envResolved.apiKey; + } const cfg = loadConfig(); const key = getCustomProviderApiKey(cfg, "minimax"); - if (key) return key; + if (key) { + return key; + } const store = ensureAuthProfileStore(); const apiProfile = listProfilesForProvider(store, "minimax").find((id) => { const cred = store.profiles[id]; return cred?.type === "api_key" || cred?.type === "token"; }); - if (!apiProfile) return undefined; + if (!apiProfile) { + return undefined; + } const cred = store.profiles[apiProfile]; if (cred?.type === "api_key" && cred.key?.trim()) { return cred.key.trim(); @@ -98,21 +116,29 @@ function resolveMinimaxApiKey(): string | undefined { function resolveXiaomiApiKey(): string | undefined { const envDirect = process.env.XIAOMI_API_KEY?.trim(); - if (envDirect) return envDirect; + if (envDirect) { + return envDirect; + } const envResolved = resolveEnvApiKey("xiaomi"); - if (envResolved?.apiKey) return envResolved.apiKey; + if (envResolved?.apiKey) { + return envResolved.apiKey; + } const cfg = loadConfig(); const key = getCustomProviderApiKey(cfg, "xiaomi"); - if (key) return key; + if (key) { + return key; + } const store = ensureAuthProfileStore(); const apiProfile = listProfilesForProvider(store, "xiaomi").find((id) => { const cred = store.profiles[id]; return cred?.type === "api_key" || cred?.type === "token"; }); - if (!apiProfile) return undefined; + if (!apiProfile) { + return undefined; + } const cred = store.profiles[apiProfile]; if (cred?.type === "api_key" && cred.key?.trim()) { return cred.key.trim(); @@ -140,12 +166,16 @@ async function resolveOAuthToken(params: { const candidates = order; const deduped: string[] = []; for (const entry of candidates) { - if (!deduped.includes(entry)) deduped.push(entry); + if (!deduped.includes(entry)) { + deduped.push(entry); + } } for (const profileId of deduped) { const cred = store.profiles[profileId]; - if (!cred || (cred.type !== "oauth" && cred.type !== "token")) continue; + if (!cred || (cred.type !== "oauth" && cred.type !== "token")) { + continue; + } try { const resolved = await resolveApiKeyForProfile({ // Usage snapshots should work even if config profile metadata is stale. @@ -155,7 +185,9 @@ async function resolveOAuthToken(params: { profileId, agentDir: params.agentDir, }); - if (!resolved?.apiKey) continue; + if (!resolved?.apiKey) { + continue; + } let token = resolved.apiKey; if (params.provider === "google-gemini-cli" || params.provider === "google-antigravity") { const parsed = parseGoogleToken(resolved.apiKey); @@ -195,7 +227,9 @@ function resolveOAuthProviders(agentDir?: string): UsageProviderId[] { }; return providers.filter((provider) => { const profiles = listProfilesForProvider(store, provider).filter(isOAuthLikeCredential); - if (profiles.length > 0) return true; + if (profiles.length > 0) { + return true; + } const normalized = normalizeProviderId(provider); const configuredProfiles = Object.entries(cfg.auth?.profiles ?? {}) .filter(([, profile]) => normalizeProviderId(profile.provider) === normalized) @@ -210,7 +244,9 @@ export async function resolveProviderAuths(params: { auth?: ProviderAuth[]; agentDir?: string; }): Promise { - if (params.auth) return params.auth; + if (params.auth) { + return params.auth; + } const oauthProviders = resolveOAuthProviders(params.agentDir); const auths: ProviderAuth[] = []; @@ -218,26 +254,36 @@ export async function resolveProviderAuths(params: { for (const provider of params.providers) { if (provider === "zai") { const apiKey = resolveZaiApiKey(); - if (apiKey) auths.push({ provider, token: apiKey }); + if (apiKey) { + auths.push({ provider, token: apiKey }); + } continue; } if (provider === "minimax") { const apiKey = resolveMinimaxApiKey(); - if (apiKey) auths.push({ provider, token: apiKey }); + if (apiKey) { + auths.push({ provider, token: apiKey }); + } continue; } if (provider === "xiaomi") { const apiKey = resolveXiaomiApiKey(); - if (apiKey) auths.push({ provider, token: apiKey }); + if (apiKey) { + auths.push({ provider, token: apiKey }); + } continue; } - if (!oauthProviders.includes(provider)) continue; + if (!oauthProviders.includes(provider)) { + continue; + } const auth = await resolveOAuthToken({ provider, agentDir: params.agentDir, }); - if (auth) auths.push(auth); + if (auth) { + auths.push(auth); + } } return auths; diff --git a/src/infra/provider-usage.fetch.antigravity.ts b/src/infra/provider-usage.fetch.antigravity.ts index b40b6d91eb..fe4fd9de10 100644 --- a/src/infra/provider-usage.fetch.antigravity.ts +++ b/src/infra/provider-usage.fetch.antigravity.ts @@ -46,19 +46,27 @@ const METADATA = { }; function parseNumber(value: number | string | undefined): number | undefined { - if (typeof value === "number" && Number.isFinite(value)) return value; + if (typeof value === "number" && Number.isFinite(value)) { + return value; + } if (typeof value === "string") { const parsed = Number.parseFloat(value); - if (Number.isFinite(parsed)) return parsed; + if (Number.isFinite(parsed)) { + return parsed; + } } return undefined; } function parseEpochMs(isoString: string | undefined): number | undefined { - if (!isoString?.trim()) return undefined; + if (!isoString?.trim()) { + return undefined; + } try { const ms = Date.parse(isoString); - if (Number.isFinite(ms)) return ms; + if (Number.isFinite(ms)) { + return ms; + } } catch { // ignore parse errors } @@ -69,7 +77,9 @@ async function parseErrorMessage(res: Response): Promise { try { const data = (await res.json()) as { error?: { message?: string } }; const message = data?.error?.message?.trim(); - if (message) return message; + if (message) { + return message; + } } catch { // ignore parse errors } @@ -79,36 +89,52 @@ async function parseErrorMessage(res: Response): Promise { function extractCredits(data: LoadCodeAssistResponse): CreditsInfo | undefined { const available = parseNumber(data.availablePromptCredits); const monthly = parseNumber(data.planInfo?.monthlyPromptCredits); - if (available === undefined || monthly === undefined || monthly <= 0) return undefined; + if (available === undefined || monthly === undefined || monthly <= 0) { + return undefined; + } return { available, monthly }; } function extractPlanInfo(data: LoadCodeAssistResponse): string | undefined { const tierName = data.currentTier?.name?.trim(); - if (tierName) return tierName; + if (tierName) { + return tierName; + } const planType = data.planType?.trim(); - if (planType) return planType; + if (planType) { + return planType; + } return undefined; } function extractProjectId(data: LoadCodeAssistResponse): string | undefined { const project = data.cloudaicompanionProject; - if (!project) return undefined; - if (typeof project === "string") return project.trim() ? project : undefined; + if (!project) { + return undefined; + } + if (typeof project === "string") { + return project.trim() ? project : undefined; + } const projectId = typeof project.id === "string" ? project.id.trim() : undefined; return projectId || undefined; } function extractModelQuotas(data: FetchAvailableModelsResponse): Map { const result = new Map(); - if (!data.models || typeof data.models !== "object") return result; + if (!data.models || typeof data.models !== "object") { + return result; + } for (const [modelId, modelInfo] of Object.entries(data.models)) { const quotaInfo = modelInfo.quotaInfo; - if (!quotaInfo) continue; + if (!quotaInfo) { + continue; + } const remainingFraction = parseNumber(quotaInfo.remainingFraction); - if (remainingFraction === undefined) continue; + if (remainingFraction === undefined) { + continue; + } const resetTime = parseEpochMs(quotaInfo.resetTime); result.set(modelId, { remainingFraction, resetTime }); @@ -145,7 +171,9 @@ function buildUsageWindows(opts: { const usedPercent = clampPercent((1 - quota.remainingFraction) * 100); const window: UsageWindow = { label: modelId, usedPercent }; - if (quota.resetTime) window.resetAt = quota.resetTime; + if (quota.resetTime) { + window.resetAt = quota.resetTime; + } modelWindows.push(window); } @@ -251,7 +279,9 @@ export async function fetchAntigravityUsage( } } } catch { - if (!lastError) lastError = "Network error"; + if (!lastError) { + lastError = "Network error"; + } } // Build windows from available data diff --git a/src/infra/provider-usage.fetch.claude.ts b/src/infra/provider-usage.fetch.claude.ts index 654962c934..9093bc6ef9 100644 --- a/src/infra/provider-usage.fetch.claude.ts +++ b/src/infra/provider-usage.fetch.claude.ts @@ -19,10 +19,14 @@ type ClaudeWebUsageResponse = ClaudeUsageResponse; function resolveClaudeWebSessionKey(): string | undefined { const direct = process.env.CLAUDE_AI_SESSION_KEY?.trim() ?? process.env.CLAUDE_WEB_SESSION_KEY?.trim(); - if (direct?.startsWith("sk-ant-")) return direct; + if (direct?.startsWith("sk-ant-")) { + return direct; + } const cookieHeader = process.env.CLAUDE_WEB_COOKIE?.trim(); - if (!cookieHeader) return undefined; + if (!cookieHeader) { + return undefined; + } const stripped = cookieHeader.replace(/^cookie:\\s*/i, ""); const match = stripped.match(/(?:^|;\\s*)sessionKey=([^;\\s]+)/i); const value = match?.[1]?.trim(); @@ -45,11 +49,15 @@ async function fetchClaudeWebUsage( timeoutMs, fetchFn, ); - if (!orgRes.ok) return null; + if (!orgRes.ok) { + return null; + } const orgs = (await orgRes.json()) as ClaudeWebOrganizationsResponse; const orgId = orgs?.[0]?.uuid?.trim(); - if (!orgId) return null; + if (!orgId) { + return null; + } const usageRes = await fetchJson( `https://claude.ai/api/organizations/${orgId}/usage`, @@ -57,7 +65,9 @@ async function fetchClaudeWebUsage( timeoutMs, fetchFn, ); - if (!usageRes.ok) return null; + if (!usageRes.ok) { + return null; + } const data = (await usageRes.json()) as ClaudeWebUsageResponse; const windows: UsageWindow[] = []; @@ -86,7 +96,9 @@ async function fetchClaudeWebUsage( }); } - if (windows.length === 0) return null; + if (windows.length === 0) { + return null; + } return { provider: "anthropic", displayName: PROVIDER_LABELS.anthropic, @@ -121,7 +133,9 @@ export async function fetchClaudeUsage( error?: { message?: unknown } | null; }; const raw = data?.error?.message; - if (typeof raw === "string" && raw.trim()) message = raw.trim(); + if (typeof raw === "string" && raw.trim()) { + message = raw.trim(); + } } catch { // ignore parse errors } @@ -133,7 +147,9 @@ export async function fetchClaudeUsage( const sessionKey = resolveClaudeWebSessionKey(); if (sessionKey) { const web = await fetchClaudeWebUsage(sessionKey, timeoutMs, fetchFn); - if (web) return web; + if (web) { + return web; + } } } diff --git a/src/infra/provider-usage.fetch.codex.ts b/src/infra/provider-usage.fetch.codex.ts index 4a3a1cf4e1..6078c95e13 100644 --- a/src/infra/provider-usage.fetch.codex.ts +++ b/src/infra/provider-usage.fetch.codex.ts @@ -30,7 +30,9 @@ export async function fetchCodexUsage( "User-Agent": "CodexBar", Accept: "application/json", }; - if (accountId) headers["ChatGPT-Account-Id"] = accountId; + if (accountId) { + headers["ChatGPT-Account-Id"] = accountId; + } const res = await fetchJson( "https://chatgpt.com/backend-api/wham/usage", diff --git a/src/infra/provider-usage.fetch.gemini.ts b/src/infra/provider-usage.fetch.gemini.ts index 5c6fe244db..39a5806417 100644 --- a/src/infra/provider-usage.fetch.gemini.ts +++ b/src/infra/provider-usage.fetch.gemini.ts @@ -45,7 +45,9 @@ export async function fetchGeminiUsage( for (const bucket of data.buckets || []) { const model = bucket.modelId || "unknown"; const frac = bucket.remainingFraction ?? 1; - if (!quotas[model] || frac < quotas[model]) quotas[model] = frac; + if (!quotas[model] || frac < quotas[model]) { + quotas[model] = frac; + } } const windows: UsageWindow[] = []; @@ -58,24 +60,30 @@ export async function fetchGeminiUsage( const lower = model.toLowerCase(); if (lower.includes("pro")) { hasPro = true; - if (frac < proMin) proMin = frac; + if (frac < proMin) { + proMin = frac; + } } if (lower.includes("flash")) { hasFlash = true; - if (frac < flashMin) flashMin = frac; + if (frac < flashMin) { + flashMin = frac; + } } } - if (hasPro) + if (hasPro) { windows.push({ label: "Pro", usedPercent: clampPercent((1 - proMin) * 100), }); - if (hasFlash) + } + if (hasFlash) { windows.push({ label: "Flash", usedPercent: clampPercent((1 - flashMin) * 100), }); + } return { provider, displayName: PROVIDER_LABELS[provider], windows }; } diff --git a/src/infra/provider-usage.fetch.minimax.ts b/src/infra/provider-usage.fetch.minimax.ts index 94f2bfcfd7..ffcd644ebe 100644 --- a/src/infra/provider-usage.fetch.minimax.ts +++ b/src/infra/provider-usage.fetch.minimax.ts @@ -155,10 +155,14 @@ function isRecord(value: unknown): value is Record { function pickNumber(record: Record, keys: readonly string[]): number | undefined { for (const key of keys) { const value = record[key]; - if (typeof value === "number" && Number.isFinite(value)) return value; + if (typeof value === "number" && Number.isFinite(value)) { + return value; + } if (typeof value === "string") { const parsed = Number.parseFloat(value); - if (Number.isFinite(parsed)) return parsed; + if (Number.isFinite(parsed)) { + return parsed; + } } } return undefined; @@ -167,19 +171,25 @@ function pickNumber(record: Record, keys: readonly string[]): n function pickString(record: Record, keys: readonly string[]): string | undefined { for (const key of keys) { const value = record[key]; - if (typeof value === "string" && value.trim()) return value.trim(); + if (typeof value === "string" && value.trim()) { + return value.trim(); + } } return undefined; } function parseEpoch(value: unknown): number | undefined { if (typeof value === "number" && Number.isFinite(value)) { - if (value < 1e12) return Math.floor(value * 1000); + if (value < 1e12) { + return Math.floor(value * 1000); + } return Math.floor(value); } if (typeof value === "string" && value.trim()) { const parsed = Date.parse(value); - if (Number.isFinite(parsed)) return parsed; + if (Number.isFinite(parsed)) { + return parsed; + } } return undefined; } @@ -190,11 +200,21 @@ function hasAny(record: Record, keys: readonly string[]): boole function scoreUsageRecord(record: Record): number { let score = 0; - if (hasAny(record, PERCENT_KEYS)) score += 4; - if (hasAny(record, TOTAL_KEYS)) score += 3; - if (hasAny(record, USED_KEYS) || hasAny(record, REMAINING_KEYS)) score += 2; - if (hasAny(record, RESET_KEYS)) score += 1; - if (hasAny(record, PLAN_KEYS)) score += 1; + if (hasAny(record, PERCENT_KEYS)) { + score += 4; + } + if (hasAny(record, TOTAL_KEYS)) { + score += 3; + } + if (hasAny(record, USED_KEYS) || hasAny(record, REMAINING_KEYS)) { + score += 2; + } + if (hasAny(record, RESET_KEYS)) { + score += 1; + } + if (hasAny(record, PLAN_KEYS)) { + score += 1; + } return score; } @@ -208,15 +228,21 @@ function collectUsageCandidates(root: Record): Record 0) candidates.push({ record: value, score, depth }); + if (score > 0) { + candidates.push({ record: value, score, depth }); + } if (depth < MAX_SCAN_DEPTH) { for (const nested of Object.values(value)) { if (isRecord(nested) || Array.isArray(nested)) { @@ -242,9 +268,13 @@ function collectUsageCandidates(root: Record): Record): string { const hours = pickNumber(payload, WINDOW_HOUR_KEYS); - if (hours && Number.isFinite(hours)) return `${hours}h`; + if (hours && Number.isFinite(hours)) { + return `${hours}h`; + } const minutes = pickNumber(payload, WINDOW_MINUTE_KEYS); - if (minutes && Number.isFinite(minutes)) return `${minutes}m`; + if (minutes && Number.isFinite(minutes)) { + return `${minutes}m`; + } return "5h"; } diff --git a/src/infra/provider-usage.fetch.zai.ts b/src/infra/provider-usage.fetch.zai.ts index 03237f2795..97a7a9a90e 100644 --- a/src/infra/provider-usage.fetch.zai.ts +++ b/src/infra/provider-usage.fetch.zai.ts @@ -63,9 +63,13 @@ export async function fetchZaiUsage( const percent = clampPercent(limit.percentage || 0); const nextReset = limit.nextResetTime ? new Date(limit.nextResetTime).getTime() : undefined; let windowLabel = "Limit"; - if (limit.unit === 1) windowLabel = `${limit.number}d`; - else if (limit.unit === 3) windowLabel = `${limit.number}h`; - else if (limit.unit === 5) windowLabel = `${limit.number}m`; + if (limit.unit === 1) { + windowLabel = `${limit.number}d`; + } else if (limit.unit === 3) { + windowLabel = `${limit.number}h`; + } else if (limit.unit === 5) { + windowLabel = `${limit.number}m`; + } if (limit.type === "TOKENS_LIMIT") { windows.push({ diff --git a/src/infra/provider-usage.format.ts b/src/infra/provider-usage.format.ts index d108790089..3b02828f49 100644 --- a/src/infra/provider-usage.format.ts +++ b/src/infra/provider-usage.format.ts @@ -2,20 +2,30 @@ import { clampPercent } from "./provider-usage.shared.js"; import type { ProviderUsageSnapshot, UsageSummary, UsageWindow } from "./provider-usage.types.js"; function formatResetRemaining(targetMs?: number, now?: number): string | null { - if (!targetMs) return null; + if (!targetMs) { + return null; + } const base = now ?? Date.now(); const diffMs = targetMs - base; - if (diffMs <= 0) return "now"; + if (diffMs <= 0) { + return "now"; + } const diffMins = Math.floor(diffMs / 60000); - if (diffMins < 60) return `${diffMins}m`; + if (diffMins < 60) { + return `${diffMins}m`; + } const hours = Math.floor(diffMins / 60); const mins = diffMins % 60; - if (hours < 24) return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`; + if (hours < 24) { + return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`; + } const days = Math.floor(hours / 24); - if (days < 7) return `${days}d ${hours % 24}h`; + if (days < 7) { + return `${days}d ${hours % 24}h`; + } return new Intl.DateTimeFormat("en-US", { month: "short", @@ -24,7 +34,9 @@ function formatResetRemaining(targetMs?: number, now?: number): string | null { } function pickPrimaryWindow(windows: UsageWindow[]): UsageWindow | undefined { - if (windows.length === 0) return undefined; + if (windows.length === 0) { + return undefined; + } return windows.reduce((best, next) => (next.usedPercent > best.usedPercent ? next : best)); } @@ -39,8 +51,12 @@ export function formatUsageWindowSummary( snapshot: ProviderUsageSnapshot, opts?: { now?: number; maxWindows?: number; includeResets?: boolean }, ): string | null { - if (snapshot.error) return null; - if (snapshot.windows.length === 0) return null; + if (snapshot.error) { + return null; + } + if (snapshot.windows.length === 0) { + return null; + } const now = opts?.now ?? Date.now(); const maxWindows = typeof opts?.maxWindows === "number" && opts.maxWindows > 0 @@ -64,17 +80,23 @@ export function formatUsageSummaryLine( const providers = summary.providers .filter((entry) => entry.windows.length > 0 && !entry.error) .slice(0, opts?.maxProviders ?? summary.providers.length); - if (providers.length === 0) return null; + if (providers.length === 0) { + return null; + } const parts = providers .map((entry) => { const window = pickPrimaryWindow(entry.windows); - if (!window) return null; + if (!window) { + return null; + } return `${entry.displayName} ${formatWindowShort(window, opts?.now)}`; }) .filter(Boolean) as string[]; - if (parts.length === 0) return null; + if (parts.length === 0) { + return null; + } return `📊 Usage: ${parts.join(" · ")}`; } diff --git a/src/infra/provider-usage.load.ts b/src/infra/provider-usage.load.ts index 5eb101d85b..519b67c0c4 100644 --- a/src/infra/provider-usage.load.ts +++ b/src/infra/provider-usage.load.ts @@ -95,8 +95,12 @@ export async function loadProviderUsageSummary( const snapshots = await Promise.all(tasks); const providers = snapshots.filter((entry) => { - if (entry.windows.length > 0) return true; - if (!entry.error) return true; + if (entry.windows.length > 0) { + return true; + } + if (!entry.error) { + return true; + } return !ignoredErrors.has(entry.error); }); diff --git a/src/infra/provider-usage.shared.ts b/src/infra/provider-usage.shared.ts index 55eca4757c..763eca4e8a 100644 --- a/src/infra/provider-usage.shared.ts +++ b/src/infra/provider-usage.shared.ts @@ -26,7 +26,9 @@ export const usageProviders: UsageProviderId[] = [ ]; export function resolveUsageProviderId(provider?: string | null): UsageProviderId | undefined { - if (!provider) return undefined; + if (!provider) { + return undefined; + } const normalized = normalizeProviderId(provider); return usageProviders.includes(normalized as UsageProviderId) ? (normalized as UsageProviderId) @@ -54,6 +56,8 @@ export const withTimeout = async (work: Promise, ms: number, fallback: T): }), ]); } finally { - if (timeout) clearTimeout(timeout); + if (timeout) { + clearTimeout(timeout); + } } }; diff --git a/src/infra/provider-usage.test.ts b/src/infra/provider-usage.test.ts index 5bc6ba5754..43e543a868 100644 --- a/src/infra/provider-usage.test.ts +++ b/src/infra/provider-usage.test.ts @@ -383,8 +383,11 @@ describe("provider usage loading", () => { expect(claude?.windows.some((w) => w.label === "5h")).toBe(true); expect(claude?.windows.some((w) => w.label === "Week")).toBe(true); } finally { - if (cookieSnapshot === undefined) delete process.env.CLAUDE_AI_SESSION_KEY; - else process.env.CLAUDE_AI_SESSION_KEY = cookieSnapshot; + if (cookieSnapshot === undefined) { + delete process.env.CLAUDE_AI_SESSION_KEY; + } else { + process.env.CLAUDE_AI_SESSION_KEY = cookieSnapshot; + } } }); }); diff --git a/src/infra/restart-sentinel.test.ts b/src/infra/restart-sentinel.test.ts index 030dea65d0..50525f9ea4 100644 --- a/src/infra/restart-sentinel.test.ts +++ b/src/infra/restart-sentinel.test.ts @@ -22,8 +22,11 @@ describe("restart sentinel", () => { }); afterEach(async () => { - if (prevStateDir) process.env.OPENCLAW_STATE_DIR = prevStateDir; - else delete process.env.OPENCLAW_STATE_DIR; + if (prevStateDir) { + process.env.OPENCLAW_STATE_DIR = prevStateDir; + } else { + delete process.env.OPENCLAW_STATE_DIR; + } await fs.rm(tempDir, { recursive: true, force: true }); }); diff --git a/src/infra/restart-sentinel.ts b/src/infra/restart-sentinel.ts index 3d2ad07fd8..e62b4e58fe 100644 --- a/src/infra/restart-sentinel.ts +++ b/src/infra/restart-sentinel.ts @@ -102,7 +102,9 @@ export async function consumeRestartSentinel( ): Promise { const filePath = resolveRestartSentinelPath(env); const parsed = await readRestartSentinel(env); - if (!parsed) return null; + if (!parsed) { + return null; + } await fs.unlink(filePath).catch(() => {}); return parsed; } @@ -119,8 +121,12 @@ export function summarizeRestartSentinel(payload: RestartSentinelPayload): strin } export function trimLogTail(input?: string | null, maxChars = 8000) { - if (!input) return null; + if (!input) { + return null; + } const text = input.trimEnd(); - if (text.length <= maxChars) return text; + if (text.length <= maxChars) { + return text; + } return `…${text.slice(text.length - maxChars)}`; } diff --git a/src/infra/restart.ts b/src/infra/restart.ts index 543c875ff5..d671c112b5 100644 --- a/src/infra/restart.ts +++ b/src/infra/restart.ts @@ -19,8 +19,12 @@ let sigusr1AuthorizedUntil = 0; let sigusr1ExternalAllowed = false; function resetSigusr1AuthorizationIfExpired(now = Date.now()) { - if (sigusr1AuthorizedCount <= 0) return; - if (now <= sigusr1AuthorizedUntil) return; + if (sigusr1AuthorizedCount <= 0) { + return; + } + if (now <= sigusr1AuthorizedUntil) { + return; + } sigusr1AuthorizedCount = 0; sigusr1AuthorizedUntil = 0; } @@ -44,7 +48,9 @@ export function authorizeGatewaySigusr1Restart(delayMs = 0) { export function consumeGatewaySigusr1RestartAuthorization(): boolean { resetSigusr1AuthorizationIfExpired(); - if (sigusr1AuthorizedCount <= 0) return false; + if (sigusr1AuthorizedCount <= 0) { + return false; + } sigusr1AuthorizedCount -= 1; if (sigusr1AuthorizedCount <= 0) { sigusr1AuthorizedUntil = 0; @@ -63,8 +69,12 @@ function formatSpawnDetail(result: { return text.replace(/\s+/g, " ").trim(); }; if (result.error) { - if (result.error instanceof Error) return result.error.message; - if (typeof result.error === "string") return result.error; + if (result.error instanceof Error) { + return result.error.message; + } + if (typeof result.error === "string") { + return result.error; + } try { return JSON.stringify(result.error); } catch { @@ -72,10 +82,16 @@ function formatSpawnDetail(result: { } } const stderr = clean(result.stderr); - if (stderr) return stderr; + if (stderr) { + return stderr; + } const stdout = clean(result.stdout); - if (stdout) return stdout; - if (typeof result.status === "number") return `exit ${result.status}`; + if (stdout) { + return stdout; + } + if (typeof result.status === "number") { + return `exit ${result.status}`; + } return "unknown error"; } diff --git a/src/infra/retry-policy.ts b/src/infra/retry-policy.ts index 6d647aa5e6..bdd1790a5c 100644 --- a/src/infra/retry-policy.ts +++ b/src/infra/retry-policy.ts @@ -22,7 +22,9 @@ export const TELEGRAM_RETRY_DEFAULTS = { const TELEGRAM_RETRY_RE = /429|timeout|connect|reset|closed|unavailable|temporarily/i; function getTelegramRetryAfterMs(err: unknown): number | undefined { - if (!err || typeof err !== "object") return undefined; + if (!err || typeof err !== "object") { + return undefined; + } const candidate = "parameters" in err && err.parameters && typeof err.parameters === "object" ? (err.parameters as { retry_after?: unknown }).retry_after diff --git a/src/infra/retry.ts b/src/infra/retry.ts index 7918cf5771..dff51fd49e 100644 --- a/src/infra/retry.ts +++ b/src/infra/retry.ts @@ -34,7 +34,9 @@ const asFiniteNumber = (value: unknown): number | undefined => const clampNumber = (value: unknown, fallback: number, min?: number, max?: number) => { const next = asFiniteNumber(value); - if (next === undefined) return fallback; + if (next === undefined) { + return fallback; + } const floor = typeof min === "number" ? min : Number.NEGATIVE_INFINITY; const ceiling = typeof max === "number" ? max : Number.POSITIVE_INFINITY; return Math.min(Math.max(next, floor), ceiling); @@ -58,7 +60,9 @@ export function resolveRetryConfig( } function applyJitter(delayMs: number, jitter: number): number { - if (jitter <= 0) return delayMs; + if (jitter <= 0) { + return delayMs; + } const offset = (Math.random() * 2 - 1) * jitter; return Math.max(0, Math.round(delayMs * (1 + offset))); } @@ -76,7 +80,9 @@ export async function retryAsync( return await fn(); } catch (err) { lastErr = err; - if (i === attempts - 1) break; + if (i === attempts - 1) { + break; + } const delay = initialDelayMs * 2 ** i; await sleep(delay); } @@ -102,7 +108,9 @@ export async function retryAsync( return await fn(); } catch (err) { lastErr = err; - if (attempt >= maxAttempts || !shouldRetry(err, attempt)) break; + if (attempt >= maxAttempts || !shouldRetry(err, attempt)) { + break; + } const retryAfterMs = options.retryAfterMs?.(err); const hasRetryAfter = typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs); diff --git a/src/infra/runtime-guard.ts b/src/infra/runtime-guard.ts index 0c3140bf27..c6bde581d7 100644 --- a/src/infra/runtime-guard.ts +++ b/src/infra/runtime-guard.ts @@ -22,9 +22,13 @@ export type RuntimeDetails = { const SEMVER_RE = /(\d+)\.(\d+)\.(\d+)/; export function parseSemver(version: string | null): Semver | null { - if (!version) return null; + if (!version) { + return null; + } const match = version.match(SEMVER_RE); - if (!match) return null; + if (!match) { + return null; + } const [, major, minor, patch] = match; return { major: Number.parseInt(major, 10), @@ -34,9 +38,15 @@ export function parseSemver(version: string | null): Semver | null { } export function isAtLeast(version: Semver | null, minimum: Semver): boolean { - if (!version) return false; - if (version.major !== minimum.major) return version.major > minimum.major; - if (version.minor !== minimum.minor) return version.minor > minimum.minor; + if (!version) { + return false; + } + if (version.major !== minimum.major) { + return version.major > minimum.major; + } + if (version.minor !== minimum.minor) { + return version.minor > minimum.minor; + } return version.patch >= minimum.patch; } @@ -54,7 +64,9 @@ export function detectRuntime(): RuntimeDetails { export function runtimeSatisfies(details: RuntimeDetails): boolean { const parsed = parseSemver(details.version); - if (details.kind === "node") return isAtLeast(parsed, MIN_NODE); + if (details.kind === "node") { + return isAtLeast(parsed, MIN_NODE); + } return false; } @@ -66,7 +78,9 @@ export function assertSupportedRuntime( runtime: RuntimeEnv = defaultRuntime, details: RuntimeDetails = detectRuntime(), ): void { - if (runtimeSatisfies(details)) return; + if (runtimeSatisfies(details)) { + return; + } const versionLabel = details.version ?? "unknown"; const runtimeLabel = diff --git a/src/infra/session-cost-usage.test.ts b/src/infra/session-cost-usage.test.ts index 2fc36e1a96..7da3e65e98 100644 --- a/src/infra/session-cost-usage.test.ts +++ b/src/infra/session-cost-usage.test.ts @@ -102,8 +102,11 @@ describe("session cost usage", () => { expect(summary.totals.totalTokens).toBe(50); expect(summary.totals.totalCost).toBeCloseTo(0.03003, 5); } finally { - if (originalState === undefined) delete process.env.OPENCLAW_STATE_DIR; - else process.env.OPENCLAW_STATE_DIR = originalState; + if (originalState === undefined) { + delete process.env.OPENCLAW_STATE_DIR; + } else { + process.env.OPENCLAW_STATE_DIR = originalState; + } } }); diff --git a/src/infra/session-cost-usage.ts b/src/infra/session-cost-usage.ts index 5188d0250d..9d5fba2fdd 100644 --- a/src/infra/session-cost-usage.ts +++ b/src/infra/session-cost-usage.ts @@ -58,18 +58,28 @@ const emptyTotals = (): CostUsageTotals => ({ }); const toFiniteNumber = (value: unknown): number | undefined => { - if (typeof value !== "number") return undefined; - if (!Number.isFinite(value)) return undefined; + if (typeof value !== "number") { + return undefined; + } + if (!Number.isFinite(value)) { + return undefined; + } return value; }; const extractCostTotal = (usageRaw?: UsageLike | null): number | undefined => { - if (!usageRaw || typeof usageRaw !== "object") return undefined; + if (!usageRaw || typeof usageRaw !== "object") { + return undefined; + } const record = usageRaw as Record; const cost = record.cost as Record | undefined; const total = toFiniteNumber(cost?.total); - if (total === undefined) return undefined; - if (total < 0) return undefined; + if (total === undefined) { + return undefined; + } + if (total < 0) { + return undefined; + } return total; }; @@ -77,13 +87,17 @@ const parseTimestamp = (entry: Record): Date | undefined => { const raw = entry.timestamp; if (typeof raw === "string") { const parsed = new Date(raw); - if (!Number.isNaN(parsed.valueOf())) return parsed; + if (!Number.isNaN(parsed.valueOf())) { + return parsed; + } } const message = entry.message as Record | undefined; const messageTimestamp = toFiniteNumber(message?.timestamp); if (messageTimestamp !== undefined) { const parsed = new Date(messageTimestamp); - if (!Number.isNaN(parsed.valueOf())) return parsed; + if (!Number.isNaN(parsed.valueOf())) { + return parsed; + } } return undefined; }; @@ -91,12 +105,16 @@ const parseTimestamp = (entry: Record): Date | undefined => { const parseUsageEntry = (entry: Record): ParsedUsageEntry | null => { const message = entry.message as Record | undefined; const role = message?.role; - if (role !== "assistant") return null; + if (role !== "assistant") { + return null; + } const usageRaw = (message?.usage as UsageLike | undefined) ?? (entry.usage as UsageLike | undefined); const usage = normalizeUsage(usageRaw); - if (!usage) return null; + if (!usage) { + return null; + } const provider = (typeof message?.provider === "string" ? message?.provider : undefined) ?? @@ -146,11 +164,15 @@ async function scanUsageFile(params: { for await (const line of rl) { const trimmed = line.trim(); - if (!trimmed) continue; + if (!trimmed) { + continue; + } try { const parsed = JSON.parse(trimmed) as Record; const entry = parseUsageEntry(parsed); - if (!entry) continue; + if (!entry) { + continue; + } if (entry.costTotal === undefined) { const cost = resolveModelCostConfig({ @@ -191,8 +213,12 @@ export async function loadCostUsageSummary(params?: { .map(async (entry) => { const filePath = path.join(sessionsDir, entry.name); const stats = await fs.promises.stat(filePath).catch(() => null); - if (!stats) return null; - if (stats.mtimeMs < sinceTime) return null; + if (!stats) { + return null; + } + if (stats.mtimeMs < sinceTime) { + return null; + } return filePath; }), ) @@ -204,7 +230,9 @@ export async function loadCostUsageSummary(params?: { config: params?.config, onEntry: (entry) => { const ts = entry.timestamp?.getTime(); - if (!ts || ts < sinceTime) return; + if (!ts || ts < sinceTime) { + return; + } const dayKey = formatDayKey(entry.timestamp ?? now); const bucket = dailyMap.get(dayKey) ?? emptyTotals(); applyUsageTotals(bucket, entry.usage); @@ -238,7 +266,9 @@ export async function loadSessionCostSummary(params: { const sessionFile = params.sessionFile ?? (params.sessionId ? resolveSessionFilePath(params.sessionId, params.sessionEntry) : undefined); - if (!sessionFile || !fs.existsSync(sessionFile)) return null; + if (!sessionFile || !fs.existsSync(sessionFile)) { + return null; + } const totals = emptyTotals(); let lastActivity: number | undefined; diff --git a/src/infra/shell-env.path.test.ts b/src/infra/shell-env.path.test.ts index 0e20e3b1bf..5e584d8979 100644 --- a/src/infra/shell-env.path.test.ts +++ b/src/infra/shell-env.path.test.ts @@ -6,7 +6,9 @@ describe("getShellPathFromLoginShell", () => { afterEach(() => resetShellPathCacheForTests()); it("returns PATH from login shell env", () => { - if (process.platform === "win32") return; + if (process.platform === "win32") { + return; + } const exec = vi .fn() .mockReturnValue(Buffer.from("PATH=/custom/bin\0HOME=/home/user\0", "utf-8")); @@ -15,7 +17,9 @@ describe("getShellPathFromLoginShell", () => { }); it("caches the value", () => { - if (process.platform === "win32") return; + if (process.platform === "win32") { + return; + } const exec = vi.fn().mockReturnValue(Buffer.from("PATH=/custom/bin\0", "utf-8")); const env = { SHELL: "/bin/sh" } as NodeJS.ProcessEnv; expect(getShellPathFromLoginShell({ env, exec })).toBe("/custom/bin"); @@ -24,7 +28,9 @@ describe("getShellPathFromLoginShell", () => { }); it("returns null on exec failure", () => { - if (process.platform === "win32") return; + if (process.platform === "win32") { + return; + } const exec = vi.fn(() => { throw new Error("boom"); }); diff --git a/src/infra/shell-env.ts b/src/infra/shell-env.ts index 3998f2836b..1d9c778f46 100644 --- a/src/infra/shell-env.ts +++ b/src/infra/shell-env.ts @@ -16,12 +16,18 @@ function parseShellEnv(stdout: Buffer): Map { const shellEnv = new Map(); const parts = stdout.toString("utf8").split("\0"); for (const part of parts) { - if (!part) continue; + if (!part) { + continue; + } const eq = part.indexOf("="); - if (eq <= 0) continue; + if (eq <= 0) { + continue; + } const key = part.slice(0, eq); const value = part.slice(eq + 1); - if (!key) continue; + if (!key) { + continue; + } shellEnv.set(key, value); } return shellEnv; @@ -83,9 +89,13 @@ export function loadShellEnvFallback(opts: ShellEnvFallbackOptions): ShellEnvFal const applied: string[] = []; for (const key of opts.expectedKeys) { - if (opts.env[key]?.trim()) continue; + if (opts.env[key]?.trim()) { + continue; + } const value = shellEnv.get(key); - if (!value?.trim()) continue; + if (!value?.trim()) { + continue; + } opts.env[key] = value; applied.push(key); } @@ -104,9 +114,13 @@ export function shouldDeferShellEnvFallback(env: NodeJS.ProcessEnv): boolean { export function resolveShellEnvFallbackTimeoutMs(env: NodeJS.ProcessEnv): number { const raw = env.OPENCLAW_SHELL_ENV_TIMEOUT_MS?.trim(); - if (!raw) return DEFAULT_TIMEOUT_MS; + if (!raw) { + return DEFAULT_TIMEOUT_MS; + } const parsed = Number.parseInt(raw, 10); - if (!Number.isFinite(parsed)) return DEFAULT_TIMEOUT_MS; + if (!Number.isFinite(parsed)) { + return DEFAULT_TIMEOUT_MS; + } return Math.max(0, parsed); } @@ -115,7 +129,9 @@ export function getShellPathFromLoginShell(opts: { timeoutMs?: number; exec?: typeof execFileSync; }): string | null { - if (cachedShellPath !== undefined) return cachedShellPath; + if (cachedShellPath !== undefined) { + return cachedShellPath; + } if (process.platform === "win32") { cachedShellPath = null; return cachedShellPath; diff --git a/src/infra/skills-remote.ts b/src/infra/skills-remote.ts index 0c43e5f6f6..0e6d100994 100644 --- a/src/infra/skills-remote.ts +++ b/src/infra/skills-remote.ts @@ -30,9 +30,15 @@ function describeNode(nodeId: string): string { } function extractErrorMessage(err: unknown): string | undefined { - if (!err) return undefined; - if (typeof err === "string") return err; - if (err instanceof Error) return err.message; + if (!err) { + return undefined; + } + if (typeof err === "string") { + return err; + } + if (err instanceof Error) { + return err.message; + } if (typeof err === "object" && "message" in err && typeof err.message === "string") { return err.message; } @@ -75,9 +81,15 @@ function isMacPlatform(platform?: string, deviceFamily?: string): boolean { const familyNorm = String(deviceFamily ?? "") .trim() .toLowerCase(); - if (platformNorm.includes("mac")) return true; - if (platformNorm.includes("darwin")) return true; - if (familyNorm === "mac") return true; + if (platformNorm.includes("mac")) { + return true; + } + if (platformNorm.includes("darwin")) { + return true; + } + if (familyNorm === "mac") { + return true; + } return false; } @@ -174,14 +186,20 @@ function collectRequiredBins(entries: SkillEntry[], targetPlatform: string): str const bins = new Set(); for (const entry of entries) { const os = entry.metadata?.os ?? []; - if (os.length > 0 && !os.includes(targetPlatform)) continue; + if (os.length > 0 && !os.includes(targetPlatform)) { + continue; + } const required = entry.metadata?.requires?.bins ?? []; const anyBins = entry.metadata?.requires?.anyBins ?? []; for (const bin of required) { - if (bin.trim()) bins.add(bin.trim()); + if (bin.trim()) { + bins.add(bin.trim()); + } } for (const bin of anyBins) { - if (bin.trim()) bins.add(bin.trim()); + if (bin.trim()) { + bins.add(bin.trim()); + } } } return [...bins]; @@ -193,7 +211,9 @@ function buildBinProbeScript(bins: string[]): string { } function parseBinProbePayload(payloadJSON: string | null | undefined, payload?: unknown): string[] { - if (!payloadJSON && !payload) return []; + if (!payloadJSON && !payload) { + return []; + } try { const parsed = payloadJSON ? (JSON.parse(payloadJSON) as { stdout?: unknown; bins?: unknown }) @@ -214,10 +234,16 @@ function parseBinProbePayload(payloadJSON: string | null | undefined, payload?: } function areBinSetsEqual(a: Set | undefined, b: Set): boolean { - if (!a) return false; - if (a.size !== b.size) return false; + if (!a) { + return false; + } + if (a.size !== b.size) { + return false; + } for (const bin of b) { - if (!a.has(bin)) return false; + if (!a.has(bin)) { + return false; + } } return true; } @@ -230,11 +256,17 @@ export async function refreshRemoteNodeBins(params: { cfg: OpenClawConfig; timeoutMs?: number; }) { - if (!remoteRegistry) return; - if (!isMacPlatform(params.platform, params.deviceFamily)) return; + if (!remoteRegistry) { + return; + } + if (!isMacPlatform(params.platform, params.deviceFamily)) { + return; + } const canWhich = supportsSystemWhich(params.commands); const canRun = supportsSystemRun(params.commands); - if (!canWhich && !canRun) return; + if (!canWhich && !canRun) { + return; + } const workspaceDirs = listWorkspaceDirs(params.cfg); const requiredBins = new Set(); @@ -244,7 +276,9 @@ export async function refreshRemoteNodeBins(params: { requiredBins.add(bin); } } - if (requiredBins.size === 0) return; + if (requiredBins.size === 0) { + return; + } try { const binsList = [...requiredBins]; @@ -274,7 +308,9 @@ export async function refreshRemoteNodeBins(params: { const nextBins = new Set(bins); const hasChanged = !areBinSetsEqual(existingBins, nextBins); recordRemoteNodeBins(params.nodeId, bins); - if (!hasChanged) return; + if (!hasChanged) { + return; + } await updatePairedNodeMetadata(params.nodeId, { bins }); bumpSkillsSnapshotVersion({ reason: "remote-node" }); } catch (err) { @@ -286,10 +322,14 @@ export function getRemoteSkillEligibility(): SkillEligibilityContext["remote"] | const macNodes = [...remoteNodes.values()].filter( (node) => isMacPlatform(node.platform, node.deviceFamily) && supportsSystemRun(node.commands), ); - if (macNodes.length === 0) return undefined; + if (macNodes.length === 0) { + return undefined; + } const bins = new Set(); for (const node of macNodes) { - for (const bin of node.bins) bins.add(bin); + for (const bin of node.bins) { + bins.add(bin); + } } const labels = macNodes.map((node) => node.displayName ?? node.nodeId).filter(Boolean); const note = @@ -305,7 +345,9 @@ export function getRemoteSkillEligibility(): SkillEligibilityContext["remote"] | } export async function refreshRemoteBinsForConnectedNodes(cfg: OpenClawConfig) { - if (!remoteRegistry) return; + if (!remoteRegistry) { + return; + } const connected = remoteRegistry.listConnected(); for (const node of connected) { await refreshRemoteNodeBins({ diff --git a/src/infra/ssh-config.ts b/src/infra/ssh-config.ts index 0b0e95015f..dd55ef635e 100644 --- a/src/infra/ssh-config.ts +++ b/src/infra/ssh-config.ts @@ -10,9 +10,13 @@ export type SshResolvedConfig = { }; function parsePort(value: string | undefined): number | undefined { - if (!value) return undefined; + if (!value) { + return undefined; + } const parsed = Number.parseInt(value, 10); - if (!Number.isFinite(parsed) || parsed <= 0) return undefined; + if (!Number.isFinite(parsed) || parsed <= 0) { + return undefined; + } return parsed; } @@ -21,10 +25,14 @@ export function parseSshConfigOutput(output: string): SshResolvedConfig { const lines = output.split("\n"); for (const raw of lines) { const line = raw.trim(); - if (!line) continue; + if (!line) { + continue; + } const [key, ...rest] = line.split(/\s+/); const value = rest.join(" ").trim(); - if (!key || !value) continue; + if (!key || !value) { + continue; + } switch (key) { case "user": result.user = value; @@ -36,7 +44,9 @@ export function parseSshConfigOutput(output: string): SshResolvedConfig { result.port = parsePort(value); break; case "identityfile": - if (value !== "none") result.identityFiles.push(value); + if (value !== "none") { + result.identityFiles.push(value); + } break; default: break; diff --git a/src/infra/ssh-tunnel.ts b/src/infra/ssh-tunnel.ts index 35068d3dfd..095af81060 100644 --- a/src/infra/ssh-tunnel.ts +++ b/src/infra/ssh-tunnel.ts @@ -24,7 +24,9 @@ function isErrno(err: unknown): err is NodeJS.ErrnoException { export function parseSshTarget(raw: string): SshParsedTarget | null { const trimmed = raw.trim().replace(/^ssh\s+/, ""); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const [userPart, hostPart] = trimmed.includes("@") ? ((): [string | undefined, string] => { @@ -40,15 +42,23 @@ export function parseSshTarget(raw: string): SshParsedTarget | null { const host = hostPart.slice(0, colonIdx).trim(); const portRaw = hostPart.slice(colonIdx + 1).trim(); const port = Number.parseInt(portRaw, 10); - if (!host || !Number.isFinite(port) || port <= 0) return null; + if (!host || !Number.isFinite(port) || port <= 0) { + return null; + } // Security: Reject hostnames starting with '-' to prevent argument injection - if (host.startsWith("-")) return null; + if (host.startsWith("-")) { + return null; + } return { user: userPart, host, port }; } - if (!hostPart) return null; + if (!hostPart) { + return null; + } // Security: Reject hostnames starting with '-' to prevent argument injection - if (hostPart.startsWith("-")) return null; + if (hostPart.startsWith("-")) { + return null; + } return { user: userPart, host: hostPart, port: 22 }; } @@ -86,7 +96,9 @@ async function canConnectLocal(port: number): Promise { async function waitForLocalListener(port: number, timeoutMs: number): Promise { const startedAt = Date.now(); while (Date.now() - startedAt < timeoutMs) { - if (await canConnectLocal(port)) return; + if (await canConnectLocal(port)) { + return; + } await new Promise((r) => setTimeout(r, 50)); } throw new Error(`ssh tunnel did not start listening on localhost:${port}`); @@ -100,7 +112,9 @@ export async function startSshPortForward(opts: { timeoutMs: number; }): Promise { const parsed = parseSshTarget(opts.target); - if (!parsed) throw new Error(`invalid SSH target: ${opts.target}`); + if (!parsed) { + throw new Error(`invalid SSH target: ${opts.target}`); + } let localPort = opts.localPortPreferred; try { @@ -155,7 +169,9 @@ export async function startSshPortForward(opts: { }); const stop = async () => { - if (child.killed) return; + if (child.killed) { + return; + } child.kill("SIGTERM"); await new Promise((resolve) => { const t = setTimeout(() => { diff --git a/src/infra/state-migrations.fs.ts b/src/infra/state-migrations.fs.ts index 298fed1bd4..73752dda68 100644 --- a/src/infra/state-migrations.fs.ts +++ b/src/infra/state-migrations.fs.ts @@ -36,8 +36,12 @@ export function fileExists(p: string): boolean { } export function isLegacyWhatsAppAuthFile(name: string): boolean { - if (name === "creds.json" || name === "creds.json.bak") return true; - if (!name.endsWith(".json")) return false; + if (name === "creds.json" || name === "creds.json.bak") { + return true; + } + if (!name.endsWith(".json")) { + return false; + } return /^(app-state-sync|session|sender-key|pre-key)-/.test(name); } diff --git a/src/infra/state-migrations.ts b/src/infra/state-migrations.ts index df247902c8..fa7f1aecb1 100644 --- a/src/infra/state-migrations.ts +++ b/src/infra/state-migrations.ts @@ -72,13 +72,23 @@ function isSurfaceGroupKey(key: string): boolean { function isLegacyGroupKey(key: string): boolean { const trimmed = key.trim(); - if (!trimmed) return false; - if (trimmed.startsWith("group:")) return true; + if (!trimmed) { + return false; + } + if (trimmed.startsWith("group:")) { + return true; + } const lower = trimmed.toLowerCase(); - if (!lower.includes("@g.us")) return false; + if (!lower.includes("@g.us")) { + return false; + } // Legacy WhatsApp group keys: bare JID or "whatsapp:" without explicit ":group:" kind. - if (!trimmed.includes(":")) return true; - if (lower.startsWith("whatsapp:") && !trimmed.includes(":group:")) return true; + if (!trimmed.includes(":")) { + return true; + } + if (lower.startsWith("whatsapp:") && !trimmed.includes(":group:")) { + return true; + } return false; } @@ -90,24 +100,34 @@ function canonicalizeSessionKeyForAgent(params: { }): string { const agentId = normalizeAgentId(params.agentId); const raw = params.key.trim(); - if (!raw) return raw; - if (raw.toLowerCase() === "global" || raw.toLowerCase() === "unknown") return raw.toLowerCase(); + if (!raw) { + return raw; + } + if (raw.toLowerCase() === "global" || raw.toLowerCase() === "unknown") { + return raw.toLowerCase(); + } const canonicalMain = canonicalizeMainSessionAlias({ cfg: { session: { scope: params.scope, mainKey: params.mainKey } }, agentId, sessionKey: raw, }); - if (canonicalMain !== raw) return canonicalMain.toLowerCase(); + if (canonicalMain !== raw) { + return canonicalMain.toLowerCase(); + } - if (raw.toLowerCase().startsWith("agent:")) return raw.toLowerCase(); + if (raw.toLowerCase().startsWith("agent:")) { + return raw.toLowerCase(); + } if (raw.toLowerCase().startsWith("subagent:")) { const rest = raw.slice("subagent:".length); return `agent:${agentId}:subagent:${rest}`.toLowerCase(); } if (raw.startsWith("group:")) { const id = raw.slice("group:".length).trim(); - if (!id) return raw; + if (!id) { + return raw; + } const channel = id.toLowerCase().includes("@g.us") ? "whatsapp" : "unknown"; return `agent:${agentId}:${channel}:group:${id}`.toLowerCase(); } @@ -133,13 +153,25 @@ function pickLatestLegacyDirectEntry( let best: SessionEntryLike | null = null; let bestUpdated = -1; for (const [key, entry] of Object.entries(store)) { - if (!entry || typeof entry !== "object") continue; + if (!entry || typeof entry !== "object") { + continue; + } const normalized = key.trim(); - if (!normalized) continue; - if (normalized === "global") continue; - if (normalized.startsWith("agent:")) continue; - if (normalized.toLowerCase().startsWith("subagent:")) continue; - if (isLegacyGroupKey(normalized) || isSurfaceGroupKey(normalized)) continue; + if (!normalized) { + continue; + } + if (normalized === "global") { + continue; + } + if (normalized.startsWith("agent:")) { + continue; + } + if (normalized.toLowerCase().startsWith("subagent:")) { + continue; + } + if (isLegacyGroupKey(normalized) || isSurfaceGroupKey(normalized)) { + continue; + } const updatedAt = typeof entry.updatedAt === "number" ? entry.updatedAt : 0; if (updatedAt > bestUpdated) { bestUpdated = updatedAt; @@ -151,7 +183,9 @@ function pickLatestLegacyDirectEntry( function normalizeSessionEntry(entry: SessionEntryLike): SessionEntry | null { const sessionId = typeof entry.sessionId === "string" ? entry.sessionId : null; - if (!sessionId) return null; + if (!sessionId) { + return null; + } const updatedAt = typeof entry.updatedAt === "number" && Number.isFinite(entry.updatedAt) ? entry.updatedAt @@ -176,11 +210,17 @@ function mergeSessionEntry(params: { incoming: SessionEntryLike; preferIncomingOnTie?: boolean; }): SessionEntryLike { - if (!params.existing) return params.incoming; + if (!params.existing) { + return params.incoming; + } const existingUpdated = resolveUpdatedAt(params.existing); const incomingUpdated = resolveUpdatedAt(params.incoming); - if (incomingUpdated > existingUpdated) return params.incoming; - if (incomingUpdated < existingUpdated) return params.existing; + if (incomingUpdated > existingUpdated) { + return params.incoming; + } + if (incomingUpdated < existingUpdated) { + return params.existing; + } return params.preferIncomingOnTie ? params.incoming : params.existing; } @@ -195,7 +235,9 @@ function canonicalizeSessionStore(params: { const legacyKeys: string[] = []; for (const [key, entry] of Object.entries(params.store)) { - if (!entry || typeof entry !== "object") continue; + if (!entry || typeof entry !== "object") { + continue; + } const canonicalKey = canonicalizeSessionKeyForAgent({ key, agentId: params.agentId, @@ -203,7 +245,9 @@ function canonicalizeSessionStore(params: { scope: params.scope, }); const isCanonical = canonicalKey === key; - if (!isCanonical) legacyKeys.push(key); + if (!isCanonical) { + legacyKeys.push(key); + } const existing = canonical[canonicalKey]; if (!existing) { canonical[canonicalKey] = entry; @@ -219,8 +263,12 @@ function canonicalizeSessionStore(params: { meta.set(canonicalKey, { isCanonical, updatedAt: incomingUpdated }); continue; } - if (incomingUpdated < existingUpdated) continue; - if (existingMeta?.isCanonical && !isCanonical) continue; + if (incomingUpdated < existingUpdated) { + continue; + } + if (existingMeta?.isCanonical && !isCanonical) { + continue; + } if (!existingMeta?.isCanonical && isCanonical) { canonical[canonicalKey] = entry; meta.set(canonicalKey, { isCanonical, updatedAt: incomingUpdated }); @@ -245,19 +293,27 @@ function listLegacySessionKeys(params: { mainKey: params.mainKey, scope: params.scope, }); - if (canonical !== key) legacy.push(key); + if (canonical !== key) { + legacy.push(key); + } } return legacy; } function emptyDirOrMissing(dir: string): boolean { - if (!existsDir(dir)) return true; + if (!existsDir(dir)) { + return true; + } return safeReadDir(dir).length === 0; } function removeDirIfEmpty(dir: string) { - if (!existsDir(dir)) return; - if (!emptyDirOrMissing(dir)) return; + if (!existsDir(dir)) { + return; + } + if (!emptyDirOrMissing(dir)) { + return; + } try { fs.rmdirSync(dir); } catch { @@ -395,7 +451,9 @@ export async function autoMigrateLegacyStateDir(params: { } try { - if (!legacyDir) throw new Error("Legacy state dir not found"); + if (!legacyDir) { + throw new Error("Legacy state dir not found"); + } fs.renameSync(legacyDir, targetDir); } catch (err) { warnings.push( @@ -405,13 +463,17 @@ export async function autoMigrateLegacyStateDir(params: { } try { - if (!legacyDir) throw new Error("Legacy state dir not found"); + if (!legacyDir) { + throw new Error("Legacy state dir not found"); + } fs.symlinkSync(targetDir, legacyDir, "dir"); changes.push(formatStateDirMigration(legacyDir, targetDir)); } catch (err) { try { if (process.platform === "win32") { - if (!legacyDir) throw new Error("Legacy state dir not found", { cause: err }); + if (!legacyDir) { + throw new Error("Legacy state dir not found", { cause: err }); + } fs.symlinkSync(targetDir, legacyDir, "junction"); changes.push(formatStateDirMigration(legacyDir, targetDir)); } else { @@ -539,7 +601,9 @@ async function migrateLegacySessions( ): Promise<{ changes: string[]; warnings: string[] }> { const changes: string[] = []; const warnings: string[] = []; - if (!detected.sessions.hasLegacy) return { changes, warnings }; + if (!detected.sessions.hasLegacy) { + return { changes, warnings }; + } ensureDir(detected.sessions.targetDir); @@ -599,7 +663,9 @@ async function migrateLegacySessions( const normalized: Record = {}; for (const [key, entry] of Object.entries(merged)) { const normalizedEntry = normalizeSessionEntry(entry); - if (!normalizedEntry) continue; + if (!normalizedEntry) { + continue; + } normalized[key] = normalizedEntry; } await saveSessionStore(detected.sessions.targetStorePath, normalized); @@ -611,11 +677,17 @@ async function migrateLegacySessions( const entries = safeReadDir(detected.sessions.legacyDir); for (const entry of entries) { - if (!entry.isFile()) continue; - if (entry.name === "sessions.json") continue; + if (!entry.isFile()) { + continue; + } + if (entry.name === "sessions.json") { + continue; + } const from = path.join(detected.sessions.legacyDir, entry.name); const to = path.join(detected.sessions.targetDir, entry.name); - if (fileExists(to)) continue; + if (fileExists(to)) { + continue; + } try { fs.renameSync(from, to); changes.push(`Moved ${entry.name} → agents/${detected.targetAgentId}/sessions`); @@ -655,7 +727,9 @@ export async function migrateLegacyAgentDir( ): Promise<{ changes: string[]; warnings: string[] }> { const changes: string[] = []; const warnings: string[] = []; - if (!detected.agentDir.hasLegacy) return { changes, warnings }; + if (!detected.agentDir.hasLegacy) { + return { changes, warnings }; + } ensureDir(detected.agentDir.targetDir); @@ -663,7 +737,9 @@ export async function migrateLegacyAgentDir( for (const entry of entries) { const from = path.join(detected.agentDir.legacyDir, entry.name); const to = path.join(detected.agentDir.targetDir, entry.name); - if (fs.existsSync(to)) continue; + if (fs.existsSync(to)) { + continue; + } try { fs.renameSync(from, to); changes.push(`Moved agent file ${entry.name} → agents/${detected.targetAgentId}/agent`); @@ -696,18 +772,28 @@ async function migrateLegacyWhatsAppAuth( ): Promise<{ changes: string[]; warnings: string[] }> { const changes: string[] = []; const warnings: string[] = []; - if (!detected.whatsappAuth.hasLegacy) return { changes, warnings }; + if (!detected.whatsappAuth.hasLegacy) { + return { changes, warnings }; + } ensureDir(detected.whatsappAuth.targetDir); const entries = safeReadDir(detected.whatsappAuth.legacyDir); for (const entry of entries) { - if (!entry.isFile()) continue; - if (entry.name === "oauth.json") continue; - if (!isLegacyWhatsAppAuthFile(entry.name)) continue; + if (!entry.isFile()) { + continue; + } + if (entry.name === "oauth.json") { + continue; + } + if (!isLegacyWhatsAppAuthFile(entry.name)) { + continue; + } const from = path.join(detected.whatsappAuth.legacyDir, entry.name); const to = path.join(detected.whatsappAuth.targetDir, entry.name); - if (fileExists(to)) continue; + if (fileExists(to)) { + continue; + } try { fs.renameSync(from, to); changes.push(`Moved WhatsApp auth ${entry.name} → whatsapp/default`); diff --git a/src/infra/system-events.ts b/src/infra/system-events.ts index b2842142dd..866dcb1627 100644 --- a/src/infra/system-events.ts +++ b/src/infra/system-events.ts @@ -28,9 +28,13 @@ function requireSessionKey(key?: string | null): string { } function normalizeContextKey(key?: string | null): string | null { - if (!key) return null; + if (!key) { + return null; + } const trimmed = key.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } return trimmed.toLowerCase(); } @@ -58,18 +62,26 @@ export function enqueueSystemEvent(text: string, options: SystemEventOptions) { return created; })(); const cleaned = text.trim(); - if (!cleaned) return; + if (!cleaned) { + return; + } entry.lastContextKey = normalizeContextKey(options?.contextKey); - if (entry.lastText === cleaned) return; // skip consecutive duplicates + if (entry.lastText === cleaned) { + return; + } // skip consecutive duplicates entry.lastText = cleaned; entry.queue.push({ text: cleaned, ts: Date.now() }); - if (entry.queue.length > MAX_EVENTS) entry.queue.shift(); + if (entry.queue.length > MAX_EVENTS) { + entry.queue.shift(); + } } export function drainSystemEventEntries(sessionKey: string): SystemEvent[] { const key = requireSessionKey(sessionKey); const entry = queues.get(key); - if (!entry || entry.queue.length === 0) return []; + if (!entry || entry.queue.length === 0) { + return []; + } const out = entry.queue.slice(); entry.queue.length = 0; entry.lastText = null; diff --git a/src/infra/system-presence.ts b/src/infra/system-presence.ts index 1c84ec0e62..c78f5ccc10 100644 --- a/src/infra/system-presence.ts +++ b/src/infra/system-presence.ts @@ -32,9 +32,13 @@ const TTL_MS = 5 * 60 * 1000; // 5 minutes const MAX_ENTRIES = 200; function normalizePresenceKey(key: string | undefined): string | undefined { - if (!key) return undefined; + if (!key) { + return undefined; + } const trimmed = key.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } return trimmed.toLowerCase(); } @@ -45,11 +49,15 @@ function resolvePrimaryIPv4(): string | undefined { for (const name of names) { const list = nets[name]; const entry = list?.find((n) => n.family === "IPv4" && !n.internal); - if (entry?.address) return entry.address; + if (entry?.address) { + return entry.address; + } } for (const list of Object.values(nets)) { const entry = list?.find((n) => n.family === "IPv4" && !n.internal); - if (entry?.address) return entry.address; + if (entry?.address) { + return entry.address; + } } return undefined; }; @@ -81,15 +89,25 @@ function initSelfPresence() { const platform = (() => { const p = os.platform(); const rel = os.release(); - if (p === "darwin") return `macos ${macOSVersion()}`; - if (p === "win32") return `windows ${rel}`; + if (p === "darwin") { + return `macos ${macOSVersion()}`; + } + if (p === "win32") { + return `windows ${rel}`; + } return `${p} ${rel}`; })(); const deviceFamily = (() => { const p = os.platform(); - if (p === "darwin") return "Mac"; - if (p === "win32") return "Windows"; - if (p === "linux") return "Linux"; + if (p === "darwin") { + return "Mac"; + } + if (p === "win32") { + return "Windows"; + } + if (p === "linux") { + return "Linux"; + } return p; })(); const text = `Gateway: ${host}${ip ? ` (${ip})` : ""} · app ${version} · mode gateway · reason self`; @@ -175,10 +193,14 @@ type SystemPresencePayload = { function mergeStringList(...values: Array): string[] | undefined { const out = new Set(); for (const list of values) { - if (!Array.isArray(list)) continue; + if (!Array.isArray(list)) { + continue; + } for (const item of list) { const trimmed = String(item).trim(); - if (trimmed) out.add(trimmed); + if (trimmed) { + out.add(trimmed); + } } } return out.size > 0 ? [...out] : undefined; @@ -266,7 +288,9 @@ export function listSystemPresence(): SystemPresence[] { // prune expired const now = Date.now(); for (const [k, v] of entries) { - if (now - v.ts > TTL_MS) entries.delete(k); + if (now - v.ts > TTL_MS) { + entries.delete(k); + } } // enforce max size (LRU by ts) if (entries.size > MAX_ENTRIES) { diff --git a/src/infra/tailnet.ts b/src/infra/tailnet.ts index f5900a24b5..ed666b8684 100644 --- a/src/infra/tailnet.ts +++ b/src/infra/tailnet.ts @@ -7,9 +7,13 @@ export type TailnetAddresses = { function isTailnetIPv4(address: string): boolean { const parts = address.split("."); - if (parts.length !== 4) return false; + if (parts.length !== 4) { + return false; + } const octets = parts.map((p) => Number.parseInt(p, 10)); - if (octets.some((n) => !Number.isFinite(n) || n < 0 || n > 255)) return false; + if (octets.some((n) => !Number.isFinite(n) || n < 0 || n > 255)) { + return false; + } // Tailscale IPv4 range: 100.64.0.0/10 // https://tailscale.com/kb/1015/100.x-addresses @@ -30,13 +34,23 @@ export function listTailnetAddresses(): TailnetAddresses { const ifaces = os.networkInterfaces(); for (const entries of Object.values(ifaces)) { - if (!entries) continue; + if (!entries) { + continue; + } for (const e of entries) { - if (!e || e.internal) continue; + if (!e || e.internal) { + continue; + } const address = e.address?.trim(); - if (!address) continue; - if (isTailnetIPv4(address)) ipv4.push(address); - if (isTailnetIPv6(address)) ipv6.push(address); + if (!address) { + continue; + } + if (isTailnetIPv4(address)) { + ipv4.push(address); + } + if (isTailnetIPv6(address)) { + ipv6.push(address); + } } } diff --git a/src/infra/tailscale.ts b/src/infra/tailscale.ts index 203773352d..ff6a070e66 100644 --- a/src/infra/tailscale.ts +++ b/src/infra/tailscale.ts @@ -29,7 +29,9 @@ function parsePossiblyNoisyJsonObject(stdout: string): Record { export async function findTailscaleBinary(): Promise { // Helper to check if a binary exists and is executable const checkBinary = async (path: string): Promise => { - if (!path || !existsSync(path)) return false; + if (!path || !existsSync(path)) { + return false; + } try { // Use Promise.race with runExec to implement timeout await Promise.race([ @@ -109,7 +111,9 @@ export async function getTailnetHostname(exec: typeof runExec = runExec, detecte let lastError: unknown; for (const candidate of candidates) { - if (candidate.startsWith("/") && !existsSync(candidate)) continue; + if (candidate.startsWith("/") && !existsSync(candidate)) { + continue; + } try { const { stdout } = await exec(candidate, ["status", "--json"], { timeoutMs: 5000, @@ -124,8 +128,12 @@ export async function getTailnetHostname(exec: typeof runExec = runExec, detecte const ips = Array.isArray(self?.TailscaleIPs) ? ((parsed.Self as { TailscaleIPs?: string[] }).TailscaleIPs ?? []) : []; - if (dns && dns.length > 0) return dns.replace(/\.$/, ""); - if (ips.length > 0) return ips[0]; + if (dns && dns.length > 0) { + return dns.replace(/\.$/, ""); + } + if (ips.length > 0) { + return ips[0]; + } throw new Error("Could not determine Tailscale DNS or IP"); } catch (err) { lastError = err; @@ -142,7 +150,9 @@ export async function getTailnetHostname(exec: typeof runExec = runExec, detecte let cachedTailscaleBinary: string | null = null; export async function getTailscaleBinary(): Promise { - if (cachedTailscaleBinary) return cachedTailscaleBinary; + if (cachedTailscaleBinary) { + return cachedTailscaleBinary; + } cachedTailscaleBinary = await findTailscaleBinary(); return cachedTailscaleBinary ?? "tailscale"; } @@ -169,7 +179,9 @@ export async function ensureGoInstalled( () => true, () => false, ); - if (hasGo) return; + if (hasGo) { + return; + } const install = await prompt( "Go is not installed. Install via Homebrew (brew install go)?", true, @@ -192,7 +204,9 @@ export async function ensureTailscaledInstalled( () => true, () => false, ); - if (hasTailscaled) return; + if (hasTailscaled) { + return; + } const install = await prompt( "tailscaled not found. Install via Homebrew (tailscale package)?", @@ -236,7 +250,9 @@ function extractExecErrorText(err: unknown) { function isPermissionDeniedError(err: unknown): boolean { const { stdout, stderr, message, code } = extractExecErrorText(err); - if (code.toUpperCase() === "EACCES") return true; + if (code.toUpperCase() === "EACCES") { + return true; + } const combined = `${stdout}\n${stderr}\n${message}`.toLowerCase(); return ( combined.includes("permission denied") || @@ -270,7 +286,9 @@ async function execWithSudoFallback( } catch (sudoErr) { const { stderr, message } = extractExecErrorText(sudoErr); const detail = (stderr || message).trim(); - if (detail) logVerbose(`Sudo retry failed: ${detail}`); + if (detail) { + logVerbose(`Sudo retry failed: ${detail}`); + } throw err; } } @@ -300,7 +318,9 @@ export async function ensureFunnel( ), ); const proceed = await prompt("Attempt local setup with user-space tailscaled?", true); - if (!proceed) runtime.exit(1); + if (!proceed) { + runtime.exit(1); + } await ensureBinary("brew", exec, runtime); await ensureGoInstalled(exec, prompt, runtime); await ensureTailscaledInstalled(exec, prompt, runtime); @@ -317,7 +337,9 @@ export async function ensureFunnel( timeoutMs: 15_000, }, ); - if (stdout.trim()) console.log(stdout.trim()); + if (stdout.trim()) { + console.log(stdout.trim()); + } } catch (err) { const errOutput = err as { stdout?: unknown; stderr?: unknown }; const stdout = typeof errOutput.stdout === "string" ? errOutput.stdout : ""; @@ -411,7 +433,9 @@ function parseWhoisIdentity(payload: Record): TailscaleWhoisIde getString(userProfile?.login) ?? getString(payload.LoginName) ?? getString(payload.login); - if (!login) return null; + if (!login) { + return null; + } const name = getString(userProfile?.DisplayName) ?? getString(userProfile?.Name) ?? @@ -423,7 +447,9 @@ function parseWhoisIdentity(payload: Record): TailscaleWhoisIde function readCachedWhois(ip: string, now: number): TailscaleWhoisIdentity | null | undefined { const cached = whoisCache.get(ip); - if (!cached) return undefined; + if (!cached) { + return undefined; + } if (cached.expiresAt <= now) { whoisCache.delete(ip); return undefined; @@ -441,10 +467,14 @@ export async function readTailscaleWhoisIdentity( opts?: { timeoutMs?: number; cacheTtlMs?: number; errorTtlMs?: number }, ): Promise { const normalized = ip.trim(); - if (!normalized) return null; + if (!normalized) { + return null; + } const now = Date.now(); const cached = readCachedWhois(normalized, now); - if (cached !== undefined) return cached; + if (cached !== undefined) { + return cached; + } const cacheTtlMs = opts?.cacheTtlMs ?? 60_000; const errorTtlMs = opts?.errorTtlMs ?? 5_000; diff --git a/src/infra/tls/gateway.ts b/src/infra/tls/gateway.ts index 6163d0010a..e722496841 100644 --- a/src/infra/tls/gateway.ts +++ b/src/infra/tls/gateway.ts @@ -69,7 +69,9 @@ export async function loadGatewayTlsRuntime( cfg: GatewayTlsConfig | undefined, log?: { info?: (msg: string) => void; warn?: (msg: string) => void }, ): Promise { - if (!cfg || cfg.enabled !== true) return { enabled: false, required: false }; + if (!cfg || cfg.enabled !== true) { + return { enabled: false, required: false }; + } const autoGenerate = cfg.autoGenerate !== false; const baseDir = path.join(CONFIG_DIR, "gateway", "tls"); diff --git a/src/infra/transport-ready.test.ts b/src/infra/transport-ready.test.ts index 79c42e22da..66100a30a5 100644 --- a/src/infra/transport-ready.test.ts +++ b/src/infra/transport-ready.test.ts @@ -15,7 +15,9 @@ describe("waitForTransportReady", () => { runtime, check: async () => { attempts += 1; - if (attempts > 4) return { ok: true }; + if (attempts > 4) { + return { ok: true }; + } return { ok: false, error: "not ready" }; }, }); diff --git a/src/infra/transport-ready.ts b/src/infra/transport-ready.ts index ec514d2e56..42c1476c20 100644 --- a/src/infra/transport-ready.ts +++ b/src/infra/transport-ready.ts @@ -29,13 +29,19 @@ export async function waitForTransportReady(params: WaitForTransportReadyParams) let lastError: string | null = null; while (true) { - if (params.abortSignal?.aborted) return; + if (params.abortSignal?.aborted) { + return; + } const res = await params.check(); - if (res.ok) return; + if (res.ok) { + return; + } lastError = res.error ?? null; const now = Date.now(); - if (now >= deadline) break; + if (now >= deadline) { + break; + } if (now >= nextLogAt) { const elapsedMs = now - started; params.runtime.error?.( @@ -47,7 +53,9 @@ export async function waitForTransportReady(params: WaitForTransportReadyParams) try { await sleepWithAbort(pollIntervalMs, params.abortSignal); } catch (err) { - if (params.abortSignal?.aborted) return; + if (params.abortSignal?.aborted) { + return; + } throw err; } } diff --git a/src/infra/unhandled-rejections.ts b/src/infra/unhandled-rejections.ts index 4d2a48d232..c60da1ff54 100644 --- a/src/infra/unhandled-rejections.ts +++ b/src/infra/unhandled-rejections.ts @@ -37,13 +37,17 @@ const TRANSIENT_NETWORK_CODES = new Set([ ]); function getErrorCause(err: unknown): unknown { - if (!err || typeof err !== "object") return undefined; + if (!err || typeof err !== "object") { + return undefined; + } return (err as { cause?: unknown }).cause; } function extractErrorCodeWithCause(err: unknown): string | undefined { const direct = extractErrorCode(err); - if (direct) return direct; + if (direct) { + return direct; + } return extractErrorCode(getErrorCause(err)); } @@ -52,12 +56,18 @@ function extractErrorCodeWithCause(err: unknown): string | undefined { * These are typically intentional cancellations (e.g., during shutdown) and shouldn't crash. */ export function isAbortError(err: unknown): boolean { - if (!err || typeof err !== "object") return false; + if (!err || typeof err !== "object") { + return false; + } const name = "name" in err ? String(err.name) : ""; - if (name === "AbortError") return true; + if (name === "AbortError") { + return true; + } // Check for "This operation was aborted" message from Node's undici const message = "message" in err && typeof err.message === "string" ? err.message : ""; - if (message === "This operation was aborted") return true; + if (message === "This operation was aborted") { + return true; + } return false; } @@ -76,15 +86,21 @@ function isConfigError(err: unknown): boolean { * These are typically temporary connectivity issues that will resolve on their own. */ export function isTransientNetworkError(err: unknown): boolean { - if (!err) return false; + if (!err) { + return false; + } const code = extractErrorCodeWithCause(err); - if (code && TRANSIENT_NETWORK_CODES.has(code)) return true; + if (code && TRANSIENT_NETWORK_CODES.has(code)) { + return true; + } // "fetch failed" TypeError from undici (Node's native fetch) if (err instanceof TypeError && err.message === "fetch failed") { const cause = getErrorCause(err); - if (cause) return isTransientNetworkError(cause); + if (cause) { + return isTransientNetworkError(cause); + } return true; } @@ -112,7 +128,9 @@ export function registerUnhandledRejectionHandler(handler: UnhandledRejectionHan export function isUnhandledRejectionHandled(reason: unknown): boolean { for (const handler of handlers) { try { - if (handler(reason)) return true; + if (handler(reason)) { + return true; + } } catch (err) { console.error( "[openclaw] Unhandled rejection handler failed:", @@ -125,7 +143,9 @@ export function isUnhandledRejectionHandled(reason: unknown): boolean { export function installUnhandledRejectionHandler(): void { process.on("unhandledRejection", (reason, _promise) => { - if (isUnhandledRejectionHandled(reason)) return; + if (isUnhandledRejectionHandled(reason)) { + return; + } // AbortError is typically an intentional cancellation (e.g., during shutdown) // Log it but don't crash - these are expected during graceful shutdown diff --git a/src/infra/update-channels.ts b/src/infra/update-channels.ts index bb40295d5e..f363d943c0 100644 --- a/src/infra/update-channels.ts +++ b/src/infra/update-channels.ts @@ -6,15 +6,23 @@ export const DEFAULT_GIT_CHANNEL: UpdateChannel = "dev"; export const DEV_BRANCH = "main"; export function normalizeUpdateChannel(value?: string | null): UpdateChannel | null { - if (!value) return null; + if (!value) { + return null; + } const normalized = value.trim().toLowerCase(); - if (normalized === "stable" || normalized === "beta" || normalized === "dev") return normalized; + if (normalized === "stable" || normalized === "beta" || normalized === "dev") { + return normalized; + } return null; } export function channelToNpmTag(channel: UpdateChannel): string { - if (channel === "beta") return "beta"; - if (channel === "dev") return "dev"; + if (channel === "beta") { + return "beta"; + } + if (channel === "dev") { + return "dev"; + } return "latest"; } @@ -60,7 +68,9 @@ export function formatUpdateChannelLabel(params: { gitTag?: string | null; gitBranch?: string | null; }): string { - if (params.source === "config") return `${params.channel} (config)`; + if (params.source === "config") { + return `${params.channel} (config)`; + } if (params.source === "git-tag") { return params.gitTag ? `${params.channel} (${params.gitTag})` : `${params.channel} (tag)`; } diff --git a/src/infra/update-check.ts b/src/infra/update-check.ts index e97215a548..f8aadd74d0 100644 --- a/src/infra/update-check.ts +++ b/src/infra/update-check.ts @@ -62,15 +62,23 @@ async function detectPackageManager(root: string): Promise { const raw = await fs.readFile(path.join(root, "package.json"), "utf-8"); const parsed = JSON.parse(raw) as { packageManager?: string }; const pm = parsed?.packageManager?.split("@")[0]?.trim(); - if (pm === "pnpm" || pm === "bun" || pm === "npm") return pm; + if (pm === "pnpm" || pm === "bun" || pm === "npm") { + return pm; + } } catch { // ignore } const files = await fs.readdir(root).catch((): string[] => []); - if (files.includes("pnpm-lock.yaml")) return "pnpm"; - if (files.includes("bun.lockb")) return "bun"; - if (files.includes("package-lock.json")) return "npm"; + if (files.includes("pnpm-lock.yaml")) { + return "pnpm"; + } + if (files.includes("bun.lockb")) { + return "bun"; + } + if (files.includes("package-lock.json")) { + return "npm"; + } return "unknown"; } @@ -78,7 +86,9 @@ async function detectGitRoot(root: string): Promise { const res = await runCommandWithTimeout(["git", "-C", root, "rev-parse", "--show-toplevel"], { timeoutMs: 4000, }).catch(() => null); - if (!res || res.code !== 0) return null; + if (!res || res.code !== 0) { + return null; + } const top = res.stdout.trim(); return top ? path.resolve(top) : null; } @@ -151,10 +161,14 @@ export async function checkGitUpdateStatus(params: { const parseCounts = (raw: string): { ahead: number; behind: number } | null => { const parts = raw.trim().split(/\s+/); - if (parts.length < 2) return null; + if (parts.length < 2) { + return null; + } const ahead = Number.parseInt(parts[0] ?? "", 10); const behind = Number.parseInt(parts[1] ?? "", 10); - if (!Number.isFinite(ahead) || !Number.isFinite(behind)) return null; + if (!Number.isFinite(ahead) || !Number.isFinite(behind)) { + return null; + } return { ahead, behind }; }; const parsed = counts && counts.code === 0 ? parseCounts(counts.stdout) : null; @@ -344,10 +358,18 @@ export async function resolveNpmChannelTag(params: { export function compareSemverStrings(a: string | null, b: string | null): number | null { const pa = parseSemver(a); const pb = parseSemver(b); - if (!pa || !pb) return null; - if (pa.major !== pb.major) return pa.major < pb.major ? -1 : 1; - if (pa.minor !== pb.minor) return pa.minor < pb.minor ? -1 : 1; - if (pa.patch !== pb.patch) return pa.patch < pb.patch ? -1 : 1; + if (!pa || !pb) { + return null; + } + if (pa.major !== pb.major) { + return pa.major < pb.major ? -1 : 1; + } + if (pa.minor !== pb.minor) { + return pa.minor < pb.minor ? -1 : 1; + } + if (pa.patch !== pb.patch) { + return pa.patch < pb.patch ? -1 : 1; + } return 0; } diff --git a/src/infra/update-global.ts b/src/infra/update-global.ts index 312521901f..940e44445a 100644 --- a/src/infra/update-global.ts +++ b/src/infra/update-global.ts @@ -39,10 +39,14 @@ export async function resolveGlobalRoot( runCommand: CommandRunner, timeoutMs: number, ): Promise { - if (manager === "bun") return resolveBunGlobalRoot(); + if (manager === "bun") { + return resolveBunGlobalRoot(); + } const argv = manager === "pnpm" ? ["pnpm", "root", "-g"] : ["npm", "root", "-g"]; const res = await runCommand(argv, { timeoutMs }).catch(() => null); - if (!res || res.code !== 0) return null; + if (!res || res.code !== 0) { + return null; + } const root = res.stdout.trim(); return root || null; } @@ -53,7 +57,9 @@ export async function resolveGlobalPackageRoot( timeoutMs: number, ): Promise { const root = await resolveGlobalRoot(manager, runCommand, timeoutMs); - if (!root) return null; + if (!root) { + return null; + } return path.join(root, PRIMARY_PACKAGE_NAME); } @@ -74,13 +80,19 @@ export async function detectGlobalInstallManagerForRoot( for (const { manager, argv } of candidates) { const res = await runCommand(argv, { timeoutMs }).catch(() => null); - if (!res || res.code !== 0) continue; + if (!res || res.code !== 0) { + continue; + } const globalRoot = res.stdout.trim(); - if (!globalRoot) continue; + if (!globalRoot) { + continue; + } const globalReal = await tryRealpath(globalRoot); for (const name of ALL_PACKAGE_NAMES) { const expected = path.join(globalReal, name); - if (path.resolve(expected) === path.resolve(pkgReal)) return manager; + if (path.resolve(expected) === path.resolve(pkgReal)) { + return manager; + } } } @@ -88,7 +100,9 @@ export async function detectGlobalInstallManagerForRoot( const bunGlobalReal = await tryRealpath(bunGlobalRoot); for (const name of ALL_PACKAGE_NAMES) { const bunExpected = path.join(bunGlobalReal, name); - if (path.resolve(bunExpected) === path.resolve(pkgReal)) return "bun"; + if (path.resolve(bunExpected) === path.resolve(pkgReal)) { + return "bun"; + } } return null; @@ -100,21 +114,31 @@ export async function detectGlobalInstallManagerByPresence( ): Promise { for (const manager of ["npm", "pnpm"] as const) { const root = await resolveGlobalRoot(manager, runCommand, timeoutMs); - if (!root) continue; + if (!root) { + continue; + } for (const name of ALL_PACKAGE_NAMES) { - if (await pathExists(path.join(root, name))) return manager; + if (await pathExists(path.join(root, name))) { + return manager; + } } } const bunRoot = resolveBunGlobalRoot(); for (const name of ALL_PACKAGE_NAMES) { - if (await pathExists(path.join(bunRoot, name))) return "bun"; + if (await pathExists(path.join(bunRoot, name))) { + return "bun"; + } } return null; } export function globalInstallArgs(manager: GlobalInstallManager, spec: string): string[] { - if (manager === "pnpm") return ["pnpm", "add", "-g", spec]; - if (manager === "bun") return ["bun", "add", "-g", spec]; + if (manager === "pnpm") { + return ["pnpm", "add", "-g", spec]; + } + if (manager === "bun") { + return ["bun", "add", "-g", spec]; + } return ["npm", "i", "-g", spec]; } diff --git a/src/infra/update-runner.test.ts b/src/infra/update-runner.test.ts index 918b0d3c8d..6492ad37d5 100644 --- a/src/infra/update-runner.test.ts +++ b/src/infra/update-runner.test.ts @@ -299,8 +299,11 @@ describe("runGatewayUpdate", () => { expect(result.after?.version).toBe("2.0.0"); expect(calls.some((call) => call === "bun add -g openclaw@latest")).toBe(true); } finally { - if (oldBunInstall === undefined) delete process.env.BUN_INSTALL; - else process.env.BUN_INSTALL = oldBunInstall; + if (oldBunInstall === undefined) { + delete process.env.BUN_INSTALL; + } else { + process.env.BUN_INSTALL = oldBunInstall; + } } }); diff --git a/src/infra/update-runner.ts b/src/infra/update-runner.ts index 5136d9f782..e03c3466d2 100644 --- a/src/infra/update-runner.ts +++ b/src/infra/update-runner.ts @@ -70,9 +70,13 @@ const DEFAULT_PACKAGE_NAME = "openclaw"; const CORE_PACKAGE_NAMES = new Set([DEFAULT_PACKAGE_NAME]); function normalizeDir(value?: string | null) { - if (!value) return null; + if (!value) { + return null; + } const trimmed = value.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } return path.resolve(trimmed); } @@ -80,8 +84,12 @@ function resolveNodeModulesBinPackageRoot(argv1: string): string | null { const normalized = path.resolve(argv1); const parts = normalized.split(path.sep); const binIndex = parts.lastIndexOf(".bin"); - if (binIndex <= 0) return null; - if (parts[binIndex - 1] !== "node_modules") return null; + if (binIndex <= 0) { + return null; + } + if (parts[binIndex - 1] !== "node_modules") { + return null; + } const binName = path.basename(normalized); const nodeModulesDir = parts.slice(0, binIndex).join(path.sep); return path.join(nodeModulesDir, binName); @@ -90,15 +98,21 @@ function resolveNodeModulesBinPackageRoot(argv1: string): string | null { function buildStartDirs(opts: UpdateRunnerOptions): string[] { const dirs: string[] = []; const cwd = normalizeDir(opts.cwd); - if (cwd) dirs.push(cwd); + if (cwd) { + dirs.push(cwd); + } const argv1 = normalizeDir(opts.argv1); if (argv1) { dirs.push(path.dirname(argv1)); const packageRoot = resolveNodeModulesBinPackageRoot(argv1); - if (packageRoot) dirs.push(packageRoot); + if (packageRoot) { + dirs.push(packageRoot); + } } const proc = normalizeDir(process.cwd()); - if (proc) dirs.push(proc); + if (proc) { + dirs.push(proc); + } return Array.from(new Set(dirs)); } @@ -131,7 +145,9 @@ async function readBranchName( const res = await runCommand(["git", "-C", root, "rev-parse", "--abbrev-ref", "HEAD"], { timeoutMs, }).catch(() => null); - if (!res || res.code !== 0) return null; + if (!res || res.code !== 0) { + return null; + } const branch = res.stdout.trim(); return branch || null; } @@ -145,7 +161,9 @@ async function listGitTags( const res = await runCommand(["git", "-C", root, "tag", "--list", pattern, "--sort=-v:refname"], { timeoutMs, }).catch(() => null); - if (!res || res.code !== 0) return []; + if (!res || res.code !== 0) { + return []; + } return res.stdout .split("\n") .map((line) => line.trim()) @@ -162,10 +180,16 @@ async function resolveChannelTag( if (channel === "beta") { const betaTag = tags.find((tag) => isBetaTag(tag)) ?? null; const stableTag = tags.find((tag) => isStableTag(tag)) ?? null; - if (!betaTag) return stableTag; - if (!stableTag) return betaTag; + if (!betaTag) { + return stableTag; + } + if (!stableTag) { + return betaTag; + } const cmp = compareSemverStrings(betaTag, stableTag); - if (cmp != null && cmp < 0) return stableTag; + if (cmp != null && cmp < 0) { + return stableTag; + } return betaTag; } return tags.find((tag) => isStableTag(tag)) ?? null; @@ -182,7 +206,9 @@ async function resolveGitRoot( }); if (res.code === 0) { const root = res.stdout.trim(); - if (root) return root; + if (root) { + return root; + } } } return null; @@ -197,12 +223,16 @@ async function findPackageRoot(candidates: string[]) { const raw = await fs.readFile(pkgPath, "utf-8"); const parsed = JSON.parse(raw) as { name?: string }; const name = parsed?.name?.trim(); - if (name && CORE_PACKAGE_NAMES.has(name)) return current; + if (name && CORE_PACKAGE_NAMES.has(name)) { + return current; + } } catch { // ignore } const parent = path.dirname(current); - if (parent === current) break; + if (parent === current) { + break; + } current = parent; } } @@ -214,15 +244,23 @@ async function detectPackageManager(root: string) { const raw = await fs.readFile(path.join(root, "package.json"), "utf-8"); const parsed = JSON.parse(raw) as { packageManager?: string }; const pm = parsed?.packageManager?.split("@")[0]?.trim(); - if (pm === "pnpm" || pm === "bun" || pm === "npm") return pm; + if (pm === "pnpm" || pm === "bun" || pm === "npm") { + return pm; + } } catch { // ignore } const files = await fs.readdir(root).catch((): string[] => []); - if (files.includes("pnpm-lock.yaml")) return "pnpm"; - if (files.includes("bun.lockb")) return "bun"; - if (files.includes("package-lock.json")) return "npm"; + if (files.includes("pnpm-lock.yaml")) { + return "pnpm"; + } + if (files.includes("bun.lockb")) { + return "bun"; + } + if (files.includes("package-lock.json")) { + return "npm"; + } return "npm"; } @@ -276,22 +314,36 @@ async function runStep(opts: RunStepOptions): Promise { } function managerScriptArgs(manager: "pnpm" | "bun" | "npm", script: string, args: string[] = []) { - if (manager === "pnpm") return ["pnpm", script, ...args]; - if (manager === "bun") return ["bun", "run", script, ...args]; - if (args.length > 0) return ["npm", "run", script, "--", ...args]; + if (manager === "pnpm") { + return ["pnpm", script, ...args]; + } + if (manager === "bun") { + return ["bun", "run", script, ...args]; + } + if (args.length > 0) { + return ["npm", "run", script, "--", ...args]; + } return ["npm", "run", script]; } function managerInstallArgs(manager: "pnpm" | "bun" | "npm") { - if (manager === "pnpm") return ["pnpm", "install"]; - if (manager === "bun") return ["bun", "install"]; + if (manager === "pnpm") { + return ["pnpm", "install"]; + } + if (manager === "bun") { + return ["bun", "install"]; + } return ["npm", "install"]; } function normalizeTag(tag?: string) { const trimmed = tag?.trim(); - if (!trimmed) return "latest"; - if (trimmed.startsWith("openclaw@")) return trimmed.slice("openclaw@".length); + if (!trimmed) { + return "latest"; + } + if (trimmed.startsWith("openclaw@")) { + return trimmed.slice("openclaw@".length); + } if (trimmed.startsWith(`${DEFAULT_PACKAGE_NAME}@`)) { return trimmed.slice(`${DEFAULT_PACKAGE_NAME}@`.length); } @@ -537,25 +589,33 @@ export async function runGatewayUpdate(opts: UpdateRunnerOptions = {}): Promise< ), ); steps.push(checkoutStep); - if (checkoutStep.exitCode !== 0) continue; + if (checkoutStep.exitCode !== 0) { + continue; + } const depsStep = await runStep( step(`preflight deps install (${shortSha})`, managerInstallArgs(manager), worktreeDir), ); steps.push(depsStep); - if (depsStep.exitCode !== 0) continue; + if (depsStep.exitCode !== 0) { + continue; + } const lintStep = await runStep( step(`preflight lint (${shortSha})`, managerScriptArgs(manager, "lint"), worktreeDir), ); steps.push(lintStep); - if (lintStep.exitCode !== 0) continue; + if (lintStep.exitCode !== 0) { + continue; + } const buildStep = await runStep( step(`preflight build (${shortSha})`, managerScriptArgs(manager, "build"), worktreeDir), ); steps.push(buildStep); - if (buildStep.exitCode !== 0) continue; + if (buildStep.exitCode !== 0) { + continue; + } selectedSha = sha; break; diff --git a/src/infra/update-startup.ts b/src/infra/update-startup.ts index d3dbc58621..d24652f655 100644 --- a/src/infra/update-startup.ts +++ b/src/infra/update-startup.ts @@ -19,8 +19,12 @@ const UPDATE_CHECK_FILENAME = "update-check.json"; const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; function shouldSkipCheck(allowInTests: boolean): boolean { - if (allowInTests) return false; - if (process.env.VITEST || process.env.NODE_ENV === "test") return true; + if (allowInTests) { + return false; + } + if (process.env.VITEST || process.env.NODE_ENV === "test") { + return true; + } return false; } @@ -45,16 +49,24 @@ export async function runGatewayUpdateCheck(params: { isNixMode: boolean; allowInTests?: boolean; }): Promise { - if (shouldSkipCheck(Boolean(params.allowInTests))) return; - if (params.isNixMode) return; - if (params.cfg.update?.checkOnStart === false) return; + if (shouldSkipCheck(Boolean(params.allowInTests))) { + return; + } + if (params.isNixMode) { + return; + } + if (params.cfg.update?.checkOnStart === false) { + return; + } const statePath = path.join(resolveStateDir(), UPDATE_CHECK_FILENAME); const state = await readState(statePath); const now = Date.now(); const lastCheckedAt = state.lastCheckedAt ? Date.parse(state.lastCheckedAt) : null; if (lastCheckedAt && Number.isFinite(lastCheckedAt)) { - if (now - lastCheckedAt < UPDATE_CHECK_INTERVAL_MS) return; + if (now - lastCheckedAt < UPDATE_CHECK_INTERVAL_MS) { + return; + } } const root = await resolveOpenClawPackageRoot({ diff --git a/src/infra/warnings.ts b/src/infra/warnings.ts index b4d82a56ee..91a98b0f32 100644 --- a/src/infra/warnings.ts +++ b/src/infra/warnings.ts @@ -26,11 +26,15 @@ export function installProcessWarningFilter(): void { const globalState = globalThis as typeof globalThis & { [warningFilterKey]?: { installed: boolean }; }; - if (globalState[warningFilterKey]?.installed) return; + if (globalState[warningFilterKey]?.installed) { + return; + } globalState[warningFilterKey] = { installed: true }; process.on("warning", (warning: Warning) => { - if (shouldIgnoreWarning(warning)) return; + if (shouldIgnoreWarning(warning)) { + return; + } process.stderr.write(`${warning.stack ?? warning.toString()}\n`); }); } diff --git a/src/infra/widearea-dns.ts b/src/infra/widearea-dns.ts index 01106b6844..6829b96619 100644 --- a/src/infra/widearea-dns.ts +++ b/src/infra/widearea-dns.ts @@ -6,7 +6,9 @@ import { CONFIG_DIR, ensureDir } from "../utils.js"; export function normalizeWideAreaDomain(raw?: string | null): string | null { const trimmed = raw?.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } return trimmed.endsWith(".") ? trimmed : `${trimmed}.`; } @@ -53,15 +55,21 @@ function formatYyyyMmDd(date: Date): string { function nextSerial(existingSerial: number | null, now: Date): number { const today = formatYyyyMmDd(now); const base = Number.parseInt(`${today}01`, 10); - if (!existingSerial || !Number.isFinite(existingSerial)) return base; + if (!existingSerial || !Number.isFinite(existingSerial)) { + return base; + } const existing = String(existingSerial); - if (existing.startsWith(today)) return existingSerial + 1; + if (existing.startsWith(today)) { + return existingSerial + 1; + } return base; } function extractSerial(zoneText: string): number | null { const match = zoneText.match(/^\s*@\s+IN\s+SOA\s+\S+\s+\S+\s+(\d+)\s+/m); - if (!match) return null; + if (!match) { + return null; + } const parsed = Number.parseInt(match[1], 10); return Number.isFinite(parsed) ? parsed : null; } diff --git a/src/infra/ws.ts b/src/infra/ws.ts index 99d7537803..b4db274308 100644 --- a/src/infra/ws.ts +++ b/src/infra/ws.ts @@ -6,9 +6,15 @@ export function rawDataToString( data: WebSocket.RawData, encoding: BufferEncoding = "utf8", ): string { - if (typeof data === "string") return data; - if (Buffer.isBuffer(data)) return data.toString(encoding); - if (Array.isArray(data)) return Buffer.concat(data).toString(encoding); + if (typeof data === "string") { + return data; + } + if (Buffer.isBuffer(data)) { + return data.toString(encoding); + } + if (Array.isArray(data)) { + return Buffer.concat(data).toString(encoding); + } if (data instanceof ArrayBuffer) { return Buffer.from(data).toString(encoding); } diff --git a/src/infra/wsl.ts b/src/infra/wsl.ts index 3de3d9ec9e..df52ab934a 100644 --- a/src/infra/wsl.ts +++ b/src/infra/wsl.ts @@ -10,7 +10,9 @@ export function isWSLEnv(): boolean { } export async function isWSL(): Promise { - if (wslCached !== null) return wslCached; + if (wslCached !== null) { + return wslCached; + } if (isWSLEnv()) { wslCached = true; return wslCached; diff --git a/src/line/accounts.ts b/src/line/accounts.ts index 6ec2a1cfd7..f8b94e2ae0 100644 --- a/src/line/accounts.ts +++ b/src/line/accounts.ts @@ -10,7 +10,9 @@ import type { export const DEFAULT_ACCOUNT_ID = "default"; function readFileIfExists(filePath: string | undefined): string | undefined { - if (!filePath) return undefined; + if (!filePath) { + return undefined; + } try { return fs.readFileSync(filePath, "utf-8").trim(); } catch { diff --git a/src/line/auto-reply-delivery.ts b/src/line/auto-reply-delivery.ts index 96b1efad7f..bbb0012c37 100644 --- a/src/line/auto-reply-delivery.ts +++ b/src/line/auto-reply-delivery.ts @@ -59,7 +59,9 @@ export async function deliverLineAutoReply(params: { let replyTokenUsed = params.replyTokenUsed; const pushLineMessages = async (messages: messagingApi.Message[]): Promise => { - if (messages.length === 0) return; + if (messages.length === 0) { + return; + } for (let i = 0; i < messages.length; i += 5) { await deps.pushMessagesLine(to, messages.slice(i, i + 5), { accountId, @@ -71,7 +73,9 @@ export async function deliverLineAutoReply(params: { messages: messagingApi.Message[], allowReplyToken: boolean, ): Promise => { - if (messages.length === 0) return; + if (messages.length === 0) { + return; + } let remaining = messages; if (allowReplyToken && replyToken && !replyTokenUsed) { diff --git a/src/line/bot-access.ts b/src/line/bot-access.ts index 2df9502fe9..4498826611 100644 --- a/src/line/bot-access.ts +++ b/src/line/bot-access.ts @@ -6,8 +6,12 @@ export type NormalizedAllowFrom = { function normalizeAllowEntry(value: string | number): string { const trimmed = String(value).trim(); - if (!trimmed) return ""; - if (trimmed === "*") return "*"; + if (!trimmed) { + return ""; + } + if (trimmed === "*") { + return "*"; + } return trimmed.replace(/^line:(?:user:)?/i, ""); } @@ -31,7 +35,9 @@ export const normalizeAllowFromWithStore = (params: { export const firstDefined = (...values: Array) => { for (const value of values) { - if (typeof value !== "undefined") return value; + if (typeof value !== "undefined") { + return value; + } } return undefined; }; @@ -41,8 +47,14 @@ export const isSenderAllowed = (params: { senderId?: string; }): boolean => { const { allow, senderId } = params; - if (!allow.hasEntries) return false; - if (allow.hasWildcard) return true; - if (!senderId) return false; + if (!allow.hasEntries) { + return false; + } + if (allow.hasWildcard) { + return true; + } + if (!senderId) { + return false; + } return allow.entries.includes(senderId); }; diff --git a/src/line/bot-handlers.ts b/src/line/bot-handlers.ts index e3f1eaa48a..5884e0a2c6 100644 --- a/src/line/bot-handlers.ts +++ b/src/line/bot-handlers.ts @@ -87,7 +87,9 @@ async function sendLinePairingReply(params: { channel: "line", id: senderId, }); - if (!created) return; + if (!created) { + return; + } logVerbose(`line pairing request sender=${senderId}`); const idLabel = (() => { try { @@ -219,7 +221,9 @@ async function handleMessageEvent(event: MessageEvent, context: LineHandlerConte const { cfg, account, runtime, mediaMaxBytes, processMessage } = context; const message = event.message; - if (!(await shouldProcessLineEvent(event, context))) return; + if (!(await shouldProcessLineEvent(event, context))) { + return; + } // Download media if applicable const allMedia: MediaRef[] = []; @@ -290,14 +294,18 @@ async function handlePostbackEvent( const data = event.postback.data; logVerbose(`line: received postback: ${data}`); - if (!(await shouldProcessLineEvent(event, context))) return; + if (!(await shouldProcessLineEvent(event, context))) { + return; + } const postbackContext = await buildLinePostbackContext({ event, cfg: context.cfg, account: context.account, }); - if (!postbackContext) return; + if (!postbackContext) { + return; + } await context.processMessage(postbackContext); } diff --git a/src/line/bot-message-context.ts b/src/line/bot-message-context.ts index 20244465d3..2de94fd75c 100644 --- a/src/line/bot-message-context.ts +++ b/src/line/bot-message-context.ts @@ -335,7 +335,9 @@ export async function buildLinePostbackContext(params: { const timestamp = event.timestamp; const rawData = event.postback?.data?.trim() ?? ""; - if (!rawData) return null; + if (!rawData) { + return null; + } let rawBody = rawData; if (rawData.includes("line.action=")) { const params = new URLSearchParams(rawData); diff --git a/src/line/flex-templates.ts b/src/line/flex-templates.ts index 077f835db5..7b8c9f0d3e 100644 --- a/src/line/flex-templates.ts +++ b/src/line/flex-templates.ts @@ -852,8 +852,12 @@ export function createAgendaCard(params: { // Secondary info line const secondaryParts: string[] = []; - if (event.location) secondaryParts.push(event.location); - if (event.calendar) secondaryParts.push(event.calendar); + if (event.location) { + secondaryParts.push(event.location); + } + if (event.calendar) { + secondaryParts.push(event.calendar); + } if (secondaryParts.length > 0) { detailContents.push({ diff --git a/src/line/http-registry.ts b/src/line/http-registry.ts index 1d971e752c..fcf6d3e98d 100644 --- a/src/line/http-registry.ts +++ b/src/line/http-registry.ts @@ -16,7 +16,9 @@ const lineHttpRoutes = new Map(); export function normalizeLineWebhookPath(path?: string | null): string { const trimmed = path?.trim(); - if (!trimmed) return "/line/webhook"; + if (!trimmed) { + return "/line/webhook"; + } return trimmed.startsWith("/") ? trimmed : `/${trimmed}`; } @@ -39,7 +41,9 @@ export async function handleLineHttpRequest( ): Promise { const url = new URL(req.url ?? "/", "http://localhost"); const handler = lineHttpRoutes.get(url.pathname); - if (!handler) return false; + if (!handler) { + return false; + } await handler(req, res); return true; } diff --git a/src/line/markdown-to-line.ts b/src/line/markdown-to-line.ts index 21253c36a9..ef41a38462 100644 --- a/src/line/markdown-to-line.ts +++ b/src/line/markdown-to-line.ts @@ -80,8 +80,12 @@ function parseTableRow(row: string): string[] { .map((cell) => cell.trim()) .filter((cell, index, arr) => { // Filter out empty cells at start/end (from leading/trailing pipes) - if (index === 0 && cell === "") return false; - if (index === arr.length - 1 && cell === "") return false; + if (index === 0 && cell === "") { + return false; + } + if (index === arr.length - 1 && cell === "") { + return false; + } return true; }); } @@ -94,7 +98,9 @@ export function convertTableToFlexBubble(table: MarkdownTable): FlexBubble { value: string | undefined, ): { text: string; bold: boolean; hasMarkup: boolean } => { const raw = value?.trim() ?? ""; - if (!raw) return { text: "-", bold: false, hasMarkup: false }; + if (!raw) { + return { text: "-", bold: false, hasMarkup: false }; + } let hasMarkup = false; const stripped = raw.replace(/\*\*(.+?)\*\*/g, (_, inner) => { @@ -417,17 +423,29 @@ export function processLineMessage(text: string): ProcessedLineMessage { export function hasMarkdownToConvert(text: string): boolean { // Check for tables MARKDOWN_TABLE_REGEX.lastIndex = 0; - if (MARKDOWN_TABLE_REGEX.test(text)) return true; + if (MARKDOWN_TABLE_REGEX.test(text)) { + return true; + } // Check for code blocks MARKDOWN_CODE_BLOCK_REGEX.lastIndex = 0; - if (MARKDOWN_CODE_BLOCK_REGEX.test(text)) return true; + if (MARKDOWN_CODE_BLOCK_REGEX.test(text)) { + return true; + } // Check for other markdown patterns - if (/\*\*[^*]+\*\*/.test(text)) return true; // bold - if (/~~[^~]+~~/.test(text)) return true; // strikethrough - if (/^#{1,6}\s+/m.test(text)) return true; // headers - if (/^>\s+/m.test(text)) return true; // blockquotes + if (/\*\*[^*]+\*\*/.test(text)) { + return true; + } // bold + if (/~~[^~]+~~/.test(text)) { + return true; + } // strikethrough + if (/^#{1,6}\s+/m.test(text)) { + return true; + } // headers + if (/^>\s+/m.test(text)) { + return true; + } // blockquotes return false; } diff --git a/src/line/monitor.ts b/src/line/monitor.ts index f06fd693ec..6017c01684 100644 --- a/src/line/monitor.ts +++ b/src/line/monitor.ts @@ -105,7 +105,9 @@ function startLineLoadingKeepalive(params: { let stopped = false; const trigger = () => { - if (stopped) return; + if (stopped) { + return; + } void showLoadingAnimation(params.userId, { accountId: params.accountId, loadingSeconds, @@ -116,7 +118,9 @@ function startLineLoadingKeepalive(params: { const timer = setInterval(trigger, intervalMs); return () => { - if (stopped) return; + if (stopped) { + return; + } stopped = true; clearInterval(timer); }; @@ -154,7 +158,9 @@ export async function monitorLineProvider( runtime, config, onMessage: async (ctx) => { - if (!ctx) return; + if (!ctx) { + return; + } const { ctxPayload, replyToken, route } = ctx; diff --git a/src/line/probe.ts b/src/line/probe.ts index d538d4271f..d5f7755cd2 100644 --- a/src/line/probe.ts +++ b/src/line/probe.ts @@ -32,12 +32,16 @@ export async function probeLineBot( } function withTimeout(promise: Promise, timeoutMs: number): Promise { - if (!timeoutMs || timeoutMs <= 0) return promise; + if (!timeoutMs || timeoutMs <= 0) { + return promise; + } let timer: NodeJS.Timeout | null = null; const timeout = new Promise((_, reject) => { timer = setTimeout(() => reject(new Error("timeout")), timeoutMs); }); return Promise.race([promise, timeout]).finally(() => { - if (timer) clearTimeout(timer); + if (timer) { + clearTimeout(timer); + } }); } diff --git a/src/line/rich-menu.ts b/src/line/rich-menu.ts index 6149405a90..670ac9b76e 100644 --- a/src/line/rich-menu.ts +++ b/src/line/rich-menu.ts @@ -42,7 +42,9 @@ function resolveToken( explicit: string | undefined, params: { accountId: string; channelAccessToken: string }, ): string { - if (explicit?.trim()) return explicit.trim(); + if (explicit?.trim()) { + return explicit.trim(); + } if (!params.channelAccessToken) { throw new Error( `LINE channel access token missing for account "${params.accountId}" (set channels.line.channelAccessToken or LINE_CHANNEL_ACCESS_TOKEN).`, diff --git a/src/line/send.ts b/src/line/send.ts index 68be26a29e..0c844b1fb9 100644 --- a/src/line/send.ts +++ b/src/line/send.ts @@ -35,7 +35,9 @@ function resolveToken( explicit: string | undefined, params: { accountId: string; channelAccessToken: string }, ): string { - if (explicit?.trim()) return explicit.trim(); + if (explicit?.trim()) { + return explicit.trim(); + } if (!params.channelAccessToken) { throw new Error( `LINE channel access token missing for account "${params.accountId}" (set channels.line.channelAccessToken or LINE_CHANNEL_ACCESS_TOKEN).`, @@ -46,7 +48,9 @@ function resolveToken( function normalizeTarget(to: string): string { const trimmed = to.trim(); - if (!trimmed) throw new Error("Recipient is required for LINE sends"); + if (!trimmed) { + throw new Error("Recipient is required for LINE sends"); + } // Strip internal prefixes let normalized = trimmed @@ -55,7 +59,9 @@ function normalizeTarget(to: string): string { .replace(/^line:user:/i, "") .replace(/^line:/i, ""); - if (!normalized) throw new Error("Recipient is required for LINE sends"); + if (!normalized) { + throw new Error("Recipient is required for LINE sends"); + } return normalized; } @@ -91,7 +97,9 @@ export function createLocationMessage(location: { } function logLineHttpError(err: unknown, context: string): void { - if (!err || typeof err !== "object") return; + if (!err || typeof err !== "object") { + return; + } const { status, statusText, body } = err as { status?: number; statusText?: string; diff --git a/src/line/webhook.ts b/src/line/webhook.ts index 9986617f9e..10a3d7dbbb 100644 --- a/src/line/webhook.ts +++ b/src/line/webhook.ts @@ -14,7 +14,9 @@ function readRawBody(req: Request): string | null { const rawBody = (req as { rawBody?: string | Buffer }).rawBody ?? (typeof req.body === "string" || Buffer.isBuffer(req.body) ? req.body : null); - if (!rawBody) return null; + if (!rawBody) { + return null; + } return Buffer.isBuffer(rawBody) ? rawBody.toString("utf-8") : rawBody; } diff --git a/src/link-understanding/detect.ts b/src/link-understanding/detect.ts index 9edecde63c..79899f94b6 100644 --- a/src/link-understanding/detect.ts +++ b/src/link-understanding/detect.ts @@ -18,8 +18,12 @@ function resolveMaxLinks(value?: number): number { function isAllowedUrl(raw: string): boolean { try { const parsed = new URL(raw); - if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return false; - if (parsed.hostname === "127.0.0.1") return false; + if (parsed.protocol !== "http:" && parsed.protocol !== "https:") { + return false; + } + if (parsed.hostname === "127.0.0.1") { + return false; + } return true; } catch { return false; @@ -28,7 +32,9 @@ function isAllowedUrl(raw: string): boolean { export function extractLinksFromMessage(message: string, opts?: { maxLinks?: number }): string[] { const source = message?.trim(); - if (!source) return []; + if (!source) { + return []; + } const maxLinks = resolveMaxLinks(opts?.maxLinks); const sanitized = stripMarkdownLinks(source); @@ -37,12 +43,20 @@ export function extractLinksFromMessage(message: string, opts?: { maxLinks?: num for (const match of sanitized.matchAll(BARE_LINK_RE)) { const raw = match[0]?.trim(); - if (!raw) continue; - if (!isAllowedUrl(raw)) continue; - if (seen.has(raw)) continue; + if (!raw) { + continue; + } + if (!isAllowedUrl(raw)) { + continue; + } + if (seen.has(raw)) { + continue; + } seen.add(raw); results.push(raw); - if (results.length >= maxLinks) break; + if (results.length >= maxLinks) { + break; + } } return results; diff --git a/src/link-understanding/format.ts b/src/link-understanding/format.ts index b28d16a1a3..a81a86bf3f 100644 --- a/src/link-understanding/format.ts +++ b/src/link-understanding/format.ts @@ -5,6 +5,8 @@ export function formatLinkUnderstandingBody(params: { body?: string; outputs: st } const base = (params.body ?? "").trim(); - if (!base) return outputs.join("\n"); + if (!base) { + return outputs.join("\n"); + } return `${base}\n\n${outputs.join("\n")}`; } diff --git a/src/link-understanding/runner.ts b/src/link-understanding/runner.ts index ff0a46b193..bc6dc2bd9f 100644 --- a/src/link-understanding/runner.ts +++ b/src/link-understanding/runner.ts @@ -44,9 +44,13 @@ async function runCliEntry(params: { url: string; config?: LinkToolsConfig; }): Promise { - if ((params.entry.type ?? "cli") !== "cli") return null; + if ((params.entry.type ?? "cli") !== "cli") { + return null; + } const command = params.entry.command.trim(); - if (!command) return null; + if (!command) { + return null; + } const args = params.entry.args ?? []; const timeoutMs = resolveTimeoutMsFromConfig({ config: params.config, entry: params.entry }); const templCtx = { @@ -84,7 +88,9 @@ async function runLinkEntries(params: { url: params.url, config: params.config, }); - if (output) return output; + if (output) { + return output; + } } catch (err) { lastError = err; if (shouldLogVerbose()) { @@ -104,7 +110,9 @@ export async function runLinkUnderstanding(params: { message?: string; }): Promise { const config = params.cfg.tools?.links; - if (!config || config.enabled === false) return { urls: [], outputs: [] }; + if (!config || config.enabled === false) { + return { urls: [], outputs: [] }; + } const scopeDecision = resolveScopeDecision({ config, ctx: params.ctx }); if (scopeDecision === "deny") { @@ -116,10 +124,14 @@ export async function runLinkUnderstanding(params: { const message = params.message ?? params.ctx.CommandBody ?? params.ctx.RawBody ?? params.ctx.Body; const links = extractLinksFromMessage(message ?? "", { maxLinks: config?.maxLinks }); - if (links.length === 0) return { urls: [], outputs: [] }; + if (links.length === 0) { + return { urls: [], outputs: [] }; + } const entries = config?.models ?? []; - if (entries.length === 0) return { urls: links, outputs: [] }; + if (entries.length === 0) { + return { urls: links, outputs: [] }; + } const outputs: string[] = []; for (const url of links) { @@ -129,7 +141,9 @@ export async function runLinkUnderstanding(params: { url, config, }); - if (output) outputs.push(output); + if (output) { + outputs.push(output); + } } return { urls: links, outputs }; diff --git a/src/logger.ts b/src/logger.ts index 6015cf3441..4ae1cb20d5 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -7,7 +7,9 @@ const subsystemPrefixRe = /^([a-z][a-z0-9-]{1,20}):\s+(.*)$/i; function splitSubsystem(message: string) { const match = message.match(subsystemPrefixRe); - if (!match) return null; + if (!match) { + return null; + } const [, subsystem, rest] = match; return { subsystem, rest }; } diff --git a/src/logging/config.ts b/src/logging/config.ts index 23060ac68d..7a98126909 100644 --- a/src/logging/config.ts +++ b/src/logging/config.ts @@ -10,11 +10,15 @@ type LoggingConfig = OpenClawConfig["logging"]; export function readLoggingConfig(): LoggingConfig | undefined { const configPath = resolveConfigPath(); try { - if (!fs.existsSync(configPath)) return undefined; + if (!fs.existsSync(configPath)) { + return undefined; + } const raw = fs.readFileSync(configPath, "utf-8"); const parsed = json5.parse(raw); const logging = parsed?.logging; - if (!logging || typeof logging !== "object" || Array.isArray(logging)) return undefined; + if (!logging || typeof logging !== "object" || Array.isArray(logging)) { + return undefined; + } return logging as LoggingConfig; } catch { return undefined; diff --git a/src/logging/console.ts b/src/logging/console.ts index 9d64f49ab3..fe33876964 100644 --- a/src/logging/console.ts +++ b/src/logging/console.ts @@ -19,7 +19,9 @@ export type ConsoleLoggerSettings = ConsoleSettings; const requireConfig = createRequire(import.meta.url); function normalizeConsoleLevel(level?: string): LogLevel { - if (isVerbose()) return "debug"; + if (isVerbose()) { + return "debug"; + } return normalizeLogLevel(level, "info"); } @@ -27,7 +29,9 @@ function normalizeConsoleStyle(style?: string): ConsoleStyle { if (style === "compact" || style === "json" || style === "pretty") { return style; } - if (!process.stdout.isTTY) return "compact"; + if (!process.stdout.isTTY) { + return "compact"; + } return "pretty"; } @@ -57,7 +61,9 @@ function resolveConsoleSettings(): ConsoleSettings { } function consoleSettingsChanged(a: ConsoleSettings | null, b: ConsoleSettings) { - if (!a) return true; + if (!a) { + return true; + } return a.level !== b.level || a.style !== b.style; } @@ -110,7 +116,9 @@ const SUPPRESSED_CONSOLE_PREFIXES = [ ] as const; function shouldSuppressConsoleMessage(message: string): boolean { - if (isVerbose()) return false; + if (isVerbose()) { + return false; + } if (SUPPRESSED_CONSOLE_PREFIXES.some((prefix) => message.startsWith(prefix))) { return true; } @@ -130,7 +138,9 @@ function isEpipeError(err: unknown): boolean { function formatConsoleTimestamp(style: ConsoleStyle): string { const now = new Date().toISOString(); - if (style === "pretty") return now.slice(11, 19); + if (style === "pretty") { + return now.slice(11, 19); + } return now; } @@ -140,7 +150,9 @@ function hasTimestampPrefix(value: string): boolean { function isJsonPayload(value: string): boolean { const trimmed = value.trim(); - if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return false; + if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) { + return false; + } try { JSON.parse(trimmed); return true; @@ -154,7 +166,9 @@ function isJsonPayload(value: string): boolean { * This keeps user-facing output unchanged but guarantees every console call is captured in log files. */ export function enableConsoleCapture(): void { - if (loggingState.consolePatched) return; + if (loggingState.consolePatched) { + return; + } loggingState.consolePatched = true; let logger: ReturnType | null = null; @@ -184,7 +198,9 @@ export function enableConsoleCapture(): void { (level: LogLevel, orig: (...args: unknown[]) => void) => (...args: unknown[]) => { const formatted = util.format(...args); - if (shouldSuppressConsoleMessage(formatted)) return; + if (shouldSuppressConsoleMessage(formatted)) { + return; + } const trimmed = stripAnsi(formatted).trimStart(); const shouldPrefixTimestamp = loggingState.consoleTimestampPrefix && @@ -219,7 +235,9 @@ export function enableConsoleCapture(): void { const line = timestamp ? `${timestamp} ${formatted}` : formatted; process.stderr.write(`${line}\n`); } catch (err) { - if (isEpipeError(err)) return; + if (isEpipeError(err)) { + return; + } throw err; } } else { @@ -238,7 +256,9 @@ export function enableConsoleCapture(): void { } orig.call(console, timestamp, ...args); } catch (err) { - if (isEpipeError(err)) return; + if (isEpipeError(err)) { + return; + } throw err; } } diff --git a/src/logging/diagnostic.ts b/src/logging/diagnostic.ts index ff51dd4bff..24dfc89611 100644 --- a/src/logging/diagnostic.ts +++ b/src/logging/diagnostic.ts @@ -41,8 +41,12 @@ function getSessionState(ref: SessionRef): SessionState { const key = resolveSessionKey(ref); const existing = sessionStates.get(key); if (existing) { - if (ref.sessionId) existing.sessionId = ref.sessionId; - if (ref.sessionKey) existing.sessionKey = ref.sessionKey; + if (ref.sessionId) { + existing.sessionId = ref.sessionId; + } + if (ref.sessionKey) { + existing.sessionKey = ref.sessionKey; + } return existing; } const created: SessionState = { @@ -201,7 +205,9 @@ export function logSessionStateChange( const prevState = state.state; state.state = params.state; state.lastActivity = Date.now(); - if (params.state === "idle") state.queueDepth = Math.max(0, state.queueDepth - 1); + if (params.state === "idle") { + state.queueDepth = Math.max(0, state.queueDepth - 1); + } if (!isProbeSession) { diag.debug( `session state: sessionId=${state.sessionId ?? "unknown"} sessionKey=${ @@ -292,7 +298,9 @@ export function logActiveRuns() { let heartbeatInterval: NodeJS.Timeout | null = null; export function startDiagnosticHeartbeat() { - if (heartbeatInterval) return; + if (heartbeatInterval) { + return; + } heartbeatInterval = setInterval(() => { const now = Date.now(); const activeCount = Array.from(sessionStates.values()).filter( @@ -311,8 +319,12 @@ export function startDiagnosticHeartbeat() { activeCount > 0 || waitingCount > 0 || totalQueued > 0; - if (!hasActivity) return; - if (now - lastActivityAt > 120_000 && activeCount === 0 && waitingCount === 0) return; + if (!hasActivity) { + return; + } + if (now - lastActivityAt > 120_000 && activeCount === 0 && waitingCount === 0) { + return; + } diag.debug( `heartbeat: webhooks=${webhookStats.received}/${webhookStats.processed}/${webhookStats.errors} active=${activeCount} waiting=${waitingCount} queued=${totalQueued}`, diff --git a/src/logging/logger.ts b/src/logging/logger.ts index ef0dddcf53..231802f21c 100644 --- a/src/logging/logger.ts +++ b/src/logging/logger.ts @@ -42,7 +42,9 @@ const externalTransports = new Set(); function attachExternalTransport(logger: TsLogger, transport: LogTransport): void { logger.attachTransport((logObj: LogObj) => { - if (!externalTransports.has(transport)) return; + if (!externalTransports.has(transport)) { + return; + } try { transport(logObj as LogTransportRecord); } catch { @@ -70,14 +72,20 @@ function resolveSettings(): ResolvedSettings { } function settingsChanged(a: ResolvedSettings | null, b: ResolvedSettings) { - if (!a) return true; + if (!a) { + return true; + } return a.level !== b.level || a.file !== b.file; } export function isFileLogLevelEnabled(level: LogLevel): boolean { const settings = (loggingState.cachedSettings as ResolvedSettings | null) ?? resolveSettings(); - if (!loggingState.cachedSettings) loggingState.cachedSettings = settings; - if (settings.level === "silent") return false; + if (!loggingState.cachedSettings) { + loggingState.cachedSettings = settings; + } + if (settings.level === "silent") { + return false; + } return levelToMinLevel(level) <= levelToMinLevel(settings.level); } @@ -223,8 +231,12 @@ function pruneOldRollingLogs(dir: string): void { const entries = fs.readdirSync(dir, { withFileTypes: true }); const cutoff = Date.now() - MAX_LOG_AGE_MS; for (const entry of entries) { - if (!entry.isFile()) continue; - if (!entry.name.startsWith(`${LOG_PREFIX}-`) || !entry.name.endsWith(LOG_SUFFIX)) continue; + if (!entry.isFile()) { + continue; + } + if (!entry.name.startsWith(`${LOG_PREFIX}-`) || !entry.name.endsWith(LOG_SUFFIX)) { + continue; + } const fullPath = path.join(dir, entry.name); try { const stat = fs.statSync(fullPath); diff --git a/src/logging/parse-log-line.ts b/src/logging/parse-log-line.ts index 1554fe7a2f..97623efa8e 100644 --- a/src/logging/parse-log-line.ts +++ b/src/logging/parse-log-line.ts @@ -10,7 +10,9 @@ export type ParsedLogLine = { function extractMessage(value: Record): string { const parts: string[] = []; for (const key of Object.keys(value)) { - if (!/^\d+$/.test(key)) continue; + if (!/^\d+$/.test(key)) { + continue; + } const item = value[key]; if (typeof item === "string") { parts.push(item); @@ -22,7 +24,9 @@ function extractMessage(value: Record): string { } function parseMetaName(raw?: unknown): { subsystem?: string; module?: string } { - if (typeof raw !== "string") return {}; + if (typeof raw !== "string") { + return {}; + } try { const parsed = JSON.parse(raw) as Record; return { diff --git a/src/logging/redact.ts b/src/logging/redact.ts index c3926d8681..5adb892dd2 100644 --- a/src/logging/redact.ts +++ b/src/logging/redact.ts @@ -46,7 +46,9 @@ function normalizeMode(value?: string): RedactSensitiveMode { } function parsePattern(raw: string): RegExp | null { - if (!raw.trim()) return null; + if (!raw.trim()) { + return null; + } const match = raw.match(/^\/(.+)\/([gimsuy]*)$/); try { if (match) { @@ -65,7 +67,9 @@ function resolvePatterns(value?: string[]): RegExp[] { } function maskToken(token: string): string { - if (token.length < DEFAULT_REDACT_MIN_LENGTH) return "***"; + if (token.length < DEFAULT_REDACT_MIN_LENGTH) { + return "***"; + } const start = token.slice(0, DEFAULT_REDACT_KEEP_START); const end = token.slice(-DEFAULT_REDACT_KEEP_END); return `${start}…${end}`; @@ -73,16 +77,22 @@ function maskToken(token: string): string { function redactPemBlock(block: string): string { const lines = block.split(/\r?\n/).filter(Boolean); - if (lines.length < 2) return "***"; + if (lines.length < 2) { + return "***"; + } return `${lines[0]}\n…redacted…\n${lines[lines.length - 1]}`; } function redactMatch(match: string, groups: string[]): string { - if (match.includes("PRIVATE KEY-----")) return redactPemBlock(match); + if (match.includes("PRIVATE KEY-----")) { + return redactPemBlock(match); + } const token = groups.filter((value) => typeof value === "string" && value.length > 0).at(-1) ?? match; const masked = maskToken(token); - if (token === match) return masked; + if (token === match) { + return masked; + } return match.replace(token, masked); } @@ -113,17 +123,25 @@ function resolveConfigRedaction(): RedactOptions { } export function redactSensitiveText(text: string, options?: RedactOptions): string { - if (!text) return text; + if (!text) { + return text; + } const resolved = options ?? resolveConfigRedaction(); - if (normalizeMode(resolved.mode) === "off") return text; + if (normalizeMode(resolved.mode) === "off") { + return text; + } const patterns = resolvePatterns(resolved.patterns); - if (!patterns.length) return text; + if (!patterns.length) { + return text; + } return redactText(text, patterns); } export function redactToolDetail(detail: string): string { const resolved = resolveConfigRedaction(); - if (normalizeMode(resolved.mode) !== "tools") return detail; + if (normalizeMode(resolved.mode) !== "tools") { + return detail; + } return redactSensitiveText(detail, resolved); } diff --git a/src/logging/subsystem.ts b/src/logging/subsystem.ts index 5471033a5d..d0e80194b1 100644 --- a/src/logging/subsystem.ts +++ b/src/logging/subsystem.ts @@ -25,7 +25,9 @@ export type SubsystemLogger = { }; function shouldLogToConsole(level: LogLevel, settings: { level: LogLevel }): boolean { - if (settings.level === "silent") return false; + if (settings.level === "silent") { + return false; + } const current = levelToMinLevel(level); const min = levelToMinLevel(settings.level); return current <= min; @@ -35,7 +37,9 @@ type ChalkInstance = InstanceType; function isRichConsoleEnv(): boolean { const term = (process.env.TERM ?? "").toLowerCase(); - if (process.env.COLORTERM || process.env.TERM_PROGRAM) return true; + if (process.env.COLORTERM || process.env.TERM_PROGRAM) { + return true; + } return term.length > 0 && term !== "dumb"; } @@ -44,7 +48,9 @@ function getColorForConsole(): ChalkInstance { typeof process.env.FORCE_COLOR === "string" && process.env.FORCE_COLOR.trim().length > 0 && process.env.FORCE_COLOR.trim() !== "0"; - if (process.env.NO_COLOR && !hasForceColor) return new Chalk({ level: 0 }); + if (process.env.NO_COLOR && !hasForceColor) { + return new Chalk({ level: 0 }); + } const hasTty = Boolean(process.stdout.isTTY || process.stderr.isTTY); return hasTty || isRichConsoleEnv() ? new Chalk({ level: 1 }) : new Chalk({ level: 0 }); } @@ -59,7 +65,9 @@ const CHANNEL_SUBSYSTEM_PREFIXES = new Set(CHAT_CHANNEL_ORDER); function pickSubsystemColor(color: ChalkInstance, subsystem: string): ChalkInstance { const override = SUBSYSTEM_COLOR_OVERRIDES[subsystem]; - if (override) return color[override]; + if (override) { + return color[override]; + } let hash = 0; for (let i = 0; i < subsystem.length; i += 1) { hash = (hash * 31 + subsystem.charCodeAt(i)) | 0; @@ -78,7 +86,9 @@ function formatSubsystemForConsole(subsystem: string): string { ) { parts.shift(); } - if (parts.length === 0) return original; + if (parts.length === 0) { + return original; + } if (CHANNEL_SUBSYSTEM_PREFIXES.has(parts[0])) { return parts[0]; } @@ -92,7 +102,9 @@ export function stripRedundantSubsystemPrefixForConsole( message: string, displaySubsystem: string, ): string { - if (!displaySubsystem) return message; + if (!displaySubsystem) { + return message; + } // Common duplication: "[discord] discord: ..." (when a message manually includes the subsystem tag). if (message.startsWith("[")) { @@ -101,22 +113,34 @@ export function stripRedundantSubsystemPrefixForConsole( const bracketTag = message.slice(1, closeIdx); if (bracketTag.toLowerCase() === displaySubsystem.toLowerCase()) { let i = closeIdx + 1; - while (message[i] === " ") i += 1; + while (message[i] === " ") { + i += 1; + } return message.slice(i); } } } const prefix = message.slice(0, displaySubsystem.length); - if (prefix.toLowerCase() !== displaySubsystem.toLowerCase()) return message; + if (prefix.toLowerCase() !== displaySubsystem.toLowerCase()) { + return message; + } const next = message.slice(displaySubsystem.length, displaySubsystem.length + 1); - if (next !== ":" && next !== " ") return message; + if (next !== ":" && next !== " ") { + return message; + } let i = displaySubsystem.length; - while (message[i] === " ") i += 1; - if (message[i] === ":") i += 1; - while (message[i] === " ") i += 1; + while (message[i] === " ") { + i += 1; + } + if (message[i] === ":") { + i += 1; + } + while (message[i] === " ") { + i += 1; + } return message.slice(i); } @@ -186,12 +210,16 @@ function logToFile( message: string, meta?: Record, ) { - if (level === "silent") return; + if (level === "silent") { + return; + } const safeLevel = level; const method = (fileLogger as unknown as Record)[safeLevel] as | ((...args: unknown[]) => void) | undefined; - if (typeof method !== "function") return; + if (typeof method !== "function") { + return; + } if (meta && Object.keys(meta).length > 0) { method.call(fileLogger, meta, message); } else { @@ -202,7 +230,9 @@ function logToFile( export function createSubsystemLogger(subsystem: string): SubsystemLogger { let fileLogger: TsLogger | null = null; const getFileLogger = () => { - if (!fileLogger) fileLogger = getChildLogger({ subsystem }); + if (!fileLogger) { + fileLogger = getChildLogger({ subsystem }); + } return fileLogger; }; const emit = (level: LogLevel, message: string, meta?: Record) => { @@ -219,8 +249,12 @@ export function createSubsystemLogger(subsystem: string): SubsystemLogger { fileMeta = Object.keys(rest).length > 0 ? rest : undefined; } logToFile(getFileLogger(), level, message, fileMeta); - if (!shouldLogToConsole(level, { level: consoleSettings.level })) return; - if (!shouldLogSubsystemToConsole(subsystem)) return; + if (!shouldLogToConsole(level, { level: consoleSettings.level })) { + return; + } + if (!shouldLogSubsystemToConsole(subsystem)) { + return; + } const consoleMessage = consoleMessageOverride ?? message; if ( !isVerbose() && diff --git a/src/macos/gateway-daemon.ts b/src/macos/gateway-daemon.ts index 9f8e3ad95f..eb02c06064 100644 --- a/src/macos/gateway-daemon.ts +++ b/src/macos/gateway-daemon.ts @@ -11,7 +11,9 @@ const BUNDLED_VERSION = function argValue(args: string[], flag: string): string | undefined { const idx = args.indexOf(flag); - if (idx < 0) return undefined; + if (idx < 0) { + return undefined; + } const value = args[idx + 1]; return value && !value.startsWith("-") ? value : undefined; } @@ -145,7 +147,9 @@ async function main() { } catch (err) { defaultRuntime.error(`gateway: shutdown error: ${String(err)}`); } finally { - if (forceExitTimer) clearTimeout(forceExitTimer); + if (forceExitTimer) { + clearTimeout(forceExitTimer); + } server = null; if (isRestart) { shuttingDown = false; diff --git a/src/macos/relay-smoke.ts b/src/macos/relay-smoke.ts index 1c1f44090d..3dac201584 100644 --- a/src/macos/relay-smoke.ts +++ b/src/macos/relay-smoke.ts @@ -7,11 +7,15 @@ export function parseRelaySmokeTest(args: string[], env: NodeJS.ProcessEnv): Rel if (!value || value.startsWith("-")) { throw new Error("Missing value for --smoke (expected: qr)"); } - if (value === "qr") return "qr"; + if (value === "qr") { + return "qr"; + } throw new Error(`Unknown smoke test: ${value}`); } - if (args.includes("--smoke-qr")) return "qr"; + if (args.includes("--smoke-qr")) { + return "qr"; + } // Back-compat: only run env-based smoke mode when no CLI args are present, // to avoid surprising early-exit when users set env vars globally. diff --git a/src/macos/relay.ts b/src/macos/relay.ts index a171adc722..c39a4f02a3 100644 --- a/src/macos/relay.ts +++ b/src/macos/relay.ts @@ -15,7 +15,9 @@ function hasFlag(args: string[], flag: string): boolean { async function patchBunLongForProtobuf(): Promise { // Bun ships a global `Long` that protobufjs detects, but it is not long.js and // misses critical APIs (fromBits, ...). Baileys WAProto expects long.js. - if (typeof process.versions.bun !== "string") return; + if (typeof process.versions.bun !== "string") { + return; + } const mod = await import("long"); const Long = (mod as unknown as { default?: unknown }).default ?? mod; (globalThis as unknown as { Long?: unknown }).Long = Long; diff --git a/src/markdown/fences.ts b/src/markdown/fences.ts index 3a11bab3d1..d3cbbced1c 100644 --- a/src/markdown/fences.ts +++ b/src/markdown/fences.ts @@ -53,7 +53,9 @@ export function parseFenceSpans(buffer: string): FenceSpan[] { } } - if (nextNewline === -1) break; + if (nextNewline === -1) { + break; + } offset = nextNewline + 1; } diff --git a/src/markdown/frontmatter.ts b/src/markdown/frontmatter.ts index 1afde6c3d8..0994a76875 100644 --- a/src/markdown/frontmatter.ts +++ b/src/markdown/frontmatter.ts @@ -13,9 +13,15 @@ function stripQuotes(value: string): string { } function coerceFrontmatterValue(value: unknown): string | undefined { - if (value === null || value === undefined) return undefined; - if (typeof value === "string") return value.trim(); - if (typeof value === "number" || typeof value === "boolean") return String(value); + if (value === null || value === undefined) { + return undefined; + } + if (typeof value === "string") { + return value.trim(); + } + if (typeof value === "number" || typeof value === "boolean") { + return String(value); + } if (typeof value === "object") { try { return JSON.stringify(value); @@ -29,13 +35,19 @@ function coerceFrontmatterValue(value: unknown): string | undefined { function parseYamlFrontmatter(block: string): ParsedFrontmatter | null { try { const parsed = YAML.parse(block) as unknown; - if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null; + if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { + return null; + } const result: ParsedFrontmatter = {}; for (const [rawKey, value] of Object.entries(parsed as Record)) { const key = rawKey.trim(); - if (!key) continue; + if (!key) { + continue; + } const coerced = coerceFrontmatterValue(value); - if (coerced === undefined) continue; + if (coerced === undefined) { + continue; + } result[key] = coerced; } return result; @@ -50,7 +62,9 @@ function extractMultiLineValue( ): { value: string; linesConsumed: number } { const startLine = lines[startIndex]; const match = startLine.match(/^([\w-]+):\s*(.*)$/); - if (!match) return { value: "", linesConsumed: 1 }; + if (!match) { + return { value: "", linesConsumed: 1 }; + } const inlineValue = match[2].trim(); if (inlineValue) { @@ -118,14 +132,20 @@ function parseLineFrontmatter(block: string): ParsedFrontmatter { export function parseFrontmatterBlock(content: string): ParsedFrontmatter { const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); - if (!normalized.startsWith("---")) return {}; + if (!normalized.startsWith("---")) { + return {}; + } const endIndex = normalized.indexOf("\n---", 3); - if (endIndex === -1) return {}; + if (endIndex === -1) { + return {}; + } const block = normalized.slice(4, endIndex); const lineParsed = parseLineFrontmatter(block); const yamlParsed = parseYamlFrontmatter(block); - if (yamlParsed === null) return lineParsed; + if (yamlParsed === null) { + return lineParsed; + } const merged: ParsedFrontmatter = { ...yamlParsed }; for (const [key, value] of Object.entries(lineParsed)) { diff --git a/src/markdown/ir.ts b/src/markdown/ir.ts index 8a9155bf3d..3fe985766c 100644 --- a/src/markdown/ir.ts +++ b/src/markdown/ir.ts @@ -112,10 +112,14 @@ function createMarkdownIt(options: MarkdownParseOptions): MarkdownIt { } function getAttr(token: MarkdownToken, name: string): string | null { - if (token.attrGet) return token.attrGet(name); + if (token.attrGet) { + return token.attrGet(name); + } if (token.attrs) { for (const [key, value] of token.attrs) { - if (key === name) return value; + if (key === name) { + return value; + } } } return null; @@ -187,7 +191,9 @@ function resolveRenderTarget(state: RenderState): RenderTarget { } function appendText(state: RenderState, value: string) { - if (!value) return; + if (!value) { + return; + } const target = resolveRenderTarget(state); target.text += value; } @@ -213,15 +219,21 @@ function closeStyle(state: RenderState, style: MarkdownStyle) { } function appendParagraphSeparator(state: RenderState) { - if (state.env.listStack.length > 0) return; - if (state.table) return; // Don't add paragraph separators inside tables + if (state.env.listStack.length > 0) { + return; + } + if (state.table) { + return; + } // Don't add paragraph separators inside tables state.text += "\n\n"; } function appendListPrefix(state: RenderState) { const stack = state.env.listStack; const top = stack[stack.length - 1]; - if (!top) return; + if (!top) { + return; + } top.index += 1; const indent = " ".repeat(Math.max(0, stack.length - 1)); const prefix = top.type === "ordered" ? `${top.index}. ` : "• "; @@ -229,7 +241,9 @@ function appendListPrefix(state: RenderState) { } function renderInlineCode(state: RenderState, content: string) { - if (!content) return; + if (!content) { + return; + } const target = resolveRenderTarget(state); const start = target.text.length; target.text += content; @@ -238,7 +252,9 @@ function renderInlineCode(state: RenderState, content: string) { function renderCodeBlock(state: RenderState, content: string) { let code = content ?? ""; - if (!code.endsWith("\n")) code = `${code}\n`; + if (!code.endsWith("\n")) { + code = `${code}\n`; + } const target = resolveRenderTarget(state); const start = target.text.length; target.text += code; @@ -251,9 +267,13 @@ function renderCodeBlock(state: RenderState, content: string) { function handleLinkClose(state: RenderState) { const target = resolveRenderTarget(state); const link = target.linkStack.pop(); - if (!link?.href) return; + if (!link?.href) { + return; + } const href = link.href.trim(); - if (!href) return; + if (!href) { + return; + } const start = link.labelStart; const end = target.text.length; if (end <= start) { @@ -286,9 +306,15 @@ function trimCell(cell: TableCell): TableCell { const text = cell.text; let start = 0; let end = text.length; - while (start < end && /\s/.test(text[start] ?? "")) start += 1; - while (end > start && /\s/.test(text[end - 1] ?? "")) end -= 1; - if (start === 0 && end === text.length) return cell; + while (start < end && /\s/.test(text[start] ?? "")) { + start += 1; + } + while (end > start && /\s/.test(text[end - 1] ?? "")) { + end -= 1; + } + if (start === 0 && end === text.length) { + return cell; + } const trimmedText = text.slice(start, end); const trimmedLength = trimmedText.length; const trimmedStyles: MarkdownStyleSpan[] = []; @@ -311,7 +337,9 @@ function trimCell(cell: TableCell): TableCell { } function appendCell(state: RenderState, cell: TableCell) { - if (!cell.text) return; + if (!cell.text) { + return; + } const start = state.text.length; state.text += cell.text; for (const span of cell.styles) { @@ -331,12 +359,16 @@ function appendCell(state: RenderState, cell: TableCell) { } function renderTableAsBullets(state: RenderState) { - if (!state.table) return; + if (!state.table) { + return; + } const headers = state.table.headers.map(trimCell); const rows = state.table.rows.map((row) => row.map(trimCell)); // If no headers or rows, skip - if (headers.length === 0 && rows.length === 0) return; + if (headers.length === 0 && rows.length === 0) { + return; + } // Determine if first column should be used as row labels // (common pattern: first column is category/feature name) @@ -345,7 +377,9 @@ function renderTableAsBullets(state: RenderState) { if (useFirstColAsLabel) { // Format: each row becomes a section with header as row[0], then key:value pairs for (const row of rows) { - if (row.length === 0) continue; + if (row.length === 0) { + continue; + } const rowLabel = row[0]; if (rowLabel?.text) { @@ -362,7 +396,9 @@ function renderTableAsBullets(state: RenderState) { for (let i = 1; i < row.length; i++) { const header = headers[i]; const value = row[i]; - if (!value?.text) continue; + if (!value?.text) { + continue; + } state.text += "• "; if (header?.text) { appendCell(state, header); @@ -381,7 +417,9 @@ function renderTableAsBullets(state: RenderState) { for (let i = 0; i < row.length; i++) { const header = headers[i]; const value = row[i]; - if (!value?.text) continue; + if (!value?.text) { + continue; + } state.text += "• "; if (header?.text) { appendCell(state, header); @@ -396,23 +434,31 @@ function renderTableAsBullets(state: RenderState) { } function renderTableAsCode(state: RenderState) { - if (!state.table) return; + if (!state.table) { + return; + } const headers = state.table.headers.map(trimCell); const rows = state.table.rows.map((row) => row.map(trimCell)); const columnCount = Math.max(headers.length, ...rows.map((row) => row.length)); - if (columnCount === 0) return; + if (columnCount === 0) { + return; + } const widths = Array.from({ length: columnCount }, () => 0); const updateWidths = (cells: TableCell[]) => { for (let i = 0; i < columnCount; i += 1) { const cell = cells[i]; const width = cell?.text.length ?? 0; - if (widths[i] < width) widths[i] = width; + if (widths[i] < width) { + widths[i] = width; + } } }; updateWidths(headers); - for (const row of rows) updateWidths(row); + for (const row of rows) { + updateWidths(row); + } const codeStart = state.text.length; @@ -421,9 +467,13 @@ function renderTableAsCode(state: RenderState) { for (let i = 0; i < columnCount; i += 1) { state.text += " "; const cell = cells[i]; - if (cell) appendCell(state, cell); + if (cell) { + appendCell(state, cell); + } const pad = widths[i] - (cell?.text.length ?? 0); - if (pad > 0) state.text += " ".repeat(pad); + if (pad > 0) { + state.text += " ".repeat(pad); + } state.text += " |"; } state.text += "\n"; @@ -457,7 +507,9 @@ function renderTokens(tokens: MarkdownToken[], state: RenderState): void { for (const token of tokens) { switch (token.type) { case "inline": - if (token.children) renderTokens(token.children, state); + if (token.children) { + renderTokens(token.children, state); + } break; case "text": appendText(state, token.content ?? ""); @@ -484,10 +536,14 @@ function renderTokens(tokens: MarkdownToken[], state: RenderState): void { renderInlineCode(state, token.content ?? ""); break; case "spoiler_open": - if (state.enableSpoilers) openStyle(state, "spoiler"); + if (state.enableSpoilers) { + openStyle(state, "spoiler"); + } break; case "spoiler_close": - if (state.enableSpoilers) closeStyle(state, "spoiler"); + if (state.enableSpoilers) { + closeStyle(state, "spoiler"); + } break; case "link_open": { const href = getAttr(token, "href") ?? ""; @@ -509,14 +565,20 @@ function renderTokens(tokens: MarkdownToken[], state: RenderState): void { appendParagraphSeparator(state); break; case "heading_open": - if (state.headingStyle === "bold") openStyle(state, "bold"); + if (state.headingStyle === "bold") { + openStyle(state, "bold"); + } break; case "heading_close": - if (state.headingStyle === "bold") closeStyle(state, "bold"); + if (state.headingStyle === "bold") { + closeStyle(state, "bold"); + } appendParagraphSeparator(state); break; case "blockquote_open": - if (state.blockquotePrefix) state.text += state.blockquotePrefix; + if (state.blockquotePrefix) { + state.text += state.blockquotePrefix; + } break; case "blockquote_close": state.text += "\n"; @@ -613,7 +675,9 @@ function renderTokens(tokens: MarkdownToken[], state: RenderState): void { state.text += "\n"; break; default: - if (token.children) renderTokens(token.children, state); + if (token.children) { + renderTokens(token.children, state); + } break; } } @@ -639,7 +703,9 @@ function clampStyleSpans(spans: MarkdownStyleSpan[], maxLength: number): Markdow for (const span of spans) { const start = Math.max(0, Math.min(span.start, maxLength)); const end = Math.max(start, Math.min(span.end, maxLength)); - if (end > start) clamped.push({ start, end, style: span.style }); + if (end > start) { + clamped.push({ start, end, style: span.style }); + } } return clamped; } @@ -649,15 +715,21 @@ function clampLinkSpans(spans: MarkdownLinkSpan[], maxLength: number): MarkdownL for (const span of spans) { const start = Math.max(0, Math.min(span.start, maxLength)); const end = Math.max(start, Math.min(span.end, maxLength)); - if (end > start) clamped.push({ start, end, href: span.href }); + if (end > start) { + clamped.push({ start, end, href: span.href }); + } } return clamped; } function mergeStyleSpans(spans: MarkdownStyleSpan[]): MarkdownStyleSpan[] { const sorted = [...spans].toSorted((a, b) => { - if (a.start !== b.start) return a.start - b.start; - if (a.end !== b.end) return a.end - b.end; + if (a.start !== b.start) { + return a.start - b.start; + } + if (a.end !== b.end) { + return a.end - b.end; + } return a.style.localeCompare(b.style); }); @@ -678,7 +750,9 @@ function sliceStyleSpans( start: number, end: number, ): MarkdownStyleSpan[] { - if (spans.length === 0) return []; + if (spans.length === 0) { + return []; + } const sliced: MarkdownStyleSpan[] = []; for (const span of spans) { const sliceStart = Math.max(span.start, start); @@ -695,7 +769,9 @@ function sliceStyleSpans( } function sliceLinkSpans(spans: MarkdownLinkSpan[], start: number, end: number): MarkdownLinkSpan[] { - if (spans.length === 0) return []; + if (spans.length === 0) { + return []; + } const sliced: MarkdownLinkSpan[] = []; for (const span of spans) { const sliceStart = Math.max(span.start, start); @@ -750,8 +826,12 @@ export function markdownToIRWithMeta( const trimmedLength = trimmedText.length; let codeBlockEnd = 0; for (const span of state.styles) { - if (span.style !== "code_block") continue; - if (span.end > codeBlockEnd) codeBlockEnd = span.end; + if (span.style !== "code_block") { + continue; + } + if (span.end > codeBlockEnd) { + codeBlockEnd = span.end; + } } const finalLength = Math.max(trimmedLength, codeBlockEnd); const finalText = @@ -768,15 +848,21 @@ export function markdownToIRWithMeta( } export function chunkMarkdownIR(ir: MarkdownIR, limit: number): MarkdownIR[] { - if (!ir.text) return []; - if (limit <= 0 || ir.text.length <= limit) return [ir]; + if (!ir.text) { + return []; + } + if (limit <= 0 || ir.text.length <= limit) { + return [ir]; + } const chunks = chunkText(ir.text, limit); const results: MarkdownIR[] = []; let cursor = 0; chunks.forEach((chunk, index) => { - if (!chunk) return; + if (!chunk) { + return; + } if (index > 0) { while (cursor < ir.text.length && /\s/.test(ir.text[cursor] ?? "")) { cursor += 1; diff --git a/src/markdown/render.ts b/src/markdown/render.ts index dc53fa695a..fb55ee8477 100644 --- a/src/markdown/render.ts +++ b/src/markdown/render.ts @@ -35,15 +35,21 @@ const STYLE_RANK = new Map( function sortStyleSpans(spans: MarkdownStyleSpan[]): MarkdownStyleSpan[] { return [...spans].toSorted((a, b) => { - if (a.start !== b.start) return a.start - b.start; - if (a.end !== b.end) return b.end - a.end; + if (a.start !== b.start) { + return a.start - b.start; + } + if (a.end !== b.end) { + return b.end - a.end; + } return (STYLE_RANK.get(a.style) ?? 0) - (STYLE_RANK.get(b.style) ?? 0); }); } export function renderMarkdownWithMarkers(ir: MarkdownIR, options: RenderOptions): string { const text = ir.text ?? ""; - if (!text) return ""; + if (!text) { + return ""; + } const styleMarkers = options.styleMarkers; const styled = sortStyleSpans(ir.styles.filter((span) => Boolean(styleMarkers[span.style]))); @@ -54,16 +60,23 @@ export function renderMarkdownWithMarkers(ir: MarkdownIR, options: RenderOptions const startsAt = new Map(); for (const span of styled) { - if (span.start === span.end) continue; + if (span.start === span.end) { + continue; + } boundaries.add(span.start); boundaries.add(span.end); const bucket = startsAt.get(span.start); - if (bucket) bucket.push(span); - else startsAt.set(span.start, [span]); + if (bucket) { + bucket.push(span); + } else { + startsAt.set(span.start, [span]); + } } for (const spans of startsAt.values()) { spans.sort((a, b) => { - if (a.end !== b.end) return b.end - a.end; + if (a.end !== b.end) { + return b.end - a.end; + } return (STYLE_RANK.get(a.style) ?? 0) - (STYLE_RANK.get(b.style) ?? 0); }); } @@ -71,14 +84,21 @@ export function renderMarkdownWithMarkers(ir: MarkdownIR, options: RenderOptions const linkStarts = new Map(); if (options.buildLink) { for (const link of ir.links) { - if (link.start === link.end) continue; + if (link.start === link.end) { + continue; + } const rendered = options.buildLink(link, text); - if (!rendered) continue; + if (!rendered) { + continue; + } boundaries.add(rendered.start); boundaries.add(rendered.end); const openBucket = linkStarts.get(rendered.start); - if (openBucket) openBucket.push(rendered); - else linkStarts.set(rendered.start, [rendered]); + if (openBucket) { + openBucket.push(rendered); + } else { + linkStarts.set(rendered.start, [rendered]); + } } } @@ -103,7 +123,9 @@ export function renderMarkdownWithMarkers(ir: MarkdownIR, options: RenderOptions // Close ALL elements (styles and links) in LIFO order at this position while (stack.length && stack[stack.length - 1]?.end === pos) { const item = stack.pop(); - if (item) out += item.close; + if (item) { + out += item.close; + } } const openingItems: OpeningItem[] = []; @@ -125,7 +147,9 @@ export function renderMarkdownWithMarkers(ir: MarkdownIR, options: RenderOptions if (openingStyles) { for (const [index, span] of openingStyles.entries()) { const marker = styleMarkers[span.style]; - if (!marker) continue; + if (!marker) { + continue; + } openingItems.push({ end: span.end, open: marker.open, @@ -139,8 +163,12 @@ export function renderMarkdownWithMarkers(ir: MarkdownIR, options: RenderOptions if (openingItems.length > 0) { openingItems.sort((a, b) => { - if (a.end !== b.end) return b.end - a.end; - if (a.kind !== b.kind) return a.kind === "link" ? -1 : 1; + if (a.end !== b.end) { + return b.end - a.end; + } + if (a.kind !== b.kind) { + return a.kind === "link" ? -1 : 1; + } if (a.kind === "style" && b.kind === "style") { return (STYLE_RANK.get(a.style) ?? 0) - (STYLE_RANK.get(b.style) ?? 0); } @@ -155,7 +183,9 @@ export function renderMarkdownWithMarkers(ir: MarkdownIR, options: RenderOptions } const next = points[i + 1]; - if (next === undefined) break; + if (next === undefined) { + break; + } if (next > pos) { out += options.escapeText(text.slice(pos, next)); } diff --git a/src/markdown/tables.ts b/src/markdown/tables.ts index 9ae2b750e2..ac83b05c5c 100644 --- a/src/markdown/tables.ts +++ b/src/markdown/tables.ts @@ -11,7 +11,9 @@ const MARKDOWN_STYLE_MARKERS = { } as const; export function convertMarkdownTables(markdown: string, mode: MarkdownTableMode): string { - if (!markdown || mode === "off") return markdown; + if (!markdown || mode === "off") { + return markdown; + } const { ir, hasTables } = markdownToIRWithMeta(markdown, { linkify: false, autolink: false, @@ -19,15 +21,21 @@ export function convertMarkdownTables(markdown: string, mode: MarkdownTableMode) blockquotePrefix: "", tableMode: mode, }); - if (!hasTables) return markdown; + if (!hasTables) { + return markdown; + } return renderMarkdownWithMarkers(ir, { styleMarkers: MARKDOWN_STYLE_MARKERS, escapeText: (text) => text, buildLink: (link, text) => { const href = link.href.trim(); - if (!href) return null; + if (!href) { + return null; + } const label = text.slice(link.start, link.end); - if (!label) return null; + if (!label) { + return null; + } return { start: link.start, end: link.end, open: "[", close: `](${href})` }; }, }); diff --git a/src/media-understanding/apply.test.ts b/src/media-understanding/apply.test.ts index a1f470258e..3dad57023c 100644 --- a/src/media-understanding/apply.test.ts +++ b/src/media-understanding/apply.test.ts @@ -16,7 +16,9 @@ vi.mock("../agents/model-auth.js", () => ({ mode: "api-key", })), requireApiKey: (auth: { apiKey?: string; mode?: string }, provider: string) => { - if (auth?.apiKey) return auth.apiKey; + if (auth?.apiKey) { + return auth.apiKey; + } throw new Error(`No API key resolved for provider "${provider}" (auth mode: ${auth?.mode}).`); }, })); diff --git a/src/media-understanding/apply.ts b/src/media-understanding/apply.ts index f3f31f667b..3c9103038b 100644 --- a/src/media-understanding/apply.ts +++ b/src/media-understanding/apply.ts @@ -120,7 +120,9 @@ function appendFileBlocks(body: string | undefined, blocks: string[]): string { } function resolveUtf16Charset(buffer?: Buffer): "utf-16le" | "utf-16be" | undefined { - if (!buffer || buffer.length < 2) return undefined; + if (!buffer || buffer.length < 2) { + return undefined; + } const b0 = buffer[0]; const b1 = buffer[1]; if (b0 === 0xff && b1 === 0xfe) { @@ -132,7 +134,9 @@ function resolveUtf16Charset(buffer?: Buffer): "utf-16le" | "utf-16be" | undefin const sampleLen = Math.min(buffer.length, 2048); let zeroCount = 0; for (let i = 0; i < sampleLen; i += 1) { - if (buffer[i] === 0) zeroCount += 1; + if (buffer[i] === 0) { + zeroCount += 1; + } } if (zeroCount / sampleLen > 0.2) { return "utf-16le"; @@ -141,7 +145,9 @@ function resolveUtf16Charset(buffer?: Buffer): "utf-16le" | "utf-16be" | undefin } function looksLikeUtf8Text(buffer?: Buffer): boolean { - if (!buffer || buffer.length === 0) return false; + if (!buffer || buffer.length === 0) { + return false; + } const sampleLen = Math.min(buffer.length, 4096); let printable = 0; let other = 0; @@ -158,12 +164,16 @@ function looksLikeUtf8Text(buffer?: Buffer): boolean { } } const total = printable + other; - if (total === 0) return false; + if (total === 0) { + return false; + } return printable / total > 0.85; } function decodeTextSample(buffer?: Buffer): string { - if (!buffer || buffer.length === 0) return ""; + if (!buffer || buffer.length === 0) { + return ""; + } const sample = buffer.subarray(0, Math.min(buffer.length, 8192)); const utf16Charset = resolveUtf16Charset(sample); if (utf16Charset === "utf-16be") { @@ -181,7 +191,9 @@ function decodeTextSample(buffer?: Buffer): string { } function guessDelimitedMime(text: string): string | undefined { - if (!text) return undefined; + if (!text) { + return undefined; + } const line = text.split(/\r?\n/)[0] ?? ""; const tabs = (line.match(/\t/g) ?? []).length; const commas = (line.match(/,/g) ?? []).length; @@ -195,7 +207,9 @@ function guessDelimitedMime(text: string): string | undefined { } function resolveTextMimeFromName(name?: string): string | undefined { - if (!name) return undefined; + if (!name) { + return undefined; + } const ext = path.extname(name).toLowerCase(); return TEXT_EXT_MIME.get(ext); } @@ -363,7 +377,9 @@ export async function applyMediaUnderstanding(params: { const outputs: MediaUnderstandingOutput[] = []; const decisions: MediaUnderstandingDecision[] = []; for (const entry of results) { - if (!entry) continue; + if (!entry) { + continue; + } for (const output of entry.outputs) { outputs.push(output); } diff --git a/src/media-understanding/attachments.ts b/src/media-understanding/attachments.ts index 041190973c..54dd90ed99 100644 --- a/src/media-understanding/attachments.ts +++ b/src/media-understanding/attachments.ts @@ -40,7 +40,9 @@ const DEFAULT_MAX_ATTACHMENTS = 1; function normalizeAttachmentPath(raw?: string | null): string | undefined { const value = raw?.trim(); - if (!value) return undefined; + if (!value) { + return undefined; + } if (value.startsWith("file://")) { try { return fileURLToPath(value); @@ -58,7 +60,9 @@ export function normalizeAttachments(ctx: MsgContext): MediaAttachment[] { const resolveMime = (count: number, index: number) => { const typeHint = typesFromArray?.[index]; const trimmed = typeof typeHint === "string" ? typeHint.trim() : ""; - if (trimmed) return trimmed; + if (trimmed) { + return trimmed; + } return count === 1 ? ctx.MediaType : undefined; }; @@ -89,7 +93,9 @@ export function normalizeAttachments(ctx: MsgContext): MediaAttachment[] { const pathValue = ctx.MediaPath?.trim(); const url = ctx.MediaUrl?.trim(); - if (!pathValue && !url) return []; + if (!pathValue && !url) { + return []; + } return [ { path: pathValue || undefined, @@ -104,12 +110,20 @@ export function resolveAttachmentKind( attachment: MediaAttachment, ): "image" | "audio" | "video" | "document" | "unknown" { const kind = kindFromMime(attachment.mime); - if (kind === "image" || kind === "audio" || kind === "video") return kind; + if (kind === "image" || kind === "audio" || kind === "video") { + return kind; + } const ext = getFileExtension(attachment.path ?? attachment.url); - if (!ext) return "unknown"; - if ([".mp4", ".mov", ".mkv", ".webm", ".avi", ".m4v"].includes(ext)) return "video"; - if (isAudioFileName(attachment.path ?? attachment.url)) return "audio"; + if (!ext) { + return "unknown"; + } + if ([".mp4", ".mov", ".mkv", ".webm", ".avi", ".m4v"].includes(ext)) { + return "video"; + } + if (isAudioFileName(attachment.path ?? attachment.url)) { + return "audio"; + } if ([".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp", ".tiff", ".tif"].includes(ext)) { return "image"; } @@ -129,14 +143,22 @@ export function isImageAttachment(attachment: MediaAttachment): boolean { } function isAbortError(err: unknown): boolean { - if (!err) return false; - if (err instanceof Error && err.name === "AbortError") return true; + if (!err) { + return false; + } + if (err instanceof Error && err.name === "AbortError") { + return true; + } return false; } function resolveRequestUrl(input: RequestInfo | URL): string { - if (typeof input === "string") return input; - if (input instanceof URL) return input.toString(); + if (typeof input === "string") { + return input; + } + if (input instanceof URL) { + return input.toString(); + } return input.url; } @@ -144,8 +166,12 @@ function orderAttachments( attachments: MediaAttachment[], prefer?: MediaUnderstandingAttachmentsConfig["prefer"], ): MediaAttachment[] { - if (!prefer || prefer === "first") return attachments; - if (prefer === "last") return [...attachments].toReversed(); + if (!prefer || prefer === "first") { + return attachments; + } + if (prefer === "last") { + return [...attachments].toReversed(); + } if (prefer === "path") { const withPath = attachments.filter((item) => item.path); const withoutPath = attachments.filter((item) => !item.path); @@ -166,11 +192,17 @@ export function selectAttachments(params: { }): MediaAttachment[] { const { capability, attachments, policy } = params; const matches = attachments.filter((item) => { - if (capability === "image") return isImageAttachment(item); - if (capability === "audio") return isAudioAttachment(item); + if (capability === "image") { + return isImageAttachment(item); + } + if (capability === "audio") { + return isAudioAttachment(item); + } return isVideoAttachment(item); }); - if (matches.length === 0) return []; + if (matches.length === 0) { + return []; + } const ordered = orderAttachments(matches, policy?.prefer); const mode = policy?.mode ?? "first"; @@ -367,13 +399,19 @@ export class MediaAttachmentCache { private resolveLocalPath(attachment: MediaAttachment): string | undefined { const rawPath = normalizeAttachmentPath(attachment.path); - if (!rawPath) return undefined; + if (!rawPath) { + return undefined; + } return path.isAbsolute(rawPath) ? rawPath : path.resolve(rawPath); } private async ensureLocalStat(entry: AttachmentCacheEntry): Promise { - if (!entry.resolvedPath) return undefined; - if (entry.statSize !== undefined) return entry.statSize; + if (!entry.resolvedPath) { + return undefined; + } + if (entry.statSize !== undefined) { + return entry.statSize; + } try { const stat = await fs.stat(entry.resolvedPath); if (!stat.isFile()) { diff --git a/src/media-understanding/concurrency.ts b/src/media-understanding/concurrency.ts index 8ccba85f41..b70be5afed 100644 --- a/src/media-understanding/concurrency.ts +++ b/src/media-understanding/concurrency.ts @@ -4,7 +4,9 @@ export async function runWithConcurrency( tasks: Array<() => Promise>, limit: number, ): Promise { - if (tasks.length === 0) return []; + if (tasks.length === 0) { + return []; + } const resolvedLimit = Math.max(1, Math.min(limit, tasks.length)); const results: T[] = Array.from({ length: tasks.length }); let next = 0; @@ -13,7 +15,9 @@ export async function runWithConcurrency( while (true) { const index = next; next += 1; - if (index >= tasks.length) return; + if (index >= tasks.length) { + return; + } try { results[index] = await tasks[index](); } catch (err) { diff --git a/src/media-understanding/format.ts b/src/media-understanding/format.ts index c0dd77020c..b0542d1651 100644 --- a/src/media-understanding/format.ts +++ b/src/media-understanding/format.ts @@ -5,8 +5,12 @@ const MEDIA_PLACEHOLDER_TOKEN_RE = /^]+>(\s*\([^)]*\))?\s*/i; export function extractMediaUserText(body?: string): string | undefined { const trimmed = body?.trim() ?? ""; - if (!trimmed) return undefined; - if (MEDIA_PLACEHOLDER_RE.test(trimmed)) return undefined; + if (!trimmed) { + return undefined; + } + if (MEDIA_PLACEHOLDER_RE.test(trimmed)) { + return undefined; + } const cleaned = trimmed.replace(MEDIA_PLACEHOLDER_TOKEN_RE, "").trim(); return cleaned || undefined; } @@ -87,6 +91,8 @@ export function formatMediaUnderstandingBody(params: { } export function formatAudioTranscripts(outputs: MediaUnderstandingOutput[]): string { - if (outputs.length === 1) return outputs[0].text; + if (outputs.length === 1) { + return outputs[0].text; + } return outputs.map((output, index) => `Audio ${index + 1}:\n${output.text}`).join("\n\n"); } diff --git a/src/media-understanding/providers/deepgram/audio.test.ts b/src/media-understanding/providers/deepgram/audio.test.ts index 17af8443c2..0635da66fa 100644 --- a/src/media-understanding/providers/deepgram/audio.test.ts +++ b/src/media-understanding/providers/deepgram/audio.test.ts @@ -3,8 +3,12 @@ import { describe, expect, it } from "vitest"; import { transcribeDeepgramAudio } from "./audio.js"; const resolveRequestUrl = (input: RequestInfo | URL) => { - if (typeof input === "string") return input; - if (input instanceof URL) return input.toString(); + if (typeof input === "string") { + return input; + } + if (input instanceof URL) { + return input.toString(); + } return input.url; }; diff --git a/src/media-understanding/providers/deepgram/audio.ts b/src/media-understanding/providers/deepgram/audio.ts index 1bc9bc782f..8d0f53f368 100644 --- a/src/media-understanding/providers/deepgram/audio.ts +++ b/src/media-understanding/providers/deepgram/audio.ts @@ -28,10 +28,14 @@ export async function transcribeDeepgramAudio( const url = new URL(`${baseUrl}/listen`); url.searchParams.set("model", model); - if (params.language?.trim()) url.searchParams.set("language", params.language.trim()); + if (params.language?.trim()) { + url.searchParams.set("language", params.language.trim()); + } if (params.query) { for (const [key, value] of Object.entries(params.query)) { - if (value === undefined) continue; + if (value === undefined) { + continue; + } url.searchParams.set(key, String(value)); } } diff --git a/src/media-understanding/providers/google/audio.ts b/src/media-understanding/providers/google/audio.ts index 52a7136d2c..b1058b9f2d 100644 --- a/src/media-understanding/providers/google/audio.ts +++ b/src/media-understanding/providers/google/audio.ts @@ -8,7 +8,9 @@ const DEFAULT_GOOGLE_AUDIO_PROMPT = "Transcribe the audio."; function resolveModel(model?: string): string { const trimmed = model?.trim(); - if (!trimmed) return DEFAULT_GOOGLE_AUDIO_MODEL; + if (!trimmed) { + return DEFAULT_GOOGLE_AUDIO_MODEL; + } return normalizeGoogleModelId(trimmed); } diff --git a/src/media-understanding/providers/google/video.test.ts b/src/media-understanding/providers/google/video.test.ts index f94778cdd7..5b776438c9 100644 --- a/src/media-understanding/providers/google/video.test.ts +++ b/src/media-understanding/providers/google/video.test.ts @@ -3,8 +3,12 @@ import { describe, expect, it } from "vitest"; import { describeGeminiVideo } from "./video.js"; const resolveRequestUrl = (input: RequestInfo | URL) => { - if (typeof input === "string") return input; - if (input instanceof URL) return input.toString(); + if (typeof input === "string") { + return input; + } + if (input instanceof URL) { + return input.toString(); + } return input.url; }; diff --git a/src/media-understanding/providers/google/video.ts b/src/media-understanding/providers/google/video.ts index 0f483b280e..6b8ba2b152 100644 --- a/src/media-understanding/providers/google/video.ts +++ b/src/media-understanding/providers/google/video.ts @@ -8,7 +8,9 @@ const DEFAULT_GOOGLE_VIDEO_PROMPT = "Describe the video."; function resolveModel(model?: string): string { const trimmed = model?.trim(); - if (!trimmed) return DEFAULT_GOOGLE_VIDEO_MODEL; + if (!trimmed) { + return DEFAULT_GOOGLE_VIDEO_MODEL; + } return normalizeGoogleModelId(trimmed); } diff --git a/src/media-understanding/providers/index.ts b/src/media-understanding/providers/index.ts index a20ba92fb9..bbc40baf81 100644 --- a/src/media-understanding/providers/index.ts +++ b/src/media-understanding/providers/index.ts @@ -18,7 +18,9 @@ const PROVIDERS: MediaUnderstandingProvider[] = [ export function normalizeMediaProviderId(id: string): string { const normalized = normalizeProviderId(id); - if (normalized === "gemini") return "google"; + if (normalized === "gemini") { + return "google"; + } return normalized; } diff --git a/src/media-understanding/providers/openai/audio.test.ts b/src/media-understanding/providers/openai/audio.test.ts index 323c394aed..a9df11c977 100644 --- a/src/media-understanding/providers/openai/audio.test.ts +++ b/src/media-understanding/providers/openai/audio.test.ts @@ -3,8 +3,12 @@ import { describe, expect, it } from "vitest"; import { transcribeOpenAiCompatibleAudio } from "./audio.js"; const resolveRequestUrl = (input: RequestInfo | URL) => { - if (typeof input === "string") return input; - if (input instanceof URL) return input.toString(); + if (typeof input === "string") { + return input; + } + if (input instanceof URL) { + return input.toString(); + } return input.url; }; diff --git a/src/media-understanding/providers/openai/audio.ts b/src/media-understanding/providers/openai/audio.ts index acfe595a9e..fb9cd90a02 100644 --- a/src/media-understanding/providers/openai/audio.ts +++ b/src/media-understanding/providers/openai/audio.ts @@ -27,8 +27,12 @@ export async function transcribeOpenAiCompatibleAudio( }); form.append("file", blob, fileName); form.append("model", model); - if (params.language?.trim()) form.append("language", params.language.trim()); - if (params.prompt?.trim()) form.append("prompt", params.prompt.trim()); + if (params.language?.trim()) { + form.append("language", params.language.trim()); + } + if (params.prompt?.trim()) { + form.append("prompt", params.prompt.trim()); + } const headers = new Headers(params.headers); if (!headers.has("authorization")) { diff --git a/src/media-understanding/providers/shared.ts b/src/media-understanding/providers/shared.ts index 9dd36992ed..fa0249df2f 100644 --- a/src/media-understanding/providers/shared.ts +++ b/src/media-understanding/providers/shared.ts @@ -24,8 +24,12 @@ export async function readErrorResponse(res: Response): Promise; }): MediaUnderstandingCapability[] | undefined { const entryType = params.entry.type ?? (params.entry.command ? "cli" : "provider"); - if (entryType === "cli") return undefined; + if (entryType === "cli") { + return undefined; + } const providerId = normalizeMediaProviderId(params.entry.provider ?? ""); - if (!providerId) return undefined; + if (!providerId) { + return undefined; + } return params.providerRegistry.get(providerId)?.capabilities; } @@ -100,7 +110,9 @@ export function resolveModelEntries(params: { ...(config?.models ?? []).map((entry) => ({ entry, source: "capability" as const })), ...sharedModels.map((entry) => ({ entry, source: "shared" as const })), ]; - if (entries.length === 0) return []; + if (entries.length === 0) { + return []; + } return entries .filter(({ entry, source }) => { @@ -147,14 +159,24 @@ export function resolveEntriesWithActiveFallback(params: { config: params.config, providerRegistry: params.providerRegistry, }); - if (entries.length > 0) return entries; - if (params.config?.enabled !== true) return entries; + if (entries.length > 0) { + return entries; + } + if (params.config?.enabled !== true) { + return entries; + } const activeProviderRaw = params.activeModel?.provider?.trim(); - if (!activeProviderRaw) return entries; + if (!activeProviderRaw) { + return entries; + } const activeProvider = normalizeMediaProviderId(activeProviderRaw); - if (!activeProvider) return entries; + if (!activeProvider) { + return entries; + } const capabilities = params.providerRegistry.get(activeProvider)?.capabilities; - if (!capabilities || !capabilities.includes(params.capability)) return entries; + if (!capabilities || !capabilities.includes(params.capability)) { + return entries; + } return [ { type: "provider", diff --git a/src/media-understanding/runner.ts b/src/media-understanding/runner.ts index 8fbfe5b047..36a2519554 100644 --- a/src/media-understanding/runner.ts +++ b/src/media-understanding/runner.ts @@ -89,10 +89,16 @@ const binaryCache = new Map>(); const geminiProbeCache = new Map>(); function expandHomeDir(value: string): string { - if (!value.startsWith("~")) return value; + if (!value.startsWith("~")) { + return value; + } const home = os.homedir(); - if (value === "~") return home; - if (value.startsWith("~/")) return path.join(home, value.slice(2)); + if (value === "~") { + return home; + } + if (value.startsWith("~/")) { + return path.join(home, value.slice(2)); + } return value; } @@ -101,9 +107,13 @@ function hasPathSeparator(value: string): boolean { } function candidateBinaryNames(name: string): string[] { - if (process.platform !== "win32") return [name]; + if (process.platform !== "win32") { + return [name]; + } const ext = path.extname(name); - if (ext) return [name]; + if (ext) { + return [name]; + } const pathext = (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM") .split(";") .map((item) => item.trim()) @@ -116,8 +126,12 @@ function candidateBinaryNames(name: string): string[] { async function isExecutable(filePath: string): Promise { try { const stat = await fs.stat(filePath); - if (!stat.isFile()) return false; - if (process.platform === "win32") return true; + if (!stat.isFile()) { + return false; + } + if (process.platform === "win32") { + return true; + } await fs.access(filePath, fsConstants.X_OK); return true; } catch { @@ -127,25 +141,35 @@ async function isExecutable(filePath: string): Promise { async function findBinary(name: string): Promise { const cached = binaryCache.get(name); - if (cached) return cached; + if (cached) { + return cached; + } const resolved = (async () => { const direct = expandHomeDir(name.trim()); if (direct && hasPathSeparator(direct)) { for (const candidate of candidateBinaryNames(direct)) { - if (await isExecutable(candidate)) return candidate; + if (await isExecutable(candidate)) { + return candidate; + } } } const searchName = name.trim(); - if (!searchName) return null; + if (!searchName) { + return null; + } const pathEntries = (process.env.PATH ?? "").split(path.delimiter); const candidates = candidateBinaryNames(searchName); for (const entryRaw of pathEntries) { const entry = expandHomeDir(entryRaw.trim().replace(/^"(.*)"$/, "$1")); - if (!entry) continue; + if (!entry) { + continue; + } for (const candidate of candidates) { const fullPath = path.join(entry, candidate); - if (await isExecutable(fullPath)) return fullPath; + if (await isExecutable(fullPath)) { + return fullPath; + } } } @@ -160,7 +184,9 @@ async function hasBinary(name: string): Promise { } async function fileExists(filePath?: string | null): Promise { - if (!filePath) return false; + if (!filePath) { + return false; + } try { await fs.stat(filePath); return true; @@ -172,7 +198,9 @@ async function fileExists(filePath?: string | null): Promise { function extractLastJsonObject(raw: string): unknown { const trimmed = raw.trim(); const start = trimmed.lastIndexOf("{"); - if (start === -1) return null; + if (start === -1) { + return null; + } const slice = trimmed.slice(start); try { return JSON.parse(slice); @@ -183,9 +211,13 @@ function extractLastJsonObject(raw: string): unknown { function extractGeminiResponse(raw: string): string | null { const payload = extractLastJsonObject(raw); - if (!payload || typeof payload !== "object") return null; + if (!payload || typeof payload !== "object") { + return null; + } const response = (payload as { response?: unknown }).response; - if (typeof response !== "string") return null; + if (typeof response !== "string") { + return null; + } const trimmed = response.trim(); return trimmed || null; } @@ -193,9 +225,13 @@ function extractGeminiResponse(raw: string): string | null { function extractSherpaOnnxText(raw: string): string | null { const tryParse = (value: string): string | null => { const trimmed = value.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } const head = trimmed[0]; - if (head !== "{" && head !== '"') return null; + if (head !== "{" && head !== '"') { + return null; + } try { const parsed = JSON.parse(trimmed) as unknown; if (typeof parsed === "string") { @@ -212,7 +248,9 @@ function extractSherpaOnnxText(raw: string): string | null { }; const direct = tryParse(raw); - if (direct) return direct; + if (direct) { + return direct; + } const lines = raw .split("\n") @@ -220,16 +258,22 @@ function extractSherpaOnnxText(raw: string): string | null { .filter(Boolean); for (let i = lines.length - 1; i >= 0; i -= 1) { const parsed = tryParse(lines[i] ?? ""); - if (parsed) return parsed; + if (parsed) { + return parsed; + } } return null; } async function probeGeminiCli(): Promise { const cached = geminiProbeCache.get("gemini"); - if (cached) return cached; + if (cached) { + return cached; + } const resolved = (async () => { - if (!(await hasBinary("gemini"))) return false; + if (!(await hasBinary("gemini"))) { + return false; + } try { const { stdout } = await runExec("gemini", ["--output-format", "json", "ok"], { timeoutMs: 8000, @@ -244,11 +288,15 @@ async function probeGeminiCli(): Promise { } async function resolveLocalWhisperCppEntry(): Promise { - if (!(await hasBinary("whisper-cli"))) return null; + if (!(await hasBinary("whisper-cli"))) { + return null; + } const envModel = process.env.WHISPER_CPP_MODEL?.trim(); const defaultModel = "/opt/homebrew/share/whisper-cpp/for-tests-ggml-tiny.bin"; const modelPath = envModel && (await fileExists(envModel)) ? envModel : defaultModel; - if (!(await fileExists(modelPath))) return null; + if (!(await fileExists(modelPath))) { + return null; + } return { type: "cli", command: "whisper-cli", @@ -257,7 +305,9 @@ async function resolveLocalWhisperCppEntry(): Promise { - if (!(await hasBinary("whisper"))) return null; + if (!(await hasBinary("whisper"))) { + return null; + } return { type: "cli", command: "whisper", @@ -276,17 +326,29 @@ async function resolveLocalWhisperEntry(): Promise { - if (!(await hasBinary("sherpa-onnx-offline"))) return null; + if (!(await hasBinary("sherpa-onnx-offline"))) { + return null; + } const modelDir = process.env.SHERPA_ONNX_MODEL_DIR?.trim(); - if (!modelDir) return null; + if (!modelDir) { + return null; + } const tokens = path.join(modelDir, "tokens.txt"); const encoder = path.join(modelDir, "encoder.onnx"); const decoder = path.join(modelDir, "decoder.onnx"); const joiner = path.join(modelDir, "joiner.onnx"); - if (!(await fileExists(tokens))) return null; - if (!(await fileExists(encoder))) return null; - if (!(await fileExists(decoder))) return null; - if (!(await fileExists(joiner))) return null; + if (!(await fileExists(tokens))) { + return null; + } + if (!(await fileExists(encoder))) { + return null; + } + if (!(await fileExists(decoder))) { + return null; + } + if (!(await fileExists(joiner))) { + return null; + } return { type: "cli", command: "sherpa-onnx-offline", @@ -302,16 +364,22 @@ async function resolveSherpaOnnxEntry(): Promise { const sherpa = await resolveSherpaOnnxEntry(); - if (sherpa) return sherpa; + if (sherpa) { + return sherpa; + } const whisperCpp = await resolveLocalWhisperCppEntry(); - if (whisperCpp) return whisperCpp; + if (whisperCpp) { + return whisperCpp; + } return await resolveLocalWhisperEntry(); } async function resolveGeminiCliEntry( _capability: MediaUnderstandingCapability, ): Promise { - if (!(await probeGeminiCli())) return null; + if (!(await probeGeminiCli())) { + return null; + } return { type: "cli", command: "gemini", @@ -341,10 +409,18 @@ async function resolveKeyEntry(params: { model?: string, ): Promise => { const provider = getMediaUnderstandingProvider(providerId, providerRegistry); - if (!provider) return null; - if (capability === "audio" && !provider.transcribeAudio) return null; - if (capability === "image" && !provider.describeImage) return null; - if (capability === "video" && !provider.describeVideo) return null; + if (!provider) { + return null; + } + if (capability === "audio" && !provider.transcribeAudio) { + return null; + } + if (capability === "image" && !provider.describeImage) { + return null; + } + if (capability === "video" && !provider.describeVideo) { + return null; + } try { await resolveApiKeyForProvider({ provider: providerId, cfg, agentDir }); return { type: "provider" as const, provider: providerId, model }; @@ -357,12 +433,16 @@ async function resolveKeyEntry(params: { const activeProvider = params.activeModel?.provider?.trim(); if (activeProvider) { const activeEntry = await checkProvider(activeProvider, params.activeModel?.model); - if (activeEntry) return activeEntry; + if (activeEntry) { + return activeEntry; + } } for (const providerId of AUTO_IMAGE_KEY_PROVIDERS) { const model = DEFAULT_IMAGE_MODELS[providerId]; const entry = await checkProvider(providerId, model); - if (entry) return entry; + if (entry) { + return entry; + } } return null; } @@ -371,11 +451,15 @@ async function resolveKeyEntry(params: { const activeProvider = params.activeModel?.provider?.trim(); if (activeProvider) { const activeEntry = await checkProvider(activeProvider, params.activeModel?.model); - if (activeEntry) return activeEntry; + if (activeEntry) { + return activeEntry; + } } for (const providerId of AUTO_VIDEO_KEY_PROVIDERS) { const entry = await checkProvider(providerId, undefined); - if (entry) return entry; + if (entry) { + return entry; + } } return null; } @@ -383,11 +467,15 @@ async function resolveKeyEntry(params: { const activeProvider = params.activeModel?.provider?.trim(); if (activeProvider) { const activeEntry = await checkProvider(activeProvider, params.activeModel?.model); - if (activeEntry) return activeEntry; + if (activeEntry) { + return activeEntry; + } } for (const providerId of AUTO_AUDIO_KEY_PROVIDERS) { const entry = await checkProvider(providerId, undefined); - if (entry) return entry; + if (entry) { + return entry; + } } return null; } @@ -400,15 +488,23 @@ async function resolveAutoEntries(params: { activeModel?: ActiveMediaModel; }): Promise { const activeEntry = await resolveActiveModelEntry(params); - if (activeEntry) return [activeEntry]; + if (activeEntry) { + return [activeEntry]; + } if (params.capability === "audio") { const localAudio = await resolveLocalAudioEntry(); - if (localAudio) return [localAudio]; + if (localAudio) { + return [localAudio]; + } } const gemini = await resolveGeminiCliEntry(params.capability); - if (gemini) return [gemini]; + if (gemini) { + return [gemini]; + } const keys = await resolveKeyEntry(params); - if (keys) return [keys]; + if (keys) { + return [keys]; + } return []; } @@ -419,11 +515,17 @@ export async function resolveAutoImageModel(params: { }): Promise { const providerRegistry = buildProviderRegistry(); const toActive = (entry: MediaUnderstandingModelConfig | null): ActiveMediaModel | null => { - if (!entry || entry.type === "cli") return null; + if (!entry || entry.type === "cli") { + return null; + } const provider = entry.provider; - if (!provider) return null; + if (!provider) { + return null; + } const model = entry.model ?? DEFAULT_IMAGE_MODELS[provider]; - if (!model) return null; + if (!model) { + return null; + } return { provider, model }; }; const activeEntry = await resolveActiveModelEntry({ @@ -434,7 +536,9 @@ export async function resolveAutoImageModel(params: { activeModel: params.activeModel, }); const resolvedActive = toActive(activeEntry); - if (resolvedActive) return resolvedActive; + if (resolvedActive) { + return resolvedActive; + } const keyEntry = await resolveKeyEntry({ cfg: params.cfg, agentDir: params.agentDir, @@ -453,14 +557,26 @@ async function resolveActiveModelEntry(params: { activeModel?: ActiveMediaModel; }): Promise { const activeProviderRaw = params.activeModel?.provider?.trim(); - if (!activeProviderRaw) return null; + if (!activeProviderRaw) { + return null; + } const providerId = normalizeMediaProviderId(activeProviderRaw); - if (!providerId) return null; + if (!providerId) { + return null; + } const provider = getMediaUnderstandingProvider(providerId, params.providerRegistry); - if (!provider) return null; - if (params.capability === "audio" && !provider.transcribeAudio) return null; - if (params.capability === "image" && !provider.describeImage) return null; - if (params.capability === "video" && !provider.describeVideo) return null; + if (!provider) { + return null; + } + if (params.capability === "audio" && !provider.transcribeAudio) { + return null; + } + if (params.capability === "image" && !provider.describeImage) { + return null; + } + if (params.capability === "video" && !provider.describeVideo) { + return null; + } try { await resolveApiKeyForProvider({ provider: providerId, @@ -479,7 +595,9 @@ async function resolveActiveModelEntry(params: { function trimOutput(text: string, maxChars?: number): string { const trimmed = text.trim(); - if (!maxChars || trimmed.length <= maxChars) return trimmed; + if (!maxChars || trimmed.length <= maxChars) { + return trimmed; + } return trimmed.slice(0, maxChars).trim(); } @@ -491,7 +609,9 @@ function findArgValue(args: string[], keys: string[]): string | undefined { for (let i = 0; i < args.length; i += 1) { if (keys.includes(args[i] ?? "")) { const value = args[i + 1]; - if (value) return value; + if (value) { + return value; + } } } return undefined; @@ -504,17 +624,25 @@ function hasArg(args: string[], keys: string[]): boolean { function resolveWhisperOutputPath(args: string[], mediaPath: string): string | null { const outputDir = findArgValue(args, ["--output_dir", "-o"]); const outputFormat = findArgValue(args, ["--output_format"]); - if (!outputDir || !outputFormat) return null; + if (!outputDir || !outputFormat) { + return null; + } const formats = outputFormat.split(",").map((value) => value.trim()); - if (!formats.includes("txt")) return null; + if (!formats.includes("txt")) { + return null; + } const base = path.parse(mediaPath).name; return path.join(outputDir, `${base}.txt`); } function resolveWhisperCppOutputPath(args: string[]): string | null { - if (!hasArg(args, ["-otxt", "--output-txt"])) return null; + if (!hasArg(args, ["-otxt", "--output-txt"])) { + return null; + } const outputBase = findArgValue(args, ["-of", "--output-file"]); - if (!outputBase) return null; + if (!outputBase) { + return null; + } return `${outputBase}.txt`; } @@ -534,18 +662,24 @@ async function resolveCliOutput(params: { if (fileOutput && (await fileExists(fileOutput))) { try { const content = await fs.readFile(fileOutput, "utf8"); - if (content.trim()) return content.trim(); + if (content.trim()) { + return content.trim(); + } } catch {} } if (commandId === "gemini") { const response = extractGeminiResponse(params.stdout); - if (response) return response; + if (response) { + return response; + } } if (commandId === "sherpa-onnx-offline") { const response = extractSherpaOnnxText(params.stdout); - if (response) return response; + if (response) { + return response; + } } return params.stdout.trim(); @@ -556,10 +690,14 @@ type ProviderQuery = Record; function normalizeProviderQuery( options?: Record, ): ProviderQuery | undefined { - if (!options) return undefined; + if (!options) { + return undefined; + } const query: ProviderQuery = {}; for (const [key, value] of Object.entries(options)) { - if (value === undefined) continue; + if (value === undefined) { + continue; + } query[key] = value; } return Object.keys(query).length > 0 ? query : undefined; @@ -570,11 +708,19 @@ function buildDeepgramCompatQuery(options?: { punctuate?: boolean; smartFormat?: boolean; }): ProviderQuery | undefined { - if (!options) return undefined; + if (!options) { + return undefined; + } const query: ProviderQuery = {}; - if (typeof options.detectLanguage === "boolean") query.detect_language = options.detectLanguage; - if (typeof options.punctuate === "boolean") query.punctuate = options.punctuate; - if (typeof options.smartFormat === "boolean") query.smart_format = options.smartFormat; + if (typeof options.detectLanguage === "boolean") { + query.detect_language = options.detectLanguage; + } + if (typeof options.punctuate === "boolean") { + query.punctuate = options.punctuate; + } + if (typeof options.smartFormat === "boolean") { + query.smart_format = options.smartFormat; + } return Object.keys(query).length > 0 ? query : undefined; } @@ -908,7 +1054,9 @@ async function runCliEntry(params: { mediaPath, }); const text = trimOutput(resolved, maxChars); - if (!text) return null; + if (!text) { + return null; + } return { kind: capability === "audio" ? "audio.transcription" : `${capability}.description`, attachmentIndex: params.attachmentIndex, @@ -964,8 +1112,12 @@ async function runAttachmentEntries(params: { }); if (result) { const decision = buildModelDecision({ entry, entryType, outcome: "success" }); - if (result.provider) decision.provider = result.provider; - if (result.model) decision.model = result.model; + if (result.provider) { + decision.provider = result.provider; + } + if (result.model) { + decision.model = result.model; + } attempts.push(decision); return { output: result, attempts }; } @@ -1129,7 +1281,9 @@ export async function runCapability(params: { entries: resolvedEntries, config, }); - if (output) outputs.push(output); + if (output) { + outputs.push(output); + } attachmentDecisions.push({ attachmentIndex: attachment.index, attempts, diff --git a/src/media-understanding/scope.ts b/src/media-understanding/scope.ts index 7aa71cc5ac..f0a13db280 100644 --- a/src/media-understanding/scope.ts +++ b/src/media-understanding/scope.ts @@ -5,8 +5,12 @@ export type MediaUnderstandingScopeDecision = "allow" | "deny"; function normalizeDecision(value?: string | null): MediaUnderstandingScopeDecision | undefined { const normalized = value?.trim().toLowerCase(); - if (normalized === "allow") return "allow"; - if (normalized === "deny") return "deny"; + if (normalized === "allow") { + return "allow"; + } + if (normalized === "deny") { + return "deny"; + } return undefined; } @@ -26,23 +30,33 @@ export function resolveMediaUnderstandingScope(params: { chatType?: string; }): MediaUnderstandingScopeDecision { const scope = params.scope; - if (!scope) return "allow"; + if (!scope) { + return "allow"; + } const channel = normalizeMatch(params.channel); const chatType = normalizeMediaUnderstandingChatType(params.chatType); const sessionKey = normalizeMatch(params.sessionKey) ?? ""; for (const rule of scope.rules ?? []) { - if (!rule) continue; + if (!rule) { + continue; + } const action = normalizeDecision(rule.action) ?? "allow"; const match = rule.match ?? {}; const matchChannel = normalizeMatch(match.channel); const matchChatType = normalizeMediaUnderstandingChatType(match.chatType); const matchPrefix = normalizeMatch(match.keyPrefix); - if (matchChannel && matchChannel !== channel) continue; - if (matchChatType && matchChatType !== chatType) continue; - if (matchPrefix && !sessionKey.startsWith(matchPrefix)) continue; + if (matchChannel && matchChannel !== channel) { + continue; + } + if (matchChatType && matchChatType !== chatType) { + continue; + } + if (matchPrefix && !sessionKey.startsWith(matchPrefix)) { + continue; + } return action; } diff --git a/src/media/audio.ts b/src/media/audio.ts index f1341740e0..aeca2ce0b5 100644 --- a/src/media/audio.ts +++ b/src/media/audio.ts @@ -11,8 +11,12 @@ export function isVoiceCompatibleAudio(opts: { return true; } const fileName = opts.fileName?.trim(); - if (!fileName) return false; + if (!fileName) { + return false; + } const ext = getFileExtension(fileName); - if (!ext) return false; + if (!ext) { + return false; + } return VOICE_AUDIO_EXTENSIONS.has(ext); } diff --git a/src/media/constants.ts b/src/media/constants.ts index e74ac69343..63fdc03fcc 100644 --- a/src/media/constants.ts +++ b/src/media/constants.ts @@ -6,12 +6,24 @@ export const MAX_DOCUMENT_BYTES = 100 * 1024 * 1024; // 100MB export type MediaKind = "image" | "audio" | "video" | "document" | "unknown"; export function mediaKindFromMime(mime?: string | null): MediaKind { - if (!mime) return "unknown"; - if (mime.startsWith("image/")) return "image"; - if (mime.startsWith("audio/")) return "audio"; - if (mime.startsWith("video/")) return "video"; - if (mime === "application/pdf") return "document"; - if (mime.startsWith("application/")) return "document"; + if (!mime) { + return "unknown"; + } + if (mime.startsWith("image/")) { + return "image"; + } + if (mime.startsWith("audio/")) { + return "audio"; + } + if (mime.startsWith("video/")) { + return "video"; + } + if (mime === "application/pdf") { + return "document"; + } + if (mime.startsWith("application/")) { + return "document"; + } return "unknown"; } diff --git a/src/media/fetch.ts b/src/media/fetch.ts index 727ab7a5d7..539d2e9e38 100644 --- a/src/media/fetch.ts +++ b/src/media/fetch.ts @@ -34,7 +34,9 @@ function stripQuotes(value: string): string { } function parseContentDispositionFileName(header?: string | null): string | undefined { - if (!header) return undefined; + if (!header) { + return undefined; + } const starMatch = /filename\*\s*=\s*([^;]+)/i.exec(header); if (starMatch?.[1]) { const cleaned = stripQuotes(starMatch[1].trim()); @@ -46,17 +48,25 @@ function parseContentDispositionFileName(header?: string | null): string | undef } } const match = /filename\s*=\s*([^;]+)/i.exec(header); - if (match?.[1]) return path.basename(stripQuotes(match[1].trim())); + if (match?.[1]) { + return path.basename(stripQuotes(match[1].trim())); + } return undefined; } async function readErrorBodySnippet(res: Response, maxChars = 200): Promise { try { const text = await res.text(); - if (!text) return undefined; + if (!text) { + return undefined; + } const collapsed = text.replace(/\s+/g, " ").trim(); - if (!collapsed) return undefined; - if (collapsed.length <= maxChars) return collapsed; + if (!collapsed) { + return undefined; + } + if (collapsed.length <= maxChars) { + return collapsed; + } return `${collapsed.slice(0, maxChars)}…`; } catch { return undefined; @@ -85,7 +95,9 @@ export async function fetchRemoteMedia(options: FetchMediaOptions): Promise maxBytes) { diff --git a/src/media/host.ts b/src/media/host.ts index f4a21ba04e..b70b6bc12f 100644 --- a/src/media/host.ts +++ b/src/media/host.ts @@ -60,7 +60,9 @@ async function isPortFree(port: number) { await ensurePortAvailable(port); return true; } catch (err) { - if (err instanceof PortInUseError) return false; + if (err instanceof PortInUseError) { + return false; + } throw err; } } diff --git a/src/media/image-ops.ts b/src/media/image-ops.ts index db4f70753f..6a71e03b92 100644 --- a/src/media/image-ops.ts +++ b/src/media/image-ops.ts @@ -69,7 +69,9 @@ function readJpegExifOrientation(buffer: Buffer): number | null { buffer[exifStart + 5] === 0 ) { const tiffStart = exifStart + 6; - if (buffer.length < tiffStart + 8) return null; + if (buffer.length < tiffStart + 8) { + return null; + } // Check byte order (II = little-endian, MM = big-endian) const byteOrder = buffer.toString("ascii", tiffStart, tiffStart + 2); @@ -83,12 +85,16 @@ function readJpegExifOrientation(buffer: Buffer): number | null { // Read IFD0 offset const ifd0Offset = readU32(tiffStart + 4); const ifd0Start = tiffStart + ifd0Offset; - if (buffer.length < ifd0Start + 2) return null; + if (buffer.length < ifd0Start + 2) { + return null; + } const numEntries = readU16(ifd0Start); for (let i = 0; i < numEntries; i++) { const entryOffset = ifd0Start + 2 + i * 12; - if (buffer.length < entryOffset + 12) break; + if (buffer.length < entryOffset + 12) { + break; + } const tag = readU16(entryOffset); // Orientation tag = 0x0112 @@ -142,11 +148,17 @@ async function sipsMetadataFromBuffer(buffer: Buffer): Promise part.trim()); const mimeType = normalizeMimeType(parts[0]); const charset = parts @@ -226,7 +230,9 @@ function decodeTextContent(buffer: Buffer, charset: string | undefined): string } function clampText(text: string, maxChars: number): string { - if (text.length <= maxChars) return text; + if (text.length <= maxChars) { + return text; + } return text.slice(0, maxChars); } @@ -250,7 +256,9 @@ async function extractPdfContent(params: { .map((item) => ("str" in item ? String(item.str) : "")) .filter(Boolean) .join(" "); - if (pageText) textParts.push(pageText); + if (pageText) { + textParts.push(pageText); + } } const text = textParts.join("\n\n"); diff --git a/src/media/mime.ts b/src/media/mime.ts index c50e9152c8..73f6a9b9ab 100644 --- a/src/media/mime.ts +++ b/src/media/mime.ts @@ -53,13 +53,17 @@ const AUDIO_FILE_EXTENSIONS = new Set([ ]); function normalizeHeaderMime(mime?: string | null): string | undefined { - if (!mime) return undefined; + if (!mime) { + return undefined; + } const cleaned = mime.split(";")[0]?.trim().toLowerCase(); return cleaned || undefined; } async function sniffMime(buffer?: Buffer): Promise { - if (!buffer) return undefined; + if (!buffer) { + return undefined; + } try { const type = await fileTypeFromBuffer(buffer); return type?.mime ?? undefined; @@ -69,7 +73,9 @@ async function sniffMime(buffer?: Buffer): Promise { } export function getFileExtension(filePath?: string | null): string | undefined { - if (!filePath) return undefined; + if (!filePath) { + return undefined; + } try { if (/^https?:\/\//i.test(filePath)) { const url = new URL(filePath); @@ -84,7 +90,9 @@ export function getFileExtension(filePath?: string | null): string | undefined { export function isAudioFileName(fileName?: string | null): boolean { const ext = getFileExtension(fileName); - if (!ext) return false; + if (!ext) { + return false; + } return AUDIO_FILE_EXTENSIONS.has(ext); } @@ -97,7 +105,9 @@ export function detectMime(opts: { } function isGenericMime(mime?: string): boolean { - if (!mime) return true; + if (!mime) { + return true; + } const m = mime.toLowerCase(); return m === "application/octet-stream" || m === "application/zip"; } @@ -115,17 +125,29 @@ async function detectMimeImpl(opts: { // Prefer sniffed types, but don't let generic container types override a more // specific extension mapping (e.g. XLSX vs ZIP). - if (sniffed && (!isGenericMime(sniffed) || !extMime)) return sniffed; - if (extMime) return extMime; - if (headerMime && !isGenericMime(headerMime)) return headerMime; - if (sniffed) return sniffed; - if (headerMime) return headerMime; + if (sniffed && (!isGenericMime(sniffed) || !extMime)) { + return sniffed; + } + if (extMime) { + return extMime; + } + if (headerMime && !isGenericMime(headerMime)) { + return headerMime; + } + if (sniffed) { + return sniffed; + } + if (headerMime) { + return headerMime; + } return undefined; } export function extensionForMime(mime?: string | null): string | undefined { - if (!mime) return undefined; + if (!mime) { + return undefined; + } return EXT_BY_MIME[mime.toLowerCase()]; } @@ -133,13 +155,17 @@ export function isGifMedia(opts: { contentType?: string | null; fileName?: string | null; }): boolean { - if (opts.contentType?.toLowerCase() === "image/gif") return true; + if (opts.contentType?.toLowerCase() === "image/gif") { + return true; + } const ext = getFileExtension(opts.fileName); return ext === ".gif"; } export function imageMimeFromFormat(format?: string | null): string | undefined { - if (!format) return undefined; + if (!format) { + return undefined; + } switch (format.toLowerCase()) { case "jpg": case "jpeg": diff --git a/src/media/parse.ts b/src/media/parse.ts index 27bdae718a..b8fe22864e 100644 --- a/src/media/parse.ts +++ b/src/media/parse.ts @@ -15,10 +15,18 @@ function cleanCandidate(raw: string) { } function isValidMedia(candidate: string, opts?: { allowSpaces?: boolean }) { - if (!candidate) return false; - if (candidate.length > 4096) return false; - if (!opts?.allowSpaces && /\s/.test(candidate)) return false; - if (/^https?:\/\//i.test(candidate)) return true; + if (!candidate) { + return false; + } + if (candidate.length > 4096) { + return false; + } + if (!opts?.allowSpaces && /\s/.test(candidate)) { + return false; + } + if (/^https?:\/\//i.test(candidate)) { + return true; + } // Local paths: only allow safe relative paths starting with ./ that do not traverse upwards. return candidate.startsWith("./") && !candidate.includes(".."); @@ -26,11 +34,17 @@ function isValidMedia(candidate: string, opts?: { allowSpaces?: boolean }) { function unwrapQuoted(value: string): string | undefined { const trimmed = value.trim(); - if (trimmed.length < 2) return undefined; + if (trimmed.length < 2) { + return undefined; + } const first = trimmed[0]; const last = trimmed[trimmed.length - 1]; - if (first !== last) return undefined; - if (first !== `"` && first !== "'" && first !== "`") return undefined; + if (first !== last) { + return undefined; + } + if (first !== `"` && first !== "'" && first !== "`") { + return undefined; + } return trimmed.slice(1, -1).trim(); } @@ -48,7 +62,9 @@ export function splitMediaFromOutput(raw: string): { // KNOWN: Leading whitespace is semantically meaningful in Markdown (lists, indented fences). // We only trim the end; token cleanup below handles removing `MEDIA:` lines. const trimmedRaw = raw.trimEnd(); - if (!trimmedRaw.trim()) return { text: "" }; + if (!trimmedRaw.trim()) { + return { text: "" }; + } const media: string[] = []; let foundMediaToken = false; @@ -189,7 +205,9 @@ export function splitMediaFromOutput(raw: string): { // Return cleaned text if we found a media token OR audio tag, otherwise original text: foundMediaToken || hasAudioAsVoice ? cleanedText : trimmedRaw, }; - if (hasAudioAsVoice) result.audioAsVoice = true; + if (hasAudioAsVoice) { + result.audioAsVoice = true; + } return result; } diff --git a/src/media/server.ts b/src/media/server.ts index 4791352d8b..ab1574f3c9 100644 --- a/src/media/server.ts +++ b/src/media/server.ts @@ -13,9 +13,15 @@ const MEDIA_ID_PATTERN = /^[\p{L}\p{N}._-]+$/u; const MAX_MEDIA_BYTES = MEDIA_MAX_BYTES; const isValidMediaId = (id: string) => { - if (!id) return false; - if (id.length > MAX_MEDIA_ID_CHARS) return false; - if (id === "." || id === "..") return false; + if (!id) { + return false; + } + if (id.length > MAX_MEDIA_ID_CHARS) { + return false; + } + if (id === "." || id === "..") { + return false; + } return MEDIA_ID_PATTERN.test(id); }; @@ -51,7 +57,9 @@ export function attachMediaRoutes( const data = await handle.readFile(); await handle.close().catch(() => {}); const mime = await detectMime({ buffer: data, filePath: realPath }); - if (mime) res.type(mime); + if (mime) { + res.type(mime); + } res.send(data); // best-effort single-use cleanup after response ends res.on("finish", () => { diff --git a/src/media/store.redirect.test.ts b/src/media/store.redirect.test.ts index 4a1ff25910..97c54e7aeb 100644 --- a/src/media/store.redirect.test.ts +++ b/src/media/store.redirect.test.ts @@ -47,7 +47,9 @@ describe("media store redirects", () => { const res = new PassThrough(); const req = { on: (event: string, handler: (...args: unknown[]) => void) => { - if (event === "error") res.on("error", handler); + if (event === "error") { + res.on("error", handler); + } return req; }, end: () => undefined, @@ -88,7 +90,9 @@ describe("media store redirects", () => { const res = new PassThrough(); const req = { on: (event: string, handler: (...args: unknown[]) => void) => { - if (event === "error") res.on("error", handler); + if (event === "error") { + res.on("error", handler); + } return req; }, end: () => undefined, diff --git a/src/media/store.test.ts b/src/media/store.test.ts index 852941bb25..169f35ba49 100644 --- a/src/media/store.test.ts +++ b/src/media/store.test.ts @@ -20,8 +20,11 @@ describe("media store", () => { const restoreEnv = () => { for (const [key, value] of Object.entries(envSnapshot)) { - if (value === undefined) delete process.env[key]; - else process.env[key] = value; + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } } }; diff --git a/src/media/store.ts b/src/media/store.ts index 2689370849..8ab481d6cb 100644 --- a/src/media/store.ts +++ b/src/media/store.ts @@ -21,7 +21,9 @@ const DEFAULT_TTL_MS = 2 * 60 * 1000; // 2 minutes */ function sanitizeFilename(name: string): string { const trimmed = name.trim(); - if (!trimmed) return ""; + if (!trimmed) { + return ""; + } const sanitized = trimmed.replace(/[^\p{L}\p{N}._-]+/gu, "_"); // Collapse multiple underscores, trim leading/trailing, limit length return sanitized.replace(/_+/g, "_").replace(/^_|_$/g, "").slice(0, 60); @@ -34,7 +36,9 @@ function sanitizeFilename(name: string): string { */ export function extractOriginalFilename(filePath: string): string { const basename = path.basename(filePath); - if (!basename) return "file.bin"; // Fallback for empty input + if (!basename) { + return "file.bin"; + } // Fallback for empty input const ext = path.extname(basename); const nameWithoutExt = path.basename(basename, ext); @@ -68,7 +72,9 @@ export async function cleanOldMedia(ttlMs = DEFAULT_TTL_MS) { entries.map(async (file) => { const full = path.join(mediaDir, file); const stat = await fs.stat(full).catch(() => null); - if (!stat) return; + if (!stat) { + return; + } if (now - stat.mtimeMs > ttlMs) { await fs.rm(full).catch(() => {}); } diff --git a/src/memory/batch-gemini.ts b/src/memory/batch-gemini.ts index 034bdda841..698a517306 100644 --- a/src/memory/batch-gemini.ts +++ b/src/memory/batch-gemini.ts @@ -38,7 +38,9 @@ const debugEmbeddings = isTruthyEnvValue(process.env.OPENCLAW_DEBUG_MEMORY_EMBED const log = createSubsystemLogger("memory/embeddings"); const debugLog = (message: string, meta?: Record) => { - if (!debugEmbeddings) return; + if (!debugEmbeddings) { + return; + } const suffix = meta ? ` ${JSON.stringify(meta)}` : ""; log.raw(`${message}${suffix}`); }; @@ -71,7 +73,9 @@ function getGeminiUploadUrl(baseUrl: string): string { } function splitGeminiBatchRequests(requests: GeminiBatchRequest[]): GeminiBatchRequest[][] { - if (requests.length <= GEMINI_BATCH_MAX_REQUESTS) return [requests]; + if (requests.length <= GEMINI_BATCH_MAX_REQUESTS) { + return [requests]; + } const groups: GeminiBatchRequest[][] = []; for (let i = 0; i < requests.length; i += GEMINI_BATCH_MAX_REQUESTS) { groups.push(requests.slice(i, i + GEMINI_BATCH_MAX_REQUESTS)); @@ -218,7 +222,9 @@ async function fetchGeminiFileContent(params: { } function parseGeminiBatchOutput(text: string): GeminiBatchOutputLine[] { - if (!text.trim()) return []; + if (!text.trim()) { + return []; + } return text .split("\n") .map((line) => line.trim()) @@ -272,7 +278,9 @@ async function waitForGeminiBatch(params: { } async function runWithConcurrency(tasks: Array<() => Promise>, limit: number): Promise { - if (tasks.length === 0) return []; + if (tasks.length === 0) { + return []; + } const resolvedLimit = Math.max(1, Math.min(limit, tasks.length)); const results: T[] = Array.from({ length: tasks.length }); let next = 0; @@ -280,10 +288,14 @@ async function runWithConcurrency(tasks: Array<() => Promise>, limit: numb const workers = Array.from({ length: resolvedLimit }, async () => { while (true) { - if (firstError) return; + if (firstError) { + return; + } const index = next; next += 1; - if (index >= tasks.length) return; + if (index >= tasks.length) { + return; + } try { results[index] = await tasks[index](); } catch (err) { @@ -294,7 +306,9 @@ async function runWithConcurrency(tasks: Array<() => Promise>, limit: numb }); await Promise.allSettled(workers); - if (firstError) throw firstError; + if (firstError) { + throw firstError; + } return results; } @@ -308,7 +322,9 @@ export async function runGeminiEmbeddingBatches(params: { concurrency: number; debug?: (message: string, data?: Record) => void; }): Promise> { - if (params.requests.length === 0) return new Map(); + if (params.requests.length === 0) { + return new Map(); + } const groups = splitGeminiBatchRequests(params.requests); const byCustomId = new Map(); @@ -373,7 +389,9 @@ export async function runGeminiEmbeddingBatches(params: { for (const line of outputLines) { const customId = line.key ?? line.custom_id ?? line.request_id; - if (!customId) continue; + if (!customId) { + continue; + } remaining.delete(customId); if (line.error?.message) { errors.push(`${customId}: ${line.error.message}`); diff --git a/src/memory/batch-openai.ts b/src/memory/batch-openai.ts index e49716fe66..ac12f8bd1f 100644 --- a/src/memory/batch-openai.ts +++ b/src/memory/batch-openai.ts @@ -56,7 +56,9 @@ function getOpenAiHeaders( } function splitOpenAiBatchRequests(requests: OpenAiBatchRequest[]): OpenAiBatchRequest[][] { - if (requests.length <= OPENAI_BATCH_MAX_REQUESTS) return [requests]; + if (requests.length <= OPENAI_BATCH_MAX_REQUESTS) { + return [requests]; + } const groups: OpenAiBatchRequest[][] = []; for (let i = 0; i < requests.length; i += OPENAI_BATCH_MAX_REQUESTS) { groups.push(requests.slice(i, i + OPENAI_BATCH_MAX_REQUESTS)); @@ -163,7 +165,9 @@ async function fetchOpenAiFileContent(params: { } function parseOpenAiBatchOutput(text: string): OpenAiBatchOutputLine[] { - if (!text.trim()) return []; + if (!text.trim()) { + return []; + } return text .split("\n") .map((line) => line.trim()) @@ -242,7 +246,9 @@ async function waitForOpenAiBatch(params: { } async function runWithConcurrency(tasks: Array<() => Promise>, limit: number): Promise { - if (tasks.length === 0) return []; + if (tasks.length === 0) { + return []; + } const resolvedLimit = Math.max(1, Math.min(limit, tasks.length)); const results: T[] = Array.from({ length: tasks.length }); let next = 0; @@ -250,10 +256,14 @@ async function runWithConcurrency(tasks: Array<() => Promise>, limit: numb const workers = Array.from({ length: resolvedLimit }, async () => { while (true) { - if (firstError) return; + if (firstError) { + return; + } const index = next; next += 1; - if (index >= tasks.length) return; + if (index >= tasks.length) { + return; + } try { results[index] = await tasks[index](); } catch (err) { @@ -264,7 +274,9 @@ async function runWithConcurrency(tasks: Array<() => Promise>, limit: numb }); await Promise.allSettled(workers); - if (firstError) throw firstError; + if (firstError) { + throw firstError; + } return results; } @@ -278,7 +290,9 @@ export async function runOpenAiEmbeddingBatches(params: { concurrency: number; debug?: (message: string, data?: Record) => void; }): Promise> { - if (params.requests.length === 0) return new Map(); + if (params.requests.length === 0) { + return new Map(); + } const groups = splitOpenAiBatchRequests(params.requests); const byCustomId = new Map(); @@ -335,7 +349,9 @@ export async function runOpenAiEmbeddingBatches(params: { for (const line of outputLines) { const customId = line.custom_id; - if (!customId) continue; + if (!customId) { + continue; + } remaining.delete(customId); if (line.error?.message) { errors.push(`${customId}: ${line.error.message}`); diff --git a/src/memory/embeddings-gemini.ts b/src/memory/embeddings-gemini.ts index 2a8ef1a7d3..bb11dda134 100644 --- a/src/memory/embeddings-gemini.ts +++ b/src/memory/embeddings-gemini.ts @@ -16,14 +16,18 @@ const debugEmbeddings = isTruthyEnvValue(process.env.OPENCLAW_DEBUG_MEMORY_EMBED const log = createSubsystemLogger("memory/embeddings"); const debugLog = (message: string, meta?: Record) => { - if (!debugEmbeddings) return; + if (!debugEmbeddings) { + return; + } const suffix = meta ? ` ${JSON.stringify(meta)}` : ""; log.raw(`${message}${suffix}`); }; function resolveRemoteApiKey(remoteApiKey?: string): string | undefined { const trimmed = remoteApiKey?.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } if (trimmed === "GOOGLE_API_KEY" || trimmed === "GEMINI_API_KEY") { return process.env[trimmed]?.trim(); } @@ -32,17 +36,25 @@ function resolveRemoteApiKey(remoteApiKey?: string): string | undefined { function normalizeGeminiModel(model: string): string { const trimmed = model.trim(); - if (!trimmed) return DEFAULT_GEMINI_EMBEDDING_MODEL; + if (!trimmed) { + return DEFAULT_GEMINI_EMBEDDING_MODEL; + } const withoutPrefix = trimmed.replace(/^models\//, ""); - if (withoutPrefix.startsWith("gemini/")) return withoutPrefix.slice("gemini/".length); - if (withoutPrefix.startsWith("google/")) return withoutPrefix.slice("google/".length); + if (withoutPrefix.startsWith("gemini/")) { + return withoutPrefix.slice("gemini/".length); + } + if (withoutPrefix.startsWith("google/")) { + return withoutPrefix.slice("google/".length); + } return withoutPrefix; } function normalizeGeminiBaseUrl(raw: string): string { const trimmed = raw.replace(/\/+$/, ""); const openAiIndex = trimmed.indexOf("/openai"); - if (openAiIndex > -1) return trimmed.slice(0, openAiIndex); + if (openAiIndex > -1) { + return trimmed.slice(0, openAiIndex); + } return trimmed; } @@ -59,7 +71,9 @@ export async function createGeminiEmbeddingProvider( const batchUrl = `${baseUrl}/${client.modelPath}:batchEmbedContents`; const embedQuery = async (text: string): Promise => { - if (!text.trim()) return []; + if (!text.trim()) { + return []; + } const res = await fetch(embedUrl, { method: "POST", headers: client.headers, @@ -77,7 +91,9 @@ export async function createGeminiEmbeddingProvider( }; const embedBatch = async (texts: string[]): Promise => { - if (texts.length === 0) return []; + if (texts.length === 0) { + return []; + } const requests = texts.map((text) => ({ model: client.modelPath, content: { parts: [{ text }] }, diff --git a/src/memory/embeddings-openai.ts b/src/memory/embeddings-openai.ts index cfc53efaeb..7efe3290e2 100644 --- a/src/memory/embeddings-openai.ts +++ b/src/memory/embeddings-openai.ts @@ -12,8 +12,12 @@ const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1"; export function normalizeOpenAiModel(model: string): string { const trimmed = model.trim(); - if (!trimmed) return DEFAULT_OPENAI_EMBEDDING_MODEL; - if (trimmed.startsWith("openai/")) return trimmed.slice("openai/".length); + if (!trimmed) { + return DEFAULT_OPENAI_EMBEDDING_MODEL; + } + if (trimmed.startsWith("openai/")) { + return trimmed.slice("openai/".length); + } return trimmed; } @@ -24,7 +28,9 @@ export async function createOpenAiEmbeddingProvider( const url = `${client.baseUrl.replace(/\/$/, "")}/embeddings`; const embed = async (input: string[]): Promise => { - if (input.length === 0) return []; + if (input.length === 0) { + return []; + } const res = await fetch(url, { method: "POST", headers: client.headers, diff --git a/src/memory/embeddings.test.ts b/src/memory/embeddings.test.ts index 1809b24b8b..55a261a1cc 100644 --- a/src/memory/embeddings.test.ts +++ b/src/memory/embeddings.test.ts @@ -5,7 +5,9 @@ import { DEFAULT_GEMINI_EMBEDDING_MODEL } from "./embeddings-gemini.js"; vi.mock("../agents/model-auth.js", () => ({ resolveApiKeyForProvider: vi.fn(), requireApiKey: (auth: { apiKey?: string; mode?: string }, provider: string) => { - if (auth?.apiKey) return auth.apiKey; + if (auth?.apiKey) { + return auth.apiKey; + } throw new Error(`No API key resolved for provider "${provider}" (auth mode: ${auth?.mode}).`); }, })); diff --git a/src/memory/embeddings.ts b/src/memory/embeddings.ts index 6e923cb383..00f674354e 100644 --- a/src/memory/embeddings.ts +++ b/src/memory/embeddings.ts @@ -47,8 +47,12 @@ const DEFAULT_LOCAL_MODEL = "hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma function canAutoSelectLocal(options: EmbeddingProviderOptions): boolean { const modelPath = options.local?.modelPath?.trim(); - if (!modelPath) return false; - if (/^(hf:|https?:)/i.test(modelPath)) return false; + if (!modelPath) { + return false; + } + if (/^(hf:|https?:)/i.test(modelPath)) { + return false; + } const resolved = resolveUserPath(modelPath); try { return fsSync.statSync(resolved).isFile(); @@ -193,12 +197,16 @@ export async function createEmbeddingProvider( } function formatError(err: unknown): string { - if (err instanceof Error) return err.message; + if (err instanceof Error) { + return err.message; + } return String(err); } function isNodeLlamaCppMissing(err: unknown): boolean { - if (!(err instanceof Error)) return false; + if (!(err instanceof Error)) { + return false; + } const code = (err as Error & { code?: unknown }).code; if (code === "ERR_MODULE_NOT_FOUND") { return err.message.includes("node-llama-cpp"); diff --git a/src/memory/headers-fingerprint.ts b/src/memory/headers-fingerprint.ts index 918500285e..122ba074a2 100644 --- a/src/memory/headers-fingerprint.ts +++ b/src/memory/headers-fingerprint.ts @@ -3,11 +3,15 @@ function normalizeHeaderName(name: string): string { } export function fingerprintHeaderNames(headers: Record | undefined): string[] { - if (!headers) return []; + if (!headers) { + return []; + } const out: string[] = []; for (const key of Object.keys(headers)) { const normalized = normalizeHeaderName(key); - if (!normalized) continue; + if (!normalized) { + continue; + } out.push(normalized); } out.sort((a, b) => a.localeCompare(b)); diff --git a/src/memory/hybrid.ts b/src/memory/hybrid.ts index 4837b1c476..1dd7c9fdab 100644 --- a/src/memory/hybrid.ts +++ b/src/memory/hybrid.ts @@ -26,7 +26,9 @@ export function buildFtsQuery(raw: string): string | null { .match(/[A-Za-z0-9_]+/g) ?.map((t) => t.trim()) .filter(Boolean) ?? []; - if (tokens.length === 0) return null; + if (tokens.length === 0) { + return null; + } const quoted = tokens.map((t) => `"${t.replaceAll('"', "")}"`); return quoted.join(" AND "); } @@ -80,7 +82,9 @@ export function mergeHybridResults(params: { const existing = byId.get(r.id); if (existing) { existing.textScore = r.textScore; - if (r.snippet && r.snippet.length > 0) existing.snippet = r.snippet; + if (r.snippet && r.snippet.length > 0) { + existing.snippet = r.snippet; + } } else { byId.set(r.id, { id: r.id, diff --git a/src/memory/index.test.ts b/src/memory/index.test.ts index 5768895993..a772700e32 100644 --- a/src/memory/index.test.ts +++ b/src/memory/index.test.ts @@ -79,7 +79,9 @@ describe("memory index", () => { }; const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); - if (!result.manager) throw new Error("manager missing"); + if (!result.manager) { + throw new Error("manager missing"); + } manager = result.manager; await result.manager.sync({ force: true }); const results = await result.manager.search("alpha"); @@ -130,7 +132,9 @@ describe("memory index", () => { agentId: "main", }); expect(first.manager).not.toBeNull(); - if (!first.manager) throw new Error("manager missing"); + if (!first.manager) { + throw new Error("manager missing"); + } await first.manager.sync({ force: true }); await first.manager.close(); @@ -151,7 +155,9 @@ describe("memory index", () => { agentId: "main", }); expect(second.manager).not.toBeNull(); - if (!second.manager) throw new Error("manager missing"); + if (!second.manager) { + throw new Error("manager missing"); + } manager = second.manager; await second.manager.sync({ reason: "test" }); const results = await second.manager.search("alpha"); @@ -177,7 +183,9 @@ describe("memory index", () => { }; const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); - if (!result.manager) throw new Error("manager missing"); + if (!result.manager) { + throw new Error("manager missing"); + } manager = result.manager; await manager.sync({ force: true }); const afterFirst = embedBatchCalls; @@ -206,7 +214,9 @@ describe("memory index", () => { }; const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); - if (!result.manager) throw new Error("manager missing"); + if (!result.manager) { + throw new Error("manager missing"); + } manager = result.manager; await manager.sync({ force: true }); @@ -245,11 +255,15 @@ describe("memory index", () => { }; const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); - if (!result.manager) throw new Error("manager missing"); + if (!result.manager) { + throw new Error("manager missing"); + } manager = result.manager; const status = manager.status(); - if (!status.fts?.available) return; + if (!status.fts?.available) { + return; + } await manager.sync({ force: true }); const results = await manager.search("zebra"); @@ -294,11 +308,15 @@ describe("memory index", () => { }; const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); - if (!result.manager) throw new Error("manager missing"); + if (!result.manager) { + throw new Error("manager missing"); + } manager = result.manager; const status = manager.status(); - if (!status.fts?.available) return; + if (!status.fts?.available) { + return; + } await manager.sync({ force: true }); const results = await manager.search("alpha beta id123"); @@ -348,11 +366,15 @@ describe("memory index", () => { }; const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); - if (!result.manager) throw new Error("manager missing"); + if (!result.manager) { + throw new Error("manager missing"); + } manager = result.manager; const status = manager.status(); - if (!status.fts?.available) return; + if (!status.fts?.available) { + return; + } await manager.sync({ force: true }); const results = await manager.search("alpha beta id123"); @@ -382,7 +404,9 @@ describe("memory index", () => { }; const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); - if (!result.manager) throw new Error("manager missing"); + if (!result.manager) { + throw new Error("manager missing"); + } manager = result.manager; const available = await result.manager.probeVectorAvailability(); const status = result.manager.status(); @@ -408,7 +432,9 @@ describe("memory index", () => { }; const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); - if (!result.manager) throw new Error("manager missing"); + if (!result.manager) { + throw new Error("manager missing"); + } manager = result.manager; await expect(result.manager.readFile({ relPath: "NOTES.md" })).rejects.toThrow("path required"); }); @@ -435,7 +461,9 @@ describe("memory index", () => { }; const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); - if (!result.manager) throw new Error("manager missing"); + if (!result.manager) { + throw new Error("manager missing"); + } manager = result.manager; await expect(result.manager.readFile({ relPath: "extra/extra.md" })).resolves.toEqual({ path: "extra/extra.md", diff --git a/src/memory/internal.ts b/src/memory/internal.ts index b2ab8c0a4f..cbdb7c6c6e 100644 --- a/src/memory/internal.ts +++ b/src/memory/internal.ts @@ -31,7 +31,9 @@ export function normalizeRelPath(value: string): string { } export function normalizeExtraMemoryPaths(workspaceDir: string, extraPaths?: string[]): string[] { - if (!extraPaths?.length) return []; + if (!extraPaths?.length) { + return []; + } const resolved = extraPaths .map((value) => value.trim()) .filter(Boolean) @@ -43,8 +45,12 @@ export function normalizeExtraMemoryPaths(workspaceDir: string, extraPaths?: str export function isMemoryPath(relPath: string): boolean { const normalized = normalizeRelPath(relPath); - if (!normalized) return false; - if (normalized === "MEMORY.md" || normalized === "memory.md") return true; + if (!normalized) { + return false; + } + if (normalized === "MEMORY.md" || normalized === "memory.md") { + return true; + } return normalized.startsWith("memory/"); } @@ -52,13 +58,19 @@ async function walkDir(dir: string, files: string[]) { const entries = await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const full = path.join(dir, entry.name); - if (entry.isSymbolicLink()) continue; + if (entry.isSymbolicLink()) { + continue; + } if (entry.isDirectory()) { await walkDir(full, files); continue; } - if (!entry.isFile()) continue; - if (!entry.name.endsWith(".md")) continue; + if (!entry.isFile()) { + continue; + } + if (!entry.name.endsWith(".md")) { + continue; + } files.push(full); } } @@ -75,8 +87,12 @@ export async function listMemoryFiles( const addMarkdownFile = async (absPath: string) => { try { const stat = await fs.lstat(absPath); - if (stat.isSymbolicLink() || !stat.isFile()) return; - if (!absPath.endsWith(".md")) return; + if (stat.isSymbolicLink() || !stat.isFile()) { + return; + } + if (!absPath.endsWith(".md")) { + return; + } result.push(absPath); } catch {} }; @@ -95,7 +111,9 @@ export async function listMemoryFiles( for (const inputPath of normalizedExtraPaths) { try { const stat = await fs.lstat(inputPath); - if (stat.isSymbolicLink()) continue; + if (stat.isSymbolicLink()) { + continue; + } if (stat.isDirectory()) { await walkDir(inputPath, result); continue; @@ -106,7 +124,9 @@ export async function listMemoryFiles( } catch {} } } - if (result.length <= 1) return result; + if (result.length <= 1) { + return result; + } const seen = new Set(); const deduped: string[] = []; for (const entry of result) { @@ -114,7 +134,9 @@ export async function listMemoryFiles( try { key = await fs.realpath(entry); } catch {} - if (seen.has(key)) continue; + if (seen.has(key)) { + continue; + } seen.add(key); deduped.push(entry); } @@ -146,7 +168,9 @@ export function chunkMarkdown( chunking: { tokens: number; overlap: number }, ): MemoryChunk[] { const lines = content.split("\n"); - if (lines.length === 0) return []; + if (lines.length === 0) { + return []; + } const maxChars = Math.max(32, chunking.tokens * 4); const overlapChars = Math.max(0, chunking.overlap * 4); const chunks: MemoryChunk[] = []; @@ -155,10 +179,14 @@ export function chunkMarkdown( let currentChars = 0; const flush = () => { - if (current.length === 0) return; + if (current.length === 0) { + return; + } const firstEntry = current[0]; const lastEntry = current[current.length - 1]; - if (!firstEntry || !lastEntry) return; + if (!firstEntry || !lastEntry) { + return; + } const text = current.map((entry) => entry.line).join("\n"); const startLine = firstEntry.lineNo; const endLine = lastEntry.lineNo; @@ -180,10 +208,14 @@ export function chunkMarkdown( const kept: Array<{ line: string; lineNo: number }> = []; for (let i = current.length - 1; i >= 0; i -= 1) { const entry = current[i]; - if (!entry) continue; + if (!entry) { + continue; + } acc += entry.line.length + 1; kept.unshift(entry); - if (acc >= overlapChars) break; + if (acc >= overlapChars) { + break; + } } current = kept; currentChars = kept.reduce((sum, entry) => sum + entry.line.length + 1, 0); @@ -224,7 +256,9 @@ export function parseEmbedding(raw: string): number[] { } export function cosineSimilarity(a: number[], b: number[]): number { - if (a.length === 0 || b.length === 0) return 0; + if (a.length === 0 || b.length === 0) { + return 0; + } const len = Math.min(a.length, b.length); let dot = 0; let normA = 0; @@ -236,6 +270,8 @@ export function cosineSimilarity(a: number[], b: number[]): number { normA += av * av; normB += bv * bv; } - if (normA === 0 || normB === 0) return 0; + if (normA === 0 || normB === 0) { + return 0; + } return dot / (Math.sqrt(normA) * Math.sqrt(normB)); } diff --git a/src/memory/manager-search.ts b/src/memory/manager-search.ts index f18be3faa1..3880c1e360 100644 --- a/src/memory/manager-search.ts +++ b/src/memory/manager-search.ts @@ -29,7 +29,9 @@ export async function searchVector(params: { sourceFilterVec: { sql: string; params: SearchSource[] }; sourceFilterChunks: { sql: string; params: SearchSource[] }; }): Promise { - if (params.queryVec.length === 0 || params.limit <= 0) return []; + if (params.queryVec.length === 0 || params.limit <= 0) { + return []; + } if (await params.ensureVectorReady(params.queryVec.length)) { const rows = params.db .prepare( @@ -143,9 +145,13 @@ export async function searchKeyword(params: { buildFtsQuery: (raw: string) => string | null; bm25RankToScore: (rank: number) => number; }): Promise> { - if (params.limit <= 0) return []; + if (params.limit <= 0) { + return []; + } const ftsQuery = params.buildFtsQuery(params.query); - if (!ftsQuery) return []; + if (!ftsQuery) { + return []; + } const rows = params.db .prepare( diff --git a/src/memory/manager.async-search.test.ts b/src/memory/manager.async-search.test.ts index 3537ded05f..020639b9e2 100644 --- a/src/memory/manager.async-search.test.ts +++ b/src/memory/manager.async-search.test.ts @@ -67,7 +67,9 @@ describe("memory search async sync", () => { const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); - if (!result.manager) throw new Error("manager missing"); + if (!result.manager) { + throw new Error("manager missing"); + } manager = result.manager; const pending = new Promise(() => {}); diff --git a/src/memory/manager.atomic-reindex.test.ts b/src/memory/manager.atomic-reindex.test.ts index 2fde5f5158..14ab997dfd 100644 --- a/src/memory/manager.atomic-reindex.test.ts +++ b/src/memory/manager.atomic-reindex.test.ts @@ -76,7 +76,9 @@ describe("memory manager atomic reindex", () => { const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); - if (!result.manager) throw new Error("manager missing"); + if (!result.manager) { + throw new Error("manager missing"); + } manager = result.manager; await manager.sync({ force: true }); diff --git a/src/memory/manager.batch.test.ts b/src/memory/manager.batch.test.ts index d7b8181b02..a2116529d0 100644 --- a/src/memory/manager.batch.test.ts +++ b/src/memory/manager.batch.test.ts @@ -79,7 +79,9 @@ describe("memory indexing with OpenAI batches", () => { throw new Error("expected FormData upload"); } for (const [key, value] of body.entries()) { - if (key !== "file") continue; + if (key !== "file") { + continue; + } if (typeof value === "string") { uploadedRequests = value .split("\n") @@ -149,13 +151,17 @@ describe("memory indexing with OpenAI batches", () => { const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); - if (!result.manager) throw new Error("manager missing"); + if (!result.manager) { + throw new Error("manager missing"); + } manager = result.manager; const labels: string[] = []; await manager.sync({ force: true, progress: (update) => { - if (update.label) labels.push(update.label); + if (update.label) { + labels.push(update.label); + } }, }); @@ -181,7 +187,9 @@ describe("memory indexing with OpenAI batches", () => { throw new Error("expected FormData upload"); } for (const [key, value] of body.entries()) { - if (key !== "file") continue; + if (key !== "file") { + continue; + } if (typeof value === "string") { uploadedRequests = value .split("\n") @@ -255,7 +263,9 @@ describe("memory indexing with OpenAI batches", () => { const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); - if (!result.manager) throw new Error("manager missing"); + if (!result.manager) { + throw new Error("manager missing"); + } manager = result.manager; await manager.sync({ force: true }); @@ -279,7 +289,9 @@ describe("memory indexing with OpenAI batches", () => { throw new Error("expected FormData upload"); } for (const [key, value] of body.entries()) { - if (key !== "file") continue; + if (key !== "file") { + continue; + } if (typeof value === "string") { uploadedRequests = value .split("\n") @@ -352,7 +364,9 @@ describe("memory indexing with OpenAI batches", () => { const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); - if (!result.manager) throw new Error("manager missing"); + if (!result.manager) { + throw new Error("manager missing"); + } manager = result.manager; await manager.sync({ force: true }); @@ -388,7 +402,9 @@ describe("memory indexing with OpenAI batches", () => { throw new Error("expected FormData upload"); } for (const [key, value] of body.entries()) { - if (key !== "file") continue; + if (key !== "file") { + continue; + } if (typeof value === "string") { uploadedRequests = value .split("\n") @@ -449,7 +465,9 @@ describe("memory indexing with OpenAI batches", () => { const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); - if (!result.manager) throw new Error("manager missing"); + if (!result.manager) { + throw new Error("manager missing"); + } manager = result.manager; await manager.sync({ force: true }); diff --git a/src/memory/manager.embedding-batches.test.ts b/src/memory/manager.embedding-batches.test.ts index b28ce84bd2..6070adb20b 100644 --- a/src/memory/manager.embedding-batches.test.ts +++ b/src/memory/manager.embedding-batches.test.ts @@ -66,7 +66,9 @@ describe("memory embedding batches", () => { const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); - if (!result.manager) throw new Error("manager missing"); + if (!result.manager) { + throw new Error("manager missing"); + } manager = result.manager; await manager.sync({ force: true }); @@ -100,7 +102,9 @@ describe("memory embedding batches", () => { const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); - if (!result.manager) throw new Error("manager missing"); + if (!result.manager) { + throw new Error("manager missing"); + } manager = result.manager; await manager.sync({ force: true }); @@ -131,7 +135,9 @@ describe("memory embedding batches", () => { const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); - if (!result.manager) throw new Error("manager missing"); + if (!result.manager) { + throw new Error("manager missing"); + } manager = result.manager; const updates: Array<{ completed: number; total: number; label?: string }> = []; await manager.sync({ @@ -194,7 +200,9 @@ describe("memory embedding batches", () => { const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); - if (!result.manager) throw new Error("manager missing"); + if (!result.manager) { + throw new Error("manager missing"); + } manager = result.manager; try { await manager.sync({ force: true }); @@ -251,7 +259,9 @@ describe("memory embedding batches", () => { const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); - if (!result.manager) throw new Error("manager missing"); + if (!result.manager) { + throw new Error("manager missing"); + } manager = result.manager; try { await manager.sync({ force: true }); @@ -283,7 +293,9 @@ describe("memory embedding batches", () => { const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); - if (!result.manager) throw new Error("manager missing"); + if (!result.manager) { + throw new Error("manager missing"); + } manager = result.manager; await manager.sync({ force: true }); diff --git a/src/memory/manager.sync-errors-do-not-crash.test.ts b/src/memory/manager.sync-errors-do-not-crash.test.ts index 38c0859997..c907384f86 100644 --- a/src/memory/manager.sync-errors-do-not-crash.test.ts +++ b/src/memory/manager.sync-errors-do-not-crash.test.ts @@ -77,7 +77,9 @@ describe("memory manager sync failures", () => { const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); - if (!result.manager) throw new Error("manager missing"); + if (!result.manager) { + throw new Error("manager missing"); + } manager = result.manager; const syncSpy = vi.spyOn(manager, "sync"); diff --git a/src/memory/manager.ts b/src/memory/manager.ts index c943183639..08b1925ea5 100644 --- a/src/memory/manager.ts +++ b/src/memory/manager.ts @@ -179,11 +179,15 @@ export class MemoryIndexManager { }): Promise { const { cfg, agentId } = params; const settings = resolveMemorySearchConfig(cfg, agentId); - if (!settings) return null; + if (!settings) { + return null; + } const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId); const key = `${agentId}:${workspaceDir}:${JSON.stringify(settings)}`; const existing = INDEX_CACHE.get(key); - if (existing) return existing; + if (existing) { + return existing; + } const providerResult = await createEmbeddingProvider({ config: cfg, agentDir: resolveAgentDir(cfg, agentId), @@ -250,13 +254,19 @@ export class MemoryIndexManager { } async warmSession(sessionKey?: string): Promise { - if (!this.settings.sync.onSessionStart) return; + if (!this.settings.sync.onSessionStart) { + return; + } const key = sessionKey?.trim() || ""; - if (key && this.sessionWarm.has(key)) return; + if (key && this.sessionWarm.has(key)) { + return; + } void this.sync({ reason: "session-start" }).catch((err) => { log.warn(`memory sync failed (session-start): ${String(err)}`); }); - if (key) this.sessionWarm.add(key); + if (key) { + this.sessionWarm.add(key); + } } async search( @@ -274,7 +284,9 @@ export class MemoryIndexManager { }); } const cleaned = query.trim(); - if (!cleaned) return []; + if (!cleaned) { + return []; + } const minScore = opts?.minScore ?? this.settings.query.minScore; const maxResults = opts?.maxResults ?? this.settings.query.maxResults; const hybrid = this.settings.query.hybrid; @@ -333,7 +345,9 @@ export class MemoryIndexManager { query: string, limit: number, ): Promise> { - if (!this.fts.enabled || !this.fts.available) return []; + if (!this.fts.enabled || !this.fts.available) { + return []; + } const sourceFilter = this.buildSourceFilter(); const results = await searchKeyword({ db: this.db, @@ -385,7 +399,9 @@ export class MemoryIndexManager { force?: boolean; progress?: (update: MemorySyncProgressUpdate) => void; }): Promise { - if (this.syncing) return this.syncing; + if (this.syncing) { + return this.syncing; + } this.syncing = this.runSync(params).finally(() => { this.syncing = null; }); @@ -417,7 +433,9 @@ export class MemoryIndexManager { for (const additionalPath of additionalPaths) { try { const stat = await fs.lstat(additionalPath); - if (stat.isSymbolicLink()) continue; + if (stat.isSymbolicLink()) { + continue; + } if (stat.isDirectory()) { if (absPath === additionalPath || absPath.startsWith(`${additionalPath}${path.sep}`)) { allowedAdditional = true; @@ -502,7 +520,9 @@ export class MemoryIndexManager { }; const sourceCounts = (() => { const sources = Array.from(this.sources); - if (sources.length === 0) return []; + if (sources.length === 0) { + return []; + } const bySource = new Map(); for (const source of sources) { bySource.set(source, { files: 0, chunks: 0 }); @@ -583,7 +603,9 @@ export class MemoryIndexManager { } async probeVectorAvailability(): Promise { - if (!this.vector.enabled) return false; + if (!this.vector.enabled) { + return false; + } return this.ensureVectorReady(); } @@ -598,7 +620,9 @@ export class MemoryIndexManager { } async close(): Promise { - if (this.closed) return; + if (this.closed) { + return; + } this.closed = true; if (this.watchTimer) { clearTimeout(this.watchTimer); @@ -625,7 +649,9 @@ export class MemoryIndexManager { } private async ensureVectorReady(dimensions?: number): Promise { - if (!this.vector.enabled) return false; + if (!this.vector.enabled) { + return false; + } if (!this.vectorReady) { this.vectorReady = this.withTimeout( this.loadVectorExtension(), @@ -651,7 +677,9 @@ export class MemoryIndexManager { } private async loadVectorExtension(): Promise { - if (this.vector.available !== null) return this.vector.available; + if (this.vector.available !== null) { + return this.vector.available; + } if (!this.vector.enabled) { this.vector.available = false; return false; @@ -661,7 +689,9 @@ export class MemoryIndexManager { ? resolveUserPath(this.vector.extensionPath) : undefined; const loaded = await loadSqliteVecExtension({ db: this.db, extensionPath: resolvedPath }); - if (!loaded.ok) throw new Error(loaded.error ?? "unknown sqlite-vec load error"); + if (!loaded.ok) { + throw new Error(loaded.error ?? "unknown sqlite-vec load error"); + } this.vector.extensionPath = loaded.extensionPath; this.vector.available = true; return true; @@ -675,7 +705,9 @@ export class MemoryIndexManager { } private ensureVectorTable(dimensions: number): void { - if (this.vector.dims === dimensions) return; + if (this.vector.dims === dimensions) { + return; + } if (this.vector.dims && this.vector.dims !== dimensions) { this.dropVectorTable(); } @@ -699,7 +731,9 @@ export class MemoryIndexManager { private buildSourceFilter(alias?: string): { sql: string; params: MemorySource[] } { const sources = Array.from(this.sources); - if (sources.length === 0) return { sql: "", params: [] }; + if (sources.length === 0) { + return { sql: "", params: [] }; + } const column = alias ? `${alias}.source` : "source"; const placeholders = sources.map(() => "?").join(", "); return { sql: ` AND ${column} IN (${placeholders})`, params: sources }; @@ -718,7 +752,9 @@ export class MemoryIndexManager { } private seedEmbeddingCache(sourceDb: DatabaseSync): void { - if (!this.cache.enabled) return; + if (!this.cache.enabled) { + return; + } try { const rows = sourceDb .prepare( @@ -733,7 +769,9 @@ export class MemoryIndexManager { dims: number | null; updated_at: number; }>; - if (!rows.length) return; + if (!rows.length) { + return; + } const insert = this.db.prepare( `INSERT INTO ${EMBEDDING_CACHE_TABLE} (provider, model, provider_key, hash, embedding, dims, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?) @@ -810,7 +848,9 @@ export class MemoryIndexManager { } private ensureWatcher() { - if (!this.sources.has("memory") || !this.settings.sync.watch || this.watcher) return; + if (!this.sources.has("memory") || !this.settings.sync.watch || this.watcher) { + return; + } const additionalPaths = normalizeExtraMemoryPaths(this.workspaceDir, this.settings.extraPaths) .map((entry) => { try { @@ -844,18 +884,26 @@ export class MemoryIndexManager { } private ensureSessionListener() { - if (!this.sources.has("sessions") || this.sessionUnsubscribe) return; + if (!this.sources.has("sessions") || this.sessionUnsubscribe) { + return; + } this.sessionUnsubscribe = onSessionTranscriptUpdate((update) => { - if (this.closed) return; + if (this.closed) { + return; + } const sessionFile = update.sessionFile; - if (!this.isSessionFileForAgent(sessionFile)) return; + if (!this.isSessionFileForAgent(sessionFile)) { + return; + } this.scheduleSessionDirty(sessionFile); }); } private scheduleSessionDirty(sessionFile: string) { this.sessionPendingFiles.add(sessionFile); - if (this.sessionWatchTimer) return; + if (this.sessionWatchTimer) { + return; + } this.sessionWatchTimer = setTimeout(() => { this.sessionWatchTimer = null; void this.processSessionDeltaBatch().catch((err) => { @@ -865,13 +913,17 @@ export class MemoryIndexManager { } private async processSessionDeltaBatch(): Promise { - if (this.sessionPendingFiles.size === 0) return; + if (this.sessionPendingFiles.size === 0) { + return; + } const pending = Array.from(this.sessionPendingFiles); this.sessionPendingFiles.clear(); let shouldSync = false; for (const sessionFile of pending) { const delta = await this.updateSessionDelta(sessionFile); - if (!delta) continue; + if (!delta) { + continue; + } const bytesThreshold = delta.deltaBytes; const messagesThreshold = delta.deltaMessages; const bytesHit = @@ -880,7 +932,9 @@ export class MemoryIndexManager { messagesThreshold <= 0 ? delta.pendingMessages > 0 : delta.pendingMessages >= messagesThreshold; - if (!bytesHit && !messagesHit) continue; + if (!bytesHit && !messagesHit) { + continue; + } this.sessionsDirtyFiles.add(sessionFile); this.sessionsDirty = true; delta.pendingBytes = @@ -903,7 +957,9 @@ export class MemoryIndexManager { pendingMessages: number; } | null> { const thresholds = this.settings.sync.sessions; - if (!thresholds) return null; + if (!thresholds) { + return null; + } let stat: { size: number }; try { stat = await fs.stat(sessionFile); @@ -954,7 +1010,9 @@ export class MemoryIndexManager { } private async countNewlines(absPath: string, start: number, end: number): Promise { - if (end <= start) return 0; + if (end <= start) { + return 0; + } const handle = await fs.open(absPath, "r"); try { let offset = start; @@ -963,9 +1021,13 @@ export class MemoryIndexManager { while (offset < end) { const toRead = Math.min(buffer.length, end - offset); const { bytesRead } = await handle.read(buffer, 0, toRead, offset); - if (bytesRead <= 0) break; + if (bytesRead <= 0) { + break; + } for (let i = 0; i < bytesRead; i += 1) { - if (buffer[i] === 10) count += 1; + if (buffer[i] === 10) { + count += 1; + } } offset += bytesRead; } @@ -977,14 +1039,18 @@ export class MemoryIndexManager { private resetSessionDelta(absPath: string, size: number): void { const state = this.sessionDeltas.get(absPath); - if (!state) return; + if (!state) { + return; + } state.lastSize = size; state.pendingBytes = 0; state.pendingMessages = 0; } private isSessionFileForAgent(sessionFile: string): boolean { - if (!sessionFile) return false; + if (!sessionFile) { + return false; + } const sessionsDir = resolveSessionTranscriptsDirForAgent(this.agentId); const resolvedFile = path.resolve(sessionFile); const resolvedDir = path.resolve(sessionsDir); @@ -993,7 +1059,9 @@ export class MemoryIndexManager { private ensureIntervalSync() { const minutes = this.settings.sync.intervalMinutes; - if (!minutes || minutes <= 0 || this.intervalTimer) return; + if (!minutes || minutes <= 0 || this.intervalTimer) { + return; + } const ms = minutes * 60 * 1000; this.intervalTimer = setInterval(() => { void this.sync({ reason: "interval" }).catch((err) => { @@ -1003,8 +1071,12 @@ export class MemoryIndexManager { } private scheduleWatchSync() { - if (!this.sources.has("memory") || !this.settings.sync.watch) return; - if (this.watchTimer) clearTimeout(this.watchTimer); + if (!this.sources.has("memory") || !this.settings.sync.watch) { + return; + } + if (this.watchTimer) { + clearTimeout(this.watchTimer); + } this.watchTimer = setTimeout(() => { this.watchTimer = null; void this.sync({ reason: "watch" }).catch((err) => { @@ -1017,11 +1089,19 @@ export class MemoryIndexManager { params?: { reason?: string; force?: boolean }, needsFullReindex = false, ) { - if (!this.sources.has("sessions")) return false; - if (params?.force) return true; + if (!this.sources.has("sessions")) { + return false; + } + if (params?.force) { + return true; + } const reason = params?.reason; - if (reason === "session-start" || reason === "watch") return false; - if (needsFullReindex) return true; + if (reason === "session-start" || reason === "watch") { + return false; + } + if (needsFullReindex) { + return true; + } return this.sessionsDirty && this.sessionsDirtyFiles.size > 0; } @@ -1078,7 +1158,9 @@ export class MemoryIndexManager { .prepare(`SELECT path FROM files WHERE source = ?`) .all("memory") as Array<{ path: string }>; for (const stale of staleRows) { - if (activePaths.has(stale.path)) continue; + if (activePaths.has(stale.path)) { + continue; + } this.db.prepare(`DELETE FROM files WHERE path = ? AND source = ?`).run(stale.path, "memory"); try { this.db @@ -1173,7 +1255,9 @@ export class MemoryIndexManager { .prepare(`SELECT path FROM files WHERE source = ?`) .all("sessions") as Array<{ path: string }>; for (const stale of staleRows) { - if (activePaths.has(stale.path)) continue; + if (activePaths.has(stale.path)) { + continue; + } this.db .prepare(`DELETE FROM files WHERE path = ? AND source = ?`) .run(stale.path, "sessions"); @@ -1205,7 +1289,9 @@ export class MemoryIndexManager { total: 0, label: undefined, report: (update) => { - if (update.label) state.label = update.label; + if (update.label) { + state.label = update.label; + } const label = update.total > 0 && state.label ? `${state.label} ${update.completed}/${update.total}` @@ -1316,8 +1402,12 @@ export class MemoryIndexManager { private async activateFallbackProvider(reason: string): Promise { const fallback = this.settings.fallback; - if (!fallback || fallback === "none" || fallback === this.provider.id) return false; - if (this.fallbackFrom) return false; + if (!fallback || fallback === "none" || fallback === this.provider.id) { + return false; + } + if (this.fallbackFrom) { + return false; + } const fallbackFrom = this.provider.id as "openai" | "gemini" | "local"; const fallbackModel = @@ -1469,7 +1559,9 @@ export class MemoryIndexManager { const row = this.db.prepare(`SELECT value FROM meta WHERE key = ?`).get(META_KEY) as | { value: string } | undefined; - if (!row?.value) return null; + if (!row?.value) { + return null; + } try { return JSON.parse(row.value) as MemoryIndexMeta; } catch { @@ -1516,16 +1608,26 @@ export class MemoryIndexManager { const normalized = this.normalizeSessionText(content); return normalized ? normalized : null; } - if (!Array.isArray(content)) return null; + if (!Array.isArray(content)) { + return null; + } const parts: string[] = []; for (const block of content) { - if (!block || typeof block !== "object") continue; + if (!block || typeof block !== "object") { + continue; + } const record = block as { type?: unknown; text?: unknown }; - if (record.type !== "text" || typeof record.text !== "string") continue; + if (record.type !== "text" || typeof record.text !== "string") { + continue; + } const normalized = this.normalizeSessionText(record.text); - if (normalized) parts.push(normalized); + if (normalized) { + parts.push(normalized); + } + } + if (parts.length === 0) { + return null; } - if (parts.length === 0) return null; return parts.join(" "); } @@ -1536,7 +1638,9 @@ export class MemoryIndexManager { const lines = raw.split("\n"); const collected: string[] = []; for (const line of lines) { - if (!line.trim()) continue; + if (!line.trim()) { + continue; + } let record: unknown; try { record = JSON.parse(line); @@ -1553,10 +1657,16 @@ export class MemoryIndexManager { const message = (record as { message?: unknown }).message as | { role?: unknown; content?: unknown } | undefined; - if (!message || typeof message.role !== "string") continue; - if (message.role !== "user" && message.role !== "assistant") continue; + if (!message || typeof message.role !== "string") { + continue; + } + if (message.role !== "user" && message.role !== "assistant") { + continue; + } const text = this.extractSessionText(message.content); - if (!text) continue; + if (!text) { + continue; + } const label = message.role === "user" ? "User" : "Assistant"; collected.push(`${label}: ${text}`); } @@ -1576,7 +1686,9 @@ export class MemoryIndexManager { } private estimateEmbeddingTokens(text: string): number { - if (!text) return 0; + if (!text) { + return 0; + } return Math.ceil(text.length / EMBEDDING_APPROX_CHARS_PER_TOKEN); } @@ -1609,17 +1721,27 @@ export class MemoryIndexManager { } private loadEmbeddingCache(hashes: string[]): Map { - if (!this.cache.enabled) return new Map(); - if (hashes.length === 0) return new Map(); + if (!this.cache.enabled) { + return new Map(); + } + if (hashes.length === 0) { + return new Map(); + } const unique: string[] = []; const seen = new Set(); for (const hash of hashes) { - if (!hash) continue; - if (seen.has(hash)) continue; + if (!hash) { + continue; + } + if (seen.has(hash)) { + continue; + } seen.add(hash); unique.push(hash); } - if (unique.length === 0) return new Map(); + if (unique.length === 0) { + return new Map(); + } const out = new Map(); const baseParams = [this.provider.id, this.provider.model, this.providerKey]; @@ -1641,8 +1763,12 @@ export class MemoryIndexManager { } private upsertEmbeddingCache(entries: Array<{ hash: string; embedding: number[] }>): void { - if (!this.cache.enabled) return; - if (entries.length === 0) return; + if (!this.cache.enabled) { + return; + } + if (entries.length === 0) { + return; + } const now = Date.now(); const stmt = this.db.prepare( `INSERT INTO ${EMBEDDING_CACHE_TABLE} (provider, model, provider_key, hash, embedding, dims, updated_at)\n` + @@ -1667,14 +1793,20 @@ export class MemoryIndexManager { } private pruneEmbeddingCacheIfNeeded(): void { - if (!this.cache.enabled) return; + if (!this.cache.enabled) { + return; + } const max = this.cache.maxEntries; - if (!max || max <= 0) return; + if (!max || max <= 0) { + return; + } const row = this.db.prepare(`SELECT COUNT(*) as c FROM ${EMBEDDING_CACHE_TABLE}`).get() as | { c: number } | undefined; const count = row?.c ?? 0; - if (count <= max) return; + if (count <= max) { + return; + } const excess = count - max; this.db .prepare( @@ -1689,7 +1821,9 @@ export class MemoryIndexManager { } private async embedChunksInBatches(chunks: MemoryChunk[]): Promise { - if (chunks.length === 0) return []; + if (chunks.length === 0) { + return []; + } const cached = this.loadEmbeddingCache(chunks.map((chunk) => chunk.hash)); const embeddings: number[][] = Array.from({ length: chunks.length }, () => []); const missing: Array<{ index: number; chunk: MemoryChunk }> = []; @@ -1704,7 +1838,9 @@ export class MemoryIndexManager { } } - if (missing.length === 0) return embeddings; + if (missing.length === 0) { + return embeddings; + } const missingChunks = missing.map((m) => m.chunk); const batches = this.buildEmbeddingBatches(missingChunks); @@ -1784,7 +1920,9 @@ export class MemoryIndexManager { if (!openAi) { return this.embedChunksInBatches(chunks); } - if (chunks.length === 0) return []; + if (chunks.length === 0) { + return []; + } const cached = this.loadEmbeddingCache(chunks.map((chunk) => chunk.hash)); const embeddings: number[][] = Array.from({ length: chunks.length }, () => []); const missing: Array<{ index: number; chunk: MemoryChunk }> = []; @@ -1799,7 +1937,9 @@ export class MemoryIndexManager { } } - if (missing.length === 0) return embeddings; + if (missing.length === 0) { + return embeddings; + } const requests: OpenAiBatchRequest[] = []; const mapping = new Map(); @@ -1834,13 +1974,17 @@ export class MemoryIndexManager { }), fallback: async () => await this.embedChunksInBatches(chunks), }); - if (Array.isArray(batchResult)) return batchResult; + if (Array.isArray(batchResult)) { + return batchResult; + } const byCustomId = batchResult; const toCache: Array<{ hash: string; embedding: number[] }> = []; for (const [customId, embedding] of byCustomId.entries()) { const mapped = mapping.get(customId); - if (!mapped) continue; + if (!mapped) { + continue; + } embeddings[mapped.index] = embedding; toCache.push({ hash: mapped.hash, embedding }); } @@ -1857,7 +2001,9 @@ export class MemoryIndexManager { if (!gemini) { return this.embedChunksInBatches(chunks); } - if (chunks.length === 0) return []; + if (chunks.length === 0) { + return []; + } const cached = this.loadEmbeddingCache(chunks.map((chunk) => chunk.hash)); const embeddings: number[][] = Array.from({ length: chunks.length }, () => []); const missing: Array<{ index: number; chunk: MemoryChunk }> = []; @@ -1872,7 +2018,9 @@ export class MemoryIndexManager { } } - if (missing.length === 0) return embeddings; + if (missing.length === 0) { + return embeddings; + } const requests: GeminiBatchRequest[] = []; const mapping = new Map(); @@ -1904,13 +2052,17 @@ export class MemoryIndexManager { }), fallback: async () => await this.embedChunksInBatches(chunks), }); - if (Array.isArray(batchResult)) return batchResult; + if (Array.isArray(batchResult)) { + return batchResult; + } const byCustomId = batchResult; const toCache: Array<{ hash: string; embedding: number[] }> = []; for (const [customId, embedding] of byCustomId.entries()) { const mapped = mapping.get(customId); - if (!mapped) continue; + if (!mapped) { + continue; + } embeddings[mapped.index] = embedding; toCache.push({ hash: mapped.hash, embedding }); } @@ -1919,7 +2071,9 @@ export class MemoryIndexManager { } private async embedBatchWithRetry(texts: string[]): Promise { - if (texts.length === 0) return []; + if (texts.length === 0) { + return []; + } let attempt = 0; let delayMs = EMBEDDING_RETRY_BASE_DELAY_MS; while (true) { @@ -1981,7 +2135,9 @@ export class MemoryIndexManager { timeoutMs: number, message: string, ): Promise { - if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return await promise; + if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) { + return await promise; + } let timer: NodeJS.Timeout | null = null; const timeoutPromise = new Promise((_, reject) => { timer = setTimeout(() => reject(new Error(message)), timeoutMs); @@ -1989,12 +2145,16 @@ export class MemoryIndexManager { try { return (await Promise.race([promise, timeoutPromise])) as T; } finally { - if (timer) clearTimeout(timer); + if (timer) { + clearTimeout(timer); + } } } private async runWithConcurrency(tasks: Array<() => Promise>, limit: number): Promise { - if (tasks.length === 0) return []; + if (tasks.length === 0) { + return []; + } const resolvedLimit = Math.max(1, Math.min(limit, tasks.length)); const results: T[] = Array.from({ length: tasks.length }); let next = 0; @@ -2002,10 +2162,14 @@ export class MemoryIndexManager { const workers = Array.from({ length: resolvedLimit }, async () => { while (true) { - if (firstError) return; + if (firstError) { + return; + } const index = next; next += 1; - if (index >= tasks.length) return; + if (index >= tasks.length) { + return; + } try { results[index] = await tasks[index](); } catch (err) { @@ -2016,7 +2180,9 @@ export class MemoryIndexManager { }); await Promise.allSettled(workers); - if (firstError) throw firstError; + if (firstError) { + throw firstError; + } return results; } diff --git a/src/memory/manager.vector-dedupe.test.ts b/src/memory/manager.vector-dedupe.test.ts index b867d0f740..6778f92dec 100644 --- a/src/memory/manager.vector-dedupe.test.ts +++ b/src/memory/manager.vector-dedupe.test.ts @@ -60,7 +60,9 @@ describe("memory vector dedupe", () => { const result = await getMemorySearchManager({ cfg, agentId: "main" }); expect(result.manager).not.toBeNull(); - if (!result.manager) throw new Error("manager missing"); + if (!result.manager) { + throw new Error("manager missing"); + } manager = result.manager; const db = ( diff --git a/src/memory/memory-schema.ts b/src/memory/memory-schema.ts index 4667b428ba..a537c35f17 100644 --- a/src/memory/memory-schema.ts +++ b/src/memory/memory-schema.ts @@ -89,6 +89,8 @@ function ensureColumn( definition: string, ): void { const rows = db.prepare(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>; - if (rows.some((row) => row.name === column)) return; + if (rows.some((row) => row.name === column)) { + return; + } db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`); } diff --git a/src/memory/session-files.ts b/src/memory/session-files.ts index 82f8c1e09b..8a953ffcaf 100644 --- a/src/memory/session-files.ts +++ b/src/memory/session-files.ts @@ -46,16 +46,26 @@ export function extractSessionText(content: unknown): string | null { const normalized = normalizeSessionText(content); return normalized ? normalized : null; } - if (!Array.isArray(content)) return null; + if (!Array.isArray(content)) { + return null; + } const parts: string[] = []; for (const block of content) { - if (!block || typeof block !== "object") continue; + if (!block || typeof block !== "object") { + continue; + } const record = block as { type?: unknown; text?: unknown }; - if (record.type !== "text" || typeof record.text !== "string") continue; + if (record.type !== "text" || typeof record.text !== "string") { + continue; + } const normalized = normalizeSessionText(record.text); - if (normalized) parts.push(normalized); + if (normalized) { + parts.push(normalized); + } + } + if (parts.length === 0) { + return null; } - if (parts.length === 0) return null; return parts.join(" "); } @@ -66,7 +76,9 @@ export async function buildSessionEntry(absPath: string): Promise; for (const stale of staleRows) { - if (activePaths.has(stale.path)) continue; + if (activePaths.has(stale.path)) { + continue; + } params.db.prepare(`DELETE FROM files WHERE path = ? AND source = ?`).run(stale.path, "memory"); try { params.db diff --git a/src/memory/sync-session-files.ts b/src/memory/sync-session-files.ts index e2aba7101e..b55108a7f0 100644 --- a/src/memory/sync-session-files.ts +++ b/src/memory/sync-session-files.ts @@ -105,7 +105,9 @@ export async function syncSessionFiles(params: { .prepare(`SELECT path FROM files WHERE source = ?`) .all("sessions") as Array<{ path: string }>; for (const stale of staleRows) { - if (activePaths.has(stale.path)) continue; + if (activePaths.has(stale.path)) { + continue; + } params.db .prepare(`DELETE FROM files WHERE path = ? AND source = ?`) .run(stale.path, "sessions"); diff --git a/src/node-host/runner.ts b/src/node-host/runner.ts index b07c52f7ff..e6865e38f8 100644 --- a/src/node-host/runner.ts +++ b/src/node-host/runner.ts @@ -199,16 +199,22 @@ class SkillBinsCache { function sanitizeEnv( overrides?: Record | null, ): Record | undefined { - if (!overrides) return undefined; + if (!overrides) { + return undefined; + } const merged = { ...process.env } as Record; const basePath = process.env.PATH ?? DEFAULT_NODE_PATH; for (const [rawKey, value] of Object.entries(overrides)) { const key = rawKey.trim(); - if (!key) continue; + if (!key) { + continue; + } const upper = key.toUpperCase(); if (upper === "PATH") { const trimmed = value.trim(); - if (!trimmed) continue; + if (!trimmed) { + continue; + } if (!basePath || trimmed === basePath) { merged[key] = trimmed; continue; @@ -219,8 +225,12 @@ function sanitizeEnv( } continue; } - if (blockedEnvKeys.has(upper)) continue; - if (blockedEnvPrefixes.some((prefix) => upper.startsWith(prefix))) continue; + if (blockedEnvKeys.has(upper)) { + continue; + } + if (blockedEnvPrefixes.some((prefix) => upper.startsWith(prefix))) { + continue; + } merged[key] = value; } return merged; @@ -241,7 +251,9 @@ function resolveBrowserProxyConfig() { let browserControlReady: Promise | null = null; async function ensureBrowserControlService(): Promise { - if (browserControlReady) return browserControlReady; + if (browserControlReady) { + return browserControlReady; + } browserControlReady = (async () => { const cfg = loadConfig(); const resolved = resolveBrowserConfig(cfg.browser, cfg); @@ -249,7 +261,9 @@ async function ensureBrowserControlService(): Promise { throw new Error("browser control disabled"); } const started = await startBrowserControlServiceFromConfig(); - if (!started) throw new Error("browser control disabled"); + if (!started) { + throw new Error("browser control disabled"); + } })(); return browserControlReady; } @@ -259,7 +273,9 @@ async function withTimeout(promise: Promise, timeoutMs?: number, label?: s typeof timeoutMs === "number" && Number.isFinite(timeoutMs) ? Math.max(1, Math.floor(timeoutMs)) : undefined; - if (!resolved) return await promise; + if (!resolved) { + return await promise; + } let timer: ReturnType | undefined; const timeoutPromise = new Promise((_, reject) => { timer = setTimeout(() => { @@ -269,14 +285,20 @@ async function withTimeout(promise: Promise, timeoutMs?: number, label?: s try { return await Promise.race([promise, timeoutPromise]); } finally { - if (timer) clearTimeout(timer); + if (timer) { + clearTimeout(timer); + } } } function isProfileAllowed(params: { allowProfiles: string[]; profile?: string | null }) { const { allowProfiles, profile } = params; - if (!allowProfiles.length) return true; - if (!profile) return false; + if (!allowProfiles.length) { + return true; + } + if (!profile) { + return false; + } return allowProfiles.includes(profile.trim()); } @@ -284,20 +306,30 @@ function collectBrowserProxyPaths(payload: unknown): string[] { const paths = new Set(); const obj = typeof payload === "object" && payload !== null ? (payload as Record) : null; - if (!obj) return []; - if (typeof obj.path === "string" && obj.path.trim()) paths.add(obj.path.trim()); - if (typeof obj.imagePath === "string" && obj.imagePath.trim()) paths.add(obj.imagePath.trim()); + if (!obj) { + return []; + } + if (typeof obj.path === "string" && obj.path.trim()) { + paths.add(obj.path.trim()); + } + if (typeof obj.imagePath === "string" && obj.imagePath.trim()) { + paths.add(obj.imagePath.trim()); + } const download = obj.download; if (download && typeof download === "object") { const dlPath = (download as Record).path; - if (typeof dlPath === "string" && dlPath.trim()) paths.add(dlPath.trim()); + if (typeof dlPath === "string" && dlPath.trim()) { + paths.add(dlPath.trim()); + } } return [...paths]; } async function readBrowserProxyFile(filePath: string): Promise { const stat = await fsPromises.stat(filePath).catch(() => null); - if (!stat || !stat.isFile()) return null; + if (!stat || !stat.isFile()) { + return null; + } if (stat.size > BROWSER_PROXY_MAX_FILE_BYTES) { throw new Error( `browser proxy file exceeds ${Math.round(BROWSER_PROXY_MAX_FILE_BYTES / (1024 * 1024))}MB`, @@ -312,16 +344,22 @@ function formatCommand(argv: string[]): string { return argv .map((arg) => { const trimmed = arg.trim(); - if (!trimmed) return '""'; + if (!trimmed) { + return '""'; + } const needsQuotes = /\s|"/.test(trimmed); - if (!needsQuotes) return trimmed; + if (!needsQuotes) { + return trimmed; + } return `"${trimmed.replace(/"/g, '\\"')}"`; }) .join(" "); } function truncateOutput(raw: string, maxChars: number): { text: string; truncated: boolean } { - if (raw.length <= maxChars) return { text: raw, truncated: false }; + if (raw.length <= maxChars) { + return { text: raw, truncated: false }; + } return { text: `... (truncated) ${raw.slice(raw.length - maxChars)}`, truncated: true }; } @@ -337,7 +375,9 @@ function requireExecApprovalsBaseHash( params: SystemExecApprovalsSetParams, snapshot: ExecApprovalsSnapshot, ) { - if (!snapshot.exists) return; + if (!snapshot.exists) { + return; + } if (!snapshot.hash) { throw new Error("INVALID_REQUEST: exec approvals base hash unavailable; reload and retry"); } @@ -380,9 +420,14 @@ async function runCommand( const slice = chunk.length > remaining ? chunk.subarray(0, remaining) : chunk; const str = slice.toString("utf8"); outputLen += slice.length; - if (target === "stdout") stdout += str; - else stderr += str; - if (chunk.length > remaining) truncated = true; + if (target === "stdout") { + stdout += str; + } else { + stderr += str; + } + if (chunk.length > remaining) { + truncated = true; + } }; child.stdout?.on("data", (chunk) => onChunk(chunk as Buffer, "stdout")); @@ -401,9 +446,13 @@ async function runCommand( } const finalize = (exitCode?: number, error?: string | null) => { - if (settled) return; + if (settled) { + return; + } settled = true; - if (timer) clearTimeout(timer); + if (timer) { + clearTimeout(timer); + } resolve({ exitCode, timedOut, @@ -437,13 +486,17 @@ function resolveEnvPath(env?: Record): string[] { function ensureNodePathEnv(): string { ensureOpenClawCliOnPath({ pathEnv: process.env.PATH ?? "" }); const current = process.env.PATH ?? ""; - if (current.trim()) return current; + if (current.trim()) { + return current; + } process.env.PATH = DEFAULT_NODE_PATH; return DEFAULT_NODE_PATH; } function resolveExecutable(bin: string, env?: Record) { - if (bin.includes("/") || bin.includes("\\")) return null; + if (bin.includes("/") || bin.includes("\\")) { + return null; + } const extensions = process.platform === "win32" ? (process.env.PATHEXT ?? process.env.PathExt ?? ".EXE;.CMD;.BAT;.COM") @@ -453,7 +506,9 @@ function resolveExecutable(bin: string, env?: Record) { for (const dir of resolveEnvPath(env)) { for (const ext of extensions) { const candidate = path.join(dir, bin + ext); - if (fs.existsSync(candidate)) return candidate; + if (fs.existsSync(candidate)) { + return candidate; + } } } return null; @@ -464,15 +519,21 @@ async function handleSystemWhich(params: SystemWhichParams, env?: Record = {}; for (const bin of bins) { const path = resolveExecutable(bin, env); - if (path) found[bin] = path; + if (path) { + found[bin] = path; + } } return { bins: found }; } function buildExecEventPayload(payload: ExecEventPayload): ExecEventPayload { - if (!payload.output) return payload; + if (!payload.output) { + return payload; + } const trimmed = payload.output.trim(); - if (!trimmed) return payload; + if (!trimmed) { + return payload; + } const { text } = truncateOutput(trimmed, OUTPUT_EVENT_TAIL); return { ...payload, output: text }; } @@ -552,9 +613,13 @@ export async function runNodeHost(opts: NodeHostRunOptions): Promise { deviceIdentity: loadOrCreateDeviceIdentity(), tlsFingerprint: gateway.tlsFingerprint, onEvent: (evt) => { - if (evt.event !== "node.invoke.request") return; + if (evt.event !== "node.invoke.request") { + return; + } const payload = coerceNodeInvokePayload(evt.payload); - if (!payload) return; + if (!payload) { + return; + } void handleInvoke(payload, client, skillBins); }, onConnectError: (err) => { @@ -711,7 +776,9 @@ async function handleInvoke( } const rawQuery = params.query ?? {}; for (const [key, value] of Object.entries(rawQuery)) { - if (value === undefined || value === null) continue; + if (value === undefined || value === null) { + continue; + } query[key] = typeof value === "string" ? value : String(value); } const dispatcher = createBrowserRouteDispatcher(createBrowserControlContext()); @@ -738,7 +805,9 @@ async function handleInvoke( typeof result === "object" && result !== null ? (result as Record) : {}; const profiles = Array.isArray(obj.profiles) ? obj.profiles : []; obj.profiles = profiles.filter((entry) => { - if (!entry || typeof entry !== "object") return false; + if (!entry || typeof entry !== "object") { + return false; + } const name = (entry as Record).name; return typeof name === "string" && allowedProfiles.includes(name); }); @@ -761,7 +830,9 @@ async function handleInvoke( } }), ); - if (loaded.length > 0) files = loaded; + if (loaded.length > 0) { + files = loaded; + } } const payload: BrowserProxyResult = files ? { result, files } : { result }; await sendInvokeResult(client, frame, { @@ -996,7 +1067,9 @@ async function handleInvoke( if (analysisOk) { for (const segment of segments) { const pattern = segment.resolution?.resolvedPath ?? ""; - if (pattern) addAllowlistEntry(approvals.file, agentId, pattern); + if (pattern) { + addAllowlistEntry(approvals.file, agentId, pattern); + } } } } @@ -1023,7 +1096,9 @@ async function handleInvoke( if (allowlistMatches.length > 0) { const seen = new Set(); for (const match of allowlistMatches) { - if (!match?.pattern || seen.has(match.pattern)) continue; + if (!match?.pattern || seen.has(match.pattern)) { + continue; + } seen.add(match.pattern); recordAllowlistUse( approvals.file, @@ -1105,12 +1180,16 @@ function decodeParams(raw?: string | null): T { } function coerceNodeInvokePayload(payload: unknown): NodeInvokeRequestPayload | null { - if (!payload || typeof payload !== "object") return null; + if (!payload || typeof payload !== "object") { + return null; + } const obj = payload as Record; const id = typeof obj.id === "string" ? obj.id.trim() : ""; const nodeId = typeof obj.nodeId === "string" ? obj.nodeId.trim() : ""; const command = typeof obj.command === "string" ? obj.command.trim() : ""; - if (!id || !nodeId || !command) return null; + if (!id || !nodeId || !command) { + return null; + } const paramsJSON = typeof obj.paramsJSON === "string" ? obj.paramsJSON diff --git a/src/pairing/pairing-store.test.ts b/src/pairing/pairing-store.test.ts index 8c0c98fc62..f4c47b7131 100644 --- a/src/pairing/pairing-store.test.ts +++ b/src/pairing/pairing-store.test.ts @@ -15,8 +15,11 @@ async function withTempStateDir(fn: (stateDir: string) => Promise) { try { return await fn(dir); } finally { - if (previous === undefined) delete process.env.OPENCLAW_STATE_DIR; - else process.env.OPENCLAW_STATE_DIR = previous; + if (previous === undefined) { + delete process.env.OPENCLAW_STATE_DIR; + } else { + process.env.OPENCLAW_STATE_DIR = previous; + } await fs.rm(dir, { recursive: true, force: true }); } } diff --git a/src/pairing/pairing-store.ts b/src/pairing/pairing-store.ts index d71a2be13c..c6fd63a284 100644 --- a/src/pairing/pairing-store.ts +++ b/src/pairing/pairing-store.ts @@ -51,9 +51,13 @@ function resolveCredentialsDir(env: NodeJS.ProcessEnv = process.env): string { /** Sanitize channel ID for use in filenames (prevent path traversal). */ function safeChannelKey(channel: PairingChannel): string { const raw = String(channel).trim().toLowerCase(); - if (!raw) throw new Error("invalid pairing channel"); + if (!raw) { + throw new Error("invalid pairing channel"); + } const safe = raw.replace(/[\\/:*?"<>|]/g, "_").replace(/\.\./g, "_"); - if (!safe || safe === "_") throw new Error("invalid pairing channel"); + if (!safe || safe === "_") { + throw new Error("invalid pairing channel"); + } return safe; } @@ -83,11 +87,15 @@ async function readJsonFile( try { const raw = await fs.promises.readFile(filePath, "utf-8"); const parsed = safeParseJson(raw); - if (parsed == null) return { value: fallback, exists: true }; + if (parsed == null) { + return { value: fallback, exists: true }; + } return { value: parsed, exists: true }; } catch (err) { const code = (err as { code?: string }).code; - if (code === "ENOENT") return { value: fallback, exists: false }; + if (code === "ENOENT") { + return { value: fallback, exists: false }; + } return { value: fallback, exists: false }; } } @@ -133,15 +141,21 @@ async function withFileLock( } function parseTimestamp(value: string | undefined): number | null { - if (!value) return null; + if (!value) { + return null; + } const parsed = Date.parse(value); - if (!Number.isFinite(parsed)) return null; + if (!Number.isFinite(parsed)) { + return null; + } return parsed; } function isExpired(entry: PairingRequest, nowMs: number): boolean { const createdAt = parseTimestamp(entry.createdAt); - if (!createdAt) return true; + if (!createdAt) { + return true; + } return nowMs - createdAt > PAIRING_PENDING_TTL_MS; } @@ -183,7 +197,9 @@ function randomCode(): string { function generateUniqueCode(existing: Set): string { for (let attempt = 0; attempt < 500; attempt += 1) { const code = randomCode(); - if (!existing.has(code)) return code; + if (!existing.has(code)) { + return code; + } } throw new Error("failed to generate unique pairing code"); } @@ -194,8 +210,12 @@ function normalizeId(value: string | number): string { function normalizeAllowEntry(channel: PairingChannel, entry: string): string { const trimmed = entry.trim(); - if (!trimmed) return ""; - if (trimmed === "*") return ""; + if (!trimmed) { + return ""; + } + if (trimmed === "*") { + return ""; + } const adapter = getPairingAdapter(channel); const normalized = adapter?.normalizeAllowEntry ? adapter.normalizeAllowEntry(trimmed) : trimmed; return String(normalized).trim(); @@ -233,8 +253,12 @@ export async function addChannelAllowFromStoreEntry(params: { .map((v) => normalizeAllowEntry(params.channel, String(v))) .filter(Boolean); const normalized = normalizeAllowEntry(params.channel, normalizeId(params.entry)); - if (!normalized) return { changed: false, allowFrom: current }; - if (current.includes(normalized)) return { changed: false, allowFrom: current }; + if (!normalized) { + return { changed: false, allowFrom: current }; + } + if (current.includes(normalized)) { + return { changed: false, allowFrom: current }; + } const next = [...current, normalized]; await writeJsonFile(filePath, { version: 1, @@ -264,9 +288,13 @@ export async function removeChannelAllowFromStoreEntry(params: { .map((v) => normalizeAllowEntry(params.channel, String(v))) .filter(Boolean); const normalized = normalizeAllowEntry(params.channel, normalizeId(params.entry)); - if (!normalized) return { changed: false, allowFrom: current }; + if (!normalized) { + return { changed: false, allowFrom: current }; + } const next = current.filter((entry) => entry !== normalized); - if (next.length === current.length) return { changed: false, allowFrom: current }; + if (next.length === current.length) { + return { changed: false, allowFrom: current }; + } await writeJsonFile(filePath, { version: 1, allowFrom: next, @@ -423,7 +451,9 @@ export async function approveChannelPairingCode(params: { }): Promise<{ id: string; entry?: PairingRequest } | null> { const env = params.env ?? process.env; const code = params.code.trim().toUpperCase(); - if (!code) return null; + if (!code) { + return null; + } const filePath = resolvePairingPath(params.channel, env); return await withFileLock( @@ -448,7 +478,9 @@ export async function approveChannelPairingCode(params: { return null; } const entry = pruned[idx]; - if (!entry) return null; + if (!entry) { + return null; + } pruned.splice(idx, 1); await writeJsonFile(filePath, { version: 1, diff --git a/src/plugins/bundled-dir.ts b/src/plugins/bundled-dir.ts index 72c4e18980..4837ae59dc 100644 --- a/src/plugins/bundled-dir.ts +++ b/src/plugins/bundled-dir.ts @@ -4,13 +4,17 @@ import { fileURLToPath } from "node:url"; export function resolveBundledPluginsDir(): string | undefined { const override = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR?.trim(); - if (override) return override; + if (override) { + return override; + } // bun --compile: ship a sibling `extensions/` next to the executable. try { const execDir = path.dirname(process.execPath); const sibling = path.join(execDir, "extensions"); - if (fs.existsSync(sibling)) return sibling; + if (fs.existsSync(sibling)) { + return sibling; + } } catch { // ignore } @@ -20,9 +24,13 @@ export function resolveBundledPluginsDir(): string | undefined { let cursor = path.dirname(fileURLToPath(import.meta.url)); for (let i = 0; i < 6; i += 1) { const candidate = path.join(cursor, "extensions"); - if (fs.existsSync(candidate)) return candidate; + if (fs.existsSync(candidate)) { + return candidate; + } const parent = path.dirname(cursor); - if (parent === cursor) break; + if (parent === cursor) { + break; + } cursor = parent; } } catch { diff --git a/src/plugins/commands.ts b/src/plugins/commands.ts index 2fd5d9e039..fa7f328e2e 100644 --- a/src/plugins/commands.ts +++ b/src/plugins/commands.ts @@ -168,7 +168,9 @@ export function matchPluginCommand( commandBody: string, ): { command: RegisteredPluginCommand; args?: string } | null { const trimmed = commandBody.trim(); - if (!trimmed.startsWith("/")) return null; + if (!trimmed.startsWith("/")) { + return null; + } // Extract command name and args const spaceIndex = trimmed.indexOf(" "); @@ -178,10 +180,14 @@ export function matchPluginCommand( const key = commandName.toLowerCase(); const command = pluginCommands.get(key); - if (!command) return null; + if (!command) { + return null; + } // If command doesn't accept args but args were provided, don't match - if (args && !command.acceptsArgs) return null; + if (args && !command.acceptsArgs) { + return null; + } return { command, args: args || undefined }; } @@ -191,7 +197,9 @@ export function matchPluginCommand( * Removes control characters and enforces length limits. */ function sanitizeArgs(args: string | undefined): string | undefined { - if (!args) return undefined; + if (!args) { + return undefined; + } // Enforce length limit if (args.length > MAX_ARGS_LENGTH) { @@ -203,7 +211,9 @@ function sanitizeArgs(args: string | undefined): string | undefined { for (const char of args) { const code = char.charCodeAt(0); const isControl = (code <= 0x1f && code !== 0x09 && code !== 0x0a) || code === 0x7f; - if (!isControl) sanitized += char; + if (!isControl) { + sanitized += char; + } } return sanitized; } diff --git a/src/plugins/config-schema.ts b/src/plugins/config-schema.ts index f040e4b801..8a11854951 100644 --- a/src/plugins/config-schema.ts +++ b/src/plugins/config-schema.ts @@ -13,7 +13,9 @@ function error(message: string): SafeParseResult { export function emptyPluginConfigSchema(): OpenClawPluginConfigSchema { return { safeParse(value: unknown): SafeParseResult { - if (value === undefined) return { success: true, data: undefined }; + if (value === undefined) { + return { success: true, data: undefined }; + } if (!value || typeof value !== "object" || Array.isArray(value)) { return error("expected config object"); } diff --git a/src/plugins/config-state.ts b/src/plugins/config-state.ts index 9c424a23e2..fc6e2ce656 100644 --- a/src/plugins/config-state.ts +++ b/src/plugins/config-state.ts @@ -16,15 +16,23 @@ export type NormalizedPluginsConfig = { export const BUNDLED_ENABLED_BY_DEFAULT = new Set(); const normalizeList = (value: unknown): string[] => { - if (!Array.isArray(value)) return []; + if (!Array.isArray(value)) { + return []; + } return value.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean); }; const normalizeSlotValue = (value: unknown): string | null | undefined => { - if (typeof value !== "string") return undefined; + if (typeof value !== "string") { + return undefined; + } const trimmed = value.trim(); - if (!trimmed) return undefined; - if (trimmed.toLowerCase() === "none") return null; + if (!trimmed) { + return undefined; + } + if (trimmed.toLowerCase() === "none") { + return null; + } return trimmed; }; @@ -34,7 +42,9 @@ const normalizePluginEntries = (entries: unknown): NormalizedPluginsConfig["entr } const normalized: NormalizedPluginsConfig["entries"] = {}; for (const [key, value] of Object.entries(entries)) { - if (!key.trim()) continue; + if (!key.trim()) { + continue; + } if (!value || typeof value !== "object" || Array.isArray(value)) { normalized[key] = {}; continue; @@ -71,14 +81,27 @@ const hasExplicitMemoryEntry = (plugins?: OpenClawConfig["plugins"]) => Boolean(plugins?.entries && Object.prototype.hasOwnProperty.call(plugins.entries, "memory-core")); const hasExplicitPluginConfig = (plugins?: OpenClawConfig["plugins"]) => { - if (!plugins) return false; - if (typeof plugins.enabled === "boolean") return true; - if (Array.isArray(plugins.allow) && plugins.allow.length > 0) return true; - if (Array.isArray(plugins.deny) && plugins.deny.length > 0) return true; - if (plugins.load?.paths && Array.isArray(plugins.load.paths) && plugins.load.paths.length > 0) + if (!plugins) { + return false; + } + if (typeof plugins.enabled === "boolean") { return true; - if (plugins.slots && Object.keys(plugins.slots).length > 0) return true; - if (plugins.entries && Object.keys(plugins.entries).length > 0) return true; + } + if (Array.isArray(plugins.allow) && plugins.allow.length > 0) { + return true; + } + if (Array.isArray(plugins.deny) && plugins.deny.length > 0) { + return true; + } + if (plugins.load?.paths && Array.isArray(plugins.load.paths) && plugins.load.paths.length > 0) { + return true; + } + if (plugins.slots && Object.keys(plugins.slots).length > 0) { + return true; + } + if (plugins.entries && Object.keys(plugins.entries).length > 0) { + return true; + } return false; }; @@ -86,7 +109,9 @@ export function applyTestPluginDefaults( cfg: OpenClawConfig, env: NodeJS.ProcessEnv = process.env, ): OpenClawConfig { - if (!env.VITEST) return cfg; + if (!env.VITEST) { + return cfg; + } const plugins = cfg.plugins; const explicitConfig = hasExplicitPluginConfig(plugins); if (explicitConfig) { @@ -122,7 +147,9 @@ export function isTestDefaultMemorySlotDisabled( cfg: OpenClawConfig, env: NodeJS.ProcessEnv = process.env, ): boolean { - if (!env.VITEST) return false; + if (!env.VITEST) { + return false; + } const plugins = cfg.plugins; if (hasExplicitMemorySlot(plugins) || hasExplicitMemoryEntry(plugins)) { return false; @@ -169,7 +196,9 @@ export function resolveMemorySlotDecision(params: { slot: string | null | undefined; selectedId: string | null; }): { enabled: boolean; reason?: string; selected?: boolean } { - if (params.kind !== "memory") return { enabled: true }; + if (params.kind !== "memory") { + return { enabled: true }; + } if (params.slot === null) { return { enabled: false, reason: "memory slot disabled" }; } diff --git a/src/plugins/discovery.ts b/src/plugins/discovery.ts index d44c478a78..41b45d813a 100644 --- a/src/plugins/discovery.ts +++ b/src/plugins/discovery.ts @@ -32,13 +32,17 @@ export type PluginDiscoveryResult = { function isExtensionFile(filePath: string): boolean { const ext = path.extname(filePath); - if (!EXTENSION_EXTS.has(ext)) return false; + if (!EXTENSION_EXTS.has(ext)) { + return false; + } return !filePath.endsWith(".d.ts"); } function readPackageManifest(dir: string): PackageManifest | null { const manifestPath = path.join(dir, "package.json"); - if (!fs.existsSync(manifestPath)) return null; + if (!fs.existsSync(manifestPath)) { + return null; + } try { const raw = fs.readFileSync(manifestPath, "utf-8"); return JSON.parse(raw) as PackageManifest; @@ -49,7 +53,9 @@ function readPackageManifest(dir: string): PackageManifest | null { function resolvePackageExtensions(manifest: PackageManifest): string[] { const raw = getPackageManifestMetadata(manifest)?.extensions; - if (!Array.isArray(raw)) return []; + if (!Array.isArray(raw)) { + return []; + } return raw.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean); } @@ -60,7 +66,9 @@ function deriveIdHint(params: { }): string { const base = path.basename(params.filePath, path.extname(params.filePath)); const rawPackageName = params.packageName?.trim(); - if (!rawPackageName) return base; + if (!rawPackageName) { + return base; + } // Prefer the unscoped name so config keys stay stable even when the npm // package is scoped (example: @openclaw/voice-call -> voice-call). @@ -68,7 +76,9 @@ function deriveIdHint(params: { ? (rawPackageName.split("/").pop() ?? rawPackageName) : rawPackageName; - if (!params.hasMultipleExtensions) return unscoped; + if (!params.hasMultipleExtensions) { + return unscoped; + } return `${unscoped}/${base}`; } @@ -84,7 +94,9 @@ function addCandidate(params: { packageDir?: string; }) { const resolved = path.resolve(params.source); - if (params.seen.has(resolved)) return; + if (params.seen.has(resolved)) { + return; + } params.seen.add(resolved); const manifest = params.manifest ?? null; params.candidates.push({ @@ -109,7 +121,9 @@ function discoverInDirectory(params: { diagnostics: PluginDiagnostic[]; seen: Set; }) { - if (!fs.existsSync(params.dir)) return; + if (!fs.existsSync(params.dir)) { + return; + } let entries: fs.Dirent[] = []; try { entries = fs.readdirSync(params.dir, { withFileTypes: true }); @@ -125,7 +139,9 @@ function discoverInDirectory(params: { for (const entry of entries) { const fullPath = path.join(params.dir, entry.name); if (entry.isFile()) { - if (!isExtensionFile(fullPath)) continue; + if (!isExtensionFile(fullPath)) { + continue; + } addCandidate({ candidates: params.candidates, seen: params.seen, @@ -136,7 +152,9 @@ function discoverInDirectory(params: { workspaceDir: params.workspaceDir, }); } - if (!entry.isDirectory()) continue; + if (!entry.isDirectory()) { + continue; + } const manifest = readPackageManifest(fullPath); const extensions = manifest ? resolvePackageExtensions(manifest) : []; @@ -292,9 +310,13 @@ export function discoverOpenClawPlugins(params: { const extra = params.extraPaths ?? []; for (const extraPath of extra) { - if (typeof extraPath !== "string") continue; + if (typeof extraPath !== "string") { + continue; + } const trimmed = extraPath.trim(); - if (!trimmed) continue; + if (!trimmed) { + continue; + } discoverFromPath({ rawPath: trimmed, origin: "config", diff --git a/src/plugins/enable.ts b/src/plugins/enable.ts index f3833c5542..9f5cc47929 100644 --- a/src/plugins/enable.ts +++ b/src/plugins/enable.ts @@ -8,7 +8,9 @@ export type PluginEnableResult = { function ensureAllowlisted(cfg: OpenClawConfig, pluginId: string): OpenClawConfig { const allow = cfg.plugins?.allow; - if (!Array.isArray(allow) || allow.includes(pluginId)) return cfg; + if (!Array.isArray(allow) || allow.includes(pluginId)) { + return cfg; + } return { ...cfg, plugins: { diff --git a/src/plugins/hooks.ts b/src/plugins/hooks.ts index a7b5665011..987e789422 100644 --- a/src/plugins/hooks.ts +++ b/src/plugins/hooks.ts @@ -104,7 +104,9 @@ export function createHookRunner(registry: PluginRegistry, options: HookRunnerOp ctx: Parameters["handler"]>>[1], ): Promise { const hooks = getHooksForName(registry, hookName); - if (hooks.length === 0) return; + if (hooks.length === 0) { + return; + } logger?.debug?.(`[hooks] running ${hookName} (${hooks.length} handlers)`); @@ -135,7 +137,9 @@ export function createHookRunner(registry: PluginRegistry, options: HookRunnerOp mergeResults?: (accumulated: TResult | undefined, next: TResult) => TResult, ): Promise { const hooks = getHooksForName(registry, hookName); - if (hooks.length === 0) return undefined; + if (hooks.length === 0) { + return undefined; + } logger?.debug?.(`[hooks] running ${hookName} (${hooks.length} handlers, sequential)`); @@ -323,7 +327,9 @@ export function createHookRunner(registry: PluginRegistry, options: HookRunnerOp ctx: PluginHookToolResultPersistContext, ): PluginHookToolResultPersistResult | undefined { const hooks = getHooksForName(registry, "tool_result_persist"); - if (hooks.length === 0) return undefined; + if (hooks.length === 0) { + return undefined; + } let current = event.message; @@ -347,7 +353,9 @@ export function createHookRunner(registry: PluginRegistry, options: HookRunnerOp } const next = (out as PluginHookToolResultPersistResult | undefined)?.message; - if (next) current = next; + if (next) { + current = next; + } } catch (err) { const msg = `[hooks] tool_result_persist handler from ${hook.pluginId} failed: ${String(err)}`; if (catchErrors) { diff --git a/src/plugins/http-path.ts b/src/plugins/http-path.ts index 341b91dcd4..069b5ff8d9 100644 --- a/src/plugins/http-path.ts +++ b/src/plugins/http-path.ts @@ -5,7 +5,9 @@ export function normalizePluginHttpPath( const trimmed = path?.trim(); if (!trimmed) { const fallbackTrimmed = fallback?.trim(); - if (!fallbackTrimmed) return null; + if (!fallbackTrimmed) { + return null; + } return fallbackTrimmed.startsWith("/") ? fallbackTrimmed : `/${fallbackTrimmed}`; } return trimmed.startsWith("/") ? trimmed : `/${trimmed}`; diff --git a/src/plugins/install.test.ts b/src/plugins/install.test.ts index 7abbaac496..bde8d7ecd6 100644 --- a/src/plugins/install.test.ts +++ b/src/plugins/install.test.ts @@ -28,7 +28,9 @@ function resolveNpmCliJs() { "bin", "npm-cli.js", ); - if (fs.existsSync(fromNodeDir)) return fromNodeDir; + if (fs.existsSync(fromNodeDir)) { + return fromNodeDir; + } const fromLibNodeModules = path.resolve( path.dirname(process.execPath), @@ -39,7 +41,9 @@ function resolveNpmCliJs() { "bin", "npm-cli.js", ); - if (fs.existsSync(fromLibNodeModules)) return fromLibNodeModules; + if (fs.existsSync(fromLibNodeModules)) { + return fromLibNodeModules; + } return null; } @@ -117,7 +121,9 @@ describe("installPluginFromArchive", () => { extensionsDir, }); expect(result.ok).toBe(true); - if (!result.ok) return; + if (!result.ok) { + return; + } expect(result.pluginId).toBe("voice-call"); expect(result.targetDir).toBe(path.join(stateDir, "extensions", "voice-call")); expect(fs.existsSync(path.join(result.targetDir, "package.json"))).toBe(true); @@ -159,7 +165,9 @@ describe("installPluginFromArchive", () => { expect(first.ok).toBe(true); expect(second.ok).toBe(false); - if (second.ok) return; + if (second.ok) { + return; + } expect(second.error).toContain("already exists"); }); @@ -189,7 +197,9 @@ describe("installPluginFromArchive", () => { }); expect(result.ok).toBe(true); - if (!result.ok) return; + if (!result.ok) { + return; + } expect(result.pluginId).toBe("zipper"); expect(result.targetDir).toBe(path.join(stateDir, "extensions", "zipper")); expect(fs.existsSync(path.join(result.targetDir, "package.json"))).toBe(true); @@ -249,7 +259,9 @@ describe("installPluginFromArchive", () => { expect(first.ok).toBe(true); expect(second.ok).toBe(true); - if (!second.ok) return; + if (!second.ok) { + return; + } const manifest = JSON.parse( fs.readFileSync(path.join(second.targetDir, "package.json"), "utf-8"), ) as { version?: string }; @@ -280,7 +292,9 @@ describe("installPluginFromArchive", () => { extensionsDir, }); expect(result.ok).toBe(false); - if (result.ok) return; + if (result.ok) { + return; + } expect(result.error).toContain("openclaw.extensions"); }); }); diff --git a/src/plugins/install.ts b/src/plugins/install.ts index c5a11d028f..a0d0287327 100644 --- a/src/plugins/install.ts +++ b/src/plugins/install.ts @@ -38,13 +38,17 @@ const defaultLogger: PluginInstallLogger = {}; function unscopedPackageName(name: string): string { const trimmed = name.trim(); - if (!trimmed) return trimmed; + if (!trimmed) { + return trimmed; + } return trimmed.includes("/") ? (trimmed.split("/").pop() ?? trimmed) : trimmed; } function safeDirName(input: string): string { const trimmed = input.trim(); - if (!trimmed) return trimmed; + if (!trimmed) { + return trimmed; + } return trimmed.replaceAll("/", "__"); } @@ -348,7 +352,9 @@ export async function installPluginFromNpmSpec(params: { const dryRun = params.dryRun ?? false; const expectedPluginId = params.expectedPluginId; const spec = params.spec.trim(); - if (!spec) return { ok: false, error: "missing npm spec" }; + if (!spec) { + return { ok: false, error: "missing npm spec" }; + } const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-npm-pack-")); logger.info?.(`Downloading ${spec}…`); diff --git a/src/plugins/loader.ts b/src/plugins/loader.ts index 226765e024..f1b28ac3f5 100644 --- a/src/plugins/loader.ts +++ b/src/plugins/loader.ts @@ -56,10 +56,14 @@ const resolvePluginSdkAlias = (): string | null => { ? [distCandidate, srcCandidate] : [srcCandidate, distCandidate]; for (const candidate of orderedCandidates) { - if (fs.existsSync(candidate)) return candidate; + if (fs.existsSync(candidate)) { + return candidate; + } } const parent = path.dirname(cursor); - if (parent === cursor) break; + if (parent === cursor) { + break; + } cursor = parent; } } catch { diff --git a/src/plugins/manifest-registry.ts b/src/plugins/manifest-registry.ts index d785c018a3..c7dfd250a1 100644 --- a/src/plugins/manifest-registry.ts +++ b/src/plugins/manifest-registry.ts @@ -37,16 +37,24 @@ const DEFAULT_MANIFEST_CACHE_MS = 200; function resolveManifestCacheMs(env: NodeJS.ProcessEnv): number { const raw = env.OPENCLAW_PLUGIN_MANIFEST_CACHE_MS?.trim(); - if (raw === "" || raw === "0") return 0; - if (!raw) return DEFAULT_MANIFEST_CACHE_MS; + if (raw === "" || raw === "0") { + return 0; + } + if (!raw) { + return DEFAULT_MANIFEST_CACHE_MS; + } const parsed = Number.parseInt(raw, 10); - if (!Number.isFinite(parsed)) return DEFAULT_MANIFEST_CACHE_MS; + if (!Number.isFinite(parsed)) { + return DEFAULT_MANIFEST_CACHE_MS; + } return Math.max(0, parsed); } function shouldUseManifestCache(env: NodeJS.ProcessEnv): boolean { const disabled = env.OPENCLAW_DISABLE_PLUGIN_MANIFEST_CACHE?.trim(); - if (disabled) return false; + if (disabled) { + return false; + } return resolveManifestCacheMs(env) > 0; } @@ -114,7 +122,9 @@ export function loadPluginManifestRegistry(params: { const cacheEnabled = params.cache !== false && shouldUseManifestCache(env); if (cacheEnabled) { const cached = registryCache.get(cacheKey); - if (cached && cached.expiresAt > Date.now()) return cached.registry; + if (cached && cached.expiresAt > Date.now()) { + return cached.registry; + } } const discovery = params.candidates diff --git a/src/plugins/manifest.ts b/src/plugins/manifest.ts index 470804d2c6..952035d322 100644 --- a/src/plugins/manifest.ts +++ b/src/plugins/manifest.ts @@ -25,7 +25,9 @@ export type PluginManifestLoadResult = | { ok: false; error: string; manifestPath: string }; function normalizeStringList(value: unknown): string[] { - if (!Array.isArray(value)) return []; + if (!Array.isArray(value)) { + return []; + } return value.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean); } @@ -36,7 +38,9 @@ function isRecord(value: unknown): value is Record { export function resolvePluginManifestPath(rootDir: string): string { for (const filename of PLUGIN_MANIFEST_FILENAMES) { const candidate = path.join(rootDir, filename); - if (fs.existsSync(candidate)) return candidate; + if (fs.existsSync(candidate)) { + return candidate; + } } return path.join(rootDir, PLUGIN_MANIFEST_FILENAME); } @@ -144,6 +148,8 @@ export type PackageManifest = { export function getPackageManifestMetadata( manifest: PackageManifest | undefined, ): OpenClawPackageManifest | undefined { - if (!manifest) return undefined; + if (!manifest) { + return undefined; + } return manifest[MANIFEST_KEY]; } diff --git a/src/plugins/registry.ts b/src/plugins/registry.ts index e7d4e3a8b9..958f43da80 100644 --- a/src/plugins/registry.ts +++ b/src/plugins/registry.ts @@ -268,7 +268,9 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) { handler: GatewayRequestHandler, ) => { const trimmed = method.trim(); - if (!trimmed) return; + if (!trimmed) { + return; + } if (coreGatewayMethods.has(trimmed) || registry.gatewayHandlers[trimmed]) { pushDiagnostic({ level: "error", @@ -397,7 +399,9 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) { const registerService = (record: PluginRecord, service: OpenClawPluginService) => { const id = service.id.trim(); - if (!id) return; + if (!id) { + return; + } record.services.push(id); registry.services.push({ pluginId: record.id, diff --git a/src/plugins/runtime/index.ts b/src/plugins/runtime/index.ts index 685dcb38b3..23e5ed2d50 100644 --- a/src/plugins/runtime/index.ts +++ b/src/plugins/runtime/index.ts @@ -150,7 +150,9 @@ import type { PluginRuntime } from "./types.js"; let cachedVersion: string | null = null; function resolveVersion(): string { - if (cachedVersion) return cachedVersion; + if (cachedVersion) { + return cachedVersion; + } try { const require = createRequire(import.meta.url); const pkg = require("../../../package.json") as { version?: string }; diff --git a/src/plugins/schema-validator.ts b/src/plugins/schema-validator.ts index d3ebd6f24b..1244dfc764 100644 --- a/src/plugins/schema-validator.ts +++ b/src/plugins/schema-validator.ts @@ -14,7 +14,9 @@ type CachedValidator = { const schemaCache = new Map(); function formatAjvErrors(errors: ErrorObject[] | null | undefined): string[] { - if (!errors || errors.length === 0) return ["invalid config"]; + if (!errors || errors.length === 0) { + return ["invalid config"]; + } return errors.map((error) => { const path = error.instancePath?.replace(/^\//, "").replace(/\//g, ".") || ""; const message = error.message ?? "invalid"; @@ -35,6 +37,8 @@ export function validateJsonSchemaValue(params: { } const ok = cached.validate(params.value); - if (ok) return { ok: true }; + if (ok) { + return { ok: true }; + } return { ok: false, errors: formatAjvErrors(cached.validate.errors) }; } diff --git a/src/plugins/services.ts b/src/plugins/services.ts index 63940f6747..8c71300c20 100644 --- a/src/plugins/services.ts +++ b/src/plugins/services.ts @@ -58,7 +58,9 @@ export async function startPluginServices(params: { return { stop: async () => { for (const entry of running.toReversed()) { - if (!entry.stop) continue; + if (!entry.stop) { + continue; + } try { await entry.stop(); } catch (err) { diff --git a/src/plugins/slots.ts b/src/plugins/slots.ts index 8b13788ed9..8fee7172a2 100644 --- a/src/plugins/slots.ts +++ b/src/plugins/slots.ts @@ -18,7 +18,9 @@ const DEFAULT_SLOT_BY_KEY: Record = { }; export function slotKeyForPluginKind(kind?: PluginKind): PluginSlotKey | null { - if (!kind) return null; + if (!kind) { + return null; + } return SLOT_BY_KIND[kind] ?? null; } @@ -62,8 +64,12 @@ export function applyExclusiveSlotSelection(params: { const disabledIds: string[] = []; if (params.registry) { for (const plugin of params.registry.plugins) { - if (plugin.id === params.selectedId) continue; - if (plugin.kind !== params.selectedKind) continue; + if (plugin.id === params.selectedId) { + continue; + } + if (plugin.kind !== params.selectedKind) { + continue; + } const entry = entries[plugin.id]; if (!entry || entry.enabled !== false) { entries[plugin.id] = { diff --git a/src/plugins/tools.ts b/src/plugins/tools.ts index 2556d538b5..eebc92cabe 100644 --- a/src/plugins/tools.ts +++ b/src/plugins/tools.ts @@ -26,11 +26,17 @@ function isOptionalToolAllowed(params: { pluginId: string; allowlist: Set; }): boolean { - if (params.allowlist.size === 0) return false; + if (params.allowlist.size === 0) { + return false; + } const toolName = normalizeToolName(params.toolName); - if (params.allowlist.has(toolName)) return true; + if (params.allowlist.has(toolName)) { + return true; + } const pluginKey = normalizeToolName(params.pluginId); - if (params.allowlist.has(pluginKey)) return true; + if (params.allowlist.has(pluginKey)) { + return true; + } return params.allowlist.has("group:plugins"); } @@ -57,7 +63,9 @@ export function resolvePluginTools(params: { const blockedPlugins = new Set(); for (const entry of registry.tools) { - if (blockedPlugins.has(entry.pluginId)) continue; + if (blockedPlugins.has(entry.pluginId)) { + continue; + } const pluginIdKey = normalizeToolName(entry.pluginId); if (existingNormalized.has(pluginIdKey)) { const message = `plugin id conflicts with core tool name (${entry.pluginId})`; @@ -78,7 +86,9 @@ export function resolvePluginTools(params: { log.error(`plugin tool failed (${entry.pluginId}): ${String(err)}`); continue; } - if (!resolved) continue; + if (!resolved) { + continue; + } const listRaw = Array.isArray(resolved) ? resolved : [resolved]; const list = entry.optional ? listRaw.filter((tool) => @@ -89,7 +99,9 @@ export function resolvePluginTools(params: { }), ) : listRaw; - if (list.length === 0) continue; + if (list.length === 0) { + continue; + } const nameSet = new Set(); for (const tool of list) { if (nameSet.has(tool.name) || existing.has(tool.name)) { diff --git a/src/plugins/update.ts b/src/plugins/update.ts index caa9630744..6e9a288080 100644 --- a/src/plugins/update.ts +++ b/src/plugins/update.ts @@ -66,11 +66,17 @@ function resolveBundledPluginSources(params: { const bundled = new Map(); for (const candidate of discovery.candidates) { - if (candidate.origin !== "bundled") continue; + if (candidate.origin !== "bundled") { + continue; + } const manifest = loadPluginManifest(candidate.rootDir); - if (!manifest.ok) continue; + if (!manifest.ok) { + continue; + } const pluginId = manifest.manifest.id; - if (bundled.has(pluginId)) continue; + if (bundled.has(pluginId)) { + continue; + } const npmSpec = candidate.packageManifest?.install?.npmSpec?.trim() || @@ -88,7 +94,9 @@ function resolveBundledPluginSources(params: { } function pathsEqual(left?: string, right?: string): boolean { - if (!left || !right) return false; + if (!left || !right) { + return false; + } return resolveUserPath(left) === resolveUserPath(right); } @@ -100,7 +108,9 @@ function buildLoadPathHelpers(existing: string[]) { const addPath = (value: string) => { const normalized = resolveUserPath(value); - if (resolved.has(normalized)) return; + if (resolved.has(normalized)) { + return; + } paths.push(value); resolved.add(normalized); changed = true; @@ -108,7 +118,9 @@ function buildLoadPathHelpers(existing: string[]) { const removePath = (value: string) => { const normalized = resolveUserPath(value); - if (!resolved.has(normalized)) return; + if (!resolved.has(normalized)) { + return; + } paths = paths.filter((entry) => resolveUserPath(entry) !== normalized); resolved = resolveSet(); changed = true; @@ -314,13 +326,17 @@ export async function syncPluginsForUpdateChannel(params: { if (params.channel === "dev") { for (const [pluginId, record] of Object.entries(installs)) { const bundledInfo = bundled.get(pluginId); - if (!bundledInfo) continue; + if (!bundledInfo) { + continue; + } loadHelpers.addPath(bundledInfo.localPath); const alreadyBundled = record.source === "path" && pathsEqual(record.sourcePath, bundledInfo.localPath); - if (alreadyBundled) continue; + if (alreadyBundled) { + continue; + } next = recordPluginInstall(next, { pluginId, @@ -336,15 +352,21 @@ export async function syncPluginsForUpdateChannel(params: { } else { for (const [pluginId, record] of Object.entries(installs)) { const bundledInfo = bundled.get(pluginId); - if (!bundledInfo) continue; + if (!bundledInfo) { + continue; + } if (record.source === "npm") { loadHelpers.removePath(bundledInfo.localPath); continue; } - if (record.source !== "path") continue; - if (!pathsEqual(record.sourcePath, bundledInfo.localPath)) continue; + if (record.source !== "path") { + continue; + } + if (!pathsEqual(record.sourcePath, bundledInfo.localPath)) { + continue; + } const spec = record.spec ?? bundledInfo.npmSpec; if (!spec) { diff --git a/src/process/child-process-bridge.test.ts b/src/process/child-process-bridge.test.ts index d6c886d4a8..6dec88bd29 100644 --- a/src/process/child-process-bridge.test.ts +++ b/src/process/child-process-bridge.test.ts @@ -90,7 +90,9 @@ describe("attachChildProcessBridge", () => { const afterSigterm = process.listeners("SIGTERM"); const addedSigterm = afterSigterm.find((listener) => !beforeSigterm.has(listener)); - if (!child.stdout) throw new Error("expected stdout"); + if (!child.stdout) { + throw new Error("expected stdout"); + } const portLine = await waitForLine(child.stdout); const port = Number(portLine); expect(Number.isFinite(port)).toBe(true); @@ -98,7 +100,9 @@ describe("attachChildProcessBridge", () => { expect(await canConnect(port)).toBe(true); // Simulate systemd sending SIGTERM to the parent process. - if (!addedSigterm) throw new Error("expected SIGTERM listener"); + if (!addedSigterm) { + throw new Error("expected SIGTERM listener"); + } addedSigterm(); await new Promise((resolve, reject) => { diff --git a/src/process/command-queue.ts b/src/process/command-queue.ts index 2f2857130b..4e5404aa66 100644 --- a/src/process/command-queue.ts +++ b/src/process/command-queue.ts @@ -27,7 +27,9 @@ const lanes = new Map(); function getLaneState(lane: string): LaneState { const existing = lanes.get(lane); - if (existing) return existing; + if (existing) { + return existing; + } const created: LaneState = { lane, queue: [], @@ -41,7 +43,9 @@ function getLaneState(lane: string): LaneState { function drainLane(lane: string) { const state = getLaneState(lane); - if (state.draining) return; + if (state.draining) { + return; + } state.draining = true; const pump = () => { @@ -130,7 +134,9 @@ export function enqueueCommand( export function getQueueSize(lane: string = CommandLane.Main) { const resolved = lane.trim() || CommandLane.Main; const state = lanes.get(resolved); - if (!state) return 0; + if (!state) { + return 0; + } return state.queue.length + state.active; } @@ -145,7 +151,9 @@ export function getTotalQueueSize() { export function clearCommandLane(lane: string = CommandLane.Main) { const cleaned = lane.trim() || CommandLane.Main; const state = lanes.get(cleaned); - if (!state) return 0; + if (!state) { + return 0; + } const removed = state.queue.length; state.queue.length = 0; return removed; diff --git a/src/process/exec.ts b/src/process/exec.ts index 44f8b2ce08..74a0413b2a 100644 --- a/src/process/exec.ts +++ b/src/process/exec.ts @@ -25,8 +25,12 @@ export async function runExec( try { const { stdout, stderr } = await execFileAsync(command, args, options); if (shouldLogVerbose()) { - if (stdout.trim()) logDebug(stdout.trim()); - if (stderr.trim()) logError(stderr.trim()); + if (stdout.trim()) { + logDebug(stdout.trim()); + } + if (stderr.trim()) { + logError(stderr.trim()); + } } return { stdout, stderr }; } catch (err) { @@ -65,7 +69,9 @@ export async function runCommandWithTimeout( const shouldSuppressNpmFund = (() => { const cmd = path.basename(argv[0] ?? ""); - if (cmd === "npm" || cmd === "npm.cmd" || cmd === "npm.exe") return true; + if (cmd === "npm" || cmd === "npm.cmd" || cmd === "npm.exe") { + return true; + } if (cmd === "node" || cmd === "node.exe") { const script = argv[1] ?? ""; return script.includes("npm-cli.js"); @@ -75,8 +81,12 @@ export async function runCommandWithTimeout( const resolvedEnv = env ? { ...process.env, ...env } : { ...process.env }; if (shouldSuppressNpmFund) { - if (resolvedEnv.NPM_CONFIG_FUND == null) resolvedEnv.NPM_CONFIG_FUND = "false"; - if (resolvedEnv.npm_config_fund == null) resolvedEnv.npm_config_fund = "false"; + if (resolvedEnv.NPM_CONFIG_FUND == null) { + resolvedEnv.NPM_CONFIG_FUND = "false"; + } + if (resolvedEnv.npm_config_fund == null) { + resolvedEnv.npm_config_fund = "false"; + } } const stdio = resolveCommandStdio({ hasInput, preferInherit: true }); @@ -109,13 +119,17 @@ export async function runCommandWithTimeout( stderr += d.toString(); }); child.on("error", (err) => { - if (settled) return; + if (settled) { + return; + } settled = true; clearTimeout(timer); reject(err); }); child.on("close", (code, signal) => { - if (settled) return; + if (settled) { + return; + } settled = true; clearTimeout(timer); resolve({ stdout, stderr, code, signal, killed: child.killed }); diff --git a/src/process/spawn-utils.ts b/src/process/spawn-utils.ts index 2d46044329..58d49598a4 100644 --- a/src/process/spawn-utils.ts +++ b/src/process/spawn-utils.ts @@ -32,14 +32,24 @@ export function resolveCommandStdio(params: { } export function formatSpawnError(err: unknown): string { - if (!(err instanceof Error)) return String(err); + if (!(err instanceof Error)) { + return String(err); + } const details = err as NodeJS.ErrnoException; const parts: string[] = []; const message = err.message?.trim(); - if (message) parts.push(message); - if (details.code && !message?.includes(details.code)) parts.push(details.code); - if (details.syscall) parts.push(`syscall=${details.syscall}`); - if (typeof details.errno === "number") parts.push(`errno=${details.errno}`); + if (message) { + parts.push(message); + } + if (details.code && !message?.includes(details.code)) { + parts.push(details.code); + } + if (details.syscall) { + parts.push(`syscall=${details.syscall}`); + } + if (typeof details.errno === "number") { + parts.push(`errno=${details.errno}`); + } return parts.join(" "); } @@ -63,13 +73,17 @@ async function spawnAndWaitForSpawn( child.removeListener("spawn", onSpawn); }; const finishResolve = () => { - if (settled) return; + if (settled) { + return; + } settled = true; cleanup(); resolve(child); }; const onError = (err: unknown) => { - if (settled) return; + if (settled) { + return; + } settled = true; cleanup(); reject(err); diff --git a/src/providers/github-copilot-models.ts b/src/providers/github-copilot-models.ts index 31b126b642..700df6aaad 100644 --- a/src/providers/github-copilot-models.ts +++ b/src/providers/github-copilot-models.ts @@ -22,7 +22,9 @@ export function getDefaultCopilotModelIds(): string[] { export function buildCopilotModelDefinition(modelId: string): ModelDefinitionConfig { const id = modelId.trim(); - if (!id) throw new Error("Model id required"); + if (!id) { + throw new Error("Model id required"); + } return { id, name: id, diff --git a/src/providers/github-copilot-token.ts b/src/providers/github-copilot-token.ts index 19efd4a9d6..eb794ed27e 100644 --- a/src/providers/github-copilot-token.ts +++ b/src/providers/github-copilot-token.ts @@ -57,18 +57,24 @@ export const DEFAULT_COPILOT_API_BASE_URL = "https://api.individual.githubcopilo export function deriveCopilotApiBaseUrlFromToken(token: string): string | null { const trimmed = token.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } // The token returned from the Copilot token endpoint is a semicolon-delimited // set of key/value pairs. One of them is `proxy-ep=...`. const match = trimmed.match(/(?:^|;)\s*proxy-ep=([^;\s]+)/i); const proxyEp = match?.[1]?.trim(); - if (!proxyEp) return null; + if (!proxyEp) { + return null; + } // pi-ai expects converting proxy.* -> api.* // (see upstream getGitHubCopilotBaseUrl). const host = proxyEp.replace(/^https?:\/\//, "").replace(/^proxy\./i, "api."); - if (!host) return null; + if (!host) { + return null; + } return `https://${host}`; } diff --git a/src/routing/bindings.ts b/src/routing/bindings.ts index 6c204b1d82..44f428edfb 100644 --- a/src/routing/bindings.ts +++ b/src/routing/bindings.ts @@ -6,7 +6,9 @@ import { normalizeAccountId, normalizeAgentId } from "./session-key.js"; function normalizeBindingChannelId(raw?: string | null): string | null { const normalized = normalizeChatChannelId(raw); - if (normalized) return normalized; + if (normalized) { + return normalized; + } const fallback = (raw ?? "").trim().toLowerCase(); return fallback || null; } @@ -17,16 +19,26 @@ export function listBindings(cfg: OpenClawConfig): AgentBinding[] { export function listBoundAccountIds(cfg: OpenClawConfig, channelId: string): string[] { const normalizedChannel = normalizeBindingChannelId(channelId); - if (!normalizedChannel) return []; + if (!normalizedChannel) { + return []; + } const ids = new Set(); for (const binding of listBindings(cfg)) { - if (!binding || typeof binding !== "object") continue; + if (!binding || typeof binding !== "object") { + continue; + } const match = binding.match; - if (!match || typeof match !== "object") continue; + if (!match || typeof match !== "object") { + continue; + } const channel = normalizeBindingChannelId(match.channel); - if (!channel || channel !== normalizedChannel) continue; + if (!channel || channel !== normalizedChannel) { + continue; + } const accountId = typeof match.accountId === "string" ? match.accountId.trim() : ""; - if (!accountId || accountId === "*") continue; + if (!accountId || accountId === "*") { + continue; + } ids.add(normalizeAccountId(accountId)); } return Array.from(ids).toSorted((a, b) => a.localeCompare(b)); @@ -37,17 +49,29 @@ export function resolveDefaultAgentBoundAccountId( channelId: string, ): string | null { const normalizedChannel = normalizeBindingChannelId(channelId); - if (!normalizedChannel) return null; + if (!normalizedChannel) { + return null; + } const defaultAgentId = normalizeAgentId(resolveDefaultAgentId(cfg)); for (const binding of listBindings(cfg)) { - if (!binding || typeof binding !== "object") continue; - if (normalizeAgentId(binding.agentId) !== defaultAgentId) continue; + if (!binding || typeof binding !== "object") { + continue; + } + if (normalizeAgentId(binding.agentId) !== defaultAgentId) { + continue; + } const match = binding.match; - if (!match || typeof match !== "object") continue; + if (!match || typeof match !== "object") { + continue; + } const channel = normalizeBindingChannelId(match.channel); - if (!channel || channel !== normalizedChannel) continue; + if (!channel || channel !== normalizedChannel) { + continue; + } const accountId = typeof match.accountId === "string" ? match.accountId.trim() : ""; - if (!accountId || accountId === "*") continue; + if (!accountId || accountId === "*") { + continue; + } return normalizeAccountId(accountId); } return null; @@ -56,18 +80,28 @@ export function resolveDefaultAgentBoundAccountId( export function buildChannelAccountBindings(cfg: OpenClawConfig) { const map = new Map>(); for (const binding of listBindings(cfg)) { - if (!binding || typeof binding !== "object") continue; + if (!binding || typeof binding !== "object") { + continue; + } const match = binding.match; - if (!match || typeof match !== "object") continue; + if (!match || typeof match !== "object") { + continue; + } const channelId = normalizeBindingChannelId(match.channel); - if (!channelId) continue; + if (!channelId) { + continue; + } const accountId = typeof match.accountId === "string" ? match.accountId.trim() : ""; - if (!accountId || accountId === "*") continue; + if (!accountId || accountId === "*") { + continue; + } const agentId = normalizeAgentId(binding.agentId); const byAgent = map.get(channelId) ?? new Map(); const list = byAgent.get(agentId) ?? []; const normalizedAccountId = normalizeAccountId(accountId); - if (!list.includes(normalizedAccountId)) list.push(normalizedAccountId); + if (!list.includes(normalizedAccountId)) { + list.push(normalizedAccountId); + } byAgent.set(agentId, list); map.set(channelId, byAgent); } @@ -79,6 +113,8 @@ export function resolvePreferredAccountId(params: { defaultAccountId: string; boundAccounts: string[]; }): string { - if (params.boundAccounts.length > 0) return params.boundAccounts[0]; + if (params.boundAccounts.length > 0) { + return params.boundAccounts[0]; + } return params.defaultAccountId; } diff --git a/src/routing/resolve-route.ts b/src/routing/resolve-route.ts index f93e834586..e034d24ca4 100644 --- a/src/routing/resolve-route.ts +++ b/src/routing/resolve-route.ts @@ -61,8 +61,12 @@ function normalizeAccountId(value: string | undefined | null): string { function matchesAccountId(match: string | undefined, actual: string): boolean { const trimmed = (match ?? "").trim(); - if (!trimmed) return actual === DEFAULT_ACCOUNT_ID; - if (trimmed === "*") return true; + if (!trimmed) { + return actual === DEFAULT_ACCOUNT_ID; + } + if (trimmed === "*") { + return true; + } return trimmed === actual; } @@ -96,12 +100,18 @@ function listAgents(cfg: OpenClawConfig) { function pickFirstExistingAgentId(cfg: OpenClawConfig, agentId: string): string { const trimmed = (agentId ?? "").trim(); - if (!trimmed) return sanitizeAgentId(resolveDefaultAgentId(cfg)); + if (!trimmed) { + return sanitizeAgentId(resolveDefaultAgentId(cfg)); + } const normalized = normalizeAgentId(trimmed); const agents = listAgents(cfg); - if (agents.length === 0) return sanitizeAgentId(trimmed); + if (agents.length === 0) { + return sanitizeAgentId(trimmed); + } const match = agents.find((agent) => normalizeAgentId(agent.id) === normalized); - if (match?.id?.trim()) return sanitizeAgentId(match.id.trim()); + if (match?.id?.trim()) { + return sanitizeAgentId(match.id.trim()); + } return sanitizeAgentId(resolveDefaultAgentId(cfg)); } @@ -110,7 +120,9 @@ function matchesChannel( channel: string, ): boolean { const key = normalizeToken(match?.channel); - if (!key) return false; + if (!key) { + return false; + } return key === channel; } @@ -119,10 +131,14 @@ function matchesPeer( peer: RoutePeer, ): boolean { const m = match?.peer; - if (!m) return false; + if (!m) { + return false; + } const kind = normalizeToken(m.kind); const id = normalizeId(m.id); - if (!kind || !id) return false; + if (!kind || !id) { + return false; + } return kind === peer.kind && id === peer.id; } @@ -131,13 +147,17 @@ function matchesGuild( guildId: string, ): boolean { const id = normalizeId(match?.guildId); - if (!id) return false; + if (!id) { + return false; + } return id === guildId; } function matchesTeam(match: { teamId?: string | undefined } | undefined, teamId: string): boolean { const id = normalizeId(match?.teamId); - if (!id) return false; + if (!id) { + return false; + } return id === teamId; } @@ -149,8 +169,12 @@ export function resolveAgentRoute(input: ResolveAgentRouteInput): ResolvedAgentR const teamId = normalizeId(input.teamId); const bindings = listBindings(input.cfg).filter((binding) => { - if (!binding || typeof binding !== "object") return false; - if (!matchesChannel(binding.match, channel)) return false; + if (!binding || typeof binding !== "object") { + return false; + } + if (!matchesChannel(binding.match, channel)) { + return false; + } return matchesAccountId(binding.match?.accountId, accountId); }); @@ -183,30 +207,40 @@ export function resolveAgentRoute(input: ResolveAgentRouteInput): ResolvedAgentR if (peer) { const peerMatch = bindings.find((b) => matchesPeer(b.match, peer)); - if (peerMatch) return choose(peerMatch.agentId, "binding.peer"); + if (peerMatch) { + return choose(peerMatch.agentId, "binding.peer"); + } } if (guildId) { const guildMatch = bindings.find((b) => matchesGuild(b.match, guildId)); - if (guildMatch) return choose(guildMatch.agentId, "binding.guild"); + if (guildMatch) { + return choose(guildMatch.agentId, "binding.guild"); + } } if (teamId) { const teamMatch = bindings.find((b) => matchesTeam(b.match, teamId)); - if (teamMatch) return choose(teamMatch.agentId, "binding.team"); + if (teamMatch) { + return choose(teamMatch.agentId, "binding.team"); + } } const accountMatch = bindings.find( (b) => b.match?.accountId?.trim() !== "*" && !b.match?.peer && !b.match?.guildId && !b.match?.teamId, ); - if (accountMatch) return choose(accountMatch.agentId, "binding.account"); + if (accountMatch) { + return choose(accountMatch.agentId, "binding.account"); + } const anyAccountMatch = bindings.find( (b) => b.match?.accountId?.trim() === "*" && !b.match?.peer && !b.match?.guildId && !b.match?.teamId, ); - if (anyAccountMatch) return choose(anyAccountMatch.agentId, "binding.channel"); + if (anyAccountMatch) { + return choose(anyAccountMatch.agentId, "binding.channel"); + } return choose(resolveDefaultAgentId(input.cfg), "default"); } diff --git a/src/routing/session-key.ts b/src/routing/session-key.ts index 320ffeb839..8f2b4ab0da 100644 --- a/src/routing/session-key.ts +++ b/src/routing/session-key.ts @@ -28,7 +28,9 @@ export function normalizeMainKey(value: string | undefined | null): string { export function toAgentRequestSessionKey(storeKey: string | undefined | null): string | undefined { const raw = (storeKey ?? "").trim(); - if (!raw) return undefined; + if (!raw) { + return undefined; + } return parseAgentSessionKey(raw)?.rest ?? raw; } @@ -42,7 +44,9 @@ export function toAgentStoreSessionKey(params: { return buildAgentMainSessionKey({ agentId: params.agentId, mainKey: params.mainKey }); } const lowered = raw.toLowerCase(); - if (lowered.startsWith("agent:")) return lowered; + if (lowered.startsWith("agent:")) { + return lowered; + } if (lowered.startsWith("subagent:")) { return `agent:${normalizeAgentId(params.agentId)}:${lowered}`; } @@ -56,9 +60,13 @@ export function resolveAgentIdFromSessionKey(sessionKey: string | undefined | nu export function normalizeAgentId(value: string | undefined | null): string { const trimmed = (value ?? "").trim(); - if (!trimmed) return DEFAULT_AGENT_ID; + if (!trimmed) { + return DEFAULT_AGENT_ID; + } // Keep it path-safe + shell-friendly. - if (VALID_ID_RE.test(trimmed)) return trimmed.toLowerCase(); + if (VALID_ID_RE.test(trimmed)) { + return trimmed.toLowerCase(); + } // Best-effort fallback: collapse invalid characters to "-" return ( trimmed @@ -72,8 +80,12 @@ export function normalizeAgentId(value: string | undefined | null): string { export function sanitizeAgentId(value: string | undefined | null): string { const trimmed = (value ?? "").trim(); - if (!trimmed) return DEFAULT_AGENT_ID; - if (VALID_ID_RE.test(trimmed)) return trimmed.toLowerCase(); + if (!trimmed) { + return DEFAULT_AGENT_ID; + } + if (VALID_ID_RE.test(trimmed)) { + return trimmed.toLowerCase(); + } return ( trimmed .toLowerCase() @@ -86,8 +98,12 @@ export function sanitizeAgentId(value: string | undefined | null): string { export function normalizeAccountId(value: string | undefined | null): string { const trimmed = (value ?? "").trim(); - if (!trimmed) return DEFAULT_ACCOUNT_ID; - if (VALID_ID_RE.test(trimmed)) return trimmed.toLowerCase(); + if (!trimmed) { + return DEFAULT_ACCOUNT_ID; + } + if (VALID_ID_RE.test(trimmed)) { + return trimmed.toLowerCase(); + } return ( trimmed .toLowerCase() @@ -130,7 +146,9 @@ export function buildAgentPeerSessionKey(params: { channel: params.channel, peerId, }); - if (linkedPeerId) peerId = linkedPeerId; + if (linkedPeerId) { + peerId = linkedPeerId; + } peerId = peerId.toLowerCase(); if (dmScope === "per-account-channel-peer" && peerId) { const channel = (params.channel ?? "").trim().toLowerCase() || "unknown"; @@ -160,22 +178,36 @@ function resolveLinkedPeerId(params: { peerId: string; }): string | null { const identityLinks = params.identityLinks; - if (!identityLinks) return null; + if (!identityLinks) { + return null; + } const peerId = params.peerId.trim(); - if (!peerId) return null; + if (!peerId) { + return null; + } const candidates = new Set(); const rawCandidate = normalizeToken(peerId); - if (rawCandidate) candidates.add(rawCandidate); + if (rawCandidate) { + candidates.add(rawCandidate); + } const channel = normalizeToken(params.channel); if (channel) { const scopedCandidate = normalizeToken(`${channel}:${peerId}`); - if (scopedCandidate) candidates.add(scopedCandidate); + if (scopedCandidate) { + candidates.add(scopedCandidate); + } + } + if (candidates.size === 0) { + return null; } - if (candidates.size === 0) return null; for (const [canonical, ids] of Object.entries(identityLinks)) { const canonicalName = canonical.trim(); - if (!canonicalName) continue; - if (!Array.isArray(ids)) continue; + if (!canonicalName) { + continue; + } + if (!Array.isArray(ids)) { + continue; + } for (const id of ids) { const normalized = normalizeToken(id); if (normalized && candidates.has(normalized)) { diff --git a/src/security/audit-extra.ts b/src/security/audit-extra.ts index 6be1719d7e..35669055b1 100644 --- a/src/security/audit-extra.ts +++ b/src/security/audit-extra.ts @@ -40,11 +40,19 @@ export type SecurityAuditFinding = { const SMALL_MODEL_PARAM_B_MAX = 300; function expandTilde(p: string, env: NodeJS.ProcessEnv): string | null { - if (!p.startsWith("~")) return p; + if (!p.startsWith("~")) { + return p; + } const home = typeof env.HOME === "string" && env.HOME.trim() ? env.HOME.trim() : null; - if (!home) return null; - if (p === "~") return home; - if (p.startsWith("~/") || p.startsWith("~\\")) return path.join(home, p.slice(2)); + if (!home) { + return null; + } + if (p === "~") { + return home; + } + if (p.startsWith("~/") || p.startsWith("~\\")) { + return path.join(home, p.slice(2)); + } return null; } @@ -54,17 +62,25 @@ function summarizeGroupPolicy(cfg: OpenClawConfig): { other: number; } { const channels = cfg.channels as Record | undefined; - if (!channels || typeof channels !== "object") return { open: 0, allowlist: 0, other: 0 }; + if (!channels || typeof channels !== "object") { + return { open: 0, allowlist: 0, other: 0 }; + } let open = 0; let allowlist = 0; let other = 0; for (const value of Object.values(channels)) { - if (!value || typeof value !== "object") continue; + if (!value || typeof value !== "object") { + continue; + } const section = value as Record; const policy = section.groupPolicy; - if (policy === "open") open += 1; - else if (policy === "allowlist") allowlist += 1; - else other += 1; + if (policy === "open") { + open += 1; + } else if (policy === "allowlist") { + allowlist += 1; + } else { + other += 1; + } } return { open, allowlist, other }; } @@ -159,7 +175,9 @@ export function collectSecretsInConfigFindings(cfg: OpenClawConfig): SecurityAud export function collectHooksHardeningFindings(cfg: OpenClawConfig): SecurityAuditFinding[] { const findings: SecurityAuditFinding[] = []; - if (cfg.hooks?.enabled !== true) return findings; + if (cfg.hooks?.enabled !== true) { + return findings; + } const token = typeof cfg.hooks?.token === "string" ? cfg.hooks.token.trim() : ""; if (token && token.length < 24) { @@ -209,24 +227,32 @@ export function collectHooksHardeningFindings(cfg: OpenClawConfig): SecurityAudi type ModelRef = { id: string; source: string }; function addModel(models: ModelRef[], raw: unknown, source: string) { - if (typeof raw !== "string") return; + if (typeof raw !== "string") { + return; + } const id = raw.trim(); - if (!id) return; + if (!id) { + return; + } models.push({ id, source }); } function collectModels(cfg: OpenClawConfig): ModelRef[] { const out: ModelRef[] = []; addModel(out, cfg.agents?.defaults?.model?.primary, "agents.defaults.model.primary"); - for (const f of cfg.agents?.defaults?.model?.fallbacks ?? []) + for (const f of cfg.agents?.defaults?.model?.fallbacks ?? []) { addModel(out, f, "agents.defaults.model.fallbacks"); + } addModel(out, cfg.agents?.defaults?.imageModel?.primary, "agents.defaults.imageModel.primary"); - for (const f of cfg.agents?.defaults?.imageModel?.fallbacks ?? []) + for (const f of cfg.agents?.defaults?.imageModel?.fallbacks ?? []) { addModel(out, f, "agents.defaults.imageModel.fallbacks"); + } const list = Array.isArray(cfg.agents?.list) ? cfg.agents?.list : []; for (const agent of list ?? []) { - if (!agent || typeof agent !== "object") continue; + if (!agent || typeof agent !== "object") { + continue; + } const id = typeof (agent as { id?: unknown }).id === "string" ? (agent as { id: string }).id : ""; const model = (agent as { model?: unknown }).model; @@ -236,7 +262,9 @@ function collectModels(cfg: OpenClawConfig): ModelRef[] { addModel(out, (model as { primary?: unknown }).primary, `agents.list.${id}.model.primary`); const fallbacks = (model as { fallbacks?: unknown }).fallbacks; if (Array.isArray(fallbacks)) { - for (const f of fallbacks) addModel(out, f, `agents.list.${id}.model.fallbacks`); + for (const f of fallbacks) { + addModel(out, f, `agents.list.${id}.model.fallbacks`); + } } } } @@ -259,10 +287,16 @@ function inferParamBFromIdOrName(text: string): number | null { let best: number | null = null; for (const match of matches) { const numRaw = match[1]; - if (!numRaw) continue; + if (!numRaw) { + continue; + } const value = Number(numRaw); - if (!Number.isFinite(value) || value <= 0) continue; - if (best === null || value > best) best = value; + if (!Number.isFinite(value) || value <= 0) { + continue; + } + if (best === null || value > best) { + best = value; + } } return best; } @@ -289,7 +323,9 @@ function isClaude45OrHigher(id: string): boolean { export function collectModelHygieneFindings(cfg: OpenClawConfig): SecurityAuditFinding[] { const findings: SecurityAuditFinding[] = []; const models = collectModels(cfg); - if (models.length === 0) return findings; + if (models.length === 0) { + return findings; + } const weakMatches = new Map(); const addWeakMatch = (model: string, source: string, reason: string) => { @@ -299,7 +335,9 @@ export function collectModelHygieneFindings(cfg: OpenClawConfig): SecurityAuditF weakMatches.set(key, { model, source, reasons: [reason] }); return; } - if (!existing.reasons.includes(reason)) existing.reasons.push(reason); + if (!existing.reasons.includes(reason)) { + existing.reasons.push(reason); + } }; for (const entry of models) { @@ -373,10 +411,14 @@ function extractAgentIdFromSource(source: string): string | null { } function pickToolPolicy(config?: { allow?: string[]; deny?: string[] }): SandboxToolPolicy | null { - if (!config) return null; + if (!config) { + return null; + } const allow = Array.isArray(config.allow) ? config.allow : undefined; const deny = Array.isArray(config.deny) ? config.deny : undefined; - if (!allow && !deny) return null; + if (!allow && !deny) { + return null; + } return { allow, deny }; } @@ -389,13 +431,19 @@ function resolveToolPolicies(params: { const policies: SandboxToolPolicy[] = []; const profile = params.agentTools?.profile ?? params.cfg.tools?.profile; const profilePolicy = resolveToolProfilePolicy(profile); - if (profilePolicy) policies.push(profilePolicy); + if (profilePolicy) { + policies.push(profilePolicy); + } const globalPolicy = pickToolPolicy(params.cfg.tools ?? undefined); - if (globalPolicy) policies.push(globalPolicy); + if (globalPolicy) { + policies.push(globalPolicy); + } const agentPolicy = pickToolPolicy(params.agentTools); - if (agentPolicy) policies.push(agentPolicy); + if (agentPolicy) { + policies.push(agentPolicy); + } if (params.sandboxMode === "all") { const sandboxPolicy = resolveSandboxToolPolicyForAgent(params.cfg, params.agentId ?? undefined); @@ -418,14 +466,20 @@ function hasWebSearchKey(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): boolean { function isWebSearchEnabled(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): boolean { const enabled = cfg.tools?.web?.search?.enabled; - if (enabled === false) return false; - if (enabled === true) return true; + if (enabled === false) { + return false; + } + if (enabled === true) { + return true; + } return hasWebSearchKey(cfg, env); } function isWebFetchEnabled(cfg: OpenClawConfig): boolean { const enabled = cfg.tools?.web?.fetch?.enabled; - if (enabled === false) return false; + if (enabled === false) { + return false; + } return true; } @@ -443,17 +497,23 @@ export function collectSmallModelRiskFindings(params: { }): SecurityAuditFinding[] { const findings: SecurityAuditFinding[] = []; const models = collectModels(params.cfg).filter((entry) => !entry.source.includes("imageModel")); - if (models.length === 0) return findings; + if (models.length === 0) { + return findings; + } const smallModels = models .map((entry) => { const paramB = inferParamBFromIdOrName(entry.id); - if (!paramB || paramB > SMALL_MODEL_PARAM_B_MAX) return null; + if (!paramB || paramB > SMALL_MODEL_PARAM_B_MAX) { + return null; + } return { ...entry, paramB }; }) .filter((entry): entry is { id: string; source: string; paramB: number } => Boolean(entry)); - if (smallModels.length === 0) return findings; + if (smallModels.length === 0) { + return findings; + } let hasUnsafe = false; const modelLines: string[] = []; @@ -473,19 +533,29 @@ export function collectSmallModelRiskFindings(params: { }); const exposed: string[] = []; if (isWebSearchEnabled(params.cfg, params.env)) { - if (isToolAllowedByPolicies("web_search", policies)) exposed.push("web_search"); + if (isToolAllowedByPolicies("web_search", policies)) { + exposed.push("web_search"); + } } if (isWebFetchEnabled(params.cfg)) { - if (isToolAllowedByPolicies("web_fetch", policies)) exposed.push("web_fetch"); + if (isToolAllowedByPolicies("web_fetch", policies)) { + exposed.push("web_fetch"); + } } if (isBrowserEnabled(params.cfg)) { - if (isToolAllowedByPolicies("browser", policies)) exposed.push("browser"); + if (isToolAllowedByPolicies("browser", policies)) { + exposed.push("browser"); + } + } + for (const tool of exposed) { + exposureSet.add(tool); } - for (const tool of exposed) exposureSet.add(tool); const sandboxLabel = sandboxMode === "all" ? "sandbox=all" : `sandbox=${sandboxMode}`; const exposureLabel = exposed.length > 0 ? ` web=[${exposed.join(", ")}]` : " web=[off]"; const safe = sandboxMode === "all" && exposed.length === 0; - if (!safe) hasUnsafe = true; + if (!safe) { + hasUnsafe = true; + } const statusLabel = safe ? "ok" : "unsafe"; modelLines.push( `- ${entry.id} (${entry.paramB}B) @ ${entry.source} (${statusLabel}; ${sandboxLabel};${exposureLabel})`, @@ -523,14 +593,18 @@ export async function collectPluginsTrustFindings(params: { const findings: SecurityAuditFinding[] = []; const extensionsDir = path.join(params.stateDir, "extensions"); const st = await safeStat(extensionsDir); - if (!st.ok || !st.isDir) return findings; + if (!st.ok || !st.isDir) { + return findings; + } const entries = await fs.readdir(extensionsDir, { withFileTypes: true }).catch(() => []); const pluginDirs = entries .filter((e) => e.isDirectory()) .map((e) => e.name) .filter(Boolean); - if (pluginDirs.length === 0) return findings; + if (pluginDirs.length === 0) { + return findings; + } const allow = params.cfg.plugins?.allow; const allowConfigured = Array.isArray(allow) && allow.length > 0; @@ -623,21 +697,32 @@ function resolveIncludePath(baseConfigPath: string, includePath: string): string function listDirectIncludes(parsed: unknown): string[] { const out: string[] = []; const visit = (value: unknown) => { - if (!value) return; - if (Array.isArray(value)) { - for (const item of value) visit(item); + if (!value) { + return; + } + if (Array.isArray(value)) { + for (const item of value) { + visit(item); + } + return; + } + if (typeof value !== "object") { return; } - if (typeof value !== "object") return; const rec = value as Record; const includeVal = rec[INCLUDE_KEY]; - if (typeof includeVal === "string") out.push(includeVal); - else if (Array.isArray(includeVal)) { + if (typeof includeVal === "string") { + out.push(includeVal); + } else if (Array.isArray(includeVal)) { for (const item of includeVal) { - if (typeof item === "string") out.push(item); + if (typeof item === "string") { + out.push(item); + } } } - for (const v of Object.values(rec)) visit(v); + for (const v of Object.values(rec)) { + visit(v); + } }; visit(parsed); return out; @@ -651,14 +736,20 @@ async function collectIncludePathsRecursive(params: { const result: string[] = []; const walk = async (basePath: string, parsed: unknown, depth: number): Promise => { - if (depth > MAX_INCLUDE_DEPTH) return; + if (depth > MAX_INCLUDE_DEPTH) { + return; + } for (const raw of listDirectIncludes(parsed)) { const resolved = resolveIncludePath(basePath, raw); - if (visited.has(resolved)) continue; + if (visited.has(resolved)) { + continue; + } visited.add(resolved); result.push(resolved); const rawText = await fs.readFile(resolved, "utf-8").catch(() => null); - if (!rawText) continue; + if (!rawText) { + continue; + } const nestedParsed = (() => { try { return JSON5.parse(rawText); @@ -684,14 +775,18 @@ export async function collectIncludeFilePermFindings(params: { execIcacls?: ExecFn; }): Promise { const findings: SecurityAuditFinding[] = []; - if (!params.configSnapshot.exists) return findings; + if (!params.configSnapshot.exists) { + return findings; + } const configPath = params.configSnapshot.path; const includePaths = await collectIncludePathsRecursive({ configPath, parsed: params.configSnapshot.parsed, }); - if (includePaths.length === 0) return findings; + if (includePaths.length === 0) { + return findings; + } for (const p of includePaths) { // eslint-disable-next-line no-await-in-loop @@ -700,7 +795,9 @@ export async function collectIncludeFilePermFindings(params: { platform: params.platform, exec: params.execIcacls, }); - if (!perms.ok) continue; + if (!perms.ok) { + continue; + } if (perms.worldWritable || perms.groupWritable) { findings.push({ checkId: "fs.config_include.perms_writable", @@ -908,18 +1005,27 @@ export async function collectStateDeepFilesystemFindings(params: { function listGroupPolicyOpen(cfg: OpenClawConfig): string[] { const out: string[] = []; const channels = cfg.channels as Record | undefined; - if (!channels || typeof channels !== "object") return out; + if (!channels || typeof channels !== "object") { + return out; + } for (const [channelId, value] of Object.entries(channels)) { - if (!value || typeof value !== "object") continue; + if (!value || typeof value !== "object") { + continue; + } const section = value as Record; - if (section.groupPolicy === "open") out.push(`channels.${channelId}.groupPolicy`); + if (section.groupPolicy === "open") { + out.push(`channels.${channelId}.groupPolicy`); + } const accounts = section.accounts; if (accounts && typeof accounts === "object") { for (const [accountId, accountVal] of Object.entries(accounts)) { - if (!accountVal || typeof accountVal !== "object") continue; + if (!accountVal || typeof accountVal !== "object") { + continue; + } const acc = accountVal as Record; - if (acc.groupPolicy === "open") + if (acc.groupPolicy === "open") { out.push(`channels.${channelId}.accounts.${accountId}.groupPolicy`); + } } } } @@ -929,7 +1035,9 @@ function listGroupPolicyOpen(cfg: OpenClawConfig): string[] { export function collectExposureMatrixFindings(cfg: OpenClawConfig): SecurityAuditFinding[] { const findings: SecurityAuditFinding[] = []; const openGroups = listGroupPolicyOpen(cfg); - if (openGroups.length === 0) return findings; + if (openGroups.length === 0) { + return findings; + } const elevatedEnabled = cfg.tools?.elevated?.enabled !== false; if (elevatedEnabled) { diff --git a/src/security/audit-fs.ts b/src/security/audit-fs.ts index 6bf0aec262..10b5dd1ab8 100644 --- a/src/security/audit-fs.ts +++ b/src/security/audit-fs.ts @@ -153,31 +153,43 @@ export function formatPermissionRemediation(params: { } export function modeBits(mode: number | null): number | null { - if (mode == null) return null; + if (mode == null) { + return null; + } return mode & 0o777; } export function formatOctal(bits: number | null): string { - if (bits == null) return "unknown"; + if (bits == null) { + return "unknown"; + } return bits.toString(8).padStart(3, "0"); } export function isWorldWritable(bits: number | null): boolean { - if (bits == null) return false; + if (bits == null) { + return false; + } return (bits & 0o002) !== 0; } export function isGroupWritable(bits: number | null): boolean { - if (bits == null) return false; + if (bits == null) { + return false; + } return (bits & 0o020) !== 0; } export function isWorldReadable(bits: number | null): boolean { - if (bits == null) return false; + if (bits == null) { + return false; + } return (bits & 0o004) !== 0; } export function isGroupReadable(bits: number | null): boolean { - if (bits == null) return false; + if (bits == null) { + return false; + } return (bits & 0o040) !== 0; } diff --git a/src/security/audit.test.ts b/src/security/audit.test.ts index 0896e2a4f4..38fbb9a77c 100644 --- a/src/security/audit.test.ts +++ b/src/security/audit.test.ts @@ -429,8 +429,11 @@ describe("security audit", () => { ]), ); } finally { - if (prevStateDir == null) delete process.env.OPENCLAW_STATE_DIR; - else process.env.OPENCLAW_STATE_DIR = prevStateDir; + if (prevStateDir == null) { + delete process.env.OPENCLAW_STATE_DIR; + } else { + process.env.OPENCLAW_STATE_DIR = prevStateDir; + } } }); @@ -475,8 +478,11 @@ describe("security audit", () => { ]), ); } finally { - if (prevStateDir == null) delete process.env.OPENCLAW_STATE_DIR; - else process.env.OPENCLAW_STATE_DIR = prevStateDir; + if (prevStateDir == null) { + delete process.env.OPENCLAW_STATE_DIR; + } else { + process.env.OPENCLAW_STATE_DIR = prevStateDir; + } } }); @@ -520,8 +526,11 @@ describe("security audit", () => { ]), ); } finally { - if (prevStateDir == null) delete process.env.OPENCLAW_STATE_DIR; - else process.env.OPENCLAW_STATE_DIR = prevStateDir; + if (prevStateDir == null) { + delete process.env.OPENCLAW_STATE_DIR; + } else { + process.env.OPENCLAW_STATE_DIR = prevStateDir; + } } }); @@ -559,8 +568,11 @@ describe("security audit", () => { ]), ); } finally { - if (prevStateDir == null) delete process.env.OPENCLAW_STATE_DIR; - else process.env.OPENCLAW_STATE_DIR = prevStateDir; + if (prevStateDir == null) { + delete process.env.OPENCLAW_STATE_DIR; + } else { + process.env.OPENCLAW_STATE_DIR = prevStateDir; + } } }); @@ -599,8 +611,11 @@ describe("security audit", () => { ]), ); } finally { - if (prevStateDir == null) delete process.env.OPENCLAW_STATE_DIR; - else process.env.OPENCLAW_STATE_DIR = prevStateDir; + if (prevStateDir == null) { + delete process.env.OPENCLAW_STATE_DIR; + } else { + process.env.OPENCLAW_STATE_DIR = prevStateDir; + } } }); @@ -637,8 +652,11 @@ describe("security audit", () => { ]), ); } finally { - if (prevStateDir == null) delete process.env.OPENCLAW_STATE_DIR; - else process.env.OPENCLAW_STATE_DIR = prevStateDir; + if (prevStateDir == null) { + delete process.env.OPENCLAW_STATE_DIR; + } else { + process.env.OPENCLAW_STATE_DIR = prevStateDir; + } } }); @@ -785,8 +803,11 @@ describe("security audit", () => { ]), ); } finally { - if (prevToken === undefined) delete process.env.OPENCLAW_GATEWAY_TOKEN; - else process.env.OPENCLAW_GATEWAY_TOKEN = prevToken; + if (prevToken === undefined) { + delete process.env.OPENCLAW_GATEWAY_TOKEN; + } else { + process.env.OPENCLAW_GATEWAY_TOKEN = prevToken; + } } }); @@ -905,14 +926,26 @@ describe("security audit", () => { ]), ); } finally { - if (prevDiscordToken == null) delete process.env.DISCORD_BOT_TOKEN; - else process.env.DISCORD_BOT_TOKEN = prevDiscordToken; - if (prevTelegramToken == null) delete process.env.TELEGRAM_BOT_TOKEN; - else process.env.TELEGRAM_BOT_TOKEN = prevTelegramToken; - if (prevSlackBotToken == null) delete process.env.SLACK_BOT_TOKEN; - else process.env.SLACK_BOT_TOKEN = prevSlackBotToken; - if (prevSlackAppToken == null) delete process.env.SLACK_APP_TOKEN; - else process.env.SLACK_APP_TOKEN = prevSlackAppToken; + if (prevDiscordToken == null) { + delete process.env.DISCORD_BOT_TOKEN; + } else { + process.env.DISCORD_BOT_TOKEN = prevDiscordToken; + } + if (prevTelegramToken == null) { + delete process.env.TELEGRAM_BOT_TOKEN; + } else { + process.env.TELEGRAM_BOT_TOKEN = prevTelegramToken; + } + if (prevSlackBotToken == null) { + delete process.env.SLACK_BOT_TOKEN; + } else { + process.env.SLACK_BOT_TOKEN = prevSlackBotToken; + } + if (prevSlackAppToken == null) { + delete process.env.SLACK_APP_TOKEN; + } else { + process.env.SLACK_APP_TOKEN = prevSlackAppToken; + } } }); @@ -949,8 +982,11 @@ describe("security audit", () => { ]), ); } finally { - if (prevDiscordToken == null) delete process.env.DISCORD_BOT_TOKEN; - else process.env.DISCORD_BOT_TOKEN = prevDiscordToken; + if (prevDiscordToken == null) { + delete process.env.DISCORD_BOT_TOKEN; + } else { + process.env.DISCORD_BOT_TOKEN = prevDiscordToken; + } } }); diff --git a/src/security/audit.ts b/src/security/audit.ts index a4a1f76f35..8ee915d74b 100644 --- a/src/security/audit.ts +++ b/src/security/audit.ts @@ -87,15 +87,21 @@ function countBySeverity(findings: SecurityAuditFinding[]): SecurityAuditSummary let warn = 0; let info = 0; for (const f of findings) { - if (f.severity === "critical") critical += 1; - else if (f.severity === "warn") warn += 1; - else info += 1; + if (f.severity === "critical") { + critical += 1; + } else if (f.severity === "warn") { + warn += 1; + } else { + info += 1; + } } return { critical, warn, info }; } function normalizeAllowFromList(list: Array | undefined | null): string[] { - if (!Array.isArray(list)) return []; + if (!Array.isArray(list)) { + return []; + } return list.map((v) => String(v).trim()).filter(Boolean); } @@ -373,11 +379,15 @@ function collectBrowserControlFindings(cfg: OpenClawConfig): SecurityAuditFindin return findings; } - if (!resolved.enabled) return findings; + if (!resolved.enabled) { + return findings; + } for (const name of Object.keys(resolved.profiles)) { const profile = resolveProfile(resolved, name); - if (!profile || profile.cdpIsLoopback) continue; + if (!profile || profile.cdpIsLoopback) { + continue; + } let url: URL; try { url = new URL(profile.cdpUrl); @@ -400,7 +410,9 @@ function collectBrowserControlFindings(cfg: OpenClawConfig): SecurityAuditFindin function collectLoggingFindings(cfg: OpenClawConfig): SecurityAuditFinding[] { const redact = cfg.logging?.redactSensitive; - if (redact !== "off") return []; + if (redact !== "off") { + return []; + } return [ { checkId: "logging.redact_off", @@ -418,8 +430,12 @@ function collectElevatedFindings(cfg: OpenClawConfig): SecurityAuditFinding[] { const allowFrom = cfg.tools?.elevated?.allowFrom ?? {}; const anyAllowFromKeys = Object.keys(allowFrom).length > 0; - if (enabled === false) return findings; - if (!anyAllowFromKeys) return findings; + if (enabled === false) { + return findings; + } + if (!anyAllowFromKeys) { + return findings; + } for (const [provider, list] of Object.entries(allowFrom)) { const normalized = normalizeAllowFromList(list); @@ -450,9 +466,15 @@ async function collectChannelSecurityFindings(params: { const findings: SecurityAuditFinding[] = []; const coerceNativeSetting = (value: unknown): boolean | "auto" | undefined => { - if (value === true) return true; - if (value === false) return false; - if (value === "auto") return "auto"; + if (value === true) { + return true; + } + if (value === false) { + return false; + } + if (value === "auto") { + return "auto"; + } return undefined; }; @@ -526,7 +548,9 @@ async function collectChannelSecurityFindings(params: { }; for (const plugin of params.plugins) { - if (!plugin.security) continue; + if (!plugin.security) { + continue; + } const accountIds = plugin.config.listAccountIds(params.cfg); const defaultAccountId = resolveChannelDefaultAccountId({ plugin, @@ -535,11 +559,15 @@ async function collectChannelSecurityFindings(params: { }); const account = plugin.config.resolveAccount(params.cfg, defaultAccountId); const enabled = plugin.config.isEnabled ? plugin.config.isEnabled(account, params.cfg) : true; - if (!enabled) continue; + if (!enabled) { + continue; + } const configured = plugin.config.isConfigured ? await plugin.config.isConfigured(account, params.cfg) : true; - if (!configured) continue; + if (!configured) { + continue; + } if (plugin.id === "discord") { const discordCfg = @@ -567,13 +595,21 @@ async function collectChannelSecurityFindings(params: { const guildEntries = (discordCfg.guilds as Record | undefined) ?? {}; const guildsConfigured = Object.keys(guildEntries).length > 0; const hasAnyUserAllowlist = Object.values(guildEntries).some((guild) => { - if (!guild || typeof guild !== "object") return false; + if (!guild || typeof guild !== "object") { + return false; + } const g = guild as Record; - if (Array.isArray(g.users) && g.users.length > 0) return true; + if (Array.isArray(g.users) && g.users.length > 0) { + return true; + } const channels = g.channels; - if (!channels || typeof channels !== "object") return false; + if (!channels || typeof channels !== "object") { + return false; + } return Object.values(channels as Record).some((channel) => { - if (!channel || typeof channel !== "object") return false; + if (!channel || typeof channel !== "object") { + return false; + } const c = channel as Record; return Array.isArray(c.users) && c.users.length > 0; }); @@ -662,7 +698,9 @@ async function collectChannelSecurityFindings(params: { normalizeAllowFromList([...dmAllowFrom, ...storeAllowFrom]).length > 0; const channels = (slackCfg.channels as Record | undefined) ?? {}; const hasAnyChannelUsersAllowlist = Object.values(channels).some((value) => { - if (!value || typeof value !== "object") return false; + if (!value || typeof value !== "object") { + return false; + } const channel = value as Record; return Array.isArray(channel.users) && channel.users.length > 0; }); @@ -706,7 +744,9 @@ async function collectChannelSecurityFindings(params: { }); for (const message of warnings ?? []) { const trimmed = String(message).trim(); - if (!trimmed) continue; + if (!trimmed) { + continue; + } findings.push({ checkId: `channels.${plugin.id}.warning.${findings.length + 1}`, severity: classifyChannelWarningSeverity(trimmed), @@ -718,7 +758,9 @@ async function collectChannelSecurityFindings(params: { if (plugin.id === "telegram") { const allowTextCommands = params.cfg.commands?.text !== false; - if (!allowTextCommands) continue; + if (!allowTextCommands) { + continue; + } const telegramCfg = (account as { config?: Record } | null)?.config ?? @@ -730,7 +772,9 @@ async function collectChannelSecurityFindings(params: { const groupsConfigured = Boolean(groups) && Object.keys(groups ?? {}).length > 0; const groupAccessPossible = groupPolicy === "open" || (groupPolicy === "allowlist" && groupsConfigured); - if (!groupAccessPossible) continue; + if (!groupAccessPossible) { + continue; + } const storeAllowFrom = await readChannelAllowFromStore("telegram").catch(() => []); const storeHasWildcard = storeAllowFrom.some((v) => String(v).trim() === "*"); @@ -741,14 +785,22 @@ async function collectChannelSecurityFindings(params: { const anyGroupOverride = Boolean( groups && Object.values(groups).some((value) => { - if (!value || typeof value !== "object") return false; + if (!value || typeof value !== "object") { + return false; + } const group = value as Record; const allowFrom = Array.isArray(group.allowFrom) ? group.allowFrom : []; - if (allowFrom.length > 0) return true; + if (allowFrom.length > 0) { + return true; + } const topics = group.topics; - if (!topics || typeof topics !== "object") return false; + if (!topics || typeof topics !== "object") { + return false; + } return Object.values(topics as Record).some((topicValue) => { - if (!topicValue || typeof topicValue !== "object") return false; + if (!topicValue || typeof topicValue !== "object") { + return false; + } const topic = topicValue as Record; const topicAllow = Array.isArray(topic.allowFrom) ? topic.allowFrom : []; return topicAllow.length > 0; diff --git a/src/security/external-content.ts b/src/security/external-content.ts index b81e99e54e..60b6a37dfe 100644 --- a/src/security/external-content.ts +++ b/src/security/external-content.ts @@ -171,8 +171,14 @@ export function isExternalHookSession(sessionKey: string): boolean { * Extracts the hook type from a session key. */ export function getHookType(sessionKey: string): ExternalContentSource { - if (sessionKey.startsWith("hook:gmail:")) return "email"; - if (sessionKey.startsWith("hook:webhook:")) return "webhook"; - if (sessionKey.startsWith("hook:")) return "webhook"; + if (sessionKey.startsWith("hook:gmail:")) { + return "email"; + } + if (sessionKey.startsWith("hook:webhook:")) { + return "webhook"; + } + if (sessionKey.startsWith("hook:")) { + return "webhook"; + } return "unknown"; } diff --git a/src/security/fix.ts b/src/security/fix.ts index 0ae281346e..972c333812 100644 --- a/src/security/fix.ts +++ b/src/security/fix.ts @@ -192,11 +192,15 @@ function setGroupPolicyAllowlist(params: { changes: string[]; policyFlips: Set; }): void { - if (!params.cfg.channels) return; + if (!params.cfg.channels) { + return; + } const section = params.cfg.channels[params.channel as keyof OpenClawConfig["channels"]] as | Record | undefined; - if (!section || typeof section !== "object") return; + if (!section || typeof section !== "object") { + return; + } const topPolicy = section.groupPolicy; if (topPolicy === "open") { @@ -206,10 +210,16 @@ function setGroupPolicyAllowlist(params: { } const accounts = section.accounts; - if (!accounts || typeof accounts !== "object") return; + if (!accounts || typeof accounts !== "object") { + return; + } for (const [accountId, accountValue] of Object.entries(accounts)) { - if (!accountId) continue; - if (!accountValue || typeof accountValue !== "object") continue; + if (!accountId) { + continue; + } + if (!accountValue || typeof accountValue !== "object") { + continue; + } const account = accountValue as Record; if (account.groupPolicy === "open") { account.groupPolicy = "allowlist"; @@ -228,15 +238,25 @@ function setWhatsAppGroupAllowFromFromStore(params: { policyFlips: Set; }): void { const section = params.cfg.channels?.whatsapp as Record | undefined; - if (!section || typeof section !== "object") return; - if (params.storeAllowFrom.length === 0) return; + if (!section || typeof section !== "object") { + return; + } + if (params.storeAllowFrom.length === 0) { + return; + } const maybeApply = (prefix: string, obj: Record) => { - if (!params.policyFlips.has(prefix)) return; + if (!params.policyFlips.has(prefix)) { + return; + } const allowFrom = Array.isArray(obj.allowFrom) ? obj.allowFrom : []; const groupAllowFrom = Array.isArray(obj.groupAllowFrom) ? obj.groupAllowFrom : []; - if (allowFrom.length > 0) return; - if (groupAllowFrom.length > 0) return; + if (allowFrom.length > 0) { + return; + } + if (groupAllowFrom.length > 0) { + return; + } obj.groupAllowFrom = params.storeAllowFrom; params.changes.push(`${prefix}groupAllowFrom=pairing-store`); }; @@ -244,9 +264,13 @@ function setWhatsAppGroupAllowFromFromStore(params: { maybeApply("channels.whatsapp.", section); const accounts = section.accounts; - if (!accounts || typeof accounts !== "object") return; + if (!accounts || typeof accounts !== "object") { + return; + } for (const [accountId, accountValue] of Object.entries(accounts)) { - if (!accountValue || typeof accountValue !== "object") continue; + if (!accountValue || typeof accountValue !== "object") { + continue; + } const account = accountValue as Record; maybeApply(`channels.whatsapp.accounts.${accountId}.`, account); } @@ -284,21 +308,32 @@ function applyConfigFixes(params: { cfg: OpenClawConfig; env: NodeJS.ProcessEnv function listDirectIncludes(parsed: unknown): string[] { const out: string[] = []; const visit = (value: unknown) => { - if (!value) return; - if (Array.isArray(value)) { - for (const item of value) visit(item); + if (!value) { + return; + } + if (Array.isArray(value)) { + for (const item of value) { + visit(item); + } + return; + } + if (typeof value !== "object") { return; } - if (typeof value !== "object") return; const rec = value as Record; const includeVal = rec[INCLUDE_KEY]; - if (typeof includeVal === "string") out.push(includeVal); - else if (Array.isArray(includeVal)) { + if (typeof includeVal === "string") { + out.push(includeVal); + } else if (Array.isArray(includeVal)) { for (const item of includeVal) { - if (typeof item === "string") out.push(item); + if (typeof item === "string") { + out.push(item); + } } } - for (const v of Object.values(rec)) visit(v); + for (const v of Object.values(rec)) { + visit(v); + } }; visit(parsed); return out; @@ -320,14 +355,20 @@ async function collectIncludePathsRecursive(params: { const result: string[] = []; const walk = async (basePath: string, parsed: unknown, depth: number): Promise => { - if (depth > MAX_INCLUDE_DEPTH) return; + if (depth > MAX_INCLUDE_DEPTH) { + return; + } for (const raw of listDirectIncludes(parsed)) { const resolved = resolveIncludePath(basePath, raw); - if (visited.has(resolved)) continue; + if (visited.has(resolved)) { + continue; + } visited.add(resolved); result.push(resolved); const rawText = await fs.readFile(resolved, "utf-8").catch(() => null); - if (!rawText) continue; + if (!rawText) { + continue; + } const nestedParsed = (() => { try { return JSON5.parse(rawText); @@ -362,8 +403,12 @@ async function chmodCredentialsAndAgentState(params: { const credsEntries = await fs.readdir(credsDir, { withFileTypes: true }).catch(() => []); for (const entry of credsEntries) { - if (!entry.isFile()) continue; - if (!entry.name.endsWith(".json")) continue; + if (!entry.isFile()) { + continue; + } + if (!entry.name.endsWith(".json")) { + continue; + } const p = path.join(credsDir, entry.name); // eslint-disable-next-line no-await-in-loop params.actions.push(await safeChmod({ path: p, mode: 0o600, require: "file" })); @@ -373,10 +418,14 @@ async function chmodCredentialsAndAgentState(params: { ids.add(resolveDefaultAgentId(params.cfg)); const list = Array.isArray(params.cfg.agents?.list) ? params.cfg.agents?.list : []; for (const agent of list ?? []) { - if (!agent || typeof agent !== "object") continue; + if (!agent || typeof agent !== "object") { + continue; + } const id = typeof (agent as { id?: unknown }).id === "string" ? (agent as { id: string }).id.trim() : ""; - if (id) ids.add(id); + if (id) { + ids.add(id); + } } for (const agentId of ids) { diff --git a/src/security/windows-acl.ts b/src/security/windows-acl.ts index 0a6779214d..b0b0445076 100644 --- a/src/security/windows-acl.ts +++ b/src/security/windows-acl.ts @@ -42,7 +42,9 @@ const normalize = (value: string) => value.trim().toLowerCase(); export function resolveWindowsUserPrincipal(env?: NodeJS.ProcessEnv): string | null { const username = env?.USERNAME?.trim() || os.userInfo().username?.trim(); - if (!username) return null; + if (!username) { + return null; + } const domain = env?.USERDOMAIN?.trim(); return domain ? `${domain}\\${username}` : username; } @@ -54,7 +56,9 @@ function buildTrustedPrincipals(env?: NodeJS.ProcessEnv): Set { trusted.add(normalize(principal)); const parts = principal.split("\\"); const userOnly = parts.at(-1); - if (userOnly) trusted.add(normalize(userOnly)); + if (userOnly) { + trusted.add(normalize(userOnly)); + } } return trusted; } @@ -65,10 +69,12 @@ function classifyPrincipal( ): "trusted" | "world" | "group" { const normalized = normalize(principal); const trusted = buildTrustedPrincipals(env); - if (trusted.has(normalized) || TRUSTED_SUFFIXES.some((s) => normalized.endsWith(s))) + if (trusted.has(normalized) || TRUSTED_SUFFIXES.some((s) => normalized.endsWith(s))) { return "trusted"; - if (WORLD_PRINCIPALS.has(normalized) || WORLD_SUFFIXES.some((s) => normalized.endsWith(s))) + } + if (WORLD_PRINCIPALS.has(normalized) || WORLD_SUFFIXES.some((s) => normalized.endsWith(s))) { return "world"; + } return "group"; } @@ -89,7 +95,9 @@ export function parseIcaclsOutput(output: string, targetPath: string): WindowsAc for (const rawLine of output.split(/\r?\n/)) { const line = rawLine.trimEnd(); - if (!line.trim()) continue; + if (!line.trim()) { + continue; + } const trimmed = line.trim(); const lower = trimmed.toLowerCase(); if ( @@ -107,10 +115,14 @@ export function parseIcaclsOutput(output: string, targetPath: string): WindowsAc } else if (lower.startsWith(quotedLower)) { entry = trimmed.slice(quotedTarget.length).trim(); } - if (!entry) continue; + if (!entry) { + continue; + } const idx = entry.indexOf(":"); - if (idx === -1) continue; + if (idx === -1) { + continue; + } const principal = entry.slice(0, idx).trim(); const rawRights = entry.slice(idx + 1).trim(); @@ -119,9 +131,13 @@ export function parseIcaclsOutput(output: string, targetPath: string): WindowsAc .match(/\(([^)]+)\)/g) ?.map((token) => token.slice(1, -1).trim()) .filter(Boolean) ?? []; - if (tokens.some((token) => token.toUpperCase() === "DENY")) continue; + if (tokens.some((token) => token.toUpperCase() === "DENY")) { + continue; + } const rights = tokens.filter((token) => !INHERIT_FLAGS.has(token.toUpperCase())); - if (rights.length === 0) continue; + if (rights.length === 0) { + continue; + } const { canRead, canWrite } = rightsFromTokens(rights); entries.push({ principal, rights, rawRights, canRead, canWrite }); } @@ -138,9 +154,13 @@ export function summarizeWindowsAcl( const untrustedGroup: WindowsAclEntry[] = []; for (const entry of entries) { const classification = classifyPrincipal(entry.principal, env); - if (classification === "trusted") trusted.push(entry); - else if (classification === "world") untrustedWorld.push(entry); - else untrustedGroup.push(entry); + if (classification === "trusted") { + trusted.push(entry); + } else if (classification === "world") { + untrustedWorld.push(entry); + } else { + untrustedGroup.push(entry); + } } return { trusted, untrustedWorld, untrustedGroup }; } @@ -169,9 +189,13 @@ export async function inspectWindowsAcl( } export function formatWindowsAclSummary(summary: WindowsAclSummary): string { - if (!summary.ok) return "unknown"; + if (!summary.ok) { + return "unknown"; + } const untrusted = [...summary.untrustedWorld, ...summary.untrustedGroup]; - if (untrusted.length === 0) return "trusted-only"; + if (untrusted.length === 0) { + return "trusted-only"; + } return untrusted.map((entry) => `${entry.principal}:${entry.rawRights}`).join(", "); } @@ -189,7 +213,9 @@ export function createIcaclsResetCommand( opts: { isDir: boolean; env?: NodeJS.ProcessEnv }, ): { command: string; args: string[]; display: string } | null { const user = resolveWindowsUserPrincipal(opts.env); - if (!user) return null; + if (!user) { + return null; + } const grant = opts.isDir ? "(OI)(CI)F" : "F"; const args = [ targetPath, diff --git a/src/sessions/level-overrides.ts b/src/sessions/level-overrides.ts index 5b0d079d98..29add6f195 100644 --- a/src/sessions/level-overrides.ts +++ b/src/sessions/level-overrides.ts @@ -4,8 +4,12 @@ import type { SessionEntry } from "../config/sessions.js"; export function parseVerboseOverride( raw: unknown, ): { ok: true; value: VerboseLevel | null | undefined } | { ok: false; error: string } { - if (raw === null) return { ok: true, value: null }; - if (raw === undefined) return { ok: true, value: undefined }; + if (raw === null) { + return { ok: true, value: null }; + } + if (raw === undefined) { + return { ok: true, value: undefined }; + } if (typeof raw !== "string") { return { ok: false, error: 'invalid verboseLevel (use "on"|"off")' }; } @@ -17,7 +21,9 @@ export function parseVerboseOverride( } export function applyVerboseOverride(entry: SessionEntry, level: VerboseLevel | null | undefined) { - if (level === undefined) return; + if (level === undefined) { + return; + } if (level === null) { delete entry.verboseLevel; return; diff --git a/src/sessions/send-policy.ts b/src/sessions/send-policy.ts index a71e890c4f..6f635c1ae9 100644 --- a/src/sessions/send-policy.ts +++ b/src/sessions/send-policy.ts @@ -6,8 +6,12 @@ export type SessionSendPolicyDecision = "allow" | "deny"; export function normalizeSendPolicy(raw?: string | null): SessionSendPolicyDecision | undefined { const value = raw?.trim().toLowerCase(); - if (value === "allow") return "allow"; - if (value === "deny") return "deny"; + if (value === "allow") { + return "allow"; + } + if (value === "deny") { + return "deny"; + } return undefined; } @@ -17,7 +21,9 @@ function normalizeMatchValue(raw?: string | null) { } function deriveChannelFromKey(key?: string) { - if (!key) return undefined; + if (!key) { + return undefined; + } const parts = key.split(":").filter(Boolean); if (parts.length >= 3 && (parts[1] === "group" || parts[1] === "channel")) { return normalizeMatchValue(parts[0]); @@ -26,9 +32,15 @@ function deriveChannelFromKey(key?: string) { } function deriveChatTypeFromKey(key?: string): SessionChatType | undefined { - if (!key) return undefined; - if (key.includes(":group:")) return "group"; - if (key.includes(":channel:")) return "channel"; + if (!key) { + return undefined; + } + if (key.includes(":group:")) { + return "group"; + } + if (key.includes(":channel:")) { + return "channel"; + } return undefined; } @@ -40,10 +52,14 @@ export function resolveSendPolicy(params: { chatType?: SessionChatType; }): SessionSendPolicyDecision { const override = normalizeSendPolicy(params.entry?.sendPolicy); - if (override) return override; + if (override) { + return override; + } const policy = params.cfg.session?.sendPolicy; - if (!policy) return "allow"; + if (!policy) { + return "allow"; + } const channel = normalizeMatchValue(params.channel) ?? @@ -57,21 +73,33 @@ export function resolveSendPolicy(params: { let allowedMatch = false; for (const rule of policy.rules ?? []) { - if (!rule) continue; + if (!rule) { + continue; + } const action = normalizeSendPolicy(rule.action) ?? "allow"; const match = rule.match ?? {}; const matchChannel = normalizeMatchValue(match.channel); const matchChatType = normalizeChatType(match.chatType); const matchPrefix = normalizeMatchValue(match.keyPrefix); - if (matchChannel && matchChannel !== channel) continue; - if (matchChatType && matchChatType !== chatType) continue; - if (matchPrefix && !sessionKey.startsWith(matchPrefix)) continue; - if (action === "deny") return "deny"; + if (matchChannel && matchChannel !== channel) { + continue; + } + if (matchChatType && matchChatType !== chatType) { + continue; + } + if (matchPrefix && !sessionKey.startsWith(matchPrefix)) { + continue; + } + if (action === "deny") { + return "deny"; + } allowedMatch = true; } - if (allowedMatch) return "allow"; + if (allowedMatch) { + return "allow"; + } const fallback = normalizeSendPolicy(policy.default); return fallback ?? "allow"; diff --git a/src/sessions/session-key-utils.ts b/src/sessions/session-key-utils.ts index 10baeb607b..ba867f552e 100644 --- a/src/sessions/session-key-utils.ts +++ b/src/sessions/session-key-utils.ts @@ -7,29 +7,45 @@ export function parseAgentSessionKey( sessionKey: string | undefined | null, ): ParsedAgentSessionKey | null { const raw = (sessionKey ?? "").trim(); - if (!raw) return null; + if (!raw) { + return null; + } const parts = raw.split(":").filter(Boolean); - if (parts.length < 3) return null; - if (parts[0] !== "agent") return null; + if (parts.length < 3) { + return null; + } + if (parts[0] !== "agent") { + return null; + } const agentId = parts[1]?.trim(); const rest = parts.slice(2).join(":"); - if (!agentId || !rest) return null; + if (!agentId || !rest) { + return null; + } return { agentId, rest }; } export function isSubagentSessionKey(sessionKey: string | undefined | null): boolean { const raw = (sessionKey ?? "").trim(); - if (!raw) return false; - if (raw.toLowerCase().startsWith("subagent:")) return true; + if (!raw) { + return false; + } + if (raw.toLowerCase().startsWith("subagent:")) { + return true; + } const parsed = parseAgentSessionKey(raw); return Boolean((parsed?.rest ?? "").toLowerCase().startsWith("subagent:")); } export function isAcpSessionKey(sessionKey: string | undefined | null): boolean { const raw = (sessionKey ?? "").trim(); - if (!raw) return false; + if (!raw) { + return false; + } const normalized = raw.toLowerCase(); - if (normalized.startsWith("acp:")) return true; + if (normalized.startsWith("acp:")) { + return true; + } const parsed = parseAgentSessionKey(raw); return Boolean((parsed?.rest ?? "").toLowerCase().startsWith("acp:")); } @@ -40,14 +56,20 @@ export function resolveThreadParentSessionKey( sessionKey: string | undefined | null, ): string | null { const raw = (sessionKey ?? "").trim(); - if (!raw) return null; + if (!raw) { + return null; + } const normalized = raw.toLowerCase(); let idx = -1; for (const marker of THREAD_SESSION_MARKERS) { const candidate = normalized.lastIndexOf(marker); - if (candidate > idx) idx = candidate; + if (candidate > idx) { + idx = candidate; + } + } + if (idx <= 0) { + return null; } - if (idx <= 0) return null; const parent = raw.slice(0, idx).trim(); return parent ? parent : null; } diff --git a/src/sessions/session-label.ts b/src/sessions/session-label.ts index f916737bd1..882e4c98aa 100644 --- a/src/sessions/session-label.ts +++ b/src/sessions/session-label.ts @@ -7,7 +7,9 @@ export function parseSessionLabel(raw: unknown): ParsedSessionLabel { return { ok: false, error: "invalid label: must be a string" }; } const trimmed = raw.trim(); - if (!trimmed) return { ok: false, error: "invalid label: empty" }; + if (!trimmed) { + return { ok: false, error: "invalid label: empty" }; + } if (trimmed.length > SESSION_LABEL_MAX_LENGTH) { return { ok: false, diff --git a/src/sessions/transcript-events.ts b/src/sessions/transcript-events.ts index 88a9cd7b78..d00be113a7 100644 --- a/src/sessions/transcript-events.ts +++ b/src/sessions/transcript-events.ts @@ -15,7 +15,9 @@ export function onSessionTranscriptUpdate(listener: SessionTranscriptListener): export function emitSessionTranscriptUpdate(sessionFile: string): void { const trimmed = sessionFile.trim(); - if (!trimmed) return; + if (!trimmed) { + return; + } const update = { sessionFile: trimmed }; for (const listener of SESSION_TRANSCRIPT_LISTENERS) { listener(update); diff --git a/src/shared/text/reasoning-tags.ts b/src/shared/text/reasoning-tags.ts index afb8f891fe..426d083220 100644 --- a/src/shared/text/reasoning-tags.ts +++ b/src/shared/text/reasoning-tags.ts @@ -38,8 +38,12 @@ function isInsideCode(pos: number, regions: CodeRegion[]): boolean { } function applyTrim(value: string, mode: ReasoningTagTrim): string { - if (mode === "none") return value; - if (mode === "start") return value.trimStart(); + if (mode === "none") { + return value; + } + if (mode === "start") { + return value.trimStart(); + } return value.trim(); } @@ -50,8 +54,12 @@ export function stripReasoningTagsFromText( trim?: ReasoningTagTrim; }, ): string { - if (!text) return text; - if (!QUICK_TAG_RE.test(text)) return text; + if (!text) { + return text; + } + if (!QUICK_TAG_RE.test(text)) { + return text; + } const mode = options?.mode ?? "strict"; const trimMode = options?.trim ?? "both"; diff --git a/src/signal/accounts.ts b/src/signal/accounts.ts index d8396837c0..3d96a3d833 100644 --- a/src/signal/accounts.ts +++ b/src/signal/accounts.ts @@ -13,19 +13,25 @@ export type ResolvedSignalAccount = { function listConfiguredAccountIds(cfg: OpenClawConfig): string[] { const accounts = cfg.channels?.signal?.accounts; - if (!accounts || typeof accounts !== "object") return []; + if (!accounts || typeof accounts !== "object") { + return []; + } return Object.keys(accounts).filter(Boolean); } export function listSignalAccountIds(cfg: OpenClawConfig): string[] { const ids = listConfiguredAccountIds(cfg); - if (ids.length === 0) return [DEFAULT_ACCOUNT_ID]; + if (ids.length === 0) { + return [DEFAULT_ACCOUNT_ID]; + } return ids.toSorted((a, b) => a.localeCompare(b)); } export function resolveDefaultSignalAccountId(cfg: OpenClawConfig): string { const ids = listSignalAccountIds(cfg); - if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID; + if (ids.includes(DEFAULT_ACCOUNT_ID)) { + return DEFAULT_ACCOUNT_ID; + } return ids[0] ?? DEFAULT_ACCOUNT_ID; } @@ -34,7 +40,9 @@ function resolveAccountConfig( accountId: string, ): SignalAccountConfig | undefined { const accounts = cfg.channels?.signal?.accounts; - if (!accounts || typeof accounts !== "object") return undefined; + if (!accounts || typeof accounts !== "object") { + return undefined; + } return accounts[accountId] as SignalAccountConfig | undefined; } diff --git a/src/signal/client.ts b/src/signal/client.ts index 5595edb5b7..59d4541ca4 100644 --- a/src/signal/client.ts +++ b/src/signal/client.ts @@ -33,7 +33,9 @@ function normalizeBaseUrl(url: string): string { if (!trimmed) { throw new Error("Signal base URL is required"); } - if (/^https?:\/\//i.test(trimmed)) return trimmed.replace(/\/+$/, ""); + if (/^https?:\/\//i.test(trimmed)) { + return trimmed.replace(/\/+$/, ""); + } return `http://${trimmed}`.replace(/\/+$/, ""); } @@ -117,7 +119,9 @@ export async function streamSignalEvents(params: { }): Promise { const baseUrl = normalizeBaseUrl(params.baseUrl); const url = new URL(`${baseUrl}/api/v1/events`); - if (params.account) url.searchParams.set("account", params.account); + if (params.account) { + url.searchParams.set("account", params.account); + } const fetchImpl = resolveFetch(); if (!fetchImpl) { @@ -138,7 +142,9 @@ export async function streamSignalEvents(params: { let currentEvent: SignalSseEvent = {}; const flushEvent = () => { - if (!currentEvent.data && !currentEvent.event && !currentEvent.id) return; + if (!currentEvent.data && !currentEvent.event && !currentEvent.id) { + return; + } params.onEvent({ event: currentEvent.event, data: currentEvent.data, @@ -149,13 +155,17 @@ export async function streamSignalEvents(params: { while (true) { const { value, done } = await reader.read(); - if (done) break; + if (done) { + break; + } buffer += decoder.decode(value, { stream: true }); let lineEnd = buffer.indexOf("\n"); while (lineEnd !== -1) { let line = buffer.slice(0, lineEnd); buffer = buffer.slice(lineEnd + 1); - if (line.endsWith("\r")) line = line.slice(0, -1); + if (line.endsWith("\r")) { + line = line.slice(0, -1); + } if (line === "") { flushEvent(); diff --git a/src/signal/daemon.ts b/src/signal/daemon.ts index ca1b01b60a..7f1311a9c3 100644 --- a/src/signal/daemon.ts +++ b/src/signal/daemon.ts @@ -20,11 +20,17 @@ export type SignalDaemonHandle = { export function classifySignalCliLogLine(line: string): "log" | "error" | null { const trimmed = line.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } // signal-cli commonly writes all logs to stderr; treat severity explicitly. - if (/\b(ERROR|WARN|WARNING)\b/.test(trimmed)) return "error"; + if (/\b(ERROR|WARN|WARNING)\b/.test(trimmed)) { + return "error"; + } // Some signal-cli failures are not tagged with WARN/ERROR but should still be surfaced loudly. - if (/\b(FAILED|SEVERE|EXCEPTION)\b/i.test(trimmed)) return "error"; + if (/\b(FAILED|SEVERE|EXCEPTION)\b/i.test(trimmed)) { + return "error"; + } return "log"; } @@ -40,9 +46,15 @@ function buildDaemonArgs(opts: SignalDaemonOpts): string[] { if (opts.receiveMode) { args.push("--receive-mode", opts.receiveMode); } - if (opts.ignoreAttachments) args.push("--ignore-attachments"); - if (opts.ignoreStories) args.push("--ignore-stories"); - if (opts.sendReadReceipts) args.push("--send-read-receipts"); + if (opts.ignoreAttachments) { + args.push("--ignore-attachments"); + } + if (opts.ignoreStories) { + args.push("--ignore-stories"); + } + if (opts.sendReadReceipts) { + args.push("--send-read-receipts"); + } return args; } @@ -58,15 +70,21 @@ export function spawnSignalDaemon(opts: SignalDaemonOpts): SignalDaemonHandle { child.stdout?.on("data", (data) => { for (const line of data.toString().split(/\r?\n/)) { const kind = classifySignalCliLogLine(line); - if (kind === "log") log(`signal-cli: ${line.trim()}`); - else if (kind === "error") error(`signal-cli: ${line.trim()}`); + if (kind === "log") { + log(`signal-cli: ${line.trim()}`); + } else if (kind === "error") { + error(`signal-cli: ${line.trim()}`); + } } }); child.stderr?.on("data", (data) => { for (const line of data.toString().split(/\r?\n/)) { const kind = classifySignalCliLogLine(line); - if (kind === "log") log(`signal-cli: ${line.trim()}`); - else if (kind === "error") error(`signal-cli: ${line.trim()}`); + if (kind === "log") { + log(`signal-cli: ${line.trim()}`); + } else if (kind === "error") { + error(`signal-cli: ${line.trim()}`); + } } }); child.on("error", (err) => { diff --git a/src/signal/format.ts b/src/signal/format.ts index c99fa47f68..51c9516fa5 100644 --- a/src/signal/format.ts +++ b/src/signal/format.ts @@ -54,8 +54,12 @@ function mapStyle(style: MarkdownStyle): SignalTextStyle | null { function mergeStyles(styles: SignalTextStyleRange[]): SignalTextStyleRange[] { const sorted = [...styles].toSorted((a, b) => { - if (a.start !== b.start) return a.start - b.start; - if (a.length !== b.length) return a.length - b.length; + if (a.start !== b.start) { + return a.start - b.start; + } + if (a.length !== b.length) { + return a.length - b.length; + } return a.style.localeCompare(b.style); }); @@ -80,7 +84,9 @@ function clampStyles(styles: SignalTextStyleRange[], maxLength: number): SignalT const start = Math.max(0, Math.min(style.start, maxLength)); const end = Math.min(style.start + style.length, maxLength); const length = end - start; - if (length > 0) clamped.push({ start, length, style: style.style }); + if (length > 0) { + clamped.push({ start, length, style: style.style }); + } } return clamped; } @@ -89,7 +95,9 @@ function applyInsertionsToStyles( spans: SignalStyleSpan[], insertions: Insertion[], ): SignalStyleSpan[] { - if (insertions.length === 0) return spans; + if (insertions.length === 0) { + return spans; + } const sortedInsertions = [...insertions].toSorted((a, b) => a.pos - b.pos); let updated = spans; @@ -135,7 +143,9 @@ function applyInsertionsToStyles( function renderSignalText(ir: MarkdownIR): SignalFormattedText { const text = ir.text ?? ""; - if (!text) return { text: "", styles: [] }; + if (!text) { + return { text: "", styles: [] }; + } const sortedLinks = [...ir.links].toSorted((a, b) => a.start - b.start); let out = ""; @@ -143,7 +153,9 @@ function renderSignalText(ir: MarkdownIR): SignalFormattedText { const insertions: Insertion[] = []; for (const link of sortedLinks) { - if (link.start < cursor) continue; + if (link.start < cursor) { + continue; + } out += text.slice(cursor, link.end); const href = link.href.trim(); @@ -170,7 +182,9 @@ function renderSignalText(ir: MarkdownIR): SignalFormattedText { const mappedStyles: SignalStyleSpan[] = ir.styles .map((span) => { const mapped = mapStyle(span.style); - if (!mapped) return null; + if (!mapped) { + return null; + } return { start: span.start, end: span.end, style: mapped }; }) .filter((span): span is SignalStyleSpan => span !== null); diff --git a/src/signal/identity.ts b/src/signal/identity.ts index ca833943dc..95d27e042c 100644 --- a/src/signal/identity.ts +++ b/src/signal/identity.ts @@ -17,7 +17,9 @@ function looksLikeUuid(value: string): boolean { return true; } const compact = value.replace(/-/g, ""); - if (!/^[0-9a-f]+$/i.test(compact)) return false; + if (!/^[0-9a-f]+$/i.test(compact)) { + return false; + } return /[a-f]/i.test(compact); } @@ -69,14 +71,20 @@ export function resolveSignalPeerId(sender: SignalSender): string { function parseSignalAllowEntry(entry: string): SignalAllowEntry | null { const trimmed = entry.trim(); - if (!trimmed) return null; - if (trimmed === "*") return { kind: "any" }; + if (!trimmed) { + return null; + } + if (trimmed === "*") { + return { kind: "any" }; + } const stripped = stripSignalPrefix(trimmed); const lower = stripped.toLowerCase(); if (lower.startsWith("uuid:")) { const raw = stripped.slice("uuid:".length).trim(); - if (!raw) return null; + if (!raw) { + return null; + } return { kind: "uuid", raw }; } @@ -88,11 +96,15 @@ function parseSignalAllowEntry(entry: string): SignalAllowEntry | null { } export function isSignalSenderAllowed(sender: SignalSender, allowFrom: string[]): boolean { - if (allowFrom.length === 0) return false; + if (allowFrom.length === 0) { + return false; + } const parsed = allowFrom .map(parseSignalAllowEntry) .filter((entry): entry is SignalAllowEntry => entry !== null); - if (parsed.some((entry) => entry.kind === "any")) return true; + if (parsed.some((entry) => entry.kind === "any")) { + return true; + } return parsed.some((entry) => { if (entry.kind === "phone" && sender.kind === "phone") { return entry.e164 === sender.e164; @@ -110,8 +122,14 @@ export function isSignalGroupAllowed(params: { sender: SignalSender; }): boolean { const { groupPolicy, allowFrom, sender } = params; - if (groupPolicy === "disabled") return false; - if (groupPolicy === "open") return true; - if (allowFrom.length === 0) return false; + if (groupPolicy === "disabled") { + return false; + } + if (groupPolicy === "open") { + return true; + } + if (allowFrom.length === 0) { + return false; + } return isSignalSenderAllowed(sender, allowFrom); } diff --git a/src/signal/monitor.ts b/src/signal/monitor.ts index fee510d022..a96d6f4eb0 100644 --- a/src/signal/monitor.ts +++ b/src/signal/monitor.ts @@ -95,7 +95,9 @@ function resolveSignalReactionTargets(reaction: SignalReactionMessage): SignalRe function isSignalReactionMessage( reaction: SignalReactionMessage | null | undefined, ): reaction is SignalReactionMessage { - if (!reaction) return false; + if (!reaction) { + return false; + } const emoji = reaction.emoji?.trim(); const timestamp = reaction.targetSentTimestamp; const hasTarget = Boolean(reaction.targetAuthor?.trim() || reaction.targetAuthorUuid?.trim()); @@ -111,10 +113,14 @@ function shouldEmitSignalReactionNotification(params: { }) { const { mode, account, targets, sender, allowlist } = params; const effectiveMode = mode ?? "own"; - if (effectiveMode === "off") return false; + if (effectiveMode === "off") { + return false; + } if (effectiveMode === "own") { const accountId = account?.trim(); - if (!accountId || !targets || targets.length === 0) return false; + if (!accountId || !targets || targets.length === 0) { + return false; + } const normalizedAccount = normalizeE164(accountId); return targets.some((target) => { if (target.kind === "uuid") { @@ -124,7 +130,9 @@ function shouldEmitSignalReactionNotification(params: { }); } if (effectiveMode === "allowlist") { - if (!sender || !allowlist || allowlist.length === 0) return false; + if (!sender || !allowlist || allowlist.length === 0) { + return false; + } return isSignalSenderAllowed(sender, allowlist); } return true; @@ -160,7 +168,9 @@ async function waitForSignalDaemonReady(params: { runtime: params.runtime, check: async () => { const res = await signalCheck(params.baseUrl, 1000); - if (res.ok) return { ok: true }; + if (res.ok) { + return { ok: true }; + } return { ok: false, error: res.error ?? (res.status ? `HTTP ${res.status}` : "unreachable"), @@ -178,7 +188,9 @@ async function fetchAttachment(params: { maxBytes: number; }): Promise<{ path: string; contentType?: string } | null> { const { attachment } = params; - if (!attachment?.id) return null; + if (!attachment?.id) { + return null; + } if (attachment.size && attachment.size > params.maxBytes) { throw new Error( `Signal attachment ${attachment.id} exceeds ${(params.maxBytes / (1024 * 1024)).toFixed(0)}MB limit`, @@ -187,15 +199,23 @@ async function fetchAttachment(params: { const rpcParams: Record = { id: attachment.id, }; - if (params.account) rpcParams.account = params.account; - if (params.groupId) rpcParams.groupId = params.groupId; - else if (params.sender) rpcParams.recipient = params.sender; - else return null; + if (params.account) { + rpcParams.account = params.account; + } + if (params.groupId) { + rpcParams.groupId = params.groupId; + } else if (params.sender) { + rpcParams.recipient = params.sender; + } else { + return null; + } const result = await signalRpcRequest<{ data?: string }>("getAttachment", rpcParams, { baseUrl: params.baseUrl, }); - if (!result?.data) return null; + if (!result?.data) { + return null; + } const buffer = Buffer.from(result.data, "base64"); const saved = await saveMediaBuffer( buffer, @@ -222,7 +242,9 @@ async function deliverReplies(params: { for (const payload of replies) { const mediaList = payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []); const text = payload.text ?? ""; - if (!text && mediaList.length === 0) continue; + if (!text && mediaList.length === 0) { + continue; + } if (mediaList.length === 0) { for (const chunk of chunkTextWithMode(text, textLimit, chunkMode)) { await sendMessageSignal(target, chunk, { @@ -367,7 +389,9 @@ export async function monitorSignalProvider(opts: MonitorSignalOpts = {}): Promi }, }); } catch (err) { - if (opts.abortSignal?.aborted) return; + if (opts.abortSignal?.aborted) { + return; + } throw err; } finally { opts.abortSignal?.removeEventListener("abort", onAbort); diff --git a/src/signal/monitor/event-handler.ts b/src/signal/monitor/event-handler.ts index 72195ff780..4c1ae1be97 100644 --- a/src/signal/monitor/event-handler.ts +++ b/src/signal/monitor/event-handler.ts @@ -176,7 +176,9 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) { const typingCallbacks = createTypingCallbacks({ start: async () => { - if (!ctxPayload.To) return; + if (!ctxPayload.To) { + return; + } await sendTypingSignal(ctxPayload.To, { baseUrl: deps.baseUrl, account: deps.account, @@ -252,17 +254,25 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) { debounceMs: inboundDebounceMs, buildKey: (entry) => { const conversationId = entry.isGroup ? (entry.groupId ?? "unknown") : entry.senderPeerId; - if (!conversationId || !entry.senderPeerId) return null; + if (!conversationId || !entry.senderPeerId) { + return null; + } return `signal:${deps.accountId}:${conversationId}:${entry.senderPeerId}`; }, shouldDebounce: (entry) => { - if (!entry.bodyText.trim()) return false; - if (entry.mediaPath || entry.mediaType) return false; + if (!entry.bodyText.trim()) { + return false; + } + if (entry.mediaPath || entry.mediaType) { + return false; + } return !hasControlCommand(entry.bodyText, deps.cfg); }, onFlush: async (entries) => { const last = entries.at(-1); - if (!last) return; + if (!last) { + return; + } if (entries.length === 1) { await handleSignalInboundMessage(last); return; @@ -271,7 +281,9 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) { .map((entry) => entry.bodyText) .filter(Boolean) .join("\\n"); - if (!combinedText.trim()) return; + if (!combinedText.trim()) { + return; + } await handleSignalInboundMessage({ ...last, bodyText: combinedText, @@ -285,7 +297,9 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) { }); return async (event: { event?: string; data?: string }) => { - if (event.event !== "receive" || !event.data) return; + if (event.event !== "receive" || !event.data) { + return; + } let payload: SignalReceivePayload | null = null; try { @@ -298,13 +312,21 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) { deps.runtime.error?.(`receive exception: ${payload.exception.message}`); } const envelope = payload?.envelope; - if (!envelope) return; - if (envelope.syncMessage) return; + if (!envelope) { + return; + } + if (envelope.syncMessage) { + return; + } const sender = resolveSignalSender(envelope); - if (!sender) return; + if (!sender) { + return; + } if (deps.account && sender.kind === "phone") { - if (sender.e164 === normalizeE164(deps.account)) return; + if (sender.e164 === normalizeE164(deps.account)) { + return; + } } const dataMessage = envelope.dataMessage ?? envelope.editMessage?.dataMessage; @@ -319,7 +341,9 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) { Boolean(messageText || quoteText) || Boolean(!reaction && dataMessage?.attachments?.length); if (reaction && !hasBodyContent) { - if (reaction.isRemove) return; // Ignore reaction removals + if (reaction.isRemove) { + return; + } // Ignore reaction removals const emojiLabel = reaction.emoji?.trim() || "emoji"; const senderDisplay = formatSignalSenderDisplay(sender); const senderName = envelope.sourceName ?? senderDisplay; @@ -332,7 +356,9 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) { sender, allowlist: deps.reactionAllowlist, }); - if (!shouldNotify) return; + if (!shouldNotify) { + return; + } const groupId = reaction.groupInfo?.groupId ?? undefined; const groupName = reaction.groupInfo?.groupName ?? undefined; @@ -373,13 +399,17 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) { enqueueSystemEvent(text, { sessionKey: route.sessionKey, contextKey }); return; } - if (!dataMessage) return; + if (!dataMessage) { + return; + } const senderDisplay = formatSignalSenderDisplay(sender); const senderRecipient = resolveSignalRecipient(sender); const senderPeerId = resolveSignalPeerId(sender); const senderAllowId = formatSignalSenderId(sender); - if (!senderRecipient) return; + if (!senderRecipient) { + return; + } const senderIdLine = formatSignalPairingIdLine(sender); const groupId = dataMessage.groupInfo?.groupId ?? undefined; const groupName = dataMessage.groupInfo?.groupName ?? undefined; @@ -391,7 +421,9 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) { deps.dmPolicy === "open" ? true : isSignalSenderAllowed(sender, effectiveDmAllow); if (!isGroup) { - if (deps.dmPolicy === "disabled") return; + if (deps.dmPolicy === "disabled") { + return; + } if (!dmAllowed) { if (deps.dmPolicy === "pairing") { const senderId = senderAllowId; @@ -490,11 +522,16 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) { } const kind = mediaKindFromMime(mediaType ?? undefined); - if (kind) placeholder = ``; - else if (dataMessage.attachments?.length) placeholder = ""; + if (kind) { + placeholder = ``; + } else if (dataMessage.attachments?.length) { + placeholder = ""; + } const bodyText = messageText || placeholder || dataMessage.quote?.text?.trim() || ""; - if (!bodyText) return; + if (!bodyText) { + return; + } const receiptTimestamp = typeof envelope.timestamp === "number" diff --git a/src/signal/probe.ts b/src/signal/probe.ts index 3892de3344..9a6238048a 100644 --- a/src/signal/probe.ts +++ b/src/signal/probe.ts @@ -9,10 +9,14 @@ export type SignalProbe = { }; function parseSignalVersion(value: unknown): string | null { - if (typeof value === "string" && value.trim()) return value.trim(); + if (typeof value === "string" && value.trim()) { + return value.trim(); + } if (typeof value === "object" && value !== null) { const version = (value as { version?: unknown }).version; - if (typeof version === "string" && version.trim()) return version.trim(); + if (typeof version === "string" && version.trim()) { + return version.trim(); + } } return null; } diff --git a/src/signal/send-reactions.ts b/src/signal/send-reactions.ts index 0caf606ea7..3298329320 100644 --- a/src/signal/send-reactions.ts +++ b/src/signal/send-reactions.ts @@ -23,13 +23,17 @@ export type SignalReactionResult = { function normalizeSignalId(raw: string): string { const trimmed = raw.trim(); - if (!trimmed) return ""; + if (!trimmed) { + return ""; + } return trimmed.replace(/^signal:/i, "").trim(); } function normalizeSignalUuid(raw: string): string { const trimmed = normalizeSignalId(raw); - if (!trimmed) return ""; + if (!trimmed) { + return ""; + } if (trimmed.toLowerCase().startsWith("uuid:")) { return trimmed.slice("uuid:".length).trim(); } @@ -44,9 +48,13 @@ function resolveTargetAuthorParams(params: { const candidates = [params.targetAuthor, params.targetAuthorUuid, params.fallback]; for (const candidate of candidates) { const raw = candidate?.trim(); - if (!raw) continue; + if (!raw) { + continue; + } const normalized = normalizeSignalUuid(raw); - if (normalized) return { targetAuthor: normalized }; + if (normalized) { + return { targetAuthor: normalized }; + } } return {}; } @@ -118,9 +126,15 @@ export async function sendReactionSignal( targetTimestamp, ...targetAuthorParams, }; - if (normalizedRecipient) params.recipients = [normalizedRecipient]; - if (groupId) params.groupIds = [groupId]; - if (account) params.account = account; + if (normalizedRecipient) { + params.recipients = [normalizedRecipient]; + } + if (groupId) { + params.groupIds = [groupId]; + } + if (account) { + params.account = account; + } const result = await signalRpcRequest<{ timestamp?: number }>("sendReaction", params, { baseUrl, @@ -179,9 +193,15 @@ export async function removeReactionSignal( remove: true, ...targetAuthorParams, }; - if (normalizedRecipient) params.recipients = [normalizedRecipient]; - if (groupId) params.groupIds = [groupId]; - if (account) params.account = account; + if (normalizedRecipient) { + params.recipients = [normalizedRecipient]; + } + if (groupId) { + params.groupIds = [groupId]; + } + if (account) { + params.account = account; + } const result = await signalRpcRequest<{ timestamp?: number }>("sendReaction", params, { baseUrl, diff --git a/src/signal/send.ts b/src/signal/send.ts index 32ca09094f..045c572e9f 100644 --- a/src/signal/send.ts +++ b/src/signal/send.ts @@ -34,7 +34,9 @@ type SignalTarget = function parseTarget(raw: string): SignalTarget { let value = raw.trim(); - if (!value) throw new Error("Signal recipient is required"); + if (!value) { + throw new Error("Signal recipient is required"); + } const lower = value.toLowerCase(); if (lower.startsWith("signal:")) { value = value.slice("signal:".length).trim(); @@ -72,15 +74,21 @@ function buildTargetParams( allow: SignalTargetAllowlist, ): SignalTargetParams | null { if (target.type === "recipient") { - if (!allow.recipient) return null; + if (!allow.recipient) { + return null; + } return { recipient: [target.recipient] }; } if (target.type === "group") { - if (!allow.group) return null; + if (!allow.group) { + return null; + } return { groupId: target.groupId }; } if (target.type === "username") { - if (!allow.username) return null; + if (!allow.username) { + return null; + } return { username: [target.username] }; } return null; @@ -139,7 +147,9 @@ export async function sendMessageSignal( let textStyles: SignalTextStyleRange[] = []; const textMode = opts.textMode ?? "markdown"; const maxBytes = (() => { - if (typeof opts.maxBytes === "number") return opts.maxBytes; + if (typeof opts.maxBytes === "number") { + return opts.maxBytes; + } if (typeof accountInfo.config.mediaMaxMb === "number") { return accountInfo.config.mediaMaxMb * 1024 * 1024; } @@ -186,7 +196,9 @@ export async function sendMessageSignal( (style) => `${style.start}:${style.length}:${style.style}`, ); } - if (account) params.account = account; + if (account) { + params.account = account; + } if (attachments && attachments.length > 0) { params.attachments = attachments; } @@ -221,10 +233,16 @@ export async function sendTypingSignal( recipient: true, group: true, }); - if (!targetParams) return false; + if (!targetParams) { + return false; + } const params: Record = { ...targetParams }; - if (account) params.account = account; - if (opts.stop) params.stop = true; + if (account) { + params.account = account; + } + if (opts.stop) { + params.stop = true; + } await signalRpcRequest("sendTyping", params, { baseUrl, timeoutMs: opts.timeoutMs, @@ -237,18 +255,24 @@ export async function sendReadReceiptSignal( targetTimestamp: number, opts: SignalRpcOpts & { type?: SignalReceiptType } = {}, ): Promise { - if (!Number.isFinite(targetTimestamp) || targetTimestamp <= 0) return false; + if (!Number.isFinite(targetTimestamp) || targetTimestamp <= 0) { + return false; + } const { baseUrl, account } = resolveSignalRpcContext(opts); const targetParams = buildTargetParams(parseTarget(to), { recipient: true, }); - if (!targetParams) return false; + if (!targetParams) { + return false; + } const params: Record = { ...targetParams, targetTimestamp, type: opts.type ?? "read", }; - if (account) params.account = account; + if (account) { + params.account = account; + } await signalRpcRequest("sendReceipt", params, { baseUrl, timeoutMs: opts.timeoutMs, diff --git a/src/signal/sse-reconnect.ts b/src/signal/sse-reconnect.ts index 9b333f77bb..f119388f3d 100644 --- a/src/signal/sse-reconnect.ts +++ b/src/signal/sse-reconnect.ts @@ -35,7 +35,9 @@ export async function runSignalSseLoop({ let reconnectAttempts = 0; const logReconnectVerbose = (message: string) => { - if (!shouldLogVerbose()) return; + if (!shouldLogVerbose()) { + return; + } logVerbose(message); }; @@ -50,13 +52,17 @@ export async function runSignalSseLoop({ onEvent(event); }, }); - if (abortSignal?.aborted) return; + if (abortSignal?.aborted) { + return; + } reconnectAttempts += 1; const delayMs = computeBackoff(reconnectPolicy, reconnectAttempts); logReconnectVerbose(`Signal SSE stream ended, reconnecting in ${delayMs / 1000}s...`); await sleepWithAbort(delayMs, abortSignal); } catch (err) { - if (abortSignal?.aborted) return; + if (abortSignal?.aborted) { + return; + } runtime.error?.(`Signal SSE stream error: ${String(err)}`); reconnectAttempts += 1; const delayMs = computeBackoff(reconnectPolicy, reconnectAttempts); @@ -64,7 +70,9 @@ export async function runSignalSseLoop({ try { await sleepWithAbort(delayMs, abortSignal); } catch (sleepErr) { - if (abortSignal?.aborted) return; + if (abortSignal?.aborted) { + return; + } throw sleepErr; } } diff --git a/src/slack/accounts.ts b/src/slack/accounts.ts index e6e1e715b1..f492b25269 100644 --- a/src/slack/accounts.ts +++ b/src/slack/accounts.ts @@ -30,19 +30,25 @@ export type ResolvedSlackAccount = { function listConfiguredAccountIds(cfg: OpenClawConfig): string[] { const accounts = cfg.channels?.slack?.accounts; - if (!accounts || typeof accounts !== "object") return []; + if (!accounts || typeof accounts !== "object") { + return []; + } return Object.keys(accounts).filter(Boolean); } export function listSlackAccountIds(cfg: OpenClawConfig): string[] { const ids = listConfiguredAccountIds(cfg); - if (ids.length === 0) return [DEFAULT_ACCOUNT_ID]; + if (ids.length === 0) { + return [DEFAULT_ACCOUNT_ID]; + } return ids.toSorted((a, b) => a.localeCompare(b)); } export function resolveDefaultSlackAccountId(cfg: OpenClawConfig): string { const ids = listSlackAccountIds(cfg); - if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID; + if (ids.includes(DEFAULT_ACCOUNT_ID)) { + return DEFAULT_ACCOUNT_ID; + } return ids[0] ?? DEFAULT_ACCOUNT_ID; } @@ -51,7 +57,9 @@ function resolveAccountConfig( accountId: string, ): SlackAccountConfig | undefined { const accounts = cfg.channels?.slack?.accounts; - if (!accounts || typeof accounts !== "object") return undefined; + if (!accounts || typeof accounts !== "object") { + return undefined; + } return accounts[accountId] as SlackAccountConfig | undefined; } diff --git a/src/slack/actions.ts b/src/slack/actions.ts index 4ce6b37ca7..a8ef335d65 100644 --- a/src/slack/actions.ts +++ b/src/slack/actions.ts @@ -107,13 +107,17 @@ export async function removeOwnSlackReactions( const toRemove = new Set(); for (const reaction of reactions ?? []) { const name = reaction?.name; - if (!name) continue; + if (!name) { + continue; + } const users = reaction?.users ?? []; if (users.includes(userId)) { toRemove.add(name); } } - if (toRemove.size === 0) return []; + if (toRemove.size === 0) { + return []; + } await Promise.all( Array.from(toRemove, (name) => client.reactions.remove({ diff --git a/src/slack/channel-migration.ts b/src/slack/channel-migration.ts index 78bc510f5c..09017e0617 100644 --- a/src/slack/channel-migration.ts +++ b/src/slack/channel-migration.ts @@ -16,12 +16,18 @@ function resolveAccountChannels( cfg: OpenClawConfig, accountId?: string | null, ): { channels?: SlackChannels } { - if (!accountId) return {}; + if (!accountId) { + return {}; + } const normalized = normalizeAccountId(accountId); const accounts = cfg.channels?.slack?.accounts; - if (!accounts || typeof accounts !== "object") return {}; + if (!accounts || typeof accounts !== "object") { + return {}; + } const exact = accounts[normalized]; - if (exact?.channels) return { channels: exact.channels }; + if (exact?.channels) { + return { channels: exact.channels }; + } const matchKey = Object.keys(accounts).find( (key) => key.toLowerCase() === normalized.toLowerCase(), ); @@ -33,10 +39,18 @@ export function migrateSlackChannelsInPlace( oldChannelId: string, newChannelId: string, ): { migrated: boolean; skippedExisting: boolean } { - if (!channels) return { migrated: false, skippedExisting: false }; - if (oldChannelId === newChannelId) return { migrated: false, skippedExisting: false }; - if (!Object.hasOwn(channels, oldChannelId)) return { migrated: false, skippedExisting: false }; - if (Object.hasOwn(channels, newChannelId)) return { migrated: false, skippedExisting: true }; + if (!channels) { + return { migrated: false, skippedExisting: false }; + } + if (oldChannelId === newChannelId) { + return { migrated: false, skippedExisting: false }; + } + if (!Object.hasOwn(channels, oldChannelId)) { + return { migrated: false, skippedExisting: false }; + } + if (Object.hasOwn(channels, newChannelId)) { + return { migrated: false, skippedExisting: true }; + } channels[newChannelId] = channels[oldChannelId]; delete channels[oldChannelId]; return { migrated: true, skippedExisting: false }; @@ -63,7 +77,9 @@ export function migrateSlackChannelConfig(params: { migrated = true; scopes.push("account"); } - if (result.skippedExisting) skippedExisting = true; + if (result.skippedExisting) { + skippedExisting = true; + } } const globalChannels = params.cfg.channels?.slack?.channels; @@ -77,7 +93,9 @@ export function migrateSlackChannelConfig(params: { migrated = true; scopes.push("global"); } - if (result.skippedExisting) skippedExisting = true; + if (result.skippedExisting) { + skippedExisting = true; + } } return { migrated, skippedExisting, scopes }; diff --git a/src/slack/directory-live.ts b/src/slack/directory-live.ts index 57da909e00..1f2265bf6f 100644 --- a/src/slack/directory-live.ts +++ b/src/slack/directory-live.ts @@ -47,8 +47,12 @@ function normalizeQuery(value?: string | null): string { function buildUserRank(user: SlackUser): number { let rank = 0; - if (!user.deleted) rank += 2; - if (!user.is_bot && !user.is_app_user) rank += 1; + if (!user.deleted) { + rank += 2; + } + if (!user.is_bot && !user.is_app_user) { + rank += 1; + } return rank; } @@ -60,7 +64,9 @@ export async function listSlackDirectoryPeersLive( params: DirectoryConfigParams, ): Promise { const token = resolveReadToken(params); - if (!token) return []; + if (!token) { + return []; + } const client = createSlackWebClient(token); const query = normalizeQuery(params.query); const members: SlackUser[] = []; @@ -71,7 +77,9 @@ export async function listSlackDirectoryPeersLive( limit: 200, cursor, })) as SlackListUsersResponse; - if (Array.isArray(res.members)) members.push(...res.members); + if (Array.isArray(res.members)) { + members.push(...res.members); + } const next = res.response_metadata?.next_cursor?.trim(); cursor = next ? next : undefined; } while (cursor); @@ -83,14 +91,18 @@ export async function listSlackDirectoryPeersLive( const candidates = [name, handle, email] .map((item) => item?.trim().toLowerCase()) .filter(Boolean); - if (!query) return true; + if (!query) { + return true; + } return candidates.some((candidate) => candidate?.includes(query)); }); const rows = filtered .map((member) => { const id = member.id?.trim(); - if (!id) return null; + if (!id) { + return null; + } const handle = member.name?.trim(); const display = member.profile?.display_name?.trim() || @@ -118,7 +130,9 @@ export async function listSlackDirectoryGroupsLive( params: DirectoryConfigParams, ): Promise { const token = resolveReadToken(params); - if (!token) return []; + if (!token) { + return []; + } const client = createSlackWebClient(token); const query = normalizeQuery(params.query); const channels: SlackChannel[] = []; @@ -131,14 +145,18 @@ export async function listSlackDirectoryGroupsLive( limit: 1000, cursor, })) as SlackListChannelsResponse; - if (Array.isArray(res.channels)) channels.push(...res.channels); + if (Array.isArray(res.channels)) { + channels.push(...res.channels); + } const next = res.response_metadata?.next_cursor?.trim(); cursor = next ? next : undefined; } while (cursor); const filtered = channels.filter((channel) => { const name = channel.name?.trim().toLowerCase(); - if (!query) return true; + if (!query) { + return true; + } return Boolean(name && name.includes(query)); }); @@ -146,7 +164,9 @@ export async function listSlackDirectoryGroupsLive( .map((channel) => { const id = channel.id?.trim(); const name = channel.name?.trim(); - if (!id || !name) return null; + if (!id || !name) { + return null; + } return { kind: "group", id: `channel:${id}`, diff --git a/src/slack/format.ts b/src/slack/format.ts index 7f44b5df21..69e2ebe4d4 100644 --- a/src/slack/format.ts +++ b/src/slack/format.ts @@ -11,7 +11,9 @@ function escapeSlackMrkdwnSegment(text: string): string { const SLACK_ANGLE_TOKEN_RE = /<[^>\n]+>/g; function isAllowedSlackAngleToken(token: string): boolean { - if (!token.startsWith("<") || !token.endsWith(">")) return false; + if (!token.startsWith("<") || !token.endsWith(">")) { + return false; + } const inner = token.slice(1, -1); return ( inner.startsWith("@") || @@ -68,13 +70,17 @@ function escapeSlackMrkdwnText(text: string): string { function buildSlackLink(link: MarkdownLinkSpan, text: string) { const href = link.href.trim(); - if (!href) return null; + if (!href) { + return null; + } const label = text.slice(link.start, link.end); const trimmedLabel = label.trim(); const comparableHref = href.startsWith("mailto:") ? href.slice("mailto:".length) : href; const useMarkup = trimmedLabel.length > 0 && trimmedLabel !== href && trimmedLabel !== comparableHref; - if (!useMarkup) return null; + if (!useMarkup) { + return null; + } const safeHref = escapeSlackMrkdwnSegment(href); return { start: link.start, diff --git a/src/slack/http/registry.test.ts b/src/slack/http/registry.test.ts index 9deee5b74e..eb34784171 100644 --- a/src/slack/http/registry.test.ts +++ b/src/slack/http/registry.test.ts @@ -23,7 +23,9 @@ describe("registerSlackHttpHandler", () => { const unregisters: Array<() => void> = []; afterEach(() => { - for (const unregister of unregisters.splice(0)) unregister(); + for (const unregister of unregisters.splice(0)) { + unregister(); + } }); it("routes requests to a registered handler", async () => { diff --git a/src/slack/http/registry.ts b/src/slack/http/registry.ts index 630c52fec7..dadf8e56c7 100644 --- a/src/slack/http/registry.ts +++ b/src/slack/http/registry.ts @@ -16,7 +16,9 @@ const slackHttpRoutes = new Map(); export function normalizeSlackWebhookPath(path?: string | null): string { const trimmed = path?.trim(); - if (!trimmed) return "/slack/events"; + if (!trimmed) { + return "/slack/events"; + } return trimmed.startsWith("/") ? trimmed : `/${trimmed}`; } @@ -39,7 +41,9 @@ export async function handleSlackHttpRequest( ): Promise { const url = new URL(req.url ?? "/", "http://localhost"); const handler = slackHttpRoutes.get(url.pathname); - if (!handler) return false; + if (!handler) { + return false; + } await handler(req, res); return true; } diff --git a/src/slack/monitor.test-helpers.ts b/src/slack/monitor.test-helpers.ts index 8d37bd9363..8f81c27ded 100644 --- a/src/slack/monitor.test-helpers.ts +++ b/src/slack/monitor.test-helpers.ts @@ -28,7 +28,9 @@ export const flush = () => new Promise((resolve) => setTimeout(resolve, 0)); export async function waitForSlackEvent(name: string) { for (let i = 0; i < 10; i += 1) { - if (getSlackHandlers()?.has(name)) return; + if (getSlackHandlers()?.has(name)) { + return; + } await flush(); } } diff --git a/src/slack/monitor.threading.missing-thread-ts.test.ts b/src/slack/monitor.threading.missing-thread-ts.test.ts index 8c0454b3ae..c20ed94141 100644 --- a/src/slack/monitor.threading.missing-thread-ts.test.ts +++ b/src/slack/monitor.threading.missing-thread-ts.test.ts @@ -106,7 +106,9 @@ const flush = () => new Promise((resolve) => setTimeout(resolve, 0)); async function waitForEvent(name: string) { for (let i = 0; i < 10; i += 1) { - if (getSlackHandlers()?.has(name)) return; + if (getSlackHandlers()?.has(name)) { + return; + } await flush(); } } @@ -137,7 +139,9 @@ describe("monitorSlackProvider threading", () => { replyMock.mockResolvedValue({ text: "thread reply" }); const client = getSlackClient(); - if (!client) throw new Error("Slack client not registered"); + if (!client) { + throw new Error("Slack client not registered"); + } const conversations = client.conversations as { history: ReturnType; }; @@ -154,7 +158,9 @@ describe("monitorSlackProvider threading", () => { await waitForEvent("message"); const handler = getSlackHandlers()?.get("message"); - if (!handler) throw new Error("Slack message handler not registered"); + if (!handler) { + throw new Error("Slack message handler not registered"); + } await handler({ event: { diff --git a/src/slack/monitor.tool-result.forces-thread-replies-replytoid-is-set.test.ts b/src/slack/monitor.tool-result.forces-thread-replies-replytoid-is-set.test.ts index c4b4b96ed9..4626bbe1ed 100644 --- a/src/slack/monitor.tool-result.forces-thread-replies-replytoid-is-set.test.ts +++ b/src/slack/monitor.tool-result.forces-thread-replies-replytoid-is-set.test.ts @@ -46,7 +46,9 @@ describe("monitorSlackProvider tool results", () => { await waitForSlackEvent("message"); const handler = getSlackHandlers()?.get("message"); - if (!handler) throw new Error("Slack message handler not registered"); + if (!handler) { + throw new Error("Slack message handler not registered"); + } await handler({ event: { @@ -70,7 +72,9 @@ describe("monitorSlackProvider tool results", () => { it("reacts to mention-gated room messages when ackReaction is enabled", async () => { replyMock.mockResolvedValue(undefined); const client = getSlackClient(); - if (!client) throw new Error("Slack client not registered"); + if (!client) { + throw new Error("Slack client not registered"); + } const conversations = client.conversations as { info: ReturnType; }; @@ -87,7 +91,9 @@ describe("monitorSlackProvider tool results", () => { await waitForSlackEvent("message"); const handler = getSlackHandlers()?.get("message"); - if (!handler) throw new Error("Slack message handler not registered"); + if (!handler) { + throw new Error("Slack message handler not registered"); + } await handler({ event: { @@ -132,7 +138,9 @@ describe("monitorSlackProvider tool results", () => { await waitForSlackEvent("message"); const handler = getSlackHandlers()?.get("message"); - if (!handler) throw new Error("Slack message handler not registered"); + if (!handler) { + throw new Error("Slack message handler not registered"); + } await handler({ event: { @@ -180,7 +188,9 @@ describe("monitorSlackProvider tool results", () => { await waitForSlackEvent("message"); const handler = getSlackHandlers()?.get("message"); - if (!handler) throw new Error("Slack message handler not registered"); + if (!handler) { + throw new Error("Slack message handler not registered"); + } const baseEvent = { type: "message", diff --git a/src/slack/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts b/src/slack/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts index 4bcaa9e35c..384db1a7c4 100644 --- a/src/slack/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts +++ b/src/slack/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts @@ -35,7 +35,9 @@ describe("monitorSlackProvider tool results", () => { await waitForSlackEvent("message"); const handler = getSlackHandlers()?.get("message"); - if (!handler) throw new Error("Slack message handler not registered"); + if (!handler) { + throw new Error("Slack message handler not registered"); + } await handler({ event: { @@ -58,7 +60,9 @@ describe("monitorSlackProvider tool results", () => { it("drops events with mismatched api_app_id", async () => { const client = getSlackClient(); - if (!client) throw new Error("Slack client not registered"); + if (!client) { + throw new Error("Slack client not registered"); + } (client.auth as { test: ReturnType }).test.mockResolvedValue({ user_id: "bot-user", team_id: "T1", @@ -74,7 +78,9 @@ describe("monitorSlackProvider tool results", () => { await waitForSlackEvent("message"); const handler = getSlackHandlers()?.get("message"); - if (!handler) throw new Error("Slack message handler not registered"); + if (!handler) { + throw new Error("Slack message handler not registered"); + } await handler({ body: { api_app_id: "A2", team_id: "T1" }, @@ -137,7 +143,9 @@ describe("monitorSlackProvider tool results", () => { await waitForSlackEvent("message"); const handler = getSlackHandlers()?.get("message"); - if (!handler) throw new Error("Slack message handler not registered"); + if (!handler) { + throw new Error("Slack message handler not registered"); + } await handler({ event: { @@ -185,7 +193,9 @@ describe("monitorSlackProvider tool results", () => { await waitForSlackEvent("message"); const handler = getSlackHandlers()?.get("message"); - if (!handler) throw new Error("Slack message handler not registered"); + if (!handler) { + throw new Error("Slack message handler not registered"); + } await handler({ event: { @@ -248,7 +258,9 @@ describe("monitorSlackProvider tool results", () => { await waitForSlackEvent("message"); const handler = getSlackHandlers()?.get("message"); - if (!handler) throw new Error("Slack message handler not registered"); + if (!handler) { + throw new Error("Slack message handler not registered"); + } await handler({ event: { @@ -311,7 +323,9 @@ describe("monitorSlackProvider tool results", () => { await waitForSlackEvent("message"); const handler = getSlackHandlers()?.get("message"); - if (!handler) throw new Error("Slack message handler not registered"); + if (!handler) { + throw new Error("Slack message handler not registered"); + } await handler({ event: { @@ -371,7 +385,9 @@ describe("monitorSlackProvider tool results", () => { await waitForSlackEvent("message"); const handler = getSlackHandlers()?.get("message"); - if (!handler) throw new Error("Slack message handler not registered"); + if (!handler) { + throw new Error("Slack message handler not registered"); + } await handler({ event: { @@ -416,7 +432,9 @@ describe("monitorSlackProvider tool results", () => { await waitForSlackEvent("message"); const handler = getSlackHandlers()?.get("message"); - if (!handler) throw new Error("Slack message handler not registered"); + if (!handler) { + throw new Error("Slack message handler not registered"); + } await handler({ event: { @@ -457,7 +475,9 @@ describe("monitorSlackProvider tool results", () => { await waitForSlackEvent("message"); const handler = getSlackHandlers()?.get("message"); - if (!handler) throw new Error("Slack message handler not registered"); + if (!handler) { + throw new Error("Slack message handler not registered"); + } await handler({ event: { @@ -501,7 +521,9 @@ describe("monitorSlackProvider tool results", () => { await waitForSlackEvent("message"); const handler = getSlackHandlers()?.get("message"); - if (!handler) throw new Error("Slack message handler not registered"); + if (!handler) { + throw new Error("Slack message handler not registered"); + } await handler({ event: { @@ -535,7 +557,9 @@ describe("monitorSlackProvider tool results", () => { await waitForSlackEvent("message"); const handler = getSlackHandlers()?.get("message"); - if (!handler) throw new Error("Slack message handler not registered"); + if (!handler) { + throw new Error("Slack message handler not registered"); + } await handler({ event: { @@ -581,7 +605,9 @@ describe("monitorSlackProvider tool results", () => { await waitForSlackEvent("message"); const handler = getSlackHandlers()?.get("message"); - if (!handler) throw new Error("Slack message handler not registered"); + if (!handler) { + throw new Error("Slack message handler not registered"); + } await handler({ event: { diff --git a/src/slack/monitor.tool-result.threads-top-level-replies-replytomode-is-all.test.ts b/src/slack/monitor.tool-result.threads-top-level-replies-replytomode-is-all.test.ts index 6825eb4a1a..a24656331c 100644 --- a/src/slack/monitor.tool-result.threads-top-level-replies-replytomode-is-all.test.ts +++ b/src/slack/monitor.tool-result.threads-top-level-replies-replytomode-is-all.test.ts @@ -46,7 +46,9 @@ describe("monitorSlackProvider tool results", () => { await waitForSlackEvent("message"); const handler = getSlackHandlers()?.get("message"); - if (!handler) throw new Error("Slack message handler not registered"); + if (!handler) { + throw new Error("Slack message handler not registered"); + } await handler({ event: { @@ -79,7 +81,9 @@ describe("monitorSlackProvider tool results", () => { await waitForSlackEvent("message"); const handler = getSlackHandlers()?.get("message"); - if (!handler) throw new Error("Slack message handler not registered"); + if (!handler) { + throw new Error("Slack message handler not registered"); + } await handler({ event: { @@ -130,7 +134,9 @@ describe("monitorSlackProvider tool results", () => { await waitForSlackEvent("message"); const handler = getSlackHandlers()?.get("message"); - if (!handler) throw new Error("Slack message handler not registered"); + if (!handler) { + throw new Error("Slack message handler not registered"); + } await handler({ event: { @@ -191,7 +197,9 @@ describe("monitorSlackProvider tool results", () => { await waitForSlackEvent("message"); const handler = getSlackHandlers()?.get("message"); - if (!handler) throw new Error("Slack message handler not registered"); + if (!handler) { + throw new Error("Slack message handler not registered"); + } await handler({ event: { @@ -257,7 +265,9 @@ describe("monitorSlackProvider tool results", () => { await waitForSlackEvent("message"); const handler = getSlackHandlers()?.get("message"); - if (!handler) throw new Error("Slack message handler not registered"); + if (!handler) { + throw new Error("Slack message handler not registered"); + } await handler({ event: { @@ -309,7 +319,9 @@ describe("monitorSlackProvider tool results", () => { await waitForSlackEvent("message"); const handler = getSlackHandlers()?.get("message"); - if (!handler) throw new Error("Slack message handler not registered"); + if (!handler) { + throw new Error("Slack message handler not registered"); + } await handler({ event: { @@ -355,7 +367,9 @@ describe("monitorSlackProvider tool results", () => { await waitForSlackEvent("message"); const handler = getSlackHandlers()?.get("message"); - if (!handler) throw new Error("Slack message handler not registered"); + if (!handler) { + throw new Error("Slack message handler not registered"); + } await handler({ event: { diff --git a/src/slack/monitor/allow-list.ts b/src/slack/monitor/allow-list.ts index 42b31b31d1..7cae542052 100644 --- a/src/slack/monitor/allow-list.ts +++ b/src/slack/monitor/allow-list.ts @@ -2,7 +2,9 @@ import type { AllowlistMatch } from "../../channels/allowlist-match.js"; export function normalizeSlackSlug(raw?: string) { const trimmed = raw?.trim().toLowerCase() ?? ""; - if (!trimmed) return ""; + if (!trimmed) { + return ""; + } const dashed = trimmed.replace(/\s+/g, "-"); const cleaned = dashed.replace(/[^a-z0-9#@._+-]+/g, "-"); return cleaned.replace(/-{2,}/g, "-").replace(/^[-.]+|[-.]+$/g, ""); @@ -26,7 +28,9 @@ export function resolveSlackAllowListMatch(params: { name?: string; }): SlackAllowListMatch { const allowList = params.allowList; - if (allowList.length === 0) return { allowed: false }; + if (allowList.length === 0) { + return { allowed: false }; + } if (allowList.includes("*")) { return { allowed: true, matchKey: "*", matchSource: "wildcard" }; } @@ -42,7 +46,9 @@ export function resolveSlackAllowListMatch(params: { { value: slug, source: "slug" }, ]; for (const candidate of candidates) { - if (!candidate.value) continue; + if (!candidate.value) { + continue; + } if (allowList.includes(candidate.value)) { return { allowed: true, @@ -64,7 +70,9 @@ export function resolveSlackUserAllowed(params: { userName?: string; }) { const allowList = normalizeAllowListLower(params.allowList); - if (allowList.length === 0) return true; + if (allowList.length === 0) { + return true; + } return allowListMatches({ allowList, id: params.userId, diff --git a/src/slack/monitor/channel-config.ts b/src/slack/monitor/channel-config.ts index 3e3c541c96..6d35cb1ae6 100644 --- a/src/slack/monitor/channel-config.ts +++ b/src/slack/monitor/channel-config.ts @@ -21,7 +21,9 @@ export type SlackChannelConfigResolved = { function firstDefined(...values: Array) { for (const value of values) { - if (typeof value !== "undefined") return value; + if (typeof value !== "undefined") { + return value; + } } return undefined; } @@ -36,13 +38,19 @@ export function shouldEmitSlackReactionNotification(params: { }) { const { mode, botId, messageAuthorId, userId, userName, allowlist } = params; const effectiveMode = mode ?? "own"; - if (effectiveMode === "off") return false; + if (effectiveMode === "off") { + return false; + } if (effectiveMode === "own") { - if (!botId || !messageAuthorId) return false; + if (!botId || !messageAuthorId) { + return false; + } return messageAuthorId === botId; } if (effectiveMode === "allowlist") { - if (!Array.isArray(allowlist) || allowlist.length === 0) return false; + if (!Array.isArray(allowlist) || allowlist.length === 0) { + return false; + } const users = normalizeAllowListLower(allowlist); return allowListMatches({ allowList: users, diff --git a/src/slack/monitor/context.ts b/src/slack/monitor/context.ts index f74592a106..8c8f9cc9a8 100644 --- a/src/slack/monitor/context.ts +++ b/src/slack/monitor/context.ts @@ -18,10 +18,18 @@ export function inferSlackChannelType( channelId?: string | null, ): SlackMessageEvent["channel_type"] | undefined { const trimmed = channelId?.trim(); - if (!trimmed) return undefined; - if (trimmed.startsWith("D")) return "im"; - if (trimmed.startsWith("C")) return "channel"; - if (trimmed.startsWith("G")) return "group"; + if (!trimmed) { + return undefined; + } + if (trimmed.startsWith("D")) { + return "im"; + } + if (trimmed.startsWith("C")) { + return "channel"; + } + if (trimmed.startsWith("G")) { + return "group"; + } return undefined; } @@ -169,7 +177,9 @@ export function createSlackMonitorContext(params: { const defaultRequireMention = params.defaultRequireMention ?? true; const markMessageSeen = (channelId: string | undefined, ts?: string) => { - if (!channelId || !ts) return false; + if (!channelId || !ts) { + return false; + } return seenMessages.check(`${channelId}:${ts}`); }; @@ -178,7 +188,9 @@ export function createSlackMonitorContext(params: { channelType?: string | null; }) => { const channelId = p.channelId?.trim() ?? ""; - if (!channelId) return params.mainKey; + if (!channelId) { + return params.mainKey; + } const channelType = normalizeSlackChannelType(p.channelType, channelId); const isDirectMessage = channelType === "im"; const isGroup = channelType === "mpim"; @@ -197,7 +209,9 @@ export function createSlackMonitorContext(params: { const resolveChannelName = async (channelId: string) => { const cached = channelCache.get(channelId); - if (cached) return cached; + if (cached) { + return cached; + } try { const info = await params.app.client.conversations.info({ token: params.botToken, @@ -227,7 +241,9 @@ export function createSlackMonitorContext(params: { const resolveUserName = async (userId: string) => { const cached = userCache.get(userId); - if (cached) return cached; + if (cached) { + return cached; + } try { const info = await params.app.client.users.info({ token: params.botToken, @@ -248,7 +264,9 @@ export function createSlackMonitorContext(params: { threadTs?: string; status: string; }) => { - if (!p.threadTs) return; + if (!p.threadTs) { + return; + } const payload = { token: params.botToken, channel_id: p.channelId, @@ -286,8 +304,12 @@ export function createSlackMonitorContext(params: { const isGroupDm = channelType === "mpim"; const isRoom = channelType === "channel" || channelType === "group"; - if (isDirectMessage && !params.dmEnabled) return false; - if (isGroupDm && !params.groupDmEnabled) return false; + if (isDirectMessage && !params.dmEnabled) { + return false; + } + if (isGroupDm && !params.groupDmEnabled) { + return false; + } if (isGroupDm && groupDmChannels.length > 0) { const allowList = normalizeAllowListLower(groupDmChannels); @@ -301,7 +323,9 @@ export function createSlackMonitorContext(params: { .map((value) => value.toLowerCase()); const permitted = allowList.includes("*") || candidates.some((candidate) => allowList.includes(candidate)); - if (!permitted) return false; + if (!permitted) { + return false; + } } if (isRoom && p.channelId) { @@ -342,7 +366,9 @@ export function createSlackMonitorContext(params: { }; const shouldDropMismatchedSlackEvent = (body: unknown) => { - if (!body || typeof body !== "object") return false; + if (!body || typeof body !== "object") { + return false; + } const raw = body as { api_app_id?: unknown; team_id?: unknown }; const incomingApiAppId = typeof raw.api_app_id === "string" ? raw.api_app_id : ""; const incomingTeamId = typeof raw.team_id === "string" ? raw.team_id : ""; diff --git a/src/slack/monitor/events/channels.ts b/src/slack/monitor/events/channels.ts index 8d9fb9e59b..2ab9b00e06 100644 --- a/src/slack/monitor/events/channels.ts +++ b/src/slack/monitor/events/channels.ts @@ -21,7 +21,9 @@ export function registerSlackChannelEvents(params: { ctx: SlackMonitorContext }) "channel_created", async ({ event, body }: SlackEventMiddlewareArgs<"channel_created">) => { try { - if (ctx.shouldDropMismatchedSlackEvent(body)) return; + if (ctx.shouldDropMismatchedSlackEvent(body)) { + return; + } const payload = event as SlackChannelCreatedEvent; const channelId = payload.channel?.id; @@ -54,7 +56,9 @@ export function registerSlackChannelEvents(params: { ctx: SlackMonitorContext }) "channel_rename", async ({ event, body }: SlackEventMiddlewareArgs<"channel_rename">) => { try { - if (ctx.shouldDropMismatchedSlackEvent(body)) return; + if (ctx.shouldDropMismatchedSlackEvent(body)) { + return; + } const payload = event as SlackChannelRenamedEvent; const channelId = payload.channel?.id; @@ -87,12 +91,16 @@ export function registerSlackChannelEvents(params: { ctx: SlackMonitorContext }) "channel_id_changed", async ({ event, body }: SlackEventMiddlewareArgs<"channel_id_changed">) => { try { - if (ctx.shouldDropMismatchedSlackEvent(body)) return; + if (ctx.shouldDropMismatchedSlackEvent(body)) { + return; + } const payload = event as SlackChannelIdChangedEvent; const oldChannelId = payload.old_channel_id; const newChannelId = payload.new_channel_id; - if (!oldChannelId || !newChannelId) return; + if (!oldChannelId || !newChannelId) { + return; + } const channelInfo = await ctx.resolveChannelName(newChannelId); const label = resolveSlackChannelLabel({ diff --git a/src/slack/monitor/events/members.ts b/src/slack/monitor/events/members.ts index c26e815ee4..2491ed6595 100644 --- a/src/slack/monitor/events/members.ts +++ b/src/slack/monitor/events/members.ts @@ -14,7 +14,9 @@ export function registerSlackMemberEvents(params: { ctx: SlackMonitorContext }) "member_joined_channel", async ({ event, body }: SlackEventMiddlewareArgs<"member_joined_channel">) => { try { - if (ctx.shouldDropMismatchedSlackEvent(body)) return; + if (ctx.shouldDropMismatchedSlackEvent(body)) { + return; + } const payload = event as SlackMemberChannelEvent; const channelId = payload.channel; const channelInfo = channelId ? await ctx.resolveChannelName(channelId) : {}; @@ -52,7 +54,9 @@ export function registerSlackMemberEvents(params: { ctx: SlackMonitorContext }) "member_left_channel", async ({ event, body }: SlackEventMiddlewareArgs<"member_left_channel">) => { try { - if (ctx.shouldDropMismatchedSlackEvent(body)) return; + if (ctx.shouldDropMismatchedSlackEvent(body)) { + return; + } const payload = event as SlackMemberChannelEvent; const channelId = payload.channel; const channelInfo = channelId ? await ctx.resolveChannelName(channelId) : {}; diff --git a/src/slack/monitor/events/messages.ts b/src/slack/monitor/events/messages.ts index 514f0e0d24..03ba506714 100644 --- a/src/slack/monitor/events/messages.ts +++ b/src/slack/monitor/events/messages.ts @@ -21,7 +21,9 @@ export function registerSlackMessageEvents(params: { ctx.app.event("message", async ({ event, body }: SlackEventMiddlewareArgs<"message">) => { try { - if (ctx.shouldDropMismatchedSlackEvent(body)) return; + if (ctx.shouldDropMismatchedSlackEvent(body)) { + return; + } const message = event as SlackMessageEvent; if (message.subtype === "message_changed") { @@ -119,7 +121,9 @@ export function registerSlackMessageEvents(params: { ctx.app.event("app_mention", async ({ event, body }: SlackEventMiddlewareArgs<"app_mention">) => { try { - if (ctx.shouldDropMismatchedSlackEvent(body)) return; + if (ctx.shouldDropMismatchedSlackEvent(body)) { + return; + } const mention = event as SlackAppMentionEvent; await handleSlackMessage(mention as unknown as SlackMessageEvent, { diff --git a/src/slack/monitor/events/pins.ts b/src/slack/monitor/events/pins.ts index 886db4383e..7005ecb79e 100644 --- a/src/slack/monitor/events/pins.ts +++ b/src/slack/monitor/events/pins.ts @@ -12,7 +12,9 @@ export function registerSlackPinEvents(params: { ctx: SlackMonitorContext }) { ctx.app.event("pin_added", async ({ event, body }: SlackEventMiddlewareArgs<"pin_added">) => { try { - if (ctx.shouldDropMismatchedSlackEvent(body)) return; + if (ctx.shouldDropMismatchedSlackEvent(body)) { + return; + } const payload = event as SlackPinEvent; const channelId = payload.channel_id; @@ -49,7 +51,9 @@ export function registerSlackPinEvents(params: { ctx: SlackMonitorContext }) { ctx.app.event("pin_removed", async ({ event, body }: SlackEventMiddlewareArgs<"pin_removed">) => { try { - if (ctx.shouldDropMismatchedSlackEvent(body)) return; + if (ctx.shouldDropMismatchedSlackEvent(body)) { + return; + } const payload = event as SlackPinEvent; const channelId = payload.channel_id; diff --git a/src/slack/monitor/events/reactions.ts b/src/slack/monitor/events/reactions.ts index 5df15912d5..21f8781299 100644 --- a/src/slack/monitor/events/reactions.ts +++ b/src/slack/monitor/events/reactions.ts @@ -13,7 +13,9 @@ export function registerSlackReactionEvents(params: { ctx: SlackMonitorContext } const handleReactionEvent = async (event: SlackReactionEvent, action: string) => { try { const item = event.item; - if (!item || item.type !== "message") return; + if (!item || item.type !== "message") { + return; + } const channelInfo = item.channel ? await ctx.resolveChannelName(item.channel) : {}; const channelType = channelInfo?.type; @@ -54,7 +56,9 @@ export function registerSlackReactionEvents(params: { ctx: SlackMonitorContext } ctx.app.event( "reaction_added", async ({ event, body }: SlackEventMiddlewareArgs<"reaction_added">) => { - if (ctx.shouldDropMismatchedSlackEvent(body)) return; + if (ctx.shouldDropMismatchedSlackEvent(body)) { + return; + } await handleReactionEvent(event as SlackReactionEvent, "added"); }, ); @@ -62,7 +66,9 @@ export function registerSlackReactionEvents(params: { ctx: SlackMonitorContext } ctx.app.event( "reaction_removed", async ({ event, body }: SlackEventMiddlewareArgs<"reaction_removed">) => { - if (ctx.shouldDropMismatchedSlackEvent(body)) return; + if (ctx.shouldDropMismatchedSlackEvent(body)) { + return; + } await handleReactionEvent(event as SlackReactionEvent, "removed"); }, ); diff --git a/src/slack/monitor/media.ts b/src/slack/monitor/media.ts index 2674e2d50a..561fefb066 100644 --- a/src/slack/monitor/media.ts +++ b/src/slack/monitor/media.ts @@ -49,7 +49,9 @@ export async function resolveSlackMedia(params: { const files = params.files ?? []; for (const file of files) { const url = file.url_private_download ?? file.url_private; - if (!url) continue; + if (!url) { + continue; + } try { // Note: We ignore init options because fetchWithSlackAuth handles // redirect behavior specially. fetchRemoteMedia only passes the URL. @@ -63,7 +65,9 @@ export async function resolveSlackMedia(params: { fetchImpl, filePathHint: file.name, }); - if (fetched.buffer.byteLength > params.maxBytes) continue; + if (fetched.buffer.byteLength > params.maxBytes) { + continue; + } const saved = await saveMediaBuffer( fetched.buffer, fetched.contentType ?? file.mimetype, @@ -99,7 +103,9 @@ export async function resolveSlackThreadStarter(params: { }): Promise { const cacheKey = `${params.channelId}:${params.threadTs}`; const cached = THREAD_STARTER_CACHE.get(cacheKey); - if (cached) return cached; + if (cached) { + return cached; + } try { const response = (await params.client.conversations.replies({ channel: params.channelId, @@ -109,7 +115,9 @@ export async function resolveSlackThreadStarter(params: { })) as { messages?: Array<{ text?: string; user?: string; ts?: string; files?: SlackFile[] }> }; const message = response?.messages?.[0]; const text = (message?.text ?? "").trim(); - if (!message || !text) return null; + if (!message || !text) { + return null; + } const starter: SlackThreadStarter = { text, userId: message.user, diff --git a/src/slack/monitor/message-handler.ts b/src/slack/monitor/message-handler.ts index 1ee736496e..42ba9b2f11 100644 --- a/src/slack/monitor/message-handler.ts +++ b/src/slack/monitor/message-handler.ts @@ -30,7 +30,9 @@ export function createSlackMessageHandler(params: { debounceMs, buildKey: (entry) => { const senderId = entry.message.user ?? entry.message.bot_id; - if (!senderId) return null; + if (!senderId) { + return null; + } const messageTs = entry.message.ts ?? entry.message.event_ts; // If Slack flags a thread reply but omits thread_ts, isolate it from root debouncing. const threadKey = entry.message.thread_ts @@ -42,13 +44,19 @@ export function createSlackMessageHandler(params: { }, shouldDebounce: (entry) => { const text = entry.message.text ?? ""; - if (!text.trim()) return false; - if (entry.message.files && entry.message.files.length > 0) return false; + if (!text.trim()) { + return false; + } + if (entry.message.files && entry.message.files.length > 0) { + return false; + } return !hasControlCommand(text, ctx.cfg); }, onFlush: async (entries) => { const last = entries.at(-1); - if (!last) return; + if (!last) { + return; + } const combinedText = entries.length === 1 ? (last.message.text ?? "") @@ -70,7 +78,9 @@ export function createSlackMessageHandler(params: { wasMentioned: combinedMentioned || last.opts.wasMentioned, }, }); - if (!prepared) return; + if (!prepared) { + return; + } if (entries.length > 1) { const ids = entries.map((entry) => entry.message.ts).filter(Boolean) as string[]; if (ids.length > 0) { @@ -87,7 +97,9 @@ export function createSlackMessageHandler(params: { }); return async (message, opts) => { - if (opts.source === "message" && message.type !== "message") return; + if (opts.source === "message" && message.type !== "message") { + return; + } if ( opts.source === "message" && message.subtype && @@ -96,7 +108,9 @@ export function createSlackMessageHandler(params: { ) { return; } - if (ctx.markMessageSeen(message.channel, message.ts)) return; + if (ctx.markMessageSeen(message.channel, message.ts)) { + return; + } const resolvedMessage = await threadTsResolver.resolve({ message, source: opts.source }); await debouncer.enqueue({ message: resolvedMessage, opts }); }; diff --git a/src/slack/monitor/message-handler/dispatch.ts b/src/slack/monitor/message-handler/dispatch.ts index 38b69f0496..52f911ae20 100644 --- a/src/slack/monitor/message-handler/dispatch.ts +++ b/src/slack/monitor/message-handler/dispatch.ts @@ -67,7 +67,9 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag }); }, stop: async () => { - if (!didSetStatus) return; + if (!didSetStatus) { + return; + } didSetStatus = false; await ctx.setSlackThreadStatus({ channelId: message.channel, diff --git a/src/slack/monitor/message-handler/prepare.ts b/src/slack/monitor/message-handler/prepare.ts index 8a2a9e1116..c84c825a13 100644 --- a/src/slack/monitor/message-handler/prepare.ts +++ b/src/slack/monitor/message-handler/prepare.ts @@ -92,7 +92,9 @@ export async function prepareSlackMessage(params: { const isBotMessage = Boolean(message.bot_id); if (isBotMessage) { - if (message.user && ctx.botUserId && message.user === ctx.botUserId) return null; + if (message.user && ctx.botUserId && message.user === ctx.botUserId) { + return null; + } if (!allowBots) { logVerbose(`slack: drop bot message ${message.bot_id ?? "unknown"} (allowBots=false)`); return null; @@ -337,7 +339,9 @@ export async function prepareSlackMessage(params: { maxBytes: ctx.mediaMaxBytes, }); const rawBody = (message.text ?? "").trim() || media?.placeholder || ""; - if (!rawBody) return null; + if (!rawBody) { + return null; + } const ackReaction = resolveAckReaction(cfg, route.agentId); const ackReactionValue = ackReaction ?? ""; @@ -552,7 +556,9 @@ export async function prepareSlackMessage(params: { }); const replyTarget = ctxPayload.To ?? undefined; - if (!replyTarget) return null; + if (!replyTarget) { + return null; + } if (shouldLogVerbose()) { logVerbose(`slack inbound: channel=${message.channel} from=${slackFrom} preview="${preview}"`); diff --git a/src/slack/monitor/policy.ts b/src/slack/monitor/policy.ts index a4ac37917b..fbf1d3a730 100644 --- a/src/slack/monitor/policy.ts +++ b/src/slack/monitor/policy.ts @@ -4,8 +4,14 @@ export function isSlackChannelAllowedByPolicy(params: { channelAllowed: boolean; }): boolean { const { groupPolicy, channelAllowlistConfigured, channelAllowed } = params; - if (groupPolicy === "disabled") return false; - if (groupPolicy === "open") return true; - if (!channelAllowlistConfigured) return false; + if (groupPolicy === "disabled") { + return false; + } + if (groupPolicy === "open") { + return true; + } + if (!channelAllowlistConfigured) { + return false; + } return channelAllowed; } diff --git a/src/slack/monitor/provider.ts b/src/slack/monitor/provider.ts index 4ee36c83a9..2bf8e0b071 100644 --- a/src/slack/monitor/provider.ts +++ b/src/slack/monitor/provider.ts @@ -36,7 +36,9 @@ const slackBolt = const { App, HTTPReceiver } = slackBolt; function parseApiAppIdFromAppToken(raw?: string) { const token = raw?.trim(); - if (!token) return undefined; + if (!token) { + return undefined; + } const match = /^xapp-\d-([a-z0-9]+)-/i.exec(token); return match?.[1]?.toUpperCase(); } @@ -220,7 +222,9 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) { if (resolveToken) { void (async () => { - if (opts.abortSignal?.aborted) return; + if (opts.abortSignal?.aborted) { + return; + } if (channelsConfig && Object.keys(channelsConfig).length > 0) { try { @@ -235,7 +239,9 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) { const unresolved: string[] = []; for (const entry of resolved) { const source = channelsConfig?.[entry.input]; - if (!source) continue; + if (!source) { + continue; + } if (!entry.resolved || !entry.id) { unresolved.push(entry.input); continue; @@ -284,12 +290,18 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) { if (channelsConfig && Object.keys(channelsConfig).length > 0) { const userEntries = new Set(); for (const channel of Object.values(channelsConfig)) { - if (!channel || typeof channel !== "object") continue; + if (!channel || typeof channel !== "object") { + continue; + } const channelUsers = (channel as { users?: Array }).users; - if (!Array.isArray(channelUsers)) continue; + if (!Array.isArray(channelUsers)) { + continue; + } for (const entry of channelUsers) { const trimmed = String(entry).trim(); - if (trimmed && trimmed !== "*") userEntries.add(trimmed); + if (trimmed && trimmed !== "*") { + userEntries.add(trimmed); + } } } @@ -309,14 +321,20 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) { const nextChannels = { ...channelsConfig }; for (const [channelKey, channelConfig] of Object.entries(channelsConfig)) { - if (!channelConfig || typeof channelConfig !== "object") continue; + if (!channelConfig || typeof channelConfig !== "object") { + continue; + } const channelUsers = (channelConfig as { users?: Array }).users; - if (!Array.isArray(channelUsers) || channelUsers.length === 0) continue; + if (!Array.isArray(channelUsers) || channelUsers.length === 0) { + continue; + } const additions: string[] = []; for (const entry of channelUsers) { const trimmed = String(entry).trim(); const resolved = resolvedMap.get(trimmed); - if (resolved?.resolved && resolved.id) additions.push(resolved.id); + if (resolved?.resolved && resolved.id) { + additions.push(resolved.id); + } } nextChannels[channelKey] = { ...channelConfig, @@ -337,7 +355,9 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) { } const stopOnAbort = () => { - if (opts.abortSignal?.aborted && slackMode === "socket") void app.stop(); + if (opts.abortSignal?.aborted && slackMode === "socket") { + void app.stop(); + } }; opts.abortSignal?.addEventListener("abort", stopOnAbort, { once: true }); @@ -348,7 +368,9 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) { } else { runtime.log?.(`slack http mode listening at ${slackWebhookPath}`); } - if (opts.abortSignal?.aborted) return; + if (opts.abortSignal?.aborted) { + return; + } await new Promise((resolve) => { opts.abortSignal?.addEventListener("abort", () => resolve(), { once: true, diff --git a/src/slack/monitor/replies.ts b/src/slack/monitor/replies.ts index 314be285f1..1c55daaa53 100644 --- a/src/slack/monitor/replies.ts +++ b/src/slack/monitor/replies.ts @@ -21,11 +21,15 @@ export async function deliverReplies(params: { const threadTs = payload.replyToId ?? params.replyThreadTs; const mediaList = payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []); const text = payload.text ?? ""; - if (!text && mediaList.length === 0) continue; + if (!text && mediaList.length === 0) { + continue; + } if (mediaList.length === 0) { const trimmed = text.trim(); - if (!trimmed || isSilentReplyText(trimmed, SILENT_REPLY_TOKEN)) continue; + if (!trimmed || isSilentReplyText(trimmed, SILENT_REPLY_TOKEN)) { + continue; + } await sendMessageSlack(params.target, trimmed, { token: params.token, threadTs, @@ -131,7 +135,9 @@ export async function deliverSlackSlashReplies(params: { const combined = [text ?? "", ...mediaList.map((url) => url.trim()).filter(Boolean)] .filter(Boolean) .join("\n"); - if (!combined) continue; + if (!combined) { + continue; + } const chunkMode = params.chunkMode ?? "length"; const markdownChunks = chunkMode === "newline" @@ -140,13 +146,17 @@ export async function deliverSlackSlashReplies(params: { const chunks = markdownChunks.flatMap((markdown) => markdownToSlackMrkdwnChunks(markdown, chunkLimit, { tableMode: params.tableMode }), ); - if (!chunks.length && combined) chunks.push(combined); + if (!chunks.length && combined) { + chunks.push(combined); + } for (const chunk of chunks) { messages.push(chunk); } } - if (messages.length === 0) return; + if (messages.length === 0) { + return; + } // Slack slash command responses can be multi-part by sending follow-ups via response_url. const responseType = params.ephemeral ? "ephemeral" : "in_channel"; diff --git a/src/slack/monitor/slash.command-arg-menus.test.ts b/src/slack/monitor/slash.command-arg-menus.test.ts index 641045ee45..3b27e7e8a7 100644 --- a/src/slack/monitor/slash.command-arg-menus.test.ts +++ b/src/slack/monitor/slash.command-arg-menus.test.ts @@ -103,7 +103,9 @@ describe("Slack native command argument menus", () => { registerSlackMonitorSlashCommands({ ctx: ctx as never, account: account as never }); const handler = commands.get("/usage"); - if (!handler) throw new Error("Missing /usage handler"); + if (!handler) { + throw new Error("Missing /usage handler"); + } const respond = vi.fn().mockResolvedValue(undefined); const ack = vi.fn().mockResolvedValue(undefined); @@ -132,7 +134,9 @@ describe("Slack native command argument menus", () => { registerSlackMonitorSlashCommands({ ctx: ctx as never, account: account as never }); const handler = actions.get("openclaw_cmdarg"); - if (!handler) throw new Error("Missing arg-menu action handler"); + if (!handler) { + throw new Error("Missing arg-menu action handler"); + } const respond = vi.fn().mockResolvedValue(undefined); await handler({ @@ -158,7 +162,9 @@ describe("Slack native command argument menus", () => { registerSlackMonitorSlashCommands({ ctx: ctx as never, account: account as never }); const handler = actions.get("openclaw_cmdarg"); - if (!handler) throw new Error("Missing arg-menu action handler"); + if (!handler) { + throw new Error("Missing arg-menu action handler"); + } const respond = vi.fn().mockResolvedValue(undefined); await handler({ @@ -186,7 +192,9 @@ describe("Slack native command argument menus", () => { registerSlackMonitorSlashCommands({ ctx: ctx as never, account: account as never }); const handler = actions.get("openclaw_cmdarg"); - if (!handler) throw new Error("Missing arg-menu action handler"); + if (!handler) { + throw new Error("Missing arg-menu action handler"); + } await handler({ ack: vi.fn().mockResolvedValue(undefined), @@ -208,7 +216,9 @@ describe("Slack native command argument menus", () => { registerSlackMonitorSlashCommands({ ctx: ctx as never, account: account as never }); const handler = actions.get("openclaw_cmdarg"); - if (!handler) throw new Error("Missing arg-menu action handler"); + if (!handler) { + throw new Error("Missing arg-menu action handler"); + } await handler({ ack: vi.fn().mockResolvedValue(undefined), diff --git a/src/slack/monitor/slash.policy.test.ts b/src/slack/monitor/slash.policy.test.ts index 7cf7430fcc..bac3e81d82 100644 --- a/src/slack/monitor/slash.policy.test.ts +++ b/src/slack/monitor/slash.policy.test.ts @@ -101,7 +101,9 @@ describe("slack slash commands channel policy", () => { registerSlackMonitorSlashCommands({ ctx: ctx as never, account: account as never }); const handler = [...commands.values()][0]; - if (!handler) throw new Error("Missing slash handler"); + if (!handler) { + throw new Error("Missing slash handler"); + } const respond = vi.fn().mockResolvedValue(undefined); await handler({ @@ -133,7 +135,9 @@ describe("slack slash commands channel policy", () => { registerSlackMonitorSlashCommands({ ctx: ctx as never, account: account as never }); const handler = [...commands.values()][0]; - if (!handler) throw new Error("Missing slash handler"); + if (!handler) { + throw new Error("Missing slash handler"); + } const respond = vi.fn().mockResolvedValue(undefined); await handler({ @@ -166,7 +170,9 @@ describe("slack slash commands channel policy", () => { registerSlackMonitorSlashCommands({ ctx: ctx as never, account: account as never }); const handler = [...commands.values()][0]; - if (!handler) throw new Error("Missing slash handler"); + if (!handler) { + throw new Error("Missing slash handler"); + } const respond = vi.fn().mockResolvedValue(undefined); await handler({ diff --git a/src/slack/monitor/slash.ts b/src/slack/monitor/slash.ts index 34f8989c36..6eb9351c02 100644 --- a/src/slack/monitor/slash.ts +++ b/src/slack/monitor/slash.ts @@ -45,7 +45,9 @@ const SLACK_COMMAND_ARG_ACTION_ID = "openclaw_cmdarg"; const SLACK_COMMAND_ARG_VALUE_PREFIX = "cmdarg"; function chunkItems(items: T[], size: number): T[][] { - if (size <= 0) return [items]; + if (size <= 0) { + return [items]; + } const rows: T[][] = []; for (let i = 0; i < items.length; i += size) { rows.push(items.slice(i, i + size)); @@ -74,11 +76,17 @@ function parseSlackCommandArgValue(raw?: string | null): { value: string; userId: string; } | null { - if (!raw) return null; + if (!raw) { + return null; + } const parts = raw.split("|"); - if (parts.length !== 5 || parts[0] !== SLACK_COMMAND_ARG_VALUE_PREFIX) return null; + if (parts.length !== 5 || parts[0] !== SLACK_COMMAND_ARG_VALUE_PREFIX) { + return null; + } const [, command, arg, value, userId] = parts; - if (!command || !arg || !value || !userId) return null; + if (!command || !arg || !value || !userId) { + return null; + } const decode = (text: string) => { try { return decodeURIComponent(text); @@ -90,7 +98,9 @@ function parseSlackCommandArgValue(raw?: string | null): { const decodedArg = decode(arg); const decodedValue = decode(value); const decodedUserId = decode(userId); - if (!decodedCommand || !decodedArg || !decodedValue || !decodedUserId) return null; + if (!decodedCommand || !decodedArg || !decodedValue || !decodedUserId) { + return null; + } return { command: decodedCommand, arg: decodedArg, @@ -163,7 +173,9 @@ export function registerSlackMonitorSlashCommands(params: { } await ack(); - if (ctx.botUserId && command.user_id === ctx.botUserId) return; + if (ctx.botUserId && command.user_id === ctx.botUserId) { + return; + } const channelInfo = await ctx.resolveChannelName(command.channel_id); const channelType = @@ -526,7 +538,9 @@ export function registerSlackMonitorSlashCommands(params: { logVerbose("slack: slash commands disabled"); } - if (nativeCommands.length === 0 || !supportsInteractiveArgMenus) return; + if (nativeCommands.length === 0 || !supportsInteractiveArgMenus) { + return; + } const registerArgAction = (actionId: string) => { ( @@ -540,7 +554,9 @@ export function registerSlackMonitorSlashCommands(params: { const respondFn = respond ?? (async (payload: { text: string; blocks?: SlackBlock[]; response_type?: string }) => { - if (!body.channel?.id || !body.user?.id) return; + if (!body.channel?.id || !body.user?.id) { + return; + } await ctx.app.client.chat.postEphemeral({ token: ctx.botToken, channel: body.channel.id, diff --git a/src/slack/monitor/thread-resolution.ts b/src/slack/monitor/thread-resolution.ts index 79c5ee3e1c..ecbe99b5c2 100644 --- a/src/slack/monitor/thread-resolution.ts +++ b/src/slack/monitor/thread-resolution.ts @@ -54,7 +54,9 @@ export function createSlackThreadTsResolver(params: { const getCached = (key: string, now: number) => { const entry = cache.get(key); - if (!entry) return undefined; + if (!entry) { + return undefined; + } if (ttlMs > 0 && now - entry.updatedAt > ttlMs) { cache.delete(key); return undefined; @@ -73,7 +75,9 @@ export function createSlackThreadTsResolver(params: { } while (cache.size > maxSize) { const oldestKey = cache.keys().next().value; - if (!oldestKey) break; + if (!oldestKey) { + break; + } cache.delete(oldestKey); } }; diff --git a/src/slack/probe.ts b/src/slack/probe.ts index a4e8808387..cde5e51573 100644 --- a/src/slack/probe.ts +++ b/src/slack/probe.ts @@ -10,13 +10,17 @@ export type SlackProbe = { }; function withTimeout(promise: Promise, timeoutMs: number): Promise { - if (!timeoutMs || timeoutMs <= 0) return promise; + if (!timeoutMs || timeoutMs <= 0) { + return promise; + } let timer: NodeJS.Timeout | null = null; const timeout = new Promise((_, reject) => { timer = setTimeout(() => reject(new Error("timeout")), timeoutMs); }); return Promise.race([promise, timeout]).finally(() => { - if (timer) clearTimeout(timer); + if (timer) { + clearTimeout(timer); + } }); } diff --git a/src/slack/resolve-channels.ts b/src/slack/resolve-channels.ts index b9f14bcf02..4003a9c108 100644 --- a/src/slack/resolve-channels.ts +++ b/src/slack/resolve-channels.ts @@ -29,7 +29,9 @@ type SlackListResponse = { function parseSlackChannelMention(raw: string): { id?: string; name?: string } { const trimmed = raw.trim(); - if (!trimmed) return {}; + if (!trimmed) { + return {}; + } const mention = trimmed.match(/^<#([A-Z0-9]+)(?:\|([^>]+))?>$/i); if (mention) { const id = mention[1]?.toUpperCase(); @@ -37,7 +39,9 @@ function parseSlackChannelMention(raw: string): { id?: string; name?: string } { return { id, name }; } const prefixed = trimmed.replace(/^(slack:|channel:)/i, ""); - if (/^[CG][A-Z0-9]+$/i.test(prefixed)) return { id: prefixed.toUpperCase() }; + if (/^[CG][A-Z0-9]+$/i.test(prefixed)) { + return { id: prefixed.toUpperCase() }; + } const name = prefixed.replace(/^#/, "").trim(); return name ? { name } : {}; } @@ -55,7 +59,9 @@ async function listSlackChannels(client: WebClient): Promise channel.name.toLowerCase() === target); - if (matches.length === 0) return undefined; + if (matches.length === 0) { + return undefined; + } const active = matches.find((channel) => !channel.archived); return active ?? matches[0]; } diff --git a/src/slack/resolve-users.ts b/src/slack/resolve-users.ts index 8210445335..24c25a5023 100644 --- a/src/slack/resolve-users.ts +++ b/src/slack/resolve-users.ts @@ -43,12 +43,20 @@ type SlackListUsersResponse = { function parseSlackUserInput(raw: string): { id?: string; name?: string; email?: string } { const trimmed = raw.trim(); - if (!trimmed) return {}; + if (!trimmed) { + return {}; + } const mention = trimmed.match(/^<@([A-Z0-9]+)>$/i); - if (mention) return { id: mention[1]?.toUpperCase() }; + if (mention) { + return { id: mention[1]?.toUpperCase() }; + } const prefixed = trimmed.replace(/^(slack:|user:)/i, ""); - if (/^[A-Z][A-Z0-9]+$/i.test(prefixed)) return { id: prefixed.toUpperCase() }; - if (trimmed.includes("@") && !trimmed.startsWith("@")) return { email: trimmed.toLowerCase() }; + if (/^[A-Z][A-Z0-9]+$/i.test(prefixed)) { + return { id: prefixed.toUpperCase() }; + } + if (trimmed.includes("@") && !trimmed.startsWith("@")) { + return { email: trimmed.toLowerCase() }; + } const name = trimmed.replace(/^@/, "").trim(); return name ? { name } : {}; } @@ -64,7 +72,9 @@ async function listSlackUsers(client: WebClient): Promise { for (const member of res.members ?? []) { const id = member.id?.trim(); const name = member.name?.trim(); - if (!id || !name) continue; + if (!id || !name) { + continue; + } const profile = member.profile ?? {}; users.push({ id, @@ -85,15 +95,23 @@ async function listSlackUsers(client: WebClient): Promise { function scoreSlackUser(user: SlackUserLookup, match: { name?: string; email?: string }): number { let score = 0; - if (!user.deleted) score += 3; - if (!user.isBot && !user.isAppUser) score += 2; - if (match.email && user.email === match.email) score += 5; + if (!user.deleted) { + score += 3; + } + if (!user.isBot && !user.isAppUser) { + score += 2; + } + if (match.email && user.email === match.email) { + score += 5; + } if (match.name) { const target = match.name.toLowerCase(); const candidates = [user.name, user.displayName, user.realName] .map((value) => value?.toLowerCase()) .filter(Boolean) as string[]; - if (candidates.some((value) => value === target)) score += 2; + if (candidates.some((value) => value === target)) { + score += 2; + } } return score; } diff --git a/src/slack/scopes.ts b/src/slack/scopes.ts index c5348c8d09..a64a5bd29e 100644 --- a/src/slack/scopes.ts +++ b/src/slack/scopes.ts @@ -16,23 +16,33 @@ function isRecord(value: unknown): value is Record { } function collectScopes(value: unknown, into: string[]) { - if (!value) return; + if (!value) { + return; + } if (Array.isArray(value)) { for (const entry of value) { - if (typeof entry === "string" && entry.trim()) into.push(entry.trim()); + if (typeof entry === "string" && entry.trim()) { + into.push(entry.trim()); + } } return; } if (typeof value === "string") { const raw = value.trim(); - if (!raw) return; + if (!raw) { + return; + } const parts = raw.split(/[,\s]+/).map((part) => part.trim()); for (const part of parts) { - if (part) into.push(part); + if (part) { + into.push(part); + } } return; } - if (!isRecord(value)) return; + if (!isRecord(value)) { + return; + } for (const entry of Object.values(value)) { if (Array.isArray(entry) || typeof entry === "string") { collectScopes(entry, into); @@ -45,7 +55,9 @@ function normalizeScopes(scopes: string[]) { } function extractScopes(payload: unknown): string[] { - if (!isRecord(payload)) return []; + if (!isRecord(payload)) { + return []; + } const scopes: string[] = []; collectScopes(payload.scopes, scopes); collectScopes(payload.scope, scopes); @@ -59,7 +71,9 @@ function extractScopes(payload: unknown): string[] { } function readError(payload: unknown): string | undefined { - if (!isRecord(payload)) return undefined; + if (!isRecord(payload)) { + return undefined; + } const error = payload.error; return typeof error === "string" && error.trim() ? error.trim() : undefined; } @@ -94,7 +108,9 @@ export async function fetchSlackScopes( return { ok: true, scopes, source: method }; } const error = readError(result); - if (error) errors.push(`${method}: ${error}`); + if (error) { + errors.push(`${method}: ${error}`); + } } return { diff --git a/src/slack/send.ts b/src/slack/send.ts index 31677278a3..57ed292b73 100644 --- a/src/slack/send.ts +++ b/src/slack/send.ts @@ -48,7 +48,9 @@ function resolveToken(params: { fallbackSource?: SlackTokenSource; }) { const explicit = resolveSlackBotToken(params.explicit); - if (explicit) return explicit; + if (explicit) { + return explicit; + } const fallback = resolveSlackBotToken(params.fallbackToken); if (!fallback) { logVerbose( @@ -161,7 +163,9 @@ export async function sendMessageSlack( const chunks = markdownChunks.flatMap((markdown) => markdownToSlackMrkdwnChunks(markdown, chunkLimit, { tableMode }), ); - if (!chunks.length && trimmedMessage) chunks.push(trimmedMessage); + if (!chunks.length && trimmedMessage) { + chunks.push(trimmedMessage); + } const mediaMaxBytes = typeof account.config.mediaMaxMb === "number" ? account.config.mediaMaxMb * 1024 * 1024 diff --git a/src/slack/targets.ts b/src/slack/targets.ts index 5701a16e21..7f66a1d5c8 100644 --- a/src/slack/targets.ts +++ b/src/slack/targets.ts @@ -18,7 +18,9 @@ export function parseSlackTarget( options: SlackTargetParseOptions = {}, ): SlackTarget | undefined { const trimmed = raw.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } const mentionMatch = trimmed.match(/^<@([A-Z0-9]+)>$/i); if (mentionMatch) { return buildMessagingTarget("user", mentionMatch[1], trimmed); diff --git a/src/telegram/accounts.ts b/src/telegram/accounts.ts index af5478584f..e985e67c61 100644 --- a/src/telegram/accounts.ts +++ b/src/telegram/accounts.ts @@ -22,10 +22,14 @@ export type ResolvedTelegramAccount = { function listConfiguredAccountIds(cfg: OpenClawConfig): string[] { const accounts = cfg.channels?.telegram?.accounts; - if (!accounts || typeof accounts !== "object") return []; + if (!accounts || typeof accounts !== "object") { + return []; + } const ids = new Set(); for (const key of Object.keys(accounts)) { - if (!key) continue; + if (!key) { + continue; + } ids.add(normalizeAccountId(key)); } return [...ids]; @@ -36,15 +40,21 @@ export function listTelegramAccountIds(cfg: OpenClawConfig): string[] { new Set([...listConfiguredAccountIds(cfg), ...listBoundAccountIds(cfg, "telegram")]), ); debugAccounts("listTelegramAccountIds", ids); - if (ids.length === 0) return [DEFAULT_ACCOUNT_ID]; + if (ids.length === 0) { + return [DEFAULT_ACCOUNT_ID]; + } return ids.toSorted((a, b) => a.localeCompare(b)); } export function resolveDefaultTelegramAccountId(cfg: OpenClawConfig): string { const boundDefault = resolveDefaultAgentBoundAccountId(cfg, "telegram"); - if (boundDefault) return boundDefault; + if (boundDefault) { + return boundDefault; + } const ids = listTelegramAccountIds(cfg); - if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID; + if (ids.includes(DEFAULT_ACCOUNT_ID)) { + return DEFAULT_ACCOUNT_ID; + } return ids[0] ?? DEFAULT_ACCOUNT_ID; } @@ -53,9 +63,13 @@ function resolveAccountConfig( accountId: string, ): TelegramAccountConfig | undefined { const accounts = cfg.channels?.telegram?.accounts; - if (!accounts || typeof accounts !== "object") return undefined; + if (!accounts || typeof accounts !== "object") { + return undefined; + } const direct = accounts[accountId] as TelegramAccountConfig | undefined; - if (direct) return direct; + if (direct) { + return direct; + } const normalized = normalizeAccountId(accountId); const matchKey = Object.keys(accounts).find((key) => normalizeAccountId(key) === normalized); return matchKey ? (accounts[matchKey] as TelegramAccountConfig | undefined) : undefined; @@ -97,16 +111,24 @@ export function resolveTelegramAccount(params: { const normalized = normalizeAccountId(params.accountId); const primary = resolve(normalized); - if (hasExplicitAccountId) return primary; - if (primary.tokenSource !== "none") return primary; + if (hasExplicitAccountId) { + return primary; + } + if (primary.tokenSource !== "none") { + return primary; + } // If accountId is omitted, prefer a configured account token over failing on // the implicit "default" account. This keeps env-based setups working while // making config-only tokens work for things like heartbeats. const fallbackId = resolveDefaultTelegramAccountId(params.cfg); - if (fallbackId === primary.accountId) return primary; + if (fallbackId === primary.accountId) { + return primary; + } const fallback = resolve(fallbackId); - if (fallback.tokenSource === "none") return primary; + if (fallback.tokenSource === "none") { + return primary; + } return fallback; } diff --git a/src/telegram/api-logging.ts b/src/telegram/api-logging.ts index 110fd4e349..4534b3f826 100644 --- a/src/telegram/api-logging.ts +++ b/src/telegram/api-logging.ts @@ -16,8 +16,12 @@ type TelegramApiLoggingParams = { const fallbackLogger = createSubsystemLogger("telegram/api"); function resolveTelegramApiLogger(runtime?: RuntimeEnv, logger?: TelegramApiLogger) { - if (logger) return logger; - if (runtime?.error) return runtime.error; + if (logger) { + return logger; + } + if (runtime?.error) { + return runtime.error; + } return (message: string) => fallbackLogger.error(message); } diff --git a/src/telegram/audit.ts b/src/telegram/audit.ts index b34901091b..54a51c6b28 100644 --- a/src/telegram/audit.ts +++ b/src/telegram/audit.ts @@ -57,12 +57,22 @@ export function collectTelegramUnmentionedGroupIds( const groupIds: string[] = []; let unresolvedGroups = 0; for (const [key, value] of Object.entries(groups)) { - if (key === "*") continue; - if (!value || typeof value !== "object") continue; - if (value.enabled === false) continue; - if (value.requireMention !== false) continue; + if (key === "*") { + continue; + } + if (!value || typeof value !== "object") { + continue; + } + if (value.enabled === false) { + continue; + } + if (value.requireMention !== false) { + continue; + } const id = String(key).trim(); - if (!id) continue; + if (!id) { + continue; + } if (/^-?\d+$/.test(id)) { groupIds.push(id); } else { diff --git a/src/telegram/bot-access.ts b/src/telegram/bot-access.ts index 2f55c1f6b1..f3ac93b4cd 100644 --- a/src/telegram/bot-access.ts +++ b/src/telegram/bot-access.ts @@ -36,7 +36,9 @@ export const normalizeAllowFromWithStore = (params: { export const firstDefined = (...values: Array) => { for (const value of values) { - if (typeof value !== "undefined") return value; + if (typeof value !== "undefined") { + return value; + } } return undefined; }; @@ -47,11 +49,19 @@ export const isSenderAllowed = (params: { senderUsername?: string; }) => { const { allow, senderId, senderUsername } = params; - if (!allow.hasEntries) return true; - if (allow.hasWildcard) return true; - if (senderId && allow.entries.includes(senderId)) return true; + if (!allow.hasEntries) { + return true; + } + if (allow.hasWildcard) { + return true; + } + if (senderId && allow.entries.includes(senderId)) { + return true; + } const username = senderUsername?.toLowerCase(); - if (!username) return false; + if (!username) { + return false; + } return allow.entriesLower.some((entry) => entry === username || entry === `@${username}`); }; @@ -64,12 +74,16 @@ export const resolveSenderAllowMatch = (params: { if (allow.hasWildcard) { return { allowed: true, matchKey: "*", matchSource: "wildcard" }; } - if (!allow.hasEntries) return { allowed: false }; + if (!allow.hasEntries) { + return { allowed: false }; + } if (senderId && allow.entries.includes(senderId)) { return { allowed: true, matchKey: senderId, matchSource: "id" }; } const username = senderUsername?.toLowerCase(); - if (!username) return { allowed: false }; + if (!username) { + return { allowed: false }; + } const entry = allow.entriesLower.find( (candidate) => candidate === username || candidate === `@${username}`, ); diff --git a/src/telegram/bot-handlers.ts b/src/telegram/bot-handlers.ts index 477b982804..2caa1bddec 100644 --- a/src/telegram/bot-handlers.ts +++ b/src/telegram/bot-handlers.ts @@ -68,14 +68,20 @@ export const registerTelegramHandlers = ({ debounceMs, buildKey: (entry) => entry.debounceKey, shouldDebounce: (entry) => { - if (entry.allMedia.length > 0) return false; + if (entry.allMedia.length > 0) { + return false; + } const text = entry.msg.text ?? entry.msg.caption ?? ""; - if (!text.trim()) return false; + if (!text.trim()) { + return false; + } return !hasControlCommand(text, cfg, { botUsername: entry.botUsername }); }, onFlush: async (entries) => { const last = entries.at(-1); - if (!last) return; + if (!last) { + return; + } if (entries.length === 1) { await processMessage(last.ctx, last.allMedia, last.storeAllowFrom); return; @@ -84,7 +90,9 @@ export const registerTelegramHandlers = ({ .map((entry) => entry.msg.text ?? entry.msg.caption ?? "") .filter(Boolean) .join("\n"); - if (!combinedText.trim()) return; + if (!combinedText.trim()) { + return; + } const first = entries[0]; const baseCtx = first.ctx as { me?: unknown; getFile?: unknown } & Record; const getFile = @@ -146,10 +154,14 @@ export const registerTelegramHandlers = ({ const first = entry.messages[0]; const last = entry.messages.at(-1); - if (!first || !last) return; + if (!first || !last) { + return; + } const combinedText = entry.messages.map((m) => m.msg.text ?? "").join(""); - if (!combinedText.trim()) return; + if (!combinedText.trim()) { + return; + } const syntheticMessage: TelegramMessage = { ...first.msg, @@ -191,8 +203,12 @@ export const registerTelegramHandlers = ({ bot.on("callback_query", async (ctx) => { const callback = ctx.callbackQuery; - if (!callback) return; - if (shouldSkipUpdate(ctx)) return; + if (!callback) { + return; + } + if (shouldSkipUpdate(ctx)) { + return; + } // Answer immediately to prevent Telegram from retrying while we process await withTelegramApiErrorLogging({ operation: "answerCallbackQuery", @@ -202,19 +218,27 @@ export const registerTelegramHandlers = ({ try { const data = (callback.data ?? "").trim(); const callbackMessage = callback.message; - if (!data || !callbackMessage) return; + if (!data || !callbackMessage) { + return; + } const inlineButtonsScope = resolveTelegramInlineButtonsScope({ cfg, accountId, }); - if (inlineButtonsScope === "off") return; + if (inlineButtonsScope === "off") { + return; + } const chatId = callbackMessage.chat.id; const isGroup = callbackMessage.chat.type === "group" || callbackMessage.chat.type === "supergroup"; - if (inlineButtonsScope === "dm" && isGroup) return; - if (inlineButtonsScope === "group" && !isGroup) return; + if (inlineButtonsScope === "dm" && isGroup) { + return; + } + if (inlineButtonsScope === "group" && !isGroup) { + return; + } const messageThreadId = (callbackMessage as { message_thread_id?: number }).message_thread_id; const isForum = (callbackMessage.chat as { is_forum?: boolean }).is_forum === true; @@ -303,7 +327,9 @@ export const registerTelegramHandlers = ({ if (inlineButtonsScope === "allowlist") { if (!isGroup) { - if (dmPolicy === "disabled") return; + if (dmPolicy === "disabled") { + return; + } if (dmPolicy !== "open") { const allowed = effectiveDmAllow.hasWildcard || @@ -313,7 +339,9 @@ export const registerTelegramHandlers = ({ senderId, senderUsername, })); - if (!allowed) return; + if (!allowed) { + return; + } } } else { const allowed = @@ -324,17 +352,23 @@ export const registerTelegramHandlers = ({ senderId, senderUsername, })); - if (!allowed) return; + if (!allowed) { + return; + } } } const paginationMatch = data.match(/^commands_page_(\d+|noop)(?::(.+))?$/); if (paginationMatch) { const pageValue = paginationMatch[1]; - if (pageValue === "noop") return; + if (pageValue === "noop") { + return; + } const page = Number.parseInt(pageValue, 10); - if (Number.isNaN(page) || page < 1) return; + if (Number.isNaN(page) || page < 1) { + return; + } const agentId = paginationMatch[2]?.trim() || resolveDefaultAgentId(cfg) || undefined; const skillCommands = listSkillCommandsForAgents({ @@ -391,8 +425,12 @@ export const registerTelegramHandlers = ({ bot.on("message:migrate_to_chat_id", async (ctx) => { try { const msg = ctx.message; - if (!msg?.migrate_to_chat_id) return; - if (shouldSkipUpdate(ctx)) return; + if (!msg?.migrate_to_chat_id) { + return; + } + if (shouldSkipUpdate(ctx)) { + return; + } const oldChatId = String(msg.chat.id); const newChatId = String(msg.migrate_to_chat_id); @@ -438,8 +476,12 @@ export const registerTelegramHandlers = ({ bot.on("message", async (ctx) => { try { const msg = ctx.message; - if (!msg) return; - if (shouldSkipUpdate(ctx)) return; + if (!msg) { + return; + } + if (shouldSkipUpdate(ctx)) { + return; + } const chatId = msg.chat.id; const isGroup = msg.chat.type === "group" || msg.chat.type === "supergroup"; diff --git a/src/telegram/bot-message-context.ts b/src/telegram/bot-message-context.ts index 9696e4f1b0..b86aa2d603 100644 --- a/src/telegram/bot-message-context.ts +++ b/src/telegram/bot-message-context.ts @@ -121,7 +121,9 @@ async function resolveStickerVisionSupport(params: { agentId: params.agentId, }); const entry = findModelInCatalog(catalog, defaultModel.provider, defaultModel.model); - if (!entry) return false; + if (!entry) { + return false; + } return modelSupportsVision(entry); } catch { return false; @@ -221,7 +223,9 @@ export const buildTelegramMessageContext = async ({ // DM access control (secure defaults): "pairing" (default) / "allowlist" / "open" / "disabled" if (!isGroup) { - if (dmPolicy === "disabled") return null; + if (dmPolicy === "disabled") { + return null; + } if (dmPolicy !== "open") { const candidate = String(chatId); @@ -333,12 +337,19 @@ export const buildTelegramMessageContext = async ({ const historyKey = isGroup ? buildTelegramGroupPeerId(chatId, resolvedThreadId) : undefined; let placeholder = ""; - if (msg.photo) placeholder = ""; - else if (msg.video) placeholder = ""; - else if (msg.video_note) placeholder = ""; - else if (msg.audio || msg.voice) placeholder = ""; - else if (msg.document) placeholder = ""; - else if (msg.sticker) placeholder = ""; + if (msg.photo) { + placeholder = ""; + } else if (msg.video) { + placeholder = ""; + } else if (msg.video_note) { + placeholder = ""; + } else if (msg.audio || msg.voice) { + placeholder = ""; + } else if (msg.document) { + placeholder = ""; + } else if (msg.sticker) { + placeholder = ""; + } // Check if sticker has a cached description - if so, use it instead of sending the image const cachedStickerDescription = allMedia[0]?.stickerMetadata?.cachedDescription; @@ -359,8 +370,12 @@ export const buildTelegramMessageContext = async ({ const rawTextSource = msg.text ?? msg.caption ?? ""; const rawText = expandTextLinks(rawTextSource, msg.entities ?? msg.caption_entities).trim(); let rawBody = [rawText, locationText].filter(Boolean).join("\n").trim(); - if (!rawBody) rawBody = placeholder; - if (!rawBody && allMedia.length === 0) return null; + if (!rawBody) { + rawBody = placeholder; + } + if (!rawBody && allMedia.length === 0) { + return null; + } let bodyText = rawBody; if (!bodyText && allMedia.length > 0) { diff --git a/src/telegram/bot-message-dispatch.ts b/src/telegram/bot-message-dispatch.ts index ea006e3167..6cc626a071 100644 --- a/src/telegram/bot-message-dispatch.ts +++ b/src/telegram/bot-message-dispatch.ts @@ -28,7 +28,9 @@ async function resolveStickerVisionSupport(cfg, agentId) { const catalog = await loadModelCatalog({ config: cfg }); const defaultModel = resolveDefaultModelForAgent({ cfg, agentId }); const entry = findModelInCatalog(catalog, defaultModel.provider, defaultModel.model); - if (!entry) return false; + if (!entry) { + return false; + } return modelSupportsVision(entry); } catch { return false; @@ -92,8 +94,12 @@ export const dispatchTelegramMessage = async ({ let lastPartialText = ""; let draftText = ""; const updateDraftFromPartial = (text?: string) => { - if (!draftStream || !text) return; - if (text === lastPartialText) return; + if (!draftStream || !text) { + return; + } + if (text === lastPartialText) { + return; + } if (streamMode === "partial") { lastPartialText = text; draftStream.update(text); @@ -108,7 +114,9 @@ export const dispatchTelegramMessage = async ({ draftText = ""; } lastPartialText = text; - if (!delta) return; + if (!delta) { + return; + } if (!draftChunker) { draftText = text; draftStream.update(draftText); @@ -124,7 +132,9 @@ export const dispatchTelegramMessage = async ({ }); }; const flushDraft = async () => { - if (!draftStream) return; + if (!draftStream) { + return; + } if (draftChunker?.hasBuffered()) { draftChunker.drain({ force: true, @@ -133,7 +143,9 @@ export const dispatchTelegramMessage = async ({ }, }); draftChunker.reset(); - if (draftText) draftStream.update(draftText); + if (draftText) { + draftStream.update(draftText); + } } await draftStream.flush(); }; @@ -240,7 +252,9 @@ export const dispatchTelegramMessage = async ({ } }, onSkip: (_payload, info) => { - if (info.reason !== "silent") deliveryState.skippedNonSilent += 1; + if (info.reason !== "silent") { + deliveryState.skippedNonSilent += 1; + } }, onError: (err, info) => { runtime.error?.(danger(`telegram ${info.kind} reply failed: ${String(err)}`)); @@ -262,7 +276,9 @@ export const dispatchTelegramMessage = async ({ onPartialReply: draftStream ? (payload) => updateDraftFromPartial(payload.text) : undefined, onReasoningStream: draftStream ? (payload) => { - if (payload.text) draftStream.update(payload.text); + if (payload.text) { + draftStream.update(payload.text); + } } : undefined, disableBlockStreaming, @@ -304,7 +320,9 @@ export const dispatchTelegramMessage = async ({ ackReactionValue: ackReactionPromise ? "ack" : null, remove: () => reactionApi?.(chatId, msg.message_id ?? 0, []) ?? Promise.resolve(), onError: (err) => { - if (!msg.message_id) return; + if (!msg.message_id) { + return; + } logAckFailure({ log: logVerbose, channel: "telegram", diff --git a/src/telegram/bot-message.ts b/src/telegram/bot-message.ts index 313296b1d7..ffaa00be58 100644 --- a/src/telegram/bot-message.ts +++ b/src/telegram/bot-message.ts @@ -46,7 +46,9 @@ export const createTelegramMessageProcessor = (deps) => { resolveGroupRequireMention, resolveTelegramGroupConfig, }); - if (!context) return; + if (!context) { + return; + } await dispatchTelegramMessage({ context, bot, diff --git a/src/telegram/bot-native-commands.ts b/src/telegram/bot-native-commands.ts index cd53459e6d..cde1656cd9 100644 --- a/src/telegram/bot-native-commands.ts +++ b/src/telegram/bot-native-commands.ts @@ -344,8 +344,12 @@ export const registerTelegramNativeCommands = ({ for (const command of nativeCommands) { bot.command(command.name, async (ctx: TelegramNativeCommandContext) => { const msg = ctx.message; - if (!msg) return; - if (shouldSkipUpdate(ctx)) return; + if (!msg) { + return; + } + if (shouldSkipUpdate(ctx)) { + return; + } const auth = await resolveTelegramCommandAuth({ msg, bot, @@ -358,7 +362,9 @@ export const registerTelegramNativeCommands = ({ resolveTelegramGroupConfig, requireAuth: true, }); - if (!auth) return; + if (!auth) { + return; + } const { chatId, isGroup, @@ -522,7 +528,9 @@ export const registerTelegramNativeCommands = ({ } }, onSkip: (_payload, info) => { - if (info.reason !== "silent") deliveryState.skippedNonSilent += 1; + if (info.reason !== "silent") { + deliveryState.skippedNonSilent += 1; + } }, onError: (err, info) => { runtime.error?.(danger(`telegram slash ${info.kind} reply failed: ${String(err)}`)); @@ -554,8 +562,12 @@ export const registerTelegramNativeCommands = ({ for (const pluginCommand of pluginCommands) { bot.command(pluginCommand.command, async (ctx: TelegramNativeCommandContext) => { const msg = ctx.message; - if (!msg) return; - if (shouldSkipUpdate(ctx)) return; + if (!msg) { + return; + } + if (shouldSkipUpdate(ctx)) { + return; + } const chatId = msg.chat.id; const rawText = ctx.match?.trim() ?? ""; const commandBody = `/${pluginCommand.command}${rawText ? ` ${rawText}` : ""}`; @@ -580,7 +592,9 @@ export const registerTelegramNativeCommands = ({ resolveTelegramGroupConfig, requireAuth: match.command.requireAuth !== false, }); - if (!auth) return; + if (!auth) { + return; + } const { resolvedThreadId, senderId, commandAuthorized, isGroup } = auth; const messageThreadId = (msg as { message_thread_id?: number }).message_thread_id; const threadIdForSend = isGroup ? resolvedThreadId : messageThreadId; diff --git a/src/telegram/bot-updates.ts b/src/telegram/bot-updates.ts index e63658eb48..662cb92df2 100644 --- a/src/telegram/bot-updates.ts +++ b/src/telegram/bot-updates.ts @@ -29,9 +29,13 @@ export const resolveTelegramUpdateId = (ctx: TelegramUpdateKeyContext) => export const buildTelegramUpdateKey = (ctx: TelegramUpdateKeyContext) => { const updateId = resolveTelegramUpdateId(ctx); - if (typeof updateId === "number") return `update:${updateId}`; + if (typeof updateId === "number") { + return `update:${updateId}`; + } const callbackId = ctx.callbackQuery?.id; - if (callbackId) return `callback:${callbackId}`; + if (callbackId) { + return `callback:${callbackId}`; + } const msg = ctx.message ?? ctx.update?.message ?? ctx.update?.edited_message ?? ctx.callbackQuery?.message; const chatId = msg?.chat?.id; diff --git a/src/telegram/bot.create-telegram-bot.accepts-group-messages-mentionpatterns-match-without-botusername.test.ts b/src/telegram/bot.create-telegram-bot.accepts-group-messages-mentionpatterns-match-without-botusername.test.ts index 180168b27e..765d0d2f87 100644 --- a/src/telegram/bot.create-telegram-bot.accepts-group-messages-mentionpatterns-match-without-botusername.test.ts +++ b/src/telegram/bot.create-telegram-bot.accepts-group-messages-mentionpatterns-match-without-botusername.test.ts @@ -129,7 +129,9 @@ let replyModule: typeof import("../auto-reply/reply.js"); const getOnHandler = (event: string) => { const handler = onSpy.mock.calls.find((call) => call[0] === event)?.[1]; - if (!handler) throw new Error(`Missing handler for event: ${event}`); + if (!handler) { + throw new Error(`Missing handler for event: ${event}`); + } return handler as (ctx: Record) => Promise; }; diff --git a/src/telegram/bot.create-telegram-bot.applies-topic-skill-filters-system-prompts.test.ts b/src/telegram/bot.create-telegram-bot.applies-topic-skill-filters-system-prompts.test.ts index a5397e3fad..4b0c852d97 100644 --- a/src/telegram/bot.create-telegram-bot.applies-topic-skill-filters-system-prompts.test.ts +++ b/src/telegram/bot.create-telegram-bot.applies-topic-skill-filters-system-prompts.test.ts @@ -128,7 +128,9 @@ let replyModule: typeof import("../auto-reply/reply.js"); const getOnHandler = (event: string) => { const handler = onSpy.mock.calls.find((call) => call[0] === event)?.[1]; - if (!handler) throw new Error(`Missing handler for event: ${event}`); + if (!handler) { + throw new Error(`Missing handler for event: ${event}`); + } return handler as (ctx: Record) => Promise; }; @@ -326,7 +328,9 @@ describe("createTelegramBot", () => { const verboseHandler = commandSpy.mock.calls.find((call) => call[0] === "verbose")?.[1] as | ((ctx: Record) => Promise) | undefined; - if (!verboseHandler) throw new Error("verbose command handler missing"); + if (!verboseHandler) { + throw new Error("verbose command handler missing"); + } await verboseHandler({ message: { diff --git a/src/telegram/bot.create-telegram-bot.blocks-all-group-messages-grouppolicy-is.test.ts b/src/telegram/bot.create-telegram-bot.blocks-all-group-messages-grouppolicy-is.test.ts index 2755e35571..7cc27f7f78 100644 --- a/src/telegram/bot.create-telegram-bot.blocks-all-group-messages-grouppolicy-is.test.ts +++ b/src/telegram/bot.create-telegram-bot.blocks-all-group-messages-grouppolicy-is.test.ts @@ -128,7 +128,9 @@ let replyModule: typeof import("../auto-reply/reply.js"); const getOnHandler = (event: string) => { const handler = onSpy.mock.calls.find((call) => call[0] === event)?.[1]; - if (!handler) throw new Error(`Missing handler for event: ${event}`); + if (!handler) { + throw new Error(`Missing handler for event: ${event}`); + } return handler as (ctx: Record) => Promise; }; diff --git a/src/telegram/bot.create-telegram-bot.dedupes-duplicate-callback-query-updates-by-update.test.ts b/src/telegram/bot.create-telegram-bot.dedupes-duplicate-callback-query-updates-by-update.test.ts index ac8e0bdc20..7feecf57d3 100644 --- a/src/telegram/bot.create-telegram-bot.dedupes-duplicate-callback-query-updates-by-update.test.ts +++ b/src/telegram/bot.create-telegram-bot.dedupes-duplicate-callback-query-updates-by-update.test.ts @@ -128,7 +128,9 @@ let replyModule: typeof import("../auto-reply/reply.js"); const getOnHandler = (event: string) => { const handler = onSpy.mock.calls.find((call) => call[0] === event)?.[1]; - if (!handler) throw new Error(`Missing handler for event: ${event}`); + if (!handler) { + throw new Error(`Missing handler for event: ${event}`); + } return handler as (ctx: Record) => Promise; }; diff --git a/src/telegram/bot.create-telegram-bot.installs-grammy-throttler.test.ts b/src/telegram/bot.create-telegram-bot.installs-grammy-throttler.test.ts index 7238375073..c1b78ea243 100644 --- a/src/telegram/bot.create-telegram-bot.installs-grammy-throttler.test.ts +++ b/src/telegram/bot.create-telegram-bot.installs-grammy-throttler.test.ts @@ -132,7 +132,9 @@ let replyModule: typeof import("../auto-reply/reply.js"); const getOnHandler = (event: string) => { const handler = onSpy.mock.calls.find((call) => call[0] === event)?.[1]; - if (!handler) throw new Error(`Missing handler for event: ${event}`); + if (!handler) { + throw new Error(`Missing handler for event: ${event}`); + } return handler as (ctx: Record) => Promise; }; diff --git a/src/telegram/bot.create-telegram-bot.matches-tg-prefixed-allowfrom-entries-case-insensitively.test.ts b/src/telegram/bot.create-telegram-bot.matches-tg-prefixed-allowfrom-entries-case-insensitively.test.ts index 2f1725947a..365e9e1a2c 100644 --- a/src/telegram/bot.create-telegram-bot.matches-tg-prefixed-allowfrom-entries-case-insensitively.test.ts +++ b/src/telegram/bot.create-telegram-bot.matches-tg-prefixed-allowfrom-entries-case-insensitively.test.ts @@ -128,7 +128,9 @@ let replyModule: typeof import("../auto-reply/reply.js"); const getOnHandler = (event: string) => { const handler = onSpy.mock.calls.find((call) => call[0] === event)?.[1]; - if (!handler) throw new Error(`Missing handler for event: ${event}`); + if (!handler) { + throw new Error(`Missing handler for event: ${event}`); + } return handler as (ctx: Record) => Promise; }; diff --git a/src/telegram/bot.create-telegram-bot.matches-usernames-case-insensitively-grouppolicy-is.test.ts b/src/telegram/bot.create-telegram-bot.matches-usernames-case-insensitively-grouppolicy-is.test.ts index 532111bbe8..d32496cdcb 100644 --- a/src/telegram/bot.create-telegram-bot.matches-usernames-case-insensitively-grouppolicy-is.test.ts +++ b/src/telegram/bot.create-telegram-bot.matches-usernames-case-insensitively-grouppolicy-is.test.ts @@ -128,7 +128,9 @@ let replyModule: typeof import("../auto-reply/reply.js"); const getOnHandler = (event: string) => { const handler = onSpy.mock.calls.find((call) => call[0] === event)?.[1]; - if (!handler) throw new Error(`Missing handler for event: ${event}`); + if (!handler) { + throw new Error(`Missing handler for event: ${event}`); + } return handler as (ctx: Record) => Promise; }; diff --git a/src/telegram/bot.create-telegram-bot.routes-dms-by-telegram-accountid-binding.test.ts b/src/telegram/bot.create-telegram-bot.routes-dms-by-telegram-accountid-binding.test.ts index 0e0774379b..929395fe6b 100644 --- a/src/telegram/bot.create-telegram-bot.routes-dms-by-telegram-accountid-binding.test.ts +++ b/src/telegram/bot.create-telegram-bot.routes-dms-by-telegram-accountid-binding.test.ts @@ -128,7 +128,9 @@ let replyModule: typeof import("../auto-reply/reply.js"); const getOnHandler = (event: string) => { const handler = onSpy.mock.calls.find((call) => call[0] === event)?.[1]; - if (!handler) throw new Error(`Missing handler for event: ${event}`); + if (!handler) { + throw new Error(`Missing handler for event: ${event}`); + } return handler as (ctx: Record) => Promise; }; diff --git a/src/telegram/bot.create-telegram-bot.sends-replies-without-native-reply-threading.test.ts b/src/telegram/bot.create-telegram-bot.sends-replies-without-native-reply-threading.test.ts index 7cac6ad72d..1d482c0a78 100644 --- a/src/telegram/bot.create-telegram-bot.sends-replies-without-native-reply-threading.test.ts +++ b/src/telegram/bot.create-telegram-bot.sends-replies-without-native-reply-threading.test.ts @@ -133,7 +133,9 @@ let replyModule: typeof import("../auto-reply/reply.js"); const getOnHandler = (event: string) => { const handler = onSpy.mock.calls.find((call) => call[0] === event)?.[1]; - if (!handler) throw new Error(`Missing handler for event: ${event}`); + if (!handler) { + throw new Error(`Missing handler for event: ${event}`); + } return handler as (ctx: Record) => Promise; }; diff --git a/src/telegram/bot.test.ts b/src/telegram/bot.test.ts index e2c775a11b..3516588f79 100644 --- a/src/telegram/bot.test.ts +++ b/src/telegram/bot.test.ts @@ -167,7 +167,9 @@ vi.mock("../auto-reply/reply.js", () => { const getOnHandler = (event: string) => { const handler = onSpy.mock.calls.find((call) => call[0] === event)?.[1]; - if (!handler) throw new Error(`Missing handler for event: ${event}`); + if (!handler) { + throw new Error(`Missing handler for event: ${event}`); + } return handler as (ctx: Record) => Promise; }; @@ -2339,7 +2341,9 @@ describe("createTelegramBot", () => { const handler = commandSpy.mock.calls.find((call) => call[0] === "status")?.[1] as | ((ctx: Record) => Promise) | undefined; - if (!handler) throw new Error("status command handler missing"); + if (!handler) { + throw new Error("status command handler missing"); + } await handler({ message: { @@ -2380,7 +2384,9 @@ describe("createTelegramBot", () => { const handler = commandSpy.mock.calls.find((call) => call[0] === "status")?.[1] as | ((ctx: Record) => Promise) | undefined; - if (!handler) throw new Error("status command handler missing"); + if (!handler) { + throw new Error("status command handler missing"); + } await handler({ message: { @@ -2422,7 +2428,9 @@ describe("createTelegramBot", () => { const handler = commandSpy.mock.calls.find((call) => call[0] === "status")?.[1] as | ((ctx: Record) => Promise) | undefined; - if (!handler) throw new Error("status command handler missing"); + if (!handler) { + throw new Error("status command handler missing"); + } await handler({ message: { @@ -2467,7 +2475,9 @@ describe("createTelegramBot", () => { const verboseHandler = commandSpy.mock.calls.find((call) => call[0] === "verbose")?.[1] as | ((ctx: Record) => Promise) | undefined; - if (!verboseHandler) throw new Error("verbose command handler missing"); + if (!verboseHandler) { + throw new Error("verbose command handler missing"); + } await verboseHandler({ message: { diff --git a/src/telegram/bot.ts b/src/telegram/bot.ts index 2baec9e1c4..c416f97762 100644 --- a/src/telegram/bot.ts +++ b/src/telegram/bot.ts @@ -91,7 +91,9 @@ export function getTelegramSequentialKey(ctx: { rawText && isControlCommandMessage(rawText, undefined, botUsername ? { botUsername } : undefined) ) { - if (typeof chatId === "number") return `telegram:${chatId}:control`; + if (typeof chatId === "number") { + return `telegram:${chatId}:control`; + } return "telegram:control"; } const isGroup = msg?.chat?.type === "group" || msg?.chat?.type === "supergroup"; @@ -158,8 +160,12 @@ export function createTelegramBot(opts: TelegramBotOptions) { const recordUpdateId = (ctx: TelegramUpdateKeyContext) => { const updateId = resolveTelegramUpdateId(ctx); - if (typeof updateId !== "number") return; - if (lastUpdateId !== null && updateId <= lastUpdateId) return; + if (typeof updateId !== "number") { + return; + } + if (lastUpdateId !== null && updateId <= lastUpdateId) { + return; + } lastUpdateId = updateId; void opts.updateOffset?.onUpdateId?.(updateId); }; @@ -167,7 +173,9 @@ export function createTelegramBot(opts: TelegramBotOptions) { const shouldSkipUpdate = (ctx: TelegramUpdateKeyContext) => { const updateId = resolveTelegramUpdateId(ctx); if (typeof updateId === "number" && lastUpdateId !== null) { - if (updateId <= lastUpdateId) return true; + if (updateId <= lastUpdateId) { + return true; + } } const key = buildTelegramUpdateKey(ctx); const skipped = recentUpdates.check(key); @@ -195,7 +203,9 @@ export function createTelegramBot(opts: TelegramBotOptions) { } if (value && typeof value === "object") { const obj = value as object; - if (seen.has(obj)) return "[Circular]"; + if (seen.has(obj)) { + return "[Circular]"; + } seen.add(obj); } return value; @@ -261,7 +271,9 @@ export function createTelegramBot(opts: TelegramBotOptions) { botHasTopicsEnabled = fromCtx.has_topics_enabled; return botHasTopicsEnabled; } - if (typeof botHasTopicsEnabled === "boolean") return botHasTopicsEnabled; + if (typeof botHasTopicsEnabled === "boolean") { + return botHasTopicsEnabled; + } try { const me = (await withTelegramApiErrorLogging({ operation: "getMe", @@ -296,8 +308,12 @@ export function createTelegramBot(opts: TelegramBotOptions) { try { const store = loadSessionStore(storePath); const entry = store[sessionKey]; - if (entry?.groupActivation === "always") return false; - if (entry?.groupActivation === "mention") return true; + if (entry?.groupActivation === "always") { + return false; + } + if (entry?.groupActivation === "mention") { + return true; + } } catch (err) { logVerbose(`Failed to load session for activation check: ${String(err)}`); } @@ -314,7 +330,9 @@ export function createTelegramBot(opts: TelegramBotOptions) { }); const resolveTelegramGroupConfig = (chatId: string | number, messageThreadId?: number) => { const groups = telegramCfg.groups; - if (!groups) return { groupConfig: undefined, topicConfig: undefined }; + if (!groups) { + return { groupConfig: undefined, topicConfig: undefined }; + } const groupKey = String(chatId); const groupConfig = groups[groupKey] ?? groups["*"]; const topicConfig = @@ -369,8 +387,12 @@ export function createTelegramBot(opts: TelegramBotOptions) { bot.on("message_reaction", async (ctx) => { try { const reaction = ctx.messageReaction; - if (!reaction) return; - if (shouldSkipUpdate(ctx)) return; + if (!reaction) { + return; + } + if (shouldSkipUpdate(ctx)) { + return; + } const chatId = reaction.chat.id; const messageId = reaction.message_id; @@ -378,9 +400,15 @@ export function createTelegramBot(opts: TelegramBotOptions) { // Resolve reaction notification mode (default: "own") const reactionMode = telegramCfg.reactionNotifications ?? "own"; - if (reactionMode === "off") return; - if (user?.is_bot) return; - if (reactionMode === "own" && !wasSentByBot(chatId, messageId)) return; + if (reactionMode === "off") { + return; + } + if (user?.is_bot) { + return; + } + if (reactionMode === "own" && !wasSentByBot(chatId, messageId)) { + return; + } // Detect added reactions const oldEmojis = new Set( @@ -392,7 +420,9 @@ export function createTelegramBot(opts: TelegramBotOptions) { .filter((r): r is { type: "emoji"; emoji: string } => r.type === "emoji") .filter((r) => !oldEmojis.has(r.emoji)); - if (addedReactions.length === 0) return; + if (addedReactions.length === 0) { + return; + } // Build sender label const senderName = user diff --git a/src/telegram/bot/delivery.ts b/src/telegram/bot/delivery.ts index 669340b205..2fca5dc723 100644 --- a/src/telegram/bot/delivery.ts +++ b/src/telegram/bot/delivery.ts @@ -105,7 +105,9 @@ export async function deliverReplies(params: { const chunks = chunkText(reply.text || ""); for (let i = 0; i < chunks.length; i += 1) { const chunk = chunks[i]; - if (!chunk) continue; + if (!chunk) { + continue; + } // Only attach buttons to the first chunk. const shouldAttachButtons = i === 0 && replyMarkup; await sendTelegramText(bot, chatId, chunk.html, runtime, { @@ -306,7 +308,9 @@ export async function resolveMedia( logVerbose("telegram: skipping animated/video sticker (only static stickers supported)"); return null; } - if (!sticker.file_id) return null; + if (!sticker.file_id) { + return null; + } try { const file = await ctx.getFile(); @@ -389,7 +393,9 @@ export async function resolveMedia( msg.document ?? msg.audio ?? msg.voice; - if (!m?.file_id) return null; + if (!m?.file_id) { + return null; + } const file = await ctx.getFile(); if (!file.file_path) { throw new Error("Telegram getFile returned no file_path"); @@ -413,10 +419,15 @@ export async function resolveMedia( originalName, ); let placeholder = ""; - if (msg.photo) placeholder = ""; - else if (msg.video) placeholder = ""; - else if (msg.video_note) placeholder = ""; - else if (msg.audio || msg.voice) placeholder = ""; + if (msg.photo) { + placeholder = ""; + } else if (msg.video) { + placeholder = ""; + } else if (msg.video_note) { + placeholder = ""; + } else if (msg.audio || msg.voice) { + placeholder = ""; + } return { path: saved.path, contentType: saved.contentType, placeholder }; } diff --git a/src/telegram/bot/helpers.ts b/src/telegram/bot/helpers.ts index c74724e500..17451387ca 100644 --- a/src/telegram/bot/helpers.ts +++ b/src/telegram/bot/helpers.ts @@ -65,7 +65,9 @@ export function resolveTelegramStreamMode( telegramCfg: Pick | undefined, ): TelegramStreamMode { const raw = telegramCfg?.streamMode?.trim().toLowerCase(); - if (raw === "off" || raw === "partial" || raw === "block") return raw; + if (raw === "off" || raw === "partial" || raw === "block") { + return raw; + } return "partial"; } @@ -97,8 +99,12 @@ export function buildSenderLabel(msg: TelegramMessage, senderId?: number | strin senderId != null && `${senderId}`.trim() ? `${senderId}`.trim() : undefined; const fallbackId = normalizedSenderId ?? (msg.from?.id != null ? String(msg.from.id) : undefined); const idPart = fallbackId ? `id:${fallbackId}` : undefined; - if (label && idPart) return `${label} ${idPart}`; - if (label) return label; + if (label && idPart) { + return `${label} ${idPart}`; + } + if (label) { + return label; + } return idPart ?? "id:unknown"; } @@ -109,18 +115,26 @@ export function buildGroupLabel( ) { const title = msg.chat?.title; const topicSuffix = messageThreadId != null ? ` topic:${messageThreadId}` : ""; - if (title) return `${title} id:${chatId}${topicSuffix}`; + if (title) { + return `${title} id:${chatId}${topicSuffix}`; + } return `group:${chatId}${topicSuffix}`; } export function hasBotMention(msg: TelegramMessage, botUsername: string) { const text = (msg.text ?? msg.caption ?? "").toLowerCase(); - if (text.includes(`@${botUsername}`)) return true; + if (text.includes(`@${botUsername}`)) { + return true; + } const entities = msg.entities ?? msg.caption_entities ?? []; for (const ent of entities) { - if (ent.type !== "mention") continue; + if (ent.type !== "mention") { + continue; + } const slice = (msg.text ?? msg.caption ?? "").slice(ent.offset, ent.offset + ent.length); - if (slice.toLowerCase() === `@${botUsername}`) return true; + if (slice.toLowerCase() === `@${botUsername}`) { + return true; + } } return false; } @@ -133,7 +147,9 @@ type TelegramTextLinkEntity = { }; export function expandTextLinks(text: string, entities?: TelegramTextLinkEntity[] | null): string { - if (!text || !entities?.length) return text; + if (!text || !entities?.length) { + return text; + } const textLinks = entities .filter( @@ -142,7 +158,9 @@ export function expandTextLinks(text: string, entities?: TelegramTextLinkEntity[ ) .toSorted((a, b) => b.offset - a.offset); - if (textLinks.length === 0) return text; + if (textLinks.length === 0) { + return text; + } let result = text; for (const entity of textLinks) { @@ -155,9 +173,13 @@ export function expandTextLinks(text: string, entities?: TelegramTextLinkEntity[ } export function resolveTelegramReplyId(raw?: string): number | undefined { - if (!raw) return undefined; + if (!raw) { + return undefined; + } const parsed = Number(raw); - if (!Number.isFinite(parsed)) return undefined; + if (!Number.isFinite(parsed)) { + return undefined; + } return parsed; } @@ -185,17 +207,25 @@ export function describeReplyTarget(msg: TelegramMessage): TelegramReplyTarget | const replyBody = (reply.text ?? reply.caption ?? "").trim(); body = replyBody; if (!body) { - if (reply.photo) body = ""; - else if (reply.video) body = ""; - else if (reply.audio || reply.voice) body = ""; - else if (reply.document) body = ""; - else { + if (reply.photo) { + body = ""; + } else if (reply.video) { + body = ""; + } else if (reply.audio || reply.voice) { + body = ""; + } else if (reply.document) { + body = ""; + } else { const locationData = extractTelegramLocation(reply); - if (locationData) body = formatLocationText(locationData); + if (locationData) { + body = formatLocationText(locationData); + } } } } - if (!body) return null; + if (!body) { + return null; + } const sender = reply ? buildSenderName(reply) : undefined; const senderLabel = sender ? `${sender}` : "unknown sender"; @@ -243,7 +273,9 @@ function buildForwardedContextFromUser(params: { type: string; }): TelegramForwardedContext | null { const { display, name, username, id } = normalizeForwardedUserLabel(params.user); - if (!display) return null; + if (!display) { + return null; + } return { from: display, date: params.date, @@ -260,7 +292,9 @@ function buildForwardedContextFromHiddenName(params: { type: string; }): TelegramForwardedContext | null { const trimmed = params.name?.trim(); - if (!trimmed) return null; + if (!trimmed) { + return null; + } return { from: trimmed, date: params.date, @@ -278,7 +312,9 @@ function buildForwardedContextFromChat(params: { const fallbackKind = params.type === "channel" || params.type === "legacy_channel" ? "channel" : "chat"; const { display, title, username, id } = normalizeForwardedChatLabel(params.chat, fallbackKind); - if (!display) return null; + if (!display) { + return null; + } const signature = params.signature?.trim() || undefined; const from = signature ? `${display} (${signature})` : display; return { @@ -339,7 +375,9 @@ export function normalizeForwardedContext(msg: TelegramMessage): TelegramForward if (forwardMsg.forward_origin) { const originContext = resolveForwardOrigin(forwardMsg.forward_origin, signature); - if (originContext) return originContext; + if (originContext) { + return originContext; + } } if (forwardMsg.forward_from_chat) { @@ -351,7 +389,9 @@ export function normalizeForwardedContext(msg: TelegramMessage): TelegramForward type: legacyType, signature, }); - if (legacyContext) return legacyContext; + if (legacyContext) { + return legacyContext; + } } if (forwardMsg.forward_from) { @@ -360,7 +400,9 @@ export function normalizeForwardedContext(msg: TelegramMessage): TelegramForward date: forwardMsg.forward_date, type: "legacy_user", }); - if (legacyContext) return legacyContext; + if (legacyContext) { + return legacyContext; + } } const hiddenContext = buildForwardedContextFromHiddenName({ @@ -368,7 +410,9 @@ export function normalizeForwardedContext(msg: TelegramMessage): TelegramForward date: forwardMsg.forward_date, type: "legacy_hidden_user", }); - if (hiddenContext) return hiddenContext; + if (hiddenContext) { + return hiddenContext; + } return null; } diff --git a/src/telegram/download.ts b/src/telegram/download.ts index 31f431db02..748749c4c0 100644 --- a/src/telegram/download.ts +++ b/src/telegram/download.ts @@ -27,7 +27,9 @@ export async function downloadTelegramFile( info: TelegramFileInfo, maxBytes?: number, ): Promise { - if (!info.file_path) throw new Error("file_path missing"); + if (!info.file_path) { + throw new Error("file_path missing"); + } const url = `https://api.telegram.org/file/bot${token}/${info.file_path}`; const res = await fetch(url); if (!res.ok || !res.body) { @@ -42,6 +44,8 @@ export async function downloadTelegramFile( // save with inbound subdir const saved = await saveMediaBuffer(array, mime, "inbound", maxBytes, info.file_path); // Ensure extension matches mime if possible - if (!saved.contentType && mime) saved.contentType = mime; + if (!saved.contentType && mime) { + saved.contentType = mime; + } return saved; } diff --git a/src/telegram/draft-stream.ts b/src/telegram/draft-stream.ts index 74c2fa26df..3d63587e6e 100644 --- a/src/telegram/draft-stream.ts +++ b/src/telegram/draft-stream.ts @@ -37,9 +37,13 @@ export function createTelegramDraftStream(params: { let stopped = false; const sendDraft = async (text: string) => { - if (stopped) return; + if (stopped) { + return; + } const trimmed = text.trimEnd(); - if (!trimmed) return; + if (!trimmed) { + return; + } if (trimmed.length > maxChars) { // Drafts are capped at 4096 chars. Stop streaming once we exceed the cap // so we don't keep sending failing updates or a truncated preview. @@ -47,7 +51,9 @@ export function createTelegramDraftStream(params: { params.warn?.(`telegram draft stream stopped (draft length ${trimmed.length} > ${maxChars})`); return; } - if (trimmed === lastSentText) return; + if (trimmed === lastSentText) { + return; + } lastSentText = trimmed; lastSentAt = Date.now(); try { @@ -72,7 +78,9 @@ export function createTelegramDraftStream(params: { const text = pendingText; pendingText = ""; if (!text.trim()) { - if (pendingText) schedule(); + if (pendingText) { + schedule(); + } return; } inFlight = true; @@ -81,11 +89,15 @@ export function createTelegramDraftStream(params: { } finally { inFlight = false; } - if (pendingText) schedule(); + if (pendingText) { + schedule(); + } }; const schedule = () => { - if (timer) return; + if (timer) { + return; + } const delay = Math.max(0, throttleMs - (Date.now() - lastSentAt)); timer = setTimeout(() => { void flush(); @@ -93,7 +105,9 @@ export function createTelegramDraftStream(params: { }; const update = (text: string) => { - if (stopped) return; + if (stopped) { + return; + } pendingText = text; if (inFlight) { schedule(); diff --git a/src/telegram/fetch.ts b/src/telegram/fetch.ts index ebed468c9a..9ae004e806 100644 --- a/src/telegram/fetch.ts +++ b/src/telegram/fetch.ts @@ -11,7 +11,9 @@ const log = createSubsystemLogger("telegram/network"); // See: https://github.com/nodejs/node/issues/54359 function applyTelegramNetworkWorkarounds(network?: TelegramNetworkConfig): void { const decision = resolveTelegramAutoSelectFamilyDecision({ network }); - if (decision.value === null || decision.value === appliedAutoSelectFamily) return; + if (decision.value === null || decision.value === appliedAutoSelectFamily) { + return; + } appliedAutoSelectFamily = decision.value; if (typeof net.setDefaultAutoSelectFamily === "function") { @@ -31,7 +33,9 @@ export function resolveTelegramFetch( options?: { network?: TelegramNetworkConfig }, ): typeof fetch | undefined { applyTelegramNetworkWorkarounds(options?.network); - if (proxyFetch) return resolveFetch(proxyFetch); + if (proxyFetch) { + return resolveFetch(proxyFetch); + } const fetchImpl = resolveFetch(); if (!fetchImpl) { throw new Error("fetch is not available; set channels.telegram.proxy in config"); diff --git a/src/telegram/format.ts b/src/telegram/format.ts index 472fc1f43f..26a003c2e3 100644 --- a/src/telegram/format.ts +++ b/src/telegram/format.ts @@ -22,8 +22,12 @@ function escapeHtmlAttr(text: string): string { function buildTelegramLink(link: MarkdownLinkSpan, _text: string) { const href = link.href.trim(); - if (!href) return null; - if (link.start === link.end) return null; + if (!href) { + return null; + } + if (link.start === link.end) { + return null; + } const safeHref = escapeHtmlAttr(href); return { start: link.start, @@ -65,7 +69,9 @@ export function renderTelegramHtmlText( options: { textMode?: "markdown" | "html"; tableMode?: MarkdownTableMode } = {}, ): string { const textMode = options.textMode ?? "markdown"; - if (textMode === "html") return text; + if (textMode === "html") { + return text; + } return markdownToTelegramHtml(text, { tableMode: options.tableMode }); } diff --git a/src/telegram/group-migration.ts b/src/telegram/group-migration.ts index ff99a2a06b..085aeabaf6 100644 --- a/src/telegram/group-migration.ts +++ b/src/telegram/group-migration.ts @@ -16,12 +16,18 @@ function resolveAccountGroups( cfg: OpenClawConfig, accountId?: string | null, ): { groups?: TelegramGroups } { - if (!accountId) return {}; + if (!accountId) { + return {}; + } const normalized = normalizeAccountId(accountId); const accounts = cfg.channels?.telegram?.accounts; - if (!accounts || typeof accounts !== "object") return {}; + if (!accounts || typeof accounts !== "object") { + return {}; + } const exact = accounts[normalized]; - if (exact?.groups) return { groups: exact.groups }; + if (exact?.groups) { + return { groups: exact.groups }; + } const matchKey = Object.keys(accounts).find( (key) => key.toLowerCase() === normalized.toLowerCase(), ); @@ -33,10 +39,18 @@ export function migrateTelegramGroupsInPlace( oldChatId: string, newChatId: string, ): { migrated: boolean; skippedExisting: boolean } { - if (!groups) return { migrated: false, skippedExisting: false }; - if (oldChatId === newChatId) return { migrated: false, skippedExisting: false }; - if (!Object.hasOwn(groups, oldChatId)) return { migrated: false, skippedExisting: false }; - if (Object.hasOwn(groups, newChatId)) return { migrated: false, skippedExisting: true }; + if (!groups) { + return { migrated: false, skippedExisting: false }; + } + if (oldChatId === newChatId) { + return { migrated: false, skippedExisting: false }; + } + if (!Object.hasOwn(groups, oldChatId)) { + return { migrated: false, skippedExisting: false }; + } + if (Object.hasOwn(groups, newChatId)) { + return { migrated: false, skippedExisting: true }; + } groups[newChatId] = groups[oldChatId]; delete groups[oldChatId]; return { migrated: true, skippedExisting: false }; @@ -59,7 +73,9 @@ export function migrateTelegramGroupConfig(params: { migrated = true; scopes.push("account"); } - if (result.skippedExisting) skippedExisting = true; + if (result.skippedExisting) { + skippedExisting = true; + } } const globalGroups = params.cfg.channels?.telegram?.groups; @@ -69,7 +85,9 @@ export function migrateTelegramGroupConfig(params: { migrated = true; scopes.push("global"); } - if (result.skippedExisting) skippedExisting = true; + if (result.skippedExisting) { + skippedExisting = true; + } } return { migrated, skippedExisting, scopes }; diff --git a/src/telegram/inline-buttons.ts b/src/telegram/inline-buttons.ts index ada8e09fef..1cf770d0a8 100644 --- a/src/telegram/inline-buttons.ts +++ b/src/telegram/inline-buttons.ts @@ -6,7 +6,9 @@ import { parseTelegramTarget } from "./targets.js"; const DEFAULT_INLINE_BUTTONS_SCOPE: TelegramInlineButtonsScope = "allowlist"; function normalizeInlineButtonsScope(value: unknown): TelegramInlineButtonsScope | undefined { - if (typeof value !== "string") return undefined; + if (typeof value !== "string") { + return undefined; + } const trimmed = value.trim().toLowerCase(); if ( trimmed === "off" || @@ -23,7 +25,9 @@ function normalizeInlineButtonsScope(value: unknown): TelegramInlineButtonsScope function resolveInlineButtonsScopeFromCapabilities( capabilities: unknown, ): TelegramInlineButtonsScope { - if (!capabilities) return DEFAULT_INLINE_BUTTONS_SCOPE; + if (!capabilities) { + return DEFAULT_INLINE_BUTTONS_SCOPE; + } if (Array.isArray(capabilities)) { const enabled = capabilities.some( (entry) => String(entry).trim().toLowerCase() === "inlinebuttons", @@ -62,10 +66,14 @@ export function isTelegramInlineButtonsEnabled(params: { } export function resolveTelegramTargetChatType(target: string): "direct" | "group" | "unknown" { - if (!target.trim()) return "unknown"; + if (!target.trim()) { + return "unknown"; + } const parsed = parseTelegramTarget(target); const chatId = parsed.chatId.trim(); - if (!chatId) return "unknown"; + if (!chatId) { + return "unknown"; + } if (/^-?\d+$/.test(chatId)) { return chatId.startsWith("-") ? "group" : "direct"; } diff --git a/src/telegram/monitor.test.ts b/src/telegram/monitor.test.ts index 2fc46827b5..66c47f112c 100644 --- a/src/telegram/monitor.test.ts +++ b/src/telegram/monitor.test.ts @@ -54,8 +54,12 @@ vi.mock("./bot.js", () => ({ const chatId = ctx.message.chat.id; const isGroup = ctx.message.chat.type !== "private"; const text = ctx.message.text ?? ctx.message.caption ?? ""; - if (isGroup && !text.includes("@mybot")) return; - if (!text.trim()) return; + if (isGroup && !text.includes("@mybot")) { + return; + } + if (!text.trim()) { + return; + } await api.sendMessage(chatId, `echo:${text}`, { parse_mode: "HTML" }); }; return { diff --git a/src/telegram/monitor.ts b/src/telegram/monitor.ts index fa741e24d2..c39eb5e59e 100644 --- a/src/telegram/monitor.ts +++ b/src/telegram/monitor.ts @@ -57,7 +57,9 @@ const TELEGRAM_POLL_RESTART_POLICY = { }; const isGetUpdatesConflict = (err: unknown) => { - if (!err || typeof err !== "object") return false; + if (!err || typeof err !== "object") { + return false; + } const typed = err as { error_code?: number; errorCode?: number; @@ -66,7 +68,9 @@ const isGetUpdatesConflict = (err: unknown) => { message?: string; }; const errorCode = typed.error_code ?? typed.errorCode; - if (errorCode !== 409) return false; + if (errorCode !== 409) { + return false; + } const haystack = [typed.method, typed.description, typed.message] .filter((value): value is string => typeof value === "string") .join(" ") @@ -85,9 +89,13 @@ const NETWORK_ERROR_SNIPPETS = [ ]; const isNetworkRelatedError = (err: unknown) => { - if (!err) return false; + if (!err) { + return false; + } const message = formatErrorMessage(err).toLowerCase(); - if (!message) return false; + if (!message) { + return false; + } return NETWORK_ERROR_SNIPPETS.some((snippet) => message.includes(snippet)); }; @@ -111,7 +119,9 @@ export async function monitorTelegramProvider(opts: MonitorTelegramOpts = {}) { accountId: account.accountId, }); const persistUpdateId = async (updateId: number) => { - if (lastUpdateId !== null && updateId <= lastUpdateId) return; + if (lastUpdateId !== null && updateId <= lastUpdateId) { + return; + } lastUpdateId = updateId; try { await writeTelegramUpdateOffset({ @@ -188,7 +198,9 @@ export async function monitorTelegramProvider(opts: MonitorTelegramOpts = {}) { try { await sleepWithAbort(delayMs, opts.abortSignal); } catch (sleepErr) { - if (opts.abortSignal?.aborted) return; + if (opts.abortSignal?.aborted) { + return; + } throw sleepErr; } } finally { diff --git a/src/telegram/network-errors.ts b/src/telegram/network-errors.ts index bb34324325..0b658f0c95 100644 --- a/src/telegram/network-errors.ts +++ b/src/telegram/network-errors.ts @@ -43,17 +43,27 @@ function normalizeCode(code?: string): string { } function getErrorName(err: unknown): string { - if (!err || typeof err !== "object") return ""; + if (!err || typeof err !== "object") { + return ""; + } return "name" in err ? String(err.name) : ""; } function getErrorCode(err: unknown): string | undefined { const direct = extractErrorCode(err); - if (direct) return direct; - if (!err || typeof err !== "object") return undefined; + if (direct) { + return direct; + } + if (!err || typeof err !== "object") { + return undefined; + } const errno = (err as { errno?: unknown }).errno; - if (typeof errno === "string") return errno; - if (typeof errno === "number") return String(errno); + if (typeof errno === "string") { + return errno; + } + if (typeof errno === "number") { + return String(errno); + } return undefined; } @@ -64,19 +74,27 @@ function collectErrorCandidates(err: unknown): unknown[] { while (queue.length > 0) { const current = queue.shift(); - if (current == null || seen.has(current)) continue; + if (current == null || seen.has(current)) { + continue; + } seen.add(current); candidates.push(current); if (typeof current === "object") { const cause = (current as { cause?: unknown }).cause; - if (cause && !seen.has(cause)) queue.push(cause); + if (cause && !seen.has(cause)) { + queue.push(cause); + } const reason = (current as { reason?: unknown }).reason; - if (reason && !seen.has(reason)) queue.push(reason); + if (reason && !seen.has(reason)) { + queue.push(reason); + } const errors = (current as { errors?: unknown }).errors; if (Array.isArray(errors)) { for (const nested of errors) { - if (nested && !seen.has(nested)) queue.push(nested); + if (nested && !seen.has(nested)) { + queue.push(nested); + } } } } @@ -91,7 +109,9 @@ export function isRecoverableTelegramNetworkError( err: unknown, options: { context?: TelegramNetworkErrorContext; allowMessageMatch?: boolean } = {}, ): boolean { - if (!err) return false; + if (!err) { + return false; + } const allowMessageMatch = typeof options.allowMessageMatch === "boolean" ? options.allowMessageMatch @@ -99,10 +119,14 @@ export function isRecoverableTelegramNetworkError( for (const candidate of collectErrorCandidates(err)) { const code = normalizeCode(getErrorCode(candidate)); - if (code && RECOVERABLE_ERROR_CODES.has(code)) return true; + if (code && RECOVERABLE_ERROR_CODES.has(code)) { + return true; + } const name = getErrorName(candidate); - if (name && RECOVERABLE_ERROR_NAMES.has(name)) return true; + if (name && RECOVERABLE_ERROR_NAMES.has(name)) { + return true; + } if (allowMessageMatch) { const message = formatErrorMessage(candidate).toLowerCase(); diff --git a/src/telegram/pairing-store.test.ts b/src/telegram/pairing-store.test.ts index 34e9fb9312..74db8ee41a 100644 --- a/src/telegram/pairing-store.test.ts +++ b/src/telegram/pairing-store.test.ts @@ -18,8 +18,11 @@ async function withTempStateDir(fn: (stateDir: string) => Promise) { try { return await fn(dir); } finally { - if (previous === undefined) delete process.env.OPENCLAW_STATE_DIR; - else process.env.OPENCLAW_STATE_DIR = previous; + if (previous === undefined) { + delete process.env.OPENCLAW_STATE_DIR; + } else { + process.env.OPENCLAW_STATE_DIR = previous; + } await fs.rm(dir, { recursive: true, force: true }); } } diff --git a/src/telegram/pairing-store.ts b/src/telegram/pairing-store.ts index 4f5eb14c63..74223fb578 100644 --- a/src/telegram/pairing-store.ts +++ b/src/telegram/pairing-store.ts @@ -79,7 +79,9 @@ export async function approveTelegramPairingCode(params: { code: params.code, env: params.env, }); - if (!res) return null; + if (!res) { + return null; + } const entry = res.entry ? { chatId: res.entry.id, diff --git a/src/telegram/send.ts b/src/telegram/send.ts index e3f3ac30ef..ec279e6e07 100644 --- a/src/telegram/send.ts +++ b/src/telegram/send.ts @@ -77,7 +77,9 @@ function createTelegramHttpLogger(cfg: ReturnType) { return () => {}; } return (label: string, err: unknown) => { - if (!(err instanceof HttpError)) return; + if (!(err instanceof HttpError)) { + return; + } const detail = redactSensitiveText(formatUncaughtError(err.error ?? err)); diagLogger.warn(`telegram http error (${label}): ${detail}`); }; @@ -105,7 +107,9 @@ function resolveTelegramClientOptions( } function resolveToken(explicit: string | undefined, params: { accountId: string; token: string }) { - if (explicit?.trim()) return explicit.trim(); + if (explicit?.trim()) { + return explicit.trim(); + } if (!params.token) { throw new Error( `Telegram bot token missing for account "${params.accountId}" (set channels.telegram.accounts.${params.accountId}.botToken/tokenFile or TELEGRAM_BOT_TOKEN for default).`, @@ -116,7 +120,9 @@ function resolveToken(explicit: string | undefined, params: { accountId: string; function normalizeChatId(to: string): string { const trimmed = to.trim(); - if (!trimmed) throw new Error("Recipient is required for Telegram sends"); + if (!trimmed) { + throw new Error("Recipient is required for Telegram sends"); + } // Common internal prefixes that sometimes leak into outbound sends. // - ctx.To uses `telegram:` @@ -128,14 +134,24 @@ function normalizeChatId(to: string): string { const m = /^https?:\/\/t\.me\/([A-Za-z0-9_]+)$/i.exec(normalized) ?? /^t\.me\/([A-Za-z0-9_]+)$/i.exec(normalized); - if (m?.[1]) normalized = `@${m[1]}`; + if (m?.[1]) { + normalized = `@${m[1]}`; + } - if (!normalized) throw new Error("Recipient is required for Telegram sends"); - if (normalized.startsWith("@")) return normalized; - if (/^-?\d+$/.test(normalized)) return normalized; + if (!normalized) { + throw new Error("Recipient is required for Telegram sends"); + } + if (normalized.startsWith("@")) { + return normalized; + } + if (/^-?\d+$/.test(normalized)) { + return normalized; + } // If the user passed a username without `@`, assume they meant a public chat/channel. - if (/^[A-Za-z0-9_]{5,}$/i.test(normalized)) return `@${normalized}`; + if (/^[A-Za-z0-9_]{5,}$/i.test(normalized)) { + return `@${normalized}`; + } return normalized; } @@ -150,7 +166,9 @@ function normalizeMessageId(raw: string | number): number { throw new Error("Message id is required for Telegram actions"); } const parsed = Number.parseInt(value, 10); - if (Number.isFinite(parsed)) return parsed; + if (Number.isFinite(parsed)) { + return parsed; + } } throw new Error("Message id is required for Telegram actions"); } @@ -158,7 +176,9 @@ function normalizeMessageId(raw: string | number): number { export function buildInlineKeyboard( buttons?: TelegramSendOpts["buttons"], ): InlineKeyboardMarkup | undefined { - if (!buttons?.length) return undefined; + if (!buttons?.length) { + return undefined; + } const rows = buttons .map((row) => row @@ -171,7 +191,9 @@ export function buildInlineKeyboard( ), ) .filter((row) => row.length > 0); - if (rows.length === 0) return undefined; + if (rows.length === 0) { + return undefined; + } return { inline_keyboard: rows }; } @@ -229,7 +251,9 @@ export async function sendMessageTelegram( throw err; }); const wrapChatNotFound = (err: unknown) => { - if (!/400: Bad Request: chat not found/i.test(formatErrorMessage(err))) return err; + if (!/400: Bad Request: chat not found/i.test(formatErrorMessage(err))) { + return err; + } return new Error( [ `Telegram send failed: chat not found (chat_id=${chatId}).`, @@ -690,7 +714,9 @@ export async function sendStickerTelegram( }); const wrapChatNotFound = (err: unknown) => { - if (!/400: Bad Request: chat not found/i.test(formatErrorMessage(err))) return err; + if (!/400: Bad Request: chat not found/i.test(formatErrorMessage(err))) { + return err; + } return new Error( [ `Telegram send failed: chat not found (chat_id=${chatId}).`, diff --git a/src/telegram/sent-message-cache.ts b/src/telegram/sent-message-cache.ts index 05c12ddf75..039d38a4c5 100644 --- a/src/telegram/sent-message-cache.ts +++ b/src/telegram/sent-message-cache.ts @@ -50,7 +50,9 @@ export function recordSentMessage(chatId: number | string, messageId: number): v export function wasSentByBot(chatId: number | string, messageId: number): boolean { const key = getChatKey(chatId); const entry = sentMessages.get(key); - if (!entry) return false; + if (!entry) { + return false; + } // Clean up expired entries on read cleanupExpired(entry); return entry.messageIds.has(messageId); diff --git a/src/telegram/sticker-cache.ts b/src/telegram/sticker-cache.ts index e97765f27c..d03276a233 100644 --- a/src/telegram/sticker-cache.ts +++ b/src/telegram/sticker-cache.ts @@ -187,7 +187,9 @@ export async function describeStickerImage(params: DescribeStickerParams): Promi (entry) => entry.provider.toLowerCase() === provider.toLowerCase() && modelSupportsVision(entry), ); - if (entries.length === 0) return undefined; + if (entries.length === 0) { + return undefined; + } const defaultId = provider === "openai" ? "gpt-5-mini" @@ -211,7 +213,9 @@ export async function describeStickerImage(params: DescribeStickerParams): Promi if (!resolved) { for (const provider of VISION_PROVIDERS) { - if (!(await hasProviderKey(provider))) continue; + if (!(await hasProviderKey(provider))) { + continue; + } const entry = selectCatalogModel(provider); if (entry) { resolved = { provider, model: entry.id }; diff --git a/src/telegram/targets.ts b/src/telegram/targets.ts index 2bf139a144..cb26c0d06b 100644 --- a/src/telegram/targets.ts +++ b/src/telegram/targets.ts @@ -18,7 +18,9 @@ export function stripTelegramInternalPrefixes(to: string): string { } return trimmed; })(); - if (next === trimmed) return trimmed; + if (next === trimmed) { + return trimmed; + } trimmed = next; } } diff --git a/src/telegram/token.ts b/src/telegram/token.ts index 64c0868209..710a764a06 100644 --- a/src/telegram/token.ts +++ b/src/telegram/token.ts @@ -28,10 +28,14 @@ export function resolveTelegramToken( // be normalized, so resolve per-account config by matching normalized IDs. const resolveAccountCfg = (id: string): TelegramAccountConfig | undefined => { const accounts = telegramCfg?.accounts; - if (!accounts || typeof accounts !== "object" || Array.isArray(accounts)) return undefined; + if (!accounts || typeof accounts !== "object" || Array.isArray(accounts)) { + return undefined; + } // Direct hit (already normalized key) const direct = accounts[id]; - if (direct) return direct; + if (direct) { + return direct; + } // Fallback: match by normalized key const matchKey = Object.keys(accounts).find((key) => normalizeAccountId(key) === id); return matchKey ? accounts[matchKey] : undefined; diff --git a/src/telegram/update-offset-store.test.ts b/src/telegram/update-offset-store.test.ts index 335adc2c71..f1dd6e8886 100644 --- a/src/telegram/update-offset-store.test.ts +++ b/src/telegram/update-offset-store.test.ts @@ -13,8 +13,11 @@ async function withTempStateDir(fn: (dir: string) => Promise) { try { return await fn(dir); } finally { - if (previous === undefined) delete process.env.OPENCLAW_STATE_DIR; - else process.env.OPENCLAW_STATE_DIR = previous; + if (previous === undefined) { + delete process.env.OPENCLAW_STATE_DIR; + } else { + process.env.OPENCLAW_STATE_DIR = previous; + } await fs.rm(dir, { recursive: true, force: true }); } } diff --git a/src/telegram/update-offset-store.ts b/src/telegram/update-offset-store.ts index 303db8c9c5..3ea2e5ef98 100644 --- a/src/telegram/update-offset-store.ts +++ b/src/telegram/update-offset-store.ts @@ -14,7 +14,9 @@ type TelegramUpdateOffsetState = { function normalizeAccountId(accountId?: string) { const trimmed = accountId?.trim(); - if (!trimmed) return "default"; + if (!trimmed) { + return "default"; + } return trimmed.replace(/[^a-z0-9._-]+/gi, "_"); } @@ -30,7 +32,9 @@ function resolveTelegramUpdateOffsetPath( function safeParseState(raw: string): TelegramUpdateOffsetState | null { try { const parsed = JSON.parse(raw) as TelegramUpdateOffsetState; - if (parsed?.version !== STORE_VERSION) return null; + if (parsed?.version !== STORE_VERSION) { + return null; + } if (parsed.lastUpdateId !== null && typeof parsed.lastUpdateId !== "number") { return null; } @@ -51,7 +55,9 @@ export async function readTelegramUpdateOffset(params: { return parsed?.lastUpdateId ?? null; } catch (err) { const code = (err as { code?: string }).code; - if (code === "ENOENT") return null; + if (code === "ENOENT") { + return null; + } return null; } } diff --git a/src/telegram/voice.ts b/src/telegram/voice.ts index bf296c8f18..39da4e5004 100644 --- a/src/telegram/voice.ts +++ b/src/telegram/voice.ts @@ -12,8 +12,12 @@ export function resolveTelegramVoiceDecision(opts: { contentType?: string | null; fileName?: string | null; }): { useVoice: boolean; reason?: string } { - if (!opts.wantsVoice) return { useVoice: false }; - if (isTelegramVoiceCompatible(opts)) return { useVoice: true }; + if (!opts.wantsVoice) { + return { useVoice: false }; + } + if (isTelegramVoiceCompatible(opts)) { + return { useVoice: true }; + } const contentType = opts.contentType ?? "unknown"; const fileName = opts.fileName ?? "unknown"; return { diff --git a/src/telegram/webhook.test.ts b/src/telegram/webhook.test.ts index 04bccfe079..64b146b487 100644 --- a/src/telegram/webhook.test.ts +++ b/src/telegram/webhook.test.ts @@ -44,7 +44,9 @@ describe("startTelegramWebhook", () => { }), ); const address = server.address(); - if (!address || typeof address === "string") throw new Error("no address"); + if (!address || typeof address === "string") { + throw new Error("no address"); + } const url = `http://127.0.0.1:${address.port}`; const health = await fetch(`${url}/healthz`); @@ -74,7 +76,9 @@ describe("startTelegramWebhook", () => { }), ); const addr = server.address(); - if (!addr || typeof addr === "string") throw new Error("no addr"); + if (!addr || typeof addr === "string") { + throw new Error("no addr"); + } await fetch(`http://127.0.0.1:${addr.port}/hook`, { method: "POST" }); expect(handlerSpy).toHaveBeenCalled(); abort.abort(); diff --git a/src/telegram/webhook.ts b/src/telegram/webhook.ts index 6ba8d1c057..582741f866 100644 --- a/src/telegram/webhook.ts +++ b/src/telegram/webhook.ts @@ -89,7 +89,9 @@ export async function startTelegramWebhook(opts: { }); } runtime.log?.(`webhook handler failed: ${errMsg}`); - if (!res.headersSent) res.writeHead(500); + if (!res.headersSent) { + res.writeHead(500); + } res.end(); }); } diff --git a/src/terminal/note.ts b/src/terminal/note.ts index 7a35cf069b..48bca06fec 100644 --- a/src/terminal/note.ts +++ b/src/terminal/note.ts @@ -3,7 +3,9 @@ import { visibleWidth } from "./ansi.js"; import { stylePromptTitle } from "./prompt-style.js"; function splitLongWord(word: string, maxLen: number): string[] { - if (maxLen <= 0) return [word]; + if (maxLen <= 0) { + return [word]; + } const chars = Array.from(word); const parts: string[] = []; for (let i = 0; i < chars.length; i += maxLen) { @@ -13,7 +15,9 @@ function splitLongWord(word: string, maxLen: number): string[] { } function wrapLine(line: string, maxWidth: number): string[] { - if (line.trim().length === 0) return [line]; + if (line.trim().length === 0) { + return [line]; + } const match = line.match(/^(\s*)([-*\u2022]\s+)?(.*)$/); const indent = match?.[1] ?? ""; const bullet = match?.[2] ?? ""; @@ -37,7 +41,9 @@ function wrapLine(line: string, maxWidth: number): string[] { lines.push(prefix + first); prefix = nextPrefix; available = nextWidth; - for (const part of parts) lines.push(prefix + part); + for (const part of parts) { + lines.push(prefix + part); + } continue; } current = word; @@ -58,7 +64,9 @@ function wrapLine(line: string, maxWidth: number): string[] { const parts = splitLongWord(word, available); const first = parts.shift() ?? ""; lines.push(prefix + first); - for (const part of parts) lines.push(prefix + part); + for (const part of parts) { + lines.push(prefix + part); + } current = ""; continue; } diff --git a/src/terminal/progress-line.ts b/src/terminal/progress-line.ts index 1ee94baab9..818c637147 100644 --- a/src/terminal/progress-line.ts +++ b/src/terminal/progress-line.ts @@ -1,17 +1,25 @@ let activeStream: NodeJS.WriteStream | null = null; export function registerActiveProgressLine(stream: NodeJS.WriteStream): void { - if (!stream.isTTY) return; + if (!stream.isTTY) { + return; + } activeStream = stream; } export function clearActiveProgressLine(): void { - if (!activeStream?.isTTY) return; + if (!activeStream?.isTTY) { + return; + } activeStream.write("\r\x1b[2K"); } export function unregisterActiveProgressLine(stream?: NodeJS.WriteStream): void { - if (!activeStream) return; - if (stream && activeStream !== stream) return; + if (!activeStream) { + return; + } + if (stream && activeStream !== stream) { + return; + } activeStream = null; } diff --git a/src/terminal/stream-writer.ts b/src/terminal/stream-writer.ts index cd471ea5f5..2c7ab21786 100644 --- a/src/terminal/stream-writer.ts +++ b/src/terminal/stream-writer.ts @@ -20,7 +20,9 @@ export function createSafeStreamWriter(options: SafeStreamWriterOptions = {}): S let notified = false; const noteBrokenPipe = (err: NodeJS.ErrnoException, stream: NodeJS.WriteStream) => { - if (notified) return; + if (notified) { + return; + } notified = true; options.onBrokenPipe?.(err, stream); }; @@ -35,7 +37,9 @@ export function createSafeStreamWriter(options: SafeStreamWriterOptions = {}): S }; const write = (stream: NodeJS.WriteStream, text: string): boolean => { - if (closed) return false; + if (closed) { + return false; + } try { options.beforeWrite?.(); } catch (err) { diff --git a/src/terminal/table.test.ts b/src/terminal/table.test.ts index 1e7b24b761..39e9a31056 100644 --- a/src/terminal/table.test.ts +++ b/src/terminal/table.test.ts @@ -50,14 +50,18 @@ describe("renderTable", () => { const ESC = "\u001b"; for (let i = 0; i < out.length; i += 1) { - if (out[i] !== ESC) continue; + if (out[i] !== ESC) { + continue; + } // SGR: ESC [ ... m if (out[i + 1] === "[") { let j = i + 2; while (j < out.length) { const ch = out[j]; - if (ch === "m") break; + if (ch === "m") { + break; + } if (ch && ch >= "0" && ch <= "9") { j += 1; continue; diff --git a/src/terminal/table.ts b/src/terminal/table.ts index d4c48351ac..f0279c3bd6 100644 --- a/src/terminal/table.ts +++ b/src/terminal/table.ts @@ -21,15 +21,21 @@ export type RenderTableOptions = { }; function repeat(ch: string, n: number): string { - if (n <= 0) return ""; + if (n <= 0) { + return ""; + } return ch.repeat(n); } function padCell(text: string, width: number, align: Align): string { const w = visibleWidth(text); - if (w >= width) return text; + if (w >= width) { + return text; + } const pad = width - w; - if (align === "right") return `${repeat(" ", pad)}${text}`; + if (align === "right") { + return `${repeat(" ", pad)}${text}`; + } if (align === "center") { const left = Math.floor(pad / 2); const right = pad - left; @@ -39,7 +45,9 @@ function padCell(text: string, width: number, align: Align): string { } function wrapLine(text: string, width: number): string[] { - if (width <= 0) return [text]; + if (width <= 0) { + return [text]; + } // ANSI-aware wrapping: never split inside ANSI SGR/OSC-8 sequences. // We don't attempt to re-open styling per line; terminals keep SGR state @@ -55,7 +63,9 @@ function wrapLine(text: string, width: number): string[] { let j = i + 2; while (j < text.length) { const ch = text[j]; - if (ch === "m") break; + if (ch === "m") { + break; + } if (ch && ch >= "0" && ch <= "9") { j += 1; continue; @@ -85,14 +95,18 @@ function wrapLine(text: string, width: number): string[] { } const cp = text.codePointAt(i); - if (!cp) break; + if (!cp) { + break; + } const ch = String.fromCodePoint(cp); tokens.push({ kind: "char", value: ch }); i += ch.length; } const firstCharIndex = tokens.findIndex((t) => t.kind === "char"); - if (firstCharIndex < 0) return [text]; + if (firstCharIndex < 0) { + return [text]; + } let lastCharIndex = -1; for (let i = tokens.length - 1; i >= 0; i -= 1) { if (tokens[i]?.kind === "char") { @@ -129,12 +143,16 @@ function wrapLine(text: string, width: number): string[] { const pushLine = (value: string) => { const cleaned = value.replace(/\s+$/, ""); - if (cleaned.trim().length === 0) return; + if (cleaned.trim().length === 0) { + return; + } lines.push(cleaned); }; const flushAt = (breakAt: number | null) => { - if (buf.length === 0) return; + if (buf.length === 0) { + return; + } if (breakAt == null || breakAt <= 0) { pushLine(bufToString()); buf.length = 0; @@ -166,11 +184,15 @@ function wrapLine(text: string, width: number): string[] { const ch = token.value; if (skipNextLf) { skipNextLf = false; - if (ch === "\n") continue; + if (ch === "\n") { + continue; + } } if (ch === "\n" || ch === "\r") { flushAt(buf.length); - if (ch === "\r") skipNextLf = true; + if (ch === "\r") { + skipNextLf = true; + } continue; } if (bufVisible + 1 > width && bufVisible > 0) { @@ -179,21 +201,33 @@ function wrapLine(text: string, width: number): string[] { buf.push(token); bufVisible += 1; - if (isBreakChar(ch)) lastBreakIndex = buf.length; + if (isBreakChar(ch)) { + lastBreakIndex = buf.length; + } } flushAt(buf.length); - if (!lines.length) return [""]; - if (!prefixAnsi && !suffixAnsi) return lines; + if (!lines.length) { + return [""]; + } + if (!prefixAnsi && !suffixAnsi) { + return lines; + } return lines.map((line) => { - if (!line) return line; + if (!line) { + return line; + } return `${prefixAnsi}${line}${suffixAnsi}`; }); } function normalizeWidth(n: number | undefined): number | undefined { - if (n == null) return undefined; - if (!Number.isFinite(n) || n <= 0) return undefined; + if (n == null) { + return undefined; + } + if (!Number.isFinite(n) || n <= 0) { + return undefined; + } return Math.floor(n); } @@ -259,13 +293,19 @@ export function renderTable(opts: RenderTableOptions): string { while (over > 0) { let progressed = false; for (const i of order) { - if ((widths[i] ?? 0) <= (minWidths[i] ?? 0)) continue; + if ((widths[i] ?? 0) <= (minWidths[i] ?? 0)) { + continue; + } widths[i] = (widths[i] ?? 0) - 1; over -= 1; progressed = true; - if (over <= 0) break; + if (over <= 0) { + break; + } + } + if (!progressed) { + break; } - if (!progressed) break; } }; @@ -298,13 +338,19 @@ export function renderTable(opts: RenderTableOptions): string { while (extra > 0) { let progressed = false; for (const i of flexCols) { - if ((widths[i] ?? 0) >= (caps[i] ?? Number.POSITIVE_INFINITY)) continue; + if ((widths[i] ?? 0) >= (caps[i] ?? Number.POSITIVE_INFINITY)) { + continue; + } widths[i] = (widths[i] ?? 0) + 1; extra -= 1; progressed = true; - if (extra <= 0) break; + if (extra <= 0) { + break; + } + } + if (!progressed) { + break; } - if (!progressed) break; } } } diff --git a/src/test-utils/channel-plugins.ts b/src/test-utils/channel-plugins.ts index 3e6bf21127..01370c9a40 100644 --- a/src/test-utils/channel-plugins.ts +++ b/src/test-utils/channel-plugins.ts @@ -45,7 +45,9 @@ export const createIMessageTestPlugin = (params?: { collectStatusIssues: (accounts) => accounts.flatMap((account) => { const lastError = typeof account.lastError === "string" ? account.lastError.trim() : ""; - if (!lastError) return []; + if (!lastError) { + return []; + } return [ { channel: "imessage", @@ -61,11 +63,15 @@ export const createIMessageTestPlugin = (params?: { targetResolver: { looksLikeId: (raw) => { const trimmed = raw.trim(); - if (!trimmed) return false; + if (!trimmed) { + return false; + } if (/^(imessage:|sms:|auto:|chat_id:|chat_guid:|chat_identifier:)/i.test(trimmed)) { return true; } - if (trimmed.includes("@")) return true; + if (trimmed.includes("@")) { + return true; + } return /^\+?\d{3,}$/.test(trimmed); }, hint: "", diff --git a/src/test-utils/ports.ts b/src/test-utils/ports.ts index 188b25271b..214f9ba8f4 100644 --- a/src/test-utils/ports.ts +++ b/src/test-utils/ports.ts @@ -2,7 +2,9 @@ import { createServer } from "node:net"; import { isMainThread, threadId } from "node:worker_threads"; async function isPortFree(port: number): Promise { - if (!Number.isFinite(port) || port <= 0 || port > 65535) return false; + if (!Number.isFinite(port) || port <= 0 || port > 65535) { + return false; + } return await new Promise((resolve) => { const server = createServer(); server.once("error", () => resolve(false)); @@ -66,7 +68,9 @@ export async function getDeterministicFreePortBlock(params?: { const ok = (await Promise.all(offsets.map((offset) => isPortFree(start + offset)))).every( Boolean, ); - if (!ok) continue; + if (!ok) { + continue; + } nextTestPortOffset = (nextTestPortOffset + attempt + blockSize) % usable; return start; } @@ -79,7 +83,9 @@ export async function getDeterministicFreePortBlock(params?: { const ok = (await Promise.all(offsets.map((offset) => isPortFree(port + offset)))).every( Boolean, ); - if (ok) return port; + if (ok) { + return port; + } } throw new Error("failed to acquire a free port block"); diff --git a/src/tts/tts.ts b/src/tts/tts.ts index 6583c36fa2..008649f3b9 100644 --- a/src/tts/tts.ts +++ b/src/tts/tts.ts @@ -208,7 +208,9 @@ type TtsStatusEntry = { let lastTtsAttempt: TtsStatusEntry | undefined; export function normalizeTtsAutoMode(value: unknown): TtsAutoMode | undefined { - if (typeof value !== "string") return undefined; + if (typeof value !== "string") { + return undefined; + } const normalized = value.trim().toLowerCase(); if (TTS_AUTO_MODES.has(normalized as TtsAutoMode)) { return normalized as TtsAutoMode; @@ -303,15 +305,21 @@ export function resolveTtsConfig(cfg: OpenClawConfig): ResolvedTtsConfig { } export function resolveTtsPrefsPath(config: ResolvedTtsConfig): string { - if (config.prefsPath?.trim()) return resolveUserPath(config.prefsPath.trim()); + if (config.prefsPath?.trim()) { + return resolveUserPath(config.prefsPath.trim()); + } const envPath = process.env.OPENCLAW_TTS_PREFS?.trim(); - if (envPath) return resolveUserPath(envPath); + if (envPath) { + return resolveUserPath(envPath); + } return path.join(CONFIG_DIR, "settings", "tts.json"); } function resolveTtsAutoModeFromPrefs(prefs: TtsUserPrefs): TtsAutoMode | undefined { const auto = normalizeTtsAutoMode(prefs.tts?.auto); - if (auto) return auto; + if (auto) { + return auto; + } if (typeof prefs.tts?.enabled === "boolean") { return prefs.tts.enabled ? "always" : "off"; } @@ -324,9 +332,13 @@ export function resolveTtsAutoMode(params: { sessionAuto?: string; }): TtsAutoMode { const sessionAuto = normalizeTtsAutoMode(params.sessionAuto); - if (sessionAuto) return sessionAuto; + if (sessionAuto) { + return sessionAuto; + } const prefsAuto = resolveTtsAutoModeFromPrefs(readPrefs(params.prefsPath)); - if (prefsAuto) return prefsAuto; + if (prefsAuto) { + return prefsAuto; + } return params.config.auto; } @@ -334,7 +346,9 @@ export function buildTtsSystemPromptHint(cfg: OpenClawConfig): string | undefine const config = resolveTtsConfig(cfg); const prefsPath = resolveTtsPrefsPath(config); const autoMode = resolveTtsAutoMode({ config, prefsPath }); - if (autoMode === "off") return undefined; + if (autoMode === "off") { + return undefined; + } const maxLength = getTtsMaxLength(prefsPath); const summarize = isSummarizationEnabled(prefsPath) ? "on" : "off"; const autoHint = @@ -355,7 +369,9 @@ export function buildTtsSystemPromptHint(cfg: OpenClawConfig): string | undefine function readPrefs(prefsPath: string): TtsUserPrefs { try { - if (!existsSync(prefsPath)) return {}; + if (!existsSync(prefsPath)) { + return {}; + } return JSON.parse(readFileSync(prefsPath, "utf8")) as TtsUserPrefs; } catch { return {}; @@ -407,11 +423,19 @@ export function setTtsEnabled(prefsPath: string, enabled: boolean): void { export function getTtsProvider(config: ResolvedTtsConfig, prefsPath: string): TtsProvider { const prefs = readPrefs(prefsPath); - if (prefs.tts?.provider) return prefs.tts.provider; - if (config.providerSource === "config") return config.provider; + if (prefs.tts?.provider) { + return prefs.tts.provider; + } + if (config.providerSource === "config") { + return config.provider; + } - if (resolveTtsApiKey(config, "openai")) return "openai"; - if (resolveTtsApiKey(config, "elevenlabs")) return "elevenlabs"; + if (resolveTtsApiKey(config, "openai")) { + return "openai"; + } + if (resolveTtsApiKey(config, "elevenlabs")) { + return "elevenlabs"; + } return "edge"; } @@ -452,7 +476,9 @@ export function setLastTtsAttempt(entry: TtsStatusEntry | undefined): void { } function resolveOutputFormat(channelId?: string | null) { - if (channelId === "telegram") return TELEGRAM_OUTPUT; + if (channelId === "telegram") { + return TELEGRAM_OUTPUT; + } return DEFAULT_OUTPUT; } @@ -484,7 +510,9 @@ export function resolveTtsProviderOrder(primary: TtsProvider): TtsProvider[] { } export function isTtsProviderConfigured(config: ResolvedTtsConfig, provider: TtsProvider): boolean { - if (provider === "edge") return config.edge.enabled; + if (provider === "edge") { + return config.edge.enabled; + } return Boolean(resolveTtsApiKey(config, provider)); } @@ -494,7 +522,9 @@ function isValidVoiceId(voiceId: string): boolean { function normalizeElevenLabsBaseUrl(baseUrl: string): string { const trimmed = baseUrl.trim(); - if (!trimmed) return DEFAULT_ELEVENLABS_BASE_URL; + if (!trimmed) { + return DEFAULT_ELEVENLABS_BASE_URL; + } return trimmed.replace(/\/+$/, ""); } @@ -513,7 +543,9 @@ function assertElevenLabsVoiceSettings(settings: ResolvedTtsConfig["elevenlabs"] function normalizeLanguageCode(code?: string): string | undefined { const trimmed = code?.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } const normalized = trimmed.toLowerCase(); if (!/^[a-z]{2}$/.test(normalized)) { throw new Error("languageCode must be a 2-letter ISO 639-1 code (e.g. en, de, fr)"); @@ -523,14 +555,20 @@ function normalizeLanguageCode(code?: string): string | undefined { function normalizeApplyTextNormalization(mode?: string): "auto" | "on" | "off" | undefined { const trimmed = mode?.trim(); - if (!trimmed) return undefined; + if (!trimmed) { + return undefined; + } const normalized = trimmed.toLowerCase(); - if (normalized === "auto" || normalized === "on" || normalized === "off") return normalized; + if (normalized === "auto" || normalized === "on" || normalized === "off") { + return normalized; + } throw new Error("applyTextNormalization must be one of: auto, on, off"); } function normalizeSeed(seed?: number): number | undefined { - if (seed == null) return undefined; + if (seed == null) { + return undefined; + } const next = Math.floor(seed); if (!Number.isFinite(next) || next < 0 || next > 4_294_967_295) { throw new Error("seed must be between 0 and 4294967295"); @@ -540,8 +578,12 @@ function normalizeSeed(seed?: number): number | undefined { function parseBooleanValue(value: string): boolean | undefined { const normalized = value.trim().toLowerCase(); - if (["true", "1", "yes", "on"].includes(normalized)) return true; - if (["false", "0", "no", "off"].includes(normalized)) return false; + if (["true", "1", "yes", "on"].includes(normalized)) { + return true; + } + if (["false", "0", "no", "off"].includes(normalized)) { + return false; + } return undefined; } @@ -578,15 +620,21 @@ function parseTtsDirectives( const tokens = body.split(/\s+/).filter(Boolean); for (const token of tokens) { const eqIndex = token.indexOf("="); - if (eqIndex === -1) continue; + if (eqIndex === -1) { + continue; + } const rawKey = token.slice(0, eqIndex).trim(); const rawValue = token.slice(eqIndex + 1).trim(); - if (!rawKey || !rawValue) continue; + if (!rawKey || !rawValue) { + continue; + } const key = rawKey.toLowerCase(); try { switch (key) { case "provider": - if (!policy.allowProvider) break; + if (!policy.allowProvider) { + break; + } if (rawValue === "openai" || rawValue === "elevenlabs" || rawValue === "edge") { overrides.provider = rawValue; } else { @@ -596,7 +644,9 @@ function parseTtsDirectives( case "voice": case "openai_voice": case "openaivoice": - if (!policy.allowVoice) break; + if (!policy.allowVoice) { + break; + } if (isValidOpenAIVoice(rawValue)) { overrides.openai = { ...overrides.openai, voice: rawValue }; } else { @@ -607,7 +657,9 @@ function parseTtsDirectives( case "voice_id": case "elevenlabs_voice": case "elevenlabsvoice": - if (!policy.allowVoice) break; + if (!policy.allowVoice) { + break; + } if (isValidVoiceId(rawValue)) { overrides.elevenlabs = { ...overrides.elevenlabs, voiceId: rawValue }; } else { @@ -621,7 +673,9 @@ function parseTtsDirectives( case "elevenlabsmodel": case "openai_model": case "openaimodel": - if (!policy.allowModelId) break; + if (!policy.allowModelId) { + break; + } if (isValidOpenAIModel(rawValue)) { overrides.openai = { ...overrides.openai, model: rawValue }; } else { @@ -629,7 +683,9 @@ function parseTtsDirectives( } break; case "stability": - if (!policy.allowVoiceSettings) break; + if (!policy.allowVoiceSettings) { + break; + } { const value = parseNumberValue(rawValue); if (value == null) { @@ -646,7 +702,9 @@ function parseTtsDirectives( case "similarity": case "similarityboost": case "similarity_boost": - if (!policy.allowVoiceSettings) break; + if (!policy.allowVoiceSettings) { + break; + } { const value = parseNumberValue(rawValue); if (value == null) { @@ -661,7 +719,9 @@ function parseTtsDirectives( } break; case "style": - if (!policy.allowVoiceSettings) break; + if (!policy.allowVoiceSettings) { + break; + } { const value = parseNumberValue(rawValue); if (value == null) { @@ -676,7 +736,9 @@ function parseTtsDirectives( } break; case "speed": - if (!policy.allowVoiceSettings) break; + if (!policy.allowVoiceSettings) { + break; + } { const value = parseNumberValue(rawValue); if (value == null) { @@ -694,7 +756,9 @@ function parseTtsDirectives( case "speaker_boost": case "usespeakerboost": case "use_speaker_boost": - if (!policy.allowVoiceSettings) break; + if (!policy.allowVoiceSettings) { + break; + } { const value = parseBooleanValue(rawValue); if (value == null) { @@ -710,7 +774,9 @@ function parseTtsDirectives( case "normalize": case "applytextnormalization": case "apply_text_normalization": - if (!policy.allowNormalization) break; + if (!policy.allowNormalization) { + break; + } overrides.elevenlabs = { ...overrides.elevenlabs, applyTextNormalization: normalizeApplyTextNormalization(rawValue), @@ -719,14 +785,18 @@ function parseTtsDirectives( case "language": case "languagecode": case "language_code": - if (!policy.allowNormalization) break; + if (!policy.allowNormalization) { + break; + } overrides.elevenlabs = { ...overrides.elevenlabs, languageCode: normalizeLanguageCode(rawValue), }; break; case "seed": - if (!policy.allowSeed) break; + if (!policy.allowSeed) { + break; + } overrides.elevenlabs = { ...overrides.elevenlabs, seed: normalizeSeed(Number.parseInt(rawValue, 10)), @@ -786,13 +856,17 @@ type OpenAiTtsVoice = (typeof OPENAI_TTS_VOICES)[number]; function isValidOpenAIModel(model: string): boolean { // Allow any model when using custom endpoint (e.g., Kokoro, LocalAI) - if (isCustomOpenAIEndpoint()) return true; + if (isCustomOpenAIEndpoint()) { + return true; + } return OPENAI_TTS_MODELS.includes(model as (typeof OPENAI_TTS_MODELS)[number]); } function isValidOpenAIVoice(voice: string): voice is OpenAiTtsVoice { // Allow any voice when using custom endpoint (e.g., Kokoro Chinese voices) - if (isCustomOpenAIEndpoint()) return true; + if (isCustomOpenAIEndpoint()) { + return true; + } return OPENAI_TTS_VOICES.includes(voice as OpenAiTtsVoice); } @@ -814,7 +888,9 @@ function resolveSummaryModelRef( ): SummaryModelSelection { const defaultRef = resolveDefaultModelForAgent({ cfg }); const override = config.summaryModel?.trim(); - if (!override) return { ref: defaultRef, source: "default" }; + if (!override) { + return { ref: defaultRef, source: "default" }; + } const aliasIndex = buildModelAliasIndex({ cfg, defaultProvider: defaultRef.provider }); const resolved = resolveModelRefFromString({ @@ -822,7 +898,9 @@ function resolveSummaryModelRef( defaultProvider: defaultRef.provider, aliasIndex, }); - if (!resolved) return { ref: defaultRef, source: "default" }; + if (!resolved) { + return { ref: defaultRef, source: "default" }; + } return { ref: resolved.ref, source: "summaryModel" }; } @@ -1046,9 +1124,15 @@ async function openaiTTS(params: { function inferEdgeExtension(outputFormat: string): string { const normalized = outputFormat.toLowerCase(); - if (normalized.includes("webm")) return ".webm"; - if (normalized.includes("ogg")) return ".ogg"; - if (normalized.includes("opus")) return ".opus"; + if (normalized.includes("webm")) { + return ".webm"; + } + if (normalized.includes("ogg")) { + return ".ogg"; + } + if (normalized.includes("opus")) { + return ".opus"; + } if (normalized.includes("wav") || normalized.includes("riff") || normalized.includes("pcm")) { return ".wav"; } @@ -1356,7 +1440,9 @@ export async function maybeApplyTtsToPayload(params: { prefsPath, sessionAuto: params.ttsAuto, }); - if (autoMode === "off") return params.payload; + if (autoMode === "off") { + return params.payload; + } const text = params.payload.text ?? ""; const directives = parseTtsDirectives(text, config.modelOverrides); @@ -1377,16 +1463,30 @@ export async function maybeApplyTtsToPayload(params: { text: visibleText.length > 0 ? visibleText : undefined, }; - if (autoMode === "tagged" && !directives.hasDirective) return nextPayload; - if (autoMode === "inbound" && params.inboundAudio !== true) return nextPayload; + if (autoMode === "tagged" && !directives.hasDirective) { + return nextPayload; + } + if (autoMode === "inbound" && params.inboundAudio !== true) { + return nextPayload; + } const mode = config.mode ?? "final"; - if (mode === "final" && params.kind && params.kind !== "final") return nextPayload; + if (mode === "final" && params.kind && params.kind !== "final") { + return nextPayload; + } - if (!ttsText.trim()) return nextPayload; - if (params.payload.mediaUrl || (params.payload.mediaUrls?.length ?? 0) > 0) return nextPayload; - if (text.includes("MEDIA:")) return nextPayload; - if (ttsText.trim().length < 10) return nextPayload; + if (!ttsText.trim()) { + return nextPayload; + } + if (params.payload.mediaUrl || (params.payload.mediaUrls?.length ?? 0) > 0) { + return nextPayload; + } + if (text.includes("MEDIA:")) { + return nextPayload; + } + if (ttsText.trim().length < 10) { + return nextPayload; + } const maxLength = getTtsMaxLength(prefsPath); let textForAudio = ttsText.trim(); diff --git a/src/tui/commands.ts b/src/tui/commands.ts index f119a93ae7..4f15841c9d 100644 --- a/src/tui/commands.ts +++ b/src/tui/commands.ts @@ -26,7 +26,9 @@ const COMMAND_ALIASES: Record = { export function parseCommand(input: string): ParsedCommand { const trimmed = input.replace(/^\//, "").trim(); - if (!trimmed) return { name: "", args: "" }; + if (!trimmed) { + return { name: "", args: "" }; + } const [name, ...rest] = trimmed.split(/\s+/); const normalized = name.toLowerCase(); return { @@ -125,7 +127,9 @@ export function getSlashCommands(options: SlashCommandOptions = {}): SlashComman const aliases = command.textAliases.length > 0 ? command.textAliases : [`/${command.key}`]; for (const alias of aliases) { const name = alias.replace(/^\//, "").trim(); - if (!name || seen.has(name)) continue; + if (!name || seen.has(name)) { + continue; + } seen.add(name); commands.push({ name, description: command.description }); } diff --git a/src/tui/components/chat-log.ts b/src/tui/components/chat-log.ts index 25061245b2..e9c696397e 100644 --- a/src/tui/components/chat-log.ts +++ b/src/tui/components/chat-log.ts @@ -71,7 +71,9 @@ export class ChatLog extends Container { updateToolArgs(toolCallId: string, args: unknown) { const existing = this.toolById.get(toolCallId); - if (!existing) return; + if (!existing) { + return; + } existing.setArgs(args); } @@ -81,7 +83,9 @@ export class ChatLog extends Container { opts?: { isError?: boolean; partial?: boolean }, ) { const existing = this.toolById.get(toolCallId); - if (!existing) return; + if (!existing) { + return; + } if (opts?.partial) { existing.setPartialResult(result as Record); return; diff --git a/src/tui/components/fuzzy-filter.ts b/src/tui/components/fuzzy-filter.ts index fb6e2acf2b..7fea774223 100644 --- a/src/tui/components/fuzzy-filter.ts +++ b/src/tui/components/fuzzy-filter.ts @@ -19,11 +19,15 @@ export function isWordBoundary(text: string, index: number): boolean { * Returns null if no match. */ export function findWordBoundaryIndex(text: string, query: string): number | null { - if (!query) return null; + if (!query) { + return null; + } const textLower = text.toLowerCase(); const queryLower = query.toLowerCase(); const maxIndex = textLower.length - queryLower.length; - if (maxIndex < 0) return null; + if (maxIndex < 0) { + return null; + } for (let i = 0; i <= maxIndex; i++) { if (textLower.startsWith(queryLower, i) && isWordBoundary(textLower, i)) { return i; @@ -37,8 +41,12 @@ export function findWordBoundaryIndex(text: string, query: string): number | nul * Returns score (lower = better) or null if no match. */ export function fuzzyMatchLower(queryLower: string, textLower: string): number | null { - if (queryLower.length === 0) return 0; - if (queryLower.length > textLower.length) return null; + if (queryLower.length === 0) { + return 0; + } + if (queryLower.length > textLower.length) { + return null; + } let queryIndex = 0; let score = 0; @@ -53,9 +61,13 @@ export function fuzzyMatchLower(queryLower: string, textLower: string): number | score -= consecutiveMatches * 5; // Reward consecutive matches } else { consecutiveMatches = 0; - if (lastMatchIndex >= 0) score += (i - lastMatchIndex - 1) * 2; // Penalize gaps + if (lastMatchIndex >= 0) { + score += (i - lastMatchIndex - 1) * 2; + } // Penalize gaps } - if (isAtWordBoundary) score -= 10; // Reward word boundary matches + if (isAtWordBoundary) { + score -= 10; + } // Reward word boundary matches score += i * 0.1; // Slight penalty for later matches lastMatchIndex = i; queryIndex++; @@ -73,10 +85,14 @@ export function fuzzyFilterLower( queryLower: string, ): T[] { const trimmed = queryLower.trim(); - if (!trimmed) return items; + if (!trimmed) { + return items; + } const tokens = trimmed.split(/\s+/).filter((t) => t.length > 0); - if (tokens.length === 0) return items; + if (tokens.length === 0) { + return items; + } const results: { item: T; score: number }[] = []; for (const item of items) { @@ -92,7 +108,9 @@ export function fuzzyFilterLower( break; } } - if (allMatch) results.push({ item, score: totalScore }); + if (allMatch) { + results.push({ item, score: totalScore }); + } } results.sort((a, b) => a.score - b.score); return results.map((r) => r.item); @@ -106,9 +124,15 @@ export function prepareSearchItems< >(items: T[]): (T & { searchTextLower: string })[] { return items.map((item) => { const parts: string[] = []; - if (item.label) parts.push(item.label); - if (item.description) parts.push(item.description); - if (item.searchText) parts.push(item.searchText); + if (item.label) { + parts.push(item.label); + } + if (item.description) { + parts.push(item.description); + } + if (item.searchText) { + parts.push(item.searchText); + } return { ...item, searchTextLower: parts.join(" ").toLowerCase() }; }); } diff --git a/src/tui/components/searchable-select-list.ts b/src/tui/components/searchable-select-list.ts index 70ba348374..046cc138c1 100644 --- a/src/tui/components/searchable-select-list.ts +++ b/src/tui/components/searchable-select-list.ts @@ -119,8 +119,12 @@ export class SearchableSelectList implements Component { a: { item: SelectItem; tier: number; score: number }, b: { item: SelectItem; tier: number; score: number }, ) => { - if (a.tier !== b.tier) return a.tier - b.tier; - if (a.score !== b.score) return a.score - b.score; + if (a.tier !== b.tier) { + return a.tier - b.tier; + } + if (a.score !== b.score) { + return a.score - b.score; + } return this.getItemLabel(a.item).localeCompare(this.getItemLabel(b.item)); }; @@ -134,7 +138,9 @@ export class SearchableSelectList implements Component { .split(/\s+/) .map((token) => token.toLowerCase()) .filter((token) => token.length > 0); - if (tokens.length === 0) return text; + if (tokens.length === 0) { + return text; + } const uniqueTokens = Array.from(new Set(tokens)).toSorted((a, b) => b.length - a.length); let result = text; @@ -186,7 +192,9 @@ export class SearchableSelectList implements Component { // Render visible items for (let i = startIndex; i < endIndex; i++) { const item = this.filteredItems[i]; - if (!item) continue; + if (!item) { + continue; + } const isSelected = i === this.selectedIndex; lines.push(this.renderItemLine(item, isSelected, width, query)); } @@ -236,7 +244,9 @@ export class SearchableSelectList implements Component { } handleInput(keyData: string): void { - if (isKeyRelease(keyData)) return; + if (isKeyRelease(keyData)) { + return; + } const allowVimNav = !this.searchInput.getValue().trim(); diff --git a/src/tui/components/tool-execution.ts b/src/tui/components/tool-execution.ts index da3aabfb7a..e5d15fec20 100644 --- a/src/tui/components/tool-execution.ts +++ b/src/tui/components/tool-execution.ts @@ -20,8 +20,12 @@ const PREVIEW_LINES = 12; function formatArgs(toolName: string, args: unknown): string { const display = resolveToolDisplay({ name: toolName, args }); const detail = formatToolDetail(display); - if (detail) return detail; - if (!args || typeof args !== "object") return ""; + if (detail) { + return detail; + } + if (!args || typeof args !== "object") { + return ""; + } try { return JSON.stringify(args); } catch { @@ -30,7 +34,9 @@ function formatArgs(toolName: string, args: unknown): string { } function extractText(result?: ToolResult): string { - if (!result?.content) return ""; + if (!result?.content) { + return ""; + } const lines: string[] = []; for (const entry of result.content) { if (entry.type === "text" && entry.text) { diff --git a/src/tui/tui-command-handlers.ts b/src/tui/tui-command-handlers.ts index a141728092..e71f9a0092 100644 --- a/src/tui/tui-command-handlers.ts +++ b/src/tui/tui-command-handlers.ts @@ -228,7 +228,9 @@ export function createCommandHandlers(context: CommandHandlerContext) { const handleCommand = async (raw: string) => { const { name, args } = parseCommand(raw); - if (!name) return; + if (!name) { + return; + } switch (name) { case "help": chatLog.addSystem( @@ -247,7 +249,9 @@ export function createCommandHandlers(context: CommandHandlerContext) { } if (status && typeof status === "object") { const lines = formatStatusSummary(status as GatewayStatusSummary); - for (const line of lines) chatLog.addSystem(line); + for (const line of lines) { + chatLog.addSystem(line); + } break; } chatLog.addSystem("status: unknown response"); diff --git a/src/tui/tui-event-handlers.ts b/src/tui/tui-event-handlers.ts index bc857c704d..0ca38e2236 100644 --- a/src/tui/tui-event-handlers.ts +++ b/src/tui/tui-event-handlers.ts @@ -20,22 +20,32 @@ export function createEventHandlers(context: EventHandlerContext) { let lastSessionKey = state.currentSessionKey; const pruneRunMap = (runs: Map) => { - if (runs.size <= 200) return; + if (runs.size <= 200) { + return; + } const keepUntil = Date.now() - 10 * 60 * 1000; for (const [key, ts] of runs) { - if (runs.size <= 150) break; - if (ts < keepUntil) runs.delete(key); + if (runs.size <= 150) { + break; + } + if (ts < keepUntil) { + runs.delete(key); + } } if (runs.size > 200) { for (const key of runs.keys()) { runs.delete(key); - if (runs.size <= 150) break; + if (runs.size <= 150) { + break; + } } } }; const syncSessionKey = () => { - if (state.currentSessionKey === lastSessionKey) return; + if (state.currentSessionKey === lastSessionKey) { + return; + } lastSessionKey = state.currentSessionKey; finalizedRuns.clear(); sessionRuns.clear(); @@ -55,13 +65,21 @@ export function createEventHandlers(context: EventHandlerContext) { }; const handleChatEvent = (payload: unknown) => { - if (!payload || typeof payload !== "object") return; + if (!payload || typeof payload !== "object") { + return; + } const evt = payload as ChatEvent; syncSessionKey(); - if (evt.sessionKey !== state.currentSessionKey) return; + if (evt.sessionKey !== state.currentSessionKey) { + return; + } if (finalizedRuns.has(evt.runId)) { - if (evt.state === "delta") return; - if (evt.state === "final") return; + if (evt.state === "delta") { + return; + } + if (evt.state === "final") { + return; + } } noteSessionRun(evt.runId); if (!state.activeChatRunId) { @@ -69,14 +87,18 @@ export function createEventHandlers(context: EventHandlerContext) { } if (evt.state === "delta") { const displayText = streamAssembler.ingestDelta(evt.runId, evt.message, state.showThinking); - if (!displayText) return; + if (!displayText) { + return; + } chatLog.updateAssistant(displayText, evt.runId); setActivityStatus("streaming"); } if (evt.state === "final") { if (isCommandMessage(evt.message)) { const text = extractTextFromMessage(evt.message); - if (text) chatLog.addSystem(text); + if (text) { + chatLog.addSystem(text); + } streamAssembler.drop(evt.runId); noteFinalizedRun(evt.runId); state.activeChatRunId = null; @@ -120,19 +142,25 @@ export function createEventHandlers(context: EventHandlerContext) { }; const handleAgentEvent = (payload: unknown) => { - if (!payload || typeof payload !== "object") return; + if (!payload || typeof payload !== "object") { + return; + } const evt = payload as AgentEvent; syncSessionKey(); // Agent events (tool streaming, lifecycle) are emitted per-run. Filter against the // active chat run id, not the session id. const isActiveRun = evt.runId === state.activeChatRunId; - if (!isActiveRun && !sessionRuns.has(evt.runId)) return; + if (!isActiveRun && !sessionRuns.has(evt.runId)) { + return; + } if (evt.stream === "tool") { const data = evt.data ?? {}; const phase = asString(data.phase, ""); const toolCallId = asString(data.toolCallId, ""); const toolName = asString(data.name, "tool"); - if (!toolCallId) return; + if (!toolCallId) { + return; + } if (phase === "start") { chatLog.startTool(toolCallId, toolName, data.args); } else if (phase === "update") { @@ -148,11 +176,19 @@ export function createEventHandlers(context: EventHandlerContext) { return; } if (evt.stream === "lifecycle") { - if (!isActiveRun) return; + if (!isActiveRun) { + return; + } const phase = typeof evt.data?.phase === "string" ? evt.data.phase : ""; - if (phase === "start") setActivityStatus("running"); - if (phase === "end") setActivityStatus("idle"); - if (phase === "error") setActivityStatus("error"); + if (phase === "start") { + setActivityStatus("running"); + } + if (phase === "end") { + setActivityStatus("idle"); + } + if (phase === "error") { + setActivityStatus("error"); + } tui.requestRender(); } }; diff --git a/src/tui/tui-formatters.ts b/src/tui/tui-formatters.ts index f77eb9ff17..f50e6ed033 100644 --- a/src/tui/tui-formatters.ts +++ b/src/tui/tui-formatters.ts @@ -6,9 +6,13 @@ export function resolveFinalAssistantText(params: { streamedText?: string | null; }) { const finalText = params.finalText ?? ""; - if (finalText.trim()) return finalText; + if (finalText.trim()) { + return finalText; + } const streamedText = params.streamedText ?? ""; - if (streamedText.trim()) return streamedText; + if (streamedText.trim()) { + return streamedText; + } return "(no output)"; } @@ -36,15 +40,23 @@ export function composeThinkingAndContent(params: { * Model-agnostic: returns empty string if no thinking blocks exist. */ export function extractThinkingFromMessage(message: unknown): string { - if (!message || typeof message !== "object") return ""; + if (!message || typeof message !== "object") { + return ""; + } const record = message as Record; const content = record.content; - if (typeof content === "string") return ""; - if (!Array.isArray(content)) return ""; + if (typeof content === "string") { + return ""; + } + if (!Array.isArray(content)) { + return ""; + } const parts: string[] = []; for (const block of content) { - if (!block || typeof block !== "object") continue; + if (!block || typeof block !== "object") { + continue; + } const rec = block as Record; if (rec.type === "thinking" && typeof rec.thinking === "string") { parts.push(rec.thinking); @@ -58,11 +70,15 @@ export function extractThinkingFromMessage(message: unknown): string { * Model-agnostic: works for any model with text content blocks. */ export function extractContentFromMessage(message: unknown): string { - if (!message || typeof message !== "object") return ""; + if (!message || typeof message !== "object") { + return ""; + } const record = message as Record; const content = record.content; - if (typeof content === "string") return content.trim(); + if (typeof content === "string") { + return content.trim(); + } // Check for error BEFORE returning empty for non-array content if (!Array.isArray(content)) { @@ -76,7 +92,9 @@ export function extractContentFromMessage(message: unknown): string { const parts: string[] = []; for (const block of content) { - if (!block || typeof block !== "object") continue; + if (!block || typeof block !== "object") { + continue; + } const rec = block as Record; if (rec.type === "text" && typeof rec.text === "string") { parts.push(rec.text); @@ -96,14 +114,20 @@ export function extractContentFromMessage(message: unknown): string { } function extractTextBlocks(content: unknown, opts?: { includeThinking?: boolean }): string { - if (typeof content === "string") return content.trim(); - if (!Array.isArray(content)) return ""; + if (typeof content === "string") { + return content.trim(); + } + if (!Array.isArray(content)) { + return ""; + } const thinkingParts: string[] = []; const textParts: string[] = []; for (const block of content) { - if (!block || typeof block !== "object") continue; + if (!block || typeof block !== "object") { + continue; + } const record = block as Record; if (record.type === "text" && typeof record.text === "string") { textParts.push(record.text); @@ -128,27 +152,39 @@ export function extractTextFromMessage( message: unknown, opts?: { includeThinking?: boolean }, ): string { - if (!message || typeof message !== "object") return ""; + if (!message || typeof message !== "object") { + return ""; + } const record = message as Record; const text = extractTextBlocks(record.content, opts); - if (text) return text; + if (text) { + return text; + } const stopReason = typeof record.stopReason === "string" ? record.stopReason : ""; - if (stopReason !== "error") return ""; + if (stopReason !== "error") { + return ""; + } const errorMessage = typeof record.errorMessage === "string" ? record.errorMessage : ""; return formatRawAssistantErrorForUi(errorMessage); } export function isCommandMessage(message: unknown): boolean { - if (!message || typeof message !== "object") return false; + if (!message || typeof message !== "object") { + return false; + } return (message as Record).command === true; } export function formatTokens(total?: number | null, context?: number | null) { - if (total == null && context == null) return "tokens ?"; + if (total == null && context == null) { + return "tokens ?"; + } const totalLabel = total == null ? "?" : formatTokenCount(total); - if (context == null) return `tokens ${totalLabel}`; + if (context == null) { + return `tokens ${totalLabel}`; + } const pct = typeof total === "number" && context > 0 ? Math.min(999, Math.round((total / context) * 100)) @@ -173,7 +209,9 @@ export function formatContextUsageLine(params: { } export function asString(value: unknown, fallback = ""): string { - if (typeof value === "string") return value; + if (typeof value === "string") { + return value; + } if (typeof value === "number" || typeof value === "boolean") { return String(value); } diff --git a/src/tui/tui-local-shell.ts b/src/tui/tui-local-shell.ts index 296862c306..0ff12a5467 100644 --- a/src/tui/tui-local-shell.ts +++ b/src/tui/tui-local-shell.ts @@ -34,8 +34,12 @@ export function createLocalShellRunner(deps: LocalShellDeps) { const maxChars = deps.maxOutputChars ?? 40_000; const ensureLocalExecAllowed = async (): Promise => { - if (localExecAllowed) return true; - if (localExecAsked) return false; + if (localExecAllowed) { + return true; + } + if (localExecAsked) { + return false; + } localExecAsked = true; return await new Promise((resolve) => { @@ -78,7 +82,9 @@ export function createLocalShellRunner(deps: LocalShellDeps) { const cmd = line.slice(1); // NOTE: A lone '!' is handled by the submit handler as a normal message. // Keep this guard anyway in case this is called directly. - if (cmd === "") return; + if (cmd === "") { + return; + } if (localExecAsked && !localExecAllowed) { deps.chatLog.addSystem("local shell: not enabled for this session"); @@ -87,7 +93,9 @@ export function createLocalShellRunner(deps: LocalShellDeps) { } const allowed = await ensureLocalExecAllowed(); - if (!allowed) return; + if (!allowed) { + return; + } deps.chatLog.addSystem(`[local] $ ${cmd}`); deps.tui.requestRender(); diff --git a/src/tui/tui-session-actions.ts b/src/tui/tui-session-actions.ts index 5dc6696ad4..7db5ed9ee0 100644 --- a/src/tui/tui-session-actions.ts +++ b/src/tui/tui-session-actions.ts @@ -53,7 +53,9 @@ export function createSessionActions(context: SessionActionContext) { })); agentNames.clear(); for (const agent of state.agents) { - if (agent.name) agentNames.set(agent.id, agent.name); + if (agent.name) { + agentNames.set(agent.id, agent.name); + } } if (!state.initialSessionApplied) { if (initialSessionAgentId) { @@ -88,7 +90,9 @@ export function createSessionActions(context: SessionActionContext) { const updateAgentFromSessionKey = (key: string) => { const parsed = parseAgentSessionKey(key); - if (!parsed) return; + if (!parsed) { + return; + } const next = normalizeAgentId(parsed.agentId); if (next !== state.currentAgentId) { state.currentAgentId = next; @@ -96,7 +100,9 @@ export function createSessionActions(context: SessionActionContext) { }; const refreshSessionInfo = async () => { - if (refreshSessionInfoPromise) return refreshSessionInfoPromise; + if (refreshSessionInfoPromise) { + return refreshSessionInfoPromise; + } refreshSessionInfoPromise = (async () => { try { const listAgentId = @@ -110,7 +116,9 @@ export function createSessionActions(context: SessionActionContext) { }); const entry = result.sessions.find((row) => { // Exact match - if (row.key === state.currentSessionKey) return true; + if (row.key === state.currentSessionKey) { + return true; + } // Also match canonical keys like "agent:default:main" against "main" const parsed = parseAgentSessionKey(row.key); return parsed?.rest === state.currentSessionKey; @@ -159,23 +167,31 @@ export function createSessionActions(context: SessionActionContext) { chatLog.clearAll(); chatLog.addSystem(`session ${state.currentSessionKey}`); for (const entry of record.messages ?? []) { - if (!entry || typeof entry !== "object") continue; + if (!entry || typeof entry !== "object") { + continue; + } const message = entry as Record; if (isCommandMessage(message)) { const text = extractTextFromMessage(message); - if (text) chatLog.addSystem(text); + if (text) { + chatLog.addSystem(text); + } continue; } if (message.role === "user") { const text = extractTextFromMessage(message); - if (text) chatLog.addUser(text); + if (text) { + chatLog.addUser(text); + } continue; } if (message.role === "assistant") { const text = extractTextFromMessage(message, { includeThinking: state.showThinking, }); - if (text) chatLog.finalizeAssistant(text); + if (text) { + chatLog.finalizeAssistant(text); + } continue; } if (message.role === "toolResult") { diff --git a/src/tui/tui-status-summary.ts b/src/tui/tui-status-summary.ts index 2c0402fac8..62f1dcd653 100644 --- a/src/tui/tui-status-summary.ts +++ b/src/tui/tui-status-summary.ts @@ -32,7 +32,9 @@ export function formatStatusSummary(summary: GatewayStatusSummary) { if (heartbeatAgents.length > 0) { const heartbeatParts = heartbeatAgents.map((agent) => { const agentId = agent.agentId ?? "unknown"; - if (!agent.enabled || !agent.everyMs) return `disabled (${agentId})`; + if (!agent.enabled || !agent.everyMs) { + return `disabled (${agentId})`; + } return `${agent.every ?? "unknown"} (${agentId})`; }); lines.push(""); diff --git a/src/tui/tui-stream-assembler.ts b/src/tui/tui-stream-assembler.ts index d6f5d817ae..f944834616 100644 --- a/src/tui/tui-stream-assembler.ts +++ b/src/tui/tui-stream-assembler.ts @@ -52,7 +52,9 @@ export class TuiStreamAssembler { const previousDisplayText = state.displayText; this.updateRunState(state, message, showThinking); - if (!state.displayText || state.displayText === previousDisplayText) return null; + if (!state.displayText || state.displayText === previousDisplayText) { + return null; + } return state.displayText; } diff --git a/src/tui/tui.ts b/src/tui/tui.ts index 566c6a1288..644146b9eb 100644 --- a/src/tui/tui.ts +++ b/src/tui/tui.ts @@ -52,7 +52,9 @@ export function createEditorSubmitHandler(params: { params.editor.setText(""); // Keep previous behavior: ignore empty/whitespace-only submissions. - if (!value) return; + if (!value) { + return; + } // Bash mode: only if the very first character is '!' and it's not just '!'. // IMPORTANT: use the raw (untrimmed) text so leading spaces do NOT trigger. @@ -259,7 +261,9 @@ export async function runTui(opts: TuiOptions) { tui.setFocus(editor); const formatSessionKey = (key: string) => { - if (key === "global" || key === "unknown") return key; + if (key === "global" || key === "unknown") { + return key; + } const parsed = parseAgentSessionKey(key); return parsed?.rest ?? key; }; @@ -271,15 +275,21 @@ export async function runTui(opts: TuiOptions) { const resolveSessionKey = (raw?: string) => { const trimmed = (raw ?? "").trim(); - if (sessionScope === "global") return "global"; + if (sessionScope === "global") { + return "global"; + } if (!trimmed) { return buildAgentMainSessionKey({ agentId: currentAgentId, mainKey: sessionMainKey, }); } - if (trimmed === "global" || trimmed === "unknown") return trimmed; - if (trimmed.startsWith("agent:")) return trimmed; + if (trimmed === "global" || trimmed === "unknown") { + return trimmed; + } + if (trimmed.startsWith("agent:")) { + return trimmed; + } return `agent:${currentAgentId}:${trimmed}`; }; @@ -301,14 +311,18 @@ export async function runTui(opts: TuiOptions) { const formatElapsed = (startMs: number) => { const totalSeconds = Math.max(0, Math.floor((Date.now() - startMs) / 1000)); - if (totalSeconds < 60) return `${totalSeconds}s`; + if (totalSeconds < 60) { + return `${totalSeconds}s`; + } const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; return `${minutes}m ${seconds}s`; }; const ensureStatusText = () => { - if (statusText) return; + if (statusText) { + return; + } statusContainer.clear(); statusLoader?.stop(); statusLoader = null; @@ -317,7 +331,9 @@ export async function runTui(opts: TuiOptions) { }; const ensureStatusLoader = () => { - if (statusLoader) return; + if (statusLoader) { + return; + } statusContainer.clear(); statusText = null; statusLoader = new Loader( @@ -334,7 +350,9 @@ export async function runTui(opts: TuiOptions) { let waitingPhrase: string | null = null; const updateBusyStatusMessage = () => { - if (!statusLoader || !statusStartedAt) return; + if (!statusLoader || !statusStartedAt) { + return; + } const elapsed = formatElapsed(statusStartedAt); if (activityStatus === "waiting") { @@ -355,21 +373,29 @@ export async function runTui(opts: TuiOptions) { }; const startStatusTimer = () => { - if (statusTimer) return; + if (statusTimer) { + return; + } statusTimer = setInterval(() => { - if (!busyStates.has(activityStatus)) return; + if (!busyStates.has(activityStatus)) { + return; + } updateBusyStatusMessage(); }, 1000); }; const stopStatusTimer = () => { - if (!statusTimer) return; + if (!statusTimer) { + return; + } clearInterval(statusTimer); statusTimer = null; }; const startWaitingTimer = () => { - if (waitingTimer) return; + if (waitingTimer) { + return; + } // Pick a phrase once per waiting session. if (!waitingPhrase) { @@ -380,13 +406,17 @@ export async function runTui(opts: TuiOptions) { waitingTick = 0; waitingTimer = setInterval(() => { - if (activityStatus !== "waiting") return; + if (activityStatus !== "waiting") { + return; + } updateBusyStatusMessage(); }, 120); }; const stopWaitingTimer = () => { - if (!waitingTimer) return; + if (!waitingTimer) { + return; + } clearInterval(waitingTimer); waitingTimer = null; waitingPhrase = null; @@ -423,7 +453,9 @@ export async function runTui(opts: TuiOptions) { const setConnectionStatus = (text: string, ttlMs?: number) => { connectionStatus = text; renderStatus(); - if (statusTimeout) clearTimeout(statusTimeout); + if (statusTimeout) { + clearTimeout(statusTimeout); + } if (ttlMs && ttlMs > 0) { statusTimeout = setTimeout(() => { connectionStatus = isConnected ? "connected" : "disconnected"; @@ -469,7 +501,9 @@ export async function runTui(opts: TuiOptions) { const { openOverlay, closeOverlay } = createOverlayHandlers(tui, editor); const initialSessionAgentId = (() => { - if (!initialSessionInput) return null; + if (!initialSessionInput) { + return null; + } const parsed = parseAgentSessionKey(initialSessionInput); return parsed ? normalizeAgentId(parsed.agentId) : null; })(); @@ -579,8 +613,12 @@ export async function runTui(opts: TuiOptions) { }; client.onEvent = (evt) => { - if (evt.event === "chat") handleChatEvent(evt.payload); - if (evt.event === "agent") handleAgentEvent(evt.payload); + if (evt.event === "chat") { + handleChatEvent(evt.payload); + } + if (evt.event === "agent") { + handleAgentEvent(evt.payload); + } }; client.onConnected = () => { diff --git a/src/utils.test.ts b/src/utils.test.ts index 22ebda7ee1..03aef53418 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -83,7 +83,9 @@ describe("jidToE164", () => { .spyOn(fs, "readFileSync") // biome-ignore lint/suspicious/noExplicitAny: forwarding to native signature .mockImplementation((path: any, encoding?: any) => { - if (path === mappingPath) return `"5551234"`; + if (path === mappingPath) { + return `"5551234"`; + } return original(path, encoding); }); expect(jidToE164("123@lid")).toBe("+5551234"); diff --git a/src/utils.ts b/src/utils.ts index 86bb1e06fe..e8a6ac7256 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -25,7 +25,9 @@ export function assertWebChannel(input: string): asserts input is WebChannel { } export function normalizePath(p: string): string { - if (!p.startsWith("/")) return `/${p}`; + if (!p.startsWith("/")) { + return `/${p}`; + } return p; } @@ -36,7 +38,9 @@ export function withWhatsAppPrefix(number: string): string { export function normalizeE164(number: string): string { const withoutPrefix = number.replace(/^whatsapp:/, "").trim(); const digits = withoutPrefix.replace(/[^\d+]/g, ""); - if (digits.startsWith("+")) return `+${digits.slice(1)}`; + if (digits.startsWith("+")) { + return `+${digits.slice(1)}`; + } return `+${digits}`; } @@ -49,11 +53,17 @@ export function isSelfChatMode( selfE164: string | null | undefined, allowFrom?: Array | null, ): boolean { - if (!selfE164) return false; - if (!Array.isArray(allowFrom) || allowFrom.length === 0) return false; + if (!selfE164) { + return false; + } + if (!Array.isArray(allowFrom) || allowFrom.length === 0) { + return false; + } const normalizedSelf = normalizeE164(selfE164); return allowFrom.some((n) => { - if (n === "*") return false; + if (n === "*") { + return false; + } try { return normalizeE164(String(n)) === normalizedSelf; } catch { @@ -64,7 +74,9 @@ export function isSelfChatMode( export function toWhatsappJid(number: string): string { const withoutPrefix = number.replace(/^whatsapp:/, "").trim(); - if (withoutPrefix.includes("@")) return withoutPrefix; + if (withoutPrefix.includes("@")) { + return withoutPrefix; + } const e164 = normalizeE164(withoutPrefix); const digits = e164.replace(/\D/g, ""); return `${digits}@s.whatsapp.net`; @@ -83,11 +95,15 @@ type LidLookup = { function resolveLidMappingDirs(opts?: JidToE164Options): string[] { const dirs = new Set(); const addDir = (dir?: string | null) => { - if (!dir) return; + if (!dir) { + return; + } dirs.add(resolveUserPath(dir)); }; addDir(opts?.authDir); - for (const dir of opts?.lidMappingDirs ?? []) addDir(dir); + for (const dir of opts?.lidMappingDirs ?? []) { + addDir(dir); + } addDir(resolveOAuthDir()); addDir(path.join(CONFIG_DIR, "credentials")); return [...dirs]; @@ -101,7 +117,9 @@ function readLidReverseMapping(lid: string, opts?: JidToE164Options): string | n try { const data = fs.readFileSync(mappingPath, "utf8"); const phone = JSON.parse(data) as string | number | null; - if (phone === null || phone === undefined) continue; + if (phone === null || phone === undefined) { + continue; + } return normalizeE164(String(phone)); } catch { // Try the next location. @@ -123,7 +141,9 @@ export function jidToE164(jid: string, opts?: JidToE164Options): string | null { if (lidMatch) { const lid = lidMatch[1]; const phone = readLidReverseMapping(lid, opts); - if (phone) return phone; + if (phone) { + return phone; + } const shouldLog = opts?.logMissing ?? shouldLogVerbose(); if (shouldLog) { logVerbose(`LID mapping not found for ${lid}; skipping inbound message`); @@ -137,14 +157,24 @@ export async function resolveJidToE164( jid: string | null | undefined, opts?: JidToE164Options & { lidLookup?: LidLookup }, ): Promise { - if (!jid) return null; + if (!jid) { + return null; + } const direct = jidToE164(jid, opts); - if (direct) return direct; - if (!/(@lid|@hosted\.lid)$/.test(jid)) return null; - if (!opts?.lidLookup?.getPNForLID) return null; + if (direct) { + return direct; + } + if (!/(@lid|@hosted\.lid)$/.test(jid)) { + return null; + } + if (!opts?.lidLookup?.getPNForLID) { + return null; + } try { const pnJid = await opts.lidLookup.getPNForLID(jid); - if (!pnJid) return null; + if (!pnJid) { + return null; + } return jidToE164(pnJid, opts); } catch (err) { if (shouldLogVerbose()) { @@ -197,13 +227,17 @@ export function sliceUtf16Safe(input: string, start: number, end?: number): stri export function truncateUtf16Safe(input: string, maxLen: number): string { const limit = Math.max(0, Math.floor(maxLen)); - if (input.length <= limit) return input; + if (input.length <= limit) { + return input; + } return sliceUtf16Safe(input, 0, limit); } export function resolveUserPath(input: string): string { const trimmed = input.trim(); - if (!trimmed) return trimmed; + if (!trimmed) { + return trimmed; + } if (trimmed.startsWith("~")) { const expanded = trimmed.replace(/^~(?=$|[\\/])/, os.homedir()); return path.resolve(expanded); @@ -216,11 +250,15 @@ export function resolveConfigDir( homedir: () => string = os.homedir, ): string { const override = env.OPENCLAW_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim(); - if (override) return resolveUserPath(override); + if (override) { + return resolveUserPath(override); + } const newDir = path.join(homedir(), ".openclaw"); try { const hasNew = fs.existsSync(newDir); - if (hasNew) return newDir; + if (hasNew) { + return newDir; + } } catch { // best-effort } @@ -229,9 +267,13 @@ export function resolveConfigDir( export function resolveHomeDir(): string | undefined { const envHome = process.env.HOME?.trim(); - if (envHome) return envHome; + if (envHome) { + return envHome; + } const envProfile = process.env.USERPROFILE?.trim(); - if (envProfile) return envProfile; + if (envProfile) { + return envProfile; + } try { const home = os.homedir(); return home?.trim() ? home : undefined; @@ -241,18 +283,30 @@ export function resolveHomeDir(): string | undefined { } export function shortenHomePath(input: string): string { - if (!input) return input; + if (!input) { + return input; + } const home = resolveHomeDir(); - if (!home) return input; - if (input === home) return "~"; - if (input.startsWith(`${home}/`)) return `~${input.slice(home.length)}`; + if (!home) { + return input; + } + if (input === home) { + return "~"; + } + if (input.startsWith(`${home}/`)) { + return `~${input.slice(home.length)}`; + } return input; } export function shortenHomeInString(input: string): string { - if (!input) return input; + if (!input) { + return input; + } const home = resolveHomeDir(); - if (!home) return input; + if (!home) { + return input; + } return input.split(home).join("~"); } diff --git a/src/utils/account-id.ts b/src/utils/account-id.ts index e8992cfd31..4827fc2142 100644 --- a/src/utils/account-id.ts +++ b/src/utils/account-id.ts @@ -1,5 +1,7 @@ export function normalizeAccountId(value?: string): string | undefined { - if (typeof value !== "string") return undefined; + if (typeof value !== "string") { + return undefined; + } const trimmed = value.trim(); return trimmed || undefined; } diff --git a/src/utils/boolean.ts b/src/utils/boolean.ts index 8847950efb..60e303f90b 100644 --- a/src/utils/boolean.ts +++ b/src/utils/boolean.ts @@ -12,15 +12,25 @@ export function parseBooleanValue( value: unknown, options: BooleanParseOptions = {}, ): boolean | undefined { - if (typeof value === "boolean") return value; - if (typeof value !== "string") return undefined; + if (typeof value === "boolean") { + return value; + } + if (typeof value !== "string") { + return undefined; + } const normalized = value.trim().toLowerCase(); - if (!normalized) return undefined; + if (!normalized) { + return undefined; + } const truthy = options.truthy ?? DEFAULT_TRUTHY; const falsy = options.falsy ?? DEFAULT_FALSY; const truthySet = truthy === DEFAULT_TRUTHY ? DEFAULT_TRUTHY_SET : new Set(truthy); const falsySet = falsy === DEFAULT_FALSY ? DEFAULT_FALSY_SET : new Set(falsy); - if (truthySet.has(normalized)) return true; - if (falsySet.has(normalized)) return false; + if (truthySet.has(normalized)) { + return true; + } + if (falsySet.has(normalized)) { + return false; + } return undefined; } diff --git a/src/utils/delivery-context.ts b/src/utils/delivery-context.ts index 61a8c08c15..97e88e9a82 100644 --- a/src/utils/delivery-context.ts +++ b/src/utils/delivery-context.ts @@ -18,7 +18,9 @@ export type DeliveryContextSessionSource = { }; export function normalizeDeliveryContext(context?: DeliveryContext): DeliveryContext | undefined { - if (!context) return undefined; + if (!context) { + return undefined; + } const channel = typeof context.channel === "string" ? (normalizeMessageChannel(context.channel) ?? context.channel.trim()) @@ -33,13 +35,17 @@ export function normalizeDeliveryContext(context?: DeliveryContext): DeliveryCon : undefined; const normalizedThreadId = typeof threadId === "string" ? (threadId ? threadId : undefined) : threadId; - if (!channel && !to && !accountId && normalizedThreadId == null) return undefined; + if (!channel && !to && !accountId && normalizedThreadId == null) { + return undefined; + } const normalized: DeliveryContext = { channel: channel || undefined, to: to || undefined, accountId, }; - if (normalizedThreadId != null) normalized.threadId = normalizedThreadId; + if (normalizedThreadId != null) { + normalized.threadId = normalizedThreadId; + } return normalized; } @@ -92,7 +98,9 @@ export function normalizeSessionDeliveryFields(source?: DeliveryContextSessionSo export function deliveryContextFromSession( entry?: DeliveryContextSessionSource & { origin?: { threadId?: string | number } }, ): DeliveryContext | undefined { - if (!entry) return undefined; + if (!entry) { + return undefined; + } const source: DeliveryContextSessionSource = { channel: entry.channel, lastChannel: entry.lastChannel, @@ -110,7 +118,9 @@ export function mergeDeliveryContext( ): DeliveryContext | undefined { const normalizedPrimary = normalizeDeliveryContext(primary); const normalizedFallback = normalizeDeliveryContext(fallback); - if (!normalizedPrimary && !normalizedFallback) return undefined; + if (!normalizedPrimary && !normalizedFallback) { + return undefined; + } return normalizeDeliveryContext({ channel: normalizedPrimary?.channel ?? normalizedFallback?.channel, to: normalizedPrimary?.to ?? normalizedFallback?.to, @@ -121,7 +131,9 @@ export function mergeDeliveryContext( export function deliveryContextKey(context?: DeliveryContext): string | undefined { const normalized = normalizeDeliveryContext(context); - if (!normalized?.channel || !normalized?.to) return undefined; + if (!normalized?.channel || !normalized?.to) { + return undefined; + } const threadId = normalized.threadId != null && normalized.threadId !== "" ? String(normalized.threadId) : ""; return `${normalized.channel}|${normalized.to}|${normalized.accountId ?? ""}|${threadId}`; diff --git a/src/utils/directive-tags.ts b/src/utils/directive-tags.ts index a58b143dca..1260b8aa5c 100644 --- a/src/utils/directive-tags.ts +++ b/src/utils/directive-tags.ts @@ -58,7 +58,9 @@ export function parseInlineDirectives( sawCurrent = true; } else { const id = idRaw.trim(); - if (id) lastExplicitId = id; + if (id) { + lastExplicitId = id; + } } return stripReplyTags ? " " : match; }); diff --git a/src/utils/message-channel.ts b/src/utils/message-channel.ts index 1b33b72099..da8205126d 100644 --- a/src/utils/message-channel.ts +++ b/src/utils/message-channel.ts @@ -46,19 +46,29 @@ export function isInternalMessageChannel(raw?: string | null): raw is InternalMe export function isWebchatClient(client?: GatewayClientInfoLike | null): boolean { const mode = normalizeGatewayClientMode(client?.mode); - if (mode === GATEWAY_CLIENT_MODES.WEBCHAT) return true; + if (mode === GATEWAY_CLIENT_MODES.WEBCHAT) { + return true; + } return normalizeGatewayClientName(client?.id) === GATEWAY_CLIENT_NAMES.WEBCHAT_UI; } export function normalizeMessageChannel(raw?: string | null): string | undefined { const normalized = raw?.trim().toLowerCase(); - if (!normalized) return undefined; - if (normalized === INTERNAL_MESSAGE_CHANNEL) return INTERNAL_MESSAGE_CHANNEL; + if (!normalized) { + return undefined; + } + if (normalized === INTERNAL_MESSAGE_CHANNEL) { + return INTERNAL_MESSAGE_CHANNEL; + } const builtIn = normalizeChatChannelId(normalized); - if (builtIn) return builtIn; + if (builtIn) { + return builtIn; + } const registry = getActivePluginRegistry(); const pluginMatch = registry?.channels.find((entry) => { - if (entry.plugin.id.toLowerCase() === normalized) return true; + if (entry.plugin.id.toLowerCase() === normalized) { + return true; + } return (entry.plugin.meta.aliases ?? []).some( (alias) => alias.trim().toLowerCase() === normalized, ); @@ -68,13 +78,17 @@ export function normalizeMessageChannel(raw?: string | null): string | undefined const listPluginChannelIds = (): string[] => { const registry = getActivePluginRegistry(); - if (!registry) return []; + if (!registry) { + return []; + } return registry.channels.map((entry) => entry.plugin.id); }; const listPluginChannelAliases = (): string[] => { const registry = getActivePluginRegistry(); - if (!registry) return []; + if (!registry) { + return []; + } return registry.channels.flatMap((entry) => entry.plugin.meta.aliases ?? []); }; @@ -112,7 +126,9 @@ export function resolveGatewayMessageChannel( raw?: string | null, ): GatewayMessageChannel | undefined { const normalized = normalizeMessageChannel(raw); - if (!normalized) return undefined; + if (!normalized) { + return undefined; + } return isGatewayMessageChannel(normalized) ? normalized : undefined; } @@ -125,6 +141,8 @@ export function resolveMessageChannel( export function isMarkdownCapableMessageChannel(raw?: string | null): boolean { const channel = normalizeMessageChannel(raw); - if (!channel) return false; + if (!channel) { + return false; + } return MARKDOWN_CAPABLE_CHANNELS.has(channel); } diff --git a/src/utils/provider-utils.ts b/src/utils/provider-utils.ts index e29ffe1ab7..046a23c78a 100644 --- a/src/utils/provider-utils.ts +++ b/src/utils/provider-utils.ts @@ -8,7 +8,9 @@ * API fields for reasoning/thinking. */ export function isReasoningTagProvider(provider: string | undefined | null): boolean { - if (!provider) return false; + if (!provider) { + return false; + } const normalized = provider.trim().toLowerCase(); // Check for exact matches or known prefixes/substrings for reasoning providers diff --git a/src/utils/queue-helpers.ts b/src/utils/queue-helpers.ts index e4bac1fe05..990b52bb58 100644 --- a/src/utils/queue-helpers.ts +++ b/src/utils/queue-helpers.ts @@ -12,7 +12,9 @@ export type QueueState = QueueSummaryState & { }; export function elideQueueText(text: string, limit = 140): string { - if (text.length <= limit) return text; + if (text.length <= limit) { + return text; + } return `${text.slice(0, Math.max(0, limit - 1)).trimEnd()}…`; } @@ -26,7 +28,9 @@ export function shouldSkipQueueItem(params: { items: T[]; dedupe?: (item: T, items: T[]) => boolean; }): boolean { - if (!params.dedupe) return false; + if (!params.dedupe) { + return false; + } return params.dedupe(params.item, params.items); } @@ -36,8 +40,12 @@ export function applyQueueDropPolicy(params: { summaryLimit?: number; }): boolean { const cap = params.queue.cap; - if (cap <= 0 || params.queue.items.length < cap) return true; - if (params.queue.dropPolicy === "new") return false; + if (cap <= 0 || params.queue.items.length < cap) { + return true; + } + if (params.queue.dropPolicy === "new") { + return false; + } const dropCount = params.queue.items.length - cap + 1; const dropped = params.queue.items.splice(0, dropCount); if (params.queue.dropPolicy === "summarize") { @@ -46,7 +54,9 @@ export function applyQueueDropPolicy(params: { params.queue.summaryLines.push(buildQueueSummaryLine(params.summarize(item))); } const limit = Math.max(0, params.summaryLimit ?? cap); - while (params.queue.summaryLines.length > limit) params.queue.summaryLines.shift(); + while (params.queue.summaryLines.length > limit) { + params.queue.summaryLines.shift(); + } } return true; } @@ -56,7 +66,9 @@ export function waitForQueueDebounce(queue: { lastEnqueuedAt: number; }): Promise { const debounceMs = Math.max(0, queue.debounceMs); - if (debounceMs <= 0) return Promise.resolve(); + if (debounceMs <= 0) { + return Promise.resolve(); + } return new Promise((resolve) => { const check = () => { const since = Date.now() - queue.lastEnqueuedAt; @@ -101,7 +113,9 @@ export function buildCollectPrompt(params: { renderItem: (item: T, index: number) => string; }): string { const blocks: string[] = [params.title]; - if (params.summary) blocks.push(params.summary); + if (params.summary) { + blocks.push(params.summary); + } params.items.forEach((item, idx) => { blocks.push(params.renderItem(item, idx)); }); @@ -117,7 +131,9 @@ export function hasCrossChannelItems( for (const item of items) { const resolved = resolveKey(item); - if (resolved.cross) return true; + if (resolved.cross) { + return true; + } if (!resolved.key) { hasUnkeyed = true; continue; @@ -125,7 +141,11 @@ export function hasCrossChannelItems( keys.add(resolved.key); } - if (keys.size === 0) return false; - if (hasUnkeyed) return true; + if (keys.size === 0) { + return false; + } + if (hasUnkeyed) { + return true; + } return keys.size > 1; } diff --git a/src/utils/time-format.ts b/src/utils/time-format.ts index bd473e4f6a..188cec4c79 100644 --- a/src/utils/time-format.ts +++ b/src/utils/time-format.ts @@ -6,10 +6,20 @@ export function formatRelativeTime(timestamp: number): string { const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); - if (seconds < 60) return "just now"; - if (minutes < 60) return `${minutes}m ago`; - if (hours < 24) return `${hours}h ago`; - if (days === 1) return "Yesterday"; - if (days < 7) return `${days}d ago`; + if (seconds < 60) { + return "just now"; + } + if (minutes < 60) { + return `${minutes}m ago`; + } + if (hours < 24) { + return `${hours}h ago`; + } + if (days === 1) { + return "Yesterday"; + } + if (days < 7) { + return `${days}d ago`; + } return new Date(timestamp).toLocaleDateString(undefined, { month: "short", day: "numeric" }); } diff --git a/src/utils/usage-format.ts b/src/utils/usage-format.ts index 77e5497b10..f8182f5dbb 100644 --- a/src/utils/usage-format.ts +++ b/src/utils/usage-format.ts @@ -17,17 +17,29 @@ export type UsageTotals = { }; export function formatTokenCount(value?: number): string { - if (value === undefined || !Number.isFinite(value)) return "0"; + if (value === undefined || !Number.isFinite(value)) { + return "0"; + } const safe = Math.max(0, value); - if (safe >= 1_000_000) return `${(safe / 1_000_000).toFixed(1)}m`; - if (safe >= 1_000) return `${(safe / 1_000).toFixed(safe >= 10_000 ? 0 : 1)}k`; + if (safe >= 1_000_000) { + return `${(safe / 1_000_000).toFixed(1)}m`; + } + if (safe >= 1_000) { + return `${(safe / 1_000).toFixed(safe >= 10_000 ? 0 : 1)}k`; + } return String(Math.round(safe)); } export function formatUsd(value?: number): string | undefined { - if (value === undefined || !Number.isFinite(value)) return undefined; - if (value >= 1) return `$${value.toFixed(2)}`; - if (value >= 0.01) return `$${value.toFixed(2)}`; + if (value === undefined || !Number.isFinite(value)) { + return undefined; + } + if (value >= 1) { + return `$${value.toFixed(2)}`; + } + if (value >= 0.01) { + return `$${value.toFixed(2)}`; + } return `$${value.toFixed(4)}`; } @@ -38,7 +50,9 @@ export function resolveModelCostConfig(params: { }): ModelCostConfig | undefined { const provider = params.provider?.trim(); const model = params.model?.trim(); - if (!provider || !model) return undefined; + if (!provider || !model) { + return undefined; + } const providers = params.config?.models?.providers ?? {}; const entry = providers[provider]?.models?.find((item) => item.id === model); return entry?.cost; @@ -53,7 +67,9 @@ export function estimateUsageCost(params: { }): number | undefined { const usage = params.usage; const cost = params.cost; - if (!usage || !cost) return undefined; + if (!usage || !cost) { + return undefined; + } const input = toNumber(usage.input); const output = toNumber(usage.output); const cacheRead = toNumber(usage.cacheRead); @@ -63,6 +79,8 @@ export function estimateUsageCost(params: { output * cost.output + cacheRead * cost.cacheRead + cacheWrite * cost.cacheWrite; - if (!Number.isFinite(total)) return undefined; + if (!Number.isFinite(total)) { + return undefined; + } return total / 1_000_000; } diff --git a/src/web/accounts.ts b/src/web/accounts.ts index 7179ea8e29..af45a414ca 100644 --- a/src/web/accounts.ts +++ b/src/web/accounts.ts @@ -32,7 +32,9 @@ export type ResolvedWhatsAppAccount = { function listConfiguredAccountIds(cfg: OpenClawConfig): string[] { const accounts = cfg.channels?.whatsapp?.accounts; - if (!accounts || typeof accounts !== "object") return []; + if (!accounts || typeof accounts !== "object") { + return []; + } return Object.keys(accounts).filter(Boolean); } @@ -49,7 +51,9 @@ export function listWhatsAppAuthDirs(cfg: OpenClawConfig): string[] { try { const entries = fs.readdirSync(whatsappDir, { withFileTypes: true }); for (const entry of entries) { - if (!entry.isDirectory()) continue; + if (!entry.isDirectory()) { + continue; + } authDirs.add(path.join(whatsappDir, entry.name)); } } catch { @@ -65,13 +69,17 @@ export function hasAnyWhatsAppAuth(cfg: OpenClawConfig): boolean { export function listWhatsAppAccountIds(cfg: OpenClawConfig): string[] { const ids = listConfiguredAccountIds(cfg); - if (ids.length === 0) return [DEFAULT_ACCOUNT_ID]; + if (ids.length === 0) { + return [DEFAULT_ACCOUNT_ID]; + } return ids.toSorted((a, b) => a.localeCompare(b)); } export function resolveDefaultWhatsAppAccountId(cfg: OpenClawConfig): string { const ids = listWhatsAppAccountIds(cfg); - if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID; + if (ids.includes(DEFAULT_ACCOUNT_ID)) { + return DEFAULT_ACCOUNT_ID; + } return ids[0] ?? DEFAULT_ACCOUNT_ID; } @@ -80,7 +88,9 @@ function resolveAccountConfig( accountId: string, ): WhatsAppAccountConfig | undefined { const accounts = cfg.channels?.whatsapp?.accounts; - if (!accounts || typeof accounts !== "object") return undefined; + if (!accounts || typeof accounts !== "object") { + return undefined; + } const entry = accounts[accountId] as WhatsAppAccountConfig | undefined; return entry; } diff --git a/src/web/auth-store.ts b/src/web/auth-store.ts index f79ef92708..0bf60ac333 100644 --- a/src/web/auth-store.ts +++ b/src/web/auth-store.ts @@ -36,9 +36,13 @@ export function hasWebCredsSync(authDir: string): boolean { function readCredsJsonRaw(filePath: string): string | null { try { - if (!fsSync.existsSync(filePath)) return null; + if (!fsSync.existsSync(filePath)) { + return null; + } const stats = fsSync.statSync(filePath); - if (!stats.isFile() || stats.size <= 1) return null; + if (!stats.isFile() || stats.size <= 1) { + return null; + } return fsSync.readFileSync(filePath, "utf-8"); } catch { return null; @@ -58,7 +62,9 @@ export function maybeRestoreCredsFromBackup(authDir: string): void { } const backupRaw = readCredsJsonRaw(backupPath); - if (!backupRaw) return; + if (!backupRaw) { + return; + } // Ensure backup is parseable before restoring. JSON.parse(backupRaw); @@ -80,7 +86,9 @@ export async function webAuthExists(authDir: string = resolveDefaultWebAuthDir() } try { const stats = await fs.stat(credsPath); - if (!stats.isFile() || stats.size <= 1) return false; + if (!stats.isFile() || stats.size <= 1) { + return false; + } const raw = await fs.readFile(credsPath, "utf-8"); JSON.parse(raw); return true; @@ -92,15 +100,25 @@ export async function webAuthExists(authDir: string = resolveDefaultWebAuthDir() async function clearLegacyBaileysAuthState(authDir: string) { const entries = await fs.readdir(authDir, { withFileTypes: true }); const shouldDelete = (name: string) => { - if (name === "oauth.json") return false; - if (name === "creds.json" || name === "creds.json.bak") return true; - if (!name.endsWith(".json")) return false; + if (name === "oauth.json") { + return false; + } + if (name === "creds.json" || name === "creds.json.bak") { + return true; + } + if (!name.endsWith(".json")) { + return false; + } return /^(app-state-sync|session|sender-key|pre-key)-/.test(name); }; await Promise.all( entries.map(async (entry) => { - if (!entry.isFile()) return; - if (!shouldDelete(entry.name)) return; + if (!entry.isFile()) { + return; + } + if (!shouldDelete(entry.name)) { + return; + } await fs.rm(path.join(authDir, entry.name), { force: true }); }), ); diff --git a/src/web/auto-reply.partial-reply-gating.test.ts b/src/web/auto-reply.partial-reply-gating.test.ts index d4ebc58702..14bb7b81b5 100644 --- a/src/web/auto-reply.partial-reply-gating.test.ts +++ b/src/web/auto-reply.partial-reply-gating.test.ts @@ -230,10 +230,14 @@ describe("partial reply gating", () => { string, { lastChannel?: string; lastTo?: string } >; - if (stored[mainSessionKey]?.lastChannel && stored[mainSessionKey]?.lastTo) break; + if (stored[mainSessionKey]?.lastChannel && stored[mainSessionKey]?.lastTo) { + break; + } await new Promise((resolve) => setTimeout(resolve, 5)); } - if (!stored) throw new Error("store not loaded"); + if (!stored) { + throw new Error("store not loaded"); + } expect(stored[mainSessionKey]?.lastChannel).toBe("whatsapp"); expect(stored[mainSessionKey]?.lastTo).toBe("+1000"); @@ -295,11 +299,14 @@ describe("partial reply gating", () => { stored[groupSessionKey]?.lastChannel && stored[groupSessionKey]?.lastTo && stored[groupSessionKey]?.lastAccountId - ) + ) { break; + } await new Promise((resolve) => setTimeout(resolve, 5)); } - if (!stored) throw new Error("store not loaded"); + if (!stored) { + throw new Error("store not loaded"); + } expect(stored[groupSessionKey]?.lastChannel).toBe("whatsapp"); expect(stored[groupSessionKey]?.lastTo).toBe("123@g.us"); expect(stored[groupSessionKey]?.lastAccountId).toBe("work"); diff --git a/src/web/auto-reply/heartbeat-runner.ts b/src/web/auto-reply/heartbeat-runner.ts index 1a194c4030..7badc6169c 100644 --- a/src/web/auto-reply/heartbeat-runner.ts +++ b/src/web/auto-reply/heartbeat-runner.ts @@ -28,11 +28,17 @@ import { elide } from "./util.js"; function resolveHeartbeatReplyPayload( replyResult: ReplyPayload | ReplyPayload[] | undefined, ): ReplyPayload | undefined { - if (!replyResult) return undefined; - if (!Array.isArray(replyResult)) return replyResult; + if (!replyResult) { + return undefined; + } + if (!Array.isArray(replyResult)) { + return replyResult; + } for (let idx = replyResult.length - 1; idx >= 0; idx -= 1) { const payload = replyResult[idx]; - if (!payload) continue; + if (!payload) { + continue; + } if (payload.text || payload.mediaUrl || (payload.mediaUrls && payload.mediaUrls.length > 0)) { return payload; } @@ -221,7 +227,9 @@ export async function runWebHeartbeatOnce(opts: { store[sessionSnapshot.key].updatedAt = sessionSnapshot.entry.updatedAt; await updateSessionStore(storePath, (nextStore) => { const nextEntry = nextStore[sessionSnapshot.key]; - if (!nextEntry) return; + if (!nextEntry) { + return; + } nextStore[sessionSnapshot.key] = { ...nextEntry, updatedAt: sessionSnapshot.entry.updatedAt, diff --git a/src/web/auto-reply/mentions.ts b/src/web/auto-reply/mentions.ts index bf1352799e..f595bd2f0a 100644 --- a/src/web/auto-reply/mentions.ts +++ b/src/web/auto-reply/mentions.ts @@ -45,10 +45,14 @@ export function isBotMentionedFromTargets( const hasMentions = (msg.mentionedJids?.length ?? 0) > 0; if (hasMentions && !isSelfChat) { - if (targets.selfE164 && targets.normalizedMentions.includes(targets.selfE164)) return true; + if (targets.selfE164 && targets.normalizedMentions.includes(targets.selfE164)) { + return true; + } if (targets.selfJid) { // Some mentions use the bare JID; match on E.164 to be safe. - if (targets.normalizedMentions.includes(targets.selfJid)) return true; + if (targets.normalizedMentions.includes(targets.selfJid)) { + return true; + } } // If the message explicitly mentions someone else, do not fall back to regex matches. return false; @@ -56,17 +60,23 @@ export function isBotMentionedFromTargets( // Self-chat mode: ignore WhatsApp @mention JIDs, otherwise @mentioning the owner in group chats triggers the bot. } const bodyClean = clean(msg.body); - if (mentionCfg.mentionRegexes.some((re) => re.test(bodyClean))) return true; + if (mentionCfg.mentionRegexes.some((re) => re.test(bodyClean))) { + return true; + } // Fallback: detect body containing our own number (with or without +, spacing) if (targets.selfE164) { const selfDigits = targets.selfE164.replace(/\D/g, ""); if (selfDigits) { const bodyDigits = bodyClean.replace(/[^\d]/g, ""); - if (bodyDigits.includes(selfDigits)) return true; + if (bodyDigits.includes(selfDigits)) { + return true; + } const bodyNoSpace = msg.body.replace(/[\s-]/g, ""); const pattern = new RegExp(`\\+?${selfDigits}`, "i"); - if (pattern.test(bodyNoSpace)) return true; + if (pattern.test(bodyNoSpace)) { + return true; + } } } diff --git a/src/web/auto-reply/monitor.ts b/src/web/auto-reply/monitor.ts index 6d05a13092..907bb79ed9 100644 --- a/src/web/auto-reply/monitor.ts +++ b/src/web/auto-reply/monitor.ts @@ -141,7 +141,9 @@ export async function monitorWebChannel( let reconnectAttempts = 0; while (true) { - if (stopRequested()) break; + if (stopRequested()) { + break; + } const connectionId = newConnectionId(); const startedAt = Date.now(); @@ -175,9 +177,15 @@ export async function monitorWebChannel( const inboundDebounceMs = resolveInboundDebounceMs({ cfg, channel: "whatsapp" }); const shouldDebounce = (msg: WebInboundMsg) => { - if (msg.mediaPath || msg.mediaType) return false; - if (msg.location) return false; - if (msg.replyToId || msg.replyToBody) return false; + if (msg.mediaPath || msg.mediaType) { + return false; + } + if (msg.location) { + return false; + } + if (msg.replyToId || msg.replyToBody) { + return false; + } return !hasControlCommand(msg.body, cfg); }; @@ -219,7 +227,9 @@ export async function monitorWebChannel( setActiveWebListener(account.accountId, listener); unregisterUnhandled = registerUnhandledRejectionHandler((reason) => { - if (!isLikelyWhatsAppCryptoError(reason)) return false; + if (!isLikelyWhatsAppCryptoError(reason)) { + return false; + } const errorStr = formatError(reason); reconnectLogger.warn( { connectionId, error: errorStr }, @@ -239,8 +249,12 @@ export async function monitorWebChannel( unregisterUnhandled(); unregisterUnhandled = null; } - if (heartbeat) clearInterval(heartbeat); - if (watchdogTimer) clearInterval(watchdogTimer); + if (heartbeat) { + clearInterval(heartbeat); + } + if (watchdogTimer) { + clearInterval(watchdogTimer); + } if (backgroundTasks.size > 0) { await Promise.allSettled(backgroundTasks); backgroundTasks.clear(); @@ -279,9 +293,13 @@ export async function monitorWebChannel( }, heartbeatSeconds * 1000); watchdogTimer = setInterval(() => { - if (!lastMessageAt) return; + if (!lastMessageAt) { + return; + } const timeSinceLastMessage = Date.now() - lastMessageAt; - if (timeSinceLastMessage <= MESSAGE_TIMEOUT_MS) return; + if (timeSinceLastMessage <= MESSAGE_TIMEOUT_MS) { + return; + } const minutesSinceLastMessage = Math.floor(timeSinceLastMessage / 60000); heartbeatLogger.warn( { diff --git a/src/web/auto-reply/monitor/ack-reaction.ts b/src/web/auto-reply/monitor/ack-reaction.ts index 6a99da312c..bfc53f8235 100644 --- a/src/web/auto-reply/monitor/ack-reaction.ts +++ b/src/web/auto-reply/monitor/ack-reaction.ts @@ -17,7 +17,9 @@ export function maybeSendAckReaction(params: { info: (obj: unknown, msg: string) => void; warn: (obj: unknown, msg: string) => void; }) { - if (!params.msg.id) return; + if (!params.msg.id) { + return; + } const ackConfig = params.cfg.channels?.whatsapp?.ackReaction; const emoji = (ackConfig?.emoji ?? "").trim(); @@ -45,7 +47,9 @@ export function maybeSendAckReaction(params: { groupActivated: activation === "always", }); - if (!shouldSendReaction()) return; + if (!shouldSendReaction()) { + return; + } params.info( { chatId: params.msg.chatId, messageId: params.msg.id, emoji }, diff --git a/src/web/auto-reply/monitor/broadcast.ts b/src/web/auto-reply/monitor/broadcast.ts index c8f84a0480..52648cd97a 100644 --- a/src/web/auto-reply/monitor/broadcast.ts +++ b/src/web/auto-reply/monitor/broadcast.ts @@ -29,8 +29,12 @@ export async function maybeBroadcastMessage(params: { ) => Promise; }) { const broadcastAgents = params.cfg.broadcast?.[params.peerId]; - if (!broadcastAgents || !Array.isArray(broadcastAgents)) return false; - if (broadcastAgents.length === 0) return false; + if (!broadcastAgents || !Array.isArray(broadcastAgents)) { + return false; + } + if (broadcastAgents.length === 0) { + return false; + } const strategy = params.cfg.broadcast?.strategy || "parallel"; whatsappInboundLog.info(`Broadcasting message to ${broadcastAgents.length} agents (${strategy})`); diff --git a/src/web/auto-reply/monitor/commands.ts b/src/web/auto-reply/monitor/commands.ts index 7c6d6423a6..2947c6909d 100644 --- a/src/web/auto-reply/monitor/commands.ts +++ b/src/web/auto-reply/monitor/commands.ts @@ -1,6 +1,8 @@ export function isStatusCommand(body: string) { const trimmed = body.trim().toLowerCase(); - if (!trimmed) return false; + if (!trimmed) { + return false; + } return trimmed === "/status" || trimmed === "status" || trimmed.startsWith("/status "); } diff --git a/src/web/auto-reply/monitor/echo.ts b/src/web/auto-reply/monitor/echo.ts index e0e6394ba4..ca13a98e90 100644 --- a/src/web/auto-reply/monitor/echo.ts +++ b/src/web/auto-reply/monitor/echo.ts @@ -25,13 +25,17 @@ export function createEchoTracker(params: { const trim = () => { while (recentlySent.size > maxItems) { const firstKey = recentlySent.values().next().value; - if (!firstKey) break; + if (!firstKey) { + break; + } recentlySent.delete(firstKey); } }; const rememberText: EchoTracker["rememberText"] = (text, opts) => { - if (!text) return; + if (!text) { + return; + } recentlySent.add(text); if (opts.combinedBody && opts.combinedBodySessionKey) { recentlySent.add( diff --git a/src/web/auto-reply/monitor/group-gating.ts b/src/web/auto-reply/monitor/group-gating.ts index 8d1a33645e..6a301ca1fa 100644 --- a/src/web/auto-reply/monitor/group-gating.ts +++ b/src/web/auto-reply/monitor/group-gating.ts @@ -21,7 +21,9 @@ export type GroupHistoryEntry = { function isOwnerSender(baseMentionConfig: MentionConfig, msg: WebInboundMsg) { const sender = normalizeE164(msg.senderE164 ?? ""); - if (!sender) return false; + if (!sender) { + return false; + } const owners = resolveOwnerList(baseMentionConfig, msg.selfE164 ?? undefined); return owners.includes(sender); } diff --git a/src/web/auto-reply/monitor/group-members.ts b/src/web/auto-reply/monitor/group-members.ts index 65e63c0a06..dc69935e94 100644 --- a/src/web/auto-reply/monitor/group-members.ts +++ b/src/web/auto-reply/monitor/group-members.ts @@ -6,10 +6,14 @@ export function noteGroupMember( e164?: string, name?: string, ) { - if (!e164 || !name) return; + if (!e164 || !name) { + return; + } const normalized = normalizeE164(e164); const key = normalized ?? e164; - if (!key) return; + if (!key) { + return; + } let roster = groupMemberNames.get(conversationId); if (!roster) { roster = new Map(); @@ -28,9 +32,13 @@ export function formatGroupMembers(params: { const ordered: string[] = []; if (participants?.length) { for (const entry of participants) { - if (!entry) continue; + if (!entry) { + continue; + } const normalized = normalizeE164(entry) ?? entry; - if (!normalized || seen.has(normalized)) continue; + if (!normalized || seen.has(normalized)) { + continue; + } seen.add(normalized); ordered.push(normalized); } @@ -38,16 +46,22 @@ export function formatGroupMembers(params: { if (roster) { for (const entry of roster.keys()) { const normalized = normalizeE164(entry) ?? entry; - if (!normalized || seen.has(normalized)) continue; + if (!normalized || seen.has(normalized)) { + continue; + } seen.add(normalized); ordered.push(normalized); } } if (ordered.length === 0 && fallbackE164) { const normalized = normalizeE164(fallbackE164) ?? fallbackE164; - if (normalized) ordered.push(normalized); + if (normalized) { + ordered.push(normalized); + } + } + if (ordered.length === 0) { + return undefined; } - if (ordered.length === 0) return undefined; return ordered .map((entry) => { const name = roster?.get(entry); diff --git a/src/web/auto-reply/monitor/last-route.ts b/src/web/auto-reply/monitor/last-route.ts index 5359dbbcdc..2943537e1c 100644 --- a/src/web/auto-reply/monitor/last-route.ts +++ b/src/web/auto-reply/monitor/last-route.ts @@ -51,7 +51,9 @@ export function updateLastRouteInBackground(params: { } export function awaitBackgroundTasks(backgroundTasks: Set>) { - if (backgroundTasks.size === 0) return Promise.resolve(); + if (backgroundTasks.size === 0) { + return Promise.resolve(); + } return Promise.allSettled(backgroundTasks).then(() => { backgroundTasks.clear(); }); diff --git a/src/web/auto-reply/monitor/message-line.ts b/src/web/auto-reply/monitor/message-line.ts index eddfbd0393..1416d8424e 100644 --- a/src/web/auto-reply/monitor/message-line.ts +++ b/src/web/auto-reply/monitor/message-line.ts @@ -4,7 +4,9 @@ import type { loadConfig } from "../../../config/config.js"; import type { WebInboundMsg } from "../types.js"; export function formatReplyContext(msg: WebInboundMsg) { - if (!msg.replyToBody) return null; + if (!msg.replyToBody) { + return null; + } const sender = msg.replyToSender ?? "unknown sender"; const idPart = msg.replyToId ? ` id:${msg.replyToId}` : ""; return `[Replying to ${sender}${idPart}]\n${msg.replyToBody}\n[/Replying]`; diff --git a/src/web/auto-reply/monitor/on-message.ts b/src/web/auto-reply/monitor/on-message.ts index 7e260d49e4..93e68b6cf9 100644 --- a/src/web/auto-reply/monitor/on-message.ts +++ b/src/web/auto-reply/monitor/on-message.ts @@ -138,7 +138,9 @@ export function createWebOnMessageHandler(params: { logVerbose, replyLogger: params.replyLogger, }); - if (!gating.shouldProcess) return; + if (!gating.shouldProcess) { + return; + } } else { // Ensure `peerId` for DMs is stable and stored as E.164 when possible. if (!msg.senderE164 && peerId && peerId.startsWith("+")) { diff --git a/src/web/auto-reply/monitor/peer.ts b/src/web/auto-reply/monitor/peer.ts index 0ebd8ac666..b41555ffa2 100644 --- a/src/web/auto-reply/monitor/peer.ts +++ b/src/web/auto-reply/monitor/peer.ts @@ -2,8 +2,14 @@ import { jidToE164, normalizeE164 } from "../../../utils.js"; import type { WebInboundMsg } from "../types.js"; export function resolvePeerId(msg: WebInboundMsg) { - if (msg.chatType === "group") return msg.conversationId ?? msg.from; - if (msg.senderE164) return normalizeE164(msg.senderE164) ?? msg.senderE164; - if (msg.from.includes("@")) return jidToE164(msg.from) ?? msg.from; + if (msg.chatType === "group") { + return msg.conversationId ?? msg.from; + } + if (msg.senderE164) { + return normalizeE164(msg.senderE164) ?? msg.senderE164; + } + if (msg.from.includes("@")) { + return jidToE164(msg.from) ?? msg.from; + } return normalizeE164(msg.from) ?? msg.from; } diff --git a/src/web/auto-reply/monitor/process-message.ts b/src/web/auto-reply/monitor/process-message.ts index 27b638cfbc..d4eba4f3b1 100644 --- a/src/web/auto-reply/monitor/process-message.ts +++ b/src/web/auto-reply/monitor/process-message.ts @@ -60,13 +60,17 @@ async function resolveWhatsAppCommandAuthorized(params: { msg: WebInboundMsg; }): Promise { const useAccessGroups = params.cfg.commands?.useAccessGroups !== false; - if (!useAccessGroups) return true; + if (!useAccessGroups) { + return true; + } const isGroup = params.msg.chatType === "group"; const senderE164 = normalizeE164( isGroup ? (params.msg.senderE164 ?? "") : (params.msg.senderE164 ?? params.msg.from ?? ""), ); - if (!senderE164) return false; + if (!senderE164) { + return false; + } const configuredAllowFrom = params.cfg.channels?.whatsapp?.allowFrom ?? []; const configuredGroupAllowFrom = @@ -74,8 +78,12 @@ async function resolveWhatsAppCommandAuthorized(params: { (configuredAllowFrom.length > 0 ? configuredAllowFrom : undefined); if (isGroup) { - if (!configuredGroupAllowFrom || configuredGroupAllowFrom.length === 0) return false; - if (configuredGroupAllowFrom.some((v) => String(v).trim() === "*")) return true; + if (!configuredGroupAllowFrom || configuredGroupAllowFrom.length === 0) { + return false; + } + if (configuredGroupAllowFrom.some((v) => String(v).trim() === "*")) { + return true; + } return normalizeAllowFromE164(configuredGroupAllowFrom).includes(senderE164); } @@ -89,7 +97,9 @@ async function resolveWhatsAppCommandAuthorized(params: { : params.msg.selfE164 ? [params.msg.selfE164] : []; - if (allowFrom.some((v) => String(v).trim() === "*")) return true; + if (allowFrom.some((v) => String(v).trim() === "*")) { + return true; + } return normalizeAllowFromE164(allowFrom).includes(senderE164); } @@ -221,9 +231,13 @@ export async function processMessage(params: { const dmRouteTarget = params.msg.chatType !== "group" ? (() => { - if (params.msg.senderE164) return normalizeE164(params.msg.senderE164); + if (params.msg.senderE164) { + return normalizeE164(params.msg.senderE164); + } // In direct chats, `msg.from` is already the canonical conversation id. - if (params.msg.from.includes("@")) return jidToE164(params.msg.from); + if (params.msg.from.includes("@")) { + return jidToE164(params.msg.from); + } return normalizeE164(params.msg.from); })() : undefined; diff --git a/src/web/auto-reply/util.ts b/src/web/auto-reply/util.ts index e99492bb1d..8a00c77bf8 100644 --- a/src/web/auto-reply/util.ts +++ b/src/web/auto-reply/util.ts @@ -1,13 +1,21 @@ export function elide(text?: string, limit = 400) { - if (!text) return text; - if (text.length <= limit) return text; + if (!text) { + return text; + } + if (text.length <= limit) { + return text; + } return `${text.slice(0, limit)}… (truncated ${text.length - limit} chars)`; } export function isLikelyWhatsAppCryptoError(reason: unknown) { const formatReason = (value: unknown): string => { - if (value == null) return ""; - if (typeof value === "string") return value; + if (value == null) { + return ""; + } + if (typeof value === "string") { + return value; + } if (value instanceof Error) { return `${value.message}\n${value.stack ?? ""}`; } @@ -18,11 +26,21 @@ export function isLikelyWhatsAppCryptoError(reason: unknown) { return Object.prototype.toString.call(value); } } - if (typeof value === "number") return String(value); - if (typeof value === "boolean") return String(value); - if (typeof value === "bigint") return String(value); - if (typeof value === "symbol") return value.description ?? value.toString(); - if (typeof value === "function") return value.name ? `[function ${value.name}]` : "[function]"; + if (typeof value === "number") { + return String(value); + } + if (typeof value === "boolean") { + return String(value); + } + if (typeof value === "bigint") { + return String(value); + } + if (typeof value === "symbol") { + return value.description ?? value.toString(); + } + if (typeof value === "function") { + return value.name ? `[function ${value.name}]` : "[function]"; + } return Object.prototype.toString.call(value); }; const raw = @@ -31,7 +49,9 @@ export function isLikelyWhatsAppCryptoError(reason: unknown) { const hasAuthError = haystack.includes("unsupported state or unable to authenticate data") || haystack.includes("bad mac"); - if (!hasAuthError) return false; + if (!hasAuthError) { + return false; + } return ( haystack.includes("@whiskeysockets/baileys") || haystack.includes("baileys") || diff --git a/src/web/inbound.media.test.ts b/src/web/inbound.media.test.ts index 13474578a1..c45287a05c 100644 --- a/src/web/inbound.media.test.ts +++ b/src/web/inbound.media.test.ts @@ -128,7 +128,9 @@ describe("web inbound media saves with extension", () => { // Allow a brief window for the async handler to fire on slower hosts. for (let i = 0; i < 50; i++) { - if (onMessage.mock.calls.length > 0) break; + if (onMessage.mock.calls.length > 0) { + break; + } await new Promise((resolve) => setTimeout(resolve, 10)); } @@ -179,7 +181,9 @@ describe("web inbound media saves with extension", () => { realSock.ev.emit("messages.upsert", upsert); for (let i = 0; i < 50; i++) { - if (onMessage.mock.calls.length > 0) break; + if (onMessage.mock.calls.length > 0) { + break; + } await new Promise((resolve) => setTimeout(resolve, 10)); } @@ -219,7 +223,9 @@ describe("web inbound media saves with extension", () => { realSock.ev.emit("messages.upsert", upsert); for (let i = 0; i < 50; i++) { - if (onMessage.mock.calls.length > 0) break; + if (onMessage.mock.calls.length > 0) { + break; + } await new Promise((resolve) => setTimeout(resolve, 10)); } diff --git a/src/web/inbound/extract.ts b/src/web/inbound/extract.ts index d794415f69..2cd9b8eb38 100644 --- a/src/web/inbound/extract.ts +++ b/src/web/inbound/extract.ts @@ -15,14 +15,18 @@ function unwrapMessage(message: proto.IMessage | undefined): proto.IMessage | un } function extractContextInfo(message: proto.IMessage | undefined): proto.IContextInfo | undefined { - if (!message) return undefined; + if (!message) { + return undefined; + } const contentType = getContentType(message); const candidate = contentType ? (message as Record)[contentType] : undefined; const contextInfo = candidate && typeof candidate === "object" && "contextInfo" in candidate ? (candidate as { contextInfo?: proto.IContextInfo }).contextInfo : undefined; - if (contextInfo) return contextInfo; + if (contextInfo) { + return contextInfo; + } const fallback = message.extendedTextMessage?.contextInfo ?? message.imageMessage?.contextInfo ?? @@ -36,19 +40,29 @@ function extractContextInfo(message: proto.IMessage | undefined): proto.IContext message.interactiveResponseMessage?.contextInfo ?? message.buttonsMessage?.contextInfo ?? message.listMessage?.contextInfo; - if (fallback) return fallback; + if (fallback) { + return fallback; + } for (const value of Object.values(message)) { - if (!value || typeof value !== "object") continue; - if (!("contextInfo" in value)) continue; + if (!value || typeof value !== "object") { + continue; + } + if (!("contextInfo" in value)) { + continue; + } const candidateContext = (value as { contextInfo?: proto.IContextInfo }).contextInfo; - if (candidateContext) return candidateContext; + if (candidateContext) { + return candidateContext; + } } return undefined; } export function extractMentionedJids(rawMessage: proto.IMessage | undefined): string[] | undefined { const message = unwrapMessage(rawMessage); - if (!message) return undefined; + if (!message) { + return undefined; + } const candidates: Array = [ message.extendedTextMessage?.contextInfo?.mentionedJid, @@ -64,34 +78,46 @@ export function extractMentionedJids(rawMessage: proto.IMessage | undefined): st ]; const flattened = candidates.flatMap((arr) => arr ?? []).filter(Boolean); - if (flattened.length === 0) return undefined; + if (flattened.length === 0) { + return undefined; + } return Array.from(new Set(flattened)); } export function extractText(rawMessage: proto.IMessage | undefined): string | undefined { const message = unwrapMessage(rawMessage); - if (!message) return undefined; + if (!message) { + return undefined; + } const extracted = extractMessageContent(message); const candidates = [message, extracted && extracted !== message ? extracted : undefined]; for (const candidate of candidates) { - if (!candidate) continue; + if (!candidate) { + continue; + } if (typeof candidate.conversation === "string" && candidate.conversation.trim()) { return candidate.conversation.trim(); } const extended = candidate.extendedTextMessage?.text; - if (extended?.trim()) return extended.trim(); + if (extended?.trim()) { + return extended.trim(); + } const caption = candidate.imageMessage?.caption ?? candidate.videoMessage?.caption ?? candidate.documentMessage?.caption; - if (caption?.trim()) return caption.trim(); + if (caption?.trim()) { + return caption.trim(); + } } const contactPlaceholder = extractContactPlaceholder(message) ?? (extracted && extracted !== message ? extractContactPlaceholder(extracted as proto.IMessage | undefined) : undefined); - if (contactPlaceholder) return contactPlaceholder; + if (contactPlaceholder) { + return contactPlaceholder; + } return undefined; } @@ -99,18 +125,32 @@ export function extractMediaPlaceholder( rawMessage: proto.IMessage | undefined, ): string | undefined { const message = unwrapMessage(rawMessage); - if (!message) return undefined; - if (message.imageMessage) return ""; - if (message.videoMessage) return ""; - if (message.audioMessage) return ""; - if (message.documentMessage) return ""; - if (message.stickerMessage) return ""; + if (!message) { + return undefined; + } + if (message.imageMessage) { + return ""; + } + if (message.videoMessage) { + return ""; + } + if (message.audioMessage) { + return ""; + } + if (message.documentMessage) { + return ""; + } + if (message.stickerMessage) { + return ""; + } return undefined; } function extractContactPlaceholder(rawMessage: proto.IMessage | undefined): string | undefined { const message = unwrapMessage(rawMessage); - if (!message) return undefined; + if (!message) { + return undefined; + } const contact = message.contactMessage ?? undefined; if (contact) { const { name, phones } = describeContact({ @@ -120,7 +160,9 @@ function extractContactPlaceholder(rawMessage: proto.IMessage | undefined): stri return formatContactPlaceholder(name, phones); } const contactsArray = message.contactsArrayMessage?.contacts ?? undefined; - if (!contactsArray || contactsArray.length === 0) return undefined; + if (!contactsArray || contactsArray.length === 0) { + return undefined; + } const labels = contactsArray .map((entry) => describeContact({ displayName: entry.displayName, vcard: entry.vcard })) .map((entry) => formatContactLabel(entry.name, entry.phones)) @@ -140,7 +182,9 @@ function describeContact(input: { displayName?: string | null; vcard?: string | function formatContactPlaceholder(name?: string, phones?: string[]): string { const label = formatContactLabel(name, phones); - if (!label) return ""; + if (!label) { + return ""; + } return ``; } @@ -158,17 +202,25 @@ function formatContactsPlaceholder(labels: string[], total: number): string { function formatContactLabel(name?: string, phones?: string[]): string | undefined { const phoneLabel = formatPhoneList(phones); const parts = [name, phoneLabel].filter((value): value is string => Boolean(value)); - if (parts.length === 0) return undefined; + if (parts.length === 0) { + return undefined; + } return parts.join(", "); } function formatPhoneList(phones?: string[]): string | undefined { const cleaned = phones?.map((phone) => phone.trim()).filter(Boolean) ?? []; - if (cleaned.length === 0) return undefined; + if (cleaned.length === 0) { + return undefined; + } const { shown, remaining } = summarizeList(cleaned, cleaned.length, 1); const [primary] = shown; - if (!primary) return undefined; - if (remaining === 0) return primary; + if (!primary) { + return undefined; + } + if (remaining === 0) { + return primary; + } return `${primary} (+${remaining} more)`; } @@ -186,7 +238,9 @@ export function extractLocationData( rawMessage: proto.IMessage | undefined, ): NormalizedLocation | null { const message = unwrapMessage(rawMessage); - if (!message) return null; + if (!message) { + return null; + } const live = message.liveLocationMessage ?? undefined; if (live) { @@ -242,15 +296,21 @@ export function describeReplyContext(rawMessage: proto.IMessage | undefined): { senderE164?: string; } | null { const message = unwrapMessage(rawMessage); - if (!message) return null; + if (!message) { + return null; + } const contextInfo = extractContextInfo(message); const quoted = normalizeMessageContent(contextInfo?.quotedMessage as proto.IMessage | undefined); - if (!quoted) return null; + if (!quoted) { + return null; + } const location = extractLocationData(quoted); const locationText = location ? formatLocationText(location) : undefined; const text = extractText(quoted); let body: string | undefined = [text, locationText].filter(Boolean).join("\n").trim(); - if (!body) body = extractMediaPlaceholder(quoted); + if (!body) { + body = extractMediaPlaceholder(quoted); + } if (!body) { const quotedType = quoted ? getContentType(quoted) : undefined; logVerbose( diff --git a/src/web/inbound/media.ts b/src/web/inbound/media.ts index 952f2755da..a44c82c6ab 100644 --- a/src/web/inbound/media.ts +++ b/src/web/inbound/media.ts @@ -13,7 +13,9 @@ export async function downloadInboundMedia( sock: Awaited>, ): Promise<{ buffer: Buffer; mimetype?: string } | undefined> { const message = unwrapMessage(msg.message as proto.IMessage | undefined); - if (!message) return undefined; + if (!message) { + return undefined; + } const mimetype = message.imageMessage?.mimetype ?? message.videoMessage?.mimetype ?? diff --git a/src/web/inbound/monitor.ts b/src/web/inbound/monitor.ts index 3633cbce90..b58782c0cb 100644 --- a/src/web/inbound/monitor.ts +++ b/src/web/inbound/monitor.ts @@ -48,7 +48,9 @@ export async function monitorWebInbox(options: { onCloseResolve = resolve; }); const resolveClose = (reason: WebListenerCloseReason) => { - if (!onCloseResolve) return; + if (!onCloseResolve) { + return; + } const resolver = onCloseResolve; onCloseResolve = null; resolver(reason); @@ -56,7 +58,9 @@ export async function monitorWebInbox(options: { try { await sock.sendPresenceUpdate("available"); - if (shouldLogVerbose()) logVerbose("Sent global 'available' presence on connect"); + if (shouldLogVerbose()) { + logVerbose("Sent global 'available' presence on connect"); + } } catch (err) { logVerbose(`Failed to send 'available' presence on connect: ${String(err)}`); } @@ -70,21 +74,27 @@ export async function monitorWebInbox(options: { msg.chatType === "group" ? (msg.senderJid ?? msg.senderE164 ?? msg.senderName ?? msg.from) : msg.from; - if (!senderKey) return null; + if (!senderKey) { + return null; + } const conversationKey = msg.chatType === "group" ? msg.chatId : msg.from; return `${msg.accountId}:${conversationKey}:${senderKey}`; }, shouldDebounce: options.shouldDebounce, onFlush: async (entries) => { const last = entries.at(-1); - if (!last) return; + if (!last) { + return; + } if (entries.length === 1) { await options.onMessage(last); return; } const mentioned = new Set(); for (const entry of entries) { - for (const jid of entry.mentionedJids ?? []) mentioned.add(jid); + for (const jid of entry.mentionedJids ?? []) { + mentioned.add(jid); + } } const combinedBody = entries .map((entry) => entry.body) @@ -114,7 +124,9 @@ export async function monitorWebInbox(options: { const getGroupMeta = async (jid: string) => { const cached = groupMetaCache.get(jid); - if (cached && cached.expires > Date.now()) return cached; + if (cached && cached.expires > Date.now()) { + return cached; + } try { const meta = await sock.groupMetadata(jid); const participants = @@ -140,7 +152,9 @@ export async function monitorWebInbox(options: { }; const handleMessagesUpsert = async (upsert: { type?: string; messages?: Array }) => { - if (upsert.type !== "notify" && upsert.type !== "append") return; + if (upsert.type !== "notify" && upsert.type !== "append") { + return; + } for (const msg of upsert.messages ?? []) { recordChannelActivity({ channel: "whatsapp", @@ -149,17 +163,25 @@ export async function monitorWebInbox(options: { }); const id = msg.key?.id ?? undefined; const remoteJid = msg.key?.remoteJid; - if (!remoteJid) continue; - if (remoteJid.endsWith("@status") || remoteJid.endsWith("@broadcast")) continue; + if (!remoteJid) { + continue; + } + if (remoteJid.endsWith("@status") || remoteJid.endsWith("@broadcast")) { + continue; + } const group = isJidGroup(remoteJid) === true; if (id) { const dedupeKey = `${options.accountId}:${remoteJid}:${id}`; - if (isRecentInboundMessage(dedupeKey)) continue; + if (isRecentInboundMessage(dedupeKey)) { + continue; + } } const participantJid = msg.key?.participant ?? undefined; const from = group ? remoteJid : await resolveInboundJid(remoteJid); - if (!from) continue; + if (!from) { + continue; + } const senderE164 = group ? participantJid ? await resolveInboundJid(participantJid) @@ -190,7 +212,9 @@ export async function monitorWebInbox(options: { sock: { sendMessage: (jid, content) => sock.sendMessage(jid, content) }, remoteJid, }); - if (!access.allowed) continue; + if (!access.allowed) { + continue; + } if (id && !access.isSelfChat && options.sendReadReceipts !== false) { const participant = msg.key?.participant; @@ -209,7 +233,9 @@ export async function monitorWebInbox(options: { } // If this is history/offline catch-up, mark read above but skip auto-reply. - if (upsert.type === "append") continue; + if (upsert.type === "append") { + continue; + } const location = extractLocationData(msg.message ?? undefined); const locationText = location ? formatLocationText(location) : undefined; @@ -219,7 +245,9 @@ export async function monitorWebInbox(options: { } if (!body) { body = extractMediaPlaceholder(msg.message ?? undefined); - if (!body) continue; + if (!body) { + continue; + } } const replyContext = describeReplyContext(msg.message as proto.IMessage | undefined); diff --git a/src/web/login-qr.ts b/src/web/login-qr.ts index aa3334edb0..c1d879e9fd 100644 --- a/src/web/login-qr.ts +++ b/src/web/login-qr.ts @@ -66,18 +66,24 @@ function attachLoginWaiter(accountId: string, login: ActiveLogin) { login.waitPromise = waitForWaConnection(login.sock) .then(() => { const current = activeLogins.get(accountId); - if (current?.id === login.id) current.connected = true; + if (current?.id === login.id) { + current.connected = true; + } }) .catch((err) => { const current = activeLogins.get(accountId); - if (current?.id !== login.id) return; + if (current?.id !== login.id) { + return; + } current.error = formatError(err); current.errorStatus = getStatusCode(err); }); } async function restartLoginSocket(login: ActiveLogin, runtime: RuntimeEnv) { - if (login.restartAttempted) return false; + if (login.restartAttempted) { + return false; + } login.restartAttempted = true; runtime.log( info("WhatsApp asked for a restart after pairing (code 515); retrying connection once…"), @@ -151,10 +157,14 @@ export async function startWebLoginWithQr( sock = await createWaSocket(false, Boolean(opts.verbose), { authDir: account.authDir, onQr: (qr: string) => { - if (pendingQr) return; + if (pendingQr) { + return; + } pendingQr = qr; const current = activeLogins.get(account.accountId); - if (current && !current.qr) current.qr = qr; + if (current && !current.qr) { + current.qr = qr; + } clearTimeout(qrTimer); runtime.log(info("WhatsApp QR received.")); resolveQr?.(qr); @@ -180,7 +190,9 @@ export async function startWebLoginWithQr( verbose: Boolean(opts.verbose), }; activeLogins.set(account.accountId, login); - if (pendingQr && !login.qr) login.qr = pendingQr; + if (pendingQr && !login.qr) { + login.qr = pendingQr; + } attachLoginWaiter(account.accountId, login); let qr: string; diff --git a/src/web/media.test.ts b/src/web/media.test.ts index b14c3778df..0a47deecd6 100644 --- a/src/web/media.test.ts +++ b/src/web/media.test.ts @@ -118,7 +118,9 @@ describe("web media loading", () => { if (name === "content-disposition") { return 'attachment; filename="report.pdf"'; } - if (name === "content-type") return "application/pdf"; + if (name === "content-type") { + return "application/pdf"; + } return null; }, }, diff --git a/src/web/media.ts b/src/web/media.ts index a48f0d5d0b..7daa4e355a 100644 --- a/src/web/media.ts +++ b/src/web/media.ts @@ -43,15 +43,23 @@ function formatCapReduce(label: string, cap: number, size: number): string { } function isHeicSource(opts: { contentType?: string; fileName?: string }): boolean { - if (opts.contentType && HEIC_MIME_RE.test(opts.contentType.trim())) return true; - if (opts.fileName && HEIC_EXT_RE.test(opts.fileName.trim())) return true; + if (opts.contentType && HEIC_MIME_RE.test(opts.contentType.trim())) { + return true; + } + if (opts.fileName && HEIC_EXT_RE.test(opts.fileName.trim())) { + return true; + } return false; } function toJpegFileName(fileName?: string): string | undefined { - if (!fileName) return undefined; + if (!fileName) { + return undefined; + } const trimmed = fileName.trim(); - if (!trimmed) return fileName; + if (!trimmed) { + return fileName; + } const parsed = path.parse(trimmed); if (!parsed.ext || HEIC_EXT_RE.test(parsed.ext)) { return path.format({ dir: parsed.dir, name: parsed.name || trimmed, ext: ".jpg" }); @@ -69,8 +77,12 @@ type OptimizedImage = { }; function logOptimizedImage(params: { originalSize: number; optimized: OptimizedImage }): void { - if (!shouldLogVerbose()) return; - if (params.optimized.optimizedSize >= params.originalSize) return; + if (!shouldLogVerbose()) { + return; + } + if (params.optimized.optimizedSize >= params.originalSize) { + return; + } if (params.optimized.format === "png") { logVerbose( `Optimized PNG (preserving alpha) from ${formatMb(params.originalSize)}MB to ${formatMb(params.optimized.optimizedSize)}MB (side≤${params.optimized.resizeSide}px)`, @@ -207,7 +219,9 @@ async function loadWebMediaInternal( let fileName = path.basename(mediaUrl) || undefined; if (fileName && !path.extname(fileName) && mime) { const ext = extensionForMime(mime); - if (ext) fileName = `${fileName}${ext}`; + if (ext) { + fileName = `${fileName}${ext}`; + } } return await clampAndFinalize({ buffer: data, diff --git a/src/web/qr-image.ts b/src/web/qr-image.ts index 82f60eac13..e60b0be67d 100644 --- a/src/web/qr-image.ts +++ b/src/web/qr-image.ts @@ -111,7 +111,9 @@ export async function renderQrPngBase64( const buf = Buffer.alloc(size * size * 4, 255); for (let row = 0; row < modules; row += 1) { for (let col = 0; col < modules; col += 1) { - if (!qr.isDark(row, col)) continue; + if (!qr.isDark(row, col)) { + continue; + } const startX = (col + marginModules) * scale; const startY = (row + marginModules) * scale; for (let y = 0; y < scale; y += 1) { diff --git a/src/web/reconnect.ts b/src/web/reconnect.ts index 86d19f7772..52982940c2 100644 --- a/src/web/reconnect.ts +++ b/src/web/reconnect.ts @@ -21,7 +21,9 @@ const clamp = (val: number, min: number, max: number) => Math.max(min, Math.min( export function resolveHeartbeatSeconds(cfg: OpenClawConfig, overrideSeconds?: number): number { const candidate = overrideSeconds ?? cfg.web?.heartbeatSeconds; - if (typeof candidate === "number" && candidate > 0) return candidate; + if (typeof candidate === "number" && candidate > 0) { + return candidate; + } return DEFAULT_HEARTBEAT_SECONDS; } diff --git a/src/web/session.test.ts b/src/web/session.test.ts index 3279e045ab..8092fbd2d3 100644 --- a/src/web/session.test.ts +++ b/src/web/session.test.ts @@ -62,7 +62,9 @@ describe("web session", () => { it("logWebSelfId prints cached E.164 when creds exist", () => { const existsSpy = vi.spyOn(fsSync, "existsSync").mockImplementation((p) => { - if (typeof p !== "string") return false; + if (typeof p !== "string") { + return false; + } return p.endsWith("creds.json"); }); const readSpy = vi.spyOn(fsSync, "readFileSync").mockImplementation((p) => { @@ -110,7 +112,9 @@ describe("web session", () => { const copySpy = vi.spyOn(fsSync, "copyFileSync").mockImplementation(() => {}); const existsSpy = vi.spyOn(fsSync, "existsSync").mockImplementation((p) => { - if (typeof p !== "string") return false; + if (typeof p !== "string") { + return false; + } return p.endsWith(credsSuffix); }); const statSpy = vi.spyOn(fsSync, "statSync").mockImplementation((p) => { @@ -193,7 +197,9 @@ describe("web session", () => { const copySpy = vi.spyOn(fsSync, "copyFileSync").mockImplementation(() => {}); const existsSpy = vi.spyOn(fsSync, "existsSync").mockImplementation((p) => { - if (typeof p !== "string") return false; + if (typeof p !== "string") { + return false; + } return p.endsWith(credsSuffix); }); const statSpy = vi.spyOn(fsSync, "statSync").mockImplementation((p) => { diff --git a/src/web/session.ts b/src/web/session.ts index 0c8f76b728..44912158d7 100644 --- a/src/web/session.ts +++ b/src/web/session.ts @@ -46,9 +46,13 @@ function enqueueSaveCreds( function readCredsJsonRaw(filePath: string): string | null { try { - if (!fsSync.existsSync(filePath)) return null; + if (!fsSync.existsSync(filePath)) { + return null; + } const stats = fsSync.statSync(filePath); - if (!stats.isFile() || stats.size <= 1) return null; + if (!stats.isFile() || stats.size <= 1) { + return null; + } return fsSync.readFileSync(filePath, "utf-8"); } catch { return null; @@ -197,7 +201,9 @@ function safeStringify(value: unknown, limit = 800): string { const raw = JSON.stringify( value, (_key, v) => { - if (typeof v === "bigint") return v.toString(); + if (typeof v === "bigint") { + return v.toString(); + } if (typeof v === "function") { const maybeName = (v as { name?: unknown }).name; const name = @@ -205,14 +211,18 @@ function safeStringify(value: unknown, limit = 800): string { return `[Function ${name}]`; } if (typeof v === "object" && v) { - if (seen.has(v)) return "[Circular]"; + if (seen.has(v)) { + return "[Circular]"; + } seen.add(v); } return v; }, 2, ); - if (!raw) return String(value); + if (!raw) { + return String(value); + } return raw.length > limit ? `${raw.slice(0, limit)}…` : raw; } catch { return String(value); @@ -224,11 +234,15 @@ function extractBoomDetails(err: unknown): { error?: string; message?: string; } | null { - if (!err || typeof err !== "object") return null; + if (!err || typeof err !== "object") { + return null; + } const output = (err as { output?: unknown })?.output as | { statusCode?: unknown; payload?: unknown } | undefined; - if (!output || typeof output !== "object") return null; + if (!output || typeof output !== "object") { + return null; + } const payload = (output as { payload?: unknown }).payload as | { error?: unknown; message?: unknown; statusCode?: unknown } | undefined; @@ -240,14 +254,22 @@ function extractBoomDetails(err: unknown): { : undefined; const error = typeof payload?.error === "string" ? payload.error : undefined; const message = typeof payload?.message === "string" ? payload.message : undefined; - if (!statusCode && !error && !message) return null; + if (!statusCode && !error && !message) { + return null; + } return { statusCode, error, message }; } export function formatError(err: unknown): string { - if (err instanceof Error) return err.message; - if (typeof err === "string") return err; - if (!err || typeof err !== "object") return String(err); + if (err instanceof Error) { + return err.message; + } + if (typeof err === "string") { + return err; + } + if (!err || typeof err !== "object") { + return String(err); + } // Baileys frequently wraps errors under `error` with a Boom-like shape. const boom = @@ -271,12 +293,22 @@ export function formatError(err: unknown): string { const message = messageCandidates[0]; const pieces: string[] = []; - if (typeof status === "number") pieces.push(`status=${status}`); - if (boom?.error) pieces.push(boom.error); - if (message) pieces.push(message); - if (codeText) pieces.push(`code=${codeText}`); + if (typeof status === "number") { + pieces.push(`status=${status}`); + } + if (boom?.error) { + pieces.push(boom.error); + } + if (message) { + pieces.push(message); + } + if (codeText) { + pieces.push(`code=${codeText}`); + } - if (pieces.length > 0) return pieces.join(" "); + if (pieces.length > 0) { + return pieces.join(" "); + } return safeStringify(err); } diff --git a/src/web/test-helpers.ts b/src/web/test-helpers.ts index 4e66f6475d..2738e68a5b 100644 --- a/src/web/test-helpers.ts +++ b/src/web/test-helpers.ts @@ -37,7 +37,9 @@ vi.mock("../config/config.js", async (importOriginal) => { ...actual, loadConfig: () => { const getter = (globalThis as Record)[CONFIG_KEY]; - if (typeof getter === "function") return getter(); + if (typeof getter === "function") { + return getter(); + } return DEFAULT_CONFIG; }, }; @@ -84,7 +86,11 @@ export function resetBaileysMocks() { export function getLastSocket(): MockBaileysSocket { const getter = (globalThis as Record)[Symbol.for("openclaw:lastSocket")]; - if (typeof getter === "function") return (getter as () => MockBaileysSocket)(); - if (!getter) throw new Error("Baileys mock not initialized"); + if (typeof getter === "function") { + return (getter as () => MockBaileysSocket)(); + } + if (!getter) { + throw new Error("Baileys mock not initialized"); + } throw new Error("Invalid Baileys socket getter"); } diff --git a/src/web/vcard.ts b/src/web/vcard.ts index 9b586a8fc4..9f729f4d65 100644 --- a/src/web/vcard.ts +++ b/src/web/vcard.ts @@ -6,23 +6,35 @@ type ParsedVcard = { const ALLOWED_VCARD_KEYS = new Set(["FN", "N", "TEL"]); export function parseVcard(vcard?: string): ParsedVcard { - if (!vcard) return { phones: [] }; + if (!vcard) { + return { phones: [] }; + } const lines = vcard.split(/\r?\n/); let nameFromN: string | undefined; let nameFromFn: string | undefined; const phones: string[] = []; for (const rawLine of lines) { const line = rawLine.trim(); - if (!line) continue; + if (!line) { + continue; + } const colonIndex = line.indexOf(":"); - if (colonIndex === -1) continue; + if (colonIndex === -1) { + continue; + } const key = line.slice(0, colonIndex).toUpperCase(); const rawValue = line.slice(colonIndex + 1).trim(); - if (!rawValue) continue; + if (!rawValue) { + continue; + } const baseKey = normalizeVcardKey(key); - if (!baseKey || !ALLOWED_VCARD_KEYS.has(baseKey)) continue; + if (!baseKey || !ALLOWED_VCARD_KEYS.has(baseKey)) { + continue; + } const value = cleanVcardValue(rawValue); - if (!value) continue; + if (!value) { + continue; + } if (baseKey === "FN" && !nameFromFn) { nameFromFn = normalizeVcardName(value); continue; @@ -33,7 +45,9 @@ export function parseVcard(vcard?: string): ParsedVcard { } if (baseKey === "TEL") { const phone = normalizeVcardPhone(value); - if (phone) phones.push(phone); + if (phone) { + phones.push(phone); + } } } return { name: nameFromFn ?? nameFromN, phones }; @@ -41,7 +55,9 @@ export function parseVcard(vcard?: string): ParsedVcard { function normalizeVcardKey(key: string): string | undefined { const [primary] = key.split(";"); - if (!primary) return undefined; + if (!primary) { + return undefined; + } const segments = primary.split("."); return segments[segments.length - 1] || undefined; } @@ -56,7 +72,9 @@ function normalizeVcardName(value: string): string { function normalizeVcardPhone(value: string): string { const trimmed = value.trim(); - if (!trimmed) return ""; + if (!trimmed) { + return ""; + } if (trimmed.toLowerCase().startsWith("tel:")) { return trimmed.slice(4).trim(); } diff --git a/src/whatsapp/normalize.ts b/src/whatsapp/normalize.ts index e5ddb09524..1d661ddc75 100644 --- a/src/whatsapp/normalize.ts +++ b/src/whatsapp/normalize.ts @@ -8,16 +8,22 @@ function stripWhatsAppTargetPrefixes(value: string): string { for (;;) { const before = candidate; candidate = candidate.replace(/^whatsapp:/i, "").trim(); - if (candidate === before) return candidate; + if (candidate === before) { + return candidate; + } } } export function isWhatsAppGroupJid(value: string): boolean { const candidate = stripWhatsAppTargetPrefixes(value); const lower = candidate.toLowerCase(); - if (!lower.endsWith("@g.us")) return false; + if (!lower.endsWith("@g.us")) { + return false; + } const localPart = candidate.slice(0, candidate.length - "@g.us".length); - if (!localPart || localPart.includes("@")) return false; + if (!localPart || localPart.includes("@")) { + return false; + } return /^[0-9]+(-[0-9]+)*$/.test(localPart); } @@ -36,15 +42,21 @@ export function isWhatsAppUserTarget(value: string): boolean { */ function extractUserJidPhone(jid: string): string | null { const userMatch = jid.match(WHATSAPP_USER_JID_RE); - if (userMatch) return userMatch[1]; + if (userMatch) { + return userMatch[1]; + } const lidMatch = jid.match(WHATSAPP_LID_RE); - if (lidMatch) return lidMatch[1]; + if (lidMatch) { + return lidMatch[1]; + } return null; } export function normalizeWhatsAppTarget(value: string): string | null { const candidate = stripWhatsAppTargetPrefixes(value); - if (!candidate) return null; + if (!candidate) { + return null; + } if (isWhatsAppGroupJid(candidate)) { const localPart = candidate.slice(0, candidate.length - "@g.us".length); return `${localPart}@g.us`; @@ -52,13 +64,17 @@ export function normalizeWhatsAppTarget(value: string): string | null { // Handle user JIDs (e.g. "41796666864:0@s.whatsapp.net") if (isWhatsAppUserTarget(candidate)) { const phone = extractUserJidPhone(candidate); - if (!phone) return null; + if (!phone) { + return null; + } const normalized = normalizeE164(phone); return normalized.length > 1 ? normalized : null; } // If the caller passed a JID-ish string that we don't understand, fail fast. // Otherwise normalizeE164 would happily treat "group:120@g.us" as a phone number. - if (candidate.includes("@")) return null; + if (candidate.includes("@")) { + return null; + } const normalized = normalizeE164(candidate); return normalized.length > 1 ? normalized : null; } diff --git a/src/wizard/onboarding.gateway-config.ts b/src/wizard/onboarding.gateway-config.ts index 2870f9e24a..6b3e57c44d 100644 --- a/src/wizard/onboarding.gateway-config.ts +++ b/src/wizard/onboarding.gateway-config.ts @@ -68,17 +68,22 @@ export async function configureGatewayForOnboarding( placeholder: "192.168.1.100", initialValue: customBindHost ?? "", validate: (value) => { - if (!value) return "IP address is required for custom bind mode"; + if (!value) { + return "IP address is required for custom bind mode"; + } const trimmed = value.trim(); const parts = trimmed.split("."); - if (parts.length !== 4) return "Invalid IPv4 address (e.g., 192.168.1.100)"; + if (parts.length !== 4) { + return "Invalid IPv4 address (e.g., 192.168.1.100)"; + } if ( parts.every((part) => { const n = parseInt(part, 10); return !Number.isNaN(n) && n >= 0 && n <= 255 && part === String(n); }) - ) + ) { return undefined; + } return "Invalid IPv4 address (each octet must be 0-255)"; }, }); diff --git a/src/wizard/onboarding.test.ts b/src/wizard/onboarding.test.ts index 3756335b63..5c739c2141 100644 --- a/src/wizard/onboarding.test.ts +++ b/src/wizard/onboarding.test.ts @@ -178,7 +178,9 @@ describe("runOnboardingWizard", () => { await fs.writeFile(path.join(workspaceDir, DEFAULT_BOOTSTRAP_FILENAME), "{}"); const select: WizardPrompter["select"] = vi.fn(async (opts) => { - if (opts.message === "How do you want to hatch your bot?") return "tui"; + if (opts.message === "How do you want to hatch your bot?") { + return "tui"; + } return "quickstart"; }); @@ -233,7 +235,9 @@ describe("runOnboardingWizard", () => { const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-onboard-")); const select: WizardPrompter["select"] = vi.fn(async (opts) => { - if (opts.message === "How do you want to hatch your bot?") return "tui"; + if (opts.message === "How do you want to hatch your bot?") { + return "tui"; + } return "quickstart"; }); diff --git a/src/wizard/onboarding.ts b/src/wizard/onboarding.ts index 5fa0131ae5..dbabaf7689 100644 --- a/src/wizard/onboarding.ts +++ b/src/wizard/onboarding.ts @@ -48,7 +48,9 @@ async function requireRiskAcknowledgement(params: { opts: OnboardOptions; prompter: WizardPrompter; }) { - if (params.opts.acceptRisk === true) return; + if (params.opts.acceptRisk === true) { + return; + } await params.prompter.note( [ @@ -238,19 +240,33 @@ export async function runOnboardingWizard( if (flow === "quickstart") { const formatBind = (value: "loopback" | "lan" | "auto" | "custom" | "tailnet") => { - if (value === "loopback") return "Loopback (127.0.0.1)"; - if (value === "lan") return "LAN"; - if (value === "custom") return "Custom IP"; - if (value === "tailnet") return "Tailnet (Tailscale IP)"; + if (value === "loopback") { + return "Loopback (127.0.0.1)"; + } + if (value === "lan") { + return "LAN"; + } + if (value === "custom") { + return "Custom IP"; + } + if (value === "tailnet") { + return "Tailnet (Tailscale IP)"; + } return "Auto"; }; const formatAuth = (value: GatewayAuthChoice) => { - if (value === "token") return "Token (default)"; + if (value === "token") { + return "Token (default)"; + } return "Password"; }; const formatTailscale = (value: "off" | "serve" | "funnel") => { - if (value === "off") return "Off"; - if (value === "serve") return "Serve"; + if (value === "off") { + return "Off"; + } + if (value === "serve") { + return "Serve"; + } return "Funnel"; }; const quickstartLines = quickstartGateway.hasExisting diff --git a/src/wizard/session.test.ts b/src/wizard/session.test.ts index a2c92c7326..da8da0fd91 100644 --- a/src/wizard/session.test.ts +++ b/src/wizard/session.test.ts @@ -21,20 +21,26 @@ describe("WizardSession", () => { const secondPeek = await session.next(); expect(secondPeek.step?.id).toBe(first.step?.id); - if (!first.step) throw new Error("expected first step"); + if (!first.step) { + throw new Error("expected first step"); + } await session.answer(first.step.id, null); const second = await session.next(); expect(second.done).toBe(false); expect(second.step?.type).toBe("text"); - if (!second.step) throw new Error("expected second step"); + if (!second.step) { + throw new Error("expected second step"); + } await session.answer(second.step.id, "Peter"); const third = await session.next(); expect(third.step?.type).toBe("note"); - if (!third.step) throw new Error("expected third step"); + if (!third.step) { + throw new Error("expected third step"); + } await session.answer(third.step.id, null); const done = await session.next(); @@ -46,7 +52,9 @@ describe("WizardSession", () => { const session = noteRunner(); const first = await session.next(); await expect(session.answer("bad-id", null)).rejects.toThrow(/wizard: no pending step/i); - if (!first.step) throw new Error("expected first step"); + if (!first.step) { + throw new Error("expected first step"); + } await session.answer(first.step.id, null); }); diff --git a/src/wizard/session.ts b/src/wizard/session.ts index 358907668e..63a1a84558 100644 --- a/src/wizard/session.ts +++ b/src/wizard/session.ts @@ -201,7 +201,9 @@ export class WizardSession { } cancel() { - if (this.status !== "running") return; + if (this.status !== "running") { + return; + } this.status = "cancelled"; this.error = "cancelled"; this.currentStep = null; @@ -245,7 +247,9 @@ export class WizardSession { } private resolveStep(step: WizardStep | null) { - if (!this.stepDeferred) return; + if (!this.stepDeferred) { + return; + } const deferred = this.stepDeferred; this.stepDeferred = null; deferred.resolve(step); diff --git a/test/fixtures/child-process-bridge/child.js b/test/fixtures/child-process-bridge/child.js index 3c24f2172c..9ef083e42b 100644 --- a/test/fixtures/child-process-bridge/child.js +++ b/test/fixtures/child-process-bridge/child.js @@ -7,7 +7,9 @@ const server = http.createServer((_, res) => { server.listen(0, "127.0.0.1", () => { const addr = server.address(); - if (!addr || typeof addr === "string") throw new Error("unexpected address"); + if (!addr || typeof addr === "string") { + throw new Error("unexpected address"); + } process.stdout.write(`${addr.port}\n`); }); diff --git a/test/gateway.multi.e2e.test.ts b/test/gateway.multi.e2e.test.ts index 6be021179e..8829ea300d 100644 --- a/test/gateway.multi.e2e.test.ts +++ b/test/gateway.multi.e2e.test.ts @@ -181,7 +181,9 @@ const stopGatewayInstance = async (inst: GatewayInstance) => { } const exited = await Promise.race([ new Promise((resolve) => { - if (inst.child.exitCode !== null) return resolve(true); + if (inst.child.exitCode !== null) { + return resolve(true); + } inst.child.once("exit", () => resolve(true)); }), sleep(5_000).then(() => false), @@ -299,17 +301,23 @@ const connectNode = async ( commands: ["system.run"], deviceIdentity, onHelloOk: () => { - if (settled) return; + if (settled) { + return; + } settled = true; resolveReady?.(); }, onConnectError: (err) => { - if (settled) return; + if (settled) { + return; + } settled = true; rejectReady?.(err); }, onClose: (code, reason) => { - if (settled) return; + if (settled) { + return; + } settled = true; rejectReady?.(new Error(`gateway closed (${code}): ${reason}`)); }, @@ -341,7 +349,9 @@ const waitForNodeStatus = async (inst: GatewayInstance, nodeId: string, timeoutM }, )) as NodeListPayload; const match = list.nodes?.find((n) => n.nodeId === nodeId); - if (match?.connected && match?.paired) return; + if (match?.connected && match?.paired) { + return; + } await sleep(50); } throw new Error(`timeout waiting for node status for ${nodeId}`); diff --git a/test/helpers/envelope-timestamp.ts b/test/helpers/envelope-timestamp.ts index d630af07a5..1bd7ad17ce 100644 --- a/test/helpers/envelope-timestamp.ts +++ b/test/helpers/envelope-timestamp.ts @@ -41,8 +41,12 @@ function formatZonedTimestamp(date: Date, timeZone?: string): string { export function formatEnvelopeTimestamp(date: Date, zone: EnvelopeTimestampZone = "utc"): string { const normalized = zone.trim().toLowerCase(); - if (normalized === "utc" || normalized === "gmt") return formatUtcTimestamp(date); - if (normalized === "local" || normalized === "host") return formatZonedTimestamp(date); + if (normalized === "utc" || normalized === "gmt") { + return formatUtcTimestamp(date); + } + if (normalized === "local" || normalized === "host") { + return formatZonedTimestamp(date); + } return formatZonedTimestamp(date, zone); } diff --git a/test/helpers/normalize-text.ts b/test/helpers/normalize-text.ts index 8f0d13a98e..d81be0106c 100644 --- a/test/helpers/normalize-text.ts +++ b/test/helpers/normalize-text.ts @@ -8,15 +8,21 @@ function stripAnsi(input: string): string { } const next = input[i + 1]; - if (next !== "[") continue; + if (next !== "[") { + continue; + } i += 1; while (i + 1 < input.length) { i += 1; const c = input[i]; - if (!c) break; + if (!c) { + break; + } const isLetter = (c >= "A" && c <= "Z") || (c >= "a" && c <= "z") || c === "~"; - if (isLetter) break; + if (isLetter) { + break; + } } } return out; diff --git a/test/helpers/poll.ts b/test/helpers/poll.ts index 3aed881e84..0b1a212e93 100644 --- a/test/helpers/poll.ts +++ b/test/helpers/poll.ts @@ -17,7 +17,9 @@ export async function pollUntil( while (Date.now() - start < timeoutMs) { const value = await fn(); - if (value !== null && value !== undefined) return value; + if (value !== null && value !== undefined) { + return value; + } await sleep(intervalMs); } diff --git a/test/helpers/temp-home.ts b/test/helpers/temp-home.ts index 6c1962fb15..30af94ca15 100644 --- a/test/helpers/temp-home.ts +++ b/test/helpers/temp-home.ts @@ -24,8 +24,11 @@ function snapshotEnv(): EnvSnapshot { function restoreEnv(snapshot: EnvSnapshot) { const restoreKey = (key: string, value: string | undefined) => { - if (value === undefined) delete process.env[key]; - else process.env[key] = value; + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } }; restoreKey("HOME", snapshot.home); restoreKey("USERPROFILE", snapshot.userProfile); @@ -36,14 +39,19 @@ function restoreEnv(snapshot: EnvSnapshot) { function snapshotExtraEnv(keys: string[]): Record { const snapshot: Record = {}; - for (const key of keys) snapshot[key] = process.env[key]; + for (const key of keys) { + snapshot[key] = process.env[key]; + } return snapshot; } function restoreExtraEnv(snapshot: Record) { for (const [key, value] of Object.entries(snapshot)) { - if (value === undefined) delete process.env[key]; - else process.env[key] = value; + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } } } @@ -52,9 +60,13 @@ function setTempHome(base: string) { process.env.USERPROFILE = base; process.env.OPENCLAW_STATE_DIR = path.join(base, ".openclaw"); - if (process.platform !== "win32") return; + if (process.platform !== "win32") { + return; + } const match = base.match(/^([A-Za-z]:)(.*)$/); - if (!match) return; + if (!match) { + return; + } process.env.HOMEDRIVE = match[1]; process.env.HOMEPATH = match[2] || "\\"; } @@ -78,8 +90,11 @@ export async function withTempHome( if (opts.env) { for (const [key, raw] of Object.entries(opts.env)) { const value = typeof raw === "function" ? raw(base) : raw; - if (value === undefined) delete process.env[key]; - else process.env[key] = value; + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } } } diff --git a/test/provider-timeout.e2e.test.ts b/test/provider-timeout.e2e.test.ts index 66adb880b9..42e488dcb6 100644 --- a/test/provider-timeout.e2e.test.ts +++ b/test/provider-timeout.e2e.test.ts @@ -83,11 +83,16 @@ async function connectClient(params: { url: string; token: string }) { return await new Promise>((resolve, reject) => { let settled = false; const stop = (err?: Error, client?: InstanceType) => { - if (settled) return; + if (settled) { + return; + } settled = true; clearTimeout(timer); - if (err) reject(err); - else resolve(client as InstanceType); + if (err) { + reject(err); + } else { + resolve(client as InstanceType); + } }; const client = new GatewayClient({ url: params.url, @@ -146,7 +151,9 @@ describe("provider timeouts (e2e)", () => { return buildOpenAIResponsesSse("fallback-ok"); } - if (!originalFetch) throw new Error(`fetch is not available (url=${url})`); + if (!originalFetch) { + throw new Error(`fetch is not available (url=${url})`); + } return await originalFetch(input, init); }; (globalThis as unknown as { fetch: unknown }).fetch = fetchImpl; @@ -263,20 +270,41 @@ describe("provider timeouts (e2e)", () => { await server.close({ reason: "timeout fallback test complete" }); await fs.rm(tempHome, { recursive: true, force: true }); (globalThis as unknown as { fetch: unknown }).fetch = originalFetch; - if (prev.home === undefined) delete process.env.HOME; - else process.env.HOME = prev.home; - if (prev.configPath === undefined) delete process.env.OPENCLAW_CONFIG_PATH; - else process.env.OPENCLAW_CONFIG_PATH = prev.configPath; - if (prev.token === undefined) delete process.env.OPENCLAW_GATEWAY_TOKEN; - else process.env.OPENCLAW_GATEWAY_TOKEN = prev.token; - if (prev.skipChannels === undefined) delete process.env.OPENCLAW_SKIP_CHANNELS; - else process.env.OPENCLAW_SKIP_CHANNELS = prev.skipChannels; - if (prev.skipGmail === undefined) delete process.env.OPENCLAW_SKIP_GMAIL_WATCHER; - else process.env.OPENCLAW_SKIP_GMAIL_WATCHER = prev.skipGmail; - if (prev.skipCron === undefined) delete process.env.OPENCLAW_SKIP_CRON; - else process.env.OPENCLAW_SKIP_CRON = prev.skipCron; - if (prev.skipCanvas === undefined) delete process.env.OPENCLAW_SKIP_CANVAS_HOST; - else process.env.OPENCLAW_SKIP_CANVAS_HOST = prev.skipCanvas; + if (prev.home === undefined) { + delete process.env.HOME; + } else { + process.env.HOME = prev.home; + } + if (prev.configPath === undefined) { + delete process.env.OPENCLAW_CONFIG_PATH; + } else { + process.env.OPENCLAW_CONFIG_PATH = prev.configPath; + } + if (prev.token === undefined) { + delete process.env.OPENCLAW_GATEWAY_TOKEN; + } else { + process.env.OPENCLAW_GATEWAY_TOKEN = prev.token; + } + if (prev.skipChannels === undefined) { + delete process.env.OPENCLAW_SKIP_CHANNELS; + } else { + process.env.OPENCLAW_SKIP_CHANNELS = prev.skipChannels; + } + if (prev.skipGmail === undefined) { + delete process.env.OPENCLAW_SKIP_GMAIL_WATCHER; + } else { + process.env.OPENCLAW_SKIP_GMAIL_WATCHER = prev.skipGmail; + } + if (prev.skipCron === undefined) { + delete process.env.OPENCLAW_SKIP_CRON; + } else { + process.env.OPENCLAW_SKIP_CRON = prev.skipCron; + } + if (prev.skipCanvas === undefined) { + delete process.env.OPENCLAW_SKIP_CANVAS_HOST; + } else { + process.env.OPENCLAW_SKIP_CANVAS_HOST = prev.skipCanvas; + } } }, ); diff --git a/test/setup.ts b/test/setup.ts index 80671a6484..215935b930 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -83,7 +83,9 @@ const createStubPlugin = (params: { listAccountIds: (cfg: OpenClawConfig) => { const channels = cfg.channels as Record | undefined; const entry = channels?.[params.id]; - if (!entry || typeof entry !== "object") return []; + if (!entry || typeof entry !== "object") { + return []; + } const accounts = (entry as { accounts?: Record }).accounts; const ids = accounts ? Object.keys(accounts).filter(Boolean) : []; return ids.length > 0 ? ids : ["default"]; @@ -91,7 +93,9 @@ const createStubPlugin = (params: { resolveAccount: (cfg: OpenClawConfig, accountId: string) => { const channels = cfg.channels as Record | undefined; const entry = channels?.[params.id]; - if (!entry || typeof entry !== "object") return {}; + if (!entry || typeof entry !== "object") { + return {}; + } const accounts = (entry as { accounts?: Record }).accounts; const match = accounts?.[accountId]; return (match && typeof match === "object") || typeof match === "string" ? match : entry; diff --git a/test/test-env.ts b/test/test-env.ts index d75ed97261..a450689834 100644 --- a/test/test-env.ts +++ b/test/test-env.ts @@ -7,14 +7,19 @@ type RestoreEntry = { key: string; value: string | undefined }; function restoreEnv(entries: RestoreEntry[]): void { for (const { key, value } of entries) { - if (value === undefined) delete process.env[key]; - else process.env[key] = value; + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } } } function loadProfileEnv(): void { const profilePath = path.join(os.homedir(), ".profile"); - if (!fs.existsSync(profilePath)) return; + if (!fs.existsSync(profilePath)) { + return; + } try { const output = execFileSync( "/bin/bash", @@ -24,11 +29,17 @@ function loadProfileEnv(): void { const entries = output.split("\0"); let applied = 0; for (const entry of entries) { - if (!entry) continue; + if (!entry) { + continue; + } const idx = entry.indexOf("="); - if (idx <= 0) continue; + if (idx <= 0) { + continue; + } const key = entry.slice(0, idx); - if (!key || (process.env[key] ?? "") !== "") continue; + if (!key || (process.env[key] ?? "") !== "") { + continue; + } process.env[key] = entry.slice(idx + 1); applied += 1; }