diff --git a/apps/server/src/auth/Layers/AuthControlPlane.ts b/apps/server/src/auth/Layers/AuthControlPlane.ts index aea07fd286f..6c42cd2db17 100644 --- a/apps/server/src/auth/Layers/AuthControlPlane.ts +++ b/apps/server/src/auth/Layers/AuthControlPlane.ts @@ -64,11 +64,16 @@ export const makeAuthControlPlane = Effect.gen(function* () { const listPairingLinks: AuthControlPlaneShape["listPairingLinks"] = (input) => bootstrapCredentials.listActive().pipe( - Effect.map((pairingLinks) => - pairingLinks - .filter((pairingLink) => (input?.role ? pairingLink.role === input.role : true)) - .filter((pairingLink) => !input?.excludeSubjects?.includes(pairingLink.subject)) - .map((pairingLink) => + Effect.map((pairingLinks) => { + const activeLinks: Array = []; + for (const pairingLink of pairingLinks) { + if (input?.role && pairingLink.role !== input.role) { + continue; + } + if (input?.excludeSubjects?.includes(pairingLink.subject)) { + continue; + } + activeLinks.push( pairingLink.label ? ({ id: pairingLink.id, @@ -87,11 +92,12 @@ export const makeAuthControlPlane = Effect.gen(function* () { createdAt: pairingLink.createdAt, expiresAt: pairingLink.expiresAt, } satisfies AuthPairingLink), - ) - .toSorted( - (left, right) => right.createdAt.epochMilliseconds - left.createdAt.epochMilliseconds, - ), - ), + ); + } + return activeLinks.toSorted( + (left, right) => right.createdAt.epochMilliseconds - left.createdAt.epochMilliseconds, + ); + }), Effect.mapError(toAuthControlPlaneError("Failed to list pairing links.")), ); diff --git a/apps/server/src/git/remoteRefs.ts b/apps/server/src/git/remoteRefs.ts index b5dfc87bd07..e3873effe47 100644 --- a/apps/server/src/git/remoteRefs.ts +++ b/apps/server/src/git/remoteRefs.ts @@ -1,8 +1,12 @@ export function parseRemoteNamesInGitOrder(stdout: string): ReadonlyArray { - return stdout - .split("\n") - .map((line) => line.trim()) - .filter((line) => line.length > 0); + const remoteNames: Array = []; + for (const line of stdout.split("\n")) { + const remoteName = line.trim(); + if (remoteName.length > 0) { + remoteNames.push(remoteName); + } + } + return remoteNames; } export function parseRemoteNames(stdout: string): ReadonlyArray { diff --git a/apps/server/src/imageMime.ts b/apps/server/src/imageMime.ts index 814abbb32c1..c8761285abe 100644 --- a/apps/server/src/imageMime.ts +++ b/apps/server/src/imageMime.ts @@ -35,10 +35,13 @@ export function parseBase64DataUrl( const match = /^data:([^,]+),([a-z0-9+/=\r\n ]+)$/i.exec(dataUrl.trim()); if (!match) return null; - const headerParts = (match[1] ?? "") - .split(";") - .map((part) => part.trim()) - .filter((part) => part.length > 0); + const headerParts: Array = []; + for (const part of (match[1] ?? "").split(";")) { + const trimmed = part.trim(); + if (trimmed.length > 0) { + headerParts.push(trimmed); + } + } if (headerParts.length < 2) { return null; } diff --git a/apps/server/src/keybindings.ts b/apps/server/src/keybindings.ts index a197984f23a..80b522eee71 100644 --- a/apps/server/src/keybindings.ts +++ b/apps/server/src/keybindings.ts @@ -32,6 +32,7 @@ import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Predicate from "effect/Predicate"; import * as PubSub from "effect/PubSub"; +import * as Result from "effect/Result"; import * as Schema from "effect/Schema"; import * as SchemaIssue from "effect/SchemaIssue"; import * as SchemaTransformation from "effect/SchemaTransformation"; @@ -538,9 +539,11 @@ const makeKeybindings = Effect.gen(function* () { return; } - const matchingDefaults = DEFAULT_KEYBINDINGS.filter((defaultRule) => - customConfig.some((entry) => isSameKeybindingRule(entry, defaultRule)), - ).map((rule) => rule.command); + const matchingDefaults = Array.filterMap(DEFAULT_KEYBINDINGS, (defaultRule) => + customConfig.some((entry) => isSameKeybindingRule(entry, defaultRule)) + ? Result.succeed(defaultRule.command) + : Result.failVoid, + ); if (matchingDefaults.length > 0) { yield* Effect.logWarning("default keybinding rule already defined in user config", { path: keybindingsConfigPath, diff --git a/apps/server/src/orchestration/Layers/CheckpointReactor.ts b/apps/server/src/orchestration/Layers/CheckpointReactor.ts index f95d5190bb0..40291ec4f66 100644 --- a/apps/server/src/orchestration/Layers/CheckpointReactor.ts +++ b/apps/server/src/orchestration/Layers/CheckpointReactor.ts @@ -1,5 +1,6 @@ import { CommandId, + type CheckpointRef, EventId, MessageId, type ProjectId, @@ -701,9 +702,12 @@ const make = Effect.gen(function* () { }); } - const staleCheckpointRefs = thread.checkpoints - .filter((checkpoint) => checkpoint.checkpointTurnCount > event.payload.turnCount) - .map((checkpoint) => checkpoint.checkpointRef); + const staleCheckpointRefs: Array = []; + for (const checkpoint of thread.checkpoints) { + if (checkpoint.checkpointTurnCount > event.payload.turnCount) { + staleCheckpointRefs.push(checkpoint.checkpointRef); + } + } if (staleCheckpointRefs.length > 0) { yield* checkpointStore.deleteCheckpointRefs({ diff --git a/apps/server/src/orchestration/Layers/ProjectionPipeline.ts b/apps/server/src/orchestration/Layers/ProjectionPipeline.ts index 1161ff6a7d7..c201e1d9f9e 100644 --- a/apps/server/src/orchestration/Layers/ProjectionPipeline.ts +++ b/apps/server/src/orchestration/Layers/ProjectionPipeline.ts @@ -537,12 +537,15 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti projectionPendingApprovalRepository.listByThreadId({ threadId }), ]); - const latestUserMessageAt = - messages - .filter((message) => message.role === "user") - .map((message) => message.createdAt) - .toSorted() - .at(-1) ?? null; + let latestUserMessageAt: string | null = null; + for (const message of messages) { + if ( + message.role === "user" && + (latestUserMessageAt === null || message.createdAt > latestUserMessageAt) + ) { + latestUserMessageAt = message.createdAt; + } + } const pendingApprovalCount = pendingApprovals.filter( (approval) => approval.status === "pending", diff --git a/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts b/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts index 9b3c0fa7ad4..e629d1604b3 100644 --- a/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts +++ b/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts @@ -24,9 +24,11 @@ import { ProjectId, ThreadId, } from "@t3tools/contracts"; +import * as Arr from "effect/Array"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; +import * as Result from "effect/Result"; import * as Schema from "effect/Schema"; import * as Struct from "effect/Struct"; import * as SqlClient from "effect/unstable/sql/SqlClient"; @@ -1488,34 +1490,36 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { const snapshot = { snapshotSequence: computeSnapshotSequence(stateRows), - projects: projectRows - .filter((row) => row.deletedAt === null) - .map((row) => - mapProjectShellRow(row, repositoryIdentities.get(row.projectId) ?? null), - ), - threads: threadRows - .filter((row) => row.deletedAt === null) - .map( - (row): OrchestrationThreadShell => ({ - id: row.threadId, - projectId: row.projectId, - title: row.title, - modelSelection: row.modelSelection, - runtimeMode: row.runtimeMode, - interactionMode: row.interactionMode, - branch: row.branch, - worktreePath: row.worktreePath, - latestTurn: latestTurnByThread.get(row.threadId) ?? null, - createdAt: row.createdAt, - updatedAt: row.updatedAt, - archivedAt: row.archivedAt, - session: sessionByThread.get(row.threadId) ?? null, - latestUserMessageAt: row.latestUserMessageAt, - hasPendingApprovals: row.pendingApprovalCount > 0, - hasPendingUserInput: row.pendingUserInputCount > 0, - hasActionableProposedPlan: row.hasActionableProposedPlan > 0, - }), - ), + projects: Arr.filterMap(projectRows, (row) => + row.deletedAt === null + ? Result.succeed( + mapProjectShellRow(row, repositoryIdentities.get(row.projectId) ?? null), + ) + : Result.failVoid, + ), + threads: Arr.filterMap(threadRows, (row) => + row.deletedAt === null + ? Result.succeed({ + id: row.threadId, + projectId: row.projectId, + title: row.title, + modelSelection: row.modelSelection, + runtimeMode: row.runtimeMode, + interactionMode: row.interactionMode, + branch: row.branch, + worktreePath: row.worktreePath, + latestTurn: latestTurnByThread.get(row.threadId) ?? null, + createdAt: row.createdAt, + updatedAt: row.updatedAt, + archivedAt: row.archivedAt, + session: sessionByThread.get(row.threadId) ?? null, + latestUserMessageAt: row.latestUserMessageAt, + hasPendingApprovals: row.pendingApprovalCount > 0, + hasPendingUserInput: row.pendingUserInputCount > 0, + hasActionableProposedPlan: row.hasActionableProposedPlan > 0, + } satisfies OrchestrationThreadShell) + : Result.failVoid, + ), updatedAt: updatedAt ?? "1970-01-01T00:00:00.000Z", }; @@ -1621,11 +1625,13 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { const snapshot = { snapshotSequence: computeSnapshotSequence(stateRows), - projects: projectRows - .filter((row) => row.deletedAt === null && activeProjectIds.has(row.projectId)) - .map((row) => - mapProjectShellRow(row, repositoryIdentities.get(row.projectId) ?? null), - ), + projects: Arr.filterMap(projectRows, (row) => + row.deletedAt === null && activeProjectIds.has(row.projectId) + ? Result.succeed( + mapProjectShellRow(row, repositoryIdentities.get(row.projectId) ?? null), + ) + : Result.failVoid, + ), threads: threadRows.map( (row): OrchestrationThreadShell => ({ id: row.threadId, diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.ts b/apps/server/src/provider/Layers/ClaudeAdapter.ts index a132bf73386..ff9bbfe0231 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.ts @@ -245,9 +245,13 @@ function toProcessError( function normalizeClaudeStreamMessages( cause: Cause.Cause<{ readonly message: string }>, ): ReadonlyArray { - const errors = Cause.prettyErrors(cause) - .map((error) => error.message.trim()) - .filter((message) => message.length > 0); + const errors: Array = []; + for (const error of Cause.prettyErrors(cause)) { + const message = error.message.trim(); + if (message.length > 0) { + errors.push(message); + } + } if (errors.length > 0) { return errors; } diff --git a/apps/server/src/provider/Layers/ClaudeProvider.ts b/apps/server/src/provider/Layers/ClaudeProvider.ts index 6522513d015..badbf98905a 100644 --- a/apps/server/src/provider/Layers/ClaudeProvider.ts +++ b/apps/server/src/provider/Layers/ClaudeProvider.ts @@ -317,11 +317,13 @@ export function resolveClaudeApiModelId(modelSelection: ModelSelection): string } function toTitleCaseWords(value: string): string { - return value - .split(/[\s_-]+/g) - .filter(Boolean) - .map((part) => part[0]!.toUpperCase() + part.slice(1).toLowerCase()) - .join(" "); + const parts: Array = []; + for (const part of value.split(/[\s_-]+/g)) { + if (part.length > 0) { + parts.push(part[0]!.toUpperCase() + part.slice(1).toLowerCase()); + } + } + return parts.join(" "); } function claudeSubscriptionLabel(subscriptionType: string | undefined): string | undefined { diff --git a/apps/server/src/provider/Layers/CodexProvider.ts b/apps/server/src/provider/Layers/CodexProvider.ts index 178450fb7fd..4d793194c1d 100644 --- a/apps/server/src/provider/Layers/CodexProvider.ts +++ b/apps/server/src/provider/Layers/CodexProvider.ts @@ -319,16 +319,21 @@ const probeCodexAppServerProvider = Effect.fn("probeCodexAppServerProvider")(fun } satisfies CodexAppServerProviderSnapshot; }); -const emptyCodexModelsFromSettings = (codexSettings: CodexSettings): ServerProvider["models"] => - codexSettings.customModels - .map((model) => model.trim()) - .filter((model, index, models) => model.length > 0 && models.indexOf(model) === index) - .map((model) => ({ - slug: model, - name: model, - isCustom: true, - capabilities: null, - })); +const emptyCodexModelsFromSettings = (codexSettings: CodexSettings): ServerProvider["models"] => { + const models = new Set(); + for (const model of codexSettings.customModels) { + const trimmed = model.trim(); + if (trimmed.length > 0) { + models.add(trimmed); + } + } + return Array.from(models, (model) => ({ + slug: model, + name: model, + isCustom: true, + capabilities: null, + })); +}; const makePendingCodexProvider = ( codexSettings: CodexSettings, diff --git a/apps/server/src/provider/Layers/CursorProvider.ts b/apps/server/src/provider/Layers/CursorProvider.ts index 035c08437a9..88a963f693b 100644 --- a/apps/server/src/provider/Layers/CursorProvider.ts +++ b/apps/server/src/provider/Layers/CursorProvider.ts @@ -596,11 +596,12 @@ export const discoverCursorModelCapabilitiesViaAcp = ( ); } - const targetModelSlugs = new Set( - existingModels - .filter((model) => !model.isCustom && !hasCursorModelCapabilities(model)) - .map((model) => model.slug), - ); + const targetModelSlugs = new Set(); + for (const model of existingModels) { + if (!model.isCustom && !hasCursorModelCapabilities(model)) { + targetModelSlugs.add(model.slug); + } + } if (targetModelSlugs.size === 0) { return buildCursorDiscoveredModels( modelChoices.map((modelChoice) => ({ @@ -721,9 +722,13 @@ export interface CursorAboutResult { } function joinProviderMessages(...messages: ReadonlyArray): string | undefined { - const parts = messages - .map((message) => message?.trim()) - .filter((message): message is string => Boolean(message)); + const parts: Array = []; + for (const message of messages) { + const trimmed = message?.trim(); + if (trimmed) { + parts.push(trimmed); + } + } return parts.length > 0 ? parts.join(" ") : undefined; } @@ -790,11 +795,13 @@ export function parseCursorCliConfigChannel(raw: string): string | undefined { } function toTitleCaseWords(value: string): string { - return value - .split(/[\s_-]+/g) - .filter((part) => part.length > 0) - .map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()) - .join(" "); + const parts: Array = []; + for (const part of value.split(/[\s_-]+/g)) { + if (part.length > 0) { + parts.push(part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()); + } + } + return parts.join(" "); } function cursorSubscriptionLabel(subscriptionType: string | undefined): string | undefined { diff --git a/apps/server/src/provider/Layers/OpenCodeAdapter.ts b/apps/server/src/provider/Layers/OpenCodeAdapter.ts index bfc36aff4c3..34a67199125 100644 --- a/apps/server/src/provider/Layers/OpenCodeAdapter.ts +++ b/apps/server/src/provider/Layers/OpenCodeAdapter.ts @@ -1366,12 +1366,15 @@ export function makeOpenCodeAdapter( }), ).pipe(Effect.mapError(toRequestError)); - const turns = (messages.data ?? []) - .filter((entry) => entry.info.role === "assistant") - .map((entry) => ({ - id: TurnId.make(entry.info.id), - items: [entry.info, ...entry.parts], - })); + const turns: Array = []; + for (const entry of messages.data ?? []) { + if (entry.info.role === "assistant") { + turns.push({ + id: TurnId.make(entry.info.id), + items: [entry.info, ...entry.parts], + }); + } + } return { threadId, diff --git a/apps/server/src/provider/Layers/OpenCodeProvider.ts b/apps/server/src/provider/Layers/OpenCodeProvider.ts index fa436b5fbe0..8842b1da5ce 100644 --- a/apps/server/src/provider/Layers/OpenCodeProvider.ts +++ b/apps/server/src/provider/Layers/OpenCodeProvider.ts @@ -137,11 +137,13 @@ function formatOpenCodeProbeError(input: { } function titleCaseSlug(value: string): string { - return value - .split(/[-_/]+/) - .filter((segment) => segment.length > 0) - .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)) - .join(" "); + const segments: Array = []; + for (const segment of value.split(/[-_/]+/)) { + if (segment.length > 0) { + segments.push(segment.charAt(0).toUpperCase() + segment.slice(1)); + } + } + return segments.join(" "); } function inferDefaultVariant( diff --git a/apps/server/src/provider/acp/AcpRuntimeModel.ts b/apps/server/src/provider/acp/AcpRuntimeModel.ts index ffd214a5bf1..3587d703dbd 100644 --- a/apps/server/src/provider/acp/AcpRuntimeModel.ts +++ b/apps/server/src/provider/acp/AcpRuntimeModel.ts @@ -126,19 +126,20 @@ export function parseSessionModeState( if (!currentModeId) { return undefined; } - const availableModes = modes.availableModes - .map((mode) => { - const id = mode.id.trim(); - const name = mode.name.trim(); - if (!id || !name) { - return undefined; - } - const description = mode.description?.trim() || undefined; - return description !== undefined + const availableModes: Array = []; + for (const mode of modes.availableModes) { + const id = mode.id.trim(); + const name = mode.name.trim(); + if (!id || !name) { + continue; + } + const description = mode.description?.trim() || undefined; + availableModes.push( + description !== undefined ? ({ id, name, description } satisfies AcpSessionMode) - : ({ id, name } satisfies AcpSessionMode); - }) - .filter((mode): mode is AcpSessionMode => mode !== undefined); + : ({ id, name } satisfies AcpSessionMode), + ); + } if (availableModes.length === 0) { return undefined; } @@ -186,9 +187,15 @@ function normalizeCommandValue(value: unknown): string | undefined { if (!Array.isArray(value)) { return undefined; } - const parts = value - .map((entry) => (typeof entry === "string" && entry.trim().length > 0 ? entry.trim() : null)) - .filter((entry): entry is string => entry !== null); + const parts: Array = []; + for (const entry of value) { + if (typeof entry === "string") { + const part = entry.trim(); + if (part.length > 0) { + parts.push(part); + } + } + } return parts.length > 0 ? parts.join(" ") : undefined; } @@ -222,18 +229,20 @@ function extractTextContentFromToolCallContent( content: ReadonlyArray | null | undefined, ): string | undefined { if (!content) return undefined; - const chunks = content - .map((entry) => { - if (entry.type !== "content") { - return undefined; - } - const nestedContent = entry.content; - if (nestedContent.type !== "text") { - return undefined; - } - return nestedContent.text.trim().length > 0 ? nestedContent.text.trim() : undefined; - }) - .filter((entry): entry is string => entry !== undefined); + const chunks: Array = []; + for (const entry of content) { + if (entry.type !== "content") { + continue; + } + const nestedContent = entry.content; + if (nestedContent.type !== "text") { + continue; + } + const text = nestedContent.text.trim(); + if (text.length > 0) { + chunks.push(text); + } + } return chunks.length > 0 ? chunks.join("\n") : undefined; } diff --git a/apps/server/src/provider/providerMaintenanceRunner.ts b/apps/server/src/provider/providerMaintenanceRunner.ts index 6a759b79f7d..f04e97a13ec 100644 --- a/apps/server/src/provider/providerMaintenanceRunner.ts +++ b/apps/server/src/provider/providerMaintenanceRunner.ts @@ -214,13 +214,15 @@ export const make = Effect.fn("ProviderMaintenanceRunner.make")(function* () { instanceId: ProviderInstanceId, ): Effect.Effect => providerRegistry.getProviders.pipe( - Effect.map((providers) => - providers - .filter( - (candidate) => candidate.driver === provider && candidate.instanceId === instanceId, - ) - .map((candidate) => candidate.instanceId), - ), + Effect.map((providers) => { + const instanceIds: Array = []; + for (const candidate of providers) { + if (candidate.driver === provider && candidate.instanceId === instanceId) { + instanceIds.push(candidate.instanceId); + } + } + return instanceIds; + }), Effect.flatMap((instanceIds) => instanceIds.length === 0 ? providerRegistry.refreshInstance(instanceId) diff --git a/apps/server/src/sourceControl/AzureDevOpsCli.ts b/apps/server/src/sourceControl/AzureDevOpsCli.ts index 29a57f993c4..fe4c76b288d 100644 --- a/apps/server/src/sourceControl/AzureDevOpsCli.ts +++ b/apps/server/src/sourceControl/AzureDevOpsCli.ts @@ -208,10 +208,13 @@ function parseRepositorySpecifier(repository: string): { readonly project: string | null; readonly name: string; } { - const parts = repository - .split("/") - .map((part) => part.trim()) - .filter((part) => part.length > 0); + const parts: Array = []; + for (const part of repository.split("/")) { + const trimmed = part.trim(); + if (trimmed.length > 0) { + parts.push(trimmed); + } + } return { project: parts.length > 1 ? (parts.at(-2) ?? null) : null, name: parts.at(-1) ?? repository.trim(), diff --git a/apps/server/src/sourceControl/GitLabCli.ts b/apps/server/src/sourceControl/GitLabCli.ts index 789e0cf72e3..b4e039cfd8e 100644 --- a/apps/server/src/sourceControl/GitLabCli.ts +++ b/apps/server/src/sourceControl/GitLabCli.ts @@ -247,10 +247,13 @@ function parseRepositoryPath(repository: string): { readonly namespacePath: string | null; readonly projectPath: string; } { - const parts = repository - .split("/") - .map((part) => part.trim()) - .filter((part) => part.length > 0); + const parts: Array = []; + for (const part of repository.split("/")) { + const trimmed = part.trim(); + if (trimmed.length > 0) { + parts.push(trimmed); + } + } const projectPath = parts.at(-1) ?? repository.trim(); const namespacePath = parts.length > 1 ? parts.slice(0, -1).join("/") : null; return { namespacePath, projectPath }; diff --git a/apps/server/src/sourceControl/SourceControlProviderDiscovery.ts b/apps/server/src/sourceControl/SourceControlProviderDiscovery.ts index 23bbada043a..b42cb853a57 100644 --- a/apps/server/src/sourceControl/SourceControlProviderDiscovery.ts +++ b/apps/server/src/sourceControl/SourceControlProviderDiscovery.ts @@ -96,15 +96,24 @@ export function unknownAuth(detail?: string): SourceControlProviderAuth { } export function combinedAuthOutput(input: SourceControlAuthProbeInput): string { - return [input.stdout, input.stderr].filter((entry) => entry.trim().length > 0).join("\n"); + const parts: string[] = []; + for (const entry of [input.stdout, input.stderr]) { + if (entry.trim().length > 0) { + parts.push(entry); + } + } + return parts.join("\n"); } function sanitizedAuthLines(text: string): ReadonlyArray { - return text - .split(/\r?\n/) - .map((entry) => entry.trim()) - .filter((entry) => entry.length > 0) - .filter((entry) => !/^[-\s]*token(?:\s+scopes?)?:/iu.test(entry)); + const lines: string[] = []; + for (const entry of text.split(/\r?\n/)) { + const line = entry.trim(); + if (line.length === 0) continue; + if (/^[-\s]*token(?:\s+scopes?)?:/iu.test(line)) continue; + lines.push(line); + } + return lines; } export function firstSafeAuthLine(text: string): string | undefined { diff --git a/apps/server/src/sourceControl/SourceControlProviderRegistry.ts b/apps/server/src/sourceControl/SourceControlProviderRegistry.ts index 5b625e6690b..b927d8c77b6 100644 --- a/apps/server/src/sourceControl/SourceControlProviderRegistry.ts +++ b/apps/server/src/sourceControl/SourceControlProviderRegistry.ts @@ -92,18 +92,17 @@ function selectProviderContext( readonly url: string; }>, ): SourceControlProvider.SourceControlProviderContext | null { - const candidates = remotes - .map((remote) => { - const provider = detectSourceControlProviderFromRemoteUrl(remote.url); - return provider - ? { - provider, - remoteName: remote.name, - remoteUrl: remote.url, - } - : null; - }) - .filter((value): value is SourceControlProvider.SourceControlProviderContext => value !== null); + const candidates: Array = []; + for (const remote of remotes) { + const provider = detectSourceControlProviderFromRemoteUrl(remote.url); + if (provider) { + candidates.push({ + provider, + remoteName: remote.name, + remoteUrl: remote.url, + }); + } + } return ( candidates.find((candidate) => candidate.remoteName === "origin") ?? diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts index 19f12862dad..7a4fb6dd46f 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.ts @@ -1,3 +1,4 @@ +import * as Arr from "effect/Array"; import * as Cache from "effect/Cache"; import * as Data from "effect/Data"; import * as DateTime from "effect/DateTime"; @@ -1793,61 +1794,60 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* } } - const localBranches = localBranchResult.stdout - .split("\n") - .map(parseBranchLine) - .filter((refName): refName is { name: string; current: boolean } => refName !== null) - .map((refName) => ({ - name: refName.name, - current: refName.current, - isRemote: false, - isDefault: refName.name === defaultBranch, - worktreePath: worktreeMap.get(refName.name) ?? null, - })) - .toSorted((a, b) => { - const aPriority = a.current ? 0 : a.isDefault ? 1 : 2; - const bPriority = b.current ? 0 : b.isDefault ? 1 : 2; - if (aPriority !== bPriority) return aPriority - bPriority; - - const aLastCommit = branchLastCommit.get(a.name) ?? 0; - const bLastCommit = branchLastCommit.get(b.name) ?? 0; - if (aLastCommit !== bLastCommit) return bLastCommit - aLastCommit; - return a.name.localeCompare(b.name); - }); + const localBranches = Arr.filterMap(localBranchResult.stdout.split("\n"), (line) => { + const refName = parseBranchLine(line); + return refName === null + ? Result.failVoid + : Result.succeed({ + name: refName.name, + current: refName.current, + isRemote: false, + isDefault: refName.name === defaultBranch, + worktreePath: worktreeMap.get(refName.name) ?? null, + }); + }).toSorted((a, b) => { + const aPriority = a.current ? 0 : a.isDefault ? 1 : 2; + const bPriority = b.current ? 0 : b.isDefault ? 1 : 2; + if (aPriority !== bPriority) return aPriority - bPriority; + + const aLastCommit = branchLastCommit.get(a.name) ?? 0; + const bLastCommit = branchLastCommit.get(b.name) ?? 0; + if (aLastCommit !== bLastCommit) return bLastCommit - aLastCommit; + return a.name.localeCompare(b.name); + }); const remoteBranches = remoteBranchResult.exitCode === 0 - ? remoteBranchResult.stdout - .split("\n") - .map(parseBranchLine) - .filter((refName): refName is { name: string; current: boolean } => refName !== null) - .map((refName) => { - const parsedRemoteRef = parseRemoteRefWithRemoteNames(refName.name, remoteNames); - const remoteBranch: { - name: string; - current: boolean; - isRemote: boolean; - remoteName?: string; - isDefault: boolean; - worktreePath: string | null; - } = { - name: refName.name, - current: false, - isRemote: true, - isDefault: false, - worktreePath: null, - }; - if (parsedRemoteRef) { - remoteBranch.remoteName = parsedRemoteRef.remoteName; - } - return remoteBranch; - }) - .toSorted((a, b) => { - const aLastCommit = branchLastCommit.get(a.name) ?? 0; - const bLastCommit = branchLastCommit.get(b.name) ?? 0; - if (aLastCommit !== bLastCommit) return bLastCommit - aLastCommit; - return a.name.localeCompare(b.name); - }) + ? Arr.filterMap(remoteBranchResult.stdout.split("\n"), (line) => { + const refName = parseBranchLine(line); + if (refName === null) { + return Result.failVoid; + } + const parsedRemoteRef = parseRemoteRefWithRemoteNames(refName.name, remoteNames); + const remoteBranch: { + name: string; + current: boolean; + isRemote: boolean; + remoteName?: string; + isDefault: boolean; + worktreePath: string | null; + } = { + name: refName.name, + current: false, + isRemote: true, + isDefault: false, + worktreePath: null, + }; + if (parsedRemoteRef) { + remoteBranch.remoteName = parsedRemoteRef.remoteName; + } + return Result.succeed(remoteBranch); + }).toSorted((a, b) => { + const aLastCommit = branchLastCommit.get(a.name) ?? 0; + const bLastCommit = branchLastCommit.get(b.name) ?? 0; + if (aLastCommit !== bLastCommit) return bLastCommit - aLastCommit; + return a.name.localeCompare(b.name); + }) : []; const refs = paginateBranches({ @@ -2106,12 +2106,16 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* "--no-column", "--format=%(refname:short)", ]).pipe( - Effect.map((stdout) => - stdout - .split("\n") - .map((line) => line.trim()) - .filter((line) => line.length > 0), - ), + Effect.map((stdout) => { + const branchNames: Array = []; + for (const line of stdout.split("\n")) { + const branchName = line.trim(); + if (branchName.length > 0) { + branchNames.push(branchName); + } + } + return branchNames; + }), ); return GitVcsDriver.GitVcsDriver.of({ diff --git a/apps/server/src/workspace/Layers/WorkspaceEntries.ts b/apps/server/src/workspace/Layers/WorkspaceEntries.ts index 0c0ab638207..db4a5c230c3 100644 --- a/apps/server/src/workspace/Layers/WorkspaceEntries.ts +++ b/apps/server/src/workspace/Layers/WorkspaceEntries.ts @@ -221,9 +221,13 @@ export const makeWorkspaceEntries = Effect.gen(function* () { return null; } - const listedPaths = [...listedFiles.paths] - .map((entry) => toPosixPath(entry)) - .filter((entry) => entry.length > 0 && !isPathInIgnoredDirectory(entry)); + const listedPaths: Array = []; + for (const rawEntry of listedFiles.paths) { + const entry = toPosixPath(rawEntry); + if (entry.length > 0 && !isPathInIgnoredDirectory(entry)) { + listedPaths.push(entry); + } + } const filePaths = yield* vcs.driver.filterIgnoredPaths(cwd, listedPaths).pipe( Effect.map((paths) => [...paths]), Effect.catch(() => filterVcsIgnoredPaths(cwd, listedPaths)), @@ -456,21 +460,23 @@ export const makeWorkspaceEntries = Effect.gen(function* () { const showHidden = endsWithSeparator || prefix.startsWith("."); const lowerPrefix = prefix.toLowerCase(); + const entries: Array<{ readonly name: string; readonly fullPath: string }> = []; + for (const dirent of dirents) { + if ( + dirent.isDirectory() && + dirent.name.toLowerCase().startsWith(lowerPrefix) && + (showHidden || !dirent.name.startsWith(".")) + ) { + entries.push({ + name: dirent.name, + fullPath: path.join(parentPath, dirent.name), + }); + } + } return { parentPath, - entries: dirents - .filter( - (dirent) => - dirent.isDirectory() && - dirent.name.toLowerCase().startsWith(lowerPrefix) && - (showHidden || !dirent.name.startsWith(".")), - ) - .map((dirent) => ({ - name: dirent.name, - fullPath: path.join(parentPath, dirent.name), - })) - .toSorted((left, right) => left.name.localeCompare(right.name)), + entries: entries.toSorted((left, right) => left.name.localeCompare(right.name)), }; }, ); diff --git a/apps/web/src/components/CommandPalette.logic.ts b/apps/web/src/components/CommandPalette.logic.ts index 3f4997e215c..4484232c2b8 100644 --- a/apps/web/src/components/CommandPalette.logic.ts +++ b/apps/web/src/components/CommandPalette.logic.ts @@ -1,5 +1,7 @@ import { type KeybindingCommand, type FilesystemBrowseEntry } from "@t3tools/contracts"; import type { SidebarThreadSortOrder } from "@t3tools/contracts/settings"; +import * as Arr from "effect/Array"; +import * as Result from "effect/Result"; import { type ReactNode } from "react"; import { sortThreads } from "../lib/threadSort"; import { formatRelativeTimeLabel } from "../timestampFormat"; @@ -252,23 +254,18 @@ export function filterCommandPaletteGroups(input: { } return searchableGroups.flatMap((group) => { - const items = group.items - .map((item, index) => { - const haystack = normalizeSearchText(item.searchTerms.join(" ")); - if (!haystack.includes(normalizedQuery)) { - return null; - } - - return { - item, - index, - rank: rankCommandPaletteItemMatch(item, normalizedQuery), - }; - }) - .filter( - (entry): entry is { item: (typeof group.items)[number]; index: number; rank: number } => - entry !== null, - ) + const items = Arr.filterMap(group.items, (item, index) => { + const haystack = normalizeSearchText(item.searchTerms.join(" ")); + if (!haystack.includes(normalizedQuery)) { + return Result.failVoid; + } + + return Result.succeed({ + item, + index, + rank: rankCommandPaletteItemMatch(item, normalizedQuery), + }); + }) .toSorted((left, right) => right.rank - left.rank || left.index - right.index) .map((entry) => entry.item); diff --git a/apps/web/src/components/DiffPanel.tsx b/apps/web/src/components/DiffPanel.tsx index f178a69fb43..33281ed58e1 100644 --- a/apps/web/src/components/DiffPanel.tsx +++ b/apps/web/src/components/DiffPanel.tsx @@ -261,12 +261,14 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { [selectedCheckpointTurnCount], ); const conversationCheckpointTurnCount = useMemo(() => { - const turnCounts = orderedTurnDiffSummaries - .map( - (summary) => - summary.checkpointTurnCount ?? inferredCheckpointTurnCountByTurnId[summary.turnId], - ) - .filter((value): value is number => typeof value === "number"); + const turnCounts: Array = []; + for (const summary of orderedTurnDiffSummaries) { + const value = + summary.checkpointTurnCount ?? inferredCheckpointTurnCountByTurnId[summary.turnId]; + if (typeof value === "number") { + turnCounts.push(value); + } + } if (turnCounts.length === 0) { return undefined; } diff --git a/apps/web/src/components/chat/ChatComposer.tsx b/apps/web/src/components/chat/ChatComposer.tsx index b19e1240364..41535110091 100644 --- a/apps/web/src/components/chat/ChatComposer.tsx +++ b/apps/web/src/components/chat/ChatComposer.tsx @@ -1323,9 +1323,12 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) } catch { const currentImageIds = new Set(composerImages.map((image) => image.id)); const fallbackPersistedAttachments = getPersistedAttachmentsForThread(); - const fallbackPersistedIds = fallbackPersistedAttachments - .map((attachment) => attachment.id) - .filter((id) => currentImageIds.has(id)); + const fallbackPersistedIds: Array = []; + for (const attachment of fallbackPersistedAttachments) { + if (currentImageIds.has(attachment.id)) { + fallbackPersistedIds.push(attachment.id); + } + } const fallbackPersistedIdSet = new Set(fallbackPersistedIds); const fallbackAttachments = fallbackPersistedAttachments.filter((attachment) => fallbackPersistedIdSet.has(attachment.id), diff --git a/apps/web/src/components/chat/ModelPickerContent.tsx b/apps/web/src/components/chat/ModelPickerContent.tsx index c3468ef8c65..c9fbd2b0fc1 100644 --- a/apps/web/src/components/chat/ModelPickerContent.tsx +++ b/apps/web/src/components/chat/ModelPickerContent.tsx @@ -272,8 +272,13 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { // ignoring sidebar selection so account-scoped searches can find a // model before the user chooses a specific instance rail item. if (props.lockedProvider !== null) { - return rankedMatches - .filter((rankedModel) => matchesLockedProvider(rankedModel.model)) + const lockedProviderMatches: Array<(typeof rankedMatches)[number]> = []; + for (const rankedModel of rankedMatches) { + if (matchesLockedProvider(rankedModel.model)) { + lockedProviderMatches.push(rankedModel); + } + } + return lockedProviderMatches .toSorted((a, b) => { const scoreDelta = a.score - b.score; if (scoreDelta !== 0) { diff --git a/apps/web/src/components/chat/TraitsPicker.tsx b/apps/web/src/components/chat/TraitsPicker.tsx index 76ff055a594..5a32a780289 100644 --- a/apps/web/src/components/chat/TraitsPicker.tsx +++ b/apps/web/src/components/chat/TraitsPicker.tsx @@ -381,22 +381,23 @@ export const TraitsPicker = memo(function TraitsPicker({ return null; } - const triggerLabel = - descriptors - .map((descriptor) => { - if (ultrathinkPromptControlled && descriptor.id === primarySelectDescriptor?.id) { - return "Ultrathink"; - } - if (descriptor.type === "boolean") { - if (descriptor.id === "fastMode") { - return descriptor.currentValue === true ? "Fast" : "Normal"; - } - return `${descriptor.label} ${descriptor.currentValue === true ? "On" : "Off"}`; - } - return getProviderOptionCurrentLabel(descriptor); - }) - .filter((label): label is string => typeof label === "string" && label.length > 0) - .join(" · ") || ""; + const triggerLabels: Array = []; + for (const descriptor of descriptors) { + const label = + ultrathinkPromptControlled && descriptor.id === primarySelectDescriptor?.id + ? "Ultrathink" + : descriptor.type === "boolean" + ? descriptor.id === "fastMode" + ? descriptor.currentValue === true + ? "Fast" + : "Normal" + : `${descriptor.label} ${descriptor.currentValue === true ? "On" : "Off"}` + : getProviderOptionCurrentLabel(descriptor); + if (typeof label === "string" && label.length > 0) { + triggerLabels.push(label); + } + } + const triggerLabel = triggerLabels.join(" · "); const isCodexStyle = provider === "codex"; diff --git a/apps/web/src/components/chat/modelPickerSearch.ts b/apps/web/src/components/chat/modelPickerSearch.ts index d3a954ee4fa..ff265e4a65a 100644 --- a/apps/web/src/components/chat/modelPickerSearch.ts +++ b/apps/web/src/components/chat/modelPickerSearch.ts @@ -68,9 +68,13 @@ export function scoreModelPickerSearch( let score = 0; for (const token of tokens) { - const tokenScores = fields - .map((field, index) => scoreModelPickerSearchToken(field, token, index * 10)) - .filter((fieldScore): fieldScore is number => fieldScore !== null); + const tokenScores: Array = []; + for (let index = 0; index < fields.length; index += 1) { + const fieldScore = scoreModelPickerSearchToken(fields[index]!, token, index * 10); + if (fieldScore !== null) { + tokenScores.push(fieldScore); + } + } if (tokenScores.length === 0) { return null; diff --git a/apps/web/src/components/chat/userMessageTerminalContexts.ts b/apps/web/src/components/chat/userMessageTerminalContexts.ts index 978210a53f4..fecc8200dd9 100644 --- a/apps/web/src/components/chat/userMessageTerminalContexts.ts +++ b/apps/web/src/components/chat/userMessageTerminalContexts.ts @@ -7,11 +7,14 @@ export function buildInlineTerminalContextText( header: string; }>, ): string { - return contexts - .map((context) => context.header.trim()) - .filter((header) => header.length > 0) - .map(formatInlineTerminalContextLabel) - .join(" "); + const labels: Array = []; + for (const context of contexts) { + const header = context.header.trim(); + if (header.length > 0) { + labels.push(formatInlineTerminalContextLabel(header)); + } + } + return labels.join(" "); } export function formatInlineTerminalContextLabel(header: string): string { diff --git a/apps/web/src/components/settings/ConnectionsSettings.tsx b/apps/web/src/components/settings/ConnectionsSettings.tsx index 17dc177a981..48c789a2fb6 100644 --- a/apps/web/src/components/settings/ConnectionsSettings.tsx +++ b/apps/web/src/components/settings/ConnectionsSettings.tsx @@ -534,21 +534,27 @@ const PairingLinkListRow = memo(function PairingLinkListRow({ const endpoint = selectPairingEndpoint(endpoints, defaultEndpointKey); return endpoint ? resolveAdvertisedEndpointPairingUrl(endpoint, pairingLink.credential) : null; }, [defaultEndpointKey, endpoints, pairingLink.credential]); - const endpointCopyOptions = useMemo( - () => - endpoints - .filter((endpoint) => endpoint.status !== "unavailable") - .map((endpoint) => { - const url = resolveAdvertisedEndpointPairingUrl(endpoint, pairingLink.credential); - return { - key: endpointDefaultPreferenceKey(endpoint), - label: endpoint.label, - url, - detail: isHostedAppPairingUrl(url) ? "Hosted app link" : "Backend pairing URL", - }; - }), - [endpoints, pairingLink.credential], - ); + const endpointCopyOptions = useMemo(() => { + const options: Array<{ + readonly key: string; + readonly label: string; + readonly url: string; + readonly detail: string; + }> = []; + for (const endpoint of endpoints) { + if (endpoint.status === "unavailable") { + continue; + } + const url = resolveAdvertisedEndpointPairingUrl(endpoint, pairingLink.credential); + options.push({ + key: endpointDefaultPreferenceKey(endpoint), + label: endpoint.label, + url, + detail: isHostedAppPairingUrl(url) ? "Hosted app link" : "Backend pairing URL", + }); + } + return options; + }, [endpoints, pairingLink.credential]); const shareablePairingUrl = endpointPairingUrl ?? (endpointUrl != null && endpointUrl !== "" diff --git a/apps/web/src/components/settings/KeybindingsSettings.logic.ts b/apps/web/src/components/settings/KeybindingsSettings.logic.ts index bb6b92048ac..da54e86e42e 100644 --- a/apps/web/src/components/settings/KeybindingsSettings.logic.ts +++ b/apps/web/src/components/settings/KeybindingsSettings.logic.ts @@ -140,14 +140,16 @@ export function keybindingConflictLabels( input: { readonly rowId: string; readonly key: string; readonly when: string }, ): ReadonlyArray { if (input.key.trim().length === 0) return []; - const conflicts = rows - .filter( - (candidate) => - candidate.id !== input.rowId && - candidate.key === input.key && - conflictsWithWhen(candidate.when, input.when), - ) - .map((candidate) => commandLabel(candidate.command)); + const conflicts: Array = []; + for (const candidate of rows) { + if ( + candidate.id !== input.rowId && + candidate.key === input.key && + conflictsWithWhen(candidate.when, input.when) + ) { + conflicts.push(commandLabel(candidate.command)); + } + } return [...new Set(conflicts)].toSorted(); } @@ -274,12 +276,13 @@ export function commandLabel(command: KeybindingCommand): string { } function titleCaseCommandSegment(segment: string): string { - return segment - .replace(/([a-z0-9])([A-Z])/g, "$1 $2") - .split(/[-_\s]+/) - .filter(Boolean) - .map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)) - .join(" "); + const words: Array = []; + for (const part of segment.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[-_\s]+/)) { + if (part.length > 0) { + words.push(part.slice(0, 1).toUpperCase() + part.slice(1)); + } + } + return words.join(" "); } export function normalizeShortcutKeyToken(key: string): string | null { diff --git a/apps/web/src/components/settings/ProviderInstanceCard.tsx b/apps/web/src/components/settings/ProviderInstanceCard.tsx index 430ec3637e0..ba3feddb462 100644 --- a/apps/web/src/components/settings/ProviderInstanceCard.tsx +++ b/apps/web/src/components/settings/ProviderInstanceCard.tsx @@ -10,6 +10,8 @@ import { Trash2Icon, XIcon, } from "lucide-react"; +import * as Arr from "effect/Array"; +import * as Result from "effect/Result"; import { useEffect, useState, type ReactNode } from "react"; import { isProviderDriverKind, @@ -118,9 +120,9 @@ export function deriveProviderModelsForDisplay(input: { readonly customModels: ReadonlyArray; }): ReadonlyArray { const liveCustomModelsBySlug = new Map( - (input.liveModels ?? []) - .filter((model) => model.isCustom) - .map((model) => [model.slug, model] as const), + Arr.filterMap(input.liveModels ?? [], (model) => + model.isCustom ? Result.succeed([model.slug, model] as const) : Result.failVoid, + ), ); const serverModels = input.liveModels?.filter((model) => !model.isCustom) ?? []; const customModels = input.customModels.map( diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx index e7f21da4809..76d5d34c355 100644 --- a/apps/web/src/components/settings/SettingsPanels.tsx +++ b/apps/web/src/components/settings/SettingsPanels.tsx @@ -14,8 +14,10 @@ import { import { scopeThreadRef } from "@t3tools/client-runtime"; import { DEFAULT_UNIFIED_SETTINGS } from "@t3tools/contracts/settings"; import { createModelSelection } from "@t3tools/shared/model"; +import * as Arr from "effect/Array"; import * as Duration from "effect/Duration"; import * as Equal from "effect/Equal"; +import * as Result from "effect/Result"; import { APP_VERSION, HOSTED_APP_CHANNEL, HOSTED_APP_CHANNEL_LABEL } from "../../branding"; import { canCheckForUpdate, @@ -1143,7 +1145,12 @@ export function ProviderSettingsPanel() { nextFavoriteModels: ReadonlyArray, ) => { const favoriteModels = [ - ...new Set(nextFavoriteModels.map((slug) => slug.trim()).filter((slug) => slug.length > 0)), + ...new Set( + Arr.filterMap(nextFavoriteModels, (slug) => { + const trimmedSlug = slug.trim(); + return trimmedSlug.length > 0 ? Result.succeed(trimmedSlug) : Result.failVoid; + }), + ), ]; updateSettings({ favorites: [ @@ -1249,9 +1256,9 @@ export function ProviderSettingsPanel() { hiddenModels: [], modelOrder: [], }; - const favoriteModels = (settings.favorites ?? []) - .filter((favorite) => favorite.provider === row.instanceId) - .map((favorite) => favorite.model); + const favoriteModels = Arr.filterMap(settings.favorites ?? [], (favorite) => + favorite.provider === row.instanceId ? Result.succeed(favorite.model) : Result.failVoid, + ); const resetLabel = driverOption?.label ?? String(row.driver); const headerAction = row.isDefault && row.isDirty ? ( @@ -1369,21 +1376,30 @@ export function ArchivedThreadsPanel() { })), ); - return [...projectsByEnvironmentAndId.values()] - .map((project) => ({ - project, - threads: threads - .filter( - (thread) => - thread.projectId === project.id && thread.environmentId === project.environmentId, - ) - .toSorted((left, right) => { + const archivedProjects = Array.from(projectsByEnvironmentAndId.values()); + const groups: Array<{ + readonly project: (typeof archivedProjects)[number]; + readonly threads: Array<(typeof threads)[number]>; + }> = []; + for (const project of archivedProjects) { + const projectThreads: Array<(typeof threads)[number]> = []; + for (const thread of threads) { + if (thread.projectId === project.id && thread.environmentId === project.environmentId) { + projectThreads.push(thread); + } + } + if (projectThreads.length > 0) { + groups.push({ + project, + threads: projectThreads.toSorted((left, right) => { const leftKey = left.archivedAt ?? left.createdAt; const rightKey = right.archivedAt ?? right.createdAt; return rightKey.localeCompare(leftKey) || right.id.localeCompare(left.id); }), - })) - .filter((group) => group.threads.length > 0); + }); + } + } + return groups; }, [archivedSnapshots]); const handleArchivedThreadContextMenu = useCallback( diff --git a/apps/web/src/composerDraftStore.ts b/apps/web/src/composerDraftStore.ts index 5c90197a406..3de3b5d706d 100644 --- a/apps/web/src/composerDraftStore.ts +++ b/apps/web/src/composerDraftStore.ts @@ -451,11 +451,13 @@ function cloneModelSelection(selection: ModelSelection): DeepMutable>, ): DeepMutable> { - return Object.fromEntries( - Object.entries(selections) - .filter((entry): entry is [string, ModelSelection] => entry[1] !== undefined) - .map(([provider, selection]) => [provider, cloneModelSelection(selection)]), - ) as DeepMutable>; + const entries: Array<[string, DeepMutable]> = []; + for (const [provider, selection] of Object.entries(selections)) { + if (selection !== undefined) { + entries.push([provider, cloneModelSelection(selection)]); + } + } + return Object.fromEntries(entries) as DeepMutable>; } const EMPTY_PERSISTED_DRAFT_STORE_STATE = Object.freeze({ @@ -1809,9 +1811,12 @@ function verifyPersistedAttachments( const persistedAttachments = attachments.filter( (attachment) => imageIdSet.has(attachment.id) && persistedIdSet.has(attachment.id), ); - const nonPersistedImageIds = current.images - .map((image) => image.id) - .filter((imageId) => !persistedIdSet.has(imageId)); + const nonPersistedImageIds: string[] = []; + for (const image of current.images) { + if (!persistedIdSet.has(image.id)) { + nonPersistedImageIds.push(image.id); + } + } const nextDraft: ComposerThreadDraftState = { ...current, persistedAttachments, diff --git a/apps/web/src/environments/runtime/service.ts b/apps/web/src/environments/runtime/service.ts index 21a4561beb4..e4df6b9f3cd 100644 --- a/apps/web/src/environments/runtime/service.ts +++ b/apps/web/src/environments/runtime/service.ts @@ -1459,10 +1459,12 @@ async function syncSavedEnvironmentConnections( records: ReadonlyArray, ): Promise { const expectedEnvironmentIds = new Set(records.map((record) => record.environmentId)); - const staleEnvironmentIds = [...environmentConnections.values()] - .filter((connection) => connection.kind === "saved") - .map((connection) => connection.environmentId) - .filter((environmentId) => !expectedEnvironmentIds.has(environmentId)); + const staleEnvironmentIds: EnvironmentId[] = []; + for (const connection of environmentConnections.values()) { + if (connection.kind !== "saved") continue; + if (expectedEnvironmentIds.has(connection.environmentId)) continue; + staleEnvironmentIds.push(connection.environmentId); + } await Promise.all( staleEnvironmentIds.map((environmentId) => disconnectSavedEnvironment(environmentId)), diff --git a/apps/web/src/lib/terminalContext.ts b/apps/web/src/lib/terminalContext.ts index 562fe742a15..ba63fd5a02b 100644 --- a/apps/web/src/lib/terminalContext.ts +++ b/apps/web/src/lib/terminalContext.ts @@ -127,19 +127,18 @@ export function buildTerminalContextPreviewTitle( if (contexts.length === 0) { return null; } - const previews = contexts - .map((context) => { - const normalized = normalizeTerminalContextSelection(context); - if (!normalized) { - return null; - } - const preview = previewTerminalContextText(normalized.text); - return preview.length > 0 + const previewParts: string[] = []; + for (const context of contexts) { + const normalized = normalizeTerminalContextSelection(context); + if (!normalized) continue; + const preview = previewTerminalContextText(normalized.text); + previewParts.push( + preview.length > 0 ? `${formatTerminalContextLabel(normalized)}\n${preview}` - : formatTerminalContextLabel(normalized); - }) - .filter((value): value is string => value !== null) - .join("\n\n"); + : formatTerminalContextLabel(normalized), + ); + } + const previews = previewParts.join("\n\n"); return previews.length > 0 ? previews : null; } @@ -152,9 +151,13 @@ function buildTerminalContextBodyLines(selection: TerminalContextSelection): str export function buildTerminalContextBlock( contexts: ReadonlyArray, ): string { - const normalizedContexts = contexts - .map((context) => normalizeTerminalContextSelection(context)) - .filter((context): context is TerminalContextSelection => context !== null); + const normalizedContexts: TerminalContextSelection[] = []; + for (const context of contexts) { + const normalized = normalizeTerminalContextSelection(context); + if (normalized !== null) { + normalizedContexts.push(normalized); + } + } if (normalizedContexts.length === 0) { return ""; } diff --git a/apps/web/src/modelSelection.ts b/apps/web/src/modelSelection.ts index 8ba0c8fa5d9..0fcf680b732 100644 --- a/apps/web/src/modelSelection.ts +++ b/apps/web/src/modelSelection.ts @@ -14,6 +14,8 @@ import { } from "@t3tools/shared/model"; import { getComposerProviderState } from "./components/chat/composerProviderState"; import { UnifiedSettings } from "@t3tools/contracts/settings"; +import * as Arr from "effect/Array"; +import * as Result from "effect/Result"; import { getDefaultServerModel, getProviderModels, @@ -150,9 +152,9 @@ export function getAppModelOptions( const options: AppModelOption[] = getProviderModels(providers, provider).map(toAppModelOption); const seen = new Set(options.map((option) => option.slug)); const builtInModelSlugs = new Set( - getProviderModels(providers, provider) - .filter((model) => !model.isCustom) - .map((model) => model.slug), + Arr.filterMap(getProviderModels(providers, provider), (model) => + model.isCustom ? Result.failVoid : Result.succeed(model.slug), + ), ); // Read from the default instance's config first (that's where edits @@ -198,7 +200,9 @@ export function getAppModelOptionsForInstance( const options: AppModelOption[] = entry.models.map(toAppModelOption); const seen = new Set(options.map((option) => option.slug)); const builtInModelSlugs = new Set( - entry.models.filter((model) => !model.isCustom).map((model) => model.slug), + Arr.filterMap(entry.models, (model) => + model.isCustom ? Result.failVoid : Result.succeed(model.slug), + ), ); const customModels = readInstanceCustomModels(settings, entry.instanceId, entry.driverKind); diff --git a/apps/web/src/pendingUserInput.ts b/apps/web/src/pendingUserInput.ts index cdfc73cb815..d3a7a129378 100644 --- a/apps/web/src/pendingUserInput.ts +++ b/apps/web/src/pendingUserInput.ts @@ -33,10 +33,14 @@ function normalizeSelectedOptionLabels(value: string[] | undefined): string[] { return []; } - const normalized = value - .filter((entry): entry is string => typeof entry === "string") - .map((entry) => entry.trim()) - .filter((entry) => entry.length > 0); + const normalized: string[] = []; + for (const entry of value) { + if (typeof entry !== "string") continue; + const trimmed = entry.trim(); + if (trimmed.length > 0) { + normalized.push(trimmed); + } + } return Array.from(new Set(normalized)); } diff --git a/apps/web/src/providerInstances.ts b/apps/web/src/providerInstances.ts index 6ff0bd1ab98..3ec67c9e25e 100644 --- a/apps/web/src/providerInstances.ts +++ b/apps/web/src/providerInstances.ts @@ -66,13 +66,15 @@ export interface ProviderInstanceEntry { * will take precedence over this fallback. */ function humanizeInstanceId(instanceId: ProviderInstanceId): string { - return instanceId + const words: string[] = []; + for (const token of instanceId .replace(/[_-]+/g, " ") .replace(/([a-z])([A-Z])/g, "$1 $2") - .split(" ") - .filter((token) => token.length > 0) - .map((token) => token.charAt(0).toUpperCase() + token.slice(1)) - .join(" "); + .split(" ")) { + if (token.length === 0) continue; + words.push(token.charAt(0).toUpperCase() + token.slice(1)); + } + return words.join(" "); } function driverKindLabel(driverKind: ProviderDriverKind): string { diff --git a/apps/web/src/providerSkillPresentation.ts b/apps/web/src/providerSkillPresentation.ts index fe077cbb191..50adc4a47d6 100644 --- a/apps/web/src/providerSkillPresentation.ts +++ b/apps/web/src/providerSkillPresentation.ts @@ -1,11 +1,12 @@ import type { ServerProviderSkill } from "@t3tools/contracts"; function titleCaseWords(value: string): string { - return value - .split(/[\s:_-]+/) - .filter((segment) => segment.length > 0) - .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)) - .join(" "); + const words: string[] = []; + for (const segment of value.split(/[\s:_-]+/)) { + if (segment.length === 0) continue; + words.push(segment.charAt(0).toUpperCase() + segment.slice(1)); + } + return words.join(" "); } function normalizePathSeparators(pathValue: string): string { diff --git a/apps/web/src/session-logic.ts b/apps/web/src/session-logic.ts index a7767672fa1..5feffe15b09 100644 --- a/apps/web/src/session-logic.ts +++ b/apps/web/src/session-logic.ts @@ -385,28 +385,25 @@ export function deriveActivePlanState( if (!Array.isArray(rawPlan)) { return null; } - const steps = rawPlan - .map((entry) => { - if (!entry || typeof entry !== "object") return null; - const record = entry as Record; - if (typeof record.step !== "string") { - return null; - } - const status = - record.status === "completed" || record.status === "inProgress" ? record.status : "pending"; - return { - step: record.step, - status, - }; - }) - .filter( - ( - step, - ): step is { - step: string; - status: "pending" | "inProgress" | "completed"; - } => step !== null, - ); + const steps: Array<{ + step: string; + status: "pending" | "inProgress" | "completed"; + }> = []; + for (const entry of rawPlan) { + if (!entry || typeof entry !== "object") { + continue; + } + const record = entry as Record; + if (typeof record.step !== "string") { + continue; + } + const status = + record.status === "completed" || record.status === "inProgress" ? record.status : "pending"; + steps.push({ + step: record.step, + status, + }); + } if (steps.length === 0) { return null; } @@ -485,14 +482,16 @@ export function deriveWorkLogEntries( latestTurnId: TurnId | undefined, ): WorkLogEntry[] { const ordered = [...activities].toSorted(compareActivitiesByOrder); - const entries = ordered - .filter((activity) => (latestTurnId ? activity.turnId === latestTurnId : true)) - .filter((activity) => activity.kind !== "tool.started") - .filter((activity) => activity.kind !== "task.started") - .filter((activity) => activity.kind !== "context-window.updated") - .filter((activity) => activity.summary !== "Checkpoint captured") - .filter((activity) => !isPlanBoundaryToolActivity(activity)) - .map(toDerivedWorkLogEntry); + const entries: DerivedWorkLogEntry[] = []; + for (const activity of ordered) { + if (latestTurnId && activity.turnId !== latestTurnId) continue; + if (activity.kind === "tool.started") continue; + if (activity.kind === "task.started") continue; + if (activity.kind === "context-window.updated") continue; + if (activity.summary === "Checkpoint captured") continue; + if (isPlanBoundaryToolActivity(activity)) continue; + entries.push(toDerivedWorkLogEntry(activity)); + } return collapseDerivedWorkLogEntries(entries).map( ({ activityKind: _activityKind, collapseKey: _collapseKey, ...entry }) => entry, ); @@ -834,9 +833,13 @@ function formatCommandValue(value: unknown): string | null { if (!Array.isArray(value)) { return null; } - const parts = value - .map((entry) => asTrimmedString(entry)) - .filter((entry): entry is string => entry !== null); + const parts: Array = []; + for (const entry of value) { + const part = asTrimmedString(entry); + if (part !== null) { + parts.push(part); + } + } if (parts.length === 0) { return null; } @@ -920,10 +923,13 @@ function normalizePreviewForComparison(value: string | null | undefined): string } function summarizeToolTextOutput(value: string): string | null { - const lines = value - .split(/\r?\n/u) - .map((line) => normalizeInlinePreview(line)) - .filter((line) => line.length > 0); + const lines: Array = []; + for (const rawLine of value.split(/\r?\n/u)) { + const line = normalizeInlinePreview(rawLine); + if (line.length > 0) { + lines.push(line); + } + } const firstLine = lines.find((line) => line !== "```"); if (firstLine) { return truncateInlinePreview(firstLine); diff --git a/packages/shared/src/git.ts b/packages/shared/src/git.ts index a813cfe4637..ae50b148835 100644 --- a/packages/shared/src/git.ts +++ b/packages/shared/src/git.ts @@ -6,6 +6,8 @@ import type { VcsStatusResult, VcsStatusStreamEvent, } from "@t3tools/contracts"; +import * as Arr from "effect/Array"; +import * as Result from "effect/Result"; import { detectSourceControlProviderFromRemoteUrl } from "./sourceControl.ts"; export const WORKTREE_BRANCH_PREFIX = "t3code"; @@ -172,7 +174,9 @@ export function dedupeRemoteBranchesWithLocalMatches( refs: ReadonlyArray, ): ReadonlyArray { const localBranchNames = new Set( - refs.filter((refName) => !refName.isRemote).map((refName) => refName.name), + Arr.filterMap(refs, (refName) => + refName.isRemote ? Result.failVoid : Result.succeed(refName.name), + ), ); return refs.filter((refName) => { diff --git a/packages/shared/src/observability.ts b/packages/shared/src/observability.ts index 4a49545e393..1fd75cf4ac6 100644 --- a/packages/shared/src/observability.ts +++ b/packages/shared/src/observability.ts @@ -184,11 +184,13 @@ function normalizeJsonValue(value: unknown, seen: WeakSet = new WeakSet( export function compactTraceAttributes( attributes: Readonly>, ): TraceAttributes { - return Object.fromEntries( - Object.entries(attributes) - .filter(([, value]) => value !== undefined) - .map(([key, value]) => [key, normalizeJsonValue(value)]), - ); + const entries: Array<[string, unknown]> = []; + for (const [key, value] of Object.entries(attributes)) { + if (value !== undefined) { + entries.push([key, normalizeJsonValue(value)]); + } + } + return Object.fromEntries(entries); } function formatTraceExit(exit: Exit.Exit): EffectTraceRecord["exit"] { diff --git a/packages/shared/src/semver.ts b/packages/shared/src/semver.ts index de213764504..1a73e33042f 100644 --- a/packages/shared/src/semver.ts +++ b/packages/shared/src/semver.ts @@ -9,10 +9,13 @@ const SEMVER_NUMBER_SEGMENT = /^\d+$/; export function normalizeSemverVersion(version: string): string { const [main, prerelease] = version.trim().split("-", 2); - const segments = (main ?? "") - .split(".") - .map((segment) => segment.trim()) - .filter((segment) => segment.length > 0); + const segments: string[] = []; + for (const segment of (main ?? "").split(".")) { + const trimmed = segment.trim(); + if (trimmed.length > 0) { + segments.push(trimmed); + } + } if (segments.length === 2) { segments.push("0"); @@ -52,14 +55,24 @@ export function parseSemver(value: string): ParsedSemver | null { major, minor, patch, - prerelease: - prerelease - ?.split(".") - .map((segment) => segment.trim()) - .filter((segment) => segment.length > 0) ?? [], + prerelease: parsePrereleaseSegments(prerelease), }; } +function parsePrereleaseSegments(prerelease: string | undefined): ReadonlyArray { + if (prerelease === undefined) { + return []; + } + const segments: string[] = []; + for (const segment of prerelease.split(".")) { + const trimmed = segment.trim(); + if (trimmed.length > 0) { + segments.push(trimmed); + } + } + return segments; +} + function comparePrereleaseIdentifier(left: string, right: string): number { const leftNumeric = SEMVER_NUMBER_SEGMENT.test(left); const rightNumeric = SEMVER_NUMBER_SEGMENT.test(right); diff --git a/packages/shared/src/shell.ts b/packages/shared/src/shell.ts index 0572aaf989a..c88ccc10d2a 100644 --- a/packages/shared/src/shell.ts +++ b/packages/shared/src/shell.ts @@ -321,11 +321,12 @@ function resolveWindowsPathExtensions(env: NodeJS.ProcessEnv): ReadonlyArray entry.trim()) - .filter((entry) => entry.length > 0) - .map((entry) => (entry.startsWith(".") ? entry.toUpperCase() : `.${entry.toUpperCase()}`)); + const parsed: string[] = []; + for (const entry of rawValue.split(";")) { + const trimmed = entry.trim(); + if (trimmed.length === 0) continue; + parsed.push(trimmed.startsWith(".") ? trimmed.toUpperCase() : `.${trimmed.toUpperCase()}`); + } return parsed.length > 0 ? Array.from(new Set(parsed)) : fallback; } @@ -397,10 +398,13 @@ export function resolveCommandPath( const pathValue = resolvePathEnvironmentVariable(env); if (pathValue.length === 0) return null; - const pathEntries = pathValue - .split(pathDelimiterForPlatform(platform)) - .map((entry) => stripWrappingQuotes(entry.trim())) - .filter((entry) => entry.length > 0); + const pathEntries: string[] = []; + for (const entry of pathValue.split(pathDelimiterForPlatform(platform))) { + const pathEntry = stripWrappingQuotes(entry.trim()); + if (pathEntry.length > 0) { + pathEntries.push(pathEntry); + } + } for (const pathEntry of pathEntries) { for (const candidate of commandCandidates) { diff --git a/packages/shared/src/toolActivity.ts b/packages/shared/src/toolActivity.ts index 5e2f18044f5..2fd04e76696 100644 --- a/packages/shared/src/toolActivity.ts +++ b/packages/shared/src/toolActivity.ts @@ -22,9 +22,13 @@ function normalizeCommandValue(value: unknown): string | undefined { if (!Array.isArray(value)) { return undefined; } - const parts = value - .map((entry) => asTrimmedString(entry)) - .filter((entry): entry is string => entry !== undefined); + const parts: string[] = []; + for (const entry of value) { + const part = asTrimmedString(entry); + if (part !== undefined) { + parts.push(part); + } + } return parts.length > 0 ? parts.join(" ") : undefined; } diff --git a/packages/ssh/src/config.ts b/packages/ssh/src/config.ts index a8d17946129..3de430c093b 100644 --- a/packages/ssh/src/config.ts +++ b/packages/ssh/src/config.ts @@ -15,12 +15,17 @@ function stripInlineComment(line: string): string { } function splitDirectiveArgs(value: string): ReadonlyArray { - return value + const args: Array = []; + for (const rawEntry of value .replace(/=(?!=)/gu, " ") .trim() - .split(/\s+/u) - .map((entry) => entry.trim()) - .filter((entry) => entry.length > 0); + .split(/\s+/u)) { + const entry = rawEntry.trim(); + if (entry.length > 0) { + args.push(entry); + } + } + return args; } function expandHomePath(input: string, homeDir: string): string { diff --git a/packages/tailscale/src/tailscale.ts b/packages/tailscale/src/tailscale.ts index c40cd54fc44..c8d9cab462d 100644 --- a/packages/tailscale/src/tailscale.ts +++ b/packages/tailscale/src/tailscale.ts @@ -112,11 +112,14 @@ export const parseTailscaleStatus = ( Effect.mapError((cause) => new TailscaleStatusParseError({ cause })), Effect.map((parsed) => { const rawIps = parsed.Self?.TailscaleIPs; - const tailnetIpv4Addresses = Array.isArray(rawIps) - ? rawIps - .filter((address): address is string => typeof address === "string") - .filter(isTailscaleIpv4Address) - : []; + const tailnetIpv4Addresses: Array = []; + if (Array.isArray(rawIps)) { + for (const address of rawIps) { + if (typeof address === "string" && isTailscaleIpv4Address(address)) { + tailnetIpv4Addresses.push(address); + } + } + } return { magicDnsName: normalizeMagicDnsName(parsed),