diff --git a/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift b/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift index 4d8b8223b..6be608f70 100644 --- a/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift +++ b/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift @@ -1679,6 +1679,27 @@ public struct ChatAbortParams: Codable, Sendable { } } +public struct ChatInjectParams: Codable, Sendable { + public let sessionkey: String + public let message: String + public let label: String? + + public init( + sessionkey: String, + message: String, + label: String? + ) { + self.sessionkey = sessionkey + self.message = message + self.label = label + } + private enum CodingKeys: String, CodingKey { + case sessionkey = "sessionKey" + case message + case label + } +} + public struct ChatEvent: Codable, Sendable { public let runid: String public let sessionkey: String diff --git a/src/agents/cli-runner.test.ts b/src/agents/cli-runner.test.ts index b7a505e11..ca3ea0b90 100644 --- a/src/agents/cli-runner.test.ts +++ b/src/agents/cli-runner.test.ts @@ -126,15 +126,7 @@ describe("cleanupSuspendedCliProcesses", () => { await cleanupSuspendedCliProcesses( { command: "codex", - resumeArgs: [ - "exec", - "resume", - "{sessionId}", - "--color", - "never", - "--sandbox", - "read-only", - ], + resumeArgs: ["exec", "resume", "{sessionId}", "--color", "never", "--sandbox", "read-only"], } as CliBackendConfig, 1, ); diff --git a/src/agents/pi-embedded-helpers.sanitizeuserfacingtext.test.ts b/src/agents/pi-embedded-helpers.sanitizeuserfacingtext.test.ts index 553e62efc..df84dd6cd 100644 --- a/src/agents/pi-embedded-helpers.sanitizeuserfacingtext.test.ts +++ b/src/agents/pi-embedded-helpers.sanitizeuserfacingtext.test.ts @@ -25,8 +25,6 @@ describe("sanitizeUserFacingText", () => { it("sanitizes raw API error payloads", () => { const raw = '{"type":"error","error":{"message":"Something exploded","type":"server_error"}}'; - expect(sanitizeUserFacingText(raw)).toBe( - "The AI service returned an error. Please try again.", - ); + expect(sanitizeUserFacingText(raw)).toBe("The AI service returned an error. Please try again."); }); }); diff --git a/src/agents/pi-embedded-helpers/errors.ts b/src/agents/pi-embedded-helpers/errors.ts index 307d0c9f8..f4e4736ab 100644 --- a/src/agents/pi-embedded-helpers/errors.ts +++ b/src/agents/pi-embedded-helpers/errors.ts @@ -230,7 +230,11 @@ export function formatAssistantErrorText( } // Catch role ordering errors - including JSON-wrapped and "400" prefix variants - if (/incorrect role information|roles must alternate|400.*role|"message".*role.*information/i.test(raw)) { + if ( + /incorrect role information|roles must alternate|400.*role|"message".*role.*information/i.test( + raw, + ) + ) { return ( "Message ordering conflict - please try again. " + "If this persists, use /new to start a fresh session." diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index 4ba7a5016..fa4a1efa2 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -422,7 +422,8 @@ export async function runEmbeddedAttempt( // Check if last message is a user message to prevent consecutive user turns const lastMsg = activeSession.messages[activeSession.messages.length - 1]; - const lastMsgRole = lastMsg && typeof lastMsg === "object" ? (lastMsg as { role?: unknown }).role : undefined; + const lastMsgRole = + lastMsg && typeof lastMsg === "object" ? (lastMsg as { role?: unknown }).role : undefined; if (lastMsgRole === "user") { // Last message was a user message. Adding another user message would create @@ -433,9 +434,11 @@ export async function runEmbeddedAttempt( // Skip this prompt to prevent "400 Incorrect role information" error. log.warn( `Skipping prompt because last message is a user message (would create consecutive user turns). ` + - `runId=${params.runId} sessionId=${params.sessionId}` + `runId=${params.runId} sessionId=${params.sessionId}`, + ); + promptError = new Error( + "Incorrect role information: consecutive user messages would violate role ordering", ); - promptError = new Error("Incorrect role information: consecutive user messages would violate role ordering"); } else { try { await activeSession.prompt(params.prompt, { images: params.images }); diff --git a/src/agents/system-prompt.ts b/src/agents/system-prompt.ts index d4447ed41..03c636b96 100644 --- a/src/agents/system-prompt.ts +++ b/src/agents/system-prompt.ts @@ -27,10 +27,7 @@ function buildSkillsSection(params: { ]; } -function buildMemorySection(params: { - isMinimal: boolean; - availableTools: Set; -}) { +function buildMemorySection(params: { isMinimal: boolean; availableTools: Set }) { if (params.isMinimal) return []; if (!params.availableTools.has("memory_search") && !params.availableTools.has("memory_get")) { return []; diff --git a/src/auto-reply/reply/commands-status.ts b/src/auto-reply/reply/commands-status.ts index 93ec426c5..6516df199 100644 --- a/src/auto-reply/reply/commands-status.ts +++ b/src/auto-reply/reply/commands-status.ts @@ -156,10 +156,7 @@ export async function buildStatusReply(params: { usageProviders.add(currentUsageProvider); } const usageByProvider = new Map(); - let usageSummaryCache: - | Awaited> - | null - | undefined; + let usageSummaryCache: Awaited> | null | undefined; if (usageProviders.size > 0) { try { usageSummaryCache = await loadProviderUsageSummary({ diff --git a/src/auto-reply/reply/reply-payloads.ts b/src/auto-reply/reply/reply-payloads.ts index f9b212377..31303e23a 100644 --- a/src/auto-reply/reply/reply-payloads.ts +++ b/src/auto-reply/reply/reply-payloads.ts @@ -43,9 +43,9 @@ export function applyReplyTagsToPayload( export function isRenderablePayload(payload: ReplyPayload): boolean { return Boolean( payload.text || - payload.mediaUrl || - (payload.mediaUrls && payload.mediaUrls.length > 0) || - payload.audioAsVoice, + payload.mediaUrl || + (payload.mediaUrls && payload.mediaUrls.length > 0) || + payload.audioAsVoice, ); } diff --git a/src/browser/chrome.executables.ts b/src/browser/chrome.executables.ts index 1dda2fc72..aa56ff7be 100644 --- a/src/browser/chrome.executables.ts +++ b/src/browser/chrome.executables.ts @@ -49,7 +49,10 @@ export function findChromeExecutableMac(): BrowserExecutable | null { }, { kind: "edge", - path: path.join(os.homedir(), "Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"), + path: path.join( + os.homedir(), + "Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge", + ), }, { kind: "chromium", diff --git a/src/channels/plugins/index.ts b/src/channels/plugins/index.ts index 33e48fa91..49e1767c8 100644 --- a/src/channels/plugins/index.ts +++ b/src/channels/plugins/index.ts @@ -19,14 +19,7 @@ import { getActivePluginRegistry } from "../../plugins/runtime.js"; // - add an entry to `src/channels/dock.ts` for shared behavior (capabilities, allowFrom, threading, …) // - add ids/aliases in `src/channels/registry.ts` function resolveCoreChannels(): ChannelPlugin[] { - return [ - telegramPlugin, - whatsappPlugin, - discordPlugin, - slackPlugin, - signalPlugin, - imessagePlugin, - ]; + return [telegramPlugin, whatsappPlugin, discordPlugin, slackPlugin, signalPlugin, imessagePlugin]; } function listPluginChannels(): ChannelPlugin[] { @@ -80,12 +73,5 @@ export function normalizeChannelId(raw?: string | null): ChannelId | null { return plugin?.id ?? null; } -export { - discordPlugin, - imessagePlugin, - signalPlugin, - slackPlugin, - telegramPlugin, - whatsappPlugin, -}; +export { discordPlugin, imessagePlugin, signalPlugin, slackPlugin, telegramPlugin, whatsappPlugin }; export type { ChannelId, ChannelPlugin } from "./types.js"; diff --git a/src/cli/models-cli.ts b/src/cli/models-cli.ts index 40d5b26da..3268e9aba 100644 --- a/src/cli/models-cli.ts +++ b/src/cli/models-cli.ts @@ -4,14 +4,14 @@ import { githubCopilotLoginCommand, modelsAliasesAddCommand, modelsAliasesListCommand, - modelsAliasesRemoveCommand, - modelsAuthAddCommand, - modelsAuthLoginCommand, - modelsAuthOrderClearCommand, - modelsAuthOrderGetCommand, - modelsAuthOrderSetCommand, - modelsAuthPasteTokenCommand, - modelsAuthSetupTokenCommand, + modelsAliasesRemoveCommand, + modelsAuthAddCommand, + modelsAuthLoginCommand, + modelsAuthOrderClearCommand, + modelsAuthOrderGetCommand, + modelsAuthOrderSetCommand, + modelsAuthPasteTokenCommand, + modelsAuthSetupTokenCommand, modelsFallbacksAddCommand, modelsFallbacksClearCommand, modelsFallbacksListCommand, diff --git a/src/commands/configure.wizard.test.ts b/src/commands/configure.wizard.test.ts index e416c0644..2fb68ebdc 100644 --- a/src/commands/configure.wizard.test.ts +++ b/src/commands/configure.wizard.test.ts @@ -119,11 +119,14 @@ describe("runConfigureWizard", () => { mocks.clackText.mockResolvedValue(""); mocks.clackConfirm.mockResolvedValue(false); - await runConfigureWizard({ command: "configure" }, { - log: vi.fn(), - error: vi.fn(), - exit: vi.fn(), - }); + await runConfigureWizard( + { command: "configure" }, + { + log: vi.fn(), + error: vi.fn(), + exit: vi.fn(), + }, + ); expect(mocks.writeConfigFile).toHaveBeenCalledWith( expect.objectContaining({ diff --git a/src/commands/models/auth.ts b/src/commands/models/auth.ts index 5a12731a5..b6f04760c 100644 --- a/src/commands/models/auth.ts +++ b/src/commands/models/auth.ts @@ -8,10 +8,18 @@ import { upsertAuthProfile, } from "../../agents/auth-profiles.js"; import { normalizeProviderId } from "../../agents/model-selection.js"; -import { resolveAgentDir, resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../../agents/agent-scope.js"; +import { + resolveAgentDir, + resolveAgentWorkspaceDir, + resolveDefaultAgentId, +} from "../../agents/agent-scope.js"; import { resolveDefaultAgentWorkspaceDir } from "../../agents/workspace.js"; import { parseDurationMs } from "../../cli/parse-duration.js"; -import { CONFIG_PATH_CLAWDBOT, readConfigFileSnapshot, type ClawdbotConfig } from "../../config/config.js"; +import { + CONFIG_PATH_CLAWDBOT, + readConfigFileSnapshot, + type ClawdbotConfig, +} from "../../config/config.js"; import type { RuntimeEnv } from "../../runtime.js"; import { stylePromptHint, stylePromptMessage } from "../../terminal/prompt-style.js"; import { applyAuthProfileConfig } from "../onboard-auth.js"; @@ -21,7 +29,11 @@ import { createVpsAwareOAuthHandlers } from "../oauth-flow.js"; import { updateConfig } from "./shared.js"; import { resolvePluginProviders } from "../../plugins/providers.js"; import { createClackPrompter } from "../../wizard/clack-prompter.js"; -import type { ProviderAuthMethod, ProviderAuthResult, ProviderPlugin } from "../../plugins/types.js"; +import type { + ProviderAuthMethod, + ProviderAuthResult, + ProviderPlugin, +} from "../../plugins/types.js"; import type { AuthProfileCredential } from "../../agents/auth-profiles/types.js"; const confirm = (params: Parameters[0]) => @@ -334,14 +346,16 @@ export async function modelsAuthLoginCommand(opts: LoginOptions, runtime: Runtim const prompter = createClackPrompter(); const selectedProvider = resolveProviderMatch(providers, opts.provider) ?? - (await prompter.select({ - message: "Select a provider", - options: providers.map((provider) => ({ - value: provider.id, - label: provider.label, - hint: provider.docsPath ? `Docs: ${provider.docsPath}` : undefined, - })), - }).then((id) => resolveProviderMatch(providers, String(id)))); + (await prompter + .select({ + message: "Select a provider", + options: providers.map((provider) => ({ + value: provider.id, + label: provider.label, + hint: provider.docsPath ? `Docs: ${provider.docsPath}` : undefined, + })), + }) + .then((id) => resolveProviderMatch(providers, String(id)))); if (!selectedProvider) { throw new Error("Unknown provider. Use --provider to pick a provider plugin."); @@ -351,16 +365,16 @@ export async function modelsAuthLoginCommand(opts: LoginOptions, runtime: Runtim pickAuthMethod(selectedProvider, opts.method) ?? (selectedProvider.auth.length === 1 ? selectedProvider.auth[0] - : await prompter.select({ - message: `Auth method for ${selectedProvider.label}`, - options: selectedProvider.auth.map((method) => ({ - value: method.id, - label: method.label, - hint: method.hint, - })), - }).then((id) => - selectedProvider.auth.find((method) => method.id === String(id)), - )); + : await prompter + .select({ + message: `Auth method for ${selectedProvider.label}`, + options: selectedProvider.auth.map((method) => ({ + value: method.id, + label: method.label, + hint: method.hint, + })), + }) + .then((id) => selectedProvider.auth.find((method) => method.id === String(id)))); if (!chosenMethod) { throw new Error("Unknown auth method. Use --method to select one."); diff --git a/src/commands/onboard-channels.test.ts b/src/commands/onboard-channels.test.ts index e4e68fb56..f23082e82 100644 --- a/src/commands/onboard-channels.test.ts +++ b/src/commands/onboard-channels.test.ts @@ -177,9 +177,7 @@ describe("setupChannels", () => { }, ); - expect(select).toHaveBeenCalledWith( - expect.objectContaining({ message: "Select a channel" }), - ); + expect(select).toHaveBeenCalledWith(expect.objectContaining({ message: "Select a channel" })); expect(multiselect).not.toHaveBeenCalled(); }); }); diff --git a/src/commands/onboard-channels.ts b/src/commands/onboard-channels.ts index a11f36a73..135c10941 100644 --- a/src/commands/onboard-channels.ts +++ b/src/commands/onboard-channels.ts @@ -317,7 +317,10 @@ export async function setupChannels( }; const buildSelectionOptions = ( - entries: Array<{ id: ChannelChoice; meta: { id: string; label: string; selectionLabel?: string } }>, + entries: Array<{ + id: ChannelChoice; + meta: { id: string; label: string; selectionLabel?: string }; + }>, ) => entries.map((entry) => { const status = statusByChannel.get(entry.id); diff --git a/src/infra/heartbeat-runner.ts b/src/infra/heartbeat-runner.ts index 0ccee5f71..6b4ff02ea 100644 --- a/src/infra/heartbeat-runner.ts +++ b/src/infra/heartbeat-runner.ts @@ -108,9 +108,7 @@ export function resolveHeartbeatIntervalMs( } export function resolveHeartbeatPrompt(cfg: ClawdbotConfig, heartbeat?: HeartbeatConfig) { - return resolveHeartbeatPromptText( - heartbeat?.prompt ?? cfg.agents?.defaults?.heartbeat?.prompt, - ); + return resolveHeartbeatPromptText(heartbeat?.prompt ?? cfg.agents?.defaults?.heartbeat?.prompt); } function resolveHeartbeatAckMaxChars(cfg: ClawdbotConfig, heartbeat?: HeartbeatConfig) { @@ -127,9 +125,7 @@ function resolveHeartbeatSession(cfg: ClawdbotConfig, agentId?: string) { const scope = sessionCfg?.scope ?? "per-sender"; const resolvedAgentId = normalizeAgentId(agentId ?? resolveDefaultAgentId(cfg)); const sessionKey = - scope === "global" - ? "global" - : resolveAgentMainSessionKey({ cfg, agentId: resolvedAgentId }); + scope === "global" ? "global" : resolveAgentMainSessionKey({ cfg, agentId: resolvedAgentId }); const storeAgentId = scope === "global" ? resolveDefaultAgentId(cfg) : resolvedAgentId; const storePath = resolveStorePath(sessionCfg?.store, { agentId: storeAgentId }); const store = loadSessionStore(storePath); @@ -337,8 +333,10 @@ export async function runHeartbeatOnce(opts: { // Suppress duplicate heartbeats (same payload) within a short window. // This prevents "nagging" when nothing changed but the model repeats the same items. - const prevHeartbeatText = typeof entry?.lastHeartbeatText === "string" ? entry.lastHeartbeatText : ""; - const prevHeartbeatAt = typeof entry?.lastHeartbeatSentAt === "number" ? entry.lastHeartbeatSentAt : undefined; + const prevHeartbeatText = + typeof entry?.lastHeartbeatText === "string" ? entry.lastHeartbeatText : ""; + const prevHeartbeatAt = + typeof entry?.lastHeartbeatSentAt === "number" ? entry.lastHeartbeatSentAt : undefined; const isDuplicateMain = !shouldSkipMain && !mediaUrls.length && diff --git a/src/infra/outbound/message-action-runner.ts b/src/infra/outbound/message-action-runner.ts index 809b3fd30..c2b4f0eb0 100644 --- a/src/infra/outbound/message-action-runner.ts +++ b/src/infra/outbound/message-action-runner.ts @@ -210,10 +210,11 @@ export async function runMessageAction( const to = readStringParam(params, "to", { required: true }); // Allow message to be omitted when sending media-only (e.g., voice notes) const mediaHint = readStringParam(params, "media", { trim: false }); - let message = readStringParam(params, "message", { - required: !mediaHint, // Only require message if no media hint - allowEmpty: true, - }) ?? ""; + let message = + readStringParam(params, "message", { + required: !mediaHint, // Only require message if no media hint + allowEmpty: true, + }) ?? ""; const parsed = parseReplyDirectives(message); message = parsed.text; diff --git a/src/tui/gateway-chat.ts b/src/tui/gateway-chat.ts index 83b839b94..61d28aad3 100644 --- a/src/tui/gateway-chat.ts +++ b/src/tui/gateway-chat.ts @@ -34,7 +34,11 @@ export type GatewaySessionList = { ts: number; path: string; count: number; - defaults?: { model?: string | null; modelProvider?: string | null; contextTokens?: number | null }; + defaults?: { + model?: string | null; + modelProvider?: string | null; + contextTokens?: number | null; + }; sessions: Array<{ key: string; sessionId?: string;