Spaces:
Sleeping
Sleeping
| import { exec, execFile } from 'node:child_process'; | |
| import { mkdtemp, rm, writeFile } from 'node:fs/promises'; | |
| import { tmpdir } from 'node:os'; | |
| import { join } from 'node:path'; | |
| import { promisify } from 'node:util'; | |
| import { createClient } from '@supabase/supabase-js'; | |
| import { commentIssueAsGitHubApp, getGitHubAppRoutingStatus, routeGitHubWebhook } from './github-app-routing.js'; | |
| const execAsync = promisify(exec); | |
| const execFileAsync = promisify(execFile); | |
| export type GitHubAgentAction = | |
| | { action: 'agent.help' } | |
| | { action: 'sin.github.health' } | |
| | { action: 'sin.github.app.routing.status' } | |
| | { action: 'sin.github.webhook.route'; payload?: unknown; rawBody?: string; routePath?: string; signature256?: string; eventName?: string; deliveryId?: string } | |
| | { action: 'sin.github.issue.comment.as_app'; repo: string; issueNumber: number; body: string; agentSlug?: string; appId?: number; installationId: number } | |
| | { action: 'sin.github.project.orchestrate'; prompt: string; contextDir?: string } | |
| | { action: 'sin.github.issue.manage'; prompt: string; issueNumber?: number } | |
| | { action: 'sin.github.wiki.sync'; prompt: string; contextDir?: string } | |
| | { action: 'sin.github.discussion.start'; prompt: string; category?: string } | |
| | { action: 'sin.github.gist.publish'; prompt: string; isPublic?: boolean } | |
| | { action: 'sin.github.security.audit'; prompt: string; contextDir?: string } | |
| | { action: 'sin.github.pr.review'; prNumber: number; contextDir?: string } | |
| | { action: 'sin.github.issue.pool.enqueue'; repoName: string; issueNumber: number; title: string; body?: string; labels?: string[]; issueUrl?: string; assignedTeam?: string; assignedAgent?: string; status?: string; state?: string; routingStrategy?: string; fanoutPlan?: unknown[] } | |
| | { action: 'sin.github.ledger.log'; agentName: string; activityTitle: string; details: string }; | |
| export async function executeGitHubAgentAction(action: GitHubAgentAction): Promise<unknown> { | |
| switch (action.action) { | |
| case 'agent.help': | |
| return { | |
| ok: true, | |
| agent: 'sin-github-issues', | |
| mandate: 'CEO Elite GitHub Operations. Orchestrates Projects, Issues, Wikis, Discussions, Gists, Security, PR Reviews, and public Showcase Ledgers.', | |
| actions: [ | |
| 'sin.github.health', | |
| 'sin.github.app.routing.status', | |
| 'sin.github.webhook.route', | |
| 'sin.github.issue.comment.as_app', | |
| 'sin.github.project.orchestrate', | |
| 'sin.github.issue.manage', | |
| 'sin.github.wiki.sync', | |
| 'sin.github.discussion.start', | |
| 'sin.github.gist.publish', | |
| 'sin.github.security.audit', | |
| 'sin.github.pr.review', | |
| 'sin.github.issue.pool.enqueue', | |
| 'sin.github.ledger.log' | |
| ], | |
| }; | |
| case 'sin.github.health': | |
| return { | |
| ok: true, | |
| agent: 'sin-github-issues', | |
| primaryModel: 'openai/gpt-5.4', | |
| fallbackModel: 'opencode/minimax-m2.5-free', | |
| team: 'Team - Coding', | |
| status: 'Elite 2026 GitHub Operations Architect Online', | |
| capabilities: ['projects', 'wikis', 'discussions', 'gists', 'security', 'prs', 'issues', 'ledger', 'github-app-routing'], | |
| githubAppRouting: await getGitHubAppRoutingStatus(), | |
| }; | |
| case 'sin.github.app.routing.status': | |
| return await getGitHubAppRoutingStatus(); | |
| case 'sin.github.webhook.route': | |
| return await routeGitHubWebhook(action); | |
| case 'sin.github.issue.comment.as_app': | |
| return await commentIssueAsGitHubApp(action); | |
| case 'sin.github.project.orchestrate': | |
| return await executeOpenCode( | |
| `Use the GitHub CLI ('gh project create/link/item-add') to orchestrate the following project board requirements: ${action.prompt}. Ensure the board is linked to the repository and items are properly assigned.`, | |
| action.contextDir | |
| ); | |
| case 'sin.github.issue.manage': | |
| return await executeOpenCode( | |
| `Manage GitHub issues based on: ${action.prompt}. ${action.issueNumber ? `Target issue: #${action.issueNumber}` : 'Create a new Epic/Task.'} Ensure proper labeling, assignment, and milestone linking.` | |
| ); | |
| case 'sin.github.wiki.sync': | |
| return await executeOpenCode( | |
| `Synchronize the repository's Wiki with the following documentation update: ${action.prompt}. Generate standard Markdown and push it to the wiki repository (.wiki.git).`, | |
| action.contextDir | |
| ); | |
| case 'sin.github.discussion.start': | |
| return await executeOpenCode( | |
| `Start or manage a GitHub Discussion to keep issues clean. Requirements: ${action.prompt}. Category: ${action.category || 'General'}.` | |
| ); | |
| case 'sin.github.gist.publish': | |
| return await executeOpenCode( | |
| `Create a GitHub Gist (public: ${action.isPublic ?? false}) for the following payload (long log, snippet, or config): ${action.prompt}. Return the Gist URL for embedding in an issue or PR.` | |
| ); | |
| case 'sin.github.security.audit': | |
| return await executeOpenCode( | |
| `Audit the repository for security vulnerabilities (Dependabot/CodeQL). Generate or update '.github/dependabot.yml' and review any active alerts based on: ${action.prompt}.`, | |
| action.contextDir | |
| ); | |
| case 'sin.github.pr.review': | |
| return await executeOpenCode( | |
| `Perform a rigorous Code Review on Pull Request #${action.prNumber}. Check for 2026 architectural compliance, security vulnerabilities, performance regressions, and test coverage. If it passes 100%, approve it ('gh pr review --approve') and merge it ('gh pr merge --auto').`, | |
| action.contextDir | |
| ); | |
| case 'sin.github.issue.pool.enqueue': | |
| return await enqueueIssuePoolItem(action); | |
| case 'sin.github.ledger.log': | |
| return await publishLedgerLog(action.agentName, action.activityTitle, action.details); | |
| } | |
| } | |
| async function executeOpenCode(prompt: string, dir?: string) { | |
| await execAsync('python3 scripts/hf_pull_script.py').catch(() => console.warn('Warning: hf_pull_script failed. Proceeding with cached credentials.')); | |
| const options = dir ? { cwd: dir } : {}; | |
| try { | |
| const { stdout, stderr } = await execFileAsync('opencode', ['run', prompt, '--model', 'openai/gpt-5.4'], options); | |
| return { | |
| ok: true, | |
| expertAnalysis: stdout, | |
| warnings: stderr || undefined, | |
| }; | |
| } catch (error: any) { | |
| throw new Error(`GitHub Operations Execution Failed: ${error.message}`); | |
| } | |
| } | |
| async function publishLedgerLog(agentName: string, activityTitle: string, details: string) { | |
| const title = `[${agentName}] ${activityTitle}`; | |
| const body = [ | |
| `# ${activityTitle}`, | |
| '', | |
| `- Agent: ${agentName}`, | |
| `- Published: ${new Date().toISOString()}`, | |
| '', | |
| '## Details', | |
| details, | |
| ].join('\n'); | |
| const tempDir = await mkdtemp(join(tmpdir(), 'sin-github-ledger-')); | |
| const bodyPath = join(tempDir, 'ledger-body.md'); | |
| try { | |
| await writeFile(bodyPath, body, 'utf8'); | |
| const { stdout, stderr } = await execFileAsync('gh', [ | |
| 'issue', | |
| 'create', | |
| '-R', | |
| 'Delqhi/OpenSIN-Ledger', | |
| '--title', | |
| title, | |
| '--body-file', | |
| bodyPath, | |
| ]); | |
| return { | |
| ok: true, | |
| repo: 'Delqhi/OpenSIN-Ledger', | |
| title, | |
| url: stdout.trim(), | |
| warnings: stderr || undefined, | |
| }; | |
| } catch (error: any) { | |
| throw new Error(`GitHub Ledger Publish Failed: ${error.message}`); | |
| } finally { | |
| await rm(tempDir, { recursive: true, force: true }).catch(() => undefined); | |
| } | |
| } | |
| async function enqueueIssuePoolItem(action: Extract<GitHubAgentAction, { action: 'sin.github.issue.pool.enqueue' }>) { | |
| const supabaseUrl = (process.env.SUPABASE_URL || process.env.SIN_SUPABASE_URL || '').trim(); | |
| const serviceRoleKey = (process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SIN_SUPABASE_SERVICE_ROLE_KEY || '').trim(); | |
| if (!supabaseUrl || !serviceRoleKey) { | |
| throw new Error('sin_supabase_config_missing: set SUPABASE_URL/SUPABASE_SERVICE_ROLE_KEY or SIN_SUPABASE_URL/SIN_SUPABASE_SERVICE_ROLE_KEY before enqueueing issue-pool items'); | |
| } | |
| const supabase = createClient(supabaseUrl, serviceRoleKey, { | |
| auth: { persistSession: false, autoRefreshToken: false }, | |
| }); | |
| const payload = { | |
| issue_number: action.issueNumber, | |
| repo_name: action.repoName, | |
| title: action.title, | |
| body: action.body || null, | |
| labels: action.labels || [], | |
| issue_url: action.issueUrl || null, | |
| state: action.state || 'open', | |
| assigned_team: action.assignedTeam || 'team-coding', | |
| assigned_agent: action.assignedAgent || null, | |
| status: action.status || 'open', | |
| routing_strategy: action.routingStrategy || 'single-dispatch', | |
| fanout_plan: action.fanoutPlan || [], | |
| updated_at: new Date().toISOString(), | |
| }; | |
| let { data, error } = await supabase.from('sin_issues_pool').insert(payload).select('*').single(); | |
| if (error && /column/i.test(error.message)) { | |
| const fallbackPayload = { | |
| issue_number: action.issueNumber, | |
| repo_name: action.repoName, | |
| title: action.title, | |
| body: action.body || null, | |
| state: action.state || 'open', | |
| assigned_team: action.assignedTeam || 'team-coding', | |
| assigned_agent: action.assignedAgent || null, | |
| status: action.status || 'pending', | |
| updated_at: new Date().toISOString(), | |
| }; | |
| ({ data, error } = await supabase.from('sin_issues_pool').insert(fallbackPayload).select('*').single()); | |
| } | |
| if (error) { | |
| throw new Error(`sin_issue_pool_enqueue_failed: ${error.message}`); | |
| } | |
| return { | |
| ok: true, | |
| table: 'sin_issues_pool', | |
| item: data, | |
| }; | |
| } | |