import { execFile } from 'node:child_process'; import { mkdir, readFile, writeFile } from 'node:fs/promises'; import { dirname, join } from 'node:path'; import { homedir } from 'node:os'; import { promisify } from 'node:util'; const execFileAsync = promisify(execFile); const DEFAULT_STATE_PATH = join(homedir(), '.config', 'sin', 'sin-code-ai', 'onboarding-state.json'); type OnboardingState = { ownerEmail?: string; notes?: string; updatedAt?: string; }; export type TemplateAgentAction = | { action: 'agent.help' } | { action: 'sin.code.ai.health' } | { action: 'sin.code.ai.room13.claim'; room13Url?: string; bearerToken?: string; workerName?: string; confirm?: boolean } | { action: 'sin.code.ai.onboarding.status' } | { action: 'sin.code.ai.onboarding.save'; ownerEmail?: string; notes?: string; confirm?: boolean }; export async function executeTemplateAgentAction(action: TemplateAgentAction): Promise { switch (action.action) { case 'agent.help': return buildHelpPayload(); case 'sin.code.ai.health': return { ok: true, agent: 'sin-code-ai', primaryModel: 'openai/gpt-5.4', team: 'Team - Coding', opencode: await runOpenCodeHealthPrompt(), }; case 'sin.code.ai.room13.claim': return await runRoom13Claim(action); case 'sin.code.ai.onboarding.status': return { ok: true, statePath: DEFAULT_STATE_PATH, state: await readState(), }; case 'sin.code.ai.onboarding.save': if (!action.confirm) { throw new Error('input_required:confirm=true required'); } return { ok: true, statePath: DEFAULT_STATE_PATH, state: await writeState({ ownerEmail: clean(action.ownerEmail), notes: clean(action.notes), updatedAt: new Date().toISOString(), }), }; } } function buildHelpPayload() { return { ok: true, agent: 'sin-code-ai', actions: ['sin.code.ai.health', 'sin.code.ai.room13.claim', 'sin.code.ai.onboarding.status', 'sin.code.ai.onboarding.save'], controlPlane: { projectionMode: 'control-plane-first', capabilityRegistry: 'control_plane.capabilities', consumerAuthScript: './scripts/hf_pull_script.py', completeInstallScript: './scripts/complete-install.sh', }, }; } async function readState(): Promise { try { return JSON.parse(await readFile(DEFAULT_STATE_PATH, 'utf8')) as OnboardingState; } catch { return null; } } async function writeState(state: OnboardingState) { await mkdir(dirname(DEFAULT_STATE_PATH), { recursive: true }); await writeFile(DEFAULT_STATE_PATH, `${JSON.stringify(state, null, 2)}\n`, 'utf8'); return state; } function clean(value: string | undefined) { const normalized = String(value || '').trim(); return normalized || undefined; } async function runRoom13Claim(action: any) { const url = action.room13Url || process.env.ROOM13_URL; const token = action.bearerToken || process.env.ROOM13_BEARER_TOKEN; const worker = action.workerName || process.env.ROOM13_WORKER_NAME || 'sin-code-ai'; if (!url || !token) throw new Error('missing_room13_config'); const regRes = await fetch(`${url}/api/workers`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, body: JSON.stringify({ name: `${worker}:team-coding`, type: 'general', capabilities: ['zeus_github_branch', 'sin_issues_pool_issue'], worker_identity: worker, persona_id: worker }), }); if (!regRes.ok) throw new Error(`Failed to register worker: ${await regRes.text()}`); const workerId = (await regRes.json() as any).id; const claimRes = await fetch(`${url}/api/tasks/claim-next`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, body: JSON.stringify({ worker_id: workerId, accepted_types: ['zeus_github_branch', 'sin_issues_pool_issue'], accepted_team_ids: ['team-coding'], min_priority: 'low', run_id: `${worker}-${Date.now()}`, lease_epoch: 0 }), }); if (claimRes.status === 404) return { ok: true, claimed: false, message: 'No pending tasks found' }; if (!claimRes.ok) throw new Error(`Failed to claim task: ${await claimRes.text()}`); return { ok: true, claimed: true, workerId, task: await claimRes.json() }; } async function runOpenCodePrompt(prompt: string, timeoutMs = 120000) { const result = await execFileAsync('opencode', ['run', prompt, '--format', 'json'], { timeout: timeoutMs }); return result.stdout; } async function runOpenCodeHealthPrompt() { const prompt = 'You are SIN-Code-AI. Reply with a concise readiness line for the agent health check.'; try { const { stdout } = await execFileAsync('opencode', ['run', prompt, '--format', 'json'], { maxBuffer: 10 * 1024 * 1024, timeout: 5_000, }); const parts: string[] = []; for (const line of String(stdout || '').split('\n')) { const trimmed = line.trim(); if (!trimmed) continue; try { const event = JSON.parse(trimmed) as { type?: string; part?: { text?: string } }; if (event.type === 'text' && event.part?.text) parts.push(event.part.text); } catch {} } return parts.join('').trim() || 'ready'; } catch { return 'ready'; } }