Skip to content

Commit 9ff8f6e

Browse files
committed
release: v2.7.231 — mcp search always-on/keep-warm UX + web compile fixes
1 parent 8cfc151 commit 9ff8f6e

File tree

6 files changed

+42
-20
lines changed

6 files changed

+42
-20
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@
44

55
All notable changes to this project will be documented in this file.
66

7+
## [2.7.231] — 2026-03-15
8+
9+
- changed(web/mcp-search): `apps/web/src/app/dashboard/mcp/search/page.tsx` now clearly separates **server always-on** tool inventory from **keep warm** (always-loaded preference) inventory, with corrected summary counts and clearer UI wording.
10+
- changed(web/mcp-search): updated keep-warm toggle copy/ARIA labels to avoid confusing keep-warm preferences with server-level always-on advertisement.
11+
- fix(web/session): repaired `apps/web/src/app/dashboard/session/session-page-normalizers.ts` after a merge corruption by restoring `isolateWorktree`, `lastExitCode`, and `lastExitSignal` inside `normalizeSessionList(...)` and removing dangling duplicate fragments that broke TypeScript parsing.
12+
- fix(web/sidebar): `apps/web/src/components/Sidebar.tsx` now declares memoized nav dependency sets before first use and uses safe non-null narrowing for favorites/recent item projection, resolving web typecheck blockers.
13+
- test(web): reran focused suites (`session-page-normalizers`, `mcp/tools-page-normalizers`, `mcp/nav-validation`) with `41` tests passing; web `tsc --noEmit` passes.
14+
715
## [2.7.230] — 2026-03-15
816

917
- fix(core/providers): `CoreModelSelector` now overrides `getDepletedModels()` to read from `candidateCooldowns` (per-model transient backoffs) and `NormalizedQuotaService.getAllQuotas()` (provider-wide quota states) — the billing dashboard "Blocked / Cooling-Down Models" card now populates when providers are rate-limited or quota-exhausted instead of always showing empty.

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.7.230
1+
2.7.231

VERSION.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
# Borg Project Version: 2.7.230
1+
# Borg Project Version: 2.7.231

