Files
zopu-code/scripts/zopu-smoke.ts
-Puter a11ed988fb feat(dogfood): integrate and document complete v0 loop
Extend the smoke harness with Orb execution-plane preflight probes
(Docker, OpenCode, Gitea reachability/creds/PR-creation, repository
writable) that report BLOCKED with exact reasons when dependencies are
absent. Add scripts/orb-project-run.ts as the missing connection point
between OrbProjectManager and the Orb/Git lifecycle. Write
docs/DOGFOOD_V0.md covering architecture, startup order, env vars,
local/server flows, demo procedure, health checks, failure recovery,
cleanup, known limitations, and next milestones.

The smoke harness now runs 18 preflight checks across control-plane and
Orb execution-plane layers, all with stable markers and sanitized JSON
reports. The orb:run script (ORB_RUN=1) drives one issue through the
full Orb project-manager lifecycle.
2026-07-25 02:15:41 +05:30

1081 lines
30 KiB
TypeScript

import { parseArgs } from "node:util";
import { api } from "@code/backend/convex/_generated/api";
import { parseSmokeEnv } from "@code/env/smoke";
import type { SmokeEnv } from "@code/env/smoke";
import {
advanceSmokeLoop,
decodeSmokeChatText,
decodeSmokeModelCatalog,
decodeSmokePlan,
decodeSmokeReview,
decodeSmokeWorkerReport,
initialSmokeLoopState,
redactSmokeText,
selectSmokeModelRoles,
validateSmokeCapabilities,
} from "@code/primitives/smoke";
import type {
SmokeCapabilityCheck,
SmokeLoopState,
SmokeModelRoles,
} from "@code/primitives/smoke";
import { createFlueClient } from "@flue/sdk";
import { ConvexHttpClient } from "convex/browser";
import { Effect } from "effect";
const DEFAULT_TIMEOUT_MS = 120_000;
const REQUIRED_ARTIFACTS = [
"agent.md",
"work.md",
"steps.md",
"artifacts.md",
"signals.md",
"agent-manager.md",
"context.md",
"card.md",
] as const;
type SmokeStatus =
| "preflight-passed"
| "completed"
| "contract-blocked"
| "failed";
interface SmokeReport {
readonly checks: readonly SmokeCapabilityCheck[];
readonly featureRequest: string;
readonly issueId?: string;
readonly loop: SmokeLoopState;
readonly mode: "preflight" | "run";
readonly roles?: Pick<SmokeModelRoles, "plannerReviewer" | "worker">;
readonly review?: {
readonly approved: boolean;
readonly findingsCount: number;
};
readonly status: SmokeStatus;
readonly worker?: {
readonly status: "completed" | "blocked" | "failed";
};
}
interface ChatMessage {
readonly content: string;
readonly role: "system" | "user";
}
const { values, positionals } = parseArgs({
allowPositionals: true,
args: process.argv.slice(2),
options: {
help: { default: false, short: "h", type: "boolean" },
report: { type: "string" },
run: { default: false, type: "boolean" },
"timeout-ms": { default: String(DEFAULT_TIMEOUT_MS), type: "string" },
},
strict: true,
});
const usage = `Usage: bun run smoke:zopu [--run] [--report <path>] [--timeout-ms <ms>]
Without --run, the command probes the control-plane (web/Convex/Flue/AgentOS/CPA)
and the Orb execution-plane (Docker/OpenCode/Gitea/repository) contracts.
With --run, it creates one issue in the explicit smoke project, drives the worker, and sends the result to the GLM planner/reviewer.
Required environment:
ZOPU_SMOKE_ACCESS_TOKEN Better Auth/Convex bearer token
ZOPU_SMOKE_PROJECT_ID disposable/test project id
ZOPU_SMOKE_CPA_API_KEY CPA key (or AGENT_MODEL_API_KEY)
ZOPU_SMOKE_CPA_BASE_URL CPA OpenAI-compatible /v1 URL (or AGENT_MODEL_BASE_URL)
ZOPU_SMOKE_CONVEX_URL Convex URL (or CONVEX_URL)
ZOPU_SMOKE_DAEMON_ID online local daemon id (or DAEMON_ID)
Optional environment:
ZOPU_SMOKE_FEATURE_REQUEST override the tiny feature request
ZOPU_SMOKE_WEB_URL default: SITE_URL or http://localhost:5173
ZOPU_SMOKE_FLUE_URL default: VITE_FLUE_URL or http://localhost:3583
Orb execution-plane environment (probed and reported as BLOCKED when absent):
GITEA_URL self-hosted Gitea base URL
GITEA_TOKEN Gitea API token for PR creation
ZOPU_SMOKE_REPOSITORY_PATH owner/repo slug for the PR-creation probe
ORB_DOCKER_IMAGE optional Docker image override
ORB_DOCKER_WORKSPACE optional Orb workspace root (default /tmp/orb-*)
`;
if (values.help || positionals.length > 0) {
console.log(usage);
process.exit(positionals.length > 0 ? 1 : 0);
}
const runEffect = <A, E>(effect: Effect.Effect<A, E>): A =>
Effect.runSync(effect);
const errorMessage = (error: unknown, secrets: readonly string[]): string => {
const message = error instanceof Error ? error.message : String(error);
return redactSmokeText(message, secrets).replaceAll(/\s+/gu, " ").trim();
};
const withTimeout = (timeoutMs: number): AbortSignal =>
AbortSignal.timeout(timeoutMs);
const normalizeBaseUrl = (value: string): URL => {
const url = new URL(value);
if (!url.pathname.endsWith("/")) {
url.pathname = `${url.pathname}/`;
}
return url;
};
const endpoint = (baseUrl: URL, path: string): string =>
new URL(path, baseUrl).toString();
const addCheck = (
checks: SmokeCapabilityCheck[],
id: string,
passed: boolean,
message: string
) => {
checks.push({ id, message, passed });
};
const probeHttp = async (
checks: SmokeCapabilityCheck[],
id: string,
label: string,
url: string,
timeoutMs: number
): Promise<void> => {
try {
const response = await fetch(url, {
redirect: "manual",
signal: withTimeout(timeoutMs),
});
const passed = response.status >= 200 && response.status < 400;
addCheck(
checks,
id,
passed,
passed
? `${label} responded with HTTP ${response.status}`
: `${label} returned HTTP ${response.status}`
);
} catch (error) {
addCheck(
checks,
id,
false,
`${label} is unreachable: ${errorMessage(error, [])}`
);
}
};
const callOpenAiChat = async (input: {
readonly apiKey: string;
readonly baseUrl: URL;
readonly messages: readonly ChatMessage[];
readonly model: string;
readonly timeoutMs: number;
}): Promise<string> => {
const response = await fetch(endpoint(input.baseUrl, "chat/completions"), {
body: JSON.stringify({
max_tokens: 512,
messages: input.messages,
model: input.model,
temperature: 0,
}),
headers: {
Authorization: `Bearer ${input.apiKey}`,
"Content-Type": "application/json",
},
method: "POST",
signal: withTimeout(input.timeoutMs),
});
if (!response.ok) {
throw new Error(
`CPA ${input.model} completion returned HTTP ${response.status}`
);
}
const body = (await response.json()) as unknown;
return await Effect.runPromise(decodeSmokeChatText(body));
};
const parseJsonObject = (text: string): unknown => {
const withoutFence = text
.trim()
.replace(/^```(?:json)?\s*/iu, "")
.replace(/\s*```$/u, "");
const start = withoutFence.indexOf("{");
const end = withoutFence.lastIndexOf("}");
if (start === -1 || end <= start) {
throw new Error("model response did not contain a JSON object");
}
return JSON.parse(withoutFence.slice(start, end + 1)) as unknown;
};
const buildPlanPrompt = (featureRequest: string): readonly ChatMessage[] => [
{
content:
"You are the planner/reviewer for a disposable Zopu web-project smoke. Return JSON only with summary, steps, acceptanceCriteria, and nonGoals. Keep the plan tiny and testable.",
role: "system",
},
{ content: featureRequest, role: "user" },
];
const buildWorkerPrompt = (input: {
readonly featureRequest: string;
readonly plan: {
readonly acceptanceCriteria: readonly string[];
readonly nonGoals: readonly string[];
readonly steps: readonly string[];
readonly summary: string;
};
}): string => `Execute this one disposable web-project smoke issue.
Feature request: ${input.featureRequest}
Plan:
${JSON.stringify(input.plan)}
Rules:
- Work only in the bound project workspace.
- Use one tool call at a time; never issue parallel tool calls because this worker is minimax-m3.
- Inspect the bound repository before editing.
- Run the narrowest relevant test or check.
- Do not claim completion without observed changed files and command evidence.
- Return JSON only: {"status":"completed"|"blocked"|"failed","summary":"..."}`;
const buildReviewPrompt = (input: {
readonly featureRequest: string;
readonly plan: unknown;
readonly workerReport: unknown;
}): readonly ChatMessage[] => [
{
content:
"You are the independent GLM-5.2 planner/reviewer. Review the worker report against the feature request and plan. Return JSON only with approved, summary, and findings (an array of concise strings). Reject missing evidence or a non-completed worker.",
role: "system",
},
{
content: JSON.stringify({
featureRequest: input.featureRequest,
plan: input.plan,
workerReport: input.workerReport,
}),
role: "user",
},
];
const printReport = (report: SmokeReport): void => {
console.log(`ZOPU_SMOKE_${report.status.toUpperCase().replaceAll("-", "_")}`);
console.log(JSON.stringify(report, null, 2));
};
const writeReport = async (
path: string | undefined,
report: SmokeReport
): Promise<void> => {
if (path !== undefined) {
await Bun.write(path, `${JSON.stringify(report, null, 2)}\n`);
}
};
const waitForIssueStatus = async (
convex: ConvexHttpClient,
projectId: string,
issueId: string,
timeoutMs: number
): Promise<string | null> => {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
// oxlint-disable-next-line no-await-in-loop -- polling intentionally waits between durable reads.
const issues = await convex.query(api.projectIssues.list, {
projectId: projectId as never,
});
const issue = issues.find((candidate) => candidate._id === issueId);
if (issue?.status === "completed" || issue?.status === "failed") {
return issue.status;
}
// oxlint-disable-next-line no-await-in-loop -- polling intentionally waits between durable reads.
await Bun.sleep(500);
}
return null;
};
const probeModelRoles = (
env: SmokeEnv,
cpaBaseUrl: URL,
checks: SmokeCapabilityCheck[],
secrets: readonly string[]
): SmokeModelRoles | undefined => {
try {
const roles = runEffect(
selectSmokeModelRoles({
plannerReviewerModel: "glm-5.2",
plannerReviewerProvider: "cheaptricks",
workerModel: env.agentModelName ?? "",
workerProvider: env.agentModelProvider ?? "cheaptricks",
workerToolCallMode: "serial",
})
);
addCheck(
checks,
"model-roles",
true,
"worker=minimax-m3; planner-reviewer=glm-5.2"
);
const workerGatewayMatches =
env.agentModelBaseUrl !== undefined &&
normalizeBaseUrl(env.agentModelBaseUrl).toString() ===
cpaBaseUrl.toString();
addCheck(
checks,
"worker-gateway-config",
workerGatewayMatches,
workerGatewayMatches
? "the local worker points at the configured CPA base URL"
: "AGENT_MODEL_BASE_URL must match the CPA base URL; the harness will not rewrite it"
);
return roles;
} catch (error) {
addCheck(checks, "model-roles", false, errorMessage(error, secrets));
addCheck(
checks,
"worker-gateway-config",
false,
"worker gateway configuration could not be checked because the model role contract failed"
);
return undefined;
}
};
const probeControlPlane = async (input: {
readonly checks: SmokeCapabilityCheck[];
readonly convex: ConvexHttpClient;
readonly daemonId: string;
readonly projectId: string | undefined;
readonly secrets: readonly string[];
}): Promise<void> => {
const { checks, convex, daemonId, projectId, secrets } = input;
try {
const health = await convex.query(api.healthCheck.get, {});
addCheck(
checks,
"convex",
health === "OK",
health === "OK"
? "Convex health query returned OK"
: "Convex health query returned an unexpected value"
);
} catch (error) {
addCheck(
checks,
"convex",
false,
`Convex health query failed: ${errorMessage(error, secrets)}`
);
}
try {
const organization = await convex.query(api.organizations.getCurrent, {});
addCheck(
checks,
"organization",
organization !== null,
organization
? "authenticated personal organization is available"
: "no personal organization; sign in through the web app before running the smoke"
);
} catch (error) {
addCheck(
checks,
"organization",
false,
`organization query failed: ${errorMessage(error, secrets)}`
);
}
if (projectId === undefined) {
addCheck(
checks,
"project-contract",
false,
"ZOPU_SMOKE_PROJECT_ID is required and must point at a disposable/test project"
);
} else {
// eslint-disable-next-line no-use-before-define -- the project probe is a focused helper below the control-plane flow.
await probeProjectContract({ checks, convex, projectId, secrets });
}
try {
const daemon = await convex.query(api.daemons.get, { daemonId });
const online = daemon.presence?.status === "online";
addCheck(
checks,
"agentos-daemon",
online,
online
? `daemon ${daemonId} is online`
: `daemon ${daemonId} is not online; start the local AgentOS daemon without changing CPA`
);
} catch (error) {
addCheck(
checks,
"agentos-daemon",
false,
`daemon probe failed: ${errorMessage(error, secrets)}`
);
}
};
const probeProjectContract = async (input: {
readonly checks: SmokeCapabilityCheck[];
readonly convex: ConvexHttpClient;
readonly projectId: string;
readonly secrets: readonly string[];
}): Promise<void> => {
const { checks, convex, projectId, secrets } = input;
try {
const project = await convex.query(api.projects.get, {
projectId: projectId as never,
});
const hasSource = (project?.sources.length ?? 0) > 0;
let projectMessage =
"project has no connected repository source; land the project backend lane first";
if (project === null) {
projectMessage =
"project was not found or is not accessible to the supplied token";
} else if (hasSource) {
projectMessage = "project source is available";
}
addCheck(
checks,
"project-contract",
project !== null && hasSource,
projectMessage
);
const artifacts = await convex.query(api.projectArtifacts.list, {
projectId: projectId as never,
});
const paths = new Set(artifacts.map((artifact) => artifact.path));
const missing = REQUIRED_ARTIFACTS.filter((path) => !paths.has(path));
addCheck(
checks,
"agentos-workspace-contract",
missing.length === 0,
missing.length === 0
? "issue-scoped AgentOS artifact contract is present"
: `missing AgentOS artifacts: ${missing.join(", ")}; land the project loop lane first`
);
} catch (error) {
addCheck(
checks,
"project-contract",
false,
`project query failed: ${errorMessage(error, secrets)}`
);
addCheck(
checks,
"agentos-workspace-contract",
false,
"project artifacts could not be inspected"
);
}
};
const probeFlue = async (input: {
readonly accessToken: string;
readonly checks: SmokeCapabilityCheck[];
readonly flueUrl: string;
readonly secrets: readonly string[];
}): Promise<ReturnType<typeof createFlueClient> | undefined> => {
const { accessToken, checks, flueUrl, secrets } = input;
const flueClient = createFlueClient({
baseUrl: flueUrl,
headers: { Authorization: `Bearer ${accessToken}` },
});
try {
await flueClient.agents.history(
"project-manager",
"zopu-smoke-capability-probe"
);
addCheck(
checks,
"flue",
true,
"project-manager history endpoint is reachable"
);
return flueClient;
} catch (error) {
const message = errorMessage(error, secrets);
const streamMissing = message.includes("[stream_not_found]");
addCheck(
checks,
"flue",
streamMissing,
streamMissing
? "project-manager history route is reachable; the probe stream is absent as expected"
: `Flue project-manager endpoint failed: ${message}`
);
return streamMissing ? flueClient : undefined;
}
};
const probeGateway = async (input: {
readonly apiKey: string;
readonly baseUrl: URL;
readonly checks: SmokeCapabilityCheck[];
readonly secrets: readonly string[];
readonly timeoutMs: number;
}): Promise<void> => {
const { apiKey, baseUrl, checks, secrets, timeoutMs } = input;
try {
const response = await fetch(endpoint(baseUrl, "models"), {
headers: { Authorization: `Bearer ${apiKey}` },
signal: withTimeout(timeoutMs),
});
if (!response.ok) {
throw new Error(`CPA models returned HTTP ${response.status}`);
}
const catalog = await Effect.runPromise(
decodeSmokeModelCatalog(await response.json())
);
const modelIds = new Set(catalog.data.map((entry) => entry.id));
const missing = ["minimax-m3", "glm-5.2"].filter(
(model) => !modelIds.has(model)
);
addCheck(
checks,
"cpa-model-catalog",
missing.length === 0,
missing.length === 0
? "CPA exposes minimax-m3 and glm-5.2"
: `CPA is missing canonical models: ${missing.join(", ")}`
);
} catch (error) {
addCheck(
checks,
"cpa-model-catalog",
false,
`CPA model probe failed: ${errorMessage(error, secrets)}`
);
}
for (const model of ["minimax-m3", "glm-5.2"] as const) {
try {
// oxlint-disable-next-line no-await-in-loop -- M3 must never be probed in parallel with another worker call.
const text = await callOpenAiChat({
apiKey,
baseUrl,
messages: [{ content: "Reply with exactly OK.", role: "user" }],
model,
timeoutMs,
});
addCheck(
checks,
`cpa-completion-${model}`,
text.trim().length > 0,
`${model} accepted a no-tools OpenAI-compatible completion`
);
} catch (error) {
addCheck(
checks,
`cpa-completion-${model}`,
false,
errorMessage(error, secrets)
);
}
}
};
// ---------------------------------------------------------------------------
// Orb-path preflight probes — Docker, OpenCode, Git/Gitea, repository writable
//
// These probes check the Orb execution-plane dependencies that the
// OrbProjectManager relies on. They are intentionally separate from the
// control-plane/gateway probes: a missing Docker daemon or Gitea token
// reports as a BLOCKED preflight item, never a fake success.
// ---------------------------------------------------------------------------
const probeDocker = async (checks: SmokeCapabilityCheck[]): Promise<void> => {
try {
const proc = Bun.spawn(
["docker", "version", "--format", "{{.Server.Version}}"],
{ stderr: "pipe", stdout: "pipe" }
);
const stdout = await new Response(proc.stdout).text();
const exitCode = await proc.exited;
const version = stdout.trim();
if (exitCode === 0 && version.length > 0) {
addCheck(
checks,
"docker-daemon",
true,
`Docker server version ${version}`
);
} else {
addCheck(
checks,
"docker-daemon",
false,
"Docker daemon is not running; start Docker Desktop or dockerd"
);
}
} catch (error) {
addCheck(
checks,
"docker-daemon",
false,
`Docker CLI unavailable: ${errorMessage(error, [])}`
);
}
};
const probeOpenCodePackage = async (
checks: SmokeCapabilityCheck[]
): Promise<void> => {
try {
await import("@agentos-software/opencode");
addCheck(
checks,
"opencode-package",
true,
"@agentos-software/opencode is resolvable"
);
} catch {
addCheck(
checks,
"opencode-package",
false,
"@agentos-software/opencode is not installed; Orb AgentOS VM cannot link OpenCode"
);
}
};
const probeGitea = async (input: {
readonly checks: SmokeCapabilityCheck[];
readonly giteaToken: string | undefined;
readonly giteaUrl: string | undefined;
readonly repositoryPath: string | undefined;
readonly secrets: readonly string[];
readonly timeoutMs: number;
}): Promise<void> => {
const { checks, giteaToken, giteaUrl, repositoryPath, secrets, timeoutMs } =
input;
if (giteaUrl === undefined) {
addCheck(
checks,
"gitea-reachable",
false,
"GITEA_URL is not set; set it to the self-hosted Gitea base URL for the Orb Git lifecycle"
);
} else {
try {
const response = await fetch(
`${giteaUrl.replace(/\/$/u, "")}/api/v1/version`,
{
headers: giteaToken ? { Authorization: `token ${giteaToken}` } : {},
signal: withTimeout(timeoutMs),
}
);
addCheck(
checks,
"gitea-reachable",
response.ok,
response.ok
? "Gitea API version endpoint is reachable"
: `Gitea API returned HTTP ${response.status}`
);
} catch (error) {
addCheck(
checks,
"gitea-reachable",
false,
`Gitea API is unreachable: ${errorMessage(error, secrets)}`
);
}
}
if (giteaUrl === undefined || giteaToken === undefined) {
addCheck(
checks,
"gitea-creds-valid",
false,
giteaToken === undefined
? "GITEA_TOKEN is not set; the Orb Git lifecycle needs it to create pull requests"
: "Gitea URL is not configured; cannot validate token"
);
} else {
try {
const response = await fetch(
`${giteaUrl.replace(/\/$/u, "")}/api/v1/user`,
{
headers: { Authorization: `token ${giteaToken}` },
signal: withTimeout(timeoutMs),
}
);
addCheck(
checks,
"gitea-creds-valid",
response.ok,
response.ok
? "Gitea token is valid"
: `Gitea token returned HTTP ${response.status}`
);
} catch (error) {
addCheck(
checks,
"gitea-creds-valid",
false,
`Gitea credential probe failed: ${errorMessage(error, secrets)}`
);
}
}
if (
repositoryPath === undefined ||
giteaToken === undefined ||
giteaUrl === undefined
) {
addCheck(
checks,
"gitea-pr-creation",
false,
repositoryPath === undefined
? "ZOPU_SMOKE_REPOSITORY_PATH (owner/repo) is not set; cannot probe PR creation"
: "Gitea URL or token is missing; cannot probe PR creation"
);
} else {
try {
const path = repositoryPath
.split("/")
.map((segment) => encodeURIComponent(segment))
.join("/");
const response = await fetch(
`${giteaUrl.replace(/\/$/u, "")}/api/v1/repos/${path}`,
{
headers: { Authorization: `token ${giteaToken}` },
signal: withTimeout(timeoutMs),
}
);
addCheck(
checks,
"gitea-pr-creation",
response.ok,
response.ok
? `Repository ${repositoryPath} is accessible for PR creation`
: `Repository ${repositoryPath} returned HTTP ${response.status}`
);
} catch (error) {
addCheck(
checks,
"gitea-pr-creation",
false,
`Gitea repository probe failed: ${errorMessage(error, secrets)}`
);
}
}
};
const probeRepositoryWritable = async (
checks: SmokeCapabilityCheck[]
): Promise<void> => {
const { mkdir, rm, writeFile } = await import("node:fs/promises");
const workspace =
process.env.ORB_DOCKER_WORKSPACE ?? "/tmp/orb-smoke-workspace-probe";
try {
await mkdir(workspace, { recursive: true });
const probe = `${workspace}/.orb-write-probe`;
await writeFile(probe, "ok");
await rm(probe);
addCheck(
checks,
"repository-writable",
true,
`Orb workspace root ${workspace} is writable`
);
} catch (error) {
addCheck(
checks,
"repository-writable",
false,
`Orb workspace root is not writable: ${errorMessage(error, [])}`
);
}
};
const probeOrbCapabilities = async (input: {
readonly checks: SmokeCapabilityCheck[];
readonly env: SmokeEnv;
readonly secrets: readonly string[];
readonly timeoutMs: number;
}): Promise<void> => {
const { checks, env, secrets, timeoutMs } = input;
await probeDocker(checks);
await probeOpenCodePackage(checks);
await probeGitea({
checks,
giteaToken: env.giteaToken,
giteaUrl: env.giteaUrl,
repositoryPath: env.repositoryPath,
secrets,
timeoutMs,
});
await probeRepositoryWritable(checks);
};
const runSmoke = async (input: {
readonly baseReport: Omit<SmokeReport, "status">;
readonly convex: ConvexHttpClient;
readonly cpaBaseUrl: URL;
readonly env: SmokeEnv;
readonly flueClient: ReturnType<typeof createFlueClient>;
readonly loop: SmokeLoopState;
readonly roles: SmokeModelRoles;
readonly secrets: readonly string[];
readonly timeoutMs: number;
}): Promise<number> => {
const {
baseReport,
convex,
cpaBaseUrl,
env,
flueClient,
roles,
secrets,
timeoutMs,
} = input;
const { projectId } = env;
if (projectId === undefined) {
throw new Error("run requested without ZOPU_SMOKE_PROJECT_ID");
}
let { loop } = input;
let issueId: string | undefined;
let worker: SmokeReport["worker"];
let review: SmokeReport["review"];
try {
const planText = await callOpenAiChat({
apiKey: env.cpaApiKey,
baseUrl: cpaBaseUrl,
messages: buildPlanPrompt(env.featureRequest),
model: roles.plannerReviewer.model,
timeoutMs,
});
const plan = await Effect.runPromise(
decodeSmokePlan(parseJsonObject(planText))
);
loop = runEffect(advanceSmokeLoop(loop, "plan-validated"));
issueId = await convex.mutation(api.projectIssues.create, {
body: `${env.featureRequest}\n\nSmoke plan:\n${JSON.stringify(plan)}`,
projectId: projectId as never,
title: "Zopu smoke: tiny web feature",
});
await convex.mutation(api.projectIssues.begin, {
issueId: issueId as never,
});
const workerResult = await flueClient.agents.prompt(
"project-manager",
issueId,
{
message: buildWorkerPrompt({
featureRequest: env.featureRequest,
plan,
}),
signal: withTimeout(timeoutMs),
}
);
const workerReport = await Effect.runPromise(
decodeSmokeWorkerReport(parseJsonObject(workerResult.result.text))
);
worker = { status: workerReport.status };
if (workerReport.status !== "completed") {
throw new Error(
`worker returned ${workerReport.status}: ${workerReport.summary}`
);
}
loop = runEffect(advanceSmokeLoop(loop, "worker-succeeded"));
const issueStatus = await waitForIssueStatus(
convex,
projectId,
issueId,
timeoutMs
);
if (issueStatus !== "completed") {
throw new Error(
`worker reported completed but durable issue status is ${issueStatus ?? "missing"}`
);
}
const reviewText = await callOpenAiChat({
apiKey: env.cpaApiKey,
baseUrl: cpaBaseUrl,
messages: buildReviewPrompt({
featureRequest: env.featureRequest,
plan,
workerReport,
}),
model: roles.plannerReviewer.model,
timeoutMs,
});
const reviewReport = await Effect.runPromise(
decodeSmokeReview(parseJsonObject(reviewText))
);
review = {
approved: reviewReport.approved,
findingsCount: reviewReport.findings.length,
};
if (!reviewReport.approved) {
throw new Error(
`GLM review rejected the worker result: ${reviewReport.summary}`
);
}
loop = runEffect(advanceSmokeLoop(loop, "review-approved"));
} catch (error) {
const message = errorMessage(error, secrets);
if (issueId !== undefined) {
await convex.mutation(api.projectIssues.markDispatchFailed, {
error: message,
issueId: issueId as never,
});
}
loop = runEffect(advanceSmokeLoop(loop, "runtime-failed", message));
const report: SmokeReport = {
...baseReport,
issueId,
loop,
status: "failed",
...(review === undefined ? {} : { review }),
...(worker === undefined ? {} : { worker }),
};
await writeReport(values.report, report);
printReport(report);
return 1;
}
const report: SmokeReport = {
...baseReport,
issueId,
loop,
review,
status: "completed",
worker,
};
await writeReport(values.report, report);
printReport(report);
return 0;
};
const main = async (): Promise<number> => {
const timeoutMs = Number(values["timeout-ms"] ?? DEFAULT_TIMEOUT_MS);
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
console.error(
"ZOPU_SMOKE_CONTRACT_BLOCKED: --timeout-ms must be a positive integer"
);
return 2;
}
let env: SmokeEnv;
try {
env = parseSmokeEnv(process.env);
} catch (error) {
console.error(
`ZOPU_SMOKE_CONTRACT_BLOCKED: environment contract failed: ${errorMessage(error, [])}`
);
return 2;
}
const secrets = [env.accessToken, env.cpaApiKey];
const checks: SmokeCapabilityCheck[] = [];
const convex = new ConvexHttpClient(env.convexUrl);
convex.setAuth(env.accessToken);
const cpaBaseUrl = normalizeBaseUrl(env.cpaBaseUrl);
const roles = probeModelRoles(env, cpaBaseUrl, checks, secrets);
await probeHttp(checks, "web", "web app", env.webUrl, timeoutMs);
await probeControlPlane({
checks,
convex,
daemonId: env.daemonId,
projectId: env.projectId,
secrets,
});
const flueClient = await probeFlue({
accessToken: env.accessToken,
checks,
flueUrl: env.flueUrl,
secrets,
});
await probeGateway({
apiKey: env.cpaApiKey,
baseUrl: cpaBaseUrl,
checks,
secrets,
timeoutMs,
});
await probeOrbCapabilities({
checks,
env,
secrets,
timeoutMs,
});
const initialLoop = initialSmokeLoopState();
let loop: SmokeLoopState;
try {
runEffect(validateSmokeCapabilities(checks));
loop = runEffect(advanceSmokeLoop(initialLoop, "preflight-passed"));
} catch (error) {
loop = runEffect(
advanceSmokeLoop(
initialLoop,
"contract-failed",
errorMessage(error, secrets)
)
);
}
const baseReport = {
checks,
featureRequest: redactSmokeText(env.featureRequest, secrets),
loop,
mode: values.run ? ("run" as const) : ("preflight" as const),
...(roles === undefined ? {} : { roles }),
};
if (loop.phase === "failed") {
const report = { ...baseReport, status: "contract-blocked" as const };
await writeReport(values.report, report);
printReport(report);
return 2;
}
if (!values.run) {
const report = { ...baseReport, status: "preflight-passed" as const };
await writeReport(values.report, report);
printReport(report);
return 0;
}
if (roles === undefined || flueClient === undefined) {
throw new Error("run prerequisites were not retained after preflight");
}
return await runSmoke({
baseReport,
convex,
cpaBaseUrl,
env,
flueClient,
loop,
roles,
secrets,
timeoutMs,
});
};
const exitCode = await main().catch((error: unknown) => {
console.error(`ZOPU_SMOKE_FAILED: ${errorMessage(error, [])}`);
return 1;
});
process.exitCode = exitCode;