chore(lint): flatten nested ternaries in server (no-nested-ternary)

This commit is contained in:
Mohamed Boudra
2026-04-23 23:12:35 +07:00
parent dffd8c4343
commit 9715df9385
24 changed files with 499 additions and 430 deletions

View File

@@ -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,
);

View File

@@ -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;

View File

@@ -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 }
: {}),

View File

@@ -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<string, unknown>,
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;

View File

@@ -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",

View File

@@ -203,22 +203,29 @@ const ClaudeToolCallPass2Schema = z.discriminatedUnion("toolKind", [
type ClaudeToolCallPass2 = z.infer<typeof ClaudeToolCallPass2Schema>;
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 {

View File

@@ -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;
}

View File

@@ -549,36 +549,36 @@ export async function parseRolloutFile(filePath: string): Promise<AgentTimelineI
.reduce((map, record) => map.set(record.callId, record.output), new Map<string, unknown>());
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);
}

View File

@@ -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,
});
}

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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,

View File

@@ -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);
});

View File

@@ -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<SessionInboundMessage, { type: "fetch_agents_request" }>,
): Promise<void> {
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<SessionInboundMessage, { type: "fetch_workspaces_request" }>,
): Promise<void> {
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({

View File

@@ -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;
}

View File

@@ -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");

View File

@@ -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),
};
}

View File

@@ -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",

View File

@@ -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 } : {}),

View File

@@ -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,

View File

@@ -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)");
}

View File

@@ -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;

View File

@@ -536,12 +536,14 @@ async function assertPortAvailable(port: number): Promise<void> {
await new Promise<void>((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, () => {