From 9715df9385867540e68aa7f744bc2d9b5cd92279 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Thu, 23 Apr 2026 23:12:35 +0700 Subject: [PATCH] chore(lint): flatten nested ternaries in server (no-nested-ternary) --- packages/server/src/client/daemon-client.ts | 12 +- .../server/src/server/agent/agent-manager.ts | 50 +++--- .../server/src/server/agent/mcp-server.ts | 12 +- .../server/agent/providers/claude-agent.ts | 135 +++++++------- .../claude/task-notification-tool-call.ts | 14 +- .../providers/claude/tool-call-mapper.ts | 35 ++-- .../agent/providers/codex-app-server-agent.ts | 76 ++++---- .../agent/providers/codex-rollout-timeline.ts | 60 +++---- .../server/agent/providers/pi-direct-agent.ts | 37 ++-- .../providers/tool-call-detail-primitives.ts | 47 +++-- .../src/server/agent/timeline-projection.ts | 14 +- packages/server/src/server/bootstrap.ts | 12 +- .../src/server/daemon-client.e2e.test.ts | 61 ++++--- packages/server/src/server/relay-transport.ts | 36 ++-- packages/server/src/server/session.ts | 168 +++++++++--------- packages/server/src/server/speech/audio.ts | 2 +- .../providers/local/sherpa/sherpa-tts.ts | 26 +-- .../src/server/speech/speech-runtime.ts | 15 +- .../src/server/voice-roundtrip.e2e.test.ts | 14 +- .../server/src/services/github-service.ts | 10 +- .../shared/agent-attention-notification.ts | 48 ++--- .../server/src/shared/daemon-endpoints.ts | 17 +- packages/server/src/terminal/terminal.ts | 14 +- packages/server/src/utils/worktree.ts | 14 +- 24 files changed, 499 insertions(+), 430 deletions(-) diff --git a/packages/server/src/client/daemon-client.ts b/packages/server/src/client/daemon-client.ts index 9f11adc7c..3c77ce18b 100644 --- a/packages/server/src/client/daemon-client.ts +++ b/packages/server/src/client/daemon-client.ts @@ -3782,12 +3782,14 @@ export class DaemonClient { if (frame) { const binaryStartMs = perfNow(); this.handleBinaryFrame(frame); + let frameKind: "output" | "snapshot" | "other" = "other"; + if (frame.opcode === TerminalStreamOpcode.Output) { + frameKind = "output"; + } else if (frame.opcode === TerminalStreamOpcode.Snapshot) { + frameKind = "snapshot"; + } this.runtimeMetrics?.recordBinaryFrame( - frame.opcode === TerminalStreamOpcode.Output - ? "output" - : frame.opcode === TerminalStreamOpcode.Snapshot - ? "snapshot" - : "other", + frameKind, rawBytes.byteLength, perfNow() - binaryStartMs, ); diff --git a/packages/server/src/server/agent/agent-manager.ts b/packages/server/src/server/agent/agent-manager.ts index bc5227b23..8c6de6303 100644 --- a/packages/server/src/server/agent/agent-manager.ts +++ b/packages/server/src/server/agent/agent-manager.ts @@ -145,6 +145,17 @@ type AttentionState = attentionTimestamp: Date; }; +function resolveInitialAttention(input: AttentionState | undefined): AttentionState { + if (input == null || !input.requiresAttention) { + return { requiresAttention: false }; + } + return { + requiresAttention: true, + attentionReason: input.attentionReason, + attentionTimestamp: new Date(input.attentionTimestamp), + }; +} + interface ForegroundTurnWaiter { turnId: string; callback: (event: AgentStreamEvent) => void; @@ -293,14 +304,14 @@ function isTurnTerminalEvent(event: AgentStreamEvent): boolean { ); } +function abortMessage(reason: unknown, fallbackMessage: string): string { + if (typeof reason === "string") return reason; + if (reason instanceof Error) return reason.message; + return fallbackMessage; +} + function createAbortError(signal: AbortSignal | undefined, fallbackMessage: string): Error { - const reason = signal?.reason; - const message = - typeof reason === "string" - ? reason - : reason instanceof Error - ? reason.message - : fallbackMessage; + const message = abortMessage(signal?.reason, fallbackMessage); return Object.assign(new Error(message), { name: "AbortError" }); } @@ -1311,11 +1322,15 @@ export class AgentManager { mutableAgent.activeForegroundTurnId = null; const terminalError = mutableAgent.lastError; const shouldHoldBusyForReplacement = mutableAgent.pendingReplacement && !terminalError; - mutableAgent.lifecycle = shouldHoldBusyForReplacement - ? "running" - : terminalError - ? "error" - : "idle"; + let nextLifecycle: "running" | "error" | "idle"; + if (shouldHoldBusyForReplacement) { + nextLifecycle = "running"; + } else if (terminalError) { + nextLifecycle = "error"; + } else { + nextLifecycle = "idle"; + } + mutableAgent.lifecycle = nextLifecycle; const persistenceHandle = mutableAgent.session.describePersistence() ?? (mutableAgent.runtimeInfo?.sessionId @@ -2004,16 +2019,7 @@ export class AgentManager { lastUserMessageAt: options?.lastUserMessageAt ?? null, lastUsage: options?.lastUsage, lastError: options?.lastError, - attention: - options?.attention != null - ? options.attention.requiresAttention - ? { - requiresAttention: true, - attentionReason: options.attention.attentionReason, - attentionTimestamp: new Date(options.attention.attentionTimestamp), - } - : { requiresAttention: false } - : { requiresAttention: false }, + attention: resolveInitialAttention(options?.attention), internal: config.internal ?? false, labels: options?.labels ?? {}, } as ActiveManagedAgent; diff --git a/packages/server/src/server/agent/mcp-server.ts b/packages/server/src/server/agent/mcp-server.ts index 5dff74ae9..d9546cad9 100644 --- a/packages/server/src/server/agent/mcp-server.ts +++ b/packages/server/src/server/agent/mcp-server.ts @@ -340,6 +340,12 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom }) : null; const resolvedProvider = resolvedProviderModel?.provider ?? callerAgent.provider; + let resolvedModel: string | undefined; + if (resolvedProviderModel?.model) { + resolvedModel = resolvedProviderModel.model; + } else if (!hasProviderOverride && callerAgent.config.model) { + resolvedModel = callerAgent.config.model; + } return { type: "new-agent" as const, config: { @@ -354,11 +360,7 @@ export async function createAgentMcpServer(options: AgentMcpServerOptions): Prom ), } : {}), - ...(resolvedProviderModel?.model - ? { model: resolvedProviderModel.model } - : !hasProviderOverride && callerAgent.config.model - ? { model: callerAgent.config.model } - : {}), + ...(resolvedModel ? { model: resolvedModel } : {}), ...(callerAgent.config.thinkingOptionId ? { thinkingOptionId: callerAgent.config.thinkingOptionId } : {}), diff --git a/packages/server/src/server/agent/providers/claude-agent.ts b/packages/server/src/server/agent/providers/claude-agent.ts index 47b97fb82..6078b832a 100644 --- a/packages/server/src/server/agent/providers/claude-agent.ts +++ b/packages/server/src/server/agent/providers/claude-agent.ts @@ -178,6 +178,41 @@ interface ClaudeAgentSessionOptions { type ClaudeThinkingEffort = "low" | "medium" | "high" | "xhigh" | "max"; +function resolvePathEnvKey(): "Path" | "PATH" | null { + if (process.env["Path"] !== undefined) return "Path"; + if (process.env["PATH"] !== undefined) return "PATH"; + return null; +} + +function errorToMessageString(error: unknown): string { + if (typeof error === "string") return error; + if (error instanceof Error) return error.message; + return ""; +} + +function firstStringField( + input: Record, + primaryKey: string, + secondaryKey: string, +): string | undefined { + const primary = input[primaryKey]; + if (typeof primary === "string") return primary; + const secondary = input[secondaryKey]; + if (typeof secondary === "string") return secondary; + return undefined; +} + +function extractSessionIdRaw(msg: { + session_id?: unknown; + sessionId?: unknown; + session?: { id?: unknown } | null; +}): string { + if (typeof msg.session_id === "string") return msg.session_id; + if (typeof msg.sessionId === "string") return msg.sessionId; + if (typeof msg.session?.id === "string") return msg.session.id; + return ""; +} + function resolveClaudeSpawnCommand( spawnOptions: SpawnOptions, runtimeSettings?: ProviderRuntimeSettings, @@ -536,6 +571,26 @@ function isMetadata(value: unknown): value is AgentMetadata { return typeof value === "object" && value !== null; } +function createDefaultToolUseCacheEntry(id: string, block: ClaudeContentChunk): ToolUseCacheEntry { + const nameFromBlock = + typeof block.name === "string" && block.name.length > 0 ? block.name : "tool"; + let server: string; + if (typeof block.server === "string" && block.server.length > 0) { + server = block.server; + } else if (typeof block.name === "string" && block.name.length > 0) { + server = block.name; + } else { + server = "tool"; + } + return { + id, + name: nameFromBlock, + server, + classification: "generic", + started: false, + }; +} + function readTrimmedString(value: unknown): string | undefined { if (typeof value !== "string") { return undefined; @@ -2137,12 +2192,7 @@ class ClaudeAgentSession implements AgentSession { this.logger.debug( { claudeBinary, - pathEnvKey: - process.env["Path"] !== undefined - ? "Path" - : process.env["PATH"] !== undefined - ? "PATH" - : null, + pathEnvKey: resolvePathEnvKey(), pathIncludesClaudeLocalBin: (process.env["Path"] ?? process.env["PATH"] ?? "") .toLowerCase() .includes("\\.local\\bin"), @@ -2333,7 +2383,7 @@ class ClaudeAgentSession implements AgentSession { if (this.getRecentStderrDiagnostic()) { return; } - const message = typeof error === "string" ? error : error instanceof Error ? error.message : ""; + const message = errorToMessageString(error); if ( !/\bprocess exited with code\b/i.test(message) && !/\bterminated by signal\b/i.test(message) @@ -2414,12 +2464,14 @@ class ClaudeAgentSession implements AgentSession { if (consecutiveRecoveries >= 3) { return false; } - const message = - typeof error === "string" - ? error - : error instanceof Error - ? `${error.message}\n${error.stack ?? ""}` - : JSON.stringify(error); + let message: string; + if (typeof error === "string") { + message = error; + } else if (error instanceof Error) { + message = `${error.message}\n${error.stack ?? ""}`; + } else { + message = JSON.stringify(error); + } return message.toLowerCase().includes("request was aborted"); } @@ -2919,15 +2971,7 @@ class ClaudeAgentSession implements AgentSession { sessionId?: unknown; session?: { id?: unknown } | null; }; - const sessionIdRaw = - typeof msg.session_id === "string" - ? msg.session_id - : typeof msg.sessionId === "string" - ? msg.sessionId - : typeof msg.session?.id === "string" - ? msg.session.id - : ""; - const sessionId = sessionIdRaw.trim(); + const sessionId = extractSessionIdRaw(msg).trim(); if (!sessionId) { return null; } @@ -2961,15 +3005,7 @@ class ClaudeAgentSession implements AgentSession { sessionId?: unknown; session?: { id?: unknown } | null; }; - const newSessionIdRaw = - typeof msg.session_id === "string" - ? msg.session_id - : typeof msg.sessionId === "string" - ? msg.sessionId - : typeof msg.session?.id === "string" - ? msg.session.id - : ""; - const newSessionId = newSessionIdRaw.trim(); + const newSessionId = extractSessionIdRaw(msg).trim(); if (!newSessionId) { return null; } @@ -3600,27 +3636,13 @@ class ClaudeAgentSession implements AgentSession { ) { if (input && typeof input.file_path === "string") { // Support both old_str/new_str and old_string/new_string parameter names - const oldContent = - typeof input.old_str === "string" - ? input.old_str - : typeof input.old_string === "string" - ? input.old_string - : undefined; - const newContent = - typeof input.new_str === "string" - ? input.new_str - : typeof input.new_string === "string" - ? input.new_string - : undefined; + const oldContent = firstStringField(input, "old_str", "old_string"); + const newContent = firstStringField(input, "new_str", "new_string"); + const diff = firstStringField(input, "patch", "diff"); return { type: "file_edit", filePath: input.file_path, - diff: - typeof input.patch === "string" - ? input.patch - : typeof input.diff === "string" - ? input.diff - : undefined, + diff, oldContent, newContent, }; @@ -3702,20 +3724,7 @@ class ClaudeAgentSession implements AgentSession { if (!id) { return null; } - const existing = - this.toolUseCache.get(id) ?? - ({ - id, - name: typeof block.name === "string" && block.name.length > 0 ? block.name : "tool", - server: - typeof block.server === "string" && block.server.length > 0 - ? block.server - : typeof block.name === "string" && block.name.length > 0 - ? block.name - : "tool", - classification: "generic", - started: false, - } satisfies ToolUseCacheEntry); + const existing = this.toolUseCache.get(id) ?? createDefaultToolUseCacheEntry(id, block); if (typeof block.name === "string" && block.name.length > 0) { existing.name = block.name; diff --git a/packages/server/src/server/agent/providers/claude/task-notification-tool-call.ts b/packages/server/src/server/agent/providers/claude/task-notification-tool-call.ts index 2b9d84494..a0789ef39 100644 --- a/packages/server/src/server/agent/providers/claude/task-notification-tool-call.ts +++ b/packages/server/src/server/agent/providers/claude/task-notification-tool-call.ts @@ -342,12 +342,14 @@ export function coerceTaskNotificationHistoryRecordToSystemMessage( } const normalizedStatus = parsed.status?.toLowerCase() ?? null; - const status = - normalizedStatus === "failed" || normalizedStatus === "error" - ? "failed" - : normalizedStatus === "canceled" || normalizedStatus === "cancelled" - ? "stopped" - : "completed"; + let status: "failed" | "stopped" | "completed"; + if (normalizedStatus === "failed" || normalizedStatus === "error") { + status = "failed"; + } else if (normalizedStatus === "canceled" || normalizedStatus === "cancelled") { + status = "stopped"; + } else { + status = "completed"; + } return { type: "system", diff --git a/packages/server/src/server/agent/providers/claude/tool-call-mapper.ts b/packages/server/src/server/agent/providers/claude/tool-call-mapper.ts index 5bf1875cf..9de28a245 100644 --- a/packages/server/src/server/agent/providers/claude/tool-call-mapper.ts +++ b/packages/server/src/server/agent/providers/claude/tool-call-mapper.ts @@ -203,22 +203,29 @@ const ClaudeToolCallPass2Schema = z.discriminatedUnion("toolKind", [ type ClaudeToolCallPass2 = z.infer; +function resolveDetailName(normalized: ClaudeToolCallPass2): string { + switch (normalized.toolKind) { + case "shell": + return "shell"; + case "read": + return "read_file"; + case "write": + return "write_file"; + case "edit": + return "apply_patch"; + case "search": + case "fetch": + return normalized.name; + case "speak": + return "speak"; + default: + return normalized.name; + } +} + function toToolCallTimelineItem(normalized: ClaudeToolCallPass2): ToolCallTimelineItem { const name = normalized.toolKind === "speak" ? ("speak" as const) : normalized.name; - const detailName = - normalized.toolKind === "shell" - ? "shell" - : normalized.toolKind === "read" - ? "read_file" - : normalized.toolKind === "write" - ? "write_file" - : normalized.toolKind === "edit" - ? "apply_patch" - : normalized.toolKind === "search" || normalized.toolKind === "fetch" - ? normalized.name - : normalized.toolKind === "speak" - ? "speak" - : normalized.name; + const detailName = resolveDetailName(normalized); const detail = deriveClaudeToolDetail(detailName, normalized.input, normalized.output); if (normalized.status === "failed") { return { diff --git a/packages/server/src/server/agent/providers/codex-app-server-agent.ts b/packages/server/src/server/agent/providers/codex-app-server-agent.ts index c20928e44..9eb23b779 100644 --- a/packages/server/src/server/agent/providers/codex-app-server-agent.ts +++ b/packages/server/src/server/agent/providers/codex-app-server-agent.ts @@ -253,6 +253,30 @@ function resolveCodexHomeDir(): string { return process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex"); } +function decodeEscapedChar(next: string): string { + if (next === "n") return "\n"; + if (next === "t") return "\t"; + return next; +} + +function resolvePermissionDecision( + response: AgentPermissionResponse, +): "accept" | "cancel" | "decline" { + if (response.behavior === "allow") return "accept"; + if (response.interrupt) return "cancel"; + return "decline"; +} + +function firstPositiveFiniteNumber(primary: unknown, secondary: unknown): number | undefined { + if (typeof primary === "number" && Number.isFinite(primary) && primary > 0) { + return primary; + } + if (typeof secondary === "number" && Number.isFinite(secondary) && secondary > 0) { + return secondary; + } + return undefined; +} + function tokenizeCommandArgs(args: string): string[] { const tokens: string[] = []; let current = ""; @@ -268,7 +292,7 @@ function tokenizeCommandArgs(args: string): string[] { const next = args[i + 1]!; if (next === quote || next === "\\" || next === "n" || next === "t") { i += 1; - current += next === "n" ? "\n" : next === "t" ? "\t" : next; + current += decodeEscapedChar(next); continue; } } @@ -789,26 +813,14 @@ function toAgentUsage(tokenUsage: unknown): AgentUsage | undefined { totalTokens?: number; }; }; - const contextWindowMaxTokens = - typeof usage.model_context_window === "number" && - Number.isFinite(usage.model_context_window) && - usage.model_context_window > 0 - ? usage.model_context_window - : typeof usage.modelContextWindow === "number" && - Number.isFinite(usage.modelContextWindow) && - usage.modelContextWindow > 0 - ? usage.modelContextWindow - : undefined; - const contextWindowUsedTokens = - typeof usage.last?.total_tokens === "number" && - Number.isFinite(usage.last.total_tokens) && - usage.last.total_tokens > 0 - ? usage.last.total_tokens - : typeof usage.last?.totalTokens === "number" && - Number.isFinite(usage.last.totalTokens) && - usage.last.totalTokens > 0 - ? usage.last.totalTokens - : undefined; + const contextWindowMaxTokens = firstPositiveFiniteNumber( + usage.model_context_window, + usage.modelContextWindow, + ); + const contextWindowUsedTokens = firstPositiveFiniteNumber( + usage.last?.total_tokens, + usage.last?.totalTokens, + ); return { inputTokens: usage.last?.inputTokens, cachedInputTokens: usage.last?.cachedInputTokens, @@ -3188,12 +3200,14 @@ class CodexAppServerAgentSession implements AgentSession { this.resolvedPermissionRequests.add(requestId); if (response.behavior === "deny" && pendingRequest?.kind === "tool") { - const fallbackName = - pendingRequest.name === "CodexBash" - ? "shell" - : pendingRequest.name === "CodexFileChange" - ? "apply_patch" - : pendingRequest.name; + let fallbackName: string; + if (pendingRequest.name === "CodexBash") { + fallbackName = "shell"; + } else if (pendingRequest.name === "CodexFileChange") { + fallbackName = "apply_patch"; + } else { + fallbackName = pendingRequest.name; + } this.emitEvent({ type: "timeline", provider: CODEX_PROVIDER, @@ -3224,16 +3238,12 @@ class CodexAppServerAgentSession implements AgentSession { }); if (pending.kind === "command") { - const decision = - response.behavior === "allow" ? "accept" : response.interrupt ? "cancel" : "decline"; - pending.resolve({ decision }); + pending.resolve({ decision: resolvePermissionDecision(response) }); return; } if (pending.kind === "file") { - const decision = - response.behavior === "allow" ? "accept" : response.interrupt ? "cancel" : "decline"; - pending.resolve({ decision }); + pending.resolve({ decision: resolvePermissionDecision(response) }); return; } diff --git a/packages/server/src/server/agent/providers/codex-rollout-timeline.ts b/packages/server/src/server/agent/providers/codex-rollout-timeline.ts index cebc7e742..f30bd342b 100644 --- a/packages/server/src/server/agent/providers/codex-rollout-timeline.ts +++ b/packages/server/src/server/agent/providers/codex-rollout-timeline.ts @@ -549,36 +549,36 @@ export async function parseRolloutFile(filePath: string): Promise map.set(record.callId, record.output), new Map()); const terminalCommandsBySessionId = buildTerminalCommandBySessionId(parsedRecords); - const timeline = parsedRecords.flatMap((record): AgentTimelineItem[] => - record.kind === "timeline" - ? [record.item] - : record.kind === "call" - ? (() => { - if (record.name === "write_stdin") { - const input = - record.input && typeof record.input === "object" - ? (record.input as { session_id?: unknown; sessionId?: unknown }) - : null; - const sessionId = - readTerminalSessionId(input?.session_id) ?? readTerminalSessionId(input?.sessionId); - return [ - mapCodexTerminalInteractionToToolCall({ - processId: sessionId, - fallbackCallId: record.callId, - command: sessionId ? terminalCommandsBySessionId.get(sessionId) : undefined, - }), - ]; - } - const mapped = mapCodexRolloutToolCall({ - callId: record.callId ?? null, - name: record.name, - input: record.input ?? null, - output: record.callId ? (outputsByCallId.get(record.callId) ?? null) : null, - }); - return mapped ? [mapped] : []; - })() - : [], - ); + const timeline = parsedRecords.flatMap((record): AgentTimelineItem[] => { + if (record.kind === "timeline") { + return [record.item]; + } + if (record.kind !== "call") { + return []; + } + if (record.name === "write_stdin") { + const input = + record.input && typeof record.input === "object" + ? (record.input as { session_id?: unknown; sessionId?: unknown }) + : null; + const sessionId = + readTerminalSessionId(input?.session_id) ?? readTerminalSessionId(input?.sessionId); + return [ + mapCodexTerminalInteractionToToolCall({ + processId: sessionId, + fallbackCallId: record.callId, + command: sessionId ? terminalCommandsBySessionId.get(sessionId) : undefined, + }), + ]; + } + const mapped = mapCodexRolloutToolCall({ + callId: record.callId ?? null, + name: record.name, + input: record.input ?? null, + output: record.callId ? (outputsByCallId.get(record.callId) ?? null) : null, + }); + return mapped ? [mapped] : []; + }); return dedupeMirroredTextTimelineItems(timeline); } diff --git a/packages/server/src/server/agent/providers/pi-direct-agent.ts b/packages/server/src/server/agent/providers/pi-direct-agent.ts index cfaed4432..15da7ef7f 100644 --- a/packages/server/src/server/agent/providers/pi-direct-agent.ts +++ b/packages/server/src/server/agent/providers/pi-direct-agent.ts @@ -870,37 +870,20 @@ export class PiDirectAgentSession implements AgentSession { error: unknown, ): void { const turnId = this.currentTurnIdForEvent(); + const detail = mapToolDetail(toolCall, result); + const baseItem = { + type: "tool_call" as const, + callId: toolCallId, + name: toolCall.toolName, + detail, + }; + const item = + status === "failed" ? { ...baseItem, status, error } : { ...baseItem, status, error: null }; this.emit({ type: "timeline", provider: PI_PROVIDER, turnId, - item: - status === "running" - ? { - type: "tool_call", - callId: toolCallId, - name: toolCall.toolName, - status, - detail: mapToolDetail(toolCall, result), - error: null, - } - : status === "completed" - ? { - type: "tool_call", - callId: toolCallId, - name: toolCall.toolName, - status, - detail: mapToolDetail(toolCall, result), - error: null, - } - : { - type: "tool_call", - callId: toolCallId, - name: toolCall.toolName, - status, - detail: mapToolDetail(toolCall, result), - error, - }, + item, }); } diff --git a/packages/server/src/server/agent/providers/tool-call-detail-primitives.ts b/packages/server/src/server/agent/providers/tool-call-detail-primitives.ts index be41dd81c..440335a19 100644 --- a/packages/server/src/server/agent/providers/tool-call-detail-primitives.ts +++ b/packages/server/src/server/agent/providers/tool-call-detail-primitives.ts @@ -31,14 +31,18 @@ export const ToolShellInputSchema = z const parsedCommand = CommandValueSchema.safeParse( "command" in value ? value.command : value.cmd, ); - const command = parsedCommand.success - ? typeof parsedCommand.data === "string" - ? nonEmptyString(parsedCommand.data) - : parsedCommand.data + let command: string | undefined; + if (parsedCommand.success) { + if (typeof parsedCommand.data === "string") { + command = nonEmptyString(parsedCommand.data); + } else { + command = + parsedCommand.data .map((token) => token.trim()) .filter((token) => token.length > 0) - .join(" ") || undefined - : undefined; + .join(" ") || undefined; + } + } return { command, cwd: nonEmptyString(value.cwd) ?? nonEmptyString(value.directory), @@ -702,14 +706,11 @@ export function toWriteToolDetail( return undefined; } + const content = input?.content ?? output?.content; return { type: "write", filePath, - ...(input?.content - ? { content: input.content } - : output?.content - ? { content: output.content } - : {}), + ...(content ? { content } : {}), }; } @@ -723,20 +724,14 @@ export function toEditToolDetail( return undefined; } + const newString = input?.newString ?? output?.newString; + const unifiedDiff = input?.unifiedDiff ?? output?.unifiedDiff; return { type: "edit", filePath, ...(input?.oldString ? { oldString: input.oldString } : {}), - ...(input?.newString - ? { newString: input.newString } - : output?.newString - ? { newString: output.newString } - : {}), - ...(input?.unifiedDiff - ? { unifiedDiff: input.unifiedDiff } - : output?.unifiedDiff - ? { unifiedDiff: output.unifiedDiff } - : {}), + ...(newString ? { newString } : {}), + ...(unifiedDiff ? { unifiedDiff } : {}), }; } @@ -750,12 +745,10 @@ export function toSearchToolDetail(params: { return undefined; } - const filePaths = - isParsedToolGrepOutput(output) || isParsedToolGlobOutput(output) - ? output.filenames.length > 0 - ? output.filenames - : undefined - : undefined; + let filePaths: string[] | undefined; + if (isParsedToolGrepOutput(output) || isParsedToolGlobOutput(output)) { + filePaths = output.filenames.length > 0 ? output.filenames : undefined; + } const webResults = isParsedToolWebSearchOutput(output) ? output.results.flatMap((entry) => (typeof entry === "string" ? [] : entry.content)) : undefined; diff --git a/packages/server/src/server/agent/timeline-projection.ts b/packages/server/src/server/agent/timeline-projection.ts index 0956ad452..a99e574b8 100644 --- a/packages/server/src/server/agent/timeline-projection.ts +++ b/packages/server/src/server/agent/timeline-projection.ts @@ -241,12 +241,14 @@ export function selectTimelineWindowByProjectedLimit(input: { }; } - const projectedEntries = - limit === 0 || limit >= projectedAll.length - ? projectedAll - : direction === "after" - ? projectedAll.slice(0, limit) - : projectedAll.slice(projectedAll.length - limit); + let projectedEntries: typeof projectedAll; + if (limit === 0 || limit >= projectedAll.length) { + projectedEntries = projectedAll; + } else if (direction === "after") { + projectedEntries = projectedAll.slice(0, limit); + } else { + projectedEntries = projectedAll.slice(projectedAll.length - limit); + } if (projectedEntries.length === 0) { return { diff --git a/packages/server/src/server/bootstrap.ts b/packages/server/src/server/bootstrap.ts index a6651368c..20901df71 100644 --- a/packages/server/src/server/bootstrap.ts +++ b/packages/server/src/server/bootstrap.ts @@ -635,12 +635,12 @@ export async function createPaseoDaemon( return; } const callerAgentIdRaw = req.query.callerAgentId; - const callerAgentId = - typeof callerAgentIdRaw === "string" - ? callerAgentIdRaw - : Array.isArray(callerAgentIdRaw) && typeof callerAgentIdRaw[0] === "string" - ? callerAgentIdRaw[0] - : undefined; + let callerAgentId: string | undefined; + if (typeof callerAgentIdRaw === "string") { + callerAgentId = callerAgentIdRaw; + } else if (Array.isArray(callerAgentIdRaw) && typeof callerAgentIdRaw[0] === "string") { + callerAgentId = callerAgentIdRaw[0]; + } transport = await createAgentMcpTransport(callerAgentId); } diff --git a/packages/server/src/server/daemon-client.e2e.test.ts b/packages/server/src/server/daemon-client.e2e.test.ts index c5738a2a6..97eded10d 100644 --- a/packages/server/src/server/daemon-client.e2e.test.ts +++ b/packages/server/src/server/daemon-client.e2e.test.ts @@ -212,38 +212,43 @@ class NonPersistentReloadClient implements AgentClient { } } +function resolveSpeechConfig() { + if (hasLocalSpeech) { + return { + providers: { + dictationStt: { provider: "local" as const, explicit: true }, + voiceStt: { provider: "local" as const, explicit: true }, + voiceTts: { provider: "local" as const, explicit: true }, + }, + local: { + modelsDir: localModelsDir, + models: { + dictationStt: + process.env.PASEO_DICTATION_LOCAL_STT_MODEL ?? "zipformer-bilingual-zh-en-2023-02-20", + voiceStt: + process.env.PASEO_VOICE_LOCAL_STT_MODEL ?? "zipformer-bilingual-zh-en-2023-02-20", + voiceTts: process.env.PASEO_VOICE_LOCAL_TTS_MODEL ?? "kitten-nano-en-v0_1-fp16", + }, + }, + }; + } + if (openaiApiKey) { + return { + providers: { + dictationStt: { provider: "openai" as const, explicit: true }, + voiceStt: { provider: "openai" as const, explicit: true }, + voiceTts: { provider: "openai" as const, explicit: true }, + }, + }; + } + return undefined; +} + describe("daemon client E2E", () => { let ctx: DaemonTestContext; beforeAll(async () => { - const speechConfig = hasLocalSpeech - ? { - providers: { - dictationStt: { provider: "local" as const, explicit: true }, - voiceStt: { provider: "local" as const, explicit: true }, - voiceTts: { provider: "local" as const, explicit: true }, - }, - local: { - modelsDir: localModelsDir, - models: { - dictationStt: - process.env.PASEO_DICTATION_LOCAL_STT_MODEL ?? - "zipformer-bilingual-zh-en-2023-02-20", - voiceStt: - process.env.PASEO_VOICE_LOCAL_STT_MODEL ?? "zipformer-bilingual-zh-en-2023-02-20", - voiceTts: process.env.PASEO_VOICE_LOCAL_TTS_MODEL ?? "kitten-nano-en-v0_1-fp16", - }, - }, - } - : openaiApiKey - ? { - providers: { - dictationStt: { provider: "openai" as const, explicit: true }, - voiceStt: { provider: "openai" as const, explicit: true }, - voiceTts: { provider: "openai" as const, explicit: true }, - }, - } - : undefined; + const speechConfig = resolveSpeechConfig(); ctx = await createDaemonTestContext({ dictationFinalTimeoutMs: 5000, diff --git a/packages/server/src/server/relay-transport.ts b/packages/server/src/server/relay-transport.ts index 1d7fa069a..436653626 100644 --- a/packages/server/src/server/relay-transport.ts +++ b/packages/server/src/server/relay-transport.ts @@ -42,10 +42,28 @@ const CONTROL_PING_INTERVAL_MS = 10_000; const CONTROL_STALE_TIMEOUT_MS = 30_000; const CONTROL_READY_TIMEOUT_MS = 8_000; +function normalizeRelaySendPayload(data: string | Uint8Array | ArrayBuffer): string | ArrayBuffer { + if (typeof data === "string") return data; + if (data instanceof ArrayBuffer) return data; + if (ArrayBuffer.isView(data)) { + const view = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); + const out = new Uint8Array(view.byteLength); + out.set(view); + return out.buffer; + } + return String(data); +} + function tryParseControlMessage(raw: unknown): ControlMessage | null { try { - const text = - typeof raw === "string" ? raw : Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw); + let text: string; + if (typeof raw === "string") { + text = raw; + } else if (Buffer.isBuffer(raw)) { + text = raw.toString("utf8"); + } else { + text = String(raw); + } const parsed = JSON.parse(text) as any; if (!parsed || typeof parsed !== "object") return null; if (parsed.type === "ping") return { type: "ping" }; @@ -443,19 +461,7 @@ function createEncryptedSocket(channel: EncryptedChannel, emitter: EventEmitter) return readyState; }, send: (data) => { - const outbound = - typeof data === "string" - ? data - : data instanceof ArrayBuffer - ? data - : ArrayBuffer.isView(data) - ? (() => { - const view = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); - const out = new Uint8Array(view.byteLength); - out.set(view); - return out.buffer; - })() - : String(data); + const outbound = normalizeRelaySendPayload(data); void channel.send(outbound).catch((error) => { emitter.emit("error", error); }); diff --git a/packages/server/src/server/session.ts b/packages/server/src/server/session.ts index 4f7d7a6ed..06054d1bf 100644 --- a/packages/server/src/server/session.ts +++ b/packages/server/src/server/session.ts @@ -238,6 +238,66 @@ const LEGACY_PROVIDER_IDS = new Set(["claude", "codex", "opencode"]); const MIN_VERSION_ALL_PROVIDERS = "0.1.45"; const MIN_VERSION_FLEXIBLE_EDITOR_IDS = "0.1.50"; +function errorToFriendlyMessage(error: unknown): string { + if (error instanceof Error) return error.message; + if (typeof error === "string") return error; + return "Unknown error"; +} + +function resolveSubscriptionId( + subscribe: unknown, + requestedSubscriptionId: string | undefined, +): string | null { + if (!subscribe) return null; + if (requestedSubscriptionId && requestedSubscriptionId.length > 0) { + return requestedSubscriptionId; + } + return uuidv4(); +} + +function diffChangeTypeFor(file: { isNew?: boolean; isDeleted?: boolean }): "A" | "D" | "M" { + if (file.isNew) return "A"; + if (file.isDeleted) return "D"; + return "M"; +} + +function buildWorkspaceCheckout( + workspace: PersistedWorkspaceRecord, + project: PersistedProjectRecord, +): ProjectPlacementPayload["checkout"] { + if (project.kind !== "git") { + return { + cwd: workspace.cwd, + isGit: false, + currentBranch: null, + remoteUrl: null, + worktreeRoot: null, + isPaseoOwnedWorktree: false, + mainRepoRoot: null, + }; + } + if (workspace.kind === "worktree") { + return { + cwd: workspace.cwd, + isGit: true, + currentBranch: workspace.displayName, + remoteUrl: null, + worktreeRoot: workspace.cwd, + isPaseoOwnedWorktree: true, + mainRepoRoot: project.rootPath, + }; + } + return { + cwd: workspace.cwd, + isGit: true, + currentBranch: workspace.displayName, + remoteUrl: null, + worktreeRoot: workspace.cwd, + isPaseoOwnedWorktree: false, + mainRepoRoot: null, + }; +} + function isAppVersionAtLeast(appVersion: string | null, minVersion: string): boolean { if (!appVersion) return false; // Strip prerelease suffix: "0.1.45-beta.4" -> "0.1.45" @@ -1057,13 +1117,7 @@ export class Session { ); } catch (error) { this.handleAgentRunError(agentId, error, "Failed to start agent run"); - const message = - error instanceof Error - ? error.message - : typeof error === "string" - ? error - : "Unknown error"; - return { ok: false, error: message }; + return { ok: false, error: errorToFriendlyMessage(error) }; } void (async () => { @@ -1082,8 +1136,7 @@ export class Session { } private handleAgentRunError(agentId: string, error: unknown, context: string): void { - const message = - error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error"; + const message = errorToFriendlyMessage(error); this.sessionLogger.error({ err: error, agentId, context }, `${context} for agent ${agentId}`); this.emit({ type: "activity_log", @@ -1408,36 +1461,7 @@ export class Session { if (!project) { throw new Error(`Project not found for workspace ${workspace.workspaceId}`); } - const checkout = - project.kind !== "git" - ? { - cwd: workspace.cwd, - isGit: false as const, - currentBranch: null, - remoteUrl: null, - worktreeRoot: null, - isPaseoOwnedWorktree: false as const, - mainRepoRoot: null, - } - : workspace.kind === "worktree" - ? { - cwd: workspace.cwd, - isGit: true as const, - currentBranch: workspace.displayName, - remoteUrl: null, - worktreeRoot: workspace.cwd, - isPaseoOwnedWorktree: true as const, - mainRepoRoot: project.rootPath, - } - : { - cwd: workspace.cwd, - isGit: true as const, - currentBranch: workspace.displayName, - remoteUrl: null, - worktreeRoot: workspace.cwd, - isPaseoOwnedWorktree: false as const, - mainRepoRoot: null, - }; + const checkout = buildWorkspaceCheckout(workspace, project); return { projectKey: project.projectId, projectName: project.displayName, @@ -3538,7 +3562,7 @@ export class Session { ? [ "Files changed:", ...diff.structured.map((file) => { - const changeType = file.isNew ? "A" : file.isDeleted ? "D" : "M"; + const changeType = diffChangeTypeFor(file); const status = file.status && file.status !== "ok" ? ` [${file.status}]` : ""; return `${changeType}\t${file.path}\t(+${file.additions} -${file.deletions})${status}`; }), @@ -3604,7 +3628,7 @@ export class Session { ? [ "Files changed:", ...diff.structured.map((file) => { - const changeType = file.isNew ? "A" : file.isDeleted ? "D" : "M"; + const changeType = diffChangeTypeFor(file); const status = file.status && file.status !== "ok" ? ` [${file.status}]` : ""; return `${changeType}\t${file.path}\t(+${file.additions} -${file.deletions})${status}`; }), @@ -6569,11 +6593,7 @@ export class Session { request: Extract, ): Promise { const requestedSubscriptionId = request.subscribe?.subscriptionId?.trim(); - const subscriptionId = request.subscribe - ? requestedSubscriptionId && requestedSubscriptionId.length > 0 - ? requestedSubscriptionId - : uuidv4() - : null; + const subscriptionId = resolveSubscriptionId(request.subscribe, requestedSubscriptionId); try { if (subscriptionId) { @@ -6657,11 +6677,7 @@ export class Session { request: Extract, ): Promise { const requestedSubscriptionId = request.subscribe?.subscriptionId?.trim(); - const subscriptionId = request.subscribe - ? requestedSubscriptionId && requestedSubscriptionId.length > 0 - ? requestedSubscriptionId - : uuidv4() - : null; + const subscriptionId = resolveSubscriptionId(request.subscribe, requestedSubscriptionId); try { this.sessionLogger.debug( @@ -7297,19 +7313,13 @@ export class Session { try { await this.agentManager.waitForAgentRunStart(agentId, { signal: startAbort.signal }); } catch (error) { - const message = - error instanceof Error - ? error.message - : typeof error === "string" - ? error - : "Unknown error"; this.emit({ type: "send_agent_message_response", payload: { requestId: msg.requestId, agentId, accepted: false, - error: message, + error: errorToFriendlyMessage(error), }, }); return; @@ -7327,19 +7337,13 @@ export class Session { }, }); } catch (error) { - const message = - error instanceof Error - ? error.message - : typeof error === "string" - ? error - : "Unknown error"; this.emit({ type: "send_agent_message_response", payload: { requestId: msg.requestId, agentId: resolved.agentId, accepted: false, - error: message, + error: errorToFriendlyMessage(error), }, }); } @@ -7383,12 +7387,14 @@ export class Session { return; } const final = this.buildStoredAgentPayload(record); - const status = - record.attentionReason === "permission" - ? "permission" - : record.lastStatus === "error" - ? "error" - : "idle"; + let status: "permission" | "error" | "idle"; + if (record.attentionReason === "permission") { + status = "permission"; + } else if (record.lastStatus === "error") { + status = "error"; + } else { + status = "idle"; + } const error = resolveWaitForFinishError({ status, final }); this.emit({ type: "wait_for_finish_response", @@ -7415,11 +7421,14 @@ export class Session { throw new Error(`Agent ${agentId} disappeared while waiting`); } - let status: "permission" | "error" | "idle" = result.permission - ? "permission" - : result.status === "error" - ? "error" - : "idle"; + let status: "permission" | "error" | "idle"; + if (result.permission) { + status = "permission"; + } else if (result.status === "error") { + status = "error"; + } else { + status = "idle"; + } const error = resolveWaitForFinishError({ status, final }); this.emit({ @@ -7431,12 +7440,7 @@ export class Session { error instanceof Error && (error.name === "AbortError" || error.message.toLowerCase().includes("aborted")); if (!isAbort) { - const message = - error instanceof Error - ? error.message - : typeof error === "string" - ? error - : "Unknown error"; + const message = errorToFriendlyMessage(error); this.sessionLogger.error({ err: error, agentId }, "wait_for_finish_request failed"); const final = await this.getAgentPayloadById(agentId); this.emit({ diff --git a/packages/server/src/server/speech/audio.ts b/packages/server/src/server/speech/audio.ts index 4908b9759..d44c97b76 100644 --- a/packages/server/src/server/speech/audio.ts +++ b/packages/server/src/server/speech/audio.ts @@ -93,7 +93,7 @@ export function pcm16leToFloat32(pcm16le: Buffer, gain: number = 1): Float32Arra const out = new Float32Array(int16.length); for (let i = 0; i < int16.length; i += 1) { const v = (int16[i]! / 32768.0) * gain; - out[i] = v > 1 ? 1 : v < -1 ? -1 : v; + out[i] = Math.max(-1, Math.min(1, v)); } return out; } diff --git a/packages/server/src/server/speech/providers/local/sherpa/sherpa-tts.ts b/packages/server/src/server/speech/providers/local/sherpa/sherpa-tts.ts index fa33e8a23..0498c9a2e 100644 --- a/packages/server/src/server/speech/providers/local/sherpa/sherpa-tts.ts +++ b/packages/server/src/server/speech/providers/local/sherpa/sherpa-tts.ts @@ -102,24 +102,28 @@ export class SherpaOnnxTTS implements TextToSpeechProvider { // from sherpa itself instead of trying to clone after generate() returns. enableExternalBuffer: false, }); - const rawSamples: Float32Array | null = - audio && audio.samples instanceof Float32Array - ? audio.samples - : audio && Array.isArray(audio.samples) - ? Float32Array.from(audio.samples as number[]) - : null; + let rawSamples: Float32Array | null = null; + if (audio && audio.samples instanceof Float32Array) { + rawSamples = audio.samples; + } else if (audio && Array.isArray(audio.samples)) { + rawSamples = Float32Array.from(audio.samples as number[]); + } // Copy to avoid "External buffers are not allowed" when sherpa-onnx // returns a Float32Array backed by native memory. const samples = rawSamples ? Float32Array.from(rawSamples) : null; - const sampleRate: number = + let sampleRate: number; + if ( audio && typeof audio.sampleRate === "number" && Number.isFinite(audio.sampleRate) && audio.sampleRate > 0 - ? audio.sampleRate - : typeof this.tts.sampleRate === "number" - ? this.tts.sampleRate - : 24000; + ) { + sampleRate = audio.sampleRate; + } else if (typeof this.tts.sampleRate === "number") { + sampleRate = this.tts.sampleRate; + } else { + sampleRate = 24000; + } if (!samples) { throw new Error("Unexpected sherpa TTS output: missing Float32 samples"); diff --git a/packages/server/src/server/speech/speech-runtime.ts b/packages/server/src/server/speech/speech-runtime.ts index c0f0e8554..5009be3ac 100644 --- a/packages/server/src/server/speech/speech-runtime.ts +++ b/packages/server/src/server/speech/speech-runtime.ts @@ -312,6 +312,15 @@ function describeRequestedProviders(providers: RequestedSpeechProviders): { }; } +function resolveVoiceTtsLabel( + ttsService: TextToSpeechProvider | null, + localVoiceTtsProvider: TextToSpeechProvider | null, +): "unavailable" | "local" | "openai" { + if (!ttsService) return "unavailable"; + if (ttsService === localVoiceTtsProvider) return "local"; + return "openai"; +} + function resolveEffectiveProviderIds(params: { turnDetectionService: TurnDetectionProvider | null; sttService: SpeechToTextProvider | null; @@ -328,11 +337,7 @@ function resolveEffectiveProviderIds(params: { dictationStt: params.dictationSttService?.id ?? "unavailable", voiceTurnDetection: params.turnDetectionService?.id ?? "unavailable", voiceStt: params.sttService?.id ?? "unavailable", - voiceTts: !params.ttsService - ? "unavailable" - : params.ttsService === params.localVoiceTtsProvider - ? "local" - : "openai", + voiceTts: resolveVoiceTtsLabel(params.ttsService, params.localVoiceTtsProvider), }; } diff --git a/packages/server/src/server/voice-roundtrip.e2e.test.ts b/packages/server/src/server/voice-roundtrip.e2e.test.ts index 59d8eda3d..48d2aa1f9 100644 --- a/packages/server/src/server/voice-roundtrip.e2e.test.ts +++ b/packages/server/src/server/voice-roundtrip.e2e.test.ts @@ -296,12 +296,14 @@ describe("voice roundtrip e2e", () => { })(); const outputRaw = Buffer.concat(outputAudio.chunks); - const outputFormat = - outputAudio.format === "pcm" - ? "audio/pcm;rate=24000;bits=16" - : outputAudio.format.includes("wav") - ? "audio/wav" - : `audio/${outputAudio.format}`; + let outputFormat: string; + if (outputAudio.format === "pcm") { + outputFormat = "audio/pcm;rate=24000;bits=16"; + } else if (outputAudio.format.includes("wav")) { + outputFormat = "audio/wav"; + } else { + outputFormat = `audio/${outputAudio.format}`; + } const transcription = await withTimeout( sttOutput.transcribe(outputRaw, outputFormat, { label: "voice-roundtrip-output", diff --git a/packages/server/src/services/github-service.ts b/packages/server/src/services/github-service.ts index 7ab8846eb..84ae5572e 100644 --- a/packages/server/src/services/github-service.ts +++ b/packages/server/src/services/github-service.ts @@ -1667,8 +1667,14 @@ function toCurrentPullRequestStatus( const repoIdentity = parseGitHubPullRequestRepo(item.url); const mergedAt = typeof item.mergedAt === "string" && item.mergedAt.trim().length > 0 ? item.mergedAt : null; - const state = - mergedAt !== null ? "merged" : item.state.trim().length > 0 ? item.state.toLowerCase() : ""; + let state: string; + if (mergedAt !== null) { + state = "merged"; + } else if (item.state.trim().length > 0) { + state = item.state.toLowerCase(); + } else { + state = ""; + } const checks = parseStatusCheckRollup(item.statusCheckRollup); return { ...(typeof item.number === "number" ? { number: item.number } : {}), diff --git a/packages/server/src/shared/agent-attention-notification.ts b/packages/server/src/shared/agent-attention-notification.ts index 14b660868..417b34408 100644 --- a/packages/server/src/shared/agent-attention-notification.ts +++ b/packages/server/src/shared/agent-attention-notification.ts @@ -166,30 +166,36 @@ export function findLatestPermissionRequest( return latest; } +function resolveAgentAttentionTitle(reason: AgentAttentionReason): string { + if (reason === "permission") return "Agent needs permission"; + if (reason === "error") return "Agent needs attention"; + return "Agent finished"; +} + +function resolveAgentAttentionPreview( + input: BuildAgentAttentionNotificationPayloadInput, +): string | null { + if (input.reason === "finished") { + return buildNotificationPreview(input.assistantMessage); + } + if (input.reason === "permission") { + return buildNotificationPreview(buildPermissionDetails(input.permissionRequest)); + } + return null; +} + +function resolveAgentAttentionFallbackBody(reason: AgentAttentionReason): string { + if (reason === "permission") return "Permission requested."; + if (reason === "error") return "Encountered an error."; + return "Finished working."; +} + export function buildAgentAttentionNotificationPayload( input: BuildAgentAttentionNotificationPayloadInput, ): AgentAttentionNotificationPayload { - const title = - input.reason === "permission" - ? "Agent needs permission" - : input.reason === "error" - ? "Agent needs attention" - : "Agent finished"; - - const preview = - input.reason === "finished" - ? buildNotificationPreview(input.assistantMessage) - : input.reason === "permission" - ? buildNotificationPreview(buildPermissionDetails(input.permissionRequest)) - : null; - - const body = - preview ?? - (input.reason === "permission" - ? "Permission requested." - : input.reason === "error" - ? "Encountered an error." - : "Finished working."); + const title = resolveAgentAttentionTitle(input.reason); + const preview = resolveAgentAttentionPreview(input); + const body = preview ?? resolveAgentAttentionFallbackBody(input.reason); return { title, diff --git a/packages/server/src/shared/daemon-endpoints.ts b/packages/server/src/shared/daemon-endpoints.ts index c720fa7e5..69366f9e9 100644 --- a/packages/server/src/shared/daemon-endpoints.ts +++ b/packages/server/src/shared/daemon-endpoints.ts @@ -17,8 +17,12 @@ export function normalizeRelayProtocolVersion( return fallback; } - const normalized = - typeof value === "string" ? value.trim() : typeof value === "number" ? String(value) : ""; + let normalized = ""; + if (typeof value === "string") { + normalized = value.trim(); + } else if (typeof value === "number") { + normalized = String(value); + } if (!normalized) { return fallback; } @@ -134,7 +138,14 @@ export function extractHostPortFromWebSocketUrl(wsUrl: string): string { } const host = parsed.hostname; - const port = parsed.port ? Number(parsed.port) : parsed.protocol === "wss:" ? 443 : 80; + let port: number; + if (parsed.port) { + port = Number(parsed.port); + } else if (parsed.protocol === "wss:") { + port = 443; + } else { + port = 80; + } if (!host) { throw new Error("Invalid WebSocket URL (missing hostname)"); } diff --git a/packages/server/src/terminal/terminal.ts b/packages/server/src/terminal/terminal.ts index 7a7c83f7b..58b303550 100644 --- a/packages/server/src/terminal/terminal.ts +++ b/packages/server/src/terminal/terminal.ts @@ -331,12 +331,14 @@ function normalizeProcessToken(token: string): string { return token; } - const quote = - token.startsWith('"') && token.endsWith('"') - ? '"' - : token.startsWith("'") && token.endsWith("'") - ? "'" - : ""; + let quote: "'" | '"' | ""; + if (token.startsWith('"') && token.endsWith('"')) { + quote = '"'; + } else if (token.startsWith("'") && token.endsWith("'")) { + quote = "'"; + } else { + quote = ""; + } const rawToken = quote ? token.slice(1, -1) : token; if (rawToken.length === 0) { return token; diff --git a/packages/server/src/utils/worktree.ts b/packages/server/src/utils/worktree.ts index 87a223b6c..1a493bb2d 100644 --- a/packages/server/src/utils/worktree.ts +++ b/packages/server/src/utils/worktree.ts @@ -536,12 +536,14 @@ async function assertPortAvailable(port: number): Promise { await new Promise((resolve, reject) => { const server = net.createServer(); server.once("error", (error: NodeJS.ErrnoException) => { - const message = - error?.code === "EADDRINUSE" - ? `Persisted worktree port ${port} is already in use` - : error instanceof Error - ? error.message - : String(error); + let message: string; + if (error?.code === "EADDRINUSE") { + message = `Persisted worktree port ${port} is already in use`; + } else if (error instanceof Error) { + message = error.message; + } else { + message = String(error); + } reject(new Error(message)); }); server.listen(port, () => {