diff --git a/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift b/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift index a64f974e1..92562e71b 100644 --- a/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift +++ b/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift @@ -845,42 +845,67 @@ public struct ConfigGetParams: Codable, Sendable { public struct ConfigSetParams: Codable, Sendable { public let raw: String + public let basehash: String? public init( - raw: String + raw: String, + basehash: String? ) { self.raw = raw + self.basehash = basehash } private enum CodingKeys: String, CodingKey { case raw + case basehash = "baseHash" } } public struct ConfigApplyParams: Codable, Sendable { public let raw: String + public let basehash: String? public let sessionkey: String? public let note: String? public let restartdelayms: Int? public init( raw: String, + basehash: String?, sessionkey: String?, note: String?, restartdelayms: Int? ) { self.raw = raw + self.basehash = basehash self.sessionkey = sessionkey self.note = note self.restartdelayms = restartdelayms } private enum CodingKeys: String, CodingKey { case raw + case basehash = "baseHash" case sessionkey = "sessionKey" case note case restartdelayms = "restartDelayMs" } } +public struct ConfigPatchParams: Codable, Sendable { + public let raw: String + public let basehash: String? + + public init( + raw: String, + basehash: String? + ) { + self.raw = raw + self.basehash = basehash + } + private enum CodingKeys: String, CodingKey { + case raw + case basehash = "baseHash" + } +} + public struct ConfigSchemaParams: Codable, Sendable { } diff --git a/src/agents/pi-embedded-runner.google-sanitize-thinking.test.ts b/src/agents/pi-embedded-runner.google-sanitize-thinking.test.ts index ebc16c62e..c63a934d6 100644 --- a/src/agents/pi-embedded-runner.google-sanitize-thinking.test.ts +++ b/src/agents/pi-embedded-runner.google-sanitize-thinking.test.ts @@ -24,9 +24,9 @@ describe("sanitizeSessionHistory (google thinking)", () => { sessionId: "session:google", }); - const assistant = out.find( - (msg) => (msg as { role?: string }).role === "assistant", - ) as { content?: Array<{ type?: string; text?: string }> }; + const assistant = out.find((msg) => (msg as { role?: string }).role === "assistant") as { + content?: Array<{ type?: string; text?: string }>; + }; expect(assistant.content?.map((block) => block.type)).toEqual(["text"]); expect(assistant.content?.[0]?.text).toBe("reasoning"); }); @@ -51,9 +51,9 @@ describe("sanitizeSessionHistory (google thinking)", () => { sessionId: "session:google", }); - const assistant = out.find( - (msg) => (msg as { role?: string }).role === "assistant", - ) as { content?: Array<{ type?: string; thinking?: string; thinkingSignature?: string }> }; + const assistant = out.find((msg) => (msg as { role?: string }).role === "assistant") as { + content?: Array<{ type?: string; thinking?: string; thinkingSignature?: string }>; + }; expect(assistant.content?.map((block) => block.type)).toEqual(["thinking"]); expect(assistant.content?.[0]?.thinking).toBe("reasoning"); expect(assistant.content?.[0]?.thinkingSignature).toBe("sig"); @@ -83,9 +83,9 @@ describe("sanitizeSessionHistory (google thinking)", () => { sessionId: "session:google-mixed", }); - const assistant = out.find( - (msg) => (msg as { role?: string }).role === "assistant", - ) as { content?: Array<{ type?: string; text?: string }> }; + const assistant = out.find((msg) => (msg as { role?: string }).role === "assistant") as { + content?: Array<{ type?: string; text?: string }>; + }; expect(assistant.content?.map((block) => block.type)).toEqual(["text", "text", "text"]); expect(assistant.content?.[1]?.text).toBe("internal note"); }); @@ -113,9 +113,9 @@ describe("sanitizeSessionHistory (google thinking)", () => { sessionId: "session:google-mixed-signatures", }); - const assistant = out.find( - (msg) => (msg as { role?: string }).role === "assistant", - ) as { content?: Array<{ type?: string; thinking?: string; text?: string }> }; + const assistant = out.find((msg) => (msg as { role?: string }).role === "assistant") as { + content?: Array<{ type?: string; thinking?: string; text?: string }>; + }; expect(assistant.content?.map((block) => block.type)).toEqual(["thinking", "text"]); expect(assistant.content?.[0]?.thinking).toBe("signed"); expect(assistant.content?.[1]?.text).toBe("unsigned"); @@ -141,9 +141,7 @@ describe("sanitizeSessionHistory (google thinking)", () => { sessionId: "session:google-empty", }); - const assistant = out.find( - (msg) => (msg as { role?: string }).role === "assistant", - ); + const assistant = out.find((msg) => (msg as { role?: string }).role === "assistant"); expect(assistant).toBeUndefined(); }); @@ -167,9 +165,9 @@ describe("sanitizeSessionHistory (google thinking)", () => { sessionId: "session:openai", }); - const assistant = out.find( - (msg) => (msg as { role?: string }).role === "assistant", - ) as { content?: Array<{ type?: string }> }; + const assistant = out.find((msg) => (msg as { role?: string }).role === "assistant") as { + content?: Array<{ type?: string }>; + }; expect(assistant.content?.map((block) => block.type)).toEqual(["thinking"]); }); }); diff --git a/src/agents/subagent-registry.ts b/src/agents/subagent-registry.ts index b020bd5a4..b9756500b 100644 --- a/src/agents/subagent-registry.ts +++ b/src/agents/subagent-registry.ts @@ -299,9 +299,7 @@ async function waitForSubagentCompletion(runId: string, waitTimeoutMs: number) { mutated = true; } entry.outcome = - wait.status === "error" - ? { status: "error", error: wait.error } - : { status: "ok" }; + wait.status === "error" ? { status: "error", error: wait.error } : { status: "ok" }; mutated = true; if (mutated) persistSubagentRuns(); if (!beginSubagentAnnounce(runId)) return; diff --git a/src/agents/tools/gateway-tool.ts b/src/agents/tools/gateway-tool.ts index 09e1976bd..2ce89819d 100644 --- a/src/agents/tools/gateway-tool.ts +++ b/src/agents/tools/gateway-tool.ts @@ -138,10 +138,7 @@ export function createGatewayTool(opts?: { } else { const rawSnapshot = (snapshot as { raw?: unknown }).raw; if (typeof rawSnapshot === "string") { - baseHash = crypto - .createHash("sha256") - .update(rawSnapshot) - .digest("hex"); + baseHash = crypto.createHash("sha256").update(rawSnapshot).digest("hex"); } } } diff --git a/src/agents/tools/web-tools.ts b/src/agents/tools/web-tools.ts index a10d76a2c..a07a4488e 100644 --- a/src/agents/tools/web-tools.ts +++ b/src/agents/tools/web-tools.ts @@ -105,9 +105,7 @@ function resolveFetchEnabled(params: { fetch?: WebFetchConfig; sandboxed?: boole function resolveSearchApiKey(search?: WebSearchConfig): string | undefined { const fromConfig = - search && "apiKey" in search && typeof search.apiKey === "string" - ? search.apiKey.trim() - : ""; + search && "apiKey" in search && typeof search.apiKey === "string" ? search.apiKey.trim() : ""; const fromEnv = (process.env.BRAVE_API_KEY ?? "").trim(); return fromConfig || fromEnv || undefined; } @@ -160,12 +158,7 @@ function readCache( return { value: entry.value, cached: true }; } -function writeCache( - cache: Map>, - key: string, - value: T, - ttlMs: number, -) { +function writeCache(cache: Map>, key: string, value: T, ttlMs: number) { if (ttlMs <= 0) return; if (cache.size >= DEFAULT_CACHE_MAX_ENTRIES) { const oldest = cache.keys().next(); @@ -319,7 +312,7 @@ async function runWebSearch(params: { } const data = (await res.json()) as BraveSearchResponse; - const results = Array.isArray(data.web?.results) ? data.web?.results ?? [] : []; + const results = Array.isArray(data.web?.results) ? (data.web?.results ?? []) : []; const mapped = results.map((entry) => ({ title: entry.title ?? "", url: entry.url ?? "", @@ -463,8 +456,7 @@ export function createWebFetchTool(options?: { execute: async (_toolCallId, args) => { const params = args as Record; const url = readStringParam(params, "url", { required: true }); - const extractMode = - readStringParam(params, "extractMode") === "text" ? "text" : "markdown"; + const extractMode = readStringParam(params, "extractMode") === "text" ? "text" : "markdown"; const maxChars = readNumberParam(params, "maxChars", { integer: true }); const result = await runWebFetch({ url, diff --git a/src/browser/cdp.ts b/src/browser/cdp.ts index 25a2964e6..b3d2f2046 100644 --- a/src/browser/cdp.ts +++ b/src/browser/cdp.ts @@ -159,10 +159,7 @@ function axValue(v: unknown): string { return ""; } -export function formatAriaSnapshot( - nodes: RawAXNode[], - limit: number, -): AriaSnapshotNode[] { +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); diff --git a/src/browser/client-fetch.ts b/src/browser/client-fetch.ts index 3f9cb00d3..8aebd539d 100644 --- a/src/browser/client-fetch.ts +++ b/src/browser/client-fetch.ts @@ -72,14 +72,11 @@ export async function fetchBrowserJson( } return h; })(); - res = await fetch( - url, - { - ...init, - ...(mergedHeaders ? { headers: mergedHeaders } : {}), - signal: ctrl.signal, - } as RequestInit, - ); + res = await fetch(url, { + ...init, + ...(mergedHeaders ? { headers: mergedHeaders } : {}), + signal: ctrl.signal, + } as RequestInit); } catch (err) { throw enhanceBrowserFetchError(url, err, timeoutMs); } finally { diff --git a/src/browser/client.ts b/src/browser/client.ts index e88501a7a..958303a25 100644 --- a/src/browser/client.ts +++ b/src/browser/client.ts @@ -159,20 +159,17 @@ export async function browserCreateProfile( driver?: "clawd" | "extension"; }, ): Promise { - return await fetchBrowserJson( - `${baseUrl}/profiles/create`, - { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - name: opts.name, - color: opts.color, - cdpUrl: opts.cdpUrl, - driver: opts.driver, - }), - timeoutMs: 10000, - }, - ); + return await fetchBrowserJson(`${baseUrl}/profiles/create`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + name: opts.name, + color: opts.color, + cdpUrl: opts.cdpUrl, + driver: opts.driver, + }), + timeoutMs: 10000, + }); } export type BrowserDeleteProfileResult = { diff --git a/src/browser/extension-relay.test.ts b/src/browser/extension-relay.test.ts index 5b8601b6b..a976b31a6 100644 --- a/src/browser/extension-relay.test.ts +++ b/src/browser/extension-relay.test.ts @@ -105,9 +105,7 @@ describe("chrome extension relay server", () => { cdpUrl = `http://127.0.0.1:${port}`; await ensureChromeExtensionRelayServer({ cdpUrl }); - const v1 = (await fetch(`${cdpUrl}/json/version`).then((r) => - r.json(), - )) as { + const v1 = (await fetch(`${cdpUrl}/json/version`).then((r) => r.json())) as { webSocketDebuggerUrl?: string; }; expect(v1.webSocketDebuggerUrl).toBeUndefined(); @@ -115,9 +113,7 @@ describe("chrome extension relay server", () => { const ext = new WebSocket(`ws://127.0.0.1:${port}/extension`); await waitForOpen(ext); - const v2 = (await fetch(`${cdpUrl}/json/version`).then((r) => - r.json(), - )) as { + const v2 = (await fetch(`${cdpUrl}/json/version`).then((r) => r.json())) as { webSocketDebuggerUrl?: string; }; expect(String(v2.webSocketDebuggerUrl ?? "")).toContain(`/cdp`); @@ -153,15 +149,11 @@ describe("chrome extension relay server", () => { }), ); - const list = (await fetch(`${cdpUrl}/json/list`).then((r) => - r.json(), - )) as Array<{ + const list = (await fetch(`${cdpUrl}/json/list`).then((r) => r.json())) as Array<{ id?: string; url?: string; }>; - expect( - list.some((t) => t.id === "t1" && t.url === "https://example.com"), - ).toBe(true); + expect(list.some((t) => t.id === "t1" && t.url === "https://example.com")).toBe(true); const cdp = new WebSocket(`ws://127.0.0.1:${port}/cdp`); await waitForOpen(cdp); diff --git a/src/browser/extension-relay.ts b/src/browser/extension-relay.ts index 6d66ed82a..acf9b2129 100644 --- a/src/browser/extension-relay.ts +++ b/src/browser/extension-relay.ts @@ -114,21 +114,13 @@ function parseBaseUrl(raw: string): { } { const parsed = new URL(raw.trim().replace(/\/$/, "")); if (parsed.protocol !== "http:" && parsed.protocol !== "https:") { - throw new Error( - `extension relay cdpUrl must be http(s), got ${parsed.protocol}`, - ); + throw new Error(`extension relay cdpUrl must be http(s), got ${parsed.protocol}`); } const host = parsed.hostname; const port = - parsed.port?.trim() !== "" - ? Number(parsed.port) - : parsed.protocol === "https:" - ? 443 - : 80; + parsed.port?.trim() !== "" ? Number(parsed.port) : parsed.protocol === "https:" ? 443 : 80; if (!Number.isFinite(port) || port <= 0 || port > 65535) { - throw new Error( - `extension relay cdpUrl has invalid port: ${parsed.port || "(empty)"}`, - ); + throw new Error(`extension relay cdpUrl has invalid port: ${parsed.port || "(empty)"}`); } return { host, port, baseUrl: parsed.toString().replace(/\/$/, "") }; } @@ -162,9 +154,7 @@ export async function ensureChromeExtensionRelayServer(opts: { }): Promise { const info = parseBaseUrl(opts.cdpUrl); if (!isLoopbackHost(info.host)) { - throw new Error( - `extension relay requires loopback cdpUrl host (got ${info.host})`, - ); + throw new Error(`extension relay requires loopback cdpUrl host (got ${info.host})`); } const existing = serversByPort.get(info.port); @@ -184,9 +174,7 @@ export async function ensureChromeExtensionRelayServer(opts: { >(); let nextExtensionId = 1; - const sendToExtension = async ( - payload: ExtensionForwardCommandMessage, - ): Promise => { + const sendToExtension = async (payload: ExtensionForwardCommandMessage): Promise => { const ws = extensionWs; if (!ws || ws.readyState !== WebSocket.OPEN) { throw new Error("Chrome extension not connected"); @@ -195,9 +183,7 @@ export async function ensureChromeExtensionRelayServer(opts: { return await new Promise((resolve, reject) => { const timer = setTimeout(() => { pendingExtension.delete(payload.id); - reject( - new Error(`extension request timeout: ${payload.params.method}`), - ); + reject(new Error(`extension request timeout: ${payload.params.method}`)); }, 30_000); pendingExtension.set(payload.id, { resolve, reject, timer }); }); @@ -216,10 +202,7 @@ export async function ensureChromeExtensionRelayServer(opts: { ws.send(JSON.stringify(res)); }; - const ensureTargetEventsForClient = ( - ws: WebSocket, - mode: "autoAttach" | "discover", - ) => { + const ensureTargetEventsForClient = (ws: WebSocket, mode: "autoAttach" | "discover") => { for (const target of connectedTargets.values()) { if (mode === "autoAttach") { ws.send( @@ -267,8 +250,7 @@ export async function ensureChromeExtensionRelayServer(opts: { }; case "Target.getTargetInfo": { const params = (cmd.params ?? {}) as { targetId?: string }; - const targetId = - typeof params.targetId === "string" ? params.targetId : undefined; + 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 }; @@ -283,8 +265,7 @@ export async function ensureChromeExtensionRelayServer(opts: { } case "Target.attachToTarget": { const params = (cmd.params ?? {}) as { targetId?: string }; - const targetId = - typeof params.targetId === "string" ? params.targetId : undefined; + const targetId = typeof params.targetId === "string" ? params.targetId : undefined; if (!targetId) throw new Error("targetId required"); for (const t of connectedTargets.values()) { if (t.targetId === targetId) return { sessionId: t.sessionId }; @@ -458,9 +439,7 @@ export async function ensureChromeExtensionRelayServer(opts: { const ping = setInterval(() => { if (ws.readyState !== WebSocket.OPEN) return; - ws.send( - JSON.stringify({ method: "ping" } satisfies ExtensionPingMessage), - ); + ws.send(JSON.stringify({ method: "ping" } satisfies ExtensionPingMessage)); }, 5000); ws.on("message", (data) => { @@ -471,21 +450,12 @@ export async function ensureChromeExtensionRelayServer(opts: { return; } - if ( - parsed && - typeof parsed === "object" && - "id" in parsed && - typeof parsed.id === "number" - ) { + if (parsed && typeof parsed === "object" && "id" in parsed && typeof parsed.id === "number") { const pending = pendingExtension.get(parsed.id); if (!pending) return; pendingExtension.delete(parsed.id); clearTimeout(pending.timer); - if ( - "error" in parsed && - typeof parsed.error === "string" && - parsed.error.trim() - ) { + if ("error" in parsed && typeof parsed.error === "string" && parsed.error.trim()) { pending.reject(new Error(parsed.error)); } else { pending.resolve((parsed as ExtensionResponseMessage).result); @@ -495,10 +465,7 @@ 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 ExtensionForwardEventMessage).method !== "forwardCDPEvent") return; const evt = parsed as ExtensionForwardEventMessage; const method = evt.params?.method; const params = evt.params?.params; @@ -591,8 +558,7 @@ export async function ensureChromeExtensionRelayServer(opts: { } if (cmd.method === "Target.attachToTarget") { const params = (cmd.params ?? {}) as { targetId?: string }; - const targetId = - typeof params.targetId === "string" ? params.targetId : undefined; + const targetId = typeof params.targetId === "string" ? params.targetId : undefined; if (targetId) { const target = Array.from(connectedTargets.values()).find( (t) => t.targetId === targetId, @@ -669,9 +635,7 @@ export async function ensureChromeExtensionRelayServer(opts: { return relay; } -export async function stopChromeExtensionRelayServer(opts: { - cdpUrl: string; -}): Promise { +export async function stopChromeExtensionRelayServer(opts: { cdpUrl: string }): Promise { const info = parseBaseUrl(opts.cdpUrl); const existing = serversByPort.get(info.port); if (!existing) return false; diff --git a/src/browser/routes/basic.ts b/src/browser/routes/basic.ts index 0567e4274..2f53ac0ca 100644 --- a/src/browser/routes/basic.ts +++ b/src/browser/routes/basic.ts @@ -111,9 +111,10 @@ export function registerBrowserBasicRoutes(app: express.Express, ctx: BrowserRou const name = toStringOrEmpty((req.body as { name?: unknown })?.name); const color = toStringOrEmpty((req.body as { color?: unknown })?.color); const cdpUrl = toStringOrEmpty((req.body as { cdpUrl?: unknown })?.cdpUrl); - const driver = toStringOrEmpty( - (req.body as { driver?: unknown })?.driver, - ) as "clawd" | "extension" | ""; + const driver = toStringOrEmpty((req.body as { driver?: unknown })?.driver) as + | "clawd" + | "extension" + | ""; if (!name) return jsonError(res, 400, "name is required"); diff --git a/src/browser/server-context.ts b/src/browser/server-context.ts index 1ccddbccf..9fa6094f9 100644 --- a/src/browser/server-context.ts +++ b/src/browser/server-context.ts @@ -342,9 +342,7 @@ function createProfileContext( const resetProfile = async () => { if (profile.driver === "extension") { - await stopChromeExtensionRelayServer({ cdpUrl: profile.cdpUrl }).catch( - () => {}, - ); + await stopChromeExtensionRelayServer({ cdpUrl: profile.cdpUrl }).catch(() => {}); return { moved: false, from: profile.cdpUrl }; } if (!profile.cdpIsLoopback) { diff --git a/src/browser/server.ts b/src/browser/server.ts index 74f4dbd9f..edfb9ab76 100644 --- a/src/browser/server.ts +++ b/src/browser/server.ts @@ -3,11 +3,7 @@ import express from "express"; import { loadConfig } from "../config/config.js"; import { createSubsystemLogger } from "../logging.js"; -import { - resolveBrowserConfig, - resolveProfile, - shouldStartLocalBrowserServer, -} from "./config.js"; +import { resolveBrowserConfig, resolveProfile, shouldStartLocalBrowserServer } from "./config.js"; import { ensureChromeExtensionRelayServer } from "./extension-relay.js"; import { registerBrowserRoutes } from "./routes/index.js"; import { type BrowserServerState, createBrowserRouteContext } from "./server-context.js"; @@ -61,13 +57,9 @@ export async function startBrowserControlServerFromConfig(): Promise { - logServer.warn( - `Chrome extension relay init failed for profile "${name}": ${String(err)}`, - ); - }, - ); + await ensureChromeExtensionRelayServer({ cdpUrl: profile.cdpUrl }).catch((err) => { + logServer.warn(`Chrome extension relay init failed for profile "${name}": ${String(err)}`); + }); } logServer.info(`Browser control listening on http://127.0.0.1:${port}/`); diff --git a/src/channels/plugins/catalog.ts b/src/channels/plugins/catalog.ts index 85049a663..83edbd244 100644 --- a/src/channels/plugins/catalog.ts +++ b/src/channels/plugins/catalog.ts @@ -34,9 +34,7 @@ export function listChannelPluginCatalogEntries(): ChannelPluginCatalogEntry[] { return [...CATALOG]; } -export function getChannelPluginCatalogEntry( - id: string, -): ChannelPluginCatalogEntry | undefined { +export function getChannelPluginCatalogEntry(id: string): ChannelPluginCatalogEntry | undefined { const trimmed = id.trim(); if (!trimmed) return undefined; return CATALOG.find((entry) => entry.id === trimmed); diff --git a/src/cli/browser-cli-extension.test.ts b/src/cli/browser-cli-extension.test.ts index 78f2e61e0..a844aae7b 100644 --- a/src/cli/browser-cli-extension.test.ts +++ b/src/cli/browser-cli-extension.test.ts @@ -17,4 +17,3 @@ describe("browser extension install", () => { expect(result.path.includes("node_modules")).toBe(false); }); }); - diff --git a/src/cli/browser-cli-extension.ts b/src/cli/browser-cli-extension.ts index d63c85b9d..bb89e46eb 100644 --- a/src/cli/browser-cli-extension.ts +++ b/src/cli/browser-cli-extension.ts @@ -75,20 +75,20 @@ export function registerBrowserExtensionCommands( defaultRuntime.log(JSON.stringify({ ok: true, path: installed.path }, null, 2)); return; } - defaultRuntime.log(installed.path); - defaultRuntime.error( - info( - [ - "Next:", - `- Chrome → chrome://extensions → enable “Developer mode”`, - `- “Load unpacked” → select: ${installed.path}`, - `- Pin “Clawdbot Browser Relay”, then click it on the tab (badge shows ON)`, - "", - `${theme.muted("Docs:")} ${formatDocsLink("/tools/chrome-extension", "docs.clawd.bot/tools/chrome-extension")}`, - ].join("\n"), - ), - ); - }); + defaultRuntime.log(installed.path); + defaultRuntime.error( + info( + [ + "Next:", + `- Chrome → chrome://extensions → enable “Developer mode”`, + `- “Load unpacked” → select: ${installed.path}`, + `- Pin “Clawdbot Browser Relay”, then click it on the tab (badge shows ON)`, + "", + `${theme.muted("Docs:")} ${formatDocsLink("/tools/chrome-extension", "docs.clawd.bot/tools/chrome-extension")}`, + ].join("\n"), + ), + ); + }); ext .command("path") diff --git a/src/cli/browser-cli-manage.ts b/src/cli/browser-cli-manage.ts index 476d42253..e5dfd4c56 100644 --- a/src/cli/browser-cli-manage.ts +++ b/src/cli/browser-cli-manage.ts @@ -384,10 +384,7 @@ export function registerBrowserManageCommands( .option("--cdp-url ", "CDP URL for remote Chrome (http/https)") .option("--driver ", "Profile driver (clawd|extension). Default: clawd") .action( - async ( - opts: { name: string; color?: string; cdpUrl?: string; driver?: string }, - cmd, - ) => { + async (opts: { name: string; color?: string; cdpUrl?: string; driver?: string }, cmd) => { const parent = parentOpts(cmd); const baseUrl = resolveBrowserControlUrl(parent?.url); try { @@ -401,9 +398,7 @@ export function registerBrowserManageCommands( defaultRuntime.log(JSON.stringify(result, null, 2)); return; } - const loc = result.isRemote - ? ` cdpUrl: ${result.cdpUrl}` - : ` port: ${result.cdpPort}`; + const loc = result.isRemote ? ` cdpUrl: ${result.cdpUrl}` : ` port: ${result.cdpPort}`; defaultRuntime.log( info( `🦞 Created profile "${result.profile}"\n${loc}\n color: ${result.color}${ diff --git a/src/cli/security-cli.ts b/src/cli/security-cli.ts index 95d10360d..8c5596e10 100644 --- a/src/cli/security-cli.ts +++ b/src/cli/security-cli.ts @@ -80,8 +80,10 @@ export function registerSecurityCli(program: Command) { for (const action of fixResult.actions) { const mode = action.mode.toString(8).padStart(3, "0"); if (action.ok) lines.push(muted(` chmod ${mode} ${action.path}`)); - else if (action.skipped) lines.push(muted(` skip chmod ${mode} ${action.path} (${action.skipped})`)); - else if (action.error) lines.push(muted(` chmod ${mode} ${action.path} failed: ${action.error}`)); + else if (action.skipped) + lines.push(muted(` skip chmod ${mode} ${action.path} (${action.skipped})`)); + else if (action.error) + lines.push(muted(` chmod ${mode} ${action.path} failed: ${action.error}`)); } if (fixResult.errors.length > 0) { for (const err of fixResult.errors) lines.push(muted(` error: ${err}`)); diff --git a/src/commands/agents.commands.add.ts b/src/commands/agents.commands.add.ts index e0fe40a18..628d7d345 100644 --- a/src/commands/agents.commands.add.ts +++ b/src/commands/agents.commands.add.ts @@ -1,7 +1,11 @@ import fs from "node:fs/promises"; import path from "node:path"; -import { resolveAgentDir, resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js"; +import { + resolveAgentDir, + resolveAgentWorkspaceDir, + resolveDefaultAgentId, +} from "../agents/agent-scope.js"; import { ensureAuthProfileStore } from "../agents/auth-profiles.js"; import { resolveAuthStorePath } from "../agents/auth-profiles/paths.js"; import { CONFIG_PATH_CLAWDBOT, writeConfigFile } from "../config/config.js"; @@ -223,9 +227,12 @@ export async function agentsAddCommand( const sourceAuthPath = resolveAuthStorePath(resolveAgentDir(cfg, defaultAgentId)); const destAuthPath = resolveAuthStorePath(agentDir); const sameAuthPath = - path.resolve(sourceAuthPath).toLowerCase() === - path.resolve(destAuthPath).toLowerCase(); - if (!sameAuthPath && (await fileExists(sourceAuthPath)) && !(await fileExists(destAuthPath))) { + path.resolve(sourceAuthPath).toLowerCase() === path.resolve(destAuthPath).toLowerCase(); + if ( + !sameAuthPath && + (await fileExists(sourceAuthPath)) && + !(await fileExists(destAuthPath)) + ) { const shouldCopy = await prompter.confirm({ message: `Copy auth profiles from "${defaultAgentId}"?`, initialValue: false, diff --git a/src/commands/onboard-channels.ts b/src/commands/onboard-channels.ts index 8424b40d8..8ff1e98dd 100644 --- a/src/commands/onboard-channels.ts +++ b/src/commands/onboard-channels.ts @@ -140,9 +140,7 @@ export async function setupChannels( quickstartScore: 0, })); const combinedStatuses = [...statusEntries, ...catalogStatuses]; - const statusByChannel = new Map( - combinedStatuses.map((entry) => [entry.channel, entry]), - ); + const statusByChannel = new Map(combinedStatuses.map((entry) => [entry.channel, entry])); const statusLines = combinedStatuses.flatMap((entry) => entry.statusLines); if (statusLines.length > 0) { await prompter.note(statusLines.join("\n"), "Channel status"); @@ -216,9 +214,7 @@ export async function setupChannels( })) as ChannelChoice[]; } - const catalogById = new Map( - catalogEntries.map((entry) => [entry.id as ChannelChoice, entry]), - ); + const catalogById = new Map(catalogEntries.map((entry) => [entry.id as ChannelChoice, entry])); if (selection.some((channel) => catalogById.has(channel))) { const workspaceDir = resolveAgentWorkspaceDir(next, resolveDefaultAgentId(next)); for (const channel of selection) { @@ -248,16 +244,10 @@ export async function setupChannels( const selectionNotes = new Map(); for (const plugin of installedPlugins) { - selectionNotes.set( - plugin.id, - formatChannelSelectionLine(plugin.meta, formatDocsLink), - ); + selectionNotes.set(plugin.id, formatChannelSelectionLine(plugin.meta, formatDocsLink)); } for (const entry of catalogEntries) { - selectionNotes.set( - entry.id, - formatChannelSelectionLine(entry.meta, formatDocsLink), - ); + selectionNotes.set(entry.id, formatChannelSelectionLine(entry.meta, formatDocsLink)); } const selectedLines = selection .map((channel) => selectionNotes.get(channel)) diff --git a/src/commands/onboarding/__tests__/test-utils.ts b/src/commands/onboarding/__tests__/test-utils.ts index 204213e01..aea7f9cda 100644 --- a/src/commands/onboarding/__tests__/test-utils.ts +++ b/src/commands/onboarding/__tests__/test-utils.ts @@ -12,9 +12,7 @@ export const makeRuntime = (overrides: Partial = {}): RuntimeEnv => ...overrides, }); -export const makePrompter = ( - overrides: Partial = {}, -): WizardPrompter => ({ +export const makePrompter = (overrides: Partial = {}): WizardPrompter => ({ intro: vi.fn(async () => {}), outro: vi.fn(async () => {}), note: vi.fn(async () => {}), diff --git a/src/commands/onboarding/plugin-install.ts b/src/commands/onboarding/plugin-install.ts index c419ae196..041f0b10e 100644 --- a/src/commands/onboarding/plugin-install.ts +++ b/src/commands/onboarding/plugin-install.ts @@ -170,8 +170,7 @@ export function reloadOnboardingPluginRegistry(params: { workspaceDir?: string; }): void { const workspaceDir = - params.workspaceDir ?? - resolveAgentWorkspaceDir(params.cfg, resolveDefaultAgentId(params.cfg)); + params.workspaceDir ?? resolveAgentWorkspaceDir(params.cfg, resolveDefaultAgentId(params.cfg)); const log = createSubsystemLogger("plugins"); loadClawdbotPlugins({ config: params.cfg, diff --git a/src/config/io.ts b/src/config/io.ts index 631952e80..089ecadc1 100644 --- a/src/config/io.ts +++ b/src/config/io.ts @@ -52,7 +52,10 @@ const SHELL_ENV_EXPECTED_KEYS = [ export type ParseConfigJson5Result = { ok: true; parsed: unknown } | { ok: false; error: string }; function hashConfigRaw(raw: string | null): string { - return crypto.createHash("sha256").update(raw ?? "").digest("hex"); + return crypto + .createHash("sha256") + .update(raw ?? "") + .digest("hex"); } export type ConfigIoDeps = { diff --git a/src/config/zod-schema.ts b/src/config/zod-schema.ts index 7c4b2f3e2..0051bcae3 100644 --- a/src/config/zod-schema.ts +++ b/src/config/zod-schema.ts @@ -82,9 +82,7 @@ export const ClawdbotSchema = z .object({ cdpPort: z.number().int().min(1).max(65535).optional(), cdpUrl: z.string().optional(), - driver: z - .union([z.literal("clawd"), z.literal("extension")]) - .optional(), + driver: z.union([z.literal("clawd"), z.literal("extension")]).optional(), color: HexColorSchema, }) .refine((value) => value.cdpPort || value.cdpUrl, { diff --git a/src/gateway/server-methods/config.ts b/src/gateway/server-methods/config.ts index d1ff9d344..ea618510a 100644 --- a/src/gateway/server-methods/config.ts +++ b/src/gateway/server-methods/config.ts @@ -227,7 +227,11 @@ export const configHandlers: GatewayRequestHandlers = { respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, parsedRes.error)); return; } - if (!parsedRes.parsed || typeof parsedRes.parsed !== "object" || Array.isArray(parsedRes.parsed)) { + if ( + !parsedRes.parsed || + typeof parsedRes.parsed !== "object" || + Array.isArray(parsedRes.parsed) + ) { respond( false, undefined, diff --git a/src/gateway/server.config-patch.test.ts b/src/gateway/server.config-patch.test.ts index 9b5e7be43..20d46f143 100644 --- a/src/gateway/server.config-patch.test.ts +++ b/src/gateway/server.config-patch.test.ts @@ -28,7 +28,10 @@ describe("gateway config.patch", () => { }, }), ); - const setRes = await onceMessage<{ ok: boolean }>(ws, (o) => o.type === "res" && o.id === setId); + const setRes = await onceMessage<{ ok: boolean }>( + ws, + (o) => o.type === "res" && o.id === setId, + ); expect(setRes.ok).toBe(true); const getId = "req-get"; @@ -85,7 +88,9 @@ describe("gateway config.patch", () => { ); const get2Res = await onceMessage<{ ok: boolean; - payload?: { config?: { gateway?: { mode?: string }; channels?: { telegram?: { botToken?: string } } } }; + payload?: { + config?: { gateway?: { mode?: string }; channels?: { telegram?: { botToken?: string } } }; + }; }>(ws, (o) => o.type === "res" && o.id === get2Id); expect(get2Res.ok).toBe(true); expect(get2Res.payload?.config?.gateway?.mode).toBe("local"); @@ -112,7 +117,10 @@ describe("gateway config.patch", () => { }, }), ); - const setRes = await onceMessage<{ ok: boolean }>(ws, (o) => o.type === "res" && o.id === setId); + const setRes = await onceMessage<{ ok: boolean }>( + ws, + (o) => o.type === "res" && o.id === setId, + ); expect(setRes.ok).toBe(true); const patchId = "req-patch-2"; @@ -154,7 +162,10 @@ describe("gateway config.patch", () => { }, }), ); - const setRes = await onceMessage<{ ok: boolean }>(ws, (o) => o.type === "res" && o.id === setId); + const setRes = await onceMessage<{ ok: boolean }>( + ws, + (o) => o.type === "res" && o.id === setId, + ); expect(setRes.ok).toBe(true); const set2Id = "req-set-4"; diff --git a/src/gateway/server/__tests__/test-utils.ts b/src/gateway/server/__tests__/test-utils.ts index 1a82c625d..e1d14f8f5 100644 --- a/src/gateway/server/__tests__/test-utils.ts +++ b/src/gateway/server/__tests__/test-utils.ts @@ -1,8 +1,6 @@ import type { PluginRegistry } from "../../../plugins/registry.js"; -export const createTestRegistry = ( - overrides: Partial = {}, -): PluginRegistry => { +export const createTestRegistry = (overrides: Partial = {}): PluginRegistry => { const base: PluginRegistry = { plugins: [], tools: [], diff --git a/src/gateway/server/plugins-http.test.ts b/src/gateway/server/plugins-http.test.ts index 9466f7354..e4d54a68b 100644 --- a/src/gateway/server/plugins-http.test.ts +++ b/src/gateway/server/plugins-http.test.ts @@ -44,7 +44,9 @@ describe("createGatewayPluginRequestHandler", () => { { pluginId: "second", handler: second, source: "second" }, ], }), - log: { warn: vi.fn() } as unknown as Parameters[0]["log"], + log: { warn: vi.fn() } as unknown as Parameters< + typeof createGatewayPluginRequestHandler + >[0]["log"], }); const { res } = makeResponse(); @@ -78,10 +80,7 @@ describe("createGatewayPluginRequestHandler", () => { expect(handled).toBe(true); expect(log.warn).toHaveBeenCalledWith(expect.stringContaining("boom")); expect(res.statusCode).toBe(500); - expect(setHeader).toHaveBeenCalledWith( - "Content-Type", - "text/plain; charset=utf-8", - ); + expect(setHeader).toHaveBeenCalledWith("Content-Type", "text/plain; charset=utf-8"); expect(end).toHaveBeenCalledWith("Internal Server Error"); }); }); diff --git a/src/gateway/test-helpers.mocks.ts b/src/gateway/test-helpers.mocks.ts index 2e6db412b..8f356417e 100644 --- a/src/gateway/test-helpers.mocks.ts +++ b/src/gateway/test-helpers.mocks.ts @@ -170,7 +170,10 @@ vi.mock("../config/config.js", async () => { const actual = await vi.importActual("../config/config.js"); const resolveConfigPath = () => path.join(testConfigRoot.value, "clawdbot.json"); const hashConfigRaw = (raw: string | null) => - crypto.createHash("sha256").update(raw ?? "").digest("hex"); + crypto + .createHash("sha256") + .update(raw ?? "") + .digest("hex"); const readConfigFileSnapshot = async () => { if (testState.legacyIssues.length > 0) { diff --git a/src/plugins/registry.ts b/src/plugins/registry.ts index 2ad93f455..d7e6296af 100644 --- a/src/plugins/registry.ts +++ b/src/plugins/registry.ts @@ -152,10 +152,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) { record.gatewayMethods.push(trimmed); }; - const registerHttpHandler = ( - record: PluginRecord, - handler: ClawdbotPluginHttpHandler, - ) => { + const registerHttpHandler = (record: PluginRecord, handler: ClawdbotPluginHttpHandler) => { record.httpHandlers += 1; registry.httpHandlers.push({ pluginId: record.id, diff --git a/src/security/audit.test.ts b/src/security/audit.test.ts index b139b2fbd..1a4d7a67b 100644 --- a/src/security/audit.test.ts +++ b/src/security/audit.test.ts @@ -84,7 +84,10 @@ describe("security audit", () => { expect(res.findings).toEqual( expect.arrayContaining([ - expect.objectContaining({ checkId: "browser.control_remote_no_token", severity: "critical" }), + expect.objectContaining({ + checkId: "browser.control_remote_no_token", + severity: "critical", + }), ]), ); } finally { diff --git a/src/security/audit.ts b/src/security/audit.ts index 8e9844b08..7df7e5603 100644 --- a/src/security/audit.ts +++ b/src/security/audit.ts @@ -351,7 +351,9 @@ function collectBrowserControlFindings(cfg: ClawdbotConfig): SecurityAuditFindin const tailscaleMode = cfg.gateway?.tailscale?.mode ?? "off"; const gatewayAuth = resolveGatewayAuth({ authConfig: cfg.gateway?.auth, tailscaleMode }); const gatewayToken = - gatewayAuth.mode === "token" && typeof gatewayAuth.token === "string" && gatewayAuth.token.trim() + gatewayAuth.mode === "token" && + typeof gatewayAuth.token === "string" && + gatewayAuth.token.trim() ? gatewayAuth.token.trim() : null; diff --git a/src/security/fix.ts b/src/security/fix.ts index 297e2594d..347fed396 100644 --- a/src/security/fix.ts +++ b/src/security/fix.ts @@ -157,10 +157,11 @@ function setWhatsAppGroupAllowFromFromStore(params: { } } -function applyConfigFixes(params: { +function applyConfigFixes(params: { cfg: ClawdbotConfig; env: NodeJS.ProcessEnv }): { cfg: ClawdbotConfig; - env: NodeJS.ProcessEnv; -}): { cfg: ClawdbotConfig; changes: string[]; policyFlips: Set } { + changes: string[]; + policyFlips: Set; +} { const next = structuredClone(params.cfg ?? {}); const changes: string[] = []; const policyFlips = new Set(); @@ -170,7 +171,15 @@ function applyConfigFixes(params: { changes.push('logging.redactSensitive=off -> "tools"'); } - for (const channel of ["telegram", "whatsapp", "discord", "signal", "imessage", "slack", "msteams"]) { + for (const channel of [ + "telegram", + "whatsapp", + "discord", + "signal", + "imessage", + "slack", + "msteams", + ]) { setGroupPolicyAllowlist({ cfg: next, channel, changes, policyFlips }); }