feat: add frosted glass appearance and improve local MCP recovery

Add an opt-in persistent frosted glass setting for the top bar and runtime checklist, with chat scrolling behind the header while preserving scroll anchors and window controls.

Retry transient local MCP initialization failures once after cleanup, include redacted connection diagnostics in feedback, and index lifecycle events to reduce execution statistics scans.

Release note: Add optional frosted glass appearance with chat visible behind the title bar; improve local tool connection recovery and diagnostic feedback.
This commit is contained in:
mesalogo
2026-09-30 23:37:26 +08:00
parent e4f7b8d993
commit f7f501a8e3
39 changed files with 1666 additions and 251 deletions
+126 -71
View File
@@ -1,4 +1,5 @@
import { randomBytes } from 'node:crypto'
import { createHash, randomBytes } from 'node:crypto'
import type { DesktopDiagnosticFailureObserver, DesktopMcpDiagnosticMetadata } from '../desktop-diagnostics'
import { isStoryGraphTool, storyGraphToolNames, type StoryGraphToolName } from '../../shared/story-graph-tools'
import type { RuntimeTarget } from '../../shared/capability-contracts'
@@ -256,6 +257,7 @@ type DownstreamMcpSession = {
}
export type KnowledgeMcpGatewayOptions = {
observeFailure?: DesktopDiagnosticFailureObserver
storyGraphService?: StoryGraphService
maximumBodyBytes?: number
magicNotesDatabase?: MagicNotesDatabase
@@ -401,6 +403,7 @@ export class KnowledgeMcpGateway {
): BrowserTabUsageLease
}
private readonly launchEnvironmentProvider?: LaunchEnvironmentProvider
private readonly observeFailure?: DesktopDiagnosticFailureObserver
private server?: Server
private endpoint?: string
@@ -416,6 +419,18 @@ export class KnowledgeMcpGateway {
this.obsidianService = options.obsidianService
this.browserService = options.browserService
this.launchEnvironmentProvider = options.launchEnvironmentProvider
this.observeFailure = options.observeFailure
}
private observeMcpFailure(mcp: DesktopMcpDiagnosticMetadata): void {
try {
this.observeFailure?.({
component: 'runtime', stage: 'connect', code: 'runtime.mcp.failed',
error: new Error('Local MCP request failed'), mcp
})
} catch {
// Diagnostics must not change the HTTP response or session lifecycle.
}
}
async start(): Promise<void> {
@@ -423,7 +438,23 @@ export class KnowledgeMcpGateway {
return
}
const server = createServer((request, response) => {
void this.handleRequest(request, response).catch(() => {
const context = { phase: 'request' as DesktopMcpDiagnosticMetadata['phase'], startedAt: performance.now(), correlationId: undefined as string | undefined }
let failed = false
response.once('finish', () => {
if (!failed && response.statusCode >= 400) {
this.observeMcpFailure({
phase: context.phase, category: 'http-rejection', status: response.statusCode,
elapsedMs: Math.round(performance.now() - context.startedAt), correlationId: context.correlationId
})
}
})
void this.handleRequest(request, response, context).catch(() => {
failed = true
this.observeMcpFailure({
phase: context.phase, category: 'handler-failure',
...(response.headersSent ? {} : { status: 500 }),
elapsedMs: Math.round(performance.now() - context.startedAt), correlationId: context.correlationId
})
sendJson(response, 500, {
jsonrpc: '2.0',
error: { code: -32603, message: 'Internal server error' },
@@ -1683,76 +1714,86 @@ export class KnowledgeMcpGateway {
mcp.setRequestHandler(
ListToolsRequestSchema,
async (_request, extra) => {
const customBindings = await this.getCustomMcpBindings(
token,
extra.signal
)
const storyGraphAvailable = await this.isStoryGraphAvailable(token)
const scopedTools = [...availableTools].filter(name => !name.startsWith('story_graph_') || storyGraphAvailable).flatMap(
(name): Tool[] => {
const definition = scopedDataToolByName.get(
name as ScopedDataToolName
)
if (!definition) {
return []
}
const inputSchema = z.toJSONSchema(
definition.inputSchema,
{ target: 'draft-7' }
) as Tool['inputSchema'] & { $schema?: string }
Reflect.deleteProperty(inputSchema, '$schema')
return [
{
name,
title: definition.title,
description: definition.description,
inputSchema,
annotations: {
readOnlyHint: definition.access === 'read',
destructiveHint:
name === 'goodbuddy_config_apply' ||
name === 'note_delete' ||
name === 'note_entry_delete'
}
const startedAt = performance.now()
try {
const customBindings = await this.getCustomMcpBindings(
token,
extra.signal
)
const storyGraphAvailable = await this.isStoryGraphAvailable(token)
const scopedTools = [...availableTools].filter(name => !name.startsWith('story_graph_') || storyGraphAvailable).flatMap(
(name): Tool[] => {
const definition = scopedDataToolByName.get(
name as ScopedDataToolName
)
if (!definition) {
return []
}
const inputSchema = z.toJSONSchema(
definition.inputSchema,
{ target: 'draft-7' }
) as Tool['inputSchema'] & { $schema?: string }
Reflect.deleteProperty(inputSchema, '$schema')
return [
{
name,
title: definition.title,
description: definition.description,
inputSchema,
annotations: {
readOnlyHint: definition.access === 'read',
destructiveHint:
name === 'goodbuddy_config_apply' ||
name === 'note_delete' ||
name === 'note_entry_delete'
}
}
]
}
)
const capability = this.getCapability(token)
const imageTool = await imageToolDefinition(capability.imageToolBinding)
const browserTools = capability.browserConversationId && capability.browserTabId
? new BrowserModelTools({
service: this.browserService!,
conversationId: capability.browserConversationId,
browserTabId: capability.browserTabId
})
: undefined
const browserDefinitions = browserTools
? browserTools.listTools().map(
(definition): Tool => ({
name: definition.name,
title: definition.displayName,
description: definition.description,
inputSchema: definition.inputSchema as Tool['inputSchema'],
annotations: {
readOnlyHint:
definition.name === 'browser_snapshot' ||
definition.name === 'browser_screenshot',
destructiveHint: false
}
})
)
: []
session.listedTools = true
return {
tools: [
...(imageTool ? [{ name: imageTool.name, description: imageTool.description, inputSchema: imageTool.inputSchema as Tool['inputSchema'], annotations: { readOnlyHint: false, destructiveHint: false } }] : []),
...scopedTools,
...browserDefinitions,
...[...customBindings.values()].map(
(binding) => binding.exposedTool
)
]
}
)
const capability = this.getCapability(token)
const imageTool = await imageToolDefinition(capability.imageToolBinding)
const browserTools = capability.browserConversationId && capability.browserTabId
? new BrowserModelTools({
service: this.browserService!,
conversationId: capability.browserConversationId,
browserTabId: capability.browserTabId
})
: undefined
const browserDefinitions = browserTools
? browserTools.listTools().map(
(definition): Tool => ({
name: definition.name,
title: definition.displayName,
description: definition.description,
inputSchema: definition.inputSchema as Tool['inputSchema'],
annotations: {
readOnlyHint:
definition.name === 'browser_snapshot' ||
definition.name === 'browser_screenshot',
destructiveHint: false
}
})
)
: []
session.listedTools = true
return {
tools: [
...(imageTool ? [{ name: imageTool.name, description: imageTool.description, inputSchema: imageTool.inputSchema as Tool['inputSchema'], annotations: { readOnlyHint: false, destructiveHint: false } }] : []),
...scopedTools,
...browserDefinitions,
...[...customBindings.values()].map(
(binding) => binding.exposedTool
)
]
} catch (error) {
this.observeMcpFailure({
phase: 'tool-discovery', category: 'handler-failure',
elapsedMs: Math.round(performance.now() - startedAt),
correlationId: `sha256:${createHash('sha256').update(token).digest('hex')}`
})
throw error
}
}
)
@@ -1921,7 +1962,8 @@ export class KnowledgeMcpGateway {
private async handleRequest(
request: IncomingMessage,
response: ServerResponse
response: ServerResponse,
context: { phase: DesktopMcpDiagnosticMetadata['phase']; startedAt: number; correlationId?: string }
): Promise<void> {
if (request.url !== '/mcp') {
sendJson(response, 404, { error: 'Not found' })
@@ -1940,6 +1982,8 @@ export class KnowledgeMcpGateway {
})
return
}
context.phase = 'authentication'
context.startedAt = performance.now()
const authorization = request.headers.authorization
if (
typeof authorization !== 'string' ||
@@ -1949,6 +1993,7 @@ export class KnowledgeMcpGateway {
return
}
const token = authorization.slice('Bearer '.length)
context.correlationId = `sha256:${createHash('sha256').update(token).digest('hex')}`
try {
this.getCapability(token)
} catch {
@@ -1956,6 +2001,8 @@ export class KnowledgeMcpGateway {
return
}
context.phase = 'request'
context.startedAt = performance.now()
let body: unknown
if (request.method === 'POST') {
try {
@@ -1971,6 +2018,8 @@ export class KnowledgeMcpGateway {
}
}
context.phase = 'session'
context.startedAt = performance.now()
const sessionId = request.headers['mcp-session-id']
let createdSession = false
let session =
@@ -2020,15 +2069,21 @@ export class KnowledgeMcpGateway {
const availableTools = new Set(
this.getAvailableToolNames(token)
)
context.phase = 'initialize'
context.startedAt = performance.now()
session = this.createDownstreamMcpSession(
token,
availableTools
)
createdSession = true
this.downstreamMcpSessions.set(session.registryKey, session)
await session.mcp.connect(session.transport)
}
try {
if (createdSession) await session.mcp.connect(session.transport)
if (body && typeof body === 'object' && 'method' in body && body.method === 'tools/list') {
context.phase = 'tool-discovery'
context.startedAt = performance.now()
}
await session.transport.handleRequest(request, response, body)
} finally {
if (createdSession && session.id === undefined) {