feat: publish HoloLake model-native living system source
This commit is contained in:
parent
6ad10edde1
commit
c395dd3a99
2467 changed files with 615073 additions and 0 deletions
113
product-source/hololake-platform/src/hooks/useMcpBridge.ts
Normal file
113
product-source/hololake-platform/src/hooks/useMcpBridge.ts
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
/**
|
||||
* Hook for communicating with the Tolaria MCP WebSocket bridge.
|
||||
*
|
||||
* Provides typed tool invocations for vault operations:
|
||||
* - readNote, createNote, searchNotes, appendToNote
|
||||
*
|
||||
* Connection is lazy — only opens when first tool is called.
|
||||
*/
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
|
||||
const DEFAULT_WS_URL = 'ws://localhost:9710'
|
||||
|
||||
interface PendingRequest {
|
||||
resolve: (value: unknown) => void
|
||||
reject: (reason: unknown) => void
|
||||
}
|
||||
|
||||
interface SearchResult {
|
||||
path: string
|
||||
title: string
|
||||
snippet: string
|
||||
}
|
||||
|
||||
export function useMcpBridge(wsUrl = DEFAULT_WS_URL) {
|
||||
const wsRef = useRef<WebSocket | null>(null)
|
||||
const pendingRef = useRef<Map<string, PendingRequest>>(new Map())
|
||||
const idCounterRef = useRef(0)
|
||||
const [connected, setConnected] = useState(false)
|
||||
|
||||
const ensureConnection = useCallback((): Promise<WebSocket> => {
|
||||
const ws = wsRef.current
|
||||
if (ws && ws.readyState === WebSocket.OPEN) return Promise.resolve(ws)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const newWs = new WebSocket(wsUrl)
|
||||
|
||||
newWs.onopen = () => {
|
||||
wsRef.current = newWs
|
||||
setConnected(true)
|
||||
resolve(newWs)
|
||||
}
|
||||
|
||||
newWs.onmessage = (event) => {
|
||||
try {
|
||||
const msg = JSON.parse(event.data)
|
||||
const pending = pendingRef.current.get(msg.id)
|
||||
if (pending) {
|
||||
pendingRef.current.delete(msg.id)
|
||||
if (msg.error) {
|
||||
pending.reject(new Error(msg.error))
|
||||
} else {
|
||||
pending.resolve(msg.result)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore malformed messages
|
||||
}
|
||||
}
|
||||
|
||||
newWs.onclose = () => {
|
||||
wsRef.current = null
|
||||
setConnected(false)
|
||||
}
|
||||
|
||||
newWs.onerror = () => {
|
||||
reject(new Error('WebSocket connection failed'))
|
||||
}
|
||||
})
|
||||
}, [wsUrl])
|
||||
|
||||
const callTool = useCallback(async <T>(tool: string, args: Record<string, unknown>): Promise<T> => {
|
||||
const ws = await ensureConnection()
|
||||
const id = `mcp-${++idCounterRef.current}`
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
pendingRef.current.set(id, { resolve: resolve as (value: unknown) => void, reject })
|
||||
ws.send(JSON.stringify({ id, tool, args }))
|
||||
|
||||
// Timeout after 30 seconds
|
||||
setTimeout(() => {
|
||||
if (pendingRef.current.has(id)) {
|
||||
pendingRef.current.delete(id)
|
||||
reject(new Error('MCP tool call timed out'))
|
||||
}
|
||||
}, 30_000)
|
||||
})
|
||||
}, [ensureConnection])
|
||||
|
||||
const readNote = useCallback(
|
||||
(path: string) => callTool<{ content: string }>('read_note', { path }),
|
||||
[callTool],
|
||||
)
|
||||
|
||||
const createNote = useCallback(
|
||||
(path: string, title: string, isA?: string) =>
|
||||
callTool<string>('create_note', { path, title, is_a: isA }),
|
||||
[callTool],
|
||||
)
|
||||
|
||||
const searchNotes = useCallback(
|
||||
(query: string, limit?: number) =>
|
||||
callTool<SearchResult[]>('search_notes', { query, limit }),
|
||||
[callTool],
|
||||
)
|
||||
|
||||
const appendToNote = useCallback(
|
||||
(path: string, text: string) =>
|
||||
callTool<{ ok: boolean }>('append_to_note', { path, text }),
|
||||
[callTool],
|
||||
)
|
||||
|
||||
return { connected, readNote, createNote, searchNotes, appendToNote, callTool }
|
||||
}
|
||||
Loading…
Reference in a new issue