feat: add AIUI node capabilities — file reading, log tailing, bitcoin/lnd deep data
Add readFileAsText() to filebrowser client, read-file and tail-logs action handlers to context broker, bitcoin.getinfo and lnd.getinfo RPC enrichment for context categories, and update AIUI protocol types. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
11cee9dc70
commit
1bb72dc87e
@@ -177,6 +177,14 @@ export class ContextBroker {
|
||||
error = 'Missing query parameter'
|
||||
break
|
||||
|
||||
case 'read-file':
|
||||
this.handleReadFileAction(id, params.path)
|
||||
return
|
||||
|
||||
case 'tail-logs':
|
||||
this.handleTailLogsAction(id, params.appId, params.lines)
|
||||
return
|
||||
|
||||
default:
|
||||
error = `Unknown action: ${action}`
|
||||
}
|
||||
@@ -334,8 +342,8 @@ export class ContextBroker {
|
||||
}
|
||||
}
|
||||
|
||||
// T7: Bitcoin status from bundled app
|
||||
private sanitizeBitcoin(store: ReturnType<typeof useAppStore>): unknown {
|
||||
// T7: Bitcoin status + deep data from backend RPC
|
||||
private async sanitizeBitcoin(store: ReturnType<typeof useAppStore>): Promise<unknown> {
|
||||
const packages = store.packages || {}
|
||||
const containerStore = useContainerStore()
|
||||
|
||||
@@ -351,10 +359,19 @@ export class ContextBroker {
|
||||
return { available: false, message: 'Bitcoin Core not running' }
|
||||
}
|
||||
|
||||
return {
|
||||
available: true,
|
||||
status: 'running',
|
||||
network: 'mainnet',
|
||||
try {
|
||||
const info = await rpcClient.call<{
|
||||
block_height: number
|
||||
sync_progress: number
|
||||
chain: string
|
||||
difficulty: number
|
||||
mempool_size: number
|
||||
mempool_tx_count: number
|
||||
verification_progress: number
|
||||
}>({ method: 'bitcoin.getinfo' })
|
||||
return { available: true, status: 'running', ...info }
|
||||
} catch {
|
||||
return { available: true, status: 'running', network: 'mainnet' }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,8 +454,8 @@ export class ContextBroker {
|
||||
}
|
||||
}
|
||||
|
||||
// T12: Wallet — LND aggregate data
|
||||
private sanitizeWallet(store: ReturnType<typeof useAppStore>): unknown {
|
||||
// T12: Wallet — LND deep data from backend RPC
|
||||
private async sanitizeWallet(store: ReturnType<typeof useAppStore>): Promise<unknown> {
|
||||
const packages = store.packages || {}
|
||||
const containerStore = useContainerStore()
|
||||
|
||||
@@ -454,10 +471,20 @@ export class ContextBroker {
|
||||
return { available: false, message: 'Lightning (LND) not running' }
|
||||
}
|
||||
|
||||
return {
|
||||
available: true,
|
||||
status: 'running',
|
||||
message: 'LND is running. Balance details require backend wallet RPC.',
|
||||
try {
|
||||
const info = await rpcClient.call<{
|
||||
alias: string
|
||||
num_active_channels: number
|
||||
num_peers: number
|
||||
synced_to_chain: boolean
|
||||
block_height: number
|
||||
balance_sats: number
|
||||
channel_balance_sats: number
|
||||
pending_open_balance: number
|
||||
}>({ method: 'lnd.getinfo' })
|
||||
return { available: true, status: 'running', ...info }
|
||||
} catch {
|
||||
return { available: true, status: 'running', message: 'LND is running but detailed info unavailable' }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,6 +497,59 @@ export class ContextBroker {
|
||||
}
|
||||
}
|
||||
|
||||
private async handleReadFileAction(id: string, path?: string) {
|
||||
const perms = useAIPermissionsStore()
|
||||
if (!perms.isEnabled('files')) {
|
||||
this.postToIframe({ type: 'action:response', id, success: false, error: 'File access not permitted' } satisfies ArchyActionResponse)
|
||||
return
|
||||
}
|
||||
if (!path) {
|
||||
this.postToIframe({ type: 'action:response', id, success: false, error: 'Missing path parameter' } satisfies ArchyActionResponse)
|
||||
return
|
||||
}
|
||||
try {
|
||||
if (!fileBrowserClient.isAuthenticated) {
|
||||
const ok = await fileBrowserClient.login()
|
||||
if (!ok) throw new Error('FileBrowser authentication failed')
|
||||
}
|
||||
const result = await fileBrowserClient.readFileAsText(path)
|
||||
this.postToIframe({
|
||||
type: 'action:response', id, success: true,
|
||||
data: { content: result.content, truncated: result.truncated, size: result.size, path },
|
||||
} as ArchyActionResponse)
|
||||
} catch (err) {
|
||||
this.postToIframe({
|
||||
type: 'action:response', id, success: false,
|
||||
error: err instanceof Error ? err.message : 'Failed to read file',
|
||||
} satisfies ArchyActionResponse)
|
||||
}
|
||||
}
|
||||
|
||||
private async handleTailLogsAction(id: string, appId?: string, linesStr?: string) {
|
||||
const perms = useAIPermissionsStore()
|
||||
if (!perms.isEnabled('apps')) {
|
||||
this.postToIframe({ type: 'action:response', id, success: false, error: 'App access not permitted' } satisfies ArchyActionResponse)
|
||||
return
|
||||
}
|
||||
if (!appId) {
|
||||
this.postToIframe({ type: 'action:response', id, success: false, error: 'Missing appId parameter' } satisfies ArchyActionResponse)
|
||||
return
|
||||
}
|
||||
const lines = Math.min(parseInt(linesStr || '50', 10) || 50, 200)
|
||||
try {
|
||||
const logs = await rpcClient.call<string[]>({ method: 'container-logs', params: { app_id: appId, lines } })
|
||||
this.postToIframe({
|
||||
type: 'action:response', id, success: true,
|
||||
data: { appId, lines: logs, count: logs.length },
|
||||
} as ArchyActionResponse)
|
||||
} catch (err) {
|
||||
this.postToIframe({
|
||||
type: 'action:response', id, success: false,
|
||||
error: err instanceof Error ? err.message : 'Failed to fetch logs',
|
||||
} satisfies ArchyActionResponse)
|
||||
}
|
||||
}
|
||||
|
||||
private postToIframe(msg: ArchyResponse) {
|
||||
if (!this.iframe.value?.contentWindow) return
|
||||
this.iframe.value.contentWindow.postMessage(msg, this.allowedOrigin)
|
||||
|
||||
Reference in New Issue
Block a user