/** * Orb Project Run — Integration Glue Entry Point * * Connects OrbProjectManager (packages/agents/src/orb/orb-project-manager.ts) * to the Orb runtime (Docker + AgentOS + OpenCode) and the Git/Gitea lifecycle * (packages/agents/src/orb/git-adapter.ts). This is the missing connection * point: OrbProjectManager was fully implemented and live-tested but had no * production entry point — no Convex action, no script invocation. * * Usage: * ORB_RUN=1 \ * ORB_GATEWAY_API_KEY=... \ * ORB_GATEWAY_BASE_URL=https://ai.example.com/v1 \ * ORB_GATEWAY_MODEL=glm-5.2 \ * ORB_GATEWAY_PROVIDER=cheaptricks \ GITEA_URL=https://git.example.com \ GITEA_TOKEN=... \ ORB_RUN_REPOSITORY_URL=https://git.example.com/owner/repo.git \ ORB_RUN_REPOSITORY_PATH=owner/repo \ bun run scripts/orb-project-run.ts * * Stable markers: ORB_RUN_COMPLETED (exit 0), ORB_RUN_BLOCKED (exit 2), * ORB_RUN_FAILED (exit 1). The script never prints gateway secrets; only * provider/model identifiers and sanitized status text. * * See docs/DOGFOOD_V0.md for the full lifecycle and the relationship between * this entry point, the Flue project-manager agent, and the web start button. */ /* eslint-disable no-console -- CLI tool */ import { createGiteaHttpTransport } from "../packages/agents/src/git/gitea"; import type { OrbModelGatewayConfig } from "../packages/agents/src/orb/domain"; import { createGitLifecyclePort } from "../packages/agents/src/orb/git-adapter"; import { createOrbAdapter } from "../packages/agents/src/orb/orb-adapter"; import { OrbProjectManager } from "../packages/agents/src/orb/orb-project-manager"; const MARKER = { blocked: "ORB_RUN_BLOCKED", completed: "ORB_RUN_COMPLETED", failed: "ORB_RUN_FAILED", } as const; interface RunConfig { readonly gateway: OrbModelGatewayConfig; readonly giteaToken: string | undefined; readonly giteaUrl: string | undefined; readonly issueBody: string; readonly issueNumber: number; readonly issueTitle: string; readonly repositoryPath: string | undefined; readonly repositoryUrl: string | undefined; readonly baseBranch: string; readonly workspaceRoot: string; } const envValue = (key: string): string | undefined => process.env[key]; const resolveGateway = (): OrbModelGatewayConfig | null => { const apiKey = envValue("ORB_GATEWAY_API_KEY") ?? envValue("AGENT_MODEL_API_KEY"); const baseUrl = envValue("ORB_GATEWAY_BASE_URL") ?? envValue("AGENT_MODEL_BASE_URL"); const model = envValue("ORB_GATEWAY_MODEL") ?? envValue("AGENT_MODEL_NAME"); const provider = envValue("ORB_GATEWAY_PROVIDER") ?? envValue("AGENT_MODEL_PROVIDER"); if ( !apiKey?.trim() || !baseUrl?.trim() || !model?.trim() || !provider?.trim() ) { return null; } return { apiKey, baseUrl, model, provider }; }; const resolveConfig = (): RunConfig | null => { const gateway = resolveGateway(); if (!gateway) { return null; } const issueTitle = envValue("ORB_RUN_ISSUE_TITLE") ?? "Orb run: echo test"; const issueBody = envValue("ORB_RUN_ISSUE_BODY") ?? "Reply with WORK_COMPLETE: orb project run succeeded"; return { baseBranch: envValue("ORB_RUN_BASE_BRANCH") ?? "main", gateway, giteaToken: envValue("GITEA_TOKEN") ?? envValue("ORB_RUN_GITEA_TOKEN"), giteaUrl: envValue("GITEA_URL") ?? envValue("ORB_RUN_GITEA_URL"), issueBody, issueNumber: Number(envValue("ORB_RUN_ISSUE_NUMBER") ?? "1"), issueTitle, repositoryPath: envValue("ORB_RUN_REPOSITORY_PATH") ?? envValue("ZOPU_SMOKE_REPOSITORY_PATH"), repositoryUrl: envValue("ORB_RUN_REPOSITORY_URL"), workspaceRoot: envValue("ORB_DOCKER_WORKSPACE") ?? `/tmp/orb-project-run-${Date.now()}`, }; }; const runComplete = async (): Promise => { const config = resolveConfig(); if (!config) { console.error( "[orb-run] missing gateway config: set ORB_GATEWAY_* (or AGENT_MODEL_* fallbacks)." ); console.log(`\n${MARKER.blocked}`); return 2; } const { baseBranch, gateway, giteaToken, giteaUrl, issueBody, issueNumber, issueTitle, repositoryPath, repositoryUrl, workspaceRoot, } = config; console.log( `[orb-run] gateway provider=${gateway.provider} model=${gateway.model}` ); // The Orb adapter wraps the OrbRuntime (Docker + AgentOS + OpenCode). const orbAdapter = createOrbAdapter(); const events: { type: string; text?: string }[] = []; // The Git lifecycle adapter connects Orb sandbox commands to Gitea PR // creation. If Gitea is not configured, the Git lifecycle is skipped but // the Orb run still completes the model turn and repository preparation. const createGitLifecycle = ( orb: Parameters[0] ) => { if (!giteaUrl || !giteaToken) { throw new Error( "GITEA_URL and GITEA_TOKEN are required for the Git publish lifecycle" ); } return createGitLifecyclePort( orb, createGiteaHttpTransport({ baseUrl: giteaUrl, token: giteaToken }) ); }; const pm = new OrbProjectManager({ createGitLifecycle, onProjectEvent: (e) => { events.push({ text: e.text, type: e.type }); const summary = (e.text ?? e.type).slice(0, 120); console.log(` [event] ${e.type}: ${summary}`); }, orbAdapter, }); const issueId = `orb-run-${Date.now()}`; const branchName = `work/orb-run-${issueNumber}-${Date.now()}`; let result; try { result = await pm.startIssue({ baseBranch, branchName, context: { artifacts: [], contextFiles: [], issueBody, issueTitle, repositoryUrl, }, contextPack: { artifacts: [], contextFiles: [], evidence: [], issueBody, issueNumber, issueTitle, repositoryMetadata: { baseBranch, repositoryName: repositoryPath ?? "orb-run", repositoryUrl: repositoryUrl ?? "local", }, }, docker: { hostWorkspacePath: workspaceRoot, }, gateway, issueId, issueNumber, issueTitle, projectId: "orb-run", runId: `run-${Date.now()}`, }); console.log( `[orb-run] session opened: ${result.sessionId ?? "?"} (status=${result.status})` ); // If the run completed with a WORK_COMPLETE marker, drive the Git // lifecycle. If Gitea is not configured, the model turn + repository // preparation still constitutes a successful Orb run. if (result.status === "completing" || result.status === "completed") { if (giteaUrl && giteaToken && repositoryPath) { console.log("[orb-run] driving Git/Gitea lifecycle..."); try { const gitResult = await pm.complete({ issueId }); console.log( `[orb-run] Git lifecycle: ${gitResult.status} branch=${gitResult.branch}` ); } catch (error) { console.log( `[orb-run] Git lifecycle failed: ${error instanceof Error ? error.message : String(error)}` ); } } else { console.log( "[orb-run] Gitea not configured; skipping Git lifecycle (model turn completed)" ); } } } catch (error) { const message = error instanceof Error ? error.message : String(error); console.error(`[orb-run] failed: ${message}`); await pm.cancel(issueId).catch((cancelError: unknown) => void cancelError); console.log(`\n${MARKER.failed}`); return 1; } await pm.cancel(issueId).catch((cancelError: unknown) => void cancelError); const eventTypes = events.map((e) => e.type); console.log( `[orb-run] events: ${[...new Set(eventTypes)].join(", ")} (${events.length} total)` ); const hasSessionOpened = eventTypes.includes("run.session_opened"); if (hasSessionOpened) { console.log(`\n${MARKER.completed}`); return 0; } console.log(`\n${MARKER.failed}`); return 1; }; if (envValue("ORB_RUN") !== "1") { console.error( "Set ORB_RUN=1 to drive one issue through the Orb project-manager (requires Docker + model gateway)." ); console.error(MARKER.blocked); process.exit(2); } (async () => { try { const code = await runComplete(); process.exit(code); } catch (error) { console.error("[orb-run] unexpected failure:", error); console.error(MARKER.failed); process.exit(1); } })();