repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
The Living OS cockpit
stars
latest
clone command
git clone gitlawb://did:key:z6Mku78K...XywC/living-os-cockp...git clone gitlawb://did:key:z6Mku78K.../living-os-cockp...59751530feat: surface worker supervisor health in live work5h ago| #1 | import { spawn } from 'child_process'; |
| #2 | import { promises as fs } from 'fs'; |
| #3 | import { basename, join } from 'path'; |
| #4 | import { CANONICAL_KING_ID, type UserContext } from '@/lib/user-context'; |
| #5 | |
| #6 | export type ToolCall = { |
| #7 | tool: string; |
| #8 | parameters: Record<string, any>; |
| #9 | requiresConfirmation: boolean; |
| #10 | reason: string; |
| #11 | }; |
| #12 | |
| #13 | export type AgenticPlan = { |
| #14 | goal: string; |
| #15 | steps: Array<{ |
| #16 | id: string; |
| #17 | tool: string; |
| #18 | parameters: Record<string, any>; |
| #19 | why: string; |
| #20 | depends_on?: string[]; |
| #21 | blast_radius?: 'low' | 'medium' | 'high'; |
| #22 | }>; |
| #23 | risks: string[]; |
| #24 | blast_radius: 'low' | 'medium' | 'high'; |
| #25 | }; |
| #26 | |
| #27 | type PendingCall = ToolCall & { |
| #28 | id: string; |
| #29 | userId: string; |
| #30 | sessionId: string; |
| #31 | createdAt: string; |
| #32 | plan?: AgenticPlan; |
| #33 | }; |
| #34 | |
| #35 | const REPO_ROOT = '/home/kingbau/Documents/Aethon-Core'; |
| #36 | const AUDIT_DIR = join(REPO_ROOT, 'data/agentic_audit'); |
| #37 | const TASK_ROOT = process.env.AETHON_TASKS_DIR ?? '/home/kingbau/.config/aethon/tasks'; |
| #38 | const RUNNER = join(REPO_ROOT, 'scripts/agentic_tool_runner.py'); |
| #39 | |
| #40 | const pendingStore = (() => { |
| #41 | const g = globalThis as typeof globalThis & { __aethonPendingTools?: Map<string, PendingCall> }; |
| #42 | if (!g.__aethonPendingTools) g.__aethonPendingTools = new Map(); |
| #43 | return g.__aethonPendingTools; |
| #44 | })(); |
| #45 | |
| #46 | export function lastMessageFromBody(body: any): string { |
| #47 | if (typeof body?.message === 'string') return body.message; |
| #48 | const messages = Array.isArray(body?.messages) ? body.messages : []; |
| #49 | for (let i = messages.length - 1; i >= 0; i -= 1) { |
| #50 | if (messages[i]?.role === 'user') return String(messages[i]?.content ?? ''); |
| #51 | } |
| #52 | return ''; |
| #53 | } |
| #54 | |
| #55 | export function sessionIdFromBody(body: any): string { |
| #56 | return String(body?.session_id || body?.sessionId || 'default'); |
| #57 | } |
| #58 | |
| #59 | export function pendingKey(ctx: UserContext, sessionId: string) { |
| #60 | return `${ctx.canonicalMemberId}:${sessionId}`; |
| #61 | } |
| #62 | |
| #63 | export function isApprovalText(message: string) { |
| #64 | return /^(approve|approved|yes|run it|execute|confirm)\b/i.test(message.trim()); |
| #65 | } |
| #66 | |
| #67 | export function classifyToolIntent(message: string): ToolCall | null { |
| #68 | const text = message.trim(); |
| #69 | const lower = text.toLowerCase(); |
| #70 | |
| #71 | if (/\b(b2 delegation|delegation harness|delegate this|director harness|summarize.+\/tmp\/b2_summary\.md)\b/i.test(text)) { |
| #72 | const sourcePath = text.match(/(\/home\/kingbau\/[^\s"'`<>]+)/)?.[1] || '/home/kingbau/Documents/Aethon-Core/CLAUDE.md'; |
| #73 | const outputPath = text.match(/(\/tmp\/[^\s"'`<>]+)/)?.[1] || '/tmp/b2_summary.md'; |
| #74 | return { |
| #75 | tool: 'delegation_harness', |
| #76 | parameters: { |
| #77 | goal: text, |
| #78 | source_path: sourcePath, |
| #79 | output_path: outputPath, |
| #80 | }, |
| #81 | requiresConfirmation: false, |
| #82 | reason: 'This asks the delegation director to inspect the source and create the first per-action approval gate. It does not execute the write step.', |
| #83 | }; |
| #84 | } |
| #85 | |
| #86 | if (/\b(generate|make|create|build)\b/i.test(text) |
| #87 | && /\b(3d|three[- ]?d|glb|mesh|model)\b/i.test(text) |
| #88 | && /\b(aethon|avatar|full[- ]body)\b/i.test(text)) { |
| #89 | return { |
| #90 | tool: 'delegation_harness', |
| #91 | parameters: { |
| #92 | goal: text, |
| #93 | image_path: '/home/kingbau/living-os-cockpit/public/brand/Aethon_Full_Body_Avatar.png', |
| #94 | model: 'pixart', |
| #95 | }, |
| #96 | requiresConfirmation: false, |
| #97 | reason: 'This asks Aethon to plan the avatar 3D generation and open the per-action approval gate before GPU eviction or rendering.', |
| #98 | }; |
| #99 | } |
| #100 | |
| #101 | if (/\b(control computer|computer[- ]use|take a screenshot|bow screenshot|change root color|xvfb)\b/i.test(text)) { |
| #102 | const readOnly = /\b(screenshot|read screen|inspect|look)\b/i.test(text) && !/\b(change|click|type|open|write|run)\b/i.test(text); |
| #103 | return { |
| #104 | tool: 'control_computer', |
| #105 | parameters: { |
| #106 | scope: 'bow', |
| #107 | action: readOnly ? 'screenshot' : 'verified_smoke', |
| #108 | }, |
| #109 | requiresConfirmation: !readOnly, |
| #110 | reason: readOnly |
| #111 | ? 'This only captures a bow framebuffer screenshot for inspection.' |
| #112 | : 'This performs a bow computer-use action and verifies the visible effect. Mac control is dormant.', |
| #113 | }; |
| #114 | } |
| #115 | |
| #116 | if (/\b(t9|ugreen|nas|filesystem safe|rename files?|bad filenames?)\b/i.test(text)) { |
| #117 | const explicitPath = text.match(/(\/Volumes\/[^\s"'`<>]+|\/home\/kingbau\/[^\s"'`<>]+)/)?.[1]; |
| #118 | const apply = /\b(apply|execute|rename in place|run rename|perform rename)\b/i.test(text); |
| #119 | return { |
| #120 | tool: 'rename_filesystem_safe', |
| #121 | parameters: { |
| #122 | root_path: explicitPath || '/Volumes', |
| #123 | apply, |
| #124 | sample_limit: 20, |
| #125 | }, |
| #126 | requiresConfirmation: apply, |
| #127 | reason: apply |
| #128 | ? 'This will rename files in place so the NAS accepts them. Directory structure and mtimes are preserved.' |
| #129 | : 'This will scan for NAS-incompatible filenames and return a rename plan without changing files.', |
| #130 | }; |
| #131 | } |
| #132 | |
| #133 | // Whole-channel YouTube catalog scrape — matched BEFORE content_intake so |
| #134 | // "scrape the entire catalog from <channel>" routes to channel ingest, not a |
| #135 | // single-video clip job. |
| #136 | if (/\b(channel|catalog|catalogue|entire|whole|all (?:the )?videos)\b/i.test(text) |
| #137 | && /\b(scrape|ingest|catalog|catalogue|breakdown|knowledge)\b/i.test(text) |
| #138 | && /(youtube\.com\/(?:@|channel\/|c\/|user\/)|(?:^|\s)@[\w.-]{2,})/i.test(text)) { |
| #139 | const urlMatch = text.match(/https?:\/\/[^\s"'`<>]*youtube\.com\/[^\s"'`<>]+/i); |
| #140 | const handleMatch = text.match(/(?:^|\s)(@[\w.-]{2,})/); |
| #141 | const channel = (urlMatch?.[0] || handleMatch?.[1] || '').replace(/[),.]+$/, ''); |
| #142 | const limitMatch = text.match(/\bfirst\s+(\d{1,3})\b/i) || text.match(/\blimit\s+(?:to\s+)?(\d{1,3})\b/i); |
| #143 | const parameters: Record<string, unknown> = { channel, background: true }; |
| #144 | if (limitMatch) parameters.limit = Number(limitMatch[1]); |
| #145 | return { |
| #146 | tool: 'ingest_youtube_channel', |
| #147 | parameters, |
| #148 | requiresConfirmation: true, |
| #149 | reason: `This enumerates ${channel || 'the channel'}'s full video catalog and ingests each transcript into the vault as knowledge (resumable background job), then writes a learnings BREAKDOWN.md with GATED framework gap-fill suggestions (suggestions only — never auto-applied).`, |
| #150 | }; |
| #151 | } |
| #152 | |
| #153 | if (/\b(content[_ -]?intake|war room|clip bank|clip_log|script mode|article mode|run content)\b/i.test(text) |
| #154 | || (/\b(youtube|youtu\.be|tiktok|instagram|facebook|x\.com|twitter\.com)\b/i.test(text) && /\b(script|clip|article|transcribe|intake)\b/i.test(text))) { |
| #155 | const urls = Array.from(text.matchAll(/https?:\/\/[^\s"'`<>]+/gi)).map((match) => match[0].replace(/[),.]+$/, '')); |
| #156 | const paths = Array.from(text.matchAll(/\/home\/kingbau\/[^\s"'`<>]+/gi)).map((match) => match[0].replace(/[),.]+$/, '')); |
| #157 | const mode = /\bscript\b/i.test(text) |
| #158 | ? 'script' |
| #159 | : /\bclip[_ -]?log\b|\bclip bank\b|\bclips?\b/i.test(text) |
| #160 | ? 'clip_log' |
| #161 | : /\barticle\b|\bnews\b/i.test(text) |
| #162 | ? 'article' |
| #163 | : 'auto'; |
| #164 | const focus = text.match(/\bframe (?:it|this)?\s*(?:around|through|as)\s+(.+?)(?:\.|$)/i)?.[1]?.trim() |
| #165 | || text.match(/\bfocus(?: on)?:\s*(.+?)(?:\.|$)/i)?.[1]?.trim() |
| #166 | || ''; |
| #167 | return { |
| #168 | tool: 'content_intake', |
| #169 | parameters: { |
| #170 | input: [...urls, ...paths], |
| #171 | mode, |
| #172 | focus, |
| #173 | target_collection: 'clip_bank', |
| #174 | }, |
| #175 | requiresConfirmation: true, |
| #176 | reason: 'This will download or read media, transcribe it, index clip metadata, and write war-room outputs.', |
| #177 | }; |
| #178 | } |
| #179 | |
| #180 | if (/\b(search|show me|find)\b/i.test(text) && /\bclip bank|clips? in our bank|every clip\b/i.test(text)) { |
| #181 | const theme = text.match(/\btheme\s+([a-z0-9_-]+)/i)?.[1] || undefined; |
| #182 | return { |
| #183 | tool: 'search_clip_bank', |
| #184 | parameters: { |
| #185 | query: text.replace(/^(search|show me|find)\s+/i, '').trim(), |
| #186 | theme, |
| #187 | limit: 8, |
| #188 | }, |
| #189 | requiresConfirmation: false, |
| #190 | reason: 'This only searches stored clip metadata and transcript snippets.', |
| #191 | }; |
| #192 | } |
| #193 | |
| #194 | if (/\bingest\b/.test(lower) && /\b(book|books|document|documents|files?)\b/.test(lower)) { |
| #195 | const path = text.match(/(\/home\/kingbau\/[^\s"'`]+)/)?.[1]; |
| #196 | const collection = text.match(/\binto\s+the\s+([a-z0-9_-]+)\s+collection/i)?.[1] |
| #197 | || text.match(/\bcategory\s+([a-z0-9_-]+)/i)?.[1] |
| #198 | || basename(path || '').replace(/[^a-z0-9_-]/gi, '').toLowerCase() |
| #199 | || 'general'; |
| #200 | return { |
| #201 | tool: 'ingest_documents', |
| #202 | parameters: { |
| #203 | path_or_paths: path ? [path] : [`/home/kingbau/ingest_queue/${collection}`], |
| #204 | category: collection, |
| #205 | tag: collection, |
| #206 | }, |
| #207 | requiresConfirmation: true, |
| #208 | reason: 'This will read files, add chunks to retrieval, and move processed files.', |
| #209 | }; |
| #210 | } |
| #211 | |
| #212 | if (/\bschedule\b|\bweekly\b|\brecurring\b/.test(lower)) { |
| #213 | const quoted = text.match(/"([^"]+)"/)?.[1] || text.match(/'([^']+)'/)?.[1] || 'scheduled Aethon task'; |
| #214 | return { |
| #215 | tool: 'schedule_task', |
| #216 | parameters: { |
| #217 | natural_language_when: lower.includes('weekly') ? 'weekly' : 'as requested', |
| #218 | tool_call: { tool: 'scrape_source', parameters: { url_or_source: quoted, depth: 1, target_collection: 'sovereign-law' } }, |
| #219 | }, |
| #220 | requiresConfirmation: true, |
| #221 | reason: 'This will create a recurring task entry.', |
| #222 | }; |
| #223 | } |
| #224 | |
| #225 | if (/\b(codex|claude-code|free-code|use codex|use claude|delegate coding|coding task)\b/i.test(text)) { |
| #226 | const mode = /\bclaude-code\b/i.test(text) || /\bclaude\b/i.test(text) |
| #227 | ? 'claude-code' |
| #228 | : /\bfree-code\b/i.test(text) |
| #229 | ? 'free-code' |
| #230 | : 'codex'; |
| #231 | const repoPath = /\bliving-os-cockpit\b/i.test(text) |
| #232 | ? '/home/kingbau/living-os-cockpit' |
| #233 | : '/home/kingbau/Documents/Aethon-Core'; |
| #234 | const prompt = text |
| #235 | .replace(/^\s*(codex|claude-code|free-code)\s*,?\s*/i, '') |
| #236 | .replace(/^\s*use\s+(codex|claude-code|free-code)\s+to\s+/i, '') |
| #237 | .trim(); |
| #238 | return { |
| #239 | tool: 'delegate_coding_task', |
| #240 | parameters: { |
| #241 | mode, |
| #242 | prompt: prompt || text, |
| #243 | repo_path: repoPath, |
| #244 | }, |
| #245 | requiresConfirmation: true, |
| #246 | reason: `This will run ${mode} in ${repoPath}. Gateway edits remain locked.`, |
| #247 | }; |
| #248 | } |
| #249 | |
| #250 | if (/\bsearch (the )?(corpus|vault|rag)\b|\blook this up\b/i.test(text)) { |
| #251 | return { |
| #252 | tool: 'search_corpus', |
| #253 | parameters: { query: text.replace(/^search (the )?(corpus|vault|rag)\s*/i, ''), k: 5 }, |
| #254 | requiresConfirmation: false, |
| #255 | reason: 'This only reads retrieval results.', |
| #256 | }; |
| #257 | } |
| #258 | |
| #259 | return null; |
| #260 | } |
| #261 | |
| #262 | export function classifyAgenticPlan(message: string): AgenticPlan | null { |
| #263 | const text = message.trim(); |
| #264 | const multiStep = /\b(find|download|fetch|search).+\b(ingest|add|index)\b|\b(prove|verify|test retrieval|after)\b|\breading list\b|\bfoundational books\b/i.test(text); |
| #265 | if (!multiStep) return null; |
| #266 | |
| #267 | if (/\b(foundational books|reading list|blackstone|commentaries|foundations collection)\b/i.test(text)) { |
| #268 | return { |
| #269 | goal: 'Find available foundational reading-list sources, ingest safe public files into the foundations collection, and prove retrieval works.', |
| #270 | steps: [ |
| #271 | { |
| #272 | id: 's1', |
| #273 | tool: 'search_corpus', |
| #274 | parameters: { query: 'foundational books reading list aethon roadmap Blackstone Commentaries', k: 8 }, |
| #275 | why: "Check Aethon's own vault first so the plan follows King's existing reading list instead of guessing.", |
| #276 | blast_radius: 'low', |
| #277 | }, |
| #278 | { |
| #279 | id: 's2', |
| #280 | tool: 'web_search', |
| #281 | parameters: { query: 'Blackstone Commentaries public domain PDF', k: 5, source_filter: 'public domain' }, |
| #282 | why: 'Find lawful public-domain sources before downloading anything.', |
| #283 | depends_on: ['s1'], |
| #284 | blast_radius: 'low', |
| #285 | }, |
| #286 | { |
| #287 | id: 's3', |
| #288 | tool: 'web_fetch', |
| #289 | parameters: { url: 'https://oll-resources.s3.us-east-2.amazonaws.com/oll3/store/titles/2142/Blackstone_1387-02_EBk_v6.0.pdf', mode: 'pdf' }, |
| #290 | why: "Fetch a known public Blackstone PDF into Aethon's download area for ingestion.", |
| #291 | depends_on: ['s2'], |
| #292 | blast_radius: 'low', |
| #293 | }, |
| #294 | { |
| #295 | id: 's4', |
| #296 | tool: 'ingest_documents', |
| #297 | parameters: { path_or_paths: ['/home/kingbau/agentic_downloads'], category: 'foundations', tag: 'foundational-reading' }, |
| #298 | why: 'Add the downloaded source material to the foundations retrieval collection.', |
| #299 | depends_on: ['s3'], |
| #300 | blast_radius: 'medium', |
| #301 | }, |
| #302 | { |
| #303 | id: 's5', |
| #304 | tool: 'search_corpus', |
| #305 | parameters: { query: 'what does Blackstone say about natural rights', k: 3 }, |
| #306 | why: 'Prove retrieval works against the newly ingested foundations material.', |
| #307 | depends_on: ['s4'], |
| #308 | blast_radius: 'low', |
| #309 | }, |
| #310 | ], |
| #311 | risks: [ |
| #312 | 'Some books may not be freely available; Aethon will skip copyrighted or blocked sources instead of forcing a download.', |
| #313 | 'Ingestion writes to the foundations vault, so King approves the whole plan once before execution.', |
| #314 | ], |
| #315 | blast_radius: 'medium', |
| #316 | }; |
| #317 | } |
| #318 | |
| #319 | const call = classifyToolIntent(text); |
| #320 | if (!call) return null; |
| #321 | return { |
| #322 | goal: text, |
| #323 | steps: [{ id: 's1', tool: call.tool, parameters: call.parameters, why: call.reason, blast_radius: call.requiresConfirmation ? 'medium' : 'low' }], |
| #324 | risks: call.requiresConfirmation ? ["This plan writes or changes data inside Aethon's workspace."] : [], |
| #325 | blast_radius: call.requiresConfirmation ? 'medium' : 'low', |
| #326 | }; |
| #327 | } |
| #328 | |
| #329 | export function canRunTool(ctx: UserContext, call: ToolCall) { |
| #330 | const adminOnly = new Set(['control_computer', 'delegation_harness', 'schedule_task', 'print_document', 'delegate_coding_task', 'content_intake', 'ingest_youtube_channel', 'search_clip_bank', 'rename_filesystem_safe', 'web_search', 'web_fetch', 'shell_exec', 'register_tool']); |
| #331 | if (call.tool === 'ingest_documents') { |
| #332 | const paths = ([] as string[]).concat(call.parameters.path_or_paths || []); |
| #333 | const systemPath = paths.some((p) => String(p).startsWith('/home/kingbau/')); |
| #334 | if (systemPath && ctx.canonicalMemberId !== CANONICAL_KING_ID) return false; |
| #335 | } |
| #336 | if (adminOnly.has(call.tool) && ctx.canonicalMemberId !== CANONICAL_KING_ID) return false; |
| #337 | return true; |
| #338 | } |
| #339 | |
| #340 | export function canRunPlan(ctx: UserContext, plan: AgenticPlan) { |
| #341 | return plan.steps.every((step) => canRunTool(ctx, { tool: step.tool, parameters: step.parameters, requiresConfirmation: false, reason: step.why })); |
| #342 | } |
| #343 | |
| #344 | export function storePendingTool(ctx: UserContext, sessionId: string, call: ToolCall) { |
| #345 | const pending: PendingCall = { |
| #346 | ...call, |
| #347 | id: `tool-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`, |
| #348 | userId: ctx.canonicalMemberId, |
| #349 | sessionId, |
| #350 | createdAt: new Date().toISOString(), |
| #351 | }; |
| #352 | pendingStore.set(pendingKey(ctx, sessionId), pending); |
| #353 | return pending; |
| #354 | } |
| #355 | |
| #356 | export function storePendingPlan(ctx: UserContext, sessionId: string, plan: AgenticPlan) { |
| #357 | const pending: PendingCall = { |
| #358 | tool: 'agentic_plan', |
| #359 | parameters: { goal: plan.goal, steps: plan.steps }, |
| #360 | requiresConfirmation: true, |
| #361 | reason: `Aethon will run ${plan.steps.length} coordinated steps after one approval.`, |
| #362 | plan, |
| #363 | id: `plan-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`, |
| #364 | userId: ctx.canonicalMemberId, |
| #365 | sessionId, |
| #366 | createdAt: new Date().toISOString(), |
| #367 | }; |
| #368 | pendingStore.set(pendingKey(ctx, sessionId), pending); |
| #369 | return pending; |
| #370 | } |
| #371 | |
| #372 | export function takePendingTool(ctx: UserContext, sessionId: string) { |
| #373 | const key = pendingKey(ctx, sessionId); |
| #374 | const pending = pendingStore.get(key) || null; |
| #375 | if (pending) pendingStore.delete(key); |
| #376 | return pending; |
| #377 | } |
| #378 | |
| #379 | export function confirmationMarkdown(call: PendingCall) { |
| #380 | if (call.plan) return planConfirmationMarkdown(call); |
| #381 | return [ |
| #382 | `### Confirm Aethon tool call`, |
| #383 | ``, |
| #384 | `Aethon can run **${call.tool}**.`, |
| #385 | ``, |
| #386 | `**Why:** ${call.reason}`, |
| #387 | ``, |
| #388 | `**Parameters**`, |
| #389 | '```json', |
| #390 | JSON.stringify(call.parameters, null, 2), |
| #391 | '```', |
| #392 | ``, |
| #393 | `Reply **approve** to run it, or ask for changes.`, |
| #394 | ].join('\n'); |
| #395 | } |
| #396 | |
| #397 | export function planConfirmationMarkdown(call: PendingCall) { |
| #398 | const plan = call.plan!; |
| #399 | return [ |
| #400 | `### Approve Aethon plan`, |
| #401 | ``, |
| #402 | `**Goal:** ${plan.goal}`, |
| #403 | ``, |
| #404 | `**Blast radius:** ${plan.blast_radius}`, |
| #405 | ``, |
| #406 | `**Steps**`, |
| #407 | ...plan.steps.map((step, index) => `${index + 1}. **${step.tool}** - ${step.why}`), |
| #408 | ``, |
| #409 | `**Risks**`, |
| #410 | ...(plan.risks.length ? plan.risks.map((risk) => `- ${risk}`) : ['- No unusual risk detected.']), |
| #411 | ``, |
| #412 | `Reply **approve** to run the whole plan, or tell Aethon what to modify.`, |
| #413 | ].join('\n'); |
| #414 | } |
| #415 | |
| #416 | export function resultMarkdown(result: any) { |
| #417 | const summary = result?.summary || result?.result?.summary || ''; |
| #418 | const payload = result?.result ?? result; |
| #419 | return [ |
| #420 | `### Aethon tool result: ${payload?.tool || result?.tool || 'tool'}`, |
| #421 | ``, |
| #422 | summary || plainSummary(payload), |
| #423 | ``, |
| #424 | `**Full result**`, |
| #425 | '```json', |
| #426 | JSON.stringify(payload, null, 2).slice(0, 12000), |
| #427 | '```', |
| #428 | ].join('\n'); |
| #429 | } |
| #430 | |
| #431 | function plainSummary(payload: any) { |
| #432 | if (payload?.tool === 'agentic_plan') { |
| #433 | if (!payload.ok && payload.blocked_step) { |
| #434 | return `Plan stopped at **${payload.blocked_step.tool}**. ${payload.blocked_step.error || 'That step failed.'} ${payload.next_action || ''}`.trim(); |
| #435 | } |
| #436 | return `Plan finished. Steps completed: ${payload.completed_steps}. Failed steps: ${payload.failed_steps}. ${payload.final_question || ''}`.trim(); |
| #437 | } |
| #438 | if (payload?.tool === 'web_search') { |
| #439 | return `Found ${Array.isArray(payload.results) ? payload.results.length : 0} web results.`; |
| #440 | } |
| #441 | if (payload?.tool === 'web_fetch') { |
| #442 | return `Fetched source to ${payload.local_path} (${payload.content_type || 'unknown type'}).`; |
| #443 | } |
| #444 | if (payload?.tool === 'shell_exec') { |
| #445 | return `Shell command finished with exit code ${payload.exit_code}.`; |
| #446 | } |
| #447 | if (payload?.tool === 'control_computer') { |
| #448 | return payload.ok |
| #449 | ? `Computer-use action verified on ${payload.scope || 'bow'}. Before: ${payload.before_screenshot}. After: ${payload.after_screenshot}.` |
| #450 | : `Computer-use action did not run: ${payload.error || 'unknown error'}.`; |
| #451 | } |
| #452 | if (payload?.tool === 'delegation_harness') { |
| #453 | return `Delegation harness plan ready. Read-only step completed, and effect step is waiting for King approval: ${payload.approval_id}.`; |
| #454 | } |
| #455 | if (payload?.tool === 'register_tool') { |
| #456 | return `Tool registration proposal saved: ${payload.spec_path}.`; |
| #457 | } |
| #458 | if (payload?.tool === 'ingest_documents') { |
| #459 | const calls = payload.embed_batch_calls ? ` Embed-batch calls: ${payload.embed_batch_calls} at max ${payload.max_chunks_per_embed_call} chunks per call.` : ''; |
| #460 | return `Ingestion complete. Files processed: ${payload.files_processed}. Chunks added: ${payload.chunks_added}.${calls} Moved files to ${payload.moved_to}.`; |
| #461 | } |
| #462 | if (payload?.tool === 'schedule_task') { |
| #463 | return `Scheduled task saved: ${payload.task_path}.`; |
| #464 | } |
| #465 | if (payload?.tool === 'search_corpus') { |
| #466 | return `Retrieved ${Array.isArray(payload.hits) ? payload.hits.length : 0} matching chunks.`; |
| #467 | } |
| #468 | if (payload?.tool === 'delegate_coding_task') { |
| #469 | const changed = Array.isArray(payload.files_changed) ? payload.files_changed.length : 0; |
| #470 | return `Coding delegate finished with exit code ${payload.exit_code}. Files changed: ${changed}. Commit: ${payload.commit || 'none'}. Runtime: ${payload.runtime_ms}ms.`; |
| #471 | } |
| #472 | if (payload?.tool === 'content_intake') { |
| #473 | const report = payload.report || {}; |
| #474 | const question = report.proactive_question ? ` ${report.proactive_question}` : ''; |
| #475 | return `Content intake complete. Mode: ${payload.mode}. Clips added: ${payload.clips_added}. Output: ${payload.output_dir}.${question}`; |
| #476 | } |
| #477 | if (payload?.tool === 'ingest_youtube_channel') { |
| #478 | const name = payload.channel_name || payload.channel; |
| #479 | if (payload.status === 'running') { |
| #480 | return `Started catalog scrape of **${name}** — **${payload.total}** videos enumerated. Ingesting transcripts into the vault in the background (resumable). Progress: \`${payload.manifest_path}\`. Learnings + gated framework gap-fill land in \`${payload.breakdown_path}\` when complete.`; |
| #481 | } |
| #482 | return `Catalog scrape of **${name}** complete: **${payload.ingested}/${payload.total}** ingested · **${payload.total_chunks}** chunks · ${payload.already_in_vault || 0} already in vault · ${payload.no_transcript || 0} no-transcript · ${payload.failed || 0} failed. Breakdown: \`${payload.breakdown_path}\`.`; |
| #483 | } |
| #484 | if (payload?.tool === 'search_clip_bank') { |
| #485 | return `Found ${Array.isArray(payload.hits) ? payload.hits.length : 0} clip-bank matches.`; |
| #486 | } |
| #487 | if (payload?.tool === 'rename_filesystem_safe') { |
| #488 | if (payload.apply) { |
| #489 | return payload.message || `Renames complete. Files renamed: ${payload.renamed_count}. Failed: ${payload.failed_count}.`; |
| #490 | } |
| #491 | return `Rename plan ready. Files needing changes: ${payload.total_files}. Unique character problems: ${Object.keys(payload.unique_character_problems || {}).join(', ') || 'none'}.`; |
| #492 | } |
| #493 | return 'Tool finished.'; |
| #494 | } |
| #495 | |
| #496 | async function ensureDirs() { |
| #497 | await fs.mkdir(AUDIT_DIR, { recursive: true }); |
| #498 | await Promise.all(['queued', 'in_progress', 'completed'].map((dir) => fs.mkdir(join(TASK_ROOT, dir), { recursive: true }))); |
| #499 | } |
| #500 | |
| #501 | async function writeTask(status: 'queued' | 'in_progress' | 'completed', id: string, title: string, body: string) { |
| #502 | await ensureDirs(); |
| #503 | const target = join(TASK_ROOT, status, `${id}.md`); |
| #504 | await fs.writeFile(target, `# ${title}\n\nPriority: MEDIUM\nType: agentic_tool\nTier 1: Aethon\n\n${body}\n`, 'utf-8'); |
| #505 | return target; |
| #506 | } |
| #507 | |
| #508 | async function removeTask(status: 'queued' | 'in_progress' | 'completed', id: string) { |
| #509 | await fs.rm(join(TASK_ROOT, status, `${id}.md`), { force: true }).catch(() => {}); |
| #510 | } |
| #511 | |
| #512 | async function runPythonTool(tool: string, parameters: Record<string, any>) { |
| #513 | const payload = JSON.stringify({ tool, parameters }); |
| #514 | return await new Promise<any>((resolve, reject) => { |
| #515 | const child = spawn('/home/kingbau/Documents/Aethon-Core/.venv/bin/python3', [RUNNER], { |
| #516 | cwd: REPO_ROOT, |
| #517 | stdio: ['pipe', 'pipe', 'pipe'], |
| #518 | env: { ...process.env, PYTHONPATH: REPO_ROOT }, |
| #519 | }); |
| #520 | let stdout = ''; |
| #521 | let stderr = ''; |
| #522 | child.stdout.on('data', (chunk) => { stdout += chunk.toString(); }); |
| #523 | child.stderr.on('data', (chunk) => { stderr += chunk.toString(); }); |
| #524 | child.on('error', reject); |
| #525 | child.on('close', (code) => { |
| #526 | if (code !== 0) return reject(new Error(stderr || stdout || `tool exited ${code}`)); |
| #527 | try { |
| #528 | resolve(JSON.parse(stdout)); |
| #529 | } catch (error: any) { |
| #530 | reject(new Error(`Tool returned invalid JSON: ${error?.message || error}`)); |
| #531 | } |
| #532 | }); |
| #533 | child.stdin.end(payload); |
| #534 | }); |
| #535 | } |
| #536 | |
| #537 | async function scheduleTask(parameters: Record<string, any>, ctx: UserContext) { |
| #538 | const id = `scheduled-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; |
| #539 | const task = { |
| #540 | ok: true, |
| #541 | tool: 'schedule_task', |
| #542 | id, |
| #543 | owner: ctx.canonicalMemberId, |
| #544 | natural_language_when: parameters.natural_language_when || 'as requested', |
| #545 | tool_call: parameters.tool_call || null, |
| #546 | created_at: new Date().toISOString(), |
| #547 | status: 'queued', |
| #548 | }; |
| #549 | const taskPath = await writeTask('queued', id, 'Scheduled Aethon tool call', '```json\n' + JSON.stringify(task, null, 2) + '\n```'); |
| #550 | return { ...task, task_path: taskPath }; |
| #551 | } |
| #552 | |
| #553 | export async function executeToolCall(ctx: UserContext, call: ToolCall) { |
| #554 | if ((call as PendingCall).plan) return await executeAgenticPlan(ctx, (call as PendingCall).plan!); |
| #555 | const started = Date.now(); |
| #556 | const taskId = `agentic-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; |
| #557 | await writeTask('in_progress', taskId, `Running ${call.tool}`, '```json\n' + JSON.stringify(call.parameters, null, 2) + '\n```'); |
| #558 | |
| #559 | let result: any; |
| #560 | try { |
| #561 | if (['ingest_documents', 'search_corpus', 'delegate_coding_task', 'content_intake', 'ingest_youtube_channel', 'search_clip_bank', 'rename_filesystem_safe', 'web_search', 'web_fetch', 'shell_exec', 'register_tool', 'control_computer', 'delegation_harness'].includes(call.tool)) { |
| #562 | result = await runPythonTool(call.tool, { ...call.parameters, user_id: ctx.legacyUserId || 'master' }); |
| #563 | } else if (call.tool === 'schedule_task') { |
| #564 | result = await scheduleTask(call.parameters, ctx); |
| #565 | } else { |
| #566 | result = { ok: false, tool: call.tool, error: 'This tool is registered but not executable in v1 yet.' }; |
| #567 | } |
| #568 | await removeTask('in_progress', taskId); |
| #569 | await writeTask('completed', taskId, `Finished ${call.tool}`, '```json\n' + JSON.stringify(result, null, 2) + '\n```'); |
| #570 | return await audit(ctx, call, result, started); |
| #571 | } catch (error: any) { |
| #572 | result = { ok: false, tool: call.tool, error: String(error?.message || error) }; |
| #573 | await removeTask('in_progress', taskId); |
| #574 | await writeTask('completed', taskId, `Failed ${call.tool}`, '```json\n' + JSON.stringify(result, null, 2) + '\n```'); |
| #575 | return await audit(ctx, call, result, started); |
| #576 | } |
| #577 | } |
| #578 | |
| #579 | export async function executeAgenticPlan(ctx: UserContext, plan: AgenticPlan) { |
| #580 | const started = Date.now(); |
| #581 | const taskId = `plan-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; |
| #582 | await writeTask('in_progress', taskId, `Running Aethon plan`, '```json\n' + JSON.stringify(plan, null, 2) + '\n```'); |
| #583 | const stepResults: any[] = []; |
| #584 | let failed = 0; |
| #585 | for (const step of plan.steps) { |
| #586 | let last: any = null; |
| #587 | for (let attempt = 1; attempt <= 3; attempt += 1) { |
| #588 | try { |
| #589 | last = step.tool === 'schedule_task' |
| #590 | ? await scheduleTask(step.parameters, ctx) |
| #591 | : await runPythonTool(step.tool, { ...step.parameters, user_id: ctx.legacyUserId || 'master' }); |
| #592 | } catch (error: any) { |
| #593 | last = { |
| #594 | ok: false, |
| #595 | tool: step.tool, |
| #596 | error: String(error?.message || error), |
| #597 | attempted_parameters: step.parameters, |
| #598 | attempt, |
| #599 | }; |
| #600 | } |
| #601 | if (last?.ok) break; |
| #602 | if (attempt < 3) await new Promise((resolve) => setTimeout(resolve, 500 * attempt)); |
| #603 | } |
| #604 | stepResults.push({ step: step.id, tool: step.tool, why: step.why, result: last }); |
| #605 | if (!last?.ok) { |
| #606 | failed += 1; |
| #607 | break; |
| #608 | } |
| #609 | } |
| #610 | const result = { |
| #611 | ok: failed === 0, |
| #612 | tool: 'agentic_plan', |
| #613 | goal: plan.goal, |
| #614 | completed_steps: stepResults.filter((row) => row.result?.ok).length, |
| #615 | failed_steps: failed, |
| #616 | steps: stepResults, |
| #617 | blocked_step: failed ? stepResults[stepResults.length - 1] : null, |
| #618 | next_action: failed ? nextActionForFailure(stepResults[stepResults.length - 1]?.result) : null, |
| #619 | final_question: 'Want me to run the next batch or tighten this plan before continuing?', |
| #620 | }; |
| #621 | await removeTask('in_progress', taskId); |
| #622 | await writeTask('completed', taskId, `${result.ok ? 'Finished' : 'Stopped'} Aethon plan`, '```json\n' + JSON.stringify(result, null, 2) + '\n```'); |
| #623 | return await audit(ctx, { tool: 'agentic_plan', parameters: plan as any, requiresConfirmation: true, reason: plan.goal }, result, started); |
| #624 | } |
| #625 | |
| #626 | function nextActionForFailure(result: any) { |
| #627 | const error = String(result?.error || '').toLowerCase(); |
| #628 | if (error.includes('tavily') || error.includes('brave') || error.includes('api key')) { |
| #629 | return 'Provide TAVILY_API_KEY or BRAVE_SEARCH_API_KEY in the cockpit service environment, then approve the plan again.'; |
| #630 | } |
| #631 | if (error.includes('unsupported runner tool')) { |
| #632 | return 'The runner is missing this tool registration; deploy the latest Aethon-Core runner and restart the cockpit.'; |
| #633 | } |
| #634 | if (error.includes('path not found') || error.includes('no supported files')) { |
| #635 | return 'Check the file path or source download, then rerun the plan.'; |
| #636 | } |
| #637 | if (error.includes('permission') || error.includes('limited')) { |
| #638 | return "Confirm King's canonical admin identity is active, then rerun the plan."; |
| #639 | } |
| #640 | return 'Review the step error above, fix that blocker, then rerun the plan.'; |
| #641 | } |
| #642 | |
| #643 | async function audit(ctx: UserContext, call: ToolCall, result: any, started: number) { |
| #644 | await ensureDirs(); |
| #645 | const entry = { |
| #646 | at: new Date().toISOString(), |
| #647 | who: { |
| #648 | canonical_member_id: ctx.canonicalMemberId, |
| #649 | display_name: ctx.displayName, |
| #650 | vault_user_key: ctx.vaultUserKey, |
| #651 | }, |
| #652 | tool: call.tool, |
| #653 | parameters: call.parameters, |
| #654 | result, |
| #655 | duration_ms: Date.now() - started, |
| #656 | }; |
| #657 | const file = join(AUDIT_DIR, `${new Date().toISOString().replace(/[:.]/g, '-')}_${call.tool}.json`); |
| #658 | await fs.writeFile(file, JSON.stringify(entry, null, 2), 'utf-8'); |
| #659 | return { ok: Boolean(result?.ok), tool: call.tool, audit_path: file, result }; |
| #660 | } |
| #661 |