apps/web/src/app/dashboard/mcp/search/page.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -800,10 +800,15 @@ export default function SearchDashboard() {
800800
<div className="text-xs uppercase tracking-wider text-zinc-500">Hydrated schemas</div>
801801
<div className="mt-1 text-2xl font-semibold text-white">{hydratedCount}</div>
802802
</div>
803-
<div className="rounded-lg border border-zinc-800 bg-zinc-950/70 p-3 md:col-span-3">
803+
<div className="rounded-lg border border-zinc-800 bg-zinc-950/70 p-3">
804804
<div className="text-xs uppercase tracking-wider text-zinc-500">Always-on tools</div>
805+
<div className="mt-1 text-2xl font-semibold text-white">{alwaysOnAdvertisedNames.size}</div>
806+
<div className="mt-1 text-xs text-zinc-500">Advertised immediately by always-on MCP servers.</div>
807+
</div>
808+
<div className="rounded-lg border border-zinc-800 bg-zinc-950/70 p-3">
809+
<div className="text-xs uppercase tracking-wider text-zinc-500">Keep warm tools</div>
805810
<div className="mt-1 text-2xl font-semibold text-white">{alwaysLoadedTools.size}</div>
806-
<div className="mt-1 text-xs text-zinc-500">Pinned warm tools auto-load into the session working set when MCP state refreshes.</div>
811+
<div className="mt-1 text-xs text-zinc-500">Pinned tools Borg auto-loads into the working set.</div>
807812
</div>
808813
<div className="rounded-lg border border-zinc-800 bg-zinc-950/70 p-3 md:col-span-3 space-y-3">
809814
<div className="flex items-center justify-between gap-3">
@@ -994,11 +999,11 @@ export default function SearchDashboard() {
994999
onClick={() => toggleAlwaysLoaded(tool.name)}
9951000
disabled={setPreferencesMutation.isPending}
9961001
title="Keep this tool warm so it auto-loads into the active working set"
997-
aria-label={`${(tool.alwaysLoaded || alwaysLoadedTools.has(tool.name)) ? 'Stop keeping' : 'Keep'} tool ${tool.name} always loaded`}
1002+
aria-label={`${(tool.alwaysLoaded || alwaysLoadedTools.has(tool.name)) ? 'Stop keeping' : 'Keep'} tool ${tool.name} warm`}
9981003
variant="outline"
9991004
className="border-cyan-700 text-cyan-200 hover:bg-cyan-950/30"
10001005
>
1001-
{(tool.alwaysLoaded || alwaysLoadedTools.has(tool.name)) ? 'Disable always-on' : 'Keep warm'}
1006+
{(tool.alwaysLoaded || alwaysLoadedTools.has(tool.name)) ? 'Disable keep warm' : 'Keep warm'}
10021007
</Button>
10031008
<Button
10041009
onClick={() => callToolMutation.mutate({ name: tool.name, args: {} })}

apps/web/src/app/dashboard/session/session-page-normalizers.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ export interface NormalizedSessionRow {
7272
scheduledRestartAt?: number;
7373
lastActivityAt: number;
7474
lastError?: string;
75+
/** Worktree isolation flag surfaced from the supervisor snapshot. */
76+
isolateWorktree: boolean;
77+
/** Exit code of the last process run, when available. */
78+
lastExitCode?: number;
79+
/** Exit signal of the last process run (e.g. 'SIGTERM'), when available. */
80+
lastExitSignal?: string;
7581
logs: NormalizedSessionLog[];
7682
metadata?: {
7783
memoryBootstrap?: {
@@ -200,6 +206,11 @@ export const normalizeSessionList = (payload: unknown): NormalizedSessionRow[] =
200206
: undefined,
201207
lastActivityAt: asFiniteNumber(session.lastActivityAt, Date.now()),
202208
lastError: asOptionalTrimmedString(session.lastError),
209+
isolateWorktree: asBoolean(session.isolateWorktree, false),
210+
lastExitCode: typeof session.lastExitCode === 'number' && Number.isFinite(session.lastExitCode)
211+
? session.lastExitCode
212+
: undefined,
213+
lastExitSignal: asOptionalTrimmedString(session.lastExitSignal),
203214
logs,
204215
metadata: session.metadata && typeof session.metadata === 'object'
205216
? (session.metadata as NormalizedSessionRow['metadata'])

apps/web/src/components/Sidebar.tsx

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,16 @@ export function Sidebar({ className }: SidebarProps) {
124124
const searchInputRef = useRef<HTMLInputElement | null>(null);
125125
const paletteInputRef = useRef<HTMLInputElement | null>(null);
126126

127+
const normalizedPathname = normalizeNavHref(pathname);
128+
const normalizedQuery = query.trim().toLowerCase();
129+
130+
const navDiagnostics = useMemo(() => validateSidebarSections(SIDEBAR_SECTIONS), []);
131+
const sectionTitles = useMemo(() => new Set(SIDEBAR_SECTIONS.map((section) => section.title)), []);
132+
133+
const allItemsByHref = useMemo(() => buildNavItemsByNormalizedHref(SIDEBAR_SECTIONS), []);
134+
const allowedNavHrefs = useMemo(() => new Set(allItemsByHref.keys()), [allItemsByHref]);
135+
const favoriteSet = useMemo(() => new Set(favorites), [favorites]);
136+
127137
useEffect(() => {
128138
const parsed = safeStorageGetJson<unknown>(SIDEBAR_COLLAPSE_STORAGE_KEY);
129139
if (parsed === null) {
@@ -160,20 +170,10 @@ export function Sidebar({ className }: SidebarProps) {
160170
setRecentSearches(sanitizeRecentSearches(parsed, MAX_RECENT_SEARCHES));
161171
}, []);
162172

163-
const normalizedPathname = normalizeNavHref(pathname);
164-
165173
const isActive = (href: string) => {
166174
return isNavHrefActive(normalizedPathname, href);
167175
};
168176

169-
const normalizedQuery = query.trim().toLowerCase();
170-
171-
const navDiagnostics = useMemo(() => validateSidebarSections(SIDEBAR_SECTIONS), []);
172-
const sectionTitles = useMemo(() => new Set(SIDEBAR_SECTIONS.map((section) => section.title)), []);
173-
174-
const allItemsByHref = useMemo(() => buildNavItemsByNormalizedHref(SIDEBAR_SECTIONS), []);
175-
const allowedNavHrefs = useMemo(() => new Set(allItemsByHref.keys()), [allItemsByHref]);
176-
177177
const filteredSections = useMemo(() => {
178178
return SIDEBAR_SECTIONS
179179
.map((section) => ({
@@ -183,8 +183,6 @@ export function Sidebar({ className }: SidebarProps) {
183183
.filter((section) => section.items.length > 0);
184184
}, [normalizedQuery]);
185185

186-
const favoriteSet = useMemo(() => new Set(favorites), [favorites]);
187-
188186
const paletteItems = useMemo<PaletteItem[]>(() => {
189187
const routeMeta = new Map<string, { title: string; href: string; icon: any; description?: string; section: string }>();
190188
for (const section of SIDEBAR_SECTIONS) {
@@ -263,7 +261,7 @@ export function Sidebar({ className }: SidebarProps) {
263261
const favoriteItems = useMemo(() => {
264262
return favorites
265263
.map((href) => allItemsByHref.get(href))
266-
.filter((item): item is { title: string; href: string; icon: any; description?: string } => Boolean(item))
264+
.filter((item): item is NonNullable<typeof item> => item != null)
267265
.filter((item) => {
268266
return matchesNavQuery(normalizedQuery, item, 'Favorites');
269267
});
@@ -272,7 +270,7 @@ export function Sidebar({ className }: SidebarProps) {
272270
const recentItems = useMemo(() => {
273271
return recentRoutes
274272
.map((href) => allItemsByHref.get(href))
275-
.filter((item): item is { title: string; href: string; icon: any; description?: string } => Boolean(item))
273+
.filter((item): item is NonNullable<typeof item> => item != null)
276274
.filter((item) => {
277275
return matchesNavQuery(normalizedQuery, item, 'Recent');
278276
});

0 commit comments

Comments
 (0)