diff --git a/extensions/device-pair/pair-command-approve.ts b/extensions/device-pair/pair-command-approve.ts index 5b8d47ef217..e5e19c1c5f0 100644 --- a/extensions/device-pair/pair-command-approve.ts +++ b/extensions/device-pair/pair-command-approve.ts @@ -36,9 +36,14 @@ export function selectPendingApprovalRequest(params: { } if (normalizeLowercaseStringOrEmpty(params.requested) === "latest") { - return { - pending: [...params.pending].toSorted((a, b) => (b.ts ?? 0) - (a.ts ?? 0))[0], - }; + let latest = params.pending[0]; + for (let index = 1; index < params.pending.length; index += 1) { + const pending = params.pending[index]; + if ((pending.ts ?? 0) > (latest.ts ?? 0)) { + latest = pending; + } + } + return { pending: latest }; } return { diff --git a/extensions/memory-core/src/rem-evidence.ts b/extensions/memory-core/src/rem-evidence.ts index c5f54b2e703..08a39d2c08b 100644 --- a/extensions/memory-core/src/rem-evidence.ts +++ b/extensions/memory-core/src/rem-evidence.ts @@ -677,6 +677,22 @@ function isRoutingSummary(summary: SectionSummary): boolean { return summary.scores.routing > 0 || REM_ROUTING_SIGNAL_RE.test(summary.text); } +function findStrongestSummary( + summaries: SectionSummary[], + predicate: (summary: SectionSummary) => boolean, +): SectionSummary | undefined { + let strongest: SectionSummary | undefined; + for (const summary of summaries) { + if (!predicate(summary)) { + continue; + } + if (!strongest || summary.scores.overall > strongest.scores.overall) { + strongest = summary; + } + } + return strongest; +} + function previewGroundedRemForFile(params: { relPath: string; content: string; @@ -847,15 +863,15 @@ function previewGroundedRemForFile(params: { (sum, { section, snippets }) => sum + scoreSection(section, snippets).tasks, 0, ); - const strongestRoutingSummary = summaries - .filter((summary) => isRoutingSummary(summary)) - .toSorted((left, right) => right.scores.overall - left.scores.overall)[0]; - const strongestIncidentSummary = summaries - .filter((summary) => summary.scores.incident > 0) - .toSorted((left, right) => right.scores.overall - left.scores.overall)[0]; - const strongestExternalizationSummary = summaries - .filter((summary) => summary.scores.externalization > 0) - .toSorted((left, right) => right.scores.overall - left.scores.overall)[0]; + const strongestRoutingSummary = findStrongestSummary(summaries, isRoutingSummary); + const strongestIncidentSummary = findStrongestSummary( + summaries, + (summary) => summary.scores.incident > 0, + ); + const strongestExternalizationSummary = findStrongestSummary( + summaries, + (summary) => summary.scores.externalization > 0, + ); if (facts.length === 0 && monitoringSignal >= 3) { addReflection( diff --git a/extensions/qa-lab/src/character-eval.ts b/extensions/qa-lab/src/character-eval.ts index 2e62d4f40f4..a402d828812 100644 --- a/extensions/qa-lab/src/character-eval.ts +++ b/extensions/qa-lab/src/character-eval.ts @@ -207,12 +207,16 @@ async function mapWithConcurrency( } function extractTranscript(result: QaSuiteResult) { - const details = result.scenarios.flatMap((scenario) => - scenario.steps - .map((step) => step.details) - .filter((detail): detail is string => Boolean(detail)), - ); - return details.toSorted((left, right) => right.length - left.length)[0] ?? result.report; + let longestDetail: string | undefined; + for (const scenario of result.scenarios) { + for (const step of scenario.steps) { + const detail = step.details; + if (detail && (!longestDetail || detail.length > longestDetail.length)) { + longestDetail = detail; + } + } + } + return longestDetail ?? result.report; } function collectTranscriptStats(transcript: string) { diff --git a/src/config/sessions/store-maintenance.ts b/src/config/sessions/store-maintenance.ts index 37ec6147278..0ac73403c40 100644 --- a/src/config/sessions/store-maintenance.ts +++ b/src/config/sessions/store-maintenance.ts @@ -208,12 +208,13 @@ export function getActiveSessionMaintenanceWarning(params: { const cutoffMs = now - params.pruneAfterMs; const wouldPrune = activeEntry.updatedAt != null ? activeEntry.updatedAt < cutoffMs : false; const keys = Object.keys(params.store); - const wouldCap = - keys.length > params.maxEntries && - keys - .toSorted((a, b) => getEntryUpdatedAt(params.store[b]) - getEntryUpdatedAt(params.store[a])) - .slice(params.maxEntries) - .includes(activeSessionKey); + const wouldCap = wouldCapActiveSession({ + store: params.store, + keys, + activeEntry, + activeSessionKey, + maxEntries: params.maxEntries, + }); if (!wouldPrune && !wouldCap) { return null; @@ -230,6 +231,40 @@ export function getActiveSessionMaintenanceWarning(params: { }; } +function wouldCapActiveSession(params: { + store: Record; + keys: string[]; + activeEntry: SessionEntry; + activeSessionKey: string; + maxEntries: number; +}): boolean { + if (params.keys.length <= params.maxEntries) { + return false; + } + if (params.maxEntries <= 0) { + return true; + } + + const activeUpdatedAt = getEntryUpdatedAt(params.activeEntry); + let newerOrTieBeforeActive = 0; + let seenActive = false; + for (const key of params.keys) { + if (key === params.activeSessionKey) { + seenActive = true; + continue; + } + const entryUpdatedAt = getEntryUpdatedAt(params.store[key]); + if (entryUpdatedAt > activeUpdatedAt || (!seenActive && entryUpdatedAt === activeUpdatedAt)) { + newerOrTieBeforeActive++; + if (newerOrTieBeforeActive >= params.maxEntries) { + return true; + } + } + } + + return false; +} + /** * Cap the store to the N most recently updated entries. * Entries without `updatedAt` are sorted last (removed first when over limit). diff --git a/src/config/sessions/store.pruning.test.ts b/src/config/sessions/store.pruning.test.ts index bcf0f07b59e..cd01ca4fd52 100644 --- a/src/config/sessions/store.pruning.test.ts +++ b/src/config/sessions/store.pruning.test.ts @@ -3,7 +3,12 @@ import fs from "node:fs/promises"; import path from "node:path"; import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { createFixtureSuite } from "../../test-utils/fixture-suite.js"; -import { capEntryCount, pruneStaleEntries, rotateSessionFile } from "./store.js"; +import { + capEntryCount, + getActiveSessionMaintenanceWarning, + pruneStaleEntries, + rotateSessionFile, +} from "./store.js"; import type { SessionEntry } from "./types.js"; const DAY_MS = 24 * 60 * 60 * 1000; @@ -70,6 +75,48 @@ describe("capEntryCount", () => { }); }); +describe("getActiveSessionMaintenanceWarning", () => { + it("warns when the active session is outside the retained recent entries", () => { + const now = Date.now(); + const store = makeStore([ + ["newest", makeEntry(now)], + ["recent", makeEntry(now - 1)], + ["active", makeEntry(now - 2)], + ["old", makeEntry(now - 3)], + ]); + + const warning = getActiveSessionMaintenanceWarning({ + store, + activeSessionKey: "active", + pruneAfterMs: DAY_MS, + maxEntries: 2, + nowMs: now, + }); + + expect(warning?.wouldCap).toBe(true); + expect(warning?.wouldPrune).toBe(false); + }); + + it("preserves insertion order tie behavior from stable sorting", () => { + const now = Date.now(); + const store = makeStore([ + ["same-before", makeEntry(now)], + ["active", makeEntry(now)], + ["same-after", makeEntry(now)], + ]); + + const warning = getActiveSessionMaintenanceWarning({ + store, + activeSessionKey: "active", + pruneAfterMs: DAY_MS, + maxEntries: 1, + nowMs: now, + }); + + expect(warning?.wouldCap).toBe(true); + }); +}); + describe("rotateSessionFile", () => { let testDir: string; let storePath: string; diff --git a/ui/src/ui/app-render.ts b/ui/src/ui/app-render.ts index ea0c4ecdd2c..1b1033e9afa 100644 --- a/ui/src/ui/app-render.ts +++ b/ui/src/ui/app-render.ts @@ -199,10 +199,15 @@ function resolveDreamingNextCycle( if (!status?.phases) { return null; } - const nextRunAtMs = Object.values(status.phases) - .filter((phase) => phase.enabled && typeof phase.nextRunAtMs === "number") - .map((phase) => phase.nextRunAtMs as number) - .toSorted((a, b) => a - b)[0]; + let nextRunAtMs: number | undefined; + for (const phase of Object.values(status.phases)) { + if (!phase.enabled || typeof phase.nextRunAtMs !== "number") { + continue; + } + if (nextRunAtMs === undefined || phase.nextRunAtMs < nextRunAtMs) { + nextRunAtMs = phase.nextRunAtMs; + } + } return formatDreamNextCycle(nextRunAtMs); } diff --git a/ui/src/ui/views/usage-metrics.ts b/ui/src/ui/views/usage-metrics.ts index 1d11609ccf4..27c0ebd7c31 100644 --- a/ui/src/ui/views/usage-metrics.ts +++ b/ui/src/ui/views/usage-metrics.ts @@ -579,15 +579,25 @@ const buildUsageInsightStats = ( const errorRate = aggregates.messages.total ? aggregates.messages.errors / aggregates.messages.total : 0; - const peakErrorDay = aggregates.daily - .filter((day) => day.messages > 0 && day.errors > 0) - .map((day) => ({ + let peakErrorDay: UsageInsightStats["peakErrorDay"]; + for (const day of aggregates.daily) { + if (day.messages <= 0 || day.errors <= 0) { + continue; + } + const candidate = { date: day.date, errors: day.errors, messages: day.messages, rate: day.errors / day.messages, - })) - .toSorted((a, b) => b.rate - a.rate || b.errors - a.errors)[0]; + }; + if ( + !peakErrorDay || + candidate.rate > peakErrorDay.rate || + (candidate.rate === peakErrorDay.rate && candidate.errors > peakErrorDay.errors) + ) { + peakErrorDay = candidate; + } + } return { durationSumMs,