diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..6333f297c --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +src/canvas-host/a2ui/a2ui.bundle.js diff --git a/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift b/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift index a44f6739a..086c198d9 100644 --- a/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift +++ b/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift @@ -424,14 +424,18 @@ public struct PollParams: Codable, Sendable { public struct AgentParams: Codable, Sendable { public let message: String + public let agentid: String? public let to: String? + public let replyto: String? public let sessionid: String? public let sessionkey: String? public let thinking: String? public let deliver: Bool? public let attachments: [AnyCodable]? public let channel: String? + public let replychannel: String? public let accountid: String? + public let replyaccountid: String? public let timeout: Int? public let lane: String? public let extrasystemprompt: String? @@ -441,14 +445,18 @@ public struct AgentParams: Codable, Sendable { public init( message: String, + agentid: String?, to: String?, + replyto: String?, sessionid: String?, sessionkey: String?, thinking: String?, deliver: Bool?, attachments: [AnyCodable]?, channel: String?, + replychannel: String?, accountid: String?, + replyaccountid: String?, timeout: Int?, lane: String?, extrasystemprompt: String?, @@ -457,14 +465,18 @@ public struct AgentParams: Codable, Sendable { spawnedby: String? ) { self.message = message + self.agentid = agentid self.to = to + self.replyto = replyto self.sessionid = sessionid self.sessionkey = sessionkey self.thinking = thinking self.deliver = deliver self.attachments = attachments self.channel = channel + self.replychannel = replychannel self.accountid = accountid + self.replyaccountid = replyaccountid self.timeout = timeout self.lane = lane self.extrasystemprompt = extrasystemprompt @@ -474,14 +486,18 @@ public struct AgentParams: Codable, Sendable { } private enum CodingKeys: String, CodingKey { case message + case agentid = "agentId" case to + case replyto = "replyTo" case sessionid = "sessionId" case sessionkey = "sessionKey" case thinking case deliver case attachments case channel + case replychannel = "replyChannel" case accountid = "accountId" + case replyaccountid = "replyAccountId" case timeout case lane case extrasystemprompt = "extraSystemPrompt" diff --git a/src/agents/anthropic.setup-token.live.test.ts b/src/agents/anthropic.setup-token.live.test.ts index 5b7ea11b5..16061b188 100644 --- a/src/agents/anthropic.setup-token.live.test.ts +++ b/src/agents/anthropic.setup-token.live.test.ts @@ -22,8 +22,7 @@ import { getApiKeyForModel } from "./model-auth.js"; import { normalizeProviderId, parseModelRef } from "./model-selection.js"; import { ensureClawdbotModelsJson } from "./models-config.js"; -const LIVE = - isTruthyEnvValue(process.env.LIVE) || isTruthyEnvValue(process.env.CLAWDBOT_LIVE_TEST); +const LIVE = isTruthyEnvValue(process.env.LIVE) || isTruthyEnvValue(process.env.CLAWDBOT_LIVE_TEST); const SETUP_TOKEN_RAW = process.env.CLAWDBOT_LIVE_SETUP_TOKEN?.trim() ?? ""; const SETUP_TOKEN_VALUE = process.env.CLAWDBOT_LIVE_SETUP_TOKEN_VALUE?.trim() ?? ""; const SETUP_TOKEN_PROFILE = process.env.CLAWDBOT_LIVE_SETUP_TOKEN_PROFILE?.trim() ?? ""; diff --git a/src/agents/clawdbot-tools.sessions.test.ts b/src/agents/clawdbot-tools.sessions.test.ts index d6879a167..8dba672da 100644 --- a/src/agents/clawdbot-tools.sessions.test.ts +++ b/src/agents/clawdbot-tools.sessions.test.ts @@ -22,11 +22,7 @@ vi.mock("../config/config.js", async (importOriginal) => { import { createClawdbotTools } from "./clawdbot-tools.js"; -const waitForCalls = async ( - getCount: () => number, - count: number, - timeoutMs = 2000, -) => { +const waitForCalls = async (getCount: () => number, count: number, timeoutMs = 2000) => { const start = Date.now(); while (getCount() < count) { if (Date.now() - start > timeoutMs) { @@ -254,18 +250,9 @@ describe("sessions tools", () => { runId: "run-1", delivery: { status: "pending", mode: "announce" }, }); - await waitForCalls( - () => calls.filter((call) => call.method === "agent").length, - 4, - ); - await waitForCalls( - () => calls.filter((call) => call.method === "agent.wait").length, - 4, - ); - await waitForCalls( - () => calls.filter((call) => call.method === "chat.history").length, - 4, - ); + await waitForCalls(() => calls.filter((call) => call.method === "agent").length, 4); + await waitForCalls(() => calls.filter((call) => call.method === "agent.wait").length, 4); + await waitForCalls(() => calls.filter((call) => call.method === "chat.history").length, 4); const waitPromise = tool.execute("call6", { sessionKey: "main", @@ -279,18 +266,9 @@ describe("sessions tools", () => { delivery: { status: "pending", mode: "announce" }, }); expect(typeof (waited.details as { runId?: string }).runId).toBe("string"); - await waitForCalls( - () => calls.filter((call) => call.method === "agent").length, - 8, - ); - await waitForCalls( - () => calls.filter((call) => call.method === "agent.wait").length, - 8, - ); - await waitForCalls( - () => calls.filter((call) => call.method === "chat.history").length, - 8, - ); + await waitForCalls(() => calls.filter((call) => call.method === "agent").length, 8); + await waitForCalls(() => calls.filter((call) => call.method === "agent.wait").length, 8); + await waitForCalls(() => calls.filter((call) => call.method === "chat.history").length, 8); const agentCalls = calls.filter((call) => call.method === "agent"); const waitCalls = calls.filter((call) => call.method === "agent.wait"); diff --git a/src/agents/google-gemini-switch.live.test.ts b/src/agents/google-gemini-switch.live.test.ts index 2b793165c..76ca91762 100644 --- a/src/agents/google-gemini-switch.live.test.ts +++ b/src/agents/google-gemini-switch.live.test.ts @@ -3,8 +3,7 @@ import { describe, expect, it } from "vitest"; import { isTruthyEnvValue } from "../infra/env.js"; const GEMINI_KEY = process.env.GEMINI_API_KEY ?? ""; -const LIVE = - isTruthyEnvValue(process.env.GEMINI_LIVE_TEST) || isTruthyEnvValue(process.env.LIVE); +const LIVE = isTruthyEnvValue(process.env.GEMINI_LIVE_TEST) || isTruthyEnvValue(process.env.LIVE); const describeLive = LIVE && GEMINI_KEY ? describe : describe.skip; diff --git a/src/agents/minimax.live.test.ts b/src/agents/minimax.live.test.ts index 5027f8ab5..ca380f2cd 100644 --- a/src/agents/minimax.live.test.ts +++ b/src/agents/minimax.live.test.ts @@ -5,8 +5,7 @@ import { isTruthyEnvValue } from "../infra/env.js"; const MINIMAX_KEY = process.env.MINIMAX_API_KEY ?? ""; const MINIMAX_BASE_URL = process.env.MINIMAX_BASE_URL?.trim() || "https://api.minimax.io/anthropic"; const MINIMAX_MODEL = process.env.MINIMAX_MODEL?.trim() || "MiniMax-M2.1"; -const LIVE = - isTruthyEnvValue(process.env.MINIMAX_LIVE_TEST) || isTruthyEnvValue(process.env.LIVE); +const LIVE = isTruthyEnvValue(process.env.MINIMAX_LIVE_TEST) || isTruthyEnvValue(process.env.LIVE); const describeLive = LIVE && MINIMAX_KEY ? describe : describe.skip; diff --git a/src/agents/models.profiles.live.test.ts b/src/agents/models.profiles.live.test.ts index 77c6f1cd5..b038343ed 100644 --- a/src/agents/models.profiles.live.test.ts +++ b/src/agents/models.profiles.live.test.ts @@ -15,8 +15,7 @@ import { getApiKeyForModel } from "./model-auth.js"; import { ensureClawdbotModelsJson } from "./models-config.js"; import { isRateLimitErrorMessage } from "./pi-embedded-helpers/errors.js"; -const LIVE = - isTruthyEnvValue(process.env.LIVE) || isTruthyEnvValue(process.env.CLAWDBOT_LIVE_TEST); +const LIVE = isTruthyEnvValue(process.env.LIVE) || isTruthyEnvValue(process.env.CLAWDBOT_LIVE_TEST); const DIRECT_ENABLED = Boolean(process.env.CLAWDBOT_LIVE_MODELS?.trim()); const REQUIRE_PROFILE_KEYS = isTruthyEnvValue(process.env.CLAWDBOT_LIVE_REQUIRE_PROFILE_KEYS); diff --git a/src/agents/pi-embedded-runner-extraparams.live.test.ts b/src/agents/pi-embedded-runner-extraparams.live.test.ts index 2ce4d1457..d47e1959f 100644 --- a/src/agents/pi-embedded-runner-extraparams.live.test.ts +++ b/src/agents/pi-embedded-runner-extraparams.live.test.ts @@ -6,8 +6,7 @@ import type { ClawdbotConfig } from "../config/config.js"; import { applyExtraParamsToAgent } from "./pi-embedded-runner.js"; const OPENAI_KEY = process.env.OPENAI_API_KEY ?? ""; -const LIVE = - isTruthyEnvValue(process.env.OPENAI_LIVE_TEST) || isTruthyEnvValue(process.env.LIVE); +const LIVE = isTruthyEnvValue(process.env.OPENAI_LIVE_TEST) || isTruthyEnvValue(process.env.LIVE); const describeLive = LIVE && OPENAI_KEY ? describe : describe.skip; diff --git a/src/auto-reply/reply/agent-runner-execution.ts b/src/auto-reply/reply/agent-runner-execution.ts index 166b6289a..277199de8 100644 --- a/src/auto-reply/reply/agent-runner-execution.ts +++ b/src/auto-reply/reply/agent-runner-execution.ts @@ -18,10 +18,7 @@ import { updateSessionStore, } from "../../config/sessions.js"; import { logVerbose } from "../../globals.js"; -import { - emitAgentEvent, - registerAgentRunContext, -} from "../../infra/agent-events.js"; +import { emitAgentEvent, registerAgentRunContext } from "../../infra/agent-events.js"; import { defaultRuntime } from "../../runtime.js"; import { isMarkdownCapableMessageChannel, @@ -32,20 +29,11 @@ import type { TemplateContext } from "../templating.js"; import type { VerboseLevel } from "../thinking.js"; import { isSilentReplyText, SILENT_REPLY_TOKEN } from "../tokens.js"; import type { GetReplyOptions, ReplyPayload } from "../types.js"; -import { - buildThreadingToolContext, - resolveEnforceFinalTag, -} from "./agent-runner-utils.js"; -import { - createBlockReplyPayloadKey, - type BlockReplyPipeline, -} from "./block-reply-pipeline.js"; +import { buildThreadingToolContext, resolveEnforceFinalTag } from "./agent-runner-utils.js"; +import { createBlockReplyPayloadKey, type BlockReplyPipeline } from "./block-reply-pipeline.js"; import type { FollowupRun } from "./queue.js"; import { parseReplyDirectives } from "./reply-directives.js"; -import { - applyReplyTagsToPayload, - isRenderablePayload, -} from "./reply-payloads.js"; +import { applyReplyTagsToPayload, isRenderablePayload } from "./reply-payloads.js"; import type { TypingSignaler } from "./typing-mode.js"; export type AgentRunLoopResult = @@ -108,12 +96,9 @@ export async function runAgentTurnWithFallback(params: { while (true) { try { const allowPartialStream = !( - params.followupRun.run.reasoningLevel === "stream" && - params.opts?.onReasoningStream + params.followupRun.run.reasoningLevel === "stream" && params.opts?.onReasoningStream ); - const normalizeStreamingText = ( - payload: ReplyPayload, - ): { text?: string; skip: boolean } => { + const normalizeStreamingText = (payload: ReplyPayload): { text?: string; skip: boolean } => { if (!allowPartialStream) return { skip: true }; let text = payload.text; if (!params.isHeartbeat && text?.includes("HEARTBEAT_OK")) { @@ -137,9 +122,7 @@ export async function runAgentTurnWithFallback(params: { if (!sanitized.trim()) return { skip: true }; return { text: sanitized, skip: false }; }; - const handlePartialForTyping = async ( - payload: ReplyPayload, - ): Promise => { + const handlePartialForTyping = async (payload: ReplyPayload): Promise => { const { text, skip } = normalizeStreamingText(payload); if (skip || !text) return undefined; await params.typingSignals.signalTextDelta(text); @@ -174,10 +157,7 @@ export async function runAgentTurnWithFallback(params: { startedAt, }, }); - const cliSessionId = getCliSessionId( - params.getActiveSessionEntry(), - provider, - ); + const cliSessionId = getCliSessionId(params.getActiveSessionEntry(), provider); return runCliAgent({ sessionId: params.followupRun.run.sessionId, sessionKey: params.sessionKey, @@ -227,8 +207,7 @@ export async function runAgentTurnWithFallback(params: { return runEmbeddedPiAgent({ sessionId: params.followupRun.run.sessionId, sessionKey: params.sessionKey, - messageProvider: - params.sessionCtx.Provider?.trim().toLowerCase() || undefined, + messageProvider: params.sessionCtx.Provider?.trim().toLowerCase() || undefined, agentAccountId: params.sessionCtx.AccountId, // Provider threading context for tool auto-injection ...buildThreadingToolContext({ @@ -244,10 +223,7 @@ export async function runAgentTurnWithFallback(params: { prompt: params.commandBody, extraSystemPrompt: params.followupRun.run.extraSystemPrompt, ownerNumbers: params.followupRun.run.ownerNumbers, - enforceFinalTag: resolveEnforceFinalTag( - params.followupRun.run, - provider, - ), + enforceFinalTag: resolveEnforceFinalTag(params.followupRun.run, provider), provider, model, authProfileId, @@ -264,9 +240,7 @@ export async function runAgentTurnWithFallback(params: { params.sessionCtx.Provider, ); if (!channel) return "markdown"; - return isMarkdownCapableMessageChannel(channel) - ? "markdown" - : "plain"; + return isMarkdownCapableMessageChannel(channel) ? "markdown" : "plain"; })(), bashElevated: params.followupRun.run.bashElevated, timeoutMs: params.followupRun.run.timeoutMs, @@ -276,11 +250,7 @@ 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, @@ -291,8 +261,7 @@ export async function runAgentTurnWithFallback(params: { await params.typingSignals.signalMessageStart(); }, onReasoningStream: - params.typingSignals.shouldStartOnReasoning || - params.opts?.onReasoningStream + params.typingSignals.shouldStartOnReasoning || params.opts?.onReasoningStream ? async (payload) => { await params.typingSignals.signalReasoningDelta(); await params.opts?.onReasoningStream?.({ @@ -305,16 +274,14 @@ export async function runAgentTurnWithFallback(params: { // Trigger typing when tools start executing. // Must await to ensure typing indicator starts before tool summaries are emitted. if (evt.stream === "tool") { - const phase = - typeof evt.data.phase === "string" ? evt.data.phase : ""; + const phase = typeof evt.data.phase === "string" ? evt.data.phase : ""; if (phase === "start" || phase === "update") { await params.typingSignals.signalToolStart(); } } // Track auto-compaction completion if (evt.stream === "compaction") { - const phase = - typeof evt.data.phase === "string" ? evt.data.phase : ""; + const phase = typeof evt.data.phase === "string" ? evt.data.phase : ""; const willRetry = Boolean(evt.data.willRetry); if (phase === "end" && !willRetry) { autoCompactionCompleted = true; @@ -338,22 +305,14 @@ export async function runAgentTurnWithFallback(params: { params.sessionCtx.MessageSid, ); // Let through payloads with audioAsVoice flag even if empty (need to track it) - if ( - !isRenderablePayload(taggedPayload) && - !payload.audioAsVoice - ) - return; - const parsed = parseReplyDirectives( - taggedPayload.text ?? "", - { - currentMessageId: params.sessionCtx.MessageSid, - silentToken: SILENT_REPLY_TOKEN, - }, - ); + if (!isRenderablePayload(taggedPayload) && !payload.audioAsVoice) return; + const parsed = parseReplyDirectives(taggedPayload.text ?? "", { + currentMessageId: params.sessionCtx.MessageSid, + silentToken: SILENT_REPLY_TOKEN, + }); const cleaned = parsed.text || undefined; const hasRenderableMedia = - Boolean(taggedPayload.mediaUrl) || - (taggedPayload.mediaUrls?.length ?? 0) > 0; + Boolean(taggedPayload.mediaUrl) || (taggedPayload.mediaUrls?.length ?? 0) > 0; // Skip empty payloads unless they have audioAsVoice flag (need to track it) if ( !cleaned && @@ -367,35 +326,25 @@ export async function runAgentTurnWithFallback(params: { const blockPayload: ReplyPayload = params.applyReplyToMode({ ...taggedPayload, text: cleaned, - audioAsVoice: Boolean( - parsed.audioAsVoice || payload.audioAsVoice, - ), + audioAsVoice: Boolean(parsed.audioAsVoice || payload.audioAsVoice), replyToId: taggedPayload.replyToId ?? parsed.replyToId, replyToTag: taggedPayload.replyToTag || parsed.replyToTag, - replyToCurrent: - taggedPayload.replyToCurrent || parsed.replyToCurrent, + replyToCurrent: taggedPayload.replyToCurrent || parsed.replyToCurrent, }); void params.typingSignals .signalTextDelta(cleaned ?? taggedPayload.text) .catch((err) => { - logVerbose( - `block reply typing signal failed: ${String(err)}`, - ); + logVerbose(`block reply typing signal failed: ${String(err)}`); }); // Use pipeline if available (block streaming enabled), otherwise send directly - if ( - params.blockStreamingEnabled && - params.blockReplyPipeline - ) { + if (params.blockStreamingEnabled && params.blockReplyPipeline) { params.blockReplyPipeline.enqueue(blockPayload); } else { // Send directly when flushing before tool execution (no streaming). // Track sent key to avoid duplicate in final payloads. - directlySentBlockKeys.add( - createBlockReplyPayloadKey(blockPayload), - ); + directlySentBlockKeys.add(createBlockReplyPayloadKey(blockPayload)); await params.opts?.onBlockReply?.(blockPayload); } } @@ -456,9 +405,7 @@ export async function runAgentTurnWithFallback(params: { }; } if (embeddedError?.kind === "role_ordering") { - const didReset = await params.resetSessionAfterRoleOrderingConflict( - embeddedError.message, - ); + const didReset = await params.resetSessionAfterRoleOrderingConflict(embeddedError.message); if (didReset) { return { kind: "final", @@ -476,10 +423,8 @@ export async function runAgentTurnWithFallback(params: { isContextOverflowError(message) || /context.*overflow|too large|context window/i.test(message); const isCompactionFailure = isCompactionFailureError(message); - const isSessionCorruption = - /function call turn comes immediately after/i.test(message); - const isRoleOrderingError = - /incorrect role information|roles must alternate/i.test(message); + const isSessionCorruption = /function call turn comes immediately after/i.test(message); + const isRoleOrderingError = /incorrect role information|roles must alternate/i.test(message); if ( isCompactionFailure && @@ -495,8 +440,7 @@ export async function runAgentTurnWithFallback(params: { }; } if (isRoleOrderingError) { - const didReset = - await params.resetSessionAfterRoleOrderingConflict(message); + const didReset = await params.resetSessionAfterRoleOrderingConflict(message); if (didReset) { return { kind: "final", @@ -523,8 +467,7 @@ export async function runAgentTurnWithFallback(params: { try { // Delete transcript file if it exists if (corruptedSessionId) { - const transcriptPath = - resolveSessionTranscriptPath(corruptedSessionId); + const transcriptPath = resolveSessionTranscriptPath(corruptedSessionId); try { fs.unlinkSync(transcriptPath); } catch { @@ -574,7 +517,6 @@ export async function runAgentTurnWithFallback(params: { fallbackModel, didLogHeartbeatStrip, autoCompactionCompleted, - directlySentBlockKeys: - directlySentBlockKeys.size > 0 ? directlySentBlockKeys : undefined, + directlySentBlockKeys: directlySentBlockKeys.size > 0 ? directlySentBlockKeys : undefined, }; } diff --git a/src/auto-reply/reply/agent-runner.heartbeat-typing.runreplyagent-typing-heartbeat.retries-after-compaction-failure-by-resetting-session.test.ts b/src/auto-reply/reply/agent-runner.heartbeat-typing.runreplyagent-typing-heartbeat.retries-after-compaction-failure-by-resetting-session.test.ts index 4d5e3c1fc..8b6c25de6 100644 --- a/src/auto-reply/reply/agent-runner.heartbeat-typing.runreplyagent-typing-heartbeat.retries-after-compaction-failure-by-resetting-session.test.ts +++ b/src/auto-reply/reply/agent-runner.heartbeat-typing.runreplyagent-typing-heartbeat.retries-after-compaction-failure-by-resetting-session.test.ts @@ -196,8 +196,7 @@ describe("runReplyAgent typing (heartbeat)", () => { durationMs: 1, error: { kind: "context_overflow", - message: - 'Context overflow: Summarization failed: 400 {"message":"prompt is too long"}', + message: 'Context overflow: Summarization failed: 400 {"message":"prompt is too long"}', }, }, })); diff --git a/src/browser/pw-session.browserless.live.test.ts b/src/browser/pw-session.browserless.live.test.ts index b769b7f8d..4207a5031 100644 --- a/src/browser/pw-session.browserless.live.test.ts +++ b/src/browser/pw-session.browserless.live.test.ts @@ -1,8 +1,7 @@ import { describe, it } from "vitest"; import { isTruthyEnvValue } from "../infra/env.js"; -const LIVE = - isTruthyEnvValue(process.env.LIVE) || isTruthyEnvValue(process.env.CLAWDBOT_LIVE_TEST); +const LIVE = isTruthyEnvValue(process.env.LIVE) || isTruthyEnvValue(process.env.CLAWDBOT_LIVE_TEST); const CDP_URL = process.env.CLAWDBOT_LIVE_BROWSER_CDP_URL?.trim() || ""; const describeLive = LIVE && CDP_URL ? describe : describe.skip; diff --git a/src/channels/plugins/onboarding/discord.ts b/src/channels/plugins/onboarding/discord.ts index a63d6fc6a..b7c19af0d 100644 --- a/src/channels/plugins/onboarding/discord.ts +++ b/src/channels/plugins/onboarding/discord.ts @@ -180,7 +180,7 @@ async function promptDiscordAllowFrom(params: { }): Promise { const accountId = params.accountId && normalizeAccountId(params.accountId) - ? normalizeAccountId(params.accountId) ?? DEFAULT_ACCOUNT_ID + ? (normalizeAccountId(params.accountId) ?? DEFAULT_ACCOUNT_ID) : resolveDefaultDiscordAccountId(params.cfg); const resolved = resolveDiscordAccount({ cfg: params.cfg, accountId }); const token = resolved.token; @@ -249,9 +249,7 @@ async function promptDiscordAllowFrom(params: { continue; } const ids = results.map((res) => res.id as string); - const unique = [ - ...new Set([...existing.map((v) => String(v).trim()).filter(Boolean), ...ids]), - ]; + const unique = [...new Set([...existing.map((v) => String(v).trim()).filter(Boolean), ...ids])]; return setDiscordAllowFrom(params.cfg, unique); } } diff --git a/src/channels/plugins/onboarding/imessage.ts b/src/channels/plugins/onboarding/imessage.ts index 897daad4b..f48006ea4 100644 --- a/src/channels/plugins/onboarding/imessage.ts +++ b/src/channels/plugins/onboarding/imessage.ts @@ -80,7 +80,7 @@ async function promptIMessageAllowFrom(params: { }): Promise { const accountId = params.accountId && normalizeAccountId(params.accountId) - ? normalizeAccountId(params.accountId) ?? DEFAULT_ACCOUNT_ID + ? (normalizeAccountId(params.accountId) ?? DEFAULT_ACCOUNT_ID) : resolveDefaultIMessageAccountId(params.cfg); const resolved = resolveIMessageAccount({ cfg: params.cfg, accountId }); const existing = resolved.config.allowFrom ?? []; diff --git a/src/channels/plugins/onboarding/signal.ts b/src/channels/plugins/onboarding/signal.ts index 3358b21b7..d8ec0b667 100644 --- a/src/channels/plugins/onboarding/signal.ts +++ b/src/channels/plugins/onboarding/signal.ts @@ -85,7 +85,7 @@ async function promptSignalAllowFrom(params: { }): Promise { const accountId = params.accountId && normalizeAccountId(params.accountId) - ? normalizeAccountId(params.accountId) ?? DEFAULT_ACCOUNT_ID + ? (normalizeAccountId(params.accountId) ?? DEFAULT_ACCOUNT_ID) : resolveDefaultSignalAccountId(params.cfg); const resolved = resolveSignalAccount({ cfg: params.cfg, accountId }); const existing = resolved.config.allowFrom ?? []; diff --git a/src/channels/plugins/onboarding/slack.ts b/src/channels/plugins/onboarding/slack.ts index 4b8144b19..6d23bb184 100644 --- a/src/channels/plugins/onboarding/slack.ts +++ b/src/channels/plugins/onboarding/slack.ts @@ -232,7 +232,7 @@ async function promptSlackAllowFrom(params: { }): Promise { const accountId = params.accountId && normalizeAccountId(params.accountId) - ? normalizeAccountId(params.accountId) ?? DEFAULT_ACCOUNT_ID + ? (normalizeAccountId(params.accountId) ?? DEFAULT_ACCOUNT_ID) : resolveDefaultSlackAccountId(params.cfg); const resolved = resolveSlackAccount({ cfg: params.cfg, accountId }); const token = resolved.config.userToken ?? resolved.config.botToken ?? ""; @@ -299,9 +299,7 @@ async function promptSlackAllowFrom(params: { continue; } const ids = results.map((res) => res.id as string); - const unique = [ - ...new Set([...existing.map((v) => String(v).trim()).filter(Boolean), ...ids]), - ]; + const unique = [...new Set([...existing.map((v) => String(v).trim()).filter(Boolean), ...ids])]; return setSlackAllowFrom(params.cfg, unique); } } diff --git a/src/channels/plugins/onboarding/telegram.ts b/src/channels/plugins/onboarding/telegram.ts index f0725c038..9150887c9 100644 --- a/src/channels/plugins/onboarding/telegram.ts +++ b/src/channels/plugins/onboarding/telegram.ts @@ -80,9 +80,10 @@ async function promptTelegramAllowFrom(params: { const username = stripped.startsWith("@") ? stripped : `@${stripped}`; const url = `https://api.telegram.org/bot${token}/getChat?chat_id=${encodeURIComponent(username)}`; const res = await fetch(url); - const data = (await res.json().catch(() => null)) as - | { ok?: boolean; result?: { id?: number | string } } - | 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); return null; @@ -164,7 +165,7 @@ async function promptTelegramAllowFromForAccount(params: { }): Promise { const accountId = params.accountId && normalizeAccountId(params.accountId) - ? normalizeAccountId(params.accountId) ?? DEFAULT_ACCOUNT_ID + ? (normalizeAccountId(params.accountId) ?? DEFAULT_ACCOUNT_ID) : resolveDefaultTelegramAccountId(params.cfg); return promptTelegramAllowFrom({ cfg: params.cfg, diff --git a/src/cli/argv.test.ts b/src/cli/argv.test.ts index 670d3fd58..2b75cedeb 100644 --- a/src/cli/argv.test.ts +++ b/src/cli/argv.test.ts @@ -17,16 +17,9 @@ describe("argv helpers", () => { }); it("extracts command path ignoring flags and terminator", () => { - expect(getCommandPath(["node", "clawdbot", "status", "--json"], 2)).toEqual([ - "status", - ]); - expect(getCommandPath(["node", "clawdbot", "agents", "list"], 2)).toEqual([ - "agents", - "list", - ]); - expect(getCommandPath(["node", "clawdbot", "status", "--", "ignored"], 2)).toEqual([ - "status", - ]); + expect(getCommandPath(["node", "clawdbot", "status", "--json"], 2)).toEqual(["status"]); + expect(getCommandPath(["node", "clawdbot", "agents", "list"], 2)).toEqual(["agents", "list"]); + expect(getCommandPath(["node", "clawdbot", "status", "--", "ignored"], 2)).toEqual(["status"]); }); it("returns primary command", () => { diff --git a/src/cli/gateway.sigterm.test.ts b/src/cli/gateway.sigterm.test.ts index 5577c29f4..0cde11b39 100644 --- a/src/cli/gateway.sigterm.test.ts +++ b/src/cli/gateway.sigterm.test.ts @@ -87,37 +87,34 @@ describe("gateway SIGTERM", () => { const out: string[] = []; const err: string[] = []; - const bunBin = process.env.BUN_INSTALL - ? path.join(process.env.BUN_INSTALL, "bin", "bun") - : "bun"; + const nodeBin = process.execPath; + const args = [ + "--import", + "tsx", + "src/entry.ts", + "gateway", + "--port", + String(port), + "--bind", + "loopback", + "--allow-unconfigured", + ]; - child = spawn( - bunBin, - [ - "src/entry.ts", - "gateway", - "--port", - String(port), - "--bind", - "loopback", - "--allow-unconfigured", - ], - { - cwd: process.cwd(), - env: { - ...process.env, - CLAWDBOT_STATE_DIR: stateDir, - CLAWDBOT_CONFIG_PATH: configPath, - CLAWDBOT_SKIP_CHANNELS: "1", - CLAWDBOT_SKIP_BROWSER_CONTROL_SERVER: "1", - CLAWDBOT_SKIP_CANVAS_HOST: "1", - // Avoid port collisions with other test processes that may also start a bridge server. - CLAWDBOT_BRIDGE_HOST: "127.0.0.1", - CLAWDBOT_BRIDGE_PORT: "0", - }, - stdio: ["ignore", "pipe", "pipe"], + child = spawn(nodeBin, args, { + cwd: process.cwd(), + env: { + ...process.env, + CLAWDBOT_STATE_DIR: stateDir, + CLAWDBOT_CONFIG_PATH: configPath, + CLAWDBOT_SKIP_CHANNELS: "1", + CLAWDBOT_SKIP_BROWSER_CONTROL_SERVER: "1", + CLAWDBOT_SKIP_CANVAS_HOST: "1", + // Avoid port collisions with other test processes that may also start a bridge server. + CLAWDBOT_BRIDGE_HOST: "127.0.0.1", + CLAWDBOT_BRIDGE_PORT: "0", }, - ); + stdio: ["ignore", "pipe", "pipe"], + }); const proc = child; if (!proc) throw new Error("failed to spawn gateway"); diff --git a/src/cli/memory-cli.ts b/src/cli/memory-cli.ts index 93e852b27..6a44cc30b 100644 --- a/src/cli/memory-cli.ts +++ b/src/cli/memory-cli.ts @@ -149,9 +149,7 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) { `(requested: ${status.requestedProvider})`, )}`, `${label("Model")} ${info(status.model)}`, - status.sources?.length - ? `${label("Sources")} ${info(status.sources.join(", "))}` - : null, + status.sources?.length ? `${label("Sources")} ${info(status.sources.join(", "))}` : null, `${label("Indexed")} ${success(`${status.files} files · ${status.chunks} chunks`)}`, `${label("Dirty")} ${status.dirty ? warn("yes") : muted("no")}`, `${label("Store")} ${info(status.dbPath)}`, @@ -222,9 +220,7 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) { status.cache.enabled && typeof status.cache.entries === "number" ? ` (${status.cache.entries} entries)` : ""; - lines.push( - `${label("Embedding cache")} ${colorize(rich, cacheColor, cacheState)}${suffix}`, - ); + lines.push(`${label("Embedding cache")} ${colorize(rich, cacheColor, cacheState)}${suffix}`); if (status.cache.enabled && typeof status.cache.maxEntries === "number") { lines.push(`${label("Cache cap")} ${info(String(status.cache.maxEntries))}`); } diff --git a/src/cli/program/register.agent.ts b/src/cli/program/register.agent.ts index db277dc92..cfde6709c 100644 --- a/src/cli/program/register.agent.ts +++ b/src/cli/program/register.agent.ts @@ -33,11 +33,7 @@ export function registerAgentCommands(program: Command, args: { agentChannelOpti "Run the embedded agent locally (requires model provider API keys in your shell)", false, ) - .option( - "--deliver", - "Send the agent's reply back to the selected channel", - false, - ) + .option("--deliver", "Send the agent's reply back to the selected channel", false) .option("--json", "Output result as JSON", false) .option( "--timeout ", diff --git a/src/cli/program/register.subclis.test.ts b/src/cli/program/register.subclis.test.ts index 5094c4687..dfd8b5852 100644 --- a/src/cli/program/register.subclis.test.ts +++ b/src/cli/program/register.subclis.test.ts @@ -78,4 +78,4 @@ describe("registerSubCliCommands", () => { expect(registerNodesCli).toHaveBeenCalledTimes(1); expect(nodesAction).toHaveBeenCalledTimes(1); }); -}); \ No newline at end of file +}); diff --git a/src/commands/agent-via-gateway.ts b/src/commands/agent-via-gateway.ts index 43d8c0711..d43598d61 100644 --- a/src/commands/agent-via-gateway.ts +++ b/src/commands/agent-via-gateway.ts @@ -51,7 +51,6 @@ export type AgentCliOpts = { local?: boolean; }; - function parseTimeoutSeconds(opts: { cfg: ReturnType; timeout?: string }) { const raw = opts.timeout !== undefined diff --git a/src/config/io.ts b/src/config/io.ts index 9170a4876..29e3d1d3a 100644 --- a/src/config/io.ts +++ b/src/config/io.ts @@ -586,13 +586,11 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) { // module scope. `CLAWDBOT_CONFIG_PATH` (and friends) are expected to work even // when set after the module has been imported (tests, one-off scripts, etc.). const DEFAULT_CONFIG_CACHE_MS = 200; -let configCache: - | { - configPath: string; - expiresAt: number; - config: ClawdbotConfig; - } - | null = null; +let configCache: { + configPath: string; + expiresAt: number; + config: ClawdbotConfig; +} | null = null; function resolveConfigCacheMs(env: NodeJS.ProcessEnv): number { const raw = env.CLAWDBOT_CONFIG_CACHE_MS?.trim(); diff --git a/src/gateway/gateway-cli-backend.live.test.ts b/src/gateway/gateway-cli-backend.live.test.ts index 6fb5fdc37..2d14a25c7 100644 --- a/src/gateway/gateway-cli-backend.live.test.ts +++ b/src/gateway/gateway-cli-backend.live.test.ts @@ -12,8 +12,7 @@ import { GatewayClient } from "./client.js"; import { renderCatNoncePngBase64 } from "./live-image-probe.js"; import { startGatewayServer } from "./server.js"; -const LIVE = - isTruthyEnvValue(process.env.LIVE) || isTruthyEnvValue(process.env.CLAWDBOT_LIVE_TEST); +const LIVE = isTruthyEnvValue(process.env.LIVE) || isTruthyEnvValue(process.env.CLAWDBOT_LIVE_TEST); const CLI_LIVE = isTruthyEnvValue(process.env.CLAWDBOT_LIVE_CLI_BACKEND); const CLI_IMAGE = isTruthyEnvValue(process.env.CLAWDBOT_LIVE_CLI_BACKEND_IMAGE_PROBE); const CLI_RESUME = isTruthyEnvValue(process.env.CLAWDBOT_LIVE_CLI_BACKEND_RESUME_PROBE); diff --git a/src/gateway/gateway-models.profiles.live.test.ts b/src/gateway/gateway-models.profiles.live.test.ts index 63232b97a..3456ade5b 100644 --- a/src/gateway/gateway-models.profiles.live.test.ts +++ b/src/gateway/gateway-models.profiles.live.test.ts @@ -31,8 +31,7 @@ import { GatewayClient } from "./client.js"; import { renderCatNoncePngBase64 } from "./live-image-probe.js"; import { startGatewayServer } from "./server.js"; -const LIVE = - isTruthyEnvValue(process.env.LIVE) || isTruthyEnvValue(process.env.CLAWDBOT_LIVE_TEST); +const LIVE = isTruthyEnvValue(process.env.LIVE) || isTruthyEnvValue(process.env.CLAWDBOT_LIVE_TEST); const GATEWAY_LIVE = isTruthyEnvValue(process.env.CLAWDBOT_LIVE_GATEWAY); const ZAI_FALLBACK = isTruthyEnvValue(process.env.CLAWDBOT_LIVE_GATEWAY_ZAI_FALLBACK); const PROVIDERS = parseFilter(process.env.CLAWDBOT_LIVE_GATEWAY_PROVIDERS); diff --git a/src/gateway/test-helpers.mocks.ts b/src/gateway/test-helpers.mocks.ts index a6b933b03..64bc7f28f 100644 --- a/src/gateway/test-helpers.mocks.ts +++ b/src/gateway/test-helpers.mocks.ts @@ -427,11 +427,15 @@ vi.mock("../config/config.js", async () => { } const fileAgents = - fileConfig.agents && typeof fileConfig.agents === "object" && !Array.isArray(fileConfig.agents) + fileConfig.agents && + typeof fileConfig.agents === "object" && + !Array.isArray(fileConfig.agents) ? (fileConfig.agents as Record) : {}; const fileDefaults = - fileAgents.defaults && typeof fileAgents.defaults === "object" && !Array.isArray(fileAgents.defaults) + fileAgents.defaults && + typeof fileAgents.defaults === "object" && + !Array.isArray(fileAgents.defaults) ? (fileAgents.defaults as Record) : {}; const defaults = { @@ -449,7 +453,9 @@ vi.mock("../config/config.js", async () => { : undefined; const fileChannels = - fileConfig.channels && typeof fileConfig.channels === "object" && !Array.isArray(fileConfig.channels) + fileConfig.channels && + typeof fileConfig.channels === "object" && + !Array.isArray(fileConfig.channels) ? ({ ...(fileConfig.channels as Record) } as Record) : {}; const overrideChannels = @@ -459,7 +465,9 @@ vi.mock("../config/config.js", async () => { const mergedChannels = { ...fileChannels, ...overrideChannels }; if (testState.allowFrom !== undefined) { const existing = - mergedChannels.whatsapp && typeof mergedChannels.whatsapp === "object" && !Array.isArray(mergedChannels.whatsapp) + mergedChannels.whatsapp && + typeof mergedChannels.whatsapp === "object" && + !Array.isArray(mergedChannels.whatsapp) ? (mergedChannels.whatsapp as Record) : {}; mergedChannels.whatsapp = { @@ -470,18 +478,23 @@ vi.mock("../config/config.js", async () => { const channels = Object.keys(mergedChannels).length > 0 ? mergedChannels : undefined; const fileSession = - fileConfig.session && typeof fileConfig.session === "object" && !Array.isArray(fileConfig.session) + fileConfig.session && + typeof fileConfig.session === "object" && + !Array.isArray(fileConfig.session) ? (fileConfig.session as Record) : {}; const session: Record = { ...fileSession, mainKey: fileSession.mainKey ?? "main", }; - if (typeof testState.sessionStorePath === "string") session.store = testState.sessionStorePath; + if (typeof testState.sessionStorePath === "string") + session.store = testState.sessionStorePath; if (testState.sessionConfig) Object.assign(session, testState.sessionConfig); const fileGateway = - fileConfig.gateway && typeof fileConfig.gateway === "object" && !Array.isArray(fileConfig.gateway) + fileConfig.gateway && + typeof fileConfig.gateway === "object" && + !Array.isArray(fileConfig.gateway) ? ({ ...(fileConfig.gateway as Record) } as Record) : {}; if (testState.gatewayBind) fileGateway.bind = testState.gatewayBind; @@ -489,14 +502,16 @@ vi.mock("../config/config.js", async () => { const gateway = Object.keys(fileGateway).length > 0 ? fileGateway : undefined; const fileCanvasHost = - fileConfig.canvasHost && typeof fileConfig.canvasHost === "object" && !Array.isArray(fileConfig.canvasHost) + fileConfig.canvasHost && + typeof fileConfig.canvasHost === "object" && + !Array.isArray(fileConfig.canvasHost) ? ({ ...(fileConfig.canvasHost as Record) } as Record) : {}; - if (typeof testState.canvasHostPort === "number") fileCanvasHost.port = testState.canvasHostPort; + 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); + const hooks = testState.hooksConfig ?? (fileConfig.hooks as HooksConfig | undefined); const fileCron = fileConfig.cron && typeof fileConfig.cron === "object" && !Array.isArray(fileConfig.cron) diff --git a/src/infra/session-cost-usage.ts b/src/infra/session-cost-usage.ts index f47e6f95d..9778f09bb 100644 --- a/src/infra/session-cost-usage.ts +++ b/src/infra/session-cost-usage.ts @@ -93,7 +93,8 @@ const parseUsageEntry = (entry: Record): ParsedUsageEntry | nul const role = message?.role; if (role !== "assistant") return null; - const usageRaw = (message?.usage as UsageLike | undefined) ?? (entry.usage as UsageLike | undefined); + const usageRaw = + (message?.usage as UsageLike | undefined) ?? (entry.usage as UsageLike | undefined); const usage = normalizeUsage(usageRaw); if (!usage) return null; @@ -123,10 +124,7 @@ const applyUsageTotals = (totals: CostUsageTotals, usage: NormalizedUsage) => { totals.cacheWrite += usage.cacheWrite ?? 0; const totalTokens = usage.total ?? - (usage.input ?? 0) + - (usage.output ?? 0) + - (usage.cacheRead ?? 0) + - (usage.cacheWrite ?? 0); + (usage.input ?? 0) + (usage.output ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0); totals.totalTokens += totalTokens; }; diff --git a/src/macos/gateway-daemon.ts b/src/macos/gateway-daemon.ts index cd020cf0b..a8dfd773e 100644 --- a/src/macos/gateway-daemon.ts +++ b/src/macos/gateway-daemon.ts @@ -45,10 +45,7 @@ async function main() { { startGatewayServer }, { setGatewayWsLogStyle }, { setVerbose }, - { - consumeGatewaySigusr1RestartAuthorization, - isGatewaySigusr1RestartExternallyAllowed, - }, + { consumeGatewaySigusr1RestartAuthorization, isGatewaySigusr1RestartExternallyAllowed }, { defaultRuntime }, { enableConsoleCapture, setConsoleTimestampPrefix }, ] = await Promise.all([ diff --git a/src/plugins/loader.test.ts b/src/plugins/loader.test.ts index 94f03b208..36119fc0c 100644 --- a/src/plugins/loader.test.ts +++ b/src/plugins/loader.test.ts @@ -75,7 +75,7 @@ describe("loadClawdbotPlugins", () => { expect(enabled?.status).toBe("loaded"); }); - it("loads bundled telegram plugin when enabled", () => { + it("loads bundled telegram plugin when enabled", { timeout: 120_000 }, () => { process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR = path.join(process.cwd(), "extensions"); const registry = loadClawdbotPlugins({ diff --git a/src/plugins/loader.ts b/src/plugins/loader.ts index 0d4dc7915..d76b39275 100644 --- a/src/plugins/loader.ts +++ b/src/plugins/loader.ts @@ -327,18 +327,7 @@ export function loadClawdbotPlugins(options: PluginLoadOptions = {}): PluginRegi const pluginSdkAlias = resolvePluginSdkAlias(); const jiti = createJiti(import.meta.url, { interopDefault: true, - extensions: [ - ".ts", - ".tsx", - ".mts", - ".cts", - ".mtsx", - ".ctsx", - ".js", - ".mjs", - ".cjs", - ".json", - ], + extensions: [".ts", ".tsx", ".mts", ".cts", ".mtsx", ".ctsx", ".js", ".mjs", ".cjs", ".json"], ...(pluginSdkAlias ? { alias: { "clawdbot/plugin-sdk": pluginSdkAlias } } : {}), }); @@ -393,9 +382,7 @@ export function loadClawdbotPlugins(options: PluginLoadOptions = {}): PluginRegi try { mod = jiti(candidate.source) as ClawdbotPluginModule; } catch (err) { - logger.error( - `[plugins] ${record.id} failed to load from ${record.source}: ${String(err)}`, - ); + logger.error(`[plugins] ${record.id} failed to load from ${record.source}: ${String(err)}`); record.status = "error"; record.error = String(err); registry.plugins.push(record); @@ -480,9 +467,7 @@ export function loadClawdbotPlugins(options: PluginLoadOptions = {}): PluginRegi }); if (!validatedConfig.ok) { - logger.error( - `[plugins] ${record.id} invalid config: ${validatedConfig.errors?.join(", ")}`, - ); + logger.error(`[plugins] ${record.id} invalid config: ${validatedConfig.errors?.join(", ")}`); record.status = "error"; record.error = `invalid config: ${validatedConfig.errors?.join(", ")}`; registry.plugins.push(record); diff --git a/src/telegram/bot-access.ts b/src/telegram/bot-access.ts index 1ac35b064..2f55c1f6b 100644 --- a/src/telegram/bot-access.ts +++ b/src/telegram/bot-access.ts @@ -28,10 +28,9 @@ export const normalizeAllowFromWithStore = (params: { allowFrom?: Array; storeAllowFrom?: string[]; }): NormalizedAllowFrom => { - const combined = [ - ...(params.allowFrom ?? []), - ...(params.storeAllowFrom ?? []), - ].map((value) => String(value).trim()).filter(Boolean); + const combined = [...(params.allowFrom ?? []), ...(params.storeAllowFrom ?? [])] + .map((value) => String(value).trim()) + .filter(Boolean); return normalizeAllowFrom(combined); }; diff --git a/src/tui/components/searchable-select-list.test.ts b/src/tui/components/searchable-select-list.test.ts index 791ecd556..f60496dca 100644 --- a/src/tui/components/searchable-select-list.test.ts +++ b/src/tui/components/searchable-select-list.test.ts @@ -13,8 +13,16 @@ const mockTheme: SearchableSelectListTheme = { }; const testItems = [ - { value: "anthropic/claude-3-opus", label: "anthropic/claude-3-opus", description: "Claude 3 Opus" }, - { value: "anthropic/claude-3-sonnet", label: "anthropic/claude-3-sonnet", description: "Claude 3 Sonnet" }, + { + value: "anthropic/claude-3-opus", + label: "anthropic/claude-3-opus", + description: "Claude 3 Opus", + }, + { + value: "anthropic/claude-3-sonnet", + label: "anthropic/claude-3-sonnet", + description: "Claude 3 Sonnet", + }, { value: "openai/gpt-4", label: "openai/gpt-4", description: "GPT-4" }, { value: "openai/gpt-4-turbo", label: "openai/gpt-4-turbo", description: "GPT-4 Turbo" }, { value: "google/gemini-pro", label: "google/gemini-pro", description: "Gemini Pro" }, @@ -50,7 +58,11 @@ describe("SearchableSelectList", () => { const items = [ { value: "openrouter/auto", label: "openrouter/auto", description: "Routes to best" }, { value: "opus-direct", label: "opus-direct", description: "Direct opus model" }, - { value: "anthropic/claude-3-opus", label: "anthropic/claude-3-opus", description: "Claude 3 Opus" }, + { + value: "anthropic/claude-3-opus", + label: "anthropic/claude-3-opus", + description: "Claude 3 Opus", + }, ]; const list = new SearchableSelectList(items, 5, mockTheme); @@ -66,7 +78,11 @@ describe("SearchableSelectList", () => { it("exact label match beats description match", () => { const items = [ - { value: "provider/other", label: "provider/other", description: "This mentions opus in description" }, + { + value: "provider/other", + label: "provider/other", + description: "This mentions opus in description", + }, { value: "provider/opus-model", label: "provider/opus-model", description: "Something else" }, ]; const list = new SearchableSelectList(items, 5, mockTheme); diff --git a/src/tui/components/searchable-select-list.ts b/src/tui/components/searchable-select-list.ts index 5c4a37d18..058128599 100644 --- a/src/tui/components/searchable-select-list.ts +++ b/src/tui/components/searchable-select-list.ts @@ -98,7 +98,11 @@ export class SearchableSelectList implements Component { exactLabel.sort(this.compareByScore); wordBoundary.sort(this.compareByScore); descriptionMatches.sort(this.compareByScore); - const fuzzyMatches = fuzzyFilter(fuzzyCandidates, query, (i) => `${i.label} ${i.description ?? ""}`); + const fuzzyMatches = fuzzyFilter( + fuzzyCandidates, + query, + (i) => `${i.label} ${i.description ?? ""}`, + ); return [ ...exactLabel.map((s) => s.item), ...wordBoundary.map((s) => s.item), @@ -133,7 +137,10 @@ export class SearchableSelectList implements Component { return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } - private compareByScore = (a: { item: SelectItem; score: number }, b: { item: SelectItem; score: number }) => { + private compareByScore = ( + a: { item: SelectItem; score: number }, + b: { item: SelectItem; score: number }, + ) => { if (a.score !== b.score) return a.score - b.score; return this.getItemLabel(a.item).localeCompare(this.getItemLabel(b.item)); }; @@ -190,7 +197,10 @@ export class SearchableSelectList implements Component { // Calculate visible range with scrolling const startIndex = Math.max( 0, - Math.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.filteredItems.length - this.maxVisible), + Math.min( + this.selectedIndex - Math.floor(this.maxVisible / 2), + this.filteredItems.length - this.maxVisible, + ), ); const endIndex = Math.min(startIndex + this.maxVisible, this.filteredItems.length); @@ -211,7 +221,12 @@ export class SearchableSelectList implements Component { return lines; } - private renderItemLine(item: SelectItem, isSelected: boolean, width: number, query: string): string { + private renderItemLine( + item: SelectItem, + isSelected: boolean, + width: number, + query: string, + ): string { const prefix = isSelected ? "→ " : " "; const prefixWidth = prefix.length; const displayValue = this.getItemLabel(item); diff --git a/src/tui/tui-command-handlers.ts b/src/tui/tui-command-handlers.ts index d48094ae7..dbf7e252e 100644 --- a/src/tui/tui-command-handlers.ts +++ b/src/tui/tui-command-handlers.ts @@ -7,7 +7,11 @@ import { import { normalizeAgentId } from "../routing/session-key.js"; import { helpText, parseCommand } from "./commands.js"; import type { ChatLog } from "./components/chat-log.js"; -import { createSearchableSelectList, createSelectList, createSettingsList } from "./components/selectors.js"; +import { + createSearchableSelectList, + createSelectList, + createSettingsList, +} from "./components/selectors.js"; import type { GatewayChatClient } from "./gateway-chat.js"; import { formatStatusSummary } from "./tui-status-summary.js"; import type { diff --git a/src/tui/tui.ts b/src/tui/tui.ts index 059cd3af7..f8e828b1d 100644 --- a/src/tui/tui.ts +++ b/src/tui/tui.ts @@ -22,10 +22,7 @@ import { editorTheme, theme } from "./theme/theme.js"; import { createCommandHandlers } from "./tui-command-handlers.js"; import { createEventHandlers } from "./tui-event-handlers.js"; import { formatTokens } from "./tui-formatters.js"; -import { - buildWaitingStatusMessage, - defaultWaitingPhrases, -} from "./tui-waiting.js"; +import { buildWaitingStatusMessage, defaultWaitingPhrases } from "./tui-waiting.js"; import { createOverlayHandlers } from "./tui-overlays.js"; import { createSessionActions } from "./tui-session-actions.js"; import type { @@ -335,8 +332,7 @@ export async function runTui(opts: TuiOptions) { // Pick a phrase once per waiting session. if (!waitingPhrase) { const idx = Math.floor(Math.random() * defaultWaitingPhrases.length); - waitingPhrase = - defaultWaitingPhrases[idx] ?? defaultWaitingPhrases[0] ?? "waiting"; + waitingPhrase = defaultWaitingPhrases[idx] ?? defaultWaitingPhrases[0] ?? "waiting"; } waitingTick = 0;