authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-05-28 16:18:58-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-05-28 16:18:58-07:00
log31268945f88a712fad9309d5ea9290cb8496af83
treef10d21c963f7de25996020995052981f7b0f1aee
parent6b3050ee740f510ad06c12a91ecd7e338792dce0
signature Signed by PGP key B5690EEEBB952194

Extract collection performance refactors from mobile stack (#2854)

Co-authored-by: codex <codex@users.noreply.github.com>

47 files changed, 646 insertions(+), 455 deletions(-)

apps/server/src/auth/Layers/AuthControlPlane.ts+16-10
......@@ -64,11 +64,16 @@ export const makeAuthControlPlane = Effect.gen(function* () {
6464
6565 const listPairingLinks: AuthControlPlaneShape["listPairingLinks"] = (input) =>
6666 bootstrapCredentials.listActive().pipe(
67 Effect.map((pairingLinks) =>
68 pairingLinks
69 .filter((pairingLink) => (input?.role ? pairingLink.role === input.role : true))
70 .filter((pairingLink) => !input?.excludeSubjects?.includes(pairingLink.subject))
71 .map((pairingLink) =>
67 Effect.map((pairingLinks) => {
68 const activeLinks: Array<AuthPairingLink> = [];
69 for (const pairingLink of pairingLinks) {
70 if (input?.role && pairingLink.role !== input.role) {
71 continue;
72 }
73 if (input?.excludeSubjects?.includes(pairingLink.subject)) {
74 continue;
75 }
76 activeLinks.push(
7277 pairingLink.label
7378 ? ({
7479 id: pairingLink.id,
......@@ -87,11 +92,12 @@ export const makeAuthControlPlane = Effect.gen(function* () {
8792 createdAt: pairingLink.createdAt,
8893 expiresAt: pairingLink.expiresAt,
8994 } satisfies AuthPairingLink),
90 )
91 .toSorted(
92 (left, right) => right.createdAt.epochMilliseconds - left.createdAt.epochMilliseconds,
93 ),
94 ),
95 );
96 }
97 return activeLinks.toSorted(
98 (left, right) => right.createdAt.epochMilliseconds - left.createdAt.epochMilliseconds,
99 );
100 }),
95101 Effect.mapError(toAuthControlPlaneError("Failed to list pairing links.")),
96102 );
97103
apps/server/src/git/remoteRefs.ts+8-4
......@@ -1,8 +1,12 @@
11export function parseRemoteNamesInGitOrder(stdout: string): ReadonlyArray<string> {
2 return stdout
3 .split("\n")
4 .map((line) => line.trim())
5 .filter((line) => line.length > 0);
2 const remoteNames: Array<string> = [];
3 for (const line of stdout.split("\n")) {
4 const remoteName = line.trim();
5 if (remoteName.length > 0) {
6 remoteNames.push(remoteName);
7 }
8 }
9 return remoteNames;
610}
711
812export function parseRemoteNames(stdout: string): ReadonlyArray<string> {
apps/server/src/imageMime.ts+7-4
......@@ -35,10 +35,13 @@ export function parseBase64DataUrl(
3535 const match = /^data:([^,]+),([a-z0-9+/=\r\n ]+)$/i.exec(dataUrl.trim());
3636 if (!match) return null;
3737
38 const headerParts = (match[1] ?? "")
39 .split(";")
40 .map((part) => part.trim())
41 .filter((part) => part.length > 0);
38 const headerParts: Array<string> = [];
39 for (const part of (match[1] ?? "").split(";")) {
40 const trimmed = part.trim();
41 if (trimmed.length > 0) {
42 headerParts.push(trimmed);
43 }
44 }
4245 if (headerParts.length < 2) {
4346 return null;
4447 }
apps/server/src/keybindings.ts+6-3
......@@ -32,6 +32,7 @@ import * as Layer from "effect/Layer";
3232import * as Option from "effect/Option";
3333import * as Predicate from "effect/Predicate";
3434import * as PubSub from "effect/PubSub";
35import * as Result from "effect/Result";
3536import * as Schema from "effect/Schema";
3637import * as SchemaIssue from "effect/SchemaIssue";
3738import * as SchemaTransformation from "effect/SchemaTransformation";
......@@ -538,9 +539,11 @@ const makeKeybindings = Effect.gen(function* () {
538539 return;
539540 }
540541
541 const matchingDefaults = DEFAULT_KEYBINDINGS.filter((defaultRule) =>
542 customConfig.some((entry) => isSameKeybindingRule(entry, defaultRule)),
543 ).map((rule) => rule.command);
542 const matchingDefaults = Array.filterMap(DEFAULT_KEYBINDINGS, (defaultRule) =>
543 customConfig.some((entry) => isSameKeybindingRule(entry, defaultRule))
544 ? Result.succeed(defaultRule.command)
545 : Result.failVoid,
546 );
544547 if (matchingDefaults.length > 0) {
545548 yield* Effect.logWarning("default keybinding rule already defined in user config", {
546549 path: keybindingsConfigPath,
apps/server/src/orchestration/Layers/CheckpointReactor.ts+7-3
......@@ -1,5 +1,6 @@
11import {
22 CommandId,
3 type CheckpointRef,
34 EventId,
45 MessageId,
56 type ProjectId,
......@@ -701,9 +702,12 @@ const make = Effect.gen(function* () {
701702 });
702703 }
703704
704 const staleCheckpointRefs = thread.checkpoints
705 .filter((checkpoint) => checkpoint.checkpointTurnCount > event.payload.turnCount)
706 .map((checkpoint) => checkpoint.checkpointRef);
705 const staleCheckpointRefs: Array<CheckpointRef> = [];
706 for (const checkpoint of thread.checkpoints) {
707 if (checkpoint.checkpointTurnCount > event.payload.turnCount) {
708 staleCheckpointRefs.push(checkpoint.checkpointRef);
709 }
710 }
707711
708712 if (staleCheckpointRefs.length > 0) {
709713 yield* checkpointStore.deleteCheckpointRefs({
apps/server/src/orchestration/Layers/ProjectionPipeline.ts+9-6
......@@ -537,12 +537,15 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
537537 projectionPendingApprovalRepository.listByThreadId({ threadId }),
538538 ]);
539539
540 const latestUserMessageAt =
541 messages
542 .filter((message) => message.role === "user")
543 .map((message) => message.createdAt)
544 .toSorted()
545 .at(-1) ?? null;
540 let latestUserMessageAt: string | null = null;
541 for (const message of messages) {
542 if (
543 message.role === "user" &&
544 (latestUserMessageAt === null || message.createdAt > latestUserMessageAt)
545 ) {
546 latestUserMessageAt = message.createdAt;
547 }
548 }
546549
547550 const pendingApprovalCount = pendingApprovals.filter(
548551 (approval) => approval.status === "pending",
apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts+39-33
......@@ -24,9 +24,11 @@ import {
2424 ProjectId,
2525 ThreadId,
2626} from "@t3tools/contracts";
27import * as Arr from "effect/Array";
2728import * as Effect from "effect/Effect";
2829import * as Layer from "effect/Layer";
2930import * as Option from "effect/Option";
31import * as Result from "effect/Result";
3032import * as Schema from "effect/Schema";
3133import * as Struct from "effect/Struct";
3234import * as SqlClient from "effect/unstable/sql/SqlClient";
......@@ -1488,34 +1490,36 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
14881490
14891491 const snapshot = {
14901492 snapshotSequence: computeSnapshotSequence(stateRows),
1491 projects: projectRows
1492 .filter((row) => row.deletedAt === null)
1493 .map((row) =>
1494 mapProjectShellRow(row, repositoryIdentities.get(row.projectId) ?? null),
1495 ),
1496 threads: threadRows
1497 .filter((row) => row.deletedAt === null)
1498 .map(
1499 (row): OrchestrationThreadShell => ({
1500 id: row.threadId,
1501 projectId: row.projectId,
1502 title: row.title,
1503 modelSelection: row.modelSelection,
1504 runtimeMode: row.runtimeMode,
1505 interactionMode: row.interactionMode,
1506 branch: row.branch,
1507 worktreePath: row.worktreePath,
1508 latestTurn: latestTurnByThread.get(row.threadId) ?? null,
1509 createdAt: row.createdAt,
1510 updatedAt: row.updatedAt,
1511 archivedAt: row.archivedAt,
1512 session: sessionByThread.get(row.threadId) ?? null,
1513 latestUserMessageAt: row.latestUserMessageAt,
1514 hasPendingApprovals: row.pendingApprovalCount > 0,
1515 hasPendingUserInput: row.pendingUserInputCount > 0,
1516 hasActionableProposedPlan: row.hasActionableProposedPlan > 0,
1517 }),
1518 ),
1493 projects: Arr.filterMap(projectRows, (row) =>
1494 row.deletedAt === null
1495 ? Result.succeed(
1496 mapProjectShellRow(row, repositoryIdentities.get(row.projectId) ?? null),
1497 )
1498 : Result.failVoid,
1499 ),
1500 threads: Arr.filterMap(threadRows, (row) =>
1501 row.deletedAt === null
1502 ? Result.succeed({
1503 id: row.threadId,
1504 projectId: row.projectId,
1505 title: row.title,
1506 modelSelection: row.modelSelection,
1507 runtimeMode: row.runtimeMode,
1508 interactionMode: row.interactionMode,
1509 branch: row.branch,
1510 worktreePath: row.worktreePath,
1511 latestTurn: latestTurnByThread.get(row.threadId) ?? null,
1512 createdAt: row.createdAt,
1513 updatedAt: row.updatedAt,
1514 archivedAt: row.archivedAt,
1515 session: sessionByThread.get(row.threadId) ?? null,
1516 latestUserMessageAt: row.latestUserMessageAt,
1517 hasPendingApprovals: row.pendingApprovalCount > 0,
1518 hasPendingUserInput: row.pendingUserInputCount > 0,
1519 hasActionableProposedPlan: row.hasActionableProposedPlan > 0,
1520 } satisfies OrchestrationThreadShell)
1521 : Result.failVoid,
1522 ),
15191523 updatedAt: updatedAt ?? "1970-01-01T00:00:00.000Z",
15201524 };
15211525
......@@ -1621,11 +1625,13 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
16211625
16221626 const snapshot = {
16231627 snapshotSequence: computeSnapshotSequence(stateRows),
1624 projects: projectRows
1625 .filter((row) => row.deletedAt === null && activeProjectIds.has(row.projectId))
1626 .map((row) =>
1627 mapProjectShellRow(row, repositoryIdentities.get(row.projectId) ?? null),
1628 ),
1628 projects: Arr.filterMap(projectRows, (row) =>
1629 row.deletedAt === null && activeProjectIds.has(row.projectId)
1630 ? Result.succeed(
1631 mapProjectShellRow(row, repositoryIdentities.get(row.projectId) ?? null),
1632 )
1633 : Result.failVoid,
1634 ),
16291635 threads: threadRows.map(
16301636 (row): OrchestrationThreadShell => ({
16311637 id: row.threadId,
apps/server/src/provider/Layers/ClaudeAdapter.ts+7-3
......@@ -245,9 +245,13 @@ function toProcessError(
245245function normalizeClaudeStreamMessages(
246246 cause: Cause.Cause<{ readonly message: string }>,
247247): ReadonlyArray<string> {
248 const errors = Cause.prettyErrors(cause)
249 .map((error) => error.message.trim())
250 .filter((message) => message.length > 0);
248 const errors: Array<string> = [];
249 for (const error of Cause.prettyErrors(cause)) {
250 const message = error.message.trim();
251 if (message.length > 0) {
252 errors.push(message);
253 }
254 }
251255 if (errors.length > 0) {
252256 return errors;
253257 }
apps/server/src/provider/Layers/ClaudeProvider.ts+7-5
......@@ -317,11 +317,13 @@ export function resolveClaudeApiModelId(modelSelection: ModelSelection): string
317317}
318318
319319function toTitleCaseWords(value: string): string {
320 return value
321 .split(/[\s_-]+/g)
322 .filter(Boolean)
323 .map((part) => part[0]!.toUpperCase() + part.slice(1).toLowerCase())
324 .join(" ");
320 const parts: Array<string> = [];
321 for (const part of value.split(/[\s_-]+/g)) {
322 if (part.length > 0) {
323 parts.push(part[0]!.toUpperCase() + part.slice(1).toLowerCase());
324 }
325 }
326 return parts.join(" ");
325327}
326328
327329function claudeSubscriptionLabel(subscriptionType: string | undefined): string | undefined {
apps/server/src/provider/Layers/CodexProvider.ts+15-10
......@@ -319,16 +319,21 @@ const probeCodexAppServerProvider = Effect.fn("probeCodexAppServerProvider")(fun
319319 } satisfies CodexAppServerProviderSnapshot;
320320});
321321
322const emptyCodexModelsFromSettings = (codexSettings: CodexSettings): ServerProvider["models"] =>
323 codexSettings.customModels
324 .map((model) => model.trim())
325 .filter((model, index, models) => model.length > 0 && models.indexOf(model) === index)
326 .map((model) => ({
327 slug: model,
328 name: model,
329 isCustom: true,
330 capabilities: null,
331 }));
322const emptyCodexModelsFromSettings = (codexSettings: CodexSettings): ServerProvider["models"] => {
323 const models = new Set<string>();
324 for (const model of codexSettings.customModels) {
325 const trimmed = model.trim();
326 if (trimmed.length > 0) {
327 models.add(trimmed);
328 }
329 }
330 return Array.from(models, (model) => ({
331 slug: model,
332 name: model,
333 isCustom: true,
334 capabilities: null,
335 }));
336};
332337
333338const makePendingCodexProvider = (
334339 codexSettings: CodexSettings,
apps/server/src/provider/Layers/CursorProvider.ts+20-13
......@@ -596,11 +596,12 @@ export const discoverCursorModelCapabilitiesViaAcp = (
596596 );
597597 }
598598
599 const targetModelSlugs = new Set(
600 existingModels
601 .filter((model) => !model.isCustom && !hasCursorModelCapabilities(model))
602 .map((model) => model.slug),
603 );
599 const targetModelSlugs = new Set<string>();
600 for (const model of existingModels) {
601 if (!model.isCustom && !hasCursorModelCapabilities(model)) {
602 targetModelSlugs.add(model.slug);
603 }
604 }
604605 if (targetModelSlugs.size === 0) {
605606 return buildCursorDiscoveredModels(
606607 modelChoices.map((modelChoice) => ({
......@@ -721,9 +722,13 @@ export interface CursorAboutResult {
721722}
722723
723724function joinProviderMessages(...messages: ReadonlyArray<string | undefined>): string | undefined {
724 const parts = messages
725 .map((message) => message?.trim())
726 .filter((message): message is string => Boolean(message));
725 const parts: Array<string> = [];
726 for (const message of messages) {
727 const trimmed = message?.trim();
728 if (trimmed) {
729 parts.push(trimmed);
730 }
731 }
727732 return parts.length > 0 ? parts.join(" ") : undefined;
728733}
729734
......@@ -790,11 +795,13 @@ export function parseCursorCliConfigChannel(raw: string): string | undefined {
790795}
791796
792797function toTitleCaseWords(value: string): string {
793 return value
794 .split(/[\s_-]+/g)
795 .filter((part) => part.length > 0)
796 .map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
797 .join(" ");
798 const parts: Array<string> = [];
799 for (const part of value.split(/[\s_-]+/g)) {
800 if (part.length > 0) {
801 parts.push(part.charAt(0).toUpperCase() + part.slice(1).toLowerCase());
802 }
803 }
804 return parts.join(" ");
798805}
799806
800807function cursorSubscriptionLabel(subscriptionType: string | undefined): string | undefined {
apps/server/src/provider/Layers/OpenCodeAdapter.ts+9-6
......@@ -1366,12 +1366,15 @@ export function makeOpenCodeAdapter(
13661366 }),
13671367 ).pipe(Effect.mapError(toRequestError));
13681368
1369 const turns = (messages.data ?? [])
1370 .filter((entry) => entry.info.role === "assistant")
1371 .map((entry) => ({
1372 id: TurnId.make(entry.info.id),
1373 items: [entry.info, ...entry.parts],
1374 }));
1369 const turns: Array<OpenCodeTurnSnapshot> = [];
1370 for (const entry of messages.data ?? []) {
1371 if (entry.info.role === "assistant") {
1372 turns.push({
1373 id: TurnId.make(entry.info.id),
1374 items: [entry.info, ...entry.parts],
1375 });
1376 }
1377 }
13751378
13761379 return {
13771380 threadId,
apps/server/src/provider/Layers/OpenCodeProvider.ts+7-5
......@@ -137,11 +137,13 @@ function formatOpenCodeProbeError(input: {
137137}
138138
139139function titleCaseSlug(value: string): string {
140 return value
141 .split(/[-_/]+/)
142 .filter((segment) => segment.length > 0)
143 .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
144 .join(" ");
140 const segments: Array<string> = [];
141 for (const segment of value.split(/[-_/]+/)) {
142 if (segment.length > 0) {
143 segments.push(segment.charAt(0).toUpperCase() + segment.slice(1));
144 }
145 }
146 return segments.join(" ");
145147}
146148
147149function inferDefaultVariant(
apps/server/src/provider/acp/AcpRuntimeModel.ts+36-27
......@@ -126,19 +126,20 @@ export function parseSessionModeState(
126126 if (!currentModeId) {
127127 return undefined;
128128 }
129 const availableModes = modes.availableModes
130 .map((mode) => {
131 const id = mode.id.trim();
132 const name = mode.name.trim();
133 if (!id || !name) {
134 return undefined;
135 }
136 const description = mode.description?.trim() || undefined;
137 return description !== undefined
129 const availableModes: Array<AcpSessionMode> = [];
130 for (const mode of modes.availableModes) {
131 const id = mode.id.trim();
132 const name = mode.name.trim();
133 if (!id || !name) {
134 continue;
135 }
136 const description = mode.description?.trim() || undefined;
137 availableModes.push(
138 description !== undefined
138139 ? ({ id, name, description } satisfies AcpSessionMode)
139 : ({ id, name } satisfies AcpSessionMode);
140 })
141 .filter((mode): mode is AcpSessionMode => mode !== undefined);
140 : ({ id, name } satisfies AcpSessionMode),
141 );
142 }
142143 if (availableModes.length === 0) {
143144 return undefined;
144145 }
......@@ -186,9 +187,15 @@ function normalizeCommandValue(value: unknown): string | undefined {
186187 if (!Array.isArray(value)) {
187188 return undefined;
188189 }
189 const parts = value
190 .map((entry) => (typeof entry === "string" && entry.trim().length > 0 ? entry.trim() : null))
191 .filter((entry): entry is string => entry !== null);
190 const parts: Array<string> = [];
191 for (const entry of value) {
192 if (typeof entry === "string") {
193 const part = entry.trim();
194 if (part.length > 0) {
195 parts.push(part);
196 }
197 }
198 }
192199 return parts.length > 0 ? parts.join(" ") : undefined;
193200}
194201
......@@ -222,18 +229,20 @@ function extractTextContentFromToolCallContent(
222229 content: ReadonlyArray<EffectAcpSchema.ToolCallContent> | null | undefined,
223230): string | undefined {
224231 if (!content) return undefined;
225 const chunks = content
226 .map((entry) => {
227 if (entry.type !== "content") {
228 return undefined;
229 }
230 const nestedContent = entry.content;
231 if (nestedContent.type !== "text") {
232 return undefined;
233 }
234 return nestedContent.text.trim().length > 0 ? nestedContent.text.trim() : undefined;
235 })
236 .filter((entry): entry is string => entry !== undefined);
232 const chunks: Array<string> = [];
233 for (const entry of content) {
234 if (entry.type !== "content") {
235 continue;
236 }
237 const nestedContent = entry.content;
238 if (nestedContent.type !== "text") {
239 continue;
240 }
241 const text = nestedContent.text.trim();
242 if (text.length > 0) {
243 chunks.push(text);
244 }
245 }
237246 return chunks.length > 0 ? chunks.join("\n") : undefined;
238247}
239248
apps/server/src/provider/providerMaintenanceRunner.ts+9-7
......@@ -214,13 +214,15 @@ export const make = Effect.fn("ProviderMaintenanceRunner.make")(function* () {
214214 instanceId: ProviderInstanceId,
215215 ): Effect.Effect<VerifiedProviderRefresh> =>
216216 providerRegistry.getProviders.pipe(
217 Effect.map((providers) =>
218 providers
219 .filter(
220 (candidate) => candidate.driver === provider && candidate.instanceId === instanceId,
221 )
222 .map((candidate) => candidate.instanceId),
223 ),
217 Effect.map((providers) => {
218 const instanceIds: Array<ProviderInstanceId> = [];
219 for (const candidate of providers) {
220 if (candidate.driver === provider && candidate.instanceId === instanceId) {
221 instanceIds.push(candidate.instanceId);
222 }
223 }
224 return instanceIds;
225 }),
224226 Effect.flatMap((instanceIds) =>
225227 instanceIds.length === 0
226228 ? providerRegistry.refreshInstance(instanceId)
apps/server/src/sourceControl/AzureDevOpsCli.ts+7-4
......@@ -208,10 +208,13 @@ function parseRepositorySpecifier(repository: string): {
208208 readonly project: string | null;
209209 readonly name: string;
210210} {
211 const parts = repository
212 .split("/")
213 .map((part) => part.trim())
214 .filter((part) => part.length > 0);
211 const parts: Array<string> = [];
212 for (const part of repository.split("/")) {
213 const trimmed = part.trim();
214 if (trimmed.length > 0) {
215 parts.push(trimmed);
216 }
217 }
215218 return {
216219 project: parts.length > 1 ? (parts.at(-2) ?? null) : null,
217220 name: parts.at(-1) ?? repository.trim(),
apps/server/src/sourceControl/GitLabCli.ts+7-4
......@@ -247,10 +247,13 @@ function parseRepositoryPath(repository: string): {
247247 readonly namespacePath: string | null;
248248 readonly projectPath: string;
249249} {
250 const parts = repository
251 .split("/")
252 .map((part) => part.trim())
253 .filter((part) => part.length > 0);
250 const parts: Array<string> = [];
251 for (const part of repository.split("/")) {
252 const trimmed = part.trim();
253 if (trimmed.length > 0) {
254 parts.push(trimmed);
255 }
256 }
254257 const projectPath = parts.at(-1) ?? repository.trim();
255258 const namespacePath = parts.length > 1 ? parts.slice(0, -1).join("/") : null;
256259 return { namespacePath, projectPath };
apps/server/src/sourceControl/SourceControlProviderDiscovery.ts+15-6
......@@ -96,15 +96,24 @@ export function unknownAuth(detail?: string): SourceControlProviderAuth {
9696}
9797
9898export function combinedAuthOutput(input: SourceControlAuthProbeInput): string {
99 return [input.stdout, input.stderr].filter((entry) => entry.trim().length > 0).join("\n");
99 const parts: string[] = [];
100 for (const entry of [input.stdout, input.stderr]) {
101 if (entry.trim().length > 0) {
102 parts.push(entry);
103 }
104 }
105 return parts.join("\n");
100106}
101107
102108function sanitizedAuthLines(text: string): ReadonlyArray<string> {
103 return text
104 .split(/\r?\n/)
105 .map((entry) => entry.trim())
106 .filter((entry) => entry.length > 0)
107 .filter((entry) => !/^[-\s]*token(?:\s+scopes?)?:/iu.test(entry));
109 const lines: string[] = [];
110 for (const entry of text.split(/\r?\n/)) {
111 const line = entry.trim();
112 if (line.length === 0) continue;
113 if (/^[-\s]*token(?:\s+scopes?)?:/iu.test(line)) continue;
114 lines.push(line);
115 }
116 return lines;
108117}
109118
110119export function firstSafeAuthLine(text: string): string | undefined {
apps/server/src/sourceControl/SourceControlProviderRegistry.ts+11-12
......@@ -92,18 +92,17 @@ function selectProviderContext(
9292 readonly url: string;
9393 }>,
9494): SourceControlProvider.SourceControlProviderContext | null {
95 const candidates = remotes
96 .map((remote) => {
97 const provider = detectSourceControlProviderFromRemoteUrl(remote.url);
98 return provider
99 ? {
100 provider,
101 remoteName: remote.name,
102 remoteUrl: remote.url,
103 }
104 : null;
105 })
106 .filter((value): value is SourceControlProvider.SourceControlProviderContext => value !== null);
95 const candidates: Array<SourceControlProvider.SourceControlProviderContext> = [];
96 for (const remote of remotes) {
97 const provider = detectSourceControlProviderFromRemoteUrl(remote.url);
98 if (provider) {
99 candidates.push({
100 provider,
101 remoteName: remote.name,
102 remoteUrl: remote.url,
103 });
104 }
105 }
107106
108107 return (
109108 candidates.find((candidate) => candidate.remoteName === "origin") ??
apps/server/src/vcs/GitVcsDriverCore.ts+62-58
......@@ -1,3 +1,4 @@
1import * as Arr from "effect/Array";
12import * as Cache from "effect/Cache";
23import * as Data from "effect/Data";
34import * as DateTime from "effect/DateTime";
......@@ -1793,61 +1794,60 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
17931794 }
17941795 }
17951796
1796 const localBranches = localBranchResult.stdout
1797 .split("\n")
1798 .map(parseBranchLine)
1799 .filter((refName): refName is { name: string; current: boolean } => refName !== null)
1800 .map((refName) => ({
1801 name: refName.name,
1802 current: refName.current,
1803 isRemote: false,
1804 isDefault: refName.name === defaultBranch,
1805 worktreePath: worktreeMap.get(refName.name) ?? null,
1806 }))
1807 .toSorted((a, b) => {
1808 const aPriority = a.current ? 0 : a.isDefault ? 1 : 2;
1809 const bPriority = b.current ? 0 : b.isDefault ? 1 : 2;
1810 if (aPriority !== bPriority) return aPriority - bPriority;
1811
1812 const aLastCommit = branchLastCommit.get(a.name) ?? 0;
1813 const bLastCommit = branchLastCommit.get(b.name) ?? 0;
1814 if (aLastCommit !== bLastCommit) return bLastCommit - aLastCommit;
1815 return a.name.localeCompare(b.name);
1816 });
1797 const localBranches = Arr.filterMap(localBranchResult.stdout.split("\n"), (line) => {
1798 const refName = parseBranchLine(line);
1799 return refName === null
1800 ? Result.failVoid
1801 : Result.succeed({
1802 name: refName.name,
1803 current: refName.current,
1804 isRemote: false,
1805 isDefault: refName.name === defaultBranch,
1806 worktreePath: worktreeMap.get(refName.name) ?? null,
1807 });
1808 }).toSorted((a, b) => {
1809 const aPriority = a.current ? 0 : a.isDefault ? 1 : 2;
1810 const bPriority = b.current ? 0 : b.isDefault ? 1 : 2;
1811 if (aPriority !== bPriority) return aPriority - bPriority;
1812
1813 const aLastCommit = branchLastCommit.get(a.name) ?? 0;
1814 const bLastCommit = branchLastCommit.get(b.name) ?? 0;
1815 if (aLastCommit !== bLastCommit) return bLastCommit - aLastCommit;
1816 return a.name.localeCompare(b.name);
1817 });
18171818
18181819 const remoteBranches =
18191820 remoteBranchResult.exitCode === 0
1820 ? remoteBranchResult.stdout
1821 .split("\n")
1822 .map(parseBranchLine)
1823 .filter((refName): refName is { name: string; current: boolean } => refName !== null)
1824 .map((refName) => {
1825 const parsedRemoteRef = parseRemoteRefWithRemoteNames(refName.name, remoteNames);
1826 const remoteBranch: {
1827 name: string;
1828 current: boolean;
1829 isRemote: boolean;
1830 remoteName?: string;
1831 isDefault: boolean;
1832 worktreePath: string | null;
1833 } = {
1834 name: refName.name,
1835 current: false,
1836 isRemote: true,
1837 isDefault: false,
1838 worktreePath: null,
1839 };
1840 if (parsedRemoteRef) {
1841 remoteBranch.remoteName = parsedRemoteRef.remoteName;
1842 }
1843 return remoteBranch;
1844 })
1845 .toSorted((a, b) => {
1846 const aLastCommit = branchLastCommit.get(a.name) ?? 0;
1847 const bLastCommit = branchLastCommit.get(b.name) ?? 0;
1848 if (aLastCommit !== bLastCommit) return bLastCommit - aLastCommit;
1849 return a.name.localeCompare(b.name);
1850 })
1821 ? Arr.filterMap(remoteBranchResult.stdout.split("\n"), (line) => {
1822 const refName = parseBranchLine(line);
1823 if (refName === null) {
1824 return Result.failVoid;
1825 }
1826 const parsedRemoteRef = parseRemoteRefWithRemoteNames(refName.name, remoteNames);
1827 const remoteBranch: {
1828 name: string;
1829 current: boolean;
1830 isRemote: boolean;
1831 remoteName?: string;
1832 isDefault: boolean;
1833 worktreePath: string | null;
1834 } = {
1835 name: refName.name,
1836 current: false,
1837 isRemote: true,
1838 isDefault: false,
1839 worktreePath: null,
1840 };
1841 if (parsedRemoteRef) {
1842 remoteBranch.remoteName = parsedRemoteRef.remoteName;
1843 }
1844 return Result.succeed(remoteBranch);
1845 }).toSorted((a, b) => {
1846 const aLastCommit = branchLastCommit.get(a.name) ?? 0;
1847 const bLastCommit = branchLastCommit.get(b.name) ?? 0;
1848 if (aLastCommit !== bLastCommit) return bLastCommit - aLastCommit;
1849 return a.name.localeCompare(b.name);
1850 })
18511851 : [];
18521852
18531853 const refs = paginateBranches({
......@@ -2106,12 +2106,16 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
21062106 "--no-column",
21072107 "--format=%(refname:short)",
21082108 ]).pipe(
2109 Effect.map((stdout) =>
2110 stdout
2111 .split("\n")
2112 .map((line) => line.trim())
2113 .filter((line) => line.length > 0),
2114 ),
2109 Effect.map((stdout) => {
2110 const branchNames: Array<string> = [];
2111 for (const line of stdout.split("\n")) {
2112 const branchName = line.trim();
2113 if (branchName.length > 0) {
2114 branchNames.push(branchName);
2115 }
2116 }
2117 return branchNames;
2118 }),
21152119 );
21162120
21172121 return GitVcsDriver.GitVcsDriver.of({
apps/server/src/workspace/Layers/WorkspaceEntries.ts+21-15
......@@ -221,9 +221,13 @@ export const makeWorkspaceEntries = Effect.gen(function* () {
221221 return null;
222222 }
223223
224 const listedPaths = [...listedFiles.paths]
225 .map((entry) => toPosixPath(entry))
226 .filter((entry) => entry.length > 0 && !isPathInIgnoredDirectory(entry));
224 const listedPaths: Array<string> = [];
225 for (const rawEntry of listedFiles.paths) {
226 const entry = toPosixPath(rawEntry);
227 if (entry.length > 0 && !isPathInIgnoredDirectory(entry)) {
228 listedPaths.push(entry);
229 }
230 }
227231 const filePaths = yield* vcs.driver.filterIgnoredPaths(cwd, listedPaths).pipe(
228232 Effect.map((paths) => [...paths]),
229233 Effect.catch(() => filterVcsIgnoredPaths(cwd, listedPaths)),
......@@ -456,21 +460,23 @@ export const makeWorkspaceEntries = Effect.gen(function* () {
456460
457461 const showHidden = endsWithSeparator || prefix.startsWith(".");
458462 const lowerPrefix = prefix.toLowerCase();
463 const entries: Array<{ readonly name: string; readonly fullPath: string }> = [];
464 for (const dirent of dirents) {
465 if (
466 dirent.isDirectory() &&
467 dirent.name.toLowerCase().startsWith(lowerPrefix) &&
468 (showHidden || !dirent.name.startsWith("."))
469 ) {
470 entries.push({
471 name: dirent.name,
472 fullPath: path.join(parentPath, dirent.name),
473 });
474 }
475 }
459476
460477 return {
461478 parentPath,
462 entries: dirents
463 .filter(
464 (dirent) =>
465 dirent.isDirectory() &&
466 dirent.name.toLowerCase().startsWith(lowerPrefix) &&
467 (showHidden || !dirent.name.startsWith(".")),
468 )
469 .map((dirent) => ({
470 name: dirent.name,
471 fullPath: path.join(parentPath, dirent.name),
472 }))
473 .toSorted((left, right) => left.name.localeCompare(right.name)),
479 entries: entries.toSorted((left, right) => left.name.localeCompare(right.name)),
474480 };
475481 },
476482 );
apps/web/src/components/CommandPalette.logic.ts+14-17
......@@ -1,5 +1,7 @@
11import { type KeybindingCommand, type FilesystemBrowseEntry } from "@t3tools/contracts";
22import type { SidebarThreadSortOrder } from "@t3tools/contracts/settings";
3import * as Arr from "effect/Array";
4import * as Result from "effect/Result";
35import { type ReactNode } from "react";
46import { sortThreads } from "../lib/threadSort";
57import { formatRelativeTimeLabel } from "../timestampFormat";
......@@ -252,23 +254,18 @@ export function filterCommandPaletteGroups(input: {
252254 }
253255
254256 return searchableGroups.flatMap((group) => {
255 const items = group.items
256 .map((item, index) => {
257 const haystack = normalizeSearchText(item.searchTerms.join(" "));
258 if (!haystack.includes(normalizedQuery)) {
259 return null;
260 }
261
262 return {
263 item,
264 index,
265 rank: rankCommandPaletteItemMatch(item, normalizedQuery),
266 };
267 })
268 .filter(
269 (entry): entry is { item: (typeof group.items)[number]; index: number; rank: number } =>
270 entry !== null,
271 )
257 const items = Arr.filterMap(group.items, (item, index) => {
258 const haystack = normalizeSearchText(item.searchTerms.join(" "));
259 if (!haystack.includes(normalizedQuery)) {
260 return Result.failVoid;
261 }
262
263 return Result.succeed({
264 item,
265 index,
266 rank: rankCommandPaletteItemMatch(item, normalizedQuery),
267 });
268 })
272269 .toSorted((left, right) => right.rank - left.rank || left.index - right.index)
273270 .map((entry) => entry.item);
274271
apps/web/src/components/DiffPanel.tsx+8-6
......@@ -261,12 +261,14 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {
261261 [selectedCheckpointTurnCount],
262262 );
263263 const conversationCheckpointTurnCount = useMemo(() => {
264 const turnCounts = orderedTurnDiffSummaries
265 .map(
266 (summary) =>
267 summary.checkpointTurnCount ?? inferredCheckpointTurnCountByTurnId[summary.turnId],
268 )
269 .filter((value): value is number => typeof value === "number");
264 const turnCounts: Array<number> = [];
265 for (const summary of orderedTurnDiffSummaries) {
266 const value =
267 summary.checkpointTurnCount ?? inferredCheckpointTurnCountByTurnId[summary.turnId];
268 if (typeof value === "number") {
269 turnCounts.push(value);
270 }
271 }
270272 if (turnCounts.length === 0) {
271273 return undefined;
272274 }
apps/web/src/components/chat/ChatComposer.tsx+6-3
......@@ -1323,9 +1323,12 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
13231323 } catch {
13241324 const currentImageIds = new Set(composerImages.map((image) => image.id));
13251325 const fallbackPersistedAttachments = getPersistedAttachmentsForThread();
1326 const fallbackPersistedIds = fallbackPersistedAttachments
1327 .map((attachment) => attachment.id)
1328 .filter((id) => currentImageIds.has(id));
1326 const fallbackPersistedIds: Array<string> = [];
1327 for (const attachment of fallbackPersistedAttachments) {
1328 if (currentImageIds.has(attachment.id)) {
1329 fallbackPersistedIds.push(attachment.id);
1330 }
1331 }
13291332 const fallbackPersistedIdSet = new Set(fallbackPersistedIds);
13301333 const fallbackAttachments = fallbackPersistedAttachments.filter((attachment) =>
13311334 fallbackPersistedIdSet.has(attachment.id),
apps/web/src/components/chat/ModelPickerContent.tsx+7-2
......@@ -272,8 +272,13 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: {
272272 // ignoring sidebar selection so account-scoped searches can find a
273273 // model before the user chooses a specific instance rail item.
274274 if (props.lockedProvider !== null) {
275 return rankedMatches
276 .filter((rankedModel) => matchesLockedProvider(rankedModel.model))
275 const lockedProviderMatches: Array<(typeof rankedMatches)[number]> = [];
276 for (const rankedModel of rankedMatches) {
277 if (matchesLockedProvider(rankedModel.model)) {
278 lockedProviderMatches.push(rankedModel);
279 }
280 }
281 return lockedProviderMatches
277282 .toSorted((a, b) => {
278283 const scoreDelta = a.score - b.score;
279284 if (scoreDelta !== 0) {
apps/web/src/components/chat/TraitsPicker.tsx+17-16
......@@ -381,22 +381,23 @@ export const TraitsPicker = memo(function TraitsPicker({
381381 return null;
382382 }
383383
384 const triggerLabel =
385 descriptors
386 .map((descriptor) => {
387 if (ultrathinkPromptControlled && descriptor.id === primarySelectDescriptor?.id) {
388 return "Ultrathink";
389 }
390 if (descriptor.type === "boolean") {
391 if (descriptor.id === "fastMode") {
392 return descriptor.currentValue === true ? "Fast" : "Normal";
393 }
394 return `${descriptor.label} ${descriptor.currentValue === true ? "On" : "Off"}`;
395 }
396 return getProviderOptionCurrentLabel(descriptor);
397 })
398 .filter((label): label is string => typeof label === "string" && label.length > 0)
399 .join(" · ") || "";
384 const triggerLabels: Array<string> = [];
385 for (const descriptor of descriptors) {
386 const label =
387 ultrathinkPromptControlled && descriptor.id === primarySelectDescriptor?.id
388 ? "Ultrathink"
389 : descriptor.type === "boolean"
390 ? descriptor.id === "fastMode"
391 ? descriptor.currentValue === true
392 ? "Fast"
393 : "Normal"
394 : `${descriptor.label} ${descriptor.currentValue === true ? "On" : "Off"}`
395 : getProviderOptionCurrentLabel(descriptor);
396 if (typeof label === "string" && label.length > 0) {
397 triggerLabels.push(label);
398 }
399 }
400 const triggerLabel = triggerLabels.join(" · ");
400401
401402 const isCodexStyle = provider === "codex";
402403
apps/web/src/components/chat/modelPickerSearch.ts+7-3
......@@ -68,9 +68,13 @@ export function scoreModelPickerSearch(
6868 let score = 0;
6969
7070 for (const token of tokens) {
71 const tokenScores = fields
72 .map((field, index) => scoreModelPickerSearchToken(field, token, index * 10))
73 .filter((fieldScore): fieldScore is number => fieldScore !== null);
71 const tokenScores: Array<number> = [];
72 for (let index = 0; index < fields.length; index += 1) {
73 const fieldScore = scoreModelPickerSearchToken(fields[index]!, token, index * 10);
74 if (fieldScore !== null) {
75 tokenScores.push(fieldScore);
76 }
77 }
7478
7579 if (tokenScores.length === 0) {
7680 return null;
apps/web/src/components/chat/userMessageTerminalContexts.ts+8-5
......@@ -7,11 +7,14 @@ export function buildInlineTerminalContextText(
77 header: string;
88 }>,
99): string {
10 return contexts
11 .map((context) => context.header.trim())
12 .filter((header) => header.length > 0)
13 .map(formatInlineTerminalContextLabel)
14 .join(" ");
10 const labels: Array<string> = [];
11 for (const context of contexts) {
12 const header = context.header.trim();
13 if (header.length > 0) {
14 labels.push(formatInlineTerminalContextLabel(header));
15 }
16 }
17 return labels.join(" ");
1518}
1619
1720export function formatInlineTerminalContextLabel(header: string): string {
apps/web/src/components/settings/ConnectionsSettings.tsx+21-15
......@@ -534,21 +534,27 @@ const PairingLinkListRow = memo(function PairingLinkListRow({
534534 const endpoint = selectPairingEndpoint(endpoints, defaultEndpointKey);
535535 return endpoint ? resolveAdvertisedEndpointPairingUrl(endpoint, pairingLink.credential) : null;
536536 }, [defaultEndpointKey, endpoints, pairingLink.credential]);
537 const endpointCopyOptions = useMemo(
538 () =>
539 endpoints
540 .filter((endpoint) => endpoint.status !== "unavailable")
541 .map((endpoint) => {
542 const url = resolveAdvertisedEndpointPairingUrl(endpoint, pairingLink.credential);
543 return {
544 key: endpointDefaultPreferenceKey(endpoint),
545 label: endpoint.label,
546 url,
547 detail: isHostedAppPairingUrl(url) ? "Hosted app link" : "Backend pairing URL",
548 };
549 }),
550 [endpoints, pairingLink.credential],
551 );
537 const endpointCopyOptions = useMemo(() => {
538 const options: Array<{
539 readonly key: string;
540 readonly label: string;
541 readonly url: string;
542 readonly detail: string;
543 }> = [];
544 for (const endpoint of endpoints) {
545 if (endpoint.status === "unavailable") {
546 continue;
547 }
548 const url = resolveAdvertisedEndpointPairingUrl(endpoint, pairingLink.credential);
549 options.push({
550 key: endpointDefaultPreferenceKey(endpoint),
551 label: endpoint.label,
552 url,
553 detail: isHostedAppPairingUrl(url) ? "Hosted app link" : "Backend pairing URL",
554 });
555 }
556 return options;
557 }, [endpoints, pairingLink.credential]);
552558 const shareablePairingUrl =
553559 endpointPairingUrl ??
554560 (endpointUrl != null && endpointUrl !== ""
apps/web/src/components/settings/KeybindingsSettings.logic.ts+17-14
......@@ -140,14 +140,16 @@ export function keybindingConflictLabels(
140140 input: { readonly rowId: string; readonly key: string; readonly when: string },
141141): ReadonlyArray<string> {
142142 if (input.key.trim().length === 0) return [];
143 const conflicts = rows
144 .filter(
145 (candidate) =>
146 candidate.id !== input.rowId &&
147 candidate.key === input.key &&
148 conflictsWithWhen(candidate.when, input.when),
149 )
150 .map((candidate) => commandLabel(candidate.command));
143 const conflicts: Array<string> = [];
144 for (const candidate of rows) {
145 if (
146 candidate.id !== input.rowId &&
147 candidate.key === input.key &&
148 conflictsWithWhen(candidate.when, input.when)
149 ) {
150 conflicts.push(commandLabel(candidate.command));
151 }
152 }
151153 return [...new Set(conflicts)].toSorted();
152154}
153155
......@@ -274,12 +276,13 @@ export function commandLabel(command: KeybindingCommand): string {
274276}
275277
276278function titleCaseCommandSegment(segment: string): string {
277 return segment
278 .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
279 .split(/[-_\s]+/)
280 .filter(Boolean)
281 .map((part) => part.slice(0, 1).toUpperCase() + part.slice(1))
282 .join(" ");
279 const words: Array<string> = [];
280 for (const part of segment.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[-_\s]+/)) {
281 if (part.length > 0) {
282 words.push(part.slice(0, 1).toUpperCase() + part.slice(1));
283 }
284 }
285 return words.join(" ");
283286}
284287
285288export function normalizeShortcutKeyToken(key: string): string | null {
apps/web/src/components/settings/ProviderInstanceCard.tsx+5-3
......@@ -10,6 +10,8 @@ import {
1010 Trash2Icon,
1111 XIcon,
1212} from "lucide-react";
13import * as Arr from "effect/Array";
14import * as Result from "effect/Result";
1315import { useEffect, useState, type ReactNode } from "react";
1416import {
1517 isProviderDriverKind,
......@@ -118,9 +120,9 @@ export function deriveProviderModelsForDisplay(input: {
118120 readonly customModels: ReadonlyArray<string>;
119121}): ReadonlyArray<ServerProviderModel> {
120122 const liveCustomModelsBySlug = new Map(
121 (input.liveModels ?? [])
122 .filter((model) => model.isCustom)
123 .map((model) => [model.slug, model] as const),
123 Arr.filterMap(input.liveModels ?? [], (model) =>
124 model.isCustom ? Result.succeed([model.slug, model] as const) : Result.failVoid,
125 ),
124126 );
125127 const serverModels = input.liveModels?.filter((model) => !model.isCustom) ?? [];
126128 const customModels = input.customModels.map(
apps/web/src/components/settings/SettingsPanels.tsx+31-15
......@@ -14,8 +14,10 @@ import {
1414import { scopeThreadRef } from "@t3tools/client-runtime";
1515import { DEFAULT_UNIFIED_SETTINGS } from "@t3tools/contracts/settings";
1616import { createModelSelection } from "@t3tools/shared/model";
17import * as Arr from "effect/Array";
1718import * as Duration from "effect/Duration";
1819import * as Equal from "effect/Equal";
20import * as Result from "effect/Result";
1921import { APP_VERSION, HOSTED_APP_CHANNEL, HOSTED_APP_CHANNEL_LABEL } from "../../branding";
2022import {
2123 canCheckForUpdate,
......@@ -1143,7 +1145,12 @@ export function ProviderSettingsPanel() {
11431145 nextFavoriteModels: ReadonlyArray<string>,
11441146 ) => {
11451147 const favoriteModels = [
1146 ...new Set(nextFavoriteModels.map((slug) => slug.trim()).filter((slug) => slug.length > 0)),
1148 ...new Set(
1149 Arr.filterMap(nextFavoriteModels, (slug) => {
1150 const trimmedSlug = slug.trim();
1151 return trimmedSlug.length > 0 ? Result.succeed(trimmedSlug) : Result.failVoid;
1152 }),
1153 ),
11471154 ];
11481155 updateSettings({
11491156 favorites: [
......@@ -1249,9 +1256,9 @@ export function ProviderSettingsPanel() {
12491256 hiddenModels: [],
12501257 modelOrder: [],
12511258 };
1252 const favoriteModels = (settings.favorites ?? [])
1253 .filter((favorite) => favorite.provider === row.instanceId)
1254 .map((favorite) => favorite.model);
1259 const favoriteModels = Arr.filterMap(settings.favorites ?? [], (favorite) =>
1260 favorite.provider === row.instanceId ? Result.succeed(favorite.model) : Result.failVoid,
1261 );
12551262 const resetLabel = driverOption?.label ?? String(row.driver);
12561263 const headerAction =
12571264 row.isDefault && row.isDirty ? (
......@@ -1369,21 +1376,30 @@ export function ArchivedThreadsPanel() {
13691376 })),
13701377 );
13711378
1372 return [...projectsByEnvironmentAndId.values()]
1373 .map((project) => ({
1374 project,
1375 threads: threads
1376 .filter(
1377 (thread) =>
1378 thread.projectId === project.id && thread.environmentId === project.environmentId,
1379 )
1380 .toSorted((left, right) => {
1379 const archivedProjects = Array.from(projectsByEnvironmentAndId.values());
1380 const groups: Array<{
1381 readonly project: (typeof archivedProjects)[number];
1382 readonly threads: Array<(typeof threads)[number]>;
1383 }> = [];
1384 for (const project of archivedProjects) {
1385 const projectThreads: Array<(typeof threads)[number]> = [];
1386 for (const thread of threads) {
1387 if (thread.projectId === project.id && thread.environmentId === project.environmentId) {
1388 projectThreads.push(thread);
1389 }
1390 }
1391 if (projectThreads.length > 0) {
1392 groups.push({
1393 project,
1394 threads: projectThreads.toSorted((left, right) => {
13811395 const leftKey = left.archivedAt ?? left.createdAt;
13821396 const rightKey = right.archivedAt ?? right.createdAt;
13831397 return rightKey.localeCompare(leftKey) || right.id.localeCompare(left.id);
13841398 }),
1385 }))
1386 .filter((group) => group.threads.length > 0);
1399 });
1400 }
1401 }
1402 return groups;
13871403 }, [archivedSnapshots]);
13881404
13891405 const handleArchivedThreadContextMenu = useCallback(
apps/web/src/composerDraftStore.ts+13-8
......@@ -451,11 +451,13 @@ function cloneModelSelection(selection: ModelSelection): DeepMutable<ModelSelect
451451function compactModelSelectionByProvider(
452452 selections: Partial<Record<ProviderInstanceId, ModelSelection>>,
453453): DeepMutable<Record<ProviderInstanceId, ModelSelection>> {
454 return Object.fromEntries(
455 Object.entries(selections)
456 .filter((entry): entry is [string, ModelSelection] => entry[1] !== undefined)
457 .map(([provider, selection]) => [provider, cloneModelSelection(selection)]),
458 ) as DeepMutable<Record<ProviderInstanceId, ModelSelection>>;
454 const entries: Array<[string, DeepMutable<ModelSelection>]> = [];
455 for (const [provider, selection] of Object.entries(selections)) {
456 if (selection !== undefined) {
457 entries.push([provider, cloneModelSelection(selection)]);
458 }
459 }
460 return Object.fromEntries(entries) as DeepMutable<Record<ProviderInstanceId, ModelSelection>>;
459461}
460462
461463const EMPTY_PERSISTED_DRAFT_STORE_STATE = Object.freeze<PersistedComposerDraftStoreState>({
......@@ -1809,9 +1811,12 @@ function verifyPersistedAttachments(
18091811 const persistedAttachments = attachments.filter(
18101812 (attachment) => imageIdSet.has(attachment.id) && persistedIdSet.has(attachment.id),
18111813 );
1812 const nonPersistedImageIds = current.images
1813 .map((image) => image.id)
1814 .filter((imageId) => !persistedIdSet.has(imageId));
1814 const nonPersistedImageIds: string[] = [];
1815 for (const image of current.images) {
1816 if (!persistedIdSet.has(image.id)) {
1817 nonPersistedImageIds.push(image.id);
1818 }
1819 }
18151820 const nextDraft: ComposerThreadDraftState = {
18161821 ...current,
18171822 persistedAttachments,
apps/web/src/environments/runtime/service.ts+6-4
......@@ -1459,10 +1459,12 @@ async function syncSavedEnvironmentConnections(
14591459 records: ReadonlyArray<SavedEnvironmentRecord>,
14601460): Promise<void> {
14611461 const expectedEnvironmentIds = new Set(records.map((record) => record.environmentId));
1462 const staleEnvironmentIds = [...environmentConnections.values()]
1463 .filter((connection) => connection.kind === "saved")
1464 .map((connection) => connection.environmentId)
1465 .filter((environmentId) => !expectedEnvironmentIds.has(environmentId));
1462 const staleEnvironmentIds: EnvironmentId[] = [];
1463 for (const connection of environmentConnections.values()) {
1464 if (connection.kind !== "saved") continue;
1465 if (expectedEnvironmentIds.has(connection.environmentId)) continue;
1466 staleEnvironmentIds.push(connection.environmentId);
1467 }
14661468
14671469 await Promise.all(
14681470 staleEnvironmentIds.map((environmentId) => disconnectSavedEnvironment(environmentId)),
apps/web/src/lib/terminalContext.ts+18-15
......@@ -127,19 +127,18 @@ export function buildTerminalContextPreviewTitle(
127127 if (contexts.length === 0) {
128128 return null;
129129 }
130 const previews = contexts
131 .map((context) => {
132 const normalized = normalizeTerminalContextSelection(context);
133 if (!normalized) {
134 return null;
135 }
136 const preview = previewTerminalContextText(normalized.text);
137 return preview.length > 0
130 const previewParts: string[] = [];
131 for (const context of contexts) {
132 const normalized = normalizeTerminalContextSelection(context);
133 if (!normalized) continue;
134 const preview = previewTerminalContextText(normalized.text);
135 previewParts.push(
136 preview.length > 0
138137 ? `${formatTerminalContextLabel(normalized)}\n${preview}`
139 : formatTerminalContextLabel(normalized);
140 })
141 .filter((value): value is string => value !== null)
142 .join("\n\n");
138 : formatTerminalContextLabel(normalized),
139 );
140 }
141 const previews = previewParts.join("\n\n");
143142 return previews.length > 0 ? previews : null;
144143}
145144
......@@ -152,9 +151,13 @@ function buildTerminalContextBodyLines(selection: TerminalContextSelection): str
152151export function buildTerminalContextBlock(
153152 contexts: ReadonlyArray<TerminalContextSelection>,
154153): string {
155 const normalizedContexts = contexts
156 .map((context) => normalizeTerminalContextSelection(context))
157 .filter((context): context is TerminalContextSelection => context !== null);
154 const normalizedContexts: TerminalContextSelection[] = [];
155 for (const context of contexts) {
156 const normalized = normalizeTerminalContextSelection(context);
157 if (normalized !== null) {
158 normalizedContexts.push(normalized);
159 }
160 }
158161 if (normalizedContexts.length === 0) {
159162 return "";
160163 }
apps/web/src/modelSelection.ts+8-4
......@@ -14,6 +14,8 @@ import {
1414} from "@t3tools/shared/model";
1515import { getComposerProviderState } from "./components/chat/composerProviderState";
1616import { UnifiedSettings } from "@t3tools/contracts/settings";
17import * as Arr from "effect/Array";
18import * as Result from "effect/Result";
1719import {
1820 getDefaultServerModel,
1921 getProviderModels,
......@@ -150,9 +152,9 @@ export function getAppModelOptions(
150152 const options: AppModelOption[] = getProviderModels(providers, provider).map(toAppModelOption);
151153 const seen = new Set(options.map((option) => option.slug));
152154 const builtInModelSlugs = new Set(
153 getProviderModels(providers, provider)
154 .filter((model) => !model.isCustom)
155 .map((model) => model.slug),
155 Arr.filterMap(getProviderModels(providers, provider), (model) =>
156 model.isCustom ? Result.failVoid : Result.succeed(model.slug),
157 ),
156158 );
157159
158160 // Read from the default instance's config first (that's where edits
......@@ -198,7 +200,9 @@ export function getAppModelOptionsForInstance(
198200 const options: AppModelOption[] = entry.models.map(toAppModelOption);
199201 const seen = new Set(options.map((option) => option.slug));
200202 const builtInModelSlugs = new Set(
201 entry.models.filter((model) => !model.isCustom).map((model) => model.slug),
203 Arr.filterMap(entry.models, (model) =>
204 model.isCustom ? Result.failVoid : Result.succeed(model.slug),
205 ),
202206 );
203207
204208 const customModels = readInstanceCustomModels(settings, entry.instanceId, entry.driverKind);
apps/web/src/pendingUserInput.ts+8-4
......@@ -33,10 +33,14 @@ function normalizeSelectedOptionLabels(value: string[] | undefined): string[] {
3333 return [];
3434 }
3535
36 const normalized = value
37 .filter((entry): entry is string => typeof entry === "string")
38 .map((entry) => entry.trim())
39 .filter((entry) => entry.length > 0);
36 const normalized: string[] = [];
37 for (const entry of value) {
38 if (typeof entry !== "string") continue;
39 const trimmed = entry.trim();
40 if (trimmed.length > 0) {
41 normalized.push(trimmed);
42 }
43 }
4044
4145 return Array.from(new Set(normalized));
4246}
apps/web/src/providerInstances.ts+7-5
......@@ -66,13 +66,15 @@ export interface ProviderInstanceEntry {
6666 * will take precedence over this fallback.
6767 */
6868function humanizeInstanceId(instanceId: ProviderInstanceId): string {
69 return instanceId
69 const words: string[] = [];
70 for (const token of instanceId
7071 .replace(/[_-]+/g, " ")
7172 .replace(/([a-z])([A-Z])/g, "$1 $2")
72 .split(" ")
73 .filter((token) => token.length > 0)
74 .map((token) => token.charAt(0).toUpperCase() + token.slice(1))
75 .join(" ");
73 .split(" ")) {
74 if (token.length === 0) continue;
75 words.push(token.charAt(0).toUpperCase() + token.slice(1));
76 }
77 return words.join(" ");
7678}
7779
7880function driverKindLabel(driverKind: ProviderDriverKind): string {
apps/web/src/providerSkillPresentation.ts+6-5
......@@ -1,11 +1,12 @@
11import type { ServerProviderSkill } from "@t3tools/contracts";
22
33function titleCaseWords(value: string): string {
4 return value
5 .split(/[\s:_-]+/)
6 .filter((segment) => segment.length > 0)
7 .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
8 .join(" ");
4 const words: string[] = [];
5 for (const segment of value.split(/[\s:_-]+/)) {
6 if (segment.length === 0) continue;
7 words.push(segment.charAt(0).toUpperCase() + segment.slice(1));
8 }
9 return words.join(" ");
910}
1011
1112function normalizePathSeparators(pathValue: string): string {
apps/web/src/session-logic.ts+43-37
......@@ -385,28 +385,25 @@ export function deriveActivePlanState(
385385 if (!Array.isArray(rawPlan)) {
386386 return null;
387387 }
388 const steps = rawPlan
389 .map((entry) => {
390 if (!entry || typeof entry !== "object") return null;
391 const record = entry as Record<string, unknown>;
392 if (typeof record.step !== "string") {
393 return null;
394 }
395 const status =
396 record.status === "completed" || record.status === "inProgress" ? record.status : "pending";
397 return {
398 step: record.step,
399 status,
400 };
401 })
402 .filter(
403 (
404 step,
405 ): step is {
406 step: string;
407 status: "pending" | "inProgress" | "completed";
408 } => step !== null,
409 );
388 const steps: Array<{
389 step: string;
390 status: "pending" | "inProgress" | "completed";
391 }> = [];
392 for (const entry of rawPlan) {
393 if (!entry || typeof entry !== "object") {
394 continue;
395 }
396 const record = entry as Record<string, unknown>;
397 if (typeof record.step !== "string") {
398 continue;
399 }
400 const status =
401 record.status === "completed" || record.status === "inProgress" ? record.status : "pending";
402 steps.push({
403 step: record.step,
404 status,
405 });
406 }
410407 if (steps.length === 0) {
411408 return null;
412409 }
......@@ -485,14 +482,16 @@ export function deriveWorkLogEntries(
485482 latestTurnId: TurnId | undefined,
486483): WorkLogEntry[] {
487484 const ordered = [...activities].toSorted(compareActivitiesByOrder);
488 const entries = ordered
489 .filter((activity) => (latestTurnId ? activity.turnId === latestTurnId : true))
490 .filter((activity) => activity.kind !== "tool.started")
491 .filter((activity) => activity.kind !== "task.started")
492 .filter((activity) => activity.kind !== "context-window.updated")
493 .filter((activity) => activity.summary !== "Checkpoint captured")
494 .filter((activity) => !isPlanBoundaryToolActivity(activity))
495 .map(toDerivedWorkLogEntry);
485 const entries: DerivedWorkLogEntry[] = [];
486 for (const activity of ordered) {
487 if (latestTurnId && activity.turnId !== latestTurnId) continue;
488 if (activity.kind === "tool.started") continue;
489 if (activity.kind === "task.started") continue;
490 if (activity.kind === "context-window.updated") continue;
491 if (activity.summary === "Checkpoint captured") continue;
492 if (isPlanBoundaryToolActivity(activity)) continue;
493 entries.push(toDerivedWorkLogEntry(activity));
494 }
496495 return collapseDerivedWorkLogEntries(entries).map(
497496 ({ activityKind: _activityKind, collapseKey: _collapseKey, ...entry }) => entry,
498497 );
......@@ -834,9 +833,13 @@ function formatCommandValue(value: unknown): string | null {
834833 if (!Array.isArray(value)) {
835834 return null;
836835 }
837 const parts = value
838 .map((entry) => asTrimmedString(entry))
839 .filter((entry): entry is string => entry !== null);
836 const parts: Array<string> = [];
837 for (const entry of value) {
838 const part = asTrimmedString(entry);
839 if (part !== null) {
840 parts.push(part);
841 }
842 }
840843 if (parts.length === 0) {
841844 return null;
842845 }
......@@ -920,10 +923,13 @@ function normalizePreviewForComparison(value: string | null | undefined): string
920923}
921924
922925function summarizeToolTextOutput(value: string): string | null {
923 const lines = value
924 .split(/\r?\n/u)
925 .map((line) => normalizeInlinePreview(line))
926 .filter((line) => line.length > 0);
926 const lines: Array<string> = [];
927 for (const rawLine of value.split(/\r?\n/u)) {
928 const line = normalizeInlinePreview(rawLine);
929 if (line.length > 0) {
930 lines.push(line);
931 }
932 }
927933 const firstLine = lines.find((line) => line !== "```");
928934 if (firstLine) {
929935 return truncateInlinePreview(firstLine);
packages/shared/src/git.ts+5-1
......@@ -6,6 +6,8 @@ import type {
66 VcsStatusResult,
77 VcsStatusStreamEvent,
88} from "@t3tools/contracts";
9import * as Arr from "effect/Array";
10import * as Result from "effect/Result";
911import { detectSourceControlProviderFromRemoteUrl } from "./sourceControl.ts";
1012
1113export const WORKTREE_BRANCH_PREFIX = "t3code";
......@@ -172,7 +174,9 @@ export function dedupeRemoteBranchesWithLocalMatches(
172174 refs: ReadonlyArray<VcsRef>,
173175): ReadonlyArray<VcsRef> {
174176 const localBranchNames = new Set(
175 refs.filter((refName) => !refName.isRemote).map((refName) => refName.name),
177 Arr.filterMap(refs, (refName) =>
178 refName.isRemote ? Result.failVoid : Result.succeed(refName.name),
179 ),
176180 );
177181
178182 return refs.filter((refName) => {
packages/shared/src/observability.ts+7-5
......@@ -184,11 +184,13 @@ function normalizeJsonValue(value: unknown, seen: WeakSet<object> = new WeakSet(
184184export function compactTraceAttributes(
185185 attributes: Readonly<Record<string, unknown>>,
186186): TraceAttributes {
187 return Object.fromEntries(
188 Object.entries(attributes)
189 .filter(([, value]) => value !== undefined)
190 .map(([key, value]) => [key, normalizeJsonValue(value)]),
191 );
187 const entries: Array<[string, unknown]> = [];
188 for (const [key, value] of Object.entries(attributes)) {
189 if (value !== undefined) {
190 entries.push([key, normalizeJsonValue(value)]);
191 }
192 }
193 return Object.fromEntries(entries);
192194}
193195
194196function formatTraceExit(exit: Exit.Exit<unknown, unknown>): EffectTraceRecord["exit"] {
packages/shared/src/semver.ts+22-9
......@@ -9,10 +9,13 @@ const SEMVER_NUMBER_SEGMENT = /^\d+$/;
99
1010export function normalizeSemverVersion(version: string): string {
1111 const [main, prerelease] = version.trim().split("-", 2);
12 const segments = (main ?? "")
13 .split(".")
14 .map((segment) => segment.trim())
15 .filter((segment) => segment.length > 0);
12 const segments: string[] = [];
13 for (const segment of (main ?? "").split(".")) {
14 const trimmed = segment.trim();
15 if (trimmed.length > 0) {
16 segments.push(trimmed);
17 }
18 }
1619
1720 if (segments.length === 2) {
1821 segments.push("0");
......@@ -52,14 +55,24 @@ export function parseSemver(value: string): ParsedSemver | null {
5255 major,
5356 minor,
5457 patch,
55 prerelease:
56 prerelease
57 ?.split(".")
58 .map((segment) => segment.trim())
59 .filter((segment) => segment.length > 0) ?? [],
58 prerelease: parsePrereleaseSegments(prerelease),
6059 };
6160}
6261
62function parsePrereleaseSegments(prerelease: string | undefined): ReadonlyArray<string> {
63 if (prerelease === undefined) {
64 return [];
65 }
66 const segments: string[] = [];
67 for (const segment of prerelease.split(".")) {
68 const trimmed = segment.trim();
69 if (trimmed.length > 0) {
70 segments.push(trimmed);
71 }
72 }
73 return segments;
74}
75
6376function comparePrereleaseIdentifier(left: string, right: string): number {
6477 const leftNumeric = SEMVER_NUMBER_SEGMENT.test(left);
6578 const rightNumeric = SEMVER_NUMBER_SEGMENT.test(right);
packages/shared/src/shell.ts+13-9
......@@ -321,11 +321,12 @@ function resolveWindowsPathExtensions(env: NodeJS.ProcessEnv): ReadonlyArray<str
321321 const fallback = [".COM", ".EXE", ".BAT", ".CMD"];
322322 if (!rawValue) return fallback;
323323
324 const parsed = rawValue
325 .split(";")
326 .map((entry) => entry.trim())
327 .filter((entry) => entry.length > 0)
328 .map((entry) => (entry.startsWith(".") ? entry.toUpperCase() : `.${entry.toUpperCase()}`));
324 const parsed: string[] = [];
325 for (const entry of rawValue.split(";")) {
326 const trimmed = entry.trim();
327 if (trimmed.length === 0) continue;
328 parsed.push(trimmed.startsWith(".") ? trimmed.toUpperCase() : `.${trimmed.toUpperCase()}`);
329 }
329330 return parsed.length > 0 ? Array.from(new Set(parsed)) : fallback;
330331}
331332
......@@ -397,10 +398,13 @@ export function resolveCommandPath(
397398
398399 const pathValue = resolvePathEnvironmentVariable(env);
399400 if (pathValue.length === 0) return null;
400 const pathEntries = pathValue
401 .split(pathDelimiterForPlatform(platform))
402 .map((entry) => stripWrappingQuotes(entry.trim()))
403 .filter((entry) => entry.length > 0);
401 const pathEntries: string[] = [];
402 for (const entry of pathValue.split(pathDelimiterForPlatform(platform))) {
403 const pathEntry = stripWrappingQuotes(entry.trim());
404 if (pathEntry.length > 0) {
405 pathEntries.push(pathEntry);
406 }
407 }
404408
405409 for (const pathEntry of pathEntries) {
406410 for (const candidate of commandCandidates) {
packages/shared/src/toolActivity.ts+7-3
......@@ -22,9 +22,13 @@ function normalizeCommandValue(value: unknown): string | undefined {
2222 if (!Array.isArray(value)) {
2323 return undefined;
2424 }
25 const parts = value
26 .map((entry) => asTrimmedString(entry))
27 .filter((entry): entry is string => entry !== undefined);
25 const parts: string[] = [];
26 for (const entry of value) {
27 const part = asTrimmedString(entry);
28 if (part !== undefined) {
29 parts.push(part);
30 }
31 }
2832 return parts.length > 0 ? parts.join(" ") : undefined;
2933}
3034
packages/ssh/src/config.ts+9-4
......@@ -15,12 +15,17 @@ function stripInlineComment(line: string): string {
1515}
1616
1717function splitDirectiveArgs(value: string): ReadonlyArray<string> {
18 return value
18 const args: Array<string> = [];
19 for (const rawEntry of value
1920 .replace(/=(?!=)/gu, " ")
2021 .trim()
21 .split(/\s+/u)
22 .map((entry) => entry.trim())
23 .filter((entry) => entry.length > 0);
22 .split(/\s+/u)) {
23 const entry = rawEntry.trim();
24 if (entry.length > 0) {
25 args.push(entry);
26 }
27 }
28 return args;
2429}
2530
2631function expandHomePath(input: string, homeDir: string): string {
packages/tailscale/src/tailscale.ts+8-5
......@@ -112,11 +112,14 @@ export const parseTailscaleStatus = (
112112 Effect.mapError((cause) => new TailscaleStatusParseError({ cause })),
113113 Effect.map((parsed) => {
114114 const rawIps = parsed.Self?.TailscaleIPs;
115 const tailnetIpv4Addresses = Array.isArray(rawIps)
116 ? rawIps
117 .filter((address): address is string => typeof address === "string")
118 .filter(isTailscaleIpv4Address)
119 : [];
115 const tailnetIpv4Addresses: Array<string> = [];
116 if (Array.isArray(rawIps)) {
117 for (const address of rawIps) {
118 if (typeof address === "string" && isTailscaleIpv4Address(address)) {
119 tailnetIpv4Addresses.push(address);
120 }
121 }
122 }
120123
121124 return {
122125 magicDnsName: normalizeMagicDnsName(parsed),