/** * Runtime smoke test — prove a runtime really works on this machine by * running one agent on it end to end. * * Through the backend's own REST API (the calls the dashboard makes): * * 1. `create_team` — temp project + team `zz-runtime-smoke-` with * one member on the runtime (a leftover of the same name is removed first). * 2. `start_member` — start it (as the owner's dashboard would). * 3. `agent_ready` — wait until it registered. Antigravity's first-run Terms * screen fails the test with "Antigravity needs its terms accepted once" * and the screen text — Crewly never accepts terms for the owner. * 4. `send_task` — `echo ok > ` with bash, then reply with a token. * 5. `bash` — the file holds `ok`. * 6. `reply` — the token `SMOKE-ok-` shows in the agent's output. The * task never contains that string verbatim (only its parts), so an echo * of the task does not count. * 7. `cleanup` — stop and delete the team and the project (always). * * Bounded by 5 minutes. specs/2026-10-01-runtime-fallback.md * * @module services/runtime-fallback/runtime-smoke-test.service */ /** Steps, in order. */ export type SmokeStep = 'create_team' | 'start_member' | 'agent_ready' | 'send_task' | 'bash' | 'reply' | 'cleanup'; /** Result of one smoke test. */ export interface SmokeTestResult { runtime: string; passed: boolean; /** The step that failed */ failedStep?: SmokeStep; /** Owner-facing reason (English) */ error?: string; /** Steps that passed, in order */ steps: Array<{ step: SmokeStep; ok: boolean; ms: number; detail?: string; }>; /** Last screen text (on failure, and for the Terms screen) */ screen?: string; durationMs: number; } /** A job (POST returns its id; GET reads it). */ export interface SmokeTestJob { jobId: string; runtime: string; state: 'running' | 'done'; startedAt: string; result?: SmokeTestResult; } /** The REST calls the smoke test makes (the default talks to the local API). */ export interface SmokeApi { listTeams(): Promise>; listProjects(): Promise>; createProject(name: string, projectPath: string): Promise; deleteProject(projectId: string): Promise; createTeam(body: Record): Promise<{ id: string; memberId: string; }>; startMember(teamId: string, memberId: string): Promise<{ ok: boolean; error?: string; }>; getMember(teamId: string, memberId: string): Promise<{ agentStatus: string; sessionName: string; } | null>; capture(sessionName: string, lines: number): Promise; /** The session's persistent log (also after the session is gone) */ sessionLog(sessionName: string, lines: number): Promise; deliver(sessionName: string, message: string): Promise<{ ok: boolean; error?: string; }>; stopTeam(teamId: string): Promise; /** Kill a session by name (a refused start can leave its PTY behind) */ killSession(sessionName: string): Promise; /** Whether a session is still live */ sessionExists(sessionName: string): Promise; deleteTeam(teamId: string): Promise; } /** Dependencies. */ export interface SmokeTestDeps { api: SmokeApi; /** Scratch root for the project + proof files */ workRoot?: string; now?: () => number; sleep?: (ms: number) => Promise; nonce?: () => string; timeoutMs?: number; pollMs?: number; /** provider/model a Crewly Agent test runs (the fallback's model, DeepSeek by default) */ crewlyAgentModel?: () => string; /** * The test stopped on the runtime's first-run Terms screen: ask the owner * (specs/2026-10-01-runtime-terms-consent.md). `ownerInitiated` = the * owner pressed Test. */ onTermsScreen?: (runtime: string, report: { ownerInitiated: boolean; screen?: string; }) => void; } /** The failure reason when the runtime stopped on its first-run Terms screen. */ export declare const SMOKE_TERMS_ERROR = "Antigravity needs its terms accepted once"; /** * Whether a screen shows Antigravity's first-run (Terms) screens. * * @param screen - Screen text * @returns True when the Terms / onboarding screen is up */ export declare function showsAntigravityTerms(screen: string): boolean; /** * The task the smoke agent gets. * * @param file - Proof file to write with bash * @param nonce - Code for the reply token * @returns Message text (never contains `SMOKE-ok-` verbatim) */ export declare function buildSmokeTask(file: string, nonce: string): string; /** Runs smoke tests and keeps their jobs. */ export declare class RuntimeSmokeTestService { private readonly deps; private readonly jobs; private readonly running; private readonly now; private readonly sleep; /** * @param deps - Dependencies */ constructor(deps: SmokeTestDeps); /** * Start a smoke test (one per runtime at a time). * * @param runtime - Runtime id * @param opts - `ownerInitiated`: the owner pressed Test (a Terms screen then asks again even after "Don't agree") * @returns The job and a promise of its result * @throws Error for an unknown runtime */ start(runtime: string, opts?: { ownerInitiated?: boolean; }): { job: SmokeTestJob; done: Promise; }; /** * A job by id. * * @param jobId - Job id * @returns Job, or null */ get(jobId: string): SmokeTestJob | null; private prune; /** * Run one smoke test. * * @param runtime - Runtime id * @returns Result (never throws) */ run(runtime: string): Promise; /** Remove a team / project left over from an earlier run. */ private removeLeftovers; private result; } /** Fetch-based client for the local API (the default {@link SmokeApi}). */ export declare class LocalSmokeApi implements SmokeApi { private readonly baseUrl; private readonly token; /** * @param baseUrl - e.g. http://127.0.0.1:8787 * @param token - Crewly API token */ constructor(baseUrl: () => string, token: () => string); private call; listTeams(): Promise>; listProjects(): Promise>; createProject(name: string, projectPath: string): Promise; deleteProject(projectId: string): Promise; createTeam(body: Record): Promise<{ id: string; memberId: string; }>; startMember(teamId: string, memberId: string): Promise<{ ok: boolean; error?: string; }>; getMember(teamId: string, memberId: string): Promise<{ agentStatus: string; sessionName: string; } | null>; capture(sessionName: string, lines: number): Promise; sessionLog(sessionName: string, lines: number): Promise; deliver(sessionName: string, message: string): Promise<{ ok: boolean; error?: string; }>; killSession(sessionName: string): Promise; sessionExists(sessionName: string): Promise; stopTeam(teamId: string): Promise; deleteTeam(teamId: string): Promise; } //# sourceMappingURL=runtime-smoke-test.service.d.ts.